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
@@ -6,9 +6,9 @@ namespace Barotrauma.Networking
{
partial class ChatMessage
{
public virtual void ClientWrite(IWriteMessage msg)
public virtual void ClientWrite(in SegmentTableWriter<ClientNetSegment> segmentTableWriter, IWriteMessage msg)
{
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
segmentTableWriter.StartNewSegment(ClientNetSegment.ChatMessage);
msg.WriteUInt16(NetStateID);
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
msg.WriteRangedInteger((int)ChatMode, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
@@ -1,12 +1,15 @@
using System.Diagnostics;
using System.IO.Pipes;
using System.Linq;
using System.Threading;
namespace Barotrauma.Networking
{
static partial class ChildServerRelay
{
public static Process Process;
public static bool IsProcessAlive => Process is { HasExited: false };
private static bool localHandlesDisposed;
private static AnonymousPipeServerStream writePipe;
private static AnonymousPipeServerStream readPipe;
@@ -44,18 +47,27 @@ namespace Barotrauma.Networking
localHandlesDisposed = true;
}
public static void ClosePipes()
public static void AttemptGracefulShutDown(int maxAttempts = 20)
{
writePipe?.Dispose(); writePipe = null;
readPipe?.Dispose(); readPipe = null;
shutDown = true;
status = StatusEnum.RequestedShutDown;
writeManualResetEvent?.Set();
int checks = 0;
while (Process is { HasExited: false })
{
if (checks >= maxAttempts)
{
DebugConsole.AddWarning("Server could not be shut down gracefully");
break;
}
Thread.Sleep(100);
checks++;
}
ForceShutDown();
}
public static void ShutDown()
public static void ForceShutDown()
{
Process?.Kill(); Process = null;
writePipe = null; readPipe = null;
PrivateShutDown();
}
@@ -14,6 +14,16 @@ namespace Barotrauma.Networking
set;
}
private SoundChannel radioNoiseChannel;
private float radioNoise;
public float RadioNoise
{
get { return radioNoise; }
set { radioNoise = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
private bool mutedLocally;
public bool MutedLocally
{
@@ -42,35 +52,64 @@ namespace Barotrauma.Networking
!HasPermission(ClientPermissions.Kick) &&
!HasPermission(ClientPermissions.Unban);
public void UpdateSoundPosition()
public void UpdateVoipSound()
{
if (VoipSound == null) { return; }
if (!VoipSound.IsPlaying)
if (VoipSound == null || !VoipSound.IsPlaying)
{
DebugConsole.Log("Destroying voipsound");
VoipSound.Dispose();
radioNoiseChannel?.Dispose();
radioNoiseChannel = null;
if (VoipSound != null)
{
DebugConsole.Log("Destroying voipsound");
VoipSound.Dispose();
}
VoipSound = null;
return;
return;
}
if (Screen.Selected is ModDownloadScreen)
{
VoipSound.Gain = 0.0f;
}
float gain = 1.0f;
float noiseGain = 0.0f;
Vector3? position = null;
if (character != null)
{
if (GameSettings.CurrentConfig.Audio.UseDirectionalVoiceChat)
{
VoipSound.SetPosition(new Vector3(character.WorldPosition.X, character.WorldPosition.Y, 0.0f));
position = new Vector3(character.WorldPosition.X, character.WorldPosition.Y, 0.0f);
}
else
{
VoipSound.SetPosition(null);
float dist = Vector3.Distance(new Vector3(character.WorldPosition, 0.0f), GameMain.SoundManager.ListenerPosition);
VoipSound.Gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist);
gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist);
}
if (RadioNoise > 0.0f)
{
noiseGain = gain * RadioNoise;
gain *= 1.0f - RadioNoise;
}
}
else
VoipSound.SetPosition(position);
VoipSound.Gain = gain;
if (noiseGain > 0.0f)
{
VoipSound.SetPosition(null);
VoipSound.Gain = 1.0f;
if (radioNoiseChannel == null || !radioNoiseChannel.IsPlaying)
{
radioNoiseChannel = SoundPlayer.PlaySound("radiostatic");
radioNoiseChannel.Category = "voip";
radioNoiseChannel.Looping = true;
}
radioNoiseChannel.Near = VoipSound.Near;
radioNoiseChannel.Far = VoipSound.Far;
radioNoiseChannel.Position = position;
radioNoiseChannel.Gain = noiseGain;
}
else if (radioNoiseChannel != null)
{
radioNoiseChannel.Gain = 0.0f;
}
}
@@ -158,6 +197,11 @@ namespace Barotrauma.Networking
VoipSound.Dispose();
VoipSound = null;
}
if (radioNoiseChannel != null)
{
radioNoiseChannel.Dispose();
radioNoiseChannel = null;
}
}
}
}
@@ -311,6 +311,12 @@ namespace Barotrauma.Networking
CoroutineManager.StartCoroutine(WaitForStartingInfo(), "WaitForStartingInfo");
}
public void SetLobbyPublic(bool isPublic)
{
GameMain.NetLobbyScreen.SetPublic(isPublic);
SteamManager.SetLobbyPublic(isPublic);
}
private ClientPeer CreateNetPeer()
{
Networking.ClientPeer.Callbacks callbacks = new ClientPeer.Callbacks(
@@ -326,10 +332,27 @@ namespace Barotrauma.Networking
};
}
public void CreateServerCrashMessage()
{
// Close any message boxes that say "The server has crashed."
var basicServerCrashMsg = TextManager.Get($"{nameof(DisconnectReason)}.{nameof(DisconnectReason.ServerCrashed)}");
GUIMessageBox.MessageBoxes
.OfType<GUIMessageBox>()
.Where(mb => mb.Text?.Text == basicServerCrashMsg)
.ToArray()
.ForEach(mb => mb.Close());
// Open a new message box with the crash report path
if (GUIMessageBox.MessageBoxes.All(
mb => (mb as GUIMessageBox)?.Text?.Text != ChildServerRelay.CrashMessage))
{
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += ReturnToPreviousMenu;
}
}
private bool ReturnToPreviousMenu(GUIButton button, object obj)
{
Quit();
Submarine.Unload();
GameMain.Client = null;
GameMain.GameSession = null;
@@ -443,7 +466,7 @@ namespace Barotrauma.Networking
foreach (Client c in ConnectedClients)
{
if (c.Character != null && c.Character.Removed) { c.Character = null; }
c.UpdateSoundPosition();
c.UpdateVoipSound();
}
if (VoipCapture.Instance != null)
@@ -479,15 +502,11 @@ namespace Barotrauma.Networking
}
catch (Exception e)
{
string errorMsg = "Error while reading a message from server. {" + e + "}. ";
string errorMsg = "Error while reading a message from server. ";
if (GameMain.Client == null) { errorMsg += "Client disposed."; }
errorMsg += "\n" + e.StackTrace.CleanupStackTrace();
if (e.InnerException != null)
{
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace();
}
AppendExceptionInfo(ref errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
DebugConsole.ThrowError("Error while reading a message from server.", e);
DebugConsole.ThrowError(errorMsg);
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", e.TargetSite.ToString())))
{
DisplayInLoadingScreens = true
@@ -529,14 +548,10 @@ namespace Barotrauma.Networking
{
if (GameMain.WindowActive)
{
if (ChildServerRelay.Process?.HasExited ?? true)
if (!ChildServerRelay.IsProcessAlive)
{
Quit();
if (!GUIMessageBox.MessageBoxes.Any(mb => (mb as GUIMessageBox)?.Text?.Text == ChildServerRelay.CrashMessage))
{
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += ReturnToPreviousMenu;
}
CreateServerCrashMessage();
}
}
}
@@ -632,14 +647,8 @@ namespace Barotrauma.Networking
}
catch (Exception e)
{
string errorMsg = "Error while reading an ingame update message from server. {" + e + "}\n" + e.StackTrace.CleanupStackTrace();
if (e.InnerException != null)
{
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace();
}
#if DEBUG
DebugConsole.ThrowError("Error while reading an ingame update message from server.", e);
#endif
string errorMsg = "Error while reading an ingame update message from server.";
AppendExceptionInfo(ref errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw;
}
@@ -868,22 +877,31 @@ namespace Barotrauma.Networking
}
byte missionCount = inc.ReadByte();
if (missionCount != GameMain.GameSession.Missions.Count())
{
string errorMsg = $"Mission equality check failed. Mission count doesn't match the server (server: {missionCount}, client: {GameMain.GameSession.Missions.Count()})";
throw new Exception(errorMsg);
}
List<Identifier> serverMissionIdentifiers = new List<Identifier>();
for (int i = 0; i < missionCount; i++)
{
serverMissionIdentifiers.Add(inc.ReadIdentifier());
}
if (missionCount != GameMain.GameSession.GameMode.Missions.Count())
{
string errorMsg =
$"Mission equality check failed. Mission count doesn't match the server. " +
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsCountMismatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
if (missionCount > 0)
{
if (!GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier).OrderBy(id => id).SequenceEqual(serverMissionIdentifiers.OrderBy(id => id)))
if (!GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier).OrderBy(id => id).SequenceEqual(serverMissionIdentifiers.OrderBy(id => id)))
{
string errorMsg = $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server (server: {string.Join(", ", serverMissionIdentifiers)}, client: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
string errorMsg =
$"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server " +
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
@@ -939,13 +957,6 @@ namespace Barotrauma.Networking
GUI.ClearCursorWait();
ChildServerRelay.ShutDown();
if (SteamManager.IsInitialized)
{
Steamworks.SteamFriends.ClearRichPresence();
}
if (disconnectPacket.ShouldCreateAnalyticsEvent)
{
GameAnalyticsManager.AddErrorEventOnce(
@@ -971,11 +982,43 @@ namespace Barotrauma.Networking
}
else
{
ReturnToPreviousMenu(null, null);
new GUIMessageBox(TextManager.Get(wasConnected ? "ConnectionLost" : "CouldNotConnectToServer"), disconnectPacket.PopupMessage)
if (ClientPeer is SteamP2PClientPeer or SteamP2POwnerPeer)
{
DisplayInLoadingScreens = true
};
SteamManager.LeaveLobby();
}
GameMain.ModDownloadScreen.Reset();
ContentPackageManager.EnabledPackages.Restore();
CampaignMode.StartRoundCancellationToken?.Cancel();
if (SteamManager.IsInitialized)
{
Steamworks.SteamFriends.ClearRichPresence();
}
foreach (var fileTransfer in FileReceiver.ActiveTransfers.ToArray())
{
FileReceiver.StopTransfer(fileTransfer, deleteFile: true);
}
ChildServerRelay.AttemptGracefulShutDown();
GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData is RoundSummary);
characterInfo?.Remove();
VoipClient?.Dispose();
VoipClient = null;
GameMain.Client = null;
GameMain.GameSession = null;
ReturnToPreviousMenu(null, null);
if (disconnectPacket.DisconnectReason != DisconnectReason.Disconnected)
{
new GUIMessageBox(TextManager.Get(wasConnected ? "ConnectionLost" : "CouldNotConnectToServer"), disconnectPacket.PopupMessage)
{
DisplayInLoadingScreens = true
};
}
}
}
@@ -1321,6 +1364,7 @@ namespace Barotrauma.Networking
ServerSettings.MaximumMoneyTransferRequest = inc.ReadInt32();
bool usingShuttle = GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
ServerSettings.ShowEnemyHealthBars = (EnemyHealthBarMode)inc.ReadByte();
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
GameMain.LightManager.LightingEnabled = true;
@@ -1878,12 +1922,11 @@ namespace Barotrauma.Networking
private void ReadLobbyUpdate(IReadMessage inc)
{
ServerNetObject objHeader;
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
SegmentTableReader<ServerNetSegment>.Read(inc, (segment, inc) =>
{
switch (objHeader)
switch (segment)
{
case ServerNetObject.SYNC_IDS:
case ServerNetSegment.SyncIds:
bool lobbyUpdated = inc.ReadBoolean();
inc.ReadPadBits();
@@ -2015,17 +2058,19 @@ namespace Barotrauma.Networking
lastSentChatMsgID = inc.ReadUInt16();
break;
case ServerNetObject.CLIENT_LIST:
case ServerNetSegment.ClientList:
ReadClientList(inc);
break;
case ServerNetObject.CHAT_MESSAGE:
case ServerNetSegment.ChatMessage:
ChatMessage.ClientRead(inc);
break;
case ServerNetObject.VOTE:
case ServerNetSegment.Vote:
Voting.ClientRead(inc);
break;
}
}
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.No;
});
}
readonly List<IServerSerializable> debugEntityList = new List<IServerSerializable>();
@@ -2035,117 +2080,106 @@ namespace Barotrauma.Networking
float sendingTime = inc.ReadSingle() - 0.0f;//TODO: reimplement inc.SenderConnection.RemoteTimeOffset;
ServerNetObject? prevObjHeader = null;
long prevBitPos = 0;
long prevBytePos = 0;
long prevBitLength = 0;
long prevByteLength = 0;
ServerNetObject? objHeader = null;
try
SegmentTableReader<ServerNetSegment>.Read(inc,
segmentDataReader: (segment, inc) =>
{
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
switch (segment)
{
switch (objHeader)
{
case ServerNetObject.SYNC_IDS:
lastSentChatMsgID = inc.ReadUInt16();
LastSentEntityEventID = inc.ReadUInt16();
case ServerNetSegment.SyncIds:
lastSentChatMsgID = inc.ReadUInt16();
LastSentEntityEventID = inc.ReadUInt16();
bool campaignUpdated = inc.ReadBoolean();
inc.ReadPadBits();
if (campaignUpdated)
{
MultiPlayerCampaign.ClientRead(inc);
}
else if (GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign)
{
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
}
break;
case ServerNetObject.ENTITY_POSITION:
inc.ReadPadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly
bool isItem = inc.ReadBoolean(); inc.ReadPadBits();
UInt32 incomingUintIdentifier = inc.ReadUInt32();
UInt16 id = inc.ReadUInt16();
uint msgLength = inc.ReadVariableUInt32();
int msgEndPos = (int)(inc.BitPosition + msgLength * 8);
bool campaignUpdated = inc.ReadBoolean();
inc.ReadPadBits();
if (campaignUpdated)
{
MultiPlayerCampaign.ClientRead(inc);
}
else if (GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign)
{
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
}
break;
case ServerNetSegment.EntityPosition:
inc.ReadPadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly
bool isItem = inc.ReadBoolean(); inc.ReadPadBits();
UInt32 incomingUintIdentifier = inc.ReadUInt32();
UInt16 id = inc.ReadUInt16();
uint msgLength = inc.ReadVariableUInt32();
int msgEndPos = (int)(inc.BitPosition + msgLength * 8);
var entity = Entity.FindEntityByID(id) as IServerPositionSync;
if (msgEndPos > inc.LengthBits)
{
DebugConsole.ThrowError($"Error while reading a position update for the entity \"({entity?.ToString() ?? "null"})\". Message length exceeds the size of the buffer.");
return;
}
var entity = Entity.FindEntityByID(id) as IServerPositionSync;
if (msgEndPos > inc.LengthBits)
{
DebugConsole.ThrowError($"Error while reading a position update for the entity \"({entity?.ToString() ?? "null"})\". Message length exceeds the size of the buffer.");
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.Yes;
}
debugEntityList.Add(entity);
if (entity != null)
debugEntityList.Add(entity);
if (entity != null)
{
if (entity is Item != isItem)
{
if (entity is Item != isItem)
{
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message. Entity type does not match (server entity is {(isItem ? "an item" : "not an item")}, client entity is {(entity?.GetType().ToString() ?? "null")}). Ignoring the message...");
}
else if (entity is MapEntity { Prefab: { UintIdentifier: { } uintIdentifier } } me &&
uintIdentifier != incomingUintIdentifier)
{
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message."
+$"Entity identifier does not match (server entity is {MapEntityPrefab.List.FirstOrDefault(p => p.UintIdentifier == incomingUintIdentifier)?.Identifier.Value ?? "[not found]"}, "
+$"client entity is {me.Prefab.Identifier}). Ignoring the message...");
}
else
{
entity.ClientReadPosition(inc, sendingTime);
}
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message. Entity type does not match (server entity is {(isItem ? "an item" : "not an item")}, client entity is {(entity?.GetType().ToString() ?? "null")}). Ignoring the message...");
}
//force to the correct position in case the entity doesn't exist
//or the message wasn't read correctly for whatever reason
inc.BitPosition = msgEndPos;
inc.ReadPadBits();
break;
case ServerNetObject.CLIENT_LIST:
ReadClientList(inc);
break;
case ServerNetObject.ENTITY_EVENT:
case ServerNetObject.ENTITY_EVENT_INITIAL:
if (!EntityEventManager.Read(objHeader.Value, inc, sendingTime, debugEntityList))
else if (entity is MapEntity { Prefab: { UintIdentifier: { } uintIdentifier } } me &&
uintIdentifier != incomingUintIdentifier)
{
return;
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message."
+$"Entity identifier does not match (server entity is {MapEntityPrefab.List.FirstOrDefault(p => p.UintIdentifier == incomingUintIdentifier)?.Identifier.Value ?? "[not found]"}, "
+$"client entity is {me.Prefab.Identifier}). Ignoring the message...");
}
break;
case ServerNetObject.CHAT_MESSAGE:
ChatMessage.ClientRead(inc);
break;
default:
throw new Exception($"Unknown object header \"{objHeader}\"!)");
}
prevBitLength = inc.BitPosition - prevBitPos;
prevByteLength = inc.BytePosition - prevBytePos;
else
{
entity.ClientReadPosition(inc, sendingTime);
}
}
prevObjHeader = objHeader;
prevBitPos = inc.BitPosition;
prevBytePos = inc.BytePosition;
//force to the correct position in case the entity doesn't exist
//or the message wasn't read correctly for whatever reason
inc.BitPosition = msgEndPos;
inc.ReadPadBits();
break;
case ServerNetSegment.ClientList:
ReadClientList(inc);
break;
case ServerNetSegment.EntityEvent:
case ServerNetSegment.EntityEventInitial:
if (!EntityEventManager.Read(segment, inc, sendingTime, debugEntityList))
{
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.Yes;
}
break;
case ServerNetSegment.ChatMessage:
ChatMessage.ClientRead(inc);
break;
default:
throw new Exception($"Unknown segment \"{segment}\"!)");
}
}
catch (Exception ex)
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.No;
},
exceptionHandler: (segment, prevSegments, ex) =>
{
List<string> errorLines = new List<string>
{
ex.Message,
"Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)",
"Read position: " + inc.BitPosition,
"Header: " + (objHeader != null ? objHeader.Value.ToString() : "Error occurred on the very first header!"),
prevObjHeader != null ? "Previous header: " + prevObjHeader : "Error occurred on the very first header!",
"Previous object was " + (prevBitLength) + " bits long (" + (prevByteLength) + " bytes)",
" "
$"Segment with error: {segment}"
};
if (prevSegments.Any())
{
errorLines.Add("Prev segments: " + string.Join(", ", prevSegments));
errorLines.Add(" ");
}
errorLines.Add(ex.StackTrace.CleanupStackTrace());
errorLines.Add(" ");
if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL ||
objHeader == ServerNetObject.ENTITY_EVENT || objHeader == ServerNetObject.ENTITY_EVENT_INITIAL ||
objHeader == ServerNetObject.ENTITY_POSITION || prevObjHeader == ServerNetObject.ENTITY_POSITION)
if (prevSegments.Concat(segment.ToEnumerable()).Any(s => s.Identifier
is ServerNetSegment.EntityPosition
or ServerNetSegment.EntityEvent
or ServerNetSegment.EntityEventInitial))
{
foreach (IServerSerializable ent in debugEntityList)
{
@@ -2159,34 +2193,18 @@ namespace Barotrauma.Networking
}
}
foreach (string line in errorLines)
{
DebugConsole.ThrowError(line);
}
errorLines.Add("Last console messages:");
for (int i = DebugConsole.Messages.Count - 1; i > Math.Max(0, DebugConsole.Messages.Count - 20); i--)
{
errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text);
}
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsManager.ErrorSeverity.Critical, string.Join("\n", errorLines));
DebugConsole.ThrowError("Writing object data to \"networkerror_data.log\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues");
using (FileStream fl = File.Open("networkerror_data.log", System.IO.FileMode.Create))
{
using (System.IO.BinaryWriter bw = new System.IO.BinaryWriter(fl))
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fl))
{
bw.Write(inc.Buffer, (int)(prevBytePos - prevByteLength), (int)(prevByteLength));
sw.WriteLine("");
foreach (string line in errorLines)
{
sw.WriteLine(line);
}
}
}
throw new Exception("Read error: please send us \"networkerror_data.log\"!");
}
throw new Exception(
$"Exception thrown while reading segment {segment.Identifier} at position {segment.Pointer}." +
(prevSegments.Any() ? $" Previous segments: {string.Join(", ", prevSegments)}" : ""),
ex);
});
}
private void SendLobbyUpdate()
@@ -2194,50 +2212,51 @@ namespace Barotrauma.Networking
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(LastClientListUpdateID);
outmsg.WriteUInt16(nameId);
outmsg.WriteString(Name);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
if (jobPreferences.Count > 0)
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(outmsg))
{
outmsg.WriteIdentifier(jobPreferences[0].Prefab.Identifier);
}
else
{
outmsg.WriteIdentifier(Identifier.Empty);
}
outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference);
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
{
outmsg.WriteUInt16((UInt16)0);
}
else
{
outmsg.WriteUInt16(campaign.LastSaveID);
outmsg.WriteByte(campaign.CampaignID);
foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(LastClientListUpdateID);
outmsg.WriteUInt16(nameId);
outmsg.WriteString(Name);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
if (jobPreferences.Count > 0)
{
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(netFlag));
outmsg.WriteIdentifier(jobPreferences[0].Prefab.Identifier);
}
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
}
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
{
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
else
{
//no more room in this packet
break;
outmsg.WriteIdentifier(Identifier.Empty);
}
chatMsgQueue[i].ClientWrite(outmsg);
}
outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE);
outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference);
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
{
outmsg.WriteUInt16((UInt16)0);
}
else
{
outmsg.WriteUInt16(campaign.LastSaveID);
outmsg.WriteByte(campaign.CampaignID);
foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(netFlag));
}
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
}
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
{
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
{
//no more room in this packet
break;
}
chatMsgQueue[i].ClientWrite(segmentTable, outmsg);
}
}
if (outmsg.LengthBytes > MsgConstants.MTU)
{
DebugConsole.ThrowError($"Maximum packet size exceeded ({outmsg.LengthBytes} > {MsgConstants.MTU})");
@@ -2253,44 +2272,47 @@ namespace Barotrauma.Networking
outmsg.WriteBoolean(EntityEventManager.MidRoundSyncingDone);
outmsg.WritePadBits();
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
outmsg.WriteUInt16(LastClientListUpdateID);
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(outmsg))
{
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
outmsg.WriteUInt16(LastClientListUpdateID);
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
{
outmsg.WriteUInt16((UInt16)0);
}
else
{
outmsg.WriteUInt16(campaign.LastSaveID);
outmsg.WriteByte(campaign.CampaignID);
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
{
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(flag));
outmsg.WriteUInt16((UInt16)0);
}
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
}
Character.Controlled?.ClientWriteInput(outmsg);
GameMain.GameScreen.Cam?.ClientWrite(outmsg);
EntityEventManager.Write(outmsg, ClientPeer?.ServerConnection);
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
{
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
else
{
//not enough room in this packet
break;
}
chatMsgQueue[i].ClientWrite(outmsg);
}
outmsg.WriteUInt16(campaign.LastSaveID);
outmsg.WriteByte(campaign.CampaignID);
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(flag));
}
outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE);
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
}
Character.Controlled?.ClientWriteInput(segmentTable, outmsg);
GameMain.GameScreen.Cam?.ClientWrite(segmentTable, outmsg);
EntityEventManager.Write(segmentTable, outmsg, ClientPeer?.ServerConnection);
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
{
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
{
//not enough room in this packet
break;
}
chatMsgQueue[i].ClientWrite(segmentTable, outmsg);
}
}
if (outmsg.LengthBytes > MsgConstants.MTU)
{
@@ -2523,7 +2545,7 @@ namespace Barotrauma.Networking
public override void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData = null)
{
if (!(entity is IClientSerializable clientSerializable))
if (entity is not IClientSerializable clientSerializable)
{
throw new InvalidCastException($"Entity is not {nameof(IClientSerializable)}");
}
@@ -2557,46 +2579,10 @@ namespace Barotrauma.Networking
public void Quit()
{
GameMain.LuaCs.Stop();
if (ClientPeer is SteamP2PClientPeer || ClientPeer is SteamP2POwnerPeer)
{
SteamManager.LeaveLobby();
}
GameMain.ModDownloadScreen.Reset();
ContentPackageManager.EnabledPackages.Restore();
CampaignMode.StartRoundCancellationToken?.Cancel();
ClientPeer?.Close(PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
ClientPeer = null;
foreach (var fileTransfer in FileReceiver.ActiveTransfers.ToArray())
{
FileReceiver.StopTransfer(fileTransfer, deleteFile: true);
}
if (ChildServerRelay.Process != null)
{
int checks = 0;
while (ChildServerRelay.Process is { HasExited: false })
{
if (checks > 10)
{
ChildServerRelay.ShutDown();
}
Thread.Sleep(100);
checks++;
}
}
ChildServerRelay.ShutDown();
GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData is RoundSummary);
characterInfo?.Remove();
VoipClient?.Dispose();
VoipClient = null;
GameMain.Client = null;
GameMain.GameSession = null;
}
public void SendCharacterInfo(string newName = null)
@@ -2604,7 +2590,6 @@ namespace Barotrauma.Networking
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
WriteCharacterInfo(msg, newName);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
}
@@ -2644,9 +2629,11 @@ namespace Barotrauma.Networking
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
msg.WriteByte((byte)ClientNetObject.VOTE);
Voting.ClientWrite(msg, voteType, data);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(msg))
{
segmentTable.StartNewSegment(ClientNetSegment.Vote);
Voting.ClientWrite(msg, voteType, data);
}
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2762,7 +2749,6 @@ namespace Barotrauma.Networking
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.ManageCampaign);
campaign.ClientWrite(msg);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2811,7 +2797,6 @@ namespace Barotrauma.Networking
msg.WriteUInt16((UInt16)ClientPermissions.SelectSub);
msg.WriteBoolean(isShuttle); msg.WritePadBits();
msg.WriteString(sub.MD5Hash.StringRepresentation);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2831,7 +2816,6 @@ namespace Barotrauma.Networking
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.SelectMode);
msg.WriteUInt16((UInt16)modeIndex);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -3537,6 +3521,23 @@ namespace Barotrauma.Networking
eventErrorWritten = true;
}
private static void AppendExceptionInfo(ref string errorMsg, Exception e)
{
if (!errorMsg.EndsWith("\n")) { errorMsg += "\n"; }
errorMsg += e.Message + "\n";
var innermostException = e.GetInnermost();
if (innermostException != e)
{
// If available, only append the stacktrace of the innermost exception,
// because that's the most important one to fix
errorMsg += "Inner exception: " + innermostException.Message + "\n" + innermostException.StackTrace.CleanupStackTrace();
}
else
{
errorMsg += e.StackTrace.CleanupStackTrace();
}
}
#if DEBUG
public void ForceTimeOut()
{
@@ -66,7 +66,7 @@ namespace Barotrauma.Networking
events.Add(newEvent);
}
public void Write(IWriteMessage msg, NetworkConnection serverConnection)
public void Write(in SegmentTableWriter<ClientNetSegment> segmentTable, IWriteMessage msg, NetworkConnection serverConnection)
{
if (events.Count == 0 || serverConnection == null) return;
@@ -103,7 +103,7 @@ namespace Barotrauma.Networking
eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
}
msg.WriteByte((byte)ClientNetObject.ENTITY_STATE);
segmentTable.StartNewSegment(ClientNetSegment.EntityState);
Write(msg, eventsToSync, out _);
}
@@ -112,11 +112,11 @@ namespace Barotrauma.Networking
/// <summary>
/// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails.
/// </summary>
public bool Read(ServerNetObject type, IReadMessage msg, float sendingTime, List<IServerSerializable> entities)
public bool Read(ServerNetSegment type, IReadMessage msg, float sendingTime, List<IServerSerializable> entities)
{
UInt16 unreceivedEntityEventCount = 0;
if (type == ServerNetObject.ENTITY_EVENT_INITIAL)
if (type == ServerNetSegment.EntityEventInitial)
{
unreceivedEntityEventCount = msg.ReadUInt16();
firstNewID = msg.ReadUInt16();
@@ -218,43 +218,20 @@ namespace Barotrauma.Networking
Microsoft.Xna.Framework.Color.Green);
}
lastReceivedID++;
try
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
if (msg.BitPosition != msgPosition + msgLength * 8)
{
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
var prevEntity = entities.Count >= 2 ? entities[entities.Count - 2] : null;
ushort prevId = prevEntity is Entity p ? p.ID : (ushort)0;
string errorMsg = $"Message byte position incorrect after reading an event for the entity \"{entity}\" (ID {(entity is Entity e ? e.ID : 0)}). "
+$"The previous entity was \"{prevEntity}\" (ID {prevId}) "
+$"Read {msg.BitPosition - msgPosition} bits, expected message length was {msgLength * 8} bits.";
if (msg.BitPosition != msgPosition + msgLength * 8)
{
var prevEntity = entities.Count >= 2 ? entities[entities.Count - 2] : null;
ushort prevId = prevEntity is Entity p ? p.ID : (ushort)0;
string errorMsg = $"Message byte position incorrect after reading an event for the entity \"{entity}\" (ID {(entity is Entity e ? e.ID : 0)}). "
+$"The previous entity was \"{prevEntity}\" (ID {prevId}) "
+$"Read {msg.BitPosition - msgPosition} bits, expected message length was {msgLength * 8} bits.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
//TODO: force the BitPosition to correct place? Having some entity in a potentially incorrect state is not as bad as a desync kick
//msg.BitPosition = (int)(msgPosition + msgLength * 8);
}
}
catch (Exception e)
{
string errorMsg = $"Failed to read event {thisEventID} for entity \"{entity}\"" +
$"{(entity is Entity { ID: var entityId } ? $", id {entityId}" : "")} ";
DebugConsole.ThrowError(errorMsg, e);
errorMsg += $"({e.Message})! (MidRoundSyncing: {thisClient.MidRoundSyncing})\n{e.StackTrace.CleanupStackTrace()}";
errorMsg += "\nPrevious entities:";
for (int j = entities.Count - 2; j >= 0; j--)
{
errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString());
}
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(),
GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
msg.BitPosition = (int)(msgPosition + msgLength * 8);
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
}
}
@@ -277,16 +254,12 @@ namespace Barotrauma.Networking
public void Clear()
{
ID = 0;
lastReceivedID = 0;
firstNewID = null;
events.Clear();
eventLastSent.Clear();
MidRoundSyncingDone = false;
ClearSelf();
}
/// <summary>
@@ -297,6 +270,10 @@ namespace Barotrauma.Networking
{
ID = 0;
events.Clear();
if (thisClient != null)
{
thisClient.LastSentEntityEventID = 0;
}
}
}
}
@@ -4,9 +4,9 @@ namespace Barotrauma.Networking
{
partial class OrderChatMessage : ChatMessage
{
public override void ClientWrite(IWriteMessage msg)
public override void ClientWrite(in SegmentTableWriter<ClientNetSegment> segmentTableWriter, IWriteMessage msg)
{
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
segmentTableWriter.StartNewSegment(ClientNetSegment.ChatMessage);
msg.WriteUInt16(NetStateID);
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
msg.WriteRangedInteger((int)ChatMode.None, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
@@ -91,15 +91,11 @@ namespace Barotrauma.Networking
ToolBox.ThrowIfNull(netClient);
ToolBox.ThrowIfNull(incomingLidgrenMessages);
if (isOwner && !(ChildServerRelay.Process is { HasExited: false }))
if (isOwner && !ChildServerRelay.IsProcessAlive)
{
var gameClient = GameMain.Client;
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += (btn, obj) =>
{
GameMain.MainMenuScreen.Select();
return false;
};
gameClient?.CreateServerCrashMessage();
return;
}
@@ -111,7 +107,7 @@ namespace Barotrauma.Networking
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
{
if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint))
if (!inc.SenderConnection.RemoteEndPoint.EquivalentTo(lidgrenEndpoint.NetEndpoint))
{
DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
continue;
@@ -187,15 +187,11 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
if (ChildServerRelay.HasShutDown || !(ChildServerRelay.Process is { HasExited: false }))
if (ChildServerRelay.HasShutDown || !ChildServerRelay.IsProcessAlive)
{
var gameClient = GameMain.Client;
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += (btn, obj) =>
{
GameMain.MainMenuScreen.Select();
return false;
};
gameClient?.CreateServerCrashMessage();
return;
}
@@ -401,8 +397,6 @@ namespace Barotrauma.Networking
ClosePeerSession(remotePeers[i]);
}
ChildServerRelay.ClosePipes();
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
SteamManager.LeaveLobby();
@@ -14,7 +14,7 @@ namespace Barotrauma.Networking
{
static class PingUtils
{
private static readonly Dictionary<IPAddress, int> activePings = new Dictionary<IPAddress, int>();
private static readonly Dictionary<IPEndPoint, int> activePings = new Dictionary<IPEndPoint, int>();
private static bool steamPingInfoReady;
@@ -36,9 +36,9 @@ namespace Barotrauma.Networking
switch (serverInfo.Endpoint)
{
case LidgrenEndpoint { NetEndpoint: { Address: var address } }:
case LidgrenEndpoint { NetEndpoint: var endPoint }:
GetIPAddressPing(serverInfo, address, onPingDiscovered);
GetIPAddressPing(serverInfo, endPoint, onPingDiscovered);
break;
case SteamP2PEndpoint steamP2PEndpoint:
TaskPool.Add($"EstimateSteamLobbyPing ({steamP2PEndpoint.StringRepresentation})",
@@ -131,9 +131,9 @@ namespace Barotrauma.Networking
}
}
private static void GetIPAddressPing(ServerInfo serverInfo, IPAddress address, Action<ServerInfo> onPingDiscovered)
private static void GetIPAddressPing(ServerInfo serverInfo, IPEndPoint endPoint, Action<ServerInfo> onPingDiscovered)
{
if (IPAddress.IsLoopback(address))
if (IPAddress.IsLoopback(endPoint.Address))
{
serverInfo.Ping = Option<int>.Some(0);
onPingDiscovered(serverInfo);
@@ -142,24 +142,24 @@ namespace Barotrauma.Networking
{
lock (activePings)
{
if (activePings.ContainsKey(address)) { return; }
activePings.Add(address, activePings.Any() ? activePings.Values.Max() + 1 : 0);
if (activePings.ContainsKey(endPoint)) { return; }
activePings.Add(endPoint, activePings.Any() ? activePings.Values.Max() + 1 : 0);
}
serverInfo.Ping = Option<int>.None();
TaskPool.Add($"PingServerAsync ({address})", PingServerAsync(address, 1000),
TaskPool.Add($"PingServerAsync ({endPoint})", PingServerAsync(endPoint, 1000),
rtt =>
{
if (!rtt.TryGetResult(out serverInfo.Ping)) { serverInfo.Ping = Option<int>.None(); }
onPingDiscovered(serverInfo);
lock (activePings)
{
activePings.Remove(address);
activePings.Remove(endPoint);
}
});
}
}
private static async Task<Option<int>> PingServerAsync(IPAddress ipAddress, int timeOut)
private static async Task<Option<int>> PingServerAsync(IPEndPoint endPoint, int timeOut)
{
await Task.Yield();
bool shouldGo = false;
@@ -167,21 +167,21 @@ namespace Barotrauma.Networking
{
lock (activePings)
{
shouldGo = activePings.Count(kvp => kvp.Value < activePings[ipAddress]) < 25;
shouldGo = activePings.Count(kvp => kvp.Value < activePings[endPoint]) < 25;
}
await Task.Delay(25);
}
if (ipAddress == null) { return Option<int>.None(); }
if (endPoint?.Address == null) { return Option<int>.None(); }
//don't attempt to ping if the address is IPv6 and it's not supported
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6 && !Socket.OSSupportsIPv6) { return Option<int>.None(); }
if (endPoint.Address.AddressFamily == AddressFamily.InterNetworkV6 && !Socket.OSSupportsIPv6) { return Option<int>.None(); }
Ping ping = new Ping();
byte[] buffer = new byte[32];
try
{
PingReply pingReply = await ping.SendPingAsync(ipAddress, timeOut, buffer, new PingOptions(128, true));
PingReply pingReply = await ping.SendPingAsync(endPoint.Address, timeOut, buffer, new PingOptions(128, true));
return pingReply.Status switch
{
@@ -191,9 +191,9 @@ namespace Barotrauma.Networking
}
catch (Exception ex)
{
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + ipAddress, GameAnalyticsManager.ErrorSeverity.Warning, "Failed to ping a server - " + (ex?.InnerException?.Message ?? ex.Message));
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + endPoint.Address, GameAnalyticsManager.ErrorSeverity.Warning, "Failed to ping a server - " + (ex?.InnerException?.Message ?? ex.Message));
#if DEBUG
DebugConsole.NewMessage("Failed to ping a server (" + ipAddress + ") - " + (ex?.InnerException?.Message ?? ex.Message), Color.Red);
DebugConsole.NewMessage("Failed to ping a server (" + endPoint.Address + ") - " + (ex?.InnerException?.Message ?? ex.Message), Color.Red);
#endif
return Option<int>.None();
@@ -140,7 +140,7 @@ namespace Barotrauma.Networking
MaxPlayers = incMsg.ReadByte();
HasPassword = incMsg.ReadBoolean();
IsPublic = incMsg.ReadBoolean();
GameMain.NetLobbyScreen.SetPublic(IsPublic);
GameMain.Client?.SetLobbyPublic(IsPublic);
AllowFileTransfers = incMsg.ReadBoolean();
incMsg.ReadPadBits();
TickRate = incMsg.ReadRangedInteger(1, 60);
@@ -367,6 +367,17 @@ namespace Barotrauma.Networking
//***********************************************
//changing server visibility on the fly is not supported in dedicated servers
if (GameMain.Client?.ClientPeer is not LidgrenClientPeer)
{
var isPublic = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform),
TextManager.Get("publicserver"))
{
ToolTip = TextManager.Get("publicservertooltip")
};
GetPropertyData(nameof(IsPublic)).AssignGUIComponent(isPublic);
}
// Sub Selection
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverTab.RectTransform), TextManager.Get("ServerSettingsSubSelection"), font: GUIStyle.SubHeadingFont);
var selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), serverTab.RectTransform), isHorizontal: true)
@@ -475,9 +486,10 @@ namespace Barotrauma.Networking
// game settings
//--------------------------------------------------------------------------------
var roundsTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.Center)) { };
var roundsTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.Center));
var roundsContent = new GUIListBox(new RectTransform(Vector2.One, roundsTab.RectTransform, Anchor.Center), style: "GUIListBoxNoBorder").Content;
GUILayoutGroup playStyleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), roundsTab.RectTransform));
GUILayoutGroup playStyleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), roundsContent.RectTransform));
// Play Style Selection
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), playStyleLayout.RectTransform), TextManager.Get("ServerSettingsPlayStyle"), font: GUIStyle.SubHeadingFont);
var playstyleList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), playStyleLayout.RectTransform))
@@ -502,7 +514,7 @@ namespace Barotrauma.Networking
GUITextBlock.AutoScaleAndNormalize(playStyleTickBoxes.Select(t => t.TextBlock));
playstyleList.RectTransform.MinSize = new Point(0, (int)(playstyleList.Content.Children.First().Rect.Height * 2.0f + playstyleList.Padding.Y + playstyleList.Padding.W));
GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.35f), roundsTab.RectTransform))
GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.35f), roundsContent.RectTransform))
{
Stretch = true
};
@@ -608,7 +620,7 @@ namespace Barotrauma.Networking
};
slider.OnMoved(slider, slider.BarScroll);
GUILayoutGroup losModeLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.14f), roundsTab.RectTransform));
GUILayoutGroup losModeLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.14f), roundsContent.RectTransform));
var losModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), losModeLayout.RectTransform),
TextManager.Get("LosEffect"));
@@ -629,7 +641,30 @@ namespace Barotrauma.Networking
}
GetPropertyData(nameof(LosMode)).AssignGUIComponent(losModeRadioButtonGroup);
GUILayoutGroup numberLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), roundsTab.RectTransform))
GUILayoutGroup healthBarModeLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.14f), roundsContent.RectTransform));
var healthBarModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), healthBarModeLayout.RectTransform),
TextManager.Get("ShowEnemyHealthBars"));
var healthBarModeRadioButtonLayout
= new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), healthBarModeLayout.RectTransform),
isHorizontal: true)
{
Stretch = true
};
var healthBarModeRadioButtonGroup = new GUIRadioButtonGroup();
EnemyHealthBarMode[] healthBarModeModes = Enum.GetValues<EnemyHealthBarMode>();
for (int i = 0; i < healthBarModeModes.Length; i++)
{
var losTick = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), healthBarModeRadioButtonLayout.RectTransform),
TextManager.Get($"ShowEnemyHealthBars.{healthBarModeModes[i]}"),
font: GUIStyle.SmallFont, style: "GUIRadioButton");
healthBarModeRadioButtonGroup.AddRadioButton(i, losTick);
}
GetPropertyData(nameof(ShowEnemyHealthBars)).AssignGUIComponent(healthBarModeRadioButtonGroup);
GUILayoutGroup numberLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), roundsContent.RectTransform))
{
Stretch = true
};
@@ -651,7 +686,7 @@ namespace Barotrauma.Networking
var disableBotConversationsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), numberLayout.RectTransform), TextManager.Get("ServerSettingsDisableBotConversations"));
GetPropertyData(nameof(DisableBotConversations)).AssignGUIComponent(disableBotConversationsBox);
GUILayoutGroup buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), roundsTab.RectTransform), isHorizontal: true)
GUILayoutGroup buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), roundsContent.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
@@ -17,9 +17,7 @@ namespace Barotrauma.Networking
get;
private set;
}
public static IReadOnlyList<string> CaptureDeviceNames =>
Alc.GetStringList(IntPtr.Zero, OpenAL.Alc.CaptureDeviceSpecifier);
private readonly IntPtr captureDevice;
@@ -169,6 +167,11 @@ namespace Barotrauma.Networking
Create(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice, storedBufferID);
}
public static IReadOnlyList<string> GetCaptureDeviceNames()
{
return Alc.GetStringList(IntPtr.Zero, OpenAL.Alc.CaptureDeviceSpecifier);
}
IntPtr nativeBuffer;
readonly short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
readonly short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
@@ -260,6 +263,13 @@ namespace Barotrauma.Networking
}
}
}
if (Screen.Selected is ModDownloadScreen)
{
allowEnqueue = false;
captureTimer = 0;
}
if (allowEnqueue || captureTimer > 0)
{
LastEnqueueAudio = DateTime.Now;
@@ -1,20 +1,23 @@
using Barotrauma.Sounds;
using Barotrauma.Items.Components;
using Barotrauma.Sounds;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Barotrauma.Items.Components;
namespace Barotrauma.Networking
{
class VoipClient : IDisposable
{
private GameClient gameClient;
private ClientPeer netClient;
/// <summary>
/// The "near" range of the voice chat (a percentage of either SpeakRange or radio range), further than this the volume starts to diminish
/// </summary>
const float RangeNear = 0.4f;
private readonly GameClient gameClient;
private readonly ClientPeer netClient;
private DateTime lastSendTime;
private List<VoipQueue> queues;
private readonly List<VoipQueue> queues;
private UInt16 storedBufferID = 0;
@@ -32,13 +35,13 @@ namespace Barotrauma.Networking
public void RegisterQueue(VoipQueue queue)
{
if (queue == VoipCapture.Instance) return;
if (!queues.Contains(queue)) queues.Add(queue);
if (queue == VoipCapture.Instance) { return; }
if (!queues.Contains(queue)) { queues.Add(queue); }
}
public void UnregisterQueue(VoipQueue queue)
{
if (queues.Contains(queue)) queues.Remove(queue);
if (queues.Contains(queue)) { queues.Remove(queue); }
}
public void SendToServer()
@@ -85,6 +88,7 @@ namespace Barotrauma.Networking
public void Read(IReadMessage msg)
{
byte queueId = msg.ReadByte();
float distanceFactor = msg.ReadRangedSingle(0.0f, 1.0f, 8);
VoipQueue queue = queues.Find(q => q.QueueID == queueId);
if (queue == null)
@@ -105,9 +109,12 @@ namespace Barotrauma.Networking
client.VoipSound = new VoipSound(client.Name, GameMain.SoundManager, client.VoipQueue);
}
GameMain.SoundManager.ForceStreamUpdate();
client.RadioNoise = 0.0f;
if (client.Character != null && !client.Character.IsDead && !client.Character.Removed && client.Character.SpeechImpediment <= 100.0f)
{
float speechImpedimentMultiplier = 1.0f - client.Character.SpeechImpediment / 100.0f;
bool spectating = Character.Controlled == null;
float rangeMultiplier = spectating ? 2.0f : 1.0f;
WifiComponent radio = null;
var messageType = !client.VoipQueue.ForceLocal && ChatMessage.CanUseRadio(client.Character, out radio) ? ChatMessageType.Radio : ChatMessageType.Default;
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
@@ -115,11 +122,17 @@ namespace Barotrauma.Networking
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
if (messageType == ChatMessageType.Radio)
{
client.VoipSound.SetRange(radio.Range * 0.8f, radio.Range);
client.VoipSound.SetRange(radio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, radio.Range * speechImpedimentMultiplier * rangeMultiplier);
if (distanceFactor > RangeNear && !spectating)
{
//noise starts increasing exponentially after 40% range
client.RadioNoise = MathF.Pow(MathUtils.InverseLerp(RangeNear, 1.0f, distanceFactor), 2);
}
}
else
{
client.VoipSound.SetRange(ChatMessage.SpeakRange * 0.4f, ChatMessage.SpeakRange);
client.VoipSound.SetRange(ChatMessage.SpeakRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, ChatMessage.SpeakRange * speechImpedimentMultiplier * rangeMultiplier);
}
client.VoipSound.UseMuffleFilter =
messageType != ChatMessageType.Radio && Character.Controlled != null && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters &&