Unstable 1.8.4.0

This commit is contained in:
Markus Isberg
2025-03-12 12:56:27 +00:00
parent a4c3e868e4
commit a4a3427e4e
627 changed files with 29860 additions and 10018 deletions
@@ -120,7 +120,7 @@ namespace Barotrauma.Networking
if (radioNoiseChannel == null || !radioNoiseChannel.IsPlaying)
{
radioNoiseChannel = SoundPlayer.PlaySound("radiostatic");
radioNoiseChannel.Category = "voip";
radioNoiseChannel.Category = SoundManager.SoundCategoryVoip;
radioNoiseChannel.Looping = true;
}
radioNoiseChannel.Near = VoipSound.Near;
@@ -50,25 +50,29 @@ readonly record struct ConnectCommand(
NameAndLidgrenEndpointOption: endpoint is LidgrenEndpoint lidgrenEndpoint
? Option.Some(new NameAndLidgrenEndpoint(ServerName: serverName, lidgrenEndpoint))
: Option.None,
SteamLobbyIdOption: Option.None) { }
SteamLobbyIdOption: Option.None)
{ }
public ConnectCommand(string serverName, ImmutableArray<P2PEndpoint> endpoints)
: this(
NameAndP2PEndpointsOption: Option.Some(new NameAndP2PEndpoints(ServerName: serverName, Endpoints: endpoints)),
NameAndLidgrenEndpointOption: Option.None,
SteamLobbyIdOption: Option.None) { }
SteamLobbyIdOption: Option.None)
{ }
public ConnectCommand(string serverName, LidgrenEndpoint endpoint)
: this(
NameAndP2PEndpointsOption: Option.None,
NameAndLidgrenEndpointOption: Option.Some(new NameAndLidgrenEndpoint(ServerName: serverName, Endpoint: endpoint)),
SteamLobbyIdOption: Option.None) { }
SteamLobbyIdOption: Option.None)
{ }
public ConnectCommand(SteamLobbyId lobbyId)
: this(
NameAndP2PEndpointsOption: Option.None,
NameAndLidgrenEndpointOption: Option.None,
SteamLobbyIdOption: Option.Some(lobbyId)) { }
SteamLobbyIdOption: Option.Some(lobbyId))
{ }
public static Option<ConnectCommand> Parse(string str)
=> Parse(ToolBox.SplitCommand(str));
@@ -260,7 +260,7 @@ namespace Barotrauma.Networking
{
try
{
Directory.CreateDirectory(downloadFolder);
Directory.CreateDirectory(downloadFolder, catchUnauthorizedAccessExceptions: false);
}
catch (Exception e)
{
@@ -572,7 +572,7 @@ namespace Barotrauma.Networking
{
try
{
File.Delete(transfer.FilePath);
File.Delete(transfer.FilePath, catchUnauthorizedAccessExceptions: false);
}
catch (Exception e)
{
File diff suppressed because it is too large Load Diff
@@ -154,13 +154,25 @@ namespace Barotrauma.Networking
//16 = entity ID, 8 = msg length
if (msg.BitPosition + 16 + 8 > msg.LengthBits)
{
string errorMsg = $"Error while reading a message from the server. Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits}).";
UInt16 potentialEntityId = Entity.NullEntityID;
try
{
potentialEntityId = msg.ReadUInt16();
}
catch
{
//failed to read the ID, do nothing (we would've just used it for the error message)
}
Entity targetEntity = Entity.FindEntityByID(potentialEntityId);
string errorMsg = $"Error while reading a message from the server (entity: {targetEntity?.ToString() ?? "unknown"}).";
errorMsg += $" Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits}).";
errorMsg += "\nPrevious entities:";
for (int j = tempEntityList.Count - 1; j >= 0; j--)
{
errorMsg += "\n" + (tempEntityList[j] == null ? "NULL" : tempEntityList[j].ToString());
}
DebugConsole.ThrowError(errorMsg);
DebugConsole.ThrowError(errorMsg, contentPackage: targetEntity?.ContentPackage);
return false;
}
@@ -172,7 +184,7 @@ namespace Barotrauma.Networking
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)",
Microsoft.Xna.Framework.Color.Orange);
Color.Orange);
}
tempEntityList.Add(null);
if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; }
@@ -187,7 +199,7 @@ namespace Barotrauma.Networking
//skip the event if we've already received it or if the entity isn't found
if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null)
{
if (thisEventID != (UInt16) (lastReceivedID + 1))
if (thisEventID != (UInt16)(lastReceivedID + 1))
{
if (GameSettings.CurrentConfig.VerboseLogging)
{
@@ -195,7 +207,7 @@ namespace Barotrauma.Networking
"Received msg " + thisEventID + " (waiting for " + (lastReceivedID + 1) + ")",
NetIdUtils.IdMoreRecent(thisEventID, (UInt16)(lastReceivedID + 1))
? GUIStyle.Red
: Microsoft.Xna.Framework.Color.Yellow);
: Color.Yellow);
}
}
else if (entity == null)
@@ -215,12 +227,18 @@ namespace Barotrauma.Networking
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("received msg " + thisEventID + " (" + entity.ToString() + ")",
Microsoft.Xna.Framework.Color.Green);
Color.Green);
}
lastReceivedID++;
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
try
{
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
}
catch (Exception exception)
{
throw new EntityEventException("Failed to read event." , entity as Entity, exception);
}
if (msg.BitPosition != msgPosition + msgLength * 8)
{
var prevEntity = tempEntityList.Count >= 2 ? tempEntityList[tempEntityList.Count - 2] : null;
@@ -231,7 +249,7 @@ namespace Barotrauma.Networking
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
throw new EntityEventException(errorMsg, entity as Entity);
}
}
}
@@ -74,7 +74,16 @@ sealed class SteamConnectSocket : P2PSocket
{
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
var connectionManager = Steamworks.SteamNetworkingSockets.ConnectRelay<ConnectionManager>(endpoint.SteamId.Value);
ConnectionManager connectionManager;
try
{
connectionManager = Steamworks.SteamNetworkingSockets.ConnectRelay<ConnectionManager>(endpoint.SteamId.Value);
}
catch (ArgumentException e)
{
DebugConsole.ThrowError("Failed to connect via SteamP2P. Are you logged in to Steam, is the same Steam account already connected to the server?", e);
return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket));
}
if (connectionManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
connectionManager.SetEndpointAndCallbacks(endpoint, callbacks);
@@ -68,7 +68,7 @@ sealed class SteamListenSocket : P2PSocket
public override void OnMessage(Steamworks.Data.Connection connection, Steamworks.Data.NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel)
{
if (!identity.IsSteamId) { return; }
if (!identity.IsSteamId || data == IntPtr.Zero) { return; }
var endpoint = new SteamP2PEndpoint(new SteamId((Steamworks.SteamId)identity));
var dataArray = new byte[size];
@@ -20,6 +20,8 @@ namespace Barotrauma.Networking
public bool AllowModDownloads { get; private set; } = true;
public string AutomaticallyAttemptedPassword = string.Empty;
public readonly record struct Callbacks(
Callbacks.MessageCallback OnMessageReceived,
Callbacks.DisconnectCallback OnDisconnect,
@@ -39,6 +41,9 @@ namespace Barotrauma.Networking
protected bool IsOwner => ownerKey.IsSome();
protected readonly Option<int> ownerKey;
/// <summary>
/// Has the ClientPeer been started? Set to true in <see cref="Start"/>, set to false when shutting the client down <see cref="Close(PeerDisconnectPacket)"/>.
/// </summary>
public bool IsActive => isActive;
protected bool isActive;
@@ -102,8 +107,12 @@ namespace Barotrauma.Networking
TaskPool.Add($"{GetType().Name}.{nameof(GetAccountId)}", GetAccountId(), t =>
{
if (GameMain.Client?.ClientPeer is null) { return; }
if (!IsActive)
{
//client has become inactive (cancelled/disconnected while waiting for initialization)
return;
}
if (!t.TryGetResult(out Option<AccountId> accountId))
{
Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
@@ -118,7 +127,7 @@ namespace Barotrauma.Networking
var body = new ClientAuthTicketAndVersionPacket
{
Name = GameMain.Client.Name,
Name = GameMain.Client?.Name ?? "Unknown",
OwnerKey = ownerKey,
AccountId = accountId,
AuthTicket = authTicket,
@@ -177,10 +186,16 @@ namespace Barotrauma.Networking
var passwordPacket = INetSerializableStruct.Read<ServerPeerPasswordPacket>(inc.Message);
if (WaitingForPassword) { return; }
passwordPacket.Salt.TryUnwrap(out passwordSalt);
passwordPacket.RetriesLeft.TryUnwrap(out var retries);
if (!string.IsNullOrWhiteSpace(AutomaticallyAttemptedPassword))
{
SendPassword(AutomaticallyAttemptedPassword);
return;
}
LocalizedString pwMsg = TextManager.Get("PasswordRequired");
passwordMsgBox?.Close();
@@ -224,10 +224,13 @@ namespace Barotrauma.Networking
ToolBox.ThrowIfNull(netPeerConfiguration);
#if DEBUG
netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance;
netPeerConfiguration.SimulatedMinimumLatency = GameMain.Client.SimulatedMinimumLatency;
netPeerConfiguration.SimulatedRandomLatency = GameMain.Client.SimulatedRandomLatency;
netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss;
if (GameMain.Client != null)
{
netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance;
netPeerConfiguration.SimulatedMinimumLatency = GameMain.Client.SimulatedMinimumLatency;
netPeerConfiguration.SimulatedRandomLatency = GameMain.Client.SimulatedRandomLatency;
netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss;
}
#endif
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
@@ -22,6 +22,12 @@ namespace Barotrauma.Networking
get; private set;
}
public DateTime ReturnTime { get; private set; }
public DateTime RespawnTime { get; private set; }
public State CurrentState { get; private set; }
public bool ReturnCountdownStarted { get; private set; }
public bool RespawnCountdownStarted { get; private set; }
public static void ShowDeathPromptIfNeeded(float delay = 1.0f)
{
if (UseDeathPrompt)
@@ -30,13 +36,18 @@ namespace Barotrauma.Networking
}
}
partial void UpdateTransportingProjSpecific(float deltaTime)
partial void UpdateTransportingProjSpecific(TeamSpecificState teamSpecificState, float deltaTime)
{
if (GameMain.Client?.Character == null || GameMain.Client.Character.Submarine != RespawnShuttle) { return; }
if (!ReturnCountdownStarted) { return; }
if (GameMain.Client?.Character == null ||
GameMain.Client.Character.Submarine is not { IsRespawnShuttle: true } ||
GameMain.Client.Character.TeamID != teamSpecificState.TeamID)
{
return;
}
if (!teamSpecificState.ReturnCountdownStarted) { return; }
//show a warning when there's 20 seconds until the shuttle leaves
if ((ReturnTime - DateTime.Now).TotalSeconds < 20.0f &&
if ((teamSpecificState.ReturnTime - DateTime.Now).TotalSeconds < 20.0f &&
(DateTime.Now - lastShuttleLeavingWarningTime).TotalSeconds > 30.0f)
{
lastShuttleLeavingWarningTime = DateTime.Now;
@@ -46,43 +57,59 @@ namespace Barotrauma.Networking
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
bool respawnPromptPending = false;
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
switch (newState)
var myTeamId = (CharacterTeamType)msg.ReadByte();
foreach (var teamSpecificState in teamSpecificStates.Values)
{
case State.Transporting:
ReturnCountdownStarted = msg.ReadBoolean();
maxTransportTime = msg.ReadSingle();
float transportTimeLeft = msg.ReadSingle();
var teamId = (CharacterTeamType)msg.ReadByte();
ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(transportTimeLeft * 1000.0f));
RespawnCountdownStarted = false;
if (CurrentState != newState)
{
CoroutineManager.StopCoroutines("forcepos");
}
break;
case State.Waiting:
PendingRespawnCount = msg.ReadUInt16();
RequiredRespawnCount = msg.ReadUInt16();
respawnPromptPending = msg.ReadBoolean();
RespawnCountdownStarted = msg.ReadBoolean();
ResetShuttle();
float newRespawnTime = msg.ReadSingle();
RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f));
break;
case State.Returning:
RespawnCountdownStarted = false;
break;
bool respawnPromptPending = false;
bool clientHasChosenNewBotViaShuttle = false;
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
switch (newState)
{
case State.Transporting:
teamSpecificState.ReturnCountdownStarted = msg.ReadBoolean();
maxTransportTime = msg.ReadSingle();
float transportTimeLeft = msg.ReadSingle();
teamSpecificState.ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(transportTimeLeft * 1000.0f));
teamSpecificState.RespawnCountdownStarted = false;
SetShuttleBodyType(teamSpecificState.TeamID, FarseerPhysics.BodyType.Dynamic);
break;
case State.Waiting:
teamSpecificState.PendingRespawnCount = msg.ReadUInt16();
teamSpecificState.RequiredRespawnCount = msg.ReadUInt16();
respawnPromptPending = msg.ReadBoolean();
clientHasChosenNewBotViaShuttle = msg.ReadBoolean();
teamSpecificState.RespawnCountdownStarted = msg.ReadBoolean();
ResetShuttle(teamSpecificState);
float newRespawnTime = msg.ReadSingle();
teamSpecificState.RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f));
SetShuttleBodyType(teamSpecificState.TeamID, FarseerPhysics.BodyType.Static);
break;
case State.Returning:
teamSpecificState.RespawnCountdownStarted = false;
break;
}
teamSpecificState.CurrentState = newState;
if (respawnPromptPending && !clientHasChosenNewBotViaShuttle)
{
GameMain.Client.HasSpawned = true;
DeathPrompt.Create(delay: 1.0f);
}
if (teamId == myTeamId)
{
PendingRespawnCount = teamSpecificState.PendingRespawnCount;
RequiredRespawnCount = teamSpecificState.RequiredRespawnCount;
ReturnTime = teamSpecificState.ReturnTime;
RespawnTime = teamSpecificState.RespawnTime;
CurrentState = teamSpecificState.CurrentState;
ReturnCountdownStarted = teamSpecificState.ReturnCountdownStarted;
RespawnCountdownStarted = teamSpecificState.RespawnCountdownStarted;
}
}
CurrentState = newState;
if (respawnPromptPending)
{
GameMain.Client.HasSpawned = true;
DeathPrompt.Create(delay: 1.0f);
}
msg.ReadPadBits();
}
}
@@ -144,9 +144,9 @@ namespace Barotrauma.Networking
var pingLocation = NetPingLocation.TryParseFromString(pingLocationStr);
if (pingLocation.HasValue && Steamworks.SteamNetworkingUtils.LocalPingLocation.HasValue)
if (pingLocation.HasValue)
{
int ping = Steamworks.SteamNetworkingUtils.LocalPingLocation.Value.EstimatePingTo(pingLocation.Value);
int ping = Steamworks.SteamNetworkingUtils.EstimatePingTo(pingLocation.Value);
if (ping < 0) { return Result.Failure(SteamLobbyPingError.PingEstimationFailed); }
return Result.Success(ping);
}
@@ -385,14 +385,24 @@ namespace Barotrauma.Networking
{ MinSize = new Point(0, 15) },
package.Name)
{
CanBeFocused = false
Enabled = false
};
packageText.Box.DisabledColor = packageText.Box.Color;
packageText.TextBlock.DisabledTextColor = packageText.TextBlock.TextColor;
if (!string.IsNullOrEmpty(package.Hash))
{
if (ContentPackageManager.AllPackages.Any(contentPackage => contentPackage.Hash.StringRepresentation == package.Hash))
if (ContentPackageManager.AllPackages.FirstOrDefault(contentPackage => contentPackage.Hash.StringRepresentation == package.Hash) is { } matchingPackage)
{
packageText.TextColor = GUIStyle.Green;
packageText.Selected = true;
matchingPackage.TryFetchUgcDescription(onFinished: (string? description) =>
{
if (packageText.ToolTip.IsNullOrEmpty() &&
!string.IsNullOrEmpty(description))
{
packageText.ToolTip = description + "...";
}
});
}
//workshop download link found
else if (package.Id.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId)
@@ -437,7 +447,7 @@ namespace Barotrauma.Networking
public void UpdateInfo(Func<string, string?> valueGetter)
{
ServerMessage = valueGetter("message") ?? "";
ServerMessage = ExtractServerMessage(valueGetter);
if (Version.TryParse(valueGetter("version"), out var version))
{
GameVersion = version;
@@ -477,6 +487,22 @@ namespace Barotrauma.Networking
}
}
private static string ExtractServerMessage(Func<string, string?> valueGetter)
{
string msg = valueGetter("message") ?? string.Empty;
if (!msg.IsNullOrEmpty()) { return msg; }
int messageIndex = 0;
string splitMessage;
do
{
splitMessage = valueGetter($"message{messageIndex}") ?? string.Empty;
msg += splitMessage;
messageIndex++;
} while (!splitMessage.IsNullOrEmpty());
return msg;
}
private static ServerListContentPackageInfo[] ExtractContentPackageInfo(string serverName, Func<string, string?> valueGetter)
{
//workaround to ServerRules queries truncating the values to 255 bytes
@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -67,6 +67,7 @@ namespace Barotrauma
return null;
});
serverInfo.Checked = true;
serverInfo.HasPassword |= entry.Passworded;
onServerDataReceived(serverInfo, this);
});
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
@@ -178,6 +179,11 @@ namespace Barotrauma.Networking
extraCargoPanel.Visible = true;
}
}
if (ReadPerks(incMsg))
{
GameMain.NetLobbyScreen?.UpdateDisembarkPointListFromServerSettings();
}
}
if (requiredFlags.HasFlag(NetFlags.HiddenSubs))
@@ -194,10 +200,41 @@ namespace Barotrauma.Networking
}
}
public static bool HasPermissionToChangePerks()
{
if (GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) { return true; }
bool isPvP = GameMain.NetLobbyScreen?.SelectedMode == GameModePreset.PvP;
bool hasSelectedTeam = MultiplayerPreferences.Instance.TeamPreference is CharacterTeamType.Team1 or CharacterTeamType.Team2;
var otherClients = GameMain.Client?.ConnectedClients.Where(static c => c.SessionId != GameMain.Client.SessionId).ToImmutableArray() ?? ImmutableArray<Client>.Empty;
if (isPvP)
{
if (!hasSelectedTeam) { return false; }
return !otherClients
.Where(static c => c.PreferredTeam == MultiplayerPreferences.Instance.TeamPreference)
.Any(static c => c.HasPermission(Networking.ClientPermissions.ManageSettings));
}
else
{
return !otherClients.Any(static c => c.HasPermission(Networking.ClientPermissions.ManageSettings));
}
}
public void ClientAdminWritePerks()
{
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.WriteByte((byte)ClientPacketHeader.SERVER_SETTINGS_PERKS);
WritePerks(outMsg);
GameMain.Client?.ClientPeer?.Send(outMsg, DeliveryMethod.Reliable);
}
public void ClientAdminWrite(
NetFlags dataToSend,
int? missionTypeOr = null,
int? missionTypeAnd = null,
Identifier addedMissionType = default,
Identifier removedMissionType = default,
int traitorDangerLevel = 0)
{
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) { return; }
@@ -220,7 +257,7 @@ namespace Barotrauma.Networking
outMsg.WriteUInt32(count);
foreach (KeyValuePair<UInt32, NetPropertyData> prop in changedProperties)
{
DebugConsole.NewMessage(prop.Value.Name.Value, Color.Lime);
DebugConsole.NewMessage($"Changed {prop.Value.Name.Value} to {prop.Value.GUIComponentValue}", Color.Lime);
outMsg.WriteUInt32(prop.Key);
prop.Value.Write(outMsg, prop.Value.GUIComponentValue);
}
@@ -237,8 +274,8 @@ namespace Barotrauma.Networking
if (dataToSend.HasFlag(NetFlags.Misc))
{
outMsg.WriteRangedInteger(missionTypeOr ?? (int)Barotrauma.MissionType.None, 0, (int)Barotrauma.MissionType.All);
outMsg.WriteRangedInteger(missionTypeAnd ?? (int)Barotrauma.MissionType.All, 0, (int)Barotrauma.MissionType.All);
outMsg.WriteIdentifier(addedMissionType);
outMsg.WriteIdentifier(removedMissionType);
outMsg.WriteByte((byte)(traitorDangerLevel + 1));
outMsg.WritePadBits();
}
@@ -418,8 +418,44 @@ namespace Barotrauma.Networking
var randomizeLevelBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsRandomizeSeed"));
AssignGUIComponent(nameof(RandomizeSeed), randomizeLevelBox);
//***********************************************
// ******* PVP ********************************
NetLobbyScreen.CreateSubHeader("gamemode.pvp", listBox.Content);
var teamSelectModeLabel = new GUITextBlock(
new RectTransform(new Vector2(1.0f, 0.05f),
listBox.Content.RectTransform),
TextManager.Get("TeamSelectionMode"));
teamSelectModeLabel.ToolTip = TextManager.Get("TeamSelectionMode.tooltip");
var teamSelectionMode = new GUISelectionCarousel<PvpTeamSelectionMode>(
new RectTransform(new Vector2(0.5f, 0.6f),
teamSelectModeLabel.RectTransform,
Anchor.CenterRight));
foreach (PvpTeamSelectionMode teamSelectionModeOption in Enum.GetValues(typeof(PvpTeamSelectionMode)))
{
var optionName = teamSelectionModeOption.ToString();
teamSelectionMode.AddElement(teamSelectionModeOption,
TextManager.Get($"TeamSelectionMode.{optionName}"),
TextManager.Get($"TeamSelectionMode.{optionName}.tooltip"));
}
AssignGUIComponent(nameof(PvpTeamSelectionMode), teamSelectionMode);
var autoBalanceThresholdLabel = new GUITextBlock(
new RectTransform(new Vector2(1.0f, 0.05f),
listBox.Content.RectTransform),
TextManager.Get("AutoBalanceThreshold"));
var autoBalanceThresholdTooltip = TextManager.Get("AutoBalanceThreshold.tooltip");
autoBalanceThresholdLabel.ToolTip = autoBalanceThresholdTooltip;
var autoBalanceThreshold = new GUISelectionCarousel<int>(
new RectTransform(new Vector2(0.5f, 0.6f),
autoBalanceThresholdLabel.RectTransform,
Anchor.CenterRight));
autoBalanceThreshold.AddElement(0, TextManager.Get($"AutoBalanceThreshold.Off"), autoBalanceThresholdTooltip);
autoBalanceThreshold.AddElement(1, "1", autoBalanceThresholdTooltip);
autoBalanceThreshold.AddElement(2, "2", autoBalanceThresholdTooltip);
autoBalanceThreshold.AddElement(3, "3", autoBalanceThresholdTooltip);
AssignGUIComponent(nameof(PvpAutoBalanceThreshold), autoBalanceThreshold);
// ******* GAMEPLAY ***************************
NetLobbyScreen.CreateSubHeader("serversettingsroundstab", listBox.Content);
var voiceChatEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
@@ -429,6 +465,12 @@ namespace Barotrauma.Networking
var allowSpecBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsAllowSpectating"));
AssignGUIComponent(nameof(AllowSpectating), allowSpecBox);
var allowAfkBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsAllowAFK"))
{
ToolTip = TextManager.Get("ServerSettingsAllowAFK.tooltip")
};
AssignGUIComponent(nameof(AllowAFK), allowAfkBox);
var losModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("LosEffect"));
var losModeSelection = new GUISelectionCarousel<LosMode>(new RectTransform(new Vector2(0.5f, 0.6f), losModeLabel.RectTransform, Anchor.CenterRight));
@@ -485,6 +527,9 @@ namespace Barotrauma.Networking
AssignGUIComponent(nameof(NewCampaignDefaultSalary), defaultSalarySlider);
defaultSalarySlider.OnMoved(defaultSalarySlider, defaultSalarySlider.BarScroll);
var pvpDisembarkPoints = NetLobbyScreen.CreateLabeledNumberInput(listBox.Content, "serversettingsdisembarkpoints", 0, 100, "serversettingsdisembarkpointstooltip");
AssignGUIComponent(nameof(DisembarkPointAllowance), pvpDisembarkPoints);
//--------------------------------------------------------------------------------
// game settings
//--------------------------------------------------------------------------------
@@ -202,6 +202,7 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("Capture device has been disconnected. You can select another available device in the settings.");
Disconnected = true;
TryRefreshDevice();
break;
}
}
@@ -320,7 +321,7 @@ namespace Barotrauma.Networking
private Sound overrideSound;
private int overridePos;
private short[] overrideBuf = new short[VoipConfig.BUFFER_SIZE];
private readonly short[] overrideBuf = new short[VoipConfig.BUFFER_SIZE];
private void FillBuffer()
{
@@ -331,13 +332,13 @@ namespace Barotrauma.Networking
{
int sampleCount = overrideSound.FillStreamBuffer(overridePos, overrideBuf);
overridePos += sampleCount * 2;
Array.Copy(overrideBuf, 0, uncompressedBuffer, totalSampleCount, sampleCount);
Array.Copy(overrideBuf, 0, uncompressedBuffer, totalSampleCount, Math.Min(sampleCount, uncompressedBuffer.Length - totalSampleCount));
totalSampleCount += sampleCount;
if (sampleCount == 0)
{
overridePos = 0;
}
}
}
int sleepMs = VoipConfig.BUFFER_SIZE * 800 / VoipConfig.FREQUENCY;
Thread.Sleep(sleepMs - 1);
@@ -382,7 +383,14 @@ namespace Barotrauma.Networking
}
else
{
overrideSound = GameMain.SoundManager.LoadSound(fileName, true);
try
{
overrideSound = GameMain.SoundManager.LoadSound(fileName, true);
}
catch (Exception e)
{
DebugConsole.ThrowError($"Failed to load the sound {fileName}.", e);
}
}
}
@@ -394,5 +402,65 @@ namespace Barotrauma.Networking
captureThread = null;
if (captureDevice != IntPtr.Zero) { Alc.CaptureCloseDevice(captureDevice); }
}
public static void TryRefreshDevice()
{
DebugConsole.NewMessage("Refreshing audio capture device");
List<string> deviceList = Alc.GetStringList(IntPtr.Zero, Alc.CaptureDeviceSpecifier).ToList();
int alcError = Alc.GetError(IntPtr.Zero);
if (alcError != Alc.NoError)
{
DebugConsole.ThrowError("Failed to list available audio input devices: " + alcError.ToString());
return;
}
if (deviceList.Any())
{
string device;
if (deviceList.Find(n => n.Equals(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice, StringComparison.OrdinalIgnoreCase))
is string availablePreviousDevice)
{
DebugConsole.NewMessage($" Previous device choice available: {availablePreviousDevice}");
device = availablePreviousDevice;
}
else
{
device = Alc.GetString(IntPtr.Zero, Alc.CaptureDefaultDeviceSpecifier);
DebugConsole.NewMessage($" Reverting to default device: {device}");
}
if (string.IsNullOrEmpty(device))
{
device = deviceList[0];
DebugConsole.NewMessage($" No default device found, resorting to first available device: {device}");
}
// Save the new device choice and generate a new voice capture instance with it
var currentConfig = GameSettings.CurrentConfig;
currentConfig.Audio.VoiceCaptureDevice = device;
GameSettings.SetCurrentConfig(currentConfig);
if (Instance is VoipCapture currentCaptureInstance)
{
currentCaptureInstance.Dispose();
}
Create(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice);
}
// Didn't end up with any capture device, so let's disable voice capture for now
if (Instance == null)
{
DebugConsole.NewMessage($" No devices found, disabling");
var currentConfig = GameSettings.CurrentConfig;
currentConfig.Audio.VoiceSetting = VoiceMode.Disabled;
GameSettings.SetCurrentConfig(currentConfig);
}
if (GUI.SettingsMenuOpen)
{
SettingsMenu.Instance?.CreateAudioAndVCTab(true);
}
}
}
}
@@ -116,12 +116,12 @@ namespace Barotrauma.Networking
bool spectating = Character.Controlled == null;
float rangeMultiplier = spectating ? 2.0f : 1.0f;
WifiComponent senderRadio = null;
var messageType =
!client.VoipQueue.ForceLocal &&
ChatMessage.CanUseRadio(client.Character, out senderRadio) &&
ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) &&
senderRadio.CanReceive(recipientRadio) ?
ChatMessageType.Radio : ChatMessageType.Default;
(spectating || (ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) && senderRadio.CanReceive(recipientRadio)))
? ChatMessageType.Radio : ChatMessageType.Default;
client.Character.ShowTextlessSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
@@ -149,7 +149,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen?.SetPlayerSpeaking(client);
GameMain.GameSession?.CrewManager?.SetClientSpeaking(client);
if ((client.VoipSound.CurrentAmplitude * client.VoipSound.Gain * GameMain.SoundManager.GetCategoryGainMultiplier("voip")) > 0.1f) //TODO: might need to tweak
if ((client.VoipSound.CurrentAmplitude * client.VoipSound.Gain * GameMain.SoundManager.GetCategoryGainMultiplier(SoundManager.SoundCategoryVoip)) > 0.1f) //TODO: might need to tweak
{
if (client.Character != null && !client.Character.Removed && !client.Character.IsDead)
{
@@ -59,22 +59,77 @@ namespace Barotrauma
switch (voteType)
{
case VoteType.Sub:
case VoteType.Mode:
GUIListBox listBox = (voteType == VoteType.Sub) ?
GameMain.NetLobbyScreen.SubList : GameMain.NetLobbyScreen.ModeList;
var subList = GameMain.NetLobbyScreen.SubList;
foreach (GUIComponent comp in listBox.Content.Children)
foreach (GUIComponent comp in subList.Content.Children)
{
if (comp.FindChild("votes") is GUITextBlock voteText) { comp.RemoveChild(voteText); }
TryRemoveVoteText(comp);
var container = comp.GetChild<GUILayoutGroup>();
var imageFrame = container.GetChild<GUIFrame>();
var coalIcon = imageFrame.GetChildByUserData(NetLobbyScreen.CoalitionIconUserData);
var sepIcon = imageFrame.GetChildByUserData(NetLobbyScreen.SeparatistsIconUserData);
coalIcon.Enabled = false;
sepIcon.Enabled = false;
TryRemoveVoteText(coalIcon);
TryRemoveVoteText(sepIcon);
static void TryRemoveVoteText(GUIComponent component)
{
if (component.FindChild("votes") is GUITextBlock foundText)
{
component.RemoveChild(foundText);
}
}
}
if (clients == null) { return; }
IReadOnlyDictionary<object, int> voteList = GetVoteCounts<object>(voteType, clients);
foreach (KeyValuePair<object, int> votable in voteList)
bool isPvP = GameMain.NetLobbyScreen?.SelectedMode == GameModePreset.PvP;
if (isPvP)
{
SetVoteText(listBox, votable.Key, votable.Value);
}
var coalitionVoteList = GetVoteCounts<SubmarineInfo>(voteType, clients.Where(static c => c.PreferredTeam is CharacterTeamType.Team1));
var separatistVoteList = GetVoteCounts<SubmarineInfo>(voteType, clients.Where(static c => c.PreferredTeam is CharacterTeamType.Team2));
foreach (var (subInfo, amount) in coalitionVoteList)
{
SetSubVoteText(subList, subInfo, amount, CharacterTeamType.Team1);
}
foreach (var (subInfo, amount) in separatistVoteList)
{
SetSubVoteText(subList, subInfo, amount, CharacterTeamType.Team2);
}
}
else
{
var subVoteList = GetVoteCounts<SubmarineInfo>(voteType, clients);
foreach (var (subInfo, amount) in subVoteList)
{
SetSubVoteText(subList, subInfo, amount, CharacterTeamType.None);
}
}
break;
case VoteType.Mode:
var modeList = GameMain.NetLobbyScreen.ModeList;
foreach (GUIComponent comp in modeList.Content.Children)
{
if (comp.FindChild("votes") is GUITextBlock voteText)
{
comp.RemoveChild(voteText);
}
}
if (clients == null) { return; }
var modeVoteList = GetVoteCounts<GameModePreset>(voteType, clients);
foreach (var (preset, amount) in modeVoteList)
{
SetVoteText(modeList, preset, amount);
}
break;
case VoteType.StartRound:
if (clients == null) { return; }
@@ -90,12 +145,70 @@ namespace Barotrauma
}
}
private void SetSubVoteText(GUIListBox subListBox, SubmarineInfo userData, int votes, CharacterTeamType type)
{
GUIComponent subElement = subListBox.Content.GetChildByUserData(userData);
if (subElement is null)
{
DebugConsole.ThrowError("Failed to find the submarine element in the listbox");
return;
}
var (coalIcon, sepIcon) = GetPvPIcons(subElement);
switch (type)
{
case CharacterTeamType.None:
{
SetVoteText(subListBox, userData, votes);
break;
}
case CharacterTeamType.Team1:
{
coalIcon.Enabled = votes > 0;
CreateSubmarineVoteText(coalIcon, votes);
break;
}
case CharacterTeamType.Team2:
{
sepIcon.Enabled = votes > 0;
CreateSubmarineVoteText(sepIcon, votes);
break;
}
default:
return;
}
static void CreateSubmarineVoteText(GUIComponent parent, int votes)
{
if (parent is null) { return; }
var voteText = new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform, Anchor.TopLeft), $"{votes}", textAlignment: Alignment.Center)
{
Padding = Vector4.Zero,
UserData = "votes",
Shadow = true
};
voteText.RectTransform.RelativeOffset = new Vector2(0.33f, 0.33f);
}
static (GUIComponent CoalitionIcon, GUIComponent SeparatistsIcon) GetPvPIcons(GUIComponent child)
{
var container = child.GetChild<GUILayoutGroup>();
var imageFrame = container.GetChild<GUIFrame>();
var coalIcon = imageFrame.GetChildByUserData(NetLobbyScreen.CoalitionIconUserData);
var sepIcon = imageFrame.GetChildByUserData(NetLobbyScreen.SeparatistsIconUserData);
return (CoalitionIcon: coalIcon, SeparatistsIcon: sepIcon);
}
}
private void SetVoteText(GUIListBox listBox, object userData, int votes)
{
if (userData == null) { return; }
foreach (GUIComponent comp in listBox.Content.Children)
{
if (comp.UserData != userData) { continue; }
if (comp.FindChild("votes") is not GUITextBlock voteText)
{
voteText = new GUITextBlock(new RectTransform(new Point(GUI.IntScale(30), comp.Rect.Height), comp.RectTransform, Anchor.CenterRight),
@@ -197,28 +310,48 @@ namespace Barotrauma
msg.WritePadBits();
return true;
}
public void ClientRead(IReadMessage inc)
{
GameMain.Client.ServerSettings.AllowSubVoting = inc.ReadBoolean();
if (GameMain.Client.ServerSettings.AllowSubVoting)
{
UpdateVoteTexts(null, VoteType.Sub);
bool isMultiSub = inc.ReadBoolean();
int votableCount = inc.ReadByte();
List<SubmarineInfo> serversubs = new List<SubmarineInfo>();
if (GameMain.NetLobbyScreen?.SubList?.Content != null)
{
foreach (GUIComponent item in GameMain.NetLobbyScreen.SubList.Content.Children)
{
if (item.UserData is SubmarineInfo info)
{
serversubs.Add(info);
}
}
}
for (int i = 0; i < votableCount; i++)
{
int votes = inc.ReadByte();
string subName = inc.ReadString();
List<SubmarineInfo> serversubs = new List<SubmarineInfo>();
if (GameMain.NetLobbyScreen?.SubList?.Content != null)
{
foreach (GUIComponent item in GameMain.NetLobbyScreen.SubList.Content.Children)
{
if (item.UserData != null && item.UserData is SubmarineInfo) { serversubs.Add(item.UserData as SubmarineInfo); }
}
}
SubmarineInfo sub = serversubs.FirstOrDefault(s => s.Name == subName);
SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes);
SetSubVoteText(GameMain.NetLobbyScreen.SubList, sub, votes, isMultiSub ? CharacterTeamType.Team1 : CharacterTeamType.None);
}
if (isMultiSub)
{
int separatistsCount = inc.ReadByte();
for (int i = 0; i < separatistsCount; i++)
{
int votes = inc.ReadByte();
string subName = inc.ReadString();
SubmarineInfo sub = serversubs.FirstOrDefault(s => s.Name == subName);
SetSubVoteText(GameMain.NetLobbyScreen.SubList, sub, votes, CharacterTeamType.Team2);
}
}
}
GameMain.Client.ServerSettings.AllowModeVoting = inc.ReadBoolean();