Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop

This commit is contained in:
EvilFactory
2024-03-28 14:26:18 -03:00
271 changed files with 13174 additions and 3021 deletions

View File

@@ -1,5 +1,5 @@
name: Bug Report
description: Found a bug? Help us squash it by making a bug report!
title: Bug Report
labels: Found a bug? Help us squash it by making a bug report!
body:
- type: markdown
attributes:
@@ -74,6 +74,8 @@ body:
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
options:
- v1.2.8.0 (Winter Update hotfix 2)
- v1.2.13.0 (unstable)
- v1.2.8.0 (EOS test build)
- Other
validations:
required: true

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Bug reports
url: https://github.com/FakeFishGames/Barotrauma/discussions/categories/bug-reports
about: Please post your bug reports in the Discussions section.

View File

@@ -543,7 +543,7 @@ namespace Barotrauma
Identifier factionId = inc.ReadIdentifier();
float minReputationToHire = 0.0f;
if (factionId != default)
if (!factionId.IsEmpty)
{
minReputationToHire = inc.ReadSingle();
}

View File

@@ -73,13 +73,16 @@ namespace Barotrauma
if (isMouseOver)
{
var glowSprite = GUIStyle.UIGlowCircular.Value.Sprite;
float glowScale = 40f / glowSprite.size.X;
if (isScrewed)
var glowSprite = GUIStyle.UIGlowCircular.Value?.Sprite;
if (glowSprite is not null)
{
glowScale *= 1.2f;
float glowScale = 40f / glowSprite.size.X;
if (isScrewed)
{
glowScale *= 1.2f;
}
glowSprite.Draw(spriteBatch, position, GUIStyle.Yellow, glowSprite.size / 2, scale: glowScale);
}
glowSprite.Draw(spriteBatch, position, GUIStyle.Yellow, glowSprite.size / 2, scale: glowScale);
}
tooltip = Option.None;

View File

@@ -24,7 +24,7 @@ namespace Barotrauma
private GUIFrame? selectedWireFrame;
private GUIListBox? componentList;
private GUITextBlock? inventoryIndicatorText;
private readonly Sprite cursorSprite = GUIStyle.CursorSprite[CursorState.Default];
private readonly Sprite? cursorSprite = GUIStyle.CursorSprite[CursorState.Default];
private Option<RectangleF> selection = Option.None;
private string searchTerm = string.Empty;

View File

@@ -198,7 +198,7 @@ namespace Barotrauma.Transition
DebugConsole.ThrowError("There was an error transferring mods", t2.Exception.GetInnermost());
}
ContentPackageManager.LocalPackages.Refresh();
if (t2.TryGetResult(out string[] modsToEnable))
if (t2.TryGetResult(out string[]? modsToEnable))
{
var newRegular = ContentPackageManager.EnabledPackages.Regular.ToList();
newRegular.AddRange(ContentPackageManager.LocalPackages.Regular

View File

@@ -9,10 +9,12 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using static Barotrauma.FabricationRecipe;
@@ -35,9 +37,7 @@ namespace Barotrauma
if (!allowCheats && !CheatsEnabled && IsCheat)
{
NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + Names[0] + "\".", Color.Red);
#if USE_STEAM
NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red);
#endif
NewMessage("Enabling cheats will disable achievements during this play session.", Color.Red);
return;
}
@@ -66,8 +66,12 @@ namespace Barotrauma
private static GUIFrame frame;
private static GUIListBox listBox;
private static GUITextBox textBox;
#if DEBUG
private const int maxLength = 100000;
#else
private const int maxLength = 1000;
#endif
public static GUITextBox TextBox => textBox;
private static readonly ChatManager chatManager = new ChatManager(true, 64);
@@ -157,7 +161,7 @@ namespace Barotrauma
activeQuestionText?.SetAsLastChild();
if (PlayerInput.KeyHit(Keys.F3))
if (PlayerInput.KeyHit(Keys.F3) && !PlayerInput.KeyDown(Keys.LeftControl) && !PlayerInput.KeyDown(Keys.RightControl))
{
Toggle();
}
@@ -249,6 +253,9 @@ namespace Barotrauma
case "unbindkey":
case "wikiimage_character":
case "wikiimage_sub":
case "eosStat":
case "eosUnlink":
case "eosLoginEpicViaSteam":
return true;
default:
return client.HasConsoleCommandPermission(command);
@@ -391,6 +398,87 @@ namespace Barotrauma
private static void InitProjectSpecific()
{
commands.Add(new Command("eosStat", "Query and display all logged in EOS users. Normally this is at most two users, but in a developer environment it could be more.", args =>
{
if (!EosInterface.Core.IsInitialized)
{
NewMessage("EOS not initialized");
return;
}
var loggedInUsers = EosInterface.IdQueries.GetLoggedInPuids();
if (!loggedInUsers.Any())
{
NewMessage("EOS user not logged in");
return;
}
NewMessage("Logged in EOS users:");
foreach (var puid in loggedInUsers)
{
TaskPool.Add(
$"eosStat -> {puid}",
EosInterface.IdQueries.GetSelfExternalAccountIds(puid),
t =>
{
if (!t.TryGetResult(out Result<ImmutableArray<AccountId>, EosInterface.IdQueries.GetSelfExternalIdError> result)) { return; }
NewMessage($" - {puid}");
if (result.TryUnwrapSuccess(out var ids))
{
foreach (var id in ids)
{
NewMessage($" - {id}");
if (id is EpicAccountId eaid)
{
async Task gameOwnershipTokenTest()
{
var tokenOption = await EosInterface.Ownership.GetGameOwnershipToken(eaid);
var verified = await tokenOption.Bind(t => t.Verify());
NewMessage($"Ownership token verify result: {verified}");
}
_ = gameOwnershipTokenTest(); // Fire and forget!
EosInterface.Login.TestEosSessionTimeoutRecovery(puid);
}
}
}
else
{
NewMessage($" - Failed to get external IDs linked to {puid}: {result}");
}
});
}
}));
AssignRelayToServer("eosStat", false);
commands.Add(new Command("eosUnlink", "Unlink the primary logged in external account ID from its corresponding EOS Product User ID and close the game. This is meant to be used to test the EOS consent flow.", args =>
{
var userId = EosInterface.IdQueries.GetLoggedInPuids().FirstOrDefault();
NewMessage($"Unlinking external account from PUID {userId}");
GameSettings.SetCurrentConfig(GameSettings.CurrentConfig with { CrossplayChoice = Eos.EosSteamPrimaryLogin.CrossplayChoice.Unknown });
GameSettings.SaveCurrentConfig();
TaskPool.Add("unlinkTask", EosInterface.Login.UnlinkExternalAccount(userId), _ =>
{
GameMain.Instance.Exit();
});
}));
AssignRelayToServer("eosUnlink", false);
commands.Add(new Command("eosLoginEpicViaSteam", "Log into an Epic account via a link to the currently logged in Steam account",
args =>
{
TaskPool.Add("eosLoginEpicViaSteam",
Eos.EosEpicSecondaryLogin.LoginToLinkedEpicAccount(),
TaskPool.IgnoredCallback);
}));
AssignRelayToServer("eosLoginEpicViaSteam", false);
commands.Add(new Command("resetgameanalyticsconsent", "Reset whether you've given your consent for the game to send statistics to GameAnalytics. After executing the command, the game should ask for your consent again on relaunch.", args =>
{
GameAnalyticsManager.ResetConsent();
}));
AssignRelayToServer("resetgameanalyticsconsent", false);
commands.Add(new Command("copyitemnames", "", (string[] args) =>
{
StringBuilder sb = new StringBuilder();
@@ -424,18 +512,16 @@ namespace Barotrauma
}
}));
commands.Add(new Command("enablecheats", "enablecheats: Enables cheat commands and disables Steam achievements during this play session.", (string[] args) =>
commands.Add(new Command("enablecheats", "enablecheats: Enables cheat commands and disables achievements during this play session.", (string[] args) =>
{
CheatsEnabled = true;
SteamAchievementManager.CheatsEnabled = true;
AchievementManager.CheatsEnabled = true;
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
{
campaign.CheatsEnabled = true;
}
NewMessage("Enabled cheat commands.", Color.Red);
#if USE_STEAM
NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
#endif
NewMessage("Achievements have been disabled during this play session.", Color.Red);
}));
AssignRelayToServer("enablecheats", true);

View File

@@ -0,0 +1,203 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Barotrauma.Steam;
using Microsoft.Xna.Framework;
namespace Barotrauma.Eos;
internal static class EosAccount
{
/// <summary>
/// The user can have several account IDs if they've linked their Steam account to an Epic Games account
/// </summary>
public static ImmutableHashSet<AccountId> SelfAccountIds { get; private set; } = ImmutableHashSet<AccountId>.Empty;
private static readonly Queue<Action> postLoginActions = new();
private static bool isLoggedIn;
public static void RefreshSelfAccountIds(Action? onRefreshComplete = null)
{
SelfAccountIds = ImmutableHashSet<AccountId>.Empty;
var selfPuids = EosInterface.IdQueries.GetLoggedInPuids();
if (selfPuids.Length == 0)
{
onRefreshComplete?.Invoke();
return;
}
var collectedIds = new Option<ImmutableArray<AccountId>>[selfPuids.Length];
Action<Task> taskDoneHandler(int index)
{
void countDoneTask(Task t)
{
try
{
if (!t.TryGetResult(out Result<ImmutableArray<AccountId>, EosInterface.IdQueries.GetSelfExternalIdError>? result)) { return; }
if (!result.TryUnwrapSuccess(out var ids)) { return; }
collectedIds[index] = Option.Some(ids);
}
finally
{
// If we failed to get IDs from this query, fill in the relevant slot in the collectedIds array
// to indicate that the task is done anyway
collectedIds[index] = Option.Some(collectedIds[index].Fallback(ImmutableArray<AccountId>.Empty));
// If all of the tasks are done, merge all of the collected IDs into the hashset
if (collectedIds.All(o => o.IsSome()))
{
SelfAccountIds = collectedIds.NotNone().SelectMany(a => a).ToImmutableHashSet();
onRefreshComplete?.Invoke();
}
}
}
return countDoneTask;
}
for (int i = 0; i < selfPuids.Length; i++)
{
TaskPool.Add($"SelfPlayerRowWithExternalAccountIds{i}",
EosInterface.IdQueries.GetSelfExternalAccountIds(selfPuids[i]),
taskDoneHandler(i));
}
}
#region Message box stuff
private static GUIMessageBox? messageBox;
public static void ReplaceMessageBox(GUIMessageBox? newMessageBox)
{
messageBox?.Close();
messageBox = newMessageBox;
}
public static void CloseMessageBox()
=> ReplaceMessageBox(null);
public static GUIMessageBox CreateLoadingMessageBox((Func<bool> CanCancel, Action Cancel)? actions = null)
{
var relativeSize = messageBox?.InnerFrame.RectTransform.RelativeSize ?? (0.35f, 0.3f);
var newMessageBox = new GUIMessageBox(
headerText: LocalizedString.EmptyString,
text: LocalizedString.EmptyString,
relativeSize: relativeSize,
buttons: actions != null ? new[] { TextManager.Get("Cancel") } : Array.Empty<LocalizedString>());
if (actions != null)
{
newMessageBox.Buttons[0].Visible = false;
newMessageBox.Buttons[0].OnClicked = (_, _) =>
{
actions.Value.Cancel.Invoke();
return false;
};
new GUICustomComponent(new RectTransform(Vector2.Zero, newMessageBox.InnerFrame.RectTransform), onUpdate:
(_, _) =>
{
bool canCancel = actions.Value.CanCancel.Invoke();
newMessageBox.Buttons[0].Visible |= canCancel;
newMessageBox.Buttons[0].Enabled = canCancel;
});
}
new GUICustomComponent(
new RectTransform(Vector2.One * 0.25f, newMessageBox.InnerFrame.RectTransform, Anchor.Center, scaleBasis: ScaleBasis.Smallest),
onDraw: static (sb, component) =>
{
GUIStyle.GenericThrobber.Draw(
sb,
spriteIndex: (int)(Timing.TotalTime * 20f) % GUIStyle.GenericThrobber.FrameCount,
pos: component.Rect.Center.ToVector2(),
color: Color.White,
origin: GUIStyle.GenericThrobber.FrameSize.ToVector2() * 0.5f,
rotate: 0f,
scale: component.Rect.Size.ToVector2() / GUIStyle.GenericThrobber.FrameSize.ToVector2());
});
ReplaceMessageBox(newMessageBox);
return newMessageBox;
}
public static Action RetryAction(LocalizedString intro, LocalizedString reason, Action retryAction, Action cancelAction)
{
return () => GameMain.ExecuteAfterContentFinishedLoading(() => AskRetry(intro, reason, retryAction, cancelAction));
}
private static void AskRetry(LocalizedString intro, LocalizedString failureReason, Action retryAction, Action cancelAction)
{
var options = new[]
{
TextManager.Get("Retry"),
TextManager.Get("Cancel")
};
var askHowToProceed = TextManager.Get("AskHowToProceed");
GUIMessageBox msgBox = new GUIMessageBox(
headerText: TextManager.Get("EosIntroHeader"),
text: intro + "\n\n" + failureReason + "\n\n" + askHowToProceed,
options,
relativeSize: (0.4f, 0.4f));
msgBox.Buttons[0].OnClicked = delegate
{
retryAction();
CloseMessageBox();
return false;
};
msgBox.Buttons[1].OnClicked = delegate
{
cancelAction();
CloseMessageBox();
return false;
};
ReplaceMessageBox(msgBox);
}
#endregion
public static void LoginPlatformSpecific()
{
if (GameMain.Instance.EgsExchangeCode.TryUnwrap(out var exchangeCode))
{
LoginEpic(exchangeCode);
}
else if (SteamManager.IsInitialized)
{
LoginSteam();
}
}
private static void LoginSteam()
=> EosSteamPrimaryLogin.Start();
private static void LoginEpic(string exchangeCode)
=> EosEpicPrimaryLogin.Start(exchangeCode);
public static void OnLoginSuccess()
{
isLoggedIn = true;
while (postLoginActions.TryDequeue(out var action))
{
action();
}
}
public static void ExecuteAfterLogin(Action action)
{
if (isLoggedIn)
{
action();
return;
}
postLoginActions.Enqueue(action);
}
}

View File

@@ -0,0 +1,70 @@
#nullable enable
using System;
using System.Threading.Tasks;
namespace Barotrauma.Eos;
/// <summary>
/// Handles a player that owns a copy of Barotrauma on Epic Games Store (therefore they
/// will use their Epic Account ID as their primary identity) logging into EOS.
/// </summary>
static class EosEpicPrimaryLogin
{
public static void Start(string exchangeCode)
{
TaskPool.Add("Eos.Core.LoginEpic", Initialize(exchangeCode), t =>
{
if (!t.TryGetResult(out Action? action)) { return; }
action();
});
}
private static void Success()
{
Eos.EosAccount.CloseMessageBox();
Eos.EosAccount.RefreshSelfAccountIds();
EosAccount.OnLoginSuccess();
}
private static async Task<Action> Initialize(string exchangeCode)
{
void retry() => Start(exchangeCode);
static void cancel() => EosInterface.Core.CleanupAndQuit();
var failedToInitializeIntro = TextManager.Get("EosFailedToInitialize");
var loginResult = await EosInterface.Login.LoginEpicExchangeCode(exchangeCode);
if (!loginResult.TryUnwrapSuccess(out var either))
{
LocalizedString localizedError = $"Login failed with unknown error code.";
if (loginResult.TryUnwrapFailure(out EosInterface.Login.LoginError errorCode))
{
localizedError = TextManager
.Get($"EosInterface.Core.InitError.{errorCode}")
.Fallback($"Failed to initialize Epic Online Services (error code {errorCode})");
}
return EosAccount.RetryAction(failedToInitializeIntro, localizedError, retry, cancel);
}
if (either.TryGet(out EosInterface.EosConnectContinuanceToken eosContinuanceToken))
{
var createProductAccountResult = await EosInterface.Login.CreateProductAccount(eosContinuanceToken);
if (!createProductAccountResult.TryUnwrapSuccess(out var puid))
{
return EosAccount.RetryAction(
failedToInitializeIntro,
$"Failed to create product user account: {(createProductAccountResult.TryUnwrapFailure(out var failure) ? failure : "unknown")}",
retry, cancel);
}
DebugConsole.NewMessage($"Logged into EOS for the first time with Epic as primary external account ID: {puid}");
return Success;
}
else if (either.TryGet(out EosInterface.ProductUserId puid))
{
DebugConsole.NewMessage($"Logged into EOS with Epic as primary external account ID: {puid}");
return Success;
}
throw new UnreachableCodeException();
}
}

View File

@@ -0,0 +1,201 @@
#nullable enable
using System.Linq;
using System.Threading.Tasks;
using Barotrauma.Extensions;
namespace Barotrauma.Eos;
/// <summary>
/// Handles a player that owns a copy of Barotrauma on Steam,
/// and wishes to link their Steam account to an Epic account
/// to interact with friends on Epic Games' account system.
/// </summary>
static class EosEpicSecondaryLogin
{
public enum ProbeResult
{
NoAccount,
LinkedExternalAccountsButNoPuid,
LoggedIn
}
public static async Task<ProbeResult> ProbeLinkedEpicAccount()
{
var loginResult = await EosInterface.Login.LoginEpicWithLinkedSteamAccount(EosInterface.Login.LoginEpicFlags.FailWithoutOpeningBrowser);
if (!loginResult.TryUnwrapSuccess(out var success)) { return ProbeResult.NoAccount; }
if (success.TryGet(out EosInterface.ProductUserId _))
{
// Make Steam account the primary external account just in case
await EosInterface.Login.LoginSteam();
return ProbeResult.LoggedIn;
}
if (success.TryGet(out EosInterface.EosConnectContinuanceToken? _))
{
return ProbeResult.LinkedExternalAccountsButNoPuid;
}
return ProbeResult.NoAccount;
}
public enum LoginErrorDesc
{
NoPrimaryPuid,
FailedToLogInViaLinkedEpicAccount,
FailedToForceSteamAsPrimaryExternalAccountId,
SteamIsNoLongerLinkedToPuid,
SteamPuidMismatchedPreviousPrimaryPuid,
FailedToLinkSteamAccountToEpicAccount,
FailedToCreatePuidForEpicAccount,
UnhandledErrorCondition
}
public readonly record struct LoginError(
LoginErrorDesc ErrorDesc,
Option<EosInterface.Login.LoginError> LoginEosConnectError = default,
Option<EosInterface.Login.LinkExternalAccountToEpicAccountError> LinkExternalToEpicError = default,
Option<EosInterface.Login.CreateProductAccountError> CreatePuidError = default)
{
public override string ToString()
{
string error = $"LoginError ({ErrorDesc}";
if (LoginEosConnectError.TryUnwrap(out var connectError))
{
error += $", {connectError}";
}
if (LinkExternalToEpicError.TryUnwrap(out var externalToEpicError))
{
error += $", {externalToEpicError}";
}
if (CreatePuidError.TryUnwrap(out var createPuidError))
{
error += $", {createPuidError}";
}
return error + ")";
}
}
public static async Task<Result<Unit, LoginError>> LoginToLinkedEpicAccount()
{
var primaryPuidOption = EosInterface.IdQueries.GetLoggedInPuids().FirstOrNone();
if (!primaryPuidOption.TryUnwrap(out var primaryPuid)) { return Result.Failure(new LoginError(LoginErrorDesc.NoPrimaryPuid)); }
// No matter what happens, refresh account IDs when returning
using var janitor = Janitor.Start();
janitor.AddAction(static () => EosAccount.RefreshSelfAccountIds());
async Task<Result<Unit, LoginError>> makeSteamPrimaryExternalAccount()
{
// By logging into EOS connect via Steam, we make sure that it's
// treated as the primary external account, which means that it's
// prioritized over the Epic account ID in other EOS functions
var loginSteamResult = await EosInterface.Login.LoginSteam();
if (!loginSteamResult.TryUnwrapSuccess(out var loginSteamSuccess))
{
return Result.Failure(new LoginError(LoginErrorDesc.FailedToForceSteamAsPrimaryExternalAccountId));
}
if (!loginSteamSuccess.TryGet(out EosInterface.ProductUserId primaryPuidAgain))
{
return Result.Failure(new LoginError(LoginErrorDesc.SteamIsNoLongerLinkedToPuid));
}
if (primaryPuid != primaryPuidAgain)
{
return Result.Failure(new LoginError(LoginErrorDesc.SteamPuidMismatchedPreviousPrimaryPuid));
}
return Result.Success(Unit.Value);
}
const int MaxLoginPasses = 5;
for (int loginPass = 0; loginPass < MaxLoginPasses; loginPass++)
{
// Try to log into EOS via Epic via Steam several times,
// only stop once we get a PUID that's linked only to the Epic account
if (EosInterface.IdQueries.GetLoggedInEpicIds() is { Length: > 0 } loggedInEpicIds)
{
// Log out of any Epic accounts to reduce chances of ending up in an inconsistent state
await Task.WhenAll(loggedInEpicIds.Select(EosInterface.Login.LogoutEpicAccount));
}
var loginResult = await EosInterface.Login.LoginEpicWithLinkedSteamAccount(
loginPass == 0
? EosInterface.Login.LoginEpicFlags.None
: EosInterface.Login.LoginEpicFlags.FailWithoutOpeningBrowser);
if (loginResult.TryUnwrapFailure(out var loginEpicFailure))
{
return Result.Failure(new LoginError(LoginErrorDesc.FailedToLogInViaLinkedEpicAccount, LoginEosConnectError: Option.Some(loginEpicFailure)));
}
if (!loginResult.TryUnwrapSuccess(out var loginEpicSuccess))
{
throw new UnreachableCodeException();
}
if (loginEpicSuccess.TryGet(out EosInterface.ProductUserId secondPuid))
{
if (primaryPuid == secondPuid)
{
// Somehow we've got two external accounts linked
// to the same PUID, let's yank them apart
// Given that the latest ID used to log into this account was
// the Epic account, this call to UnlinkExternalAccount will
// keep the SteamID linked to this PUID and only unlink the
// Epic account, which is what we want.
await EosInterface.Login.UnlinkExternalAccount(secondPuid);
// Once that's done, log back into EOS Connect with
// the SteamID as the primary ID
if ((await makeSteamPrimaryExternalAccount()).TryUnwrapFailure(out var loginSteamError))
{
return Result.Failure(loginSteamError);
}
}
else
{
// We already have a PUID for this Epic account. We're done here!
return Result.Success(Unit.Value);
}
}
else if (loginEpicSuccess.TryGet(out EosInterface.EgsAuthContinuanceToken? egsAuthContinuanceToken))
{
// We got an EGS Auth continuance token, which means that the player
// has provided an Epic account to link the current Steam account to.
var linkExternalToEpicResult = await EosInterface.Login.LinkExternalAccountToEpicAccount(egsAuthContinuanceToken);
if (linkExternalToEpicResult.TryUnwrapFailure(out var linkExternalToEpicError))
{
return Result.Failure(new LoginError(LoginErrorDesc.FailedToLinkSteamAccountToEpicAccount, LinkExternalToEpicError: Option.Some(linkExternalToEpicError)));
}
}
else if (loginEpicSuccess.TryGet(out EosInterface.EosConnectContinuanceToken? eosConnectContinuanceToken))
{
// We got an EOS Connect continuance token, we need
// a new Product User ID for the given Epic account.
var createPuidResult = await EosInterface.Login.CreateProductAccount(eosConnectContinuanceToken);
if (createPuidResult.TryUnwrapFailure(out var createPuidError))
{
return Result.Failure(new LoginError(LoginErrorDesc.FailedToCreatePuidForEpicAccount, CreatePuidError: Option.Some(createPuidError)));
}
// PUID has been created! We're done here!
return Result.Success(Unit.Value);
}
else
{
throw new UnreachableCodeException();
}
}
return Result.Failure(new LoginError(LoginErrorDesc.UnhandledErrorCondition));
}
}

View File

@@ -0,0 +1,321 @@
#nullable enable
using Barotrauma.Steam;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Barotrauma.Eos;
/// <summary>
/// Handles a player that owns a copy of Barotrauma on Steam (therefore they
/// will use their SteamID as their primary identity) logging into EOS.
/// </summary>
public static class EosSteamPrimaryLogin
{
public static bool IsNewEosPlayer = false;
public enum CrossplayChoice
{
Unknown,
Enabled,
Disabled
}
public static CrossplayChoice EnableCrossplay
{
get => GameSettings.CurrentConfig.CrossplayChoice;
set
{
GameSettings.SetCurrentConfig(GameSettings.CurrentConfig with { CrossplayChoice = value });
GameAnalyticsManager.AddDesignEvent("Crossplay:" + value);
GameSettings.SaveCurrentConfig();
}
}
public static void Start()
{
TaskPool.Add(
"EosSteamPrimaryLogin",
Initialize(),
OnTaskComplete);
}
private static void OnTaskComplete(Task t)
{
if (t.Exception?.GetInnermost() is { } exception)
{
DebugConsole.ThrowError($"{nameof(EosSteamPrimaryLogin)}.{nameof(Initialize)} failed with exception {exception.Message} {exception.StackTrace.CleanupStackTrace()}");
}
if (!t.TryGetResult(out Action? action)) { return; }
action();
}
private static void Success()
{
Eos.EosAccount.CloseMessageBox();
Eos.EosAccount.RefreshSelfAccountIds();
EosAccount.OnLoginSuccess();
}
private static async Task<Action> Initialize()
{
static void retry() => Start();
static void cancel() => EosInterface.Core.CleanupAndQuit();
var failedToInitializeIntro = TextManager.Get("EosFailedToInitialize");
if (EnableCrossplay is CrossplayChoice.Unknown)
{
// Don't even try to initialize EOS until we get the user's consent
return SteamAccountHasNoLinkedPuid();
}
if (EnableCrossplay is CrossplayChoice.Disabled)
{
// Crossplay is disabled, return immediately
return Success;
}
if (!SteamManager.IsInitialized)
{
return EosAccount.RetryAction(failedToInitializeIntro, "Steamworks is not initialized", retry, cancel);
}
Result<Unit, EosInterface.Core.InitError> initResult = Result.Failure(EosInterface.Core.InitError.UnhandledErrorCondition);
CrossThread.RequestExecutionOnMainThread(() => initResult = EosInterface.Core.Init(EosInterface.ApplicationCredentials.Client, enableOverlay: false));
if (initResult.TryUnwrapFailure(out var initError))
{
return EosAccount.RetryAction(failedToInitializeIntro, GetErrorMessage(initError), retry, cancel);
}
var steamPuidResult = await EosInterface.Login.LoginSteam();
if (!steamPuidResult.TryUnwrapSuccess(out var steamPuidOrContToken))
{
return EosAccount.RetryAction(failedToInitializeIntro, $"Failed to log into EOS with Steam account: {steamPuidResult}", retry, cancel);
}
if (steamPuidOrContToken.TryGet(out EosInterface.ProductUserId puid))
{
return await SteamAccountHasLinkedPuid(puid);
}
else if (steamPuidOrContToken.TryGet(out EosInterface.EosConnectContinuanceToken _))
{
return SteamAccountHasNoLinkedPuid();
}
throw new UnreachableCodeException();
}
private static async Task<Action> SteamAccountHasLinkedPuid(EosInterface.ProductUserId _)
{
await EosEpicSecondaryLogin.ProbeLinkedEpicAccount();
return Success;
}
private static Action SteamAccountHasNoLinkedPuid()
{
return () => GameMain.ExecuteAfterContentFinishedLoading(AskPlayerToEnableCrossplay);
}
private static void AskPlayerToEnableCrossplay()
{
LocalizedString[] options =
{
TextManager.Get("EnableCrossplay"),
TextManager.Get("DisableCrossplay")
};
var introText = "\n" + LocalizedString.Join(
"\n\n",
Enumerable.Range(0, 3).Select(static i => TextManager.Get($"EosIntro{i}"))) + "\n";
GUIMessageBox msgBox = new GUIMessageBox(
headerText: TextManager.Get("EosIntroHeader"),
text: introText,
Array.Empty<LocalizedString>(),
relativeSize: (0.8f, 0.5f));
msgBox.Content.ChildAnchor = Anchor.TopCenter;
msgBox.Content.Stretch = true;
msgBox.InnerFrame.RectTransform.ScaleBasis = ScaleBasis.Smallest;
int? selectedRadioButton = null;
var radioButtonLayout = new GUILayoutGroup(new RectTransform(Vector2.One, msgBox.Content.RectTransform)) { Stretch = true };
var radioButtonGroup = new GUIRadioButtonGroup();
for (int i = 0; i < options.Length; i++)
{
var radioButton = new GUITickBox(
new RectTransform(Vector2.One, radioButtonLayout.RectTransform),
label: options[i],
style: "GUIRadioButton");
radioButtonGroup.AddRadioButton(
key: i,
radioButton: radioButton);
radioButton.RectTransform.MinSize = Point.Zero;
radioButton.RectTransform.MaxSize = new Point(int.MaxValue);
radioButton.RectTransform.ScaleBasis = ScaleBasis.Normal;
radioButton.RectTransform.RelativeSize = Vector2.One;
}
//spacing
new GUIFrame(new RectTransform(new Point(0), radioButtonLayout.RectTransform) { MinSize = new Point(0, GUI.IntScale(30)) }, style: null);
var submitButton = new GUIButton(new RectTransform(Vector2.One, radioButtonLayout.RectTransform),
TextManager.Get("Submit").Fallback("Submit")) { Enabled = false };
radioButtonGroup.OnSelect = (rbg, val) =>
{
selectedRadioButton = val;
submitButton.Enabled = true;
};
msgBox.ForceLayoutRecalculation();
var maxOptionWidth = options.Select(o => GUIStyle.Font.MeasureString(o).X).Max();
int extraWidth = (int)(GUIStyle.Font.LineHeight * 4f);
radioButtonLayout.RectTransform.IsFixedSize = true;
radioButtonLayout.RectTransform.NonScaledSize = new Point((int)maxOptionWidth + extraWidth, (int)(GUIStyle.Font.LineHeight * options.Length * 1.5f) + submitButton.Rect.Height * 2);
msgBox.ForceLayoutRecalculation();
static void textSizeFixHack(GUITextBlock textBlock, int width)
{
textBlock.RectTransform.IsFixedSize = true;
textBlock.RectTransform.MinSize = Point.Zero;
textBlock.RectTransform.MaxSize = new Point(int.MaxValue);
textBlock.RectTransform.NonScaledSize = new Point(width, 0);
textBlock.CalculateHeightFromText();
}
textSizeFixHack(msgBox.Header, (int)(msgBox.InnerFrame.Rect.Width * 0.9f));
textSizeFixHack(msgBox.Text, (int)(msgBox.InnerFrame.Rect.Width * 0.9f));
msgBox.ForceLayoutRecalculation();
msgBox.InnerFrame.RectTransform.IsFixedSize = true;
msgBox.InnerFrame.RectTransform.NonScaledSize = new Point(
msgBox.InnerFrame.Rect.Width,
(int)((msgBox.Content.Children.Select(c => c.Rect.Height + GUI.IntScale(5)).Sum() + GUIStyle.Font.LineHeight) / 0.9f));
submitButton.OnClicked = delegate
{
switch (selectedRadioButton)
{
case 0:
PlayerWantsToEnableCrossplay();
return false;
case 1:
PlayerWantsToDisableCrossplay();
return false;
default:
throw new UnreachableCodeException();
}
};
Eos.EosAccount.ReplaceMessageBox(msgBox);
}
private static void PlayerWantsToEnableCrossplay()
{
Eos.EosAccount.CreateLoadingMessageBox();
TaskPool.Add(
nameof(EnableCrossplayAndCreatePuidWithOneToken),
EnableCrossplayAndCreatePuidWithOneToken(),
OnTaskComplete);
}
private static void PlayerWantsToDisableCrossplay()
{
EosInterface.Core.CleanupAndQuit();
var action = DisableCrossplay();
action();
}
private static async Task<Action> EnableCrossplayAndCreatePuidWithOneToken()
{
void retry() => PlayerWantsToEnableCrossplay();
static void cancel() => EosInterface.Core.CleanupAndQuit();
var failedToCreatePuidIntro = TextManager.Get("FailedToCreatePuid");
EnableCrossplay = CrossplayChoice.Enabled;
if (!SteamManager.IsInitialized)
{
return EosAccount.RetryAction(failedToCreatePuidIntro, "Steamworks is not initialized", retry, cancel);
}
Result<Unit, EosInterface.Core.InitError> initResult = Result.Failure(EosInterface.Core.InitError.UnhandledErrorCondition);
CrossThread.RequestExecutionOnMainThread(() => initResult = EosInterface.Core.Init(EosInterface.ApplicationCredentials.Client, enableOverlay: false));
if (initResult.TryUnwrapFailure(out var initError))
{
return EosAccount.RetryAction(failedToCreatePuidIntro, GetErrorMessage(initError), retry, cancel);
}
EosInterface.EosConnectContinuanceToken steamEosContinuanceToken;
var steamLoginResult = await EosInterface.Login.LoginSteam();
if (steamLoginResult.TryUnwrapSuccess(out var either))
{
if (either.TryGet(out EosInterface.EosConnectContinuanceToken newSteamCt))
{
steamEosContinuanceToken = newSteamCt;
}
else
{
await EosEpicSecondaryLogin.ProbeLinkedEpicAccount();
SocialOverlay.Instance?.DisplayBindHintToPlayer();
return Success;
}
}
else
{
return EosAccount.RetryAction(failedToCreatePuidIntro, $"Failed to refresh continuance token: {steamLoginResult}", retry, cancel);
}
var newPuidResult = await EosInterface.Login.CreateProductAccount(steamEosContinuanceToken);
if (newPuidResult.IsFailure)
{
return EosAccount.RetryAction(failedToCreatePuidIntro, $"Failed to create PUID: {newPuidResult}", retry, cancel);
}
IsNewEosPlayer = true;
SocialOverlay.Instance?.DisplayBindHintToPlayer();
return Success;
}
private static LocalizedString GetErrorMessage(EosInterface.Core.InitError errorCode)
{
return TextManager.Get($"EosInterface.Core.InitError.{errorCode}").Fallback($"Failed to initialize Epic Online Services (error code {errorCode})");
}
private static Action DisableCrossplay()
{
EnableCrossplay = CrossplayChoice.Disabled;
return Success;
}
public static void HandleCrossplayChoiceChange(CrossplayChoice newChoice)
{
if (StoreIntegration.CurrentStore != StoreIntegration.Store.Steam) { return; }
if (GameSettings.CurrentConfig.CrossplayChoice == newChoice) { return; }
switch (newChoice)
{
case CrossplayChoice.Disabled:
EosInterface.Core.CleanupAndQuit();
break;
case CrossplayChoice.Enabled:
if (EosInterface.Core.CurrentStatus == EosInterface.Core.Status.ShutDown)
{
var msgBox = new GUIMessageBox(TextManager.Get("EosAllowCrossplay"),
TextManager.Get("RestartRequiredGeneric"), new[] { TextManager.Get("ok") })
{
DrawOnTop = true
};
msgBox.Buttons[0].OnClicked = (_, _) =>
{
msgBox.Close();
return true;
};
}
else
{
PlayerWantsToEnableCrossplay();
}
break;
}
}
}

View File

@@ -65,25 +65,11 @@ namespace Barotrauma
private int texDims;
private uint baseChar;
private readonly struct GlyphData
{
public readonly int TexIndex;
public readonly Vector2 DrawOffset;
public readonly float Advance;
public readonly Rectangle TexCoords;
public GlyphData(
int texIndex = default,
Vector2 drawOffset = default,
float advance = default,
Rectangle texCoords = default)
{
TexIndex = texIndex;
DrawOffset = drawOffset;
Advance = advance;
TexCoords = texCoords;
}
}
public readonly record struct GlyphData(
int TexIndex = default,
Vector2 DrawOffset = default,
float Advance = default,
Rectangle TexCoords = default);
public static TextManager.SpeciallyHandledCharCategory ExtractShccFromXElement(XElement element)
=> TextManager.SpeciallyHandledCharCategories
@@ -94,7 +80,7 @@ namespace Barotrauma
// For backwards compatibility, we assume that Cyrillic is supported by default
TextManager.SpeciallyHandledCharCategory.Cyrillic => true,
_ => throw new NotImplementedException($"nameof{category} not implemented.")
}))
.Aggregate(TextManager.SpeciallyHandledCharCategory.None, (current, category) => current | category);
@@ -209,8 +195,8 @@ namespace Barotrauma
if (glyphIndex == 0)
{
texCoords.Add(j, new GlyphData(
advance: 0,
texIndex: -1));
Advance: 0,
TexIndex: -1));
continue;
}
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
@@ -218,8 +204,8 @@ namespace Barotrauma
{
//glyph is empty, but char might still apply advance
GlyphData blankData = new GlyphData(
advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f),
texIndex: -1); //indicates no texture because the glyph is empty
Advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f),
TexIndex: -1); //indicates no texture because the glyph is empty
texCoords.Add(j, blankData);
continue;
@@ -262,10 +248,10 @@ namespace Barotrauma
}
GlyphData newData = new GlyphData(
advance: (float)face.Glyph.Metrics.HorizontalAdvance,
texIndex: texIndex,
texCoords: new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight),
drawOffset: new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop)
Advance: (float)face.Glyph.Metrics.HorizontalAdvance,
TexIndex: texIndex,
TexCoords: new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight),
DrawOffset: new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop)
);
texCoords.Add(j, newData);
@@ -354,8 +340,8 @@ namespace Barotrauma
if (glyphIndex == 0)
{
texCoords.Add(character, new GlyphData(
advance: 0,
texIndex: -1));
Advance: 0,
TexIndex: -1));
continue;
}
@@ -365,8 +351,8 @@ namespace Barotrauma
{
//glyph is empty, but char might still apply advance
GlyphData blankData = new GlyphData(
advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f),
texIndex: -1); //indicates no texture because the glyph is empty
Advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f),
TexIndex: -1); //indicates no texture because the glyph is empty
texCoords.Add(character, blankData);
continue;
}
@@ -403,10 +389,10 @@ namespace Barotrauma
}
GlyphData newData = new GlyphData(
advance: (float)horizontalAdvance,
texIndex: textures.Count - 1,
texCoords: new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight),
drawOffset: drawOffset
Advance: (float)horizontalAdvance,
TexIndex: textures.Count - 1,
TexCoords: new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight),
DrawOffset: drawOffset
);
texCoords.Add(character, newData);
@@ -490,7 +476,7 @@ namespace Barotrauma
return gd;
}
return new GlyphData(texIndex: -1);
return new GlyphData(TexIndex: -1);
}
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
@@ -814,14 +800,22 @@ namespace Barotrauma
{
Vector2 retVal = Vector2.Zero;
retVal.Y = LineHeight;
var (gd, _) = GetGlyphDataAndTextureForChar(c);
retVal.X = gd.Advance;
return retVal;
}
public (GlyphData GlyphData, Texture2D Texture) GetGlyphDataAndTextureForChar(char c)
{
if (DynamicLoading && !texCoords.ContainsKey(c))
{
DynamicRenderAtlas(graphicsDevice, c);
}
GlyphData gd = GetGlyphData(c);
retVal.X = gd.Advance;
return retVal;
var tex = gd.TexIndex >= 0 ? textures[gd.TexIndex] : null;
return (gd, tex);
}
public void Dispose()

View File

@@ -199,19 +199,15 @@ namespace Barotrauma
public static bool PauseMenuOpen { get; private set; }
public static bool InputBlockingMenuOpen
{
get
{
return PauseMenuOpen ||
SettingsMenuOpen ||
DebugConsole.IsOpen ||
GameSession.IsTabMenuOpen ||
GameMain.GameSession?.GameMode is { Paused: true } ||
CharacterHUD.IsCampaignInterfaceOpen ||
GameMain.GameSession?.Campaign is { SlideshowPlayer: { Finished: false, Visible: true } };
}
}
public static bool InputBlockingMenuOpen =>
PauseMenuOpen
|| SettingsMenuOpen
|| SocialOverlay.Instance is { IsOpen: true }
|| DebugConsole.IsOpen
|| GameSession.IsTabMenuOpen
|| GameMain.GameSession?.GameMode is { Paused: true }
|| CharacterHUD.IsCampaignInterfaceOpen
|| GameMain.GameSession?.Campaign is { SlideshowPlayer: { Finished: false, Visible: true } };
public static bool PreventPauseMenuToggle = false;
@@ -882,25 +878,30 @@ namespace Barotrauma
private static void HandlePersistingElements(float deltaTime)
{
lock (mutex)
bool currentMessageBoxIsVerificationPrompt = GUIMessageBox.VisibleBox is GUIMessageBox { DrawOnTop: true };
if (!currentMessageBoxIsVerificationPrompt)
{
GUIMessageBox.AddActiveToGUIUpdateList();
GUIContextMenu.AddActiveToGUIUpdateList();
}
if (PauseMenuOpen)
{
PauseMenu.AddToGUIUpdateList();
}
if (SettingsMenuOpen)
{
SettingsMenuContainer.AddToGUIUpdateList();
}
if (SettingsMenuOpen)
{
SettingsMenuContainer.AddToGUIUpdateList();
}
else if (PauseMenuOpen)
{
PauseMenu.AddToGUIUpdateList();
}
//the "are you sure you want to quit" prompts are drawn on top of everything else
if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt" || GUIMessageBox.VisibleBox?.UserData as string == "bugreporter")
{
GUIMessageBox.VisibleBox.AddToGUIUpdateList();
}
SocialOverlay.Instance?.AddToGuiUpdateList();
GUIContextMenu.AddActiveToGUIUpdateList();
//the "are you sure you want to quit" prompts are drawn on top of everything else
if (currentMessageBoxIsVerificationPrompt)
{
GUIMessageBox.VisibleBox.AddToGUIUpdateList();
}
}
@@ -2560,7 +2561,8 @@ namespace Barotrauma
var msgBox = new GUIMessageBox("", TextManager.Get(textTag),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
UserData = "verificationprompt"
UserData = "verificationprompt",
DrawOnTop = true
};
msgBox.Buttons[0].OnClicked = (_, __) =>
{

View File

@@ -168,7 +168,7 @@ namespace Barotrauma
public override bool PlaySoundOnSelect { get; set; } = true;
public GUIButton(RectTransform rectT, Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : this(rectT, new RawLString(""), textAlignment, style, color) { }
public GUIButton(RectTransform rectT, Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : this(rectT, LocalizedString.EmptyString, textAlignment, style, color) { }
public GUIButton(RectTransform rectT, LocalizedString text, Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : base(style, rectT)
{

View File

@@ -159,7 +159,7 @@ namespace Barotrauma
SelectedValue = Math.Clamp(1f - (y / mainArea.Height), 0, 1);
}
CurrentColor = ToolBox.HSVToRGB(SelectedHue, SelectedSaturation, SelectedValue);
CurrentColor = ToolBoxCore.HSVToRGB(SelectedHue, SelectedSaturation, SelectedValue);
OnColorSelected?.Invoke(this, CurrentColor);
}
@@ -201,7 +201,7 @@ namespace Barotrauma
}
}
private Color DrawHVArea(float x, float y) => ToolBox.HSVToRGB(SelectedHue, x, 1.0f - y);
private Color DrawHueArea(float x, float y) => ToolBox.HSVToRGB(y * 360f, 1f, 1f);
private Color DrawHVArea(float x, float y) => ToolBoxCore.HSVToRGB(SelectedHue, x, 1.0f - y);
private Color DrawHueArea(float x, float y) => ToolBoxCore.HSVToRGB(y * 360f, 1f, 1f);
}
}

View File

@@ -8,8 +8,7 @@ using System.Xml.Linq;
using Barotrauma.IO;
using RestSharp;
using System.Net;
using System.Collections.Immutable;
using Barotrauma.Tutorials;
using Barotrauma.Steam;
namespace Barotrauma
{
@@ -1174,11 +1173,14 @@ namespace Barotrauma
{
try
{
#if USE_STEAM
Steam.SteamManager.OverlayCustomUrl(url);
#else
ToolBox.OpenFileWithShell(url);
#endif
if (SteamManager.IsInitialized)
{
SteamManager.OverlayCustomUrl(url);
}
else
{
ToolBox.OpenFileWithShell(url);
}
}
catch (Exception e)
{

View File

@@ -76,12 +76,12 @@ namespace Barotrauma
if (hasHeader)
{
InflateSize(ref estimatedSize, header, headerFont);
InflateSize(ref estimatedSize, header, headerFont!);
}
foreach (ContextMenuOption option in options)
{
Vector2 optionSize = InflateSize(ref estimatedSize, option.Label, font);
Vector2 optionSize = InflateSize(ref estimatedSize, option.Label, font!);
optionsAndSizes.Add(option, optionSize);
}
@@ -104,7 +104,7 @@ namespace Barotrauma
if (hasHeader)
{
Point sz = Point.Zero;
InflateSize(ref sz, header, headerFont);
InflateSize(ref sz, header, headerFont!);
listSize.Y -= sz.Y;
HeaderLabel = new GUITextBlock(new RectTransform(sz, background.RectTransform), header, font: headerFont) { Padding = headerPadding };
}

View File

@@ -168,7 +168,7 @@ namespace Barotrauma
public GUIDropDown(RectTransform rectT, LocalizedString text = null, int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false, Alignment textAlignment = Alignment.CenterLeft) : base(style, rectT)
{
text ??= new RawLString("");
text ??= LocalizedString.EmptyString;
HoverCursor = CursorState.Hand;
CanBeFocused = true;

View File

@@ -205,5 +205,11 @@ namespace Barotrauma
Recalculate();
}
}
public override void ForceLayoutRecalculation()
{
Recalculate();
base.ForceLayoutRecalculation();
}
}
}

View File

@@ -86,6 +86,11 @@ namespace Barotrauma
public Type MessageBoxType => type;
/// <summary>
/// If enabled, the box is always drawn in front of all other elements.
/// </summary>
public bool DrawOnTop;
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
public GUIMessageBox(LocalizedString headerText, LocalizedString text, Vector2? relativeSize = null, Point? minSize = null, Type type = Type.Default)
@@ -477,13 +482,13 @@ namespace Barotrauma
for (int i = 0; i < MessageBoxes.Count; i++)
{
if (MessageBoxes[i] == null) { continue; }
if (!(MessageBoxes[i] is GUIMessageBox messageBox))
if (MessageBoxes[i] is not GUIMessageBox messageBox)
{
if (type == Type.Default)
{
// Message box not of type GUIMessageBox is likely the round summary
MessageBoxes[i].AddToGUIUpdateList();
if (!(MessageBoxes[i].UserData is RoundSummary)) { break; }
if (MessageBoxes[i].UserData is not RoundSummary) { break; }
}
continue;
}
@@ -494,8 +499,7 @@ namespace Barotrauma
}
// These are handled separately in GUI.HandlePersistingElements()
if (MessageBoxes[i].UserData as string == "verificationprompt") { continue; }
if (MessageBoxes[i].UserData as string == "bugreporter") { continue; }
if (messageBox.DrawOnTop) { continue; }
messageBox.AddToGUIUpdateList();
break;

View File

@@ -1,4 +1,5 @@
using Barotrauma.Extensions;
#nullable enable
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -54,8 +55,8 @@ namespace Barotrauma
public class GUIFontPrefab : GUIPrefab
{
private readonly ContentXElement element;
private ScalableFont font;
public ScalableFont Font
private ScalableFont? font;
public ScalableFont? Font
{
get
{
@@ -64,11 +65,13 @@ namespace Barotrauma
}
}
private ImmutableDictionary<TextManager.SpeciallyHandledCharCategory, ScalableFont> specialHandlingFonts;
private ImmutableDictionary<TextManager.SpeciallyHandledCharCategory, ScalableFont>? specialHandlingFonts;
public ScalableFont GetFontForCategory(TextManager.SpeciallyHandledCharCategory category)
public ScalableFont? GetFontForCategory(TextManager.SpeciallyHandledCharCategory category)
{
if (Language != GameSettings.CurrentConfig.Language) { LoadFont(); }
if (font is null) { return null; }
if (specialHandlingFonts is null) { return font; }
if (font.SpeciallyHandledCharCategory.HasFlag(category)) { return font; }
return specialHandlingFonts.TryGetValue(category, out var resultFont)
? resultFont
@@ -87,7 +90,7 @@ namespace Barotrauma
public void LoadFont()
{
string fontPath = GetFontFilePath(element);
string? fontPath = GetFontFilePath(element);
uint size = GetFontSize(element);
bool dynamicLoading = GetFontDynamicLoading(element);
var shcc = GetShcc(element);
@@ -125,21 +128,21 @@ namespace Barotrauma
specialHandlingFonts = null;
}
private ScalableFont ExtractFont(TextManager.SpeciallyHandledCharCategory flag, ContentXElement element)
private ScalableFont? ExtractFont(TextManager.SpeciallyHandledCharCategory flag, ContentXElement element)
{
foreach (var subElement in element.Elements().Reverse())
{
if (subElement.NameAsIdentifier() != "override") { continue; }
if (ScalableFont.ExtractShccFromXElement(subElement).HasFlag(flag))
{
return new ScalableFont(subElement, font.Size, GameMain.Instance.GraphicsDevice);
return new ScalableFont(subElement, font?.Size ?? 14, GameMain.Instance.GraphicsDevice);
}
}
ScalableFont hardcodedFallback(string path)
=> new ScalableFont(
path,
font.Size,
font?.Size ?? 0,
GameMain.Instance.GraphicsDevice,
dynamicLoading: true,
speciallyHandledCharCategory: flag);
@@ -154,7 +157,7 @@ namespace Barotrauma
};
}
private string GetFontFilePath(ContentXElement element)
private string? GetFontFilePath(ContentXElement element)
{
foreach (var subElement in element.Elements())
{
@@ -227,20 +230,20 @@ namespace Barotrauma
{
public GUIFont(string identifier) : base(identifier) { }
public bool HasValue => !Prefabs.IsEmpty;
public ScalableFont Value => Prefabs.ActivePrefab.Font;
public bool HasValue => Value is not null;
public static implicit operator ScalableFont(GUIFont reference) => reference.Value;
public ScalableFont? Value => Prefabs.ActivePrefab?.Font;
public static implicit operator ScalableFont?(GUIFont reference) => reference.Value;
public bool ForceUpperCase => Prefabs.ActivePrefab?.Font is { ForceUpperCase: true };
public uint Size => HasValue ? Value.Size : 0;
public uint Size => Value?.Size ?? 0;
private ScalableFont GetFontForStr(LocalizedString str) => GetFontForStr(str.Value);
private ScalableFont? GetFontForStr(LocalizedString str) => GetFontForStr(str.Value);
public ScalableFont GetFontForStr(string str) =>
Prefabs.ActivePrefab.GetFontForCategory(TextManager.GetSpeciallyHandledCategories(str));
public ScalableFont? GetFontForStr(string str) =>
Prefabs.ActivePrefab?.GetFontForCategory(TextManager.GetSpeciallyHandledCategories(str));
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float layerDepth)
{
@@ -249,7 +252,7 @@ namespace Barotrauma
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float layerDepth)
{
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth);
GetFontForStr(text)?.DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth);
}
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, Alignment alignment = Alignment.TopLeft)
@@ -259,7 +262,7 @@ namespace Barotrauma
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
{
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, alignment, forceUpperCase);
GetFontForStr(text)?.DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, alignment, forceUpperCase);
}
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit, bool italics = false)
@@ -269,34 +272,46 @@ namespace Barotrauma
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit, bool italics = false)
{
GetFontForStr(text).DrawString(sb, text, position, color, forceUpperCase, italics);
GetFontForStr(text)?.DrawString(sb, text, position, color, forceUpperCase, italics);
}
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, in ImmutableArray<RichTextData>? richTextData, int rtdOffset = 0, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
{
GetFontForStr(text).DrawStringWithColors(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, richTextData, rtdOffset, alignment, forceUpperCase);
GetFontForStr(text)?.DrawStringWithColors(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, richTextData, rtdOffset, alignment, forceUpperCase);
}
public Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing = false)
{
return GetFontForStr(str).MeasureString(str, removeExtraSpacing);
return GetFontForStr(str)?.MeasureString(str, removeExtraSpacing) ?? Vector2.Zero;
}
public Vector2 MeasureChar(char c)
{
return GetFontForStr($"{c}").MeasureChar(c);
return GetFontForStr($"{c}")?.MeasureChar(c) ?? Vector2.Zero;
}
public string WrapText(string text, float width)
=> GetFontForStr(text).WrapText(text, width);
=> GetFontForStr(text)?.WrapText(text, width) ?? text;
public string WrapText(string text, float width, int requestCharPos, out Vector2 requestedCharPos)
=> GetFontForStr(text).WrapText(text, width, requestCharPos, out requestedCharPos);
public string WrapText(string text, float width, out Vector2[] allCharPositions)
=> GetFontForStr(text).WrapText(text, width, out allCharPositions);
{
requestedCharPos = default;
return GetFontForStr(text)?.WrapText(text, width, requestCharPos, out requestedCharPos) ?? text;
}
public float LineHeight => Value.LineHeight;
public string WrapText(string text, float width, out Vector2[] allCharPositions)
{
var scalableFont = GetFontForStr(text);
if (scalableFont != null)
{
return scalableFont.WrapText(text, width, out allCharPositions);
}
allCharPositions = Enumerable.Range(0, text.Length + 1).Select(_ => Vector2.Zero).ToArray();
return text;
}
public float LineHeight => Value?.LineHeight ?? 0;
}
public class GUIColorPrefab : GUIPrefab
@@ -355,19 +370,19 @@ namespace Barotrauma
{
public GUISprite(string identifier) : base(identifier) { }
public UISprite Value
public UISprite? Value
{
get
{
return Prefabs.ActivePrefab.Sprite;
return Prefabs.ActivePrefab?.Sprite;
}
}
public static implicit operator UISprite(GUISprite reference) => reference.Value;
public static implicit operator UISprite?(GUISprite reference) => reference.Value;
public void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None)
{
Value.Draw(spriteBatch, rect, color, spriteEffects);
Value?.Draw(spriteBatch, rect, color, spriteEffects);
}
}
@@ -390,33 +405,33 @@ namespace Barotrauma
{
public GUISpriteSheet(string identifier) : base(identifier) { }
public SpriteSheet Value
public SpriteSheet? Value
{
get
{
return Prefabs.ActivePrefab.SpriteSheet;
return Prefabs.ActivePrefab?.SpriteSheet;
}
}
public int FrameCount => Value.FrameCount;
public Point FrameSize => Value.FrameSize;
public int FrameCount => Value?.FrameCount ?? 1;
public Point FrameSize => Value?.FrameSize ?? Point.Zero;
public void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate = 0, float scale = 1, SpriteEffects spriteEffects = SpriteEffects.None)
{
Value.Draw(spriteBatch, pos, rotate, scale, spriteEffects);
Value?.Draw(spriteBatch, pos, rotate, scale, spriteEffects);
}
public void Draw(ISpriteBatch spriteBatch, Vector2 pos, Color color, Vector2 origin, float rotate = 0, float scale = 1, SpriteEffects spriteEffects = SpriteEffects.None, float? depth = null)
{
Value.Draw(spriteBatch, pos, color, origin, rotate, scale, spriteEffects, depth);
Value?.Draw(spriteBatch, pos, color, origin, rotate, scale, spriteEffects, depth);
}
public void Draw(ISpriteBatch spriteBatch, int spriteIndex, Vector2 pos, Color color, Vector2 origin, float rotate, Vector2 scale, SpriteEffects spriteEffects = SpriteEffects.None, float? depth = null)
{
Value.Draw(spriteBatch, spriteIndex, pos, color, origin, rotate, scale, spriteEffects, depth);
Value?.Draw(spriteBatch, spriteIndex, pos, color, origin, rotate, scale, spriteEffects, depth);
}
public static implicit operator SpriteSheet(GUISpriteSheet reference) => reference.Value;
public static implicit operator SpriteSheet?(GUISpriteSheet reference) => reference.Value;
}
public class GUICursorPrefab : GUIPrefab
@@ -446,6 +461,6 @@ namespace Barotrauma
{
public GUICursor(string identifier) : base(identifier) { }
public Sprite this[CursorState k] => Prefabs.ActivePrefab.Sprites[(int)k];
public Sprite? this[CursorState k] => Prefabs.ActivePrefab?.Sprites[(int)k];
}
}

View File

@@ -433,14 +433,7 @@ namespace Barotrauma
return Font.MeasureString(" ");
}
Vector2 size = Vector2.Zero;
while (size == Vector2.Zero)
{
try { size = Font.MeasureString(string.IsNullOrEmpty(text) ? " " : text); }
catch { text = text.Length > 0 ? text.Substring(0, text.Length - 1) : ""; }
}
return size;
return Font.MeasureString(string.IsNullOrEmpty(text) ? " " : text);
}
protected override void SetAlpha(float a)

View File

@@ -128,7 +128,7 @@ namespace Barotrauma
public void LoadContent(string contentPath, VideoSettings videoSettings, TextSettings textSettings, Identifier contentId, bool startPlayback)
{
LoadContent(contentPath, videoSettings, textSettings, contentId, startPlayback, new RawLString(""), null);
LoadContent(contentPath, videoSettings, textSettings, contentId, startPlayback, LocalizedString.EmptyString, null);
}
public void LoadContent(string contentPath, VideoSettings videoSettings, TextSettings textSettings, Identifier contentId, bool startPlayback, LocalizedString objective, Action onStop = null)

View File

@@ -9,7 +9,7 @@ namespace Barotrauma
{
static partial void CreateConsentPrompt()
{
if (consentTextAvailable)
if (ConsentTextAvailable)
{
var background = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas), style: "GUIBackgroundBlocker");
var frame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.7f), background.RectTransform, Anchor.Center) { MinSize = new Point(800, 0), MaxSize = new Point(1500, int.MaxValue) });
@@ -20,8 +20,13 @@ namespace Barotrauma
AbsoluteSpacing = GUI.IntScale(15)
};
string consentTextTag = "statisticsconsenttext";
if (EosInterface.IdQueries.IsLoggedIntoEosConnect)
{
consentTextTag = "statisticsconsenteostext";
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.Get("statisticsconsentheader"), font: GUIStyle.SubHeadingFont, textColor: Color.White);
var mainText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), RichString.Rich(TextManager.Get("statisticsconsenttext")), wrap: true);
var mainText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), RichString.Rich(TextManager.Get(consentTextTag)), wrap: true);
foreach (var data in mainText.RichTextData)
{
@@ -30,7 +35,7 @@ namespace Barotrauma
Data = data,
OnClick = (GUITextBlock component, GUITextBlock.ClickableArea area) =>
{
GameMain.ShowOpenUrlInWebBrowserPrompt("https://gameanalytics.com/privacy/");
GameMain.ShowOpenUriPrompt("https://gameanalytics.com/privacy/");
}
});
}
@@ -70,7 +75,7 @@ namespace Barotrauma
}
yield return CoroutineStatus.Success;
}
buttonContainerSpacing(0.2f);
var noBtn = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("No"));

View File

@@ -45,7 +45,9 @@ namespace Barotrauma
public static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
public static string[] ConsoleArguments;
public readonly ImmutableArray<string> ConsoleArguments;
public readonly Option<string> EgsExchangeCode;
public static GameScreen GameScreen;
public static MainMenuScreen MainMenuScreen;
@@ -215,6 +217,10 @@ namespace Barotrauma
public static ChatMode ActiveChatMode { get; set; } = ChatMode.Radio;
private static bool contentLoaded;
private static readonly Queue<Action> postContentLoadActions = new();
public GameMain(string[] args)
{
Content.RootDirectory = "Content";
@@ -242,11 +248,13 @@ namespace Barotrauma
GameSettings.Init();
CreatureMetrics.Init();
ConsoleArguments = args;
ConsoleArguments = args.ToImmutableArray();
EgsExchangeCode = EosInterface.Login.ParseEgsExchangeCode(args);
try
{
ConnectCommand = ToolBox.ParseConnectCommand(ConsoleArguments);
ConnectCommand = Barotrauma.Networking.ConnectCommand.Parse(ConsoleArguments);
}
catch (IndexOutOfRangeException e)
{
@@ -273,6 +281,16 @@ namespace Barotrauma
Window.FileDropped += OnFileDropped;
}
public static void ExecuteAfterContentFinishedLoading(Action action)
{
if (contentLoaded)
{
action();
return;
}
postContentLoadActions.Enqueue(action);
}
public static void OnFileDropped(object sender, FileDropEventArgs args)
{
if (!(Screen.Selected is { } screen)) { return; }
@@ -419,6 +437,8 @@ namespace Barotrauma
WaitForLanguageSelection = GameSettings.CurrentConfig.Language == LanguageIdentifier.None
};
Eos.EosAccount.LoginPlatformSpecific();
initialLoadingThread = new Thread(Load);
initialLoadingThread.Start();
}
@@ -486,6 +506,7 @@ namespace Barotrauma
TextManager.VerifyLanguageAvailable();
SocialOverlay.Init();
DebugConsole.Init();
ContentPackageManager.LogEnabledRegularPackageErrors();
@@ -517,25 +538,27 @@ namespace Barotrauma
TitleScreen.LoadState = 85.0f;
#if USE_STEAM
if (SteamManager.IsInitialized)
{
Steamworks.SteamFriends.OnGameRichPresenceJoinRequested += OnInvitedToGame;
Steamworks.SteamFriends.OnGameLobbyJoinRequested += OnLobbyJoinRequested;
Steamworks.SteamFriends.OnGameRichPresenceJoinRequested += OnInvitedToSteamGame;
Steamworks.SteamFriends.OnGameLobbyJoinRequested += OnSteamLobbyJoinRequested;
if (SteamManager.TryGetUnlockedAchievements(out List<Steamworks.Data.Achievement> achievements))
{
//check the achievements too, so we don't consider people who've played the game before this "gamelaunchcount" stat was added as being 1st-time-players
//(people who have played previous versions, but not unlocked any achievements, will be incorrectly considered 1st-time-players, but that should be a small enough group to not skew the statistics)
if (!achievements.Any() && SteamManager.GetStatInt("gamelaunchcount".ToIdentifier()) <= 0)
if (!achievements.Any() && SteamManager.GetStatInt(AchievementStat.GameLaunchCount) <= 0)
{
IsFirstLaunch = true;
GameAnalyticsManager.AddDesignEvent("FirstLaunch");
}
}
SteamManager.IncrementStat("gamelaunchcount".ToIdentifier(), 1);
SteamManager.IncrementStat(AchievementStat.GameLaunchCount, 1);
}
#endif
Eos.EosAccount.ExecuteAfterLogin(ProcessLaunchCountEos);
SubEditorScreen = new SubEditorScreen();
TestScreen = new TestScreen();
@@ -567,10 +590,48 @@ namespace Barotrauma
TitleScreen.LoadState = 100.0f;
HasLoaded = true;
log("LOADING COROUTINE FINISHED");
#if CLIENT
LuaCsInstaller.CheckUpdate();
#endif
contentLoaded = true;
while (postContentLoadActions.TryDequeue(out Action action))
{
action();
}
}
private static void ProcessLaunchCountEos()
{
if (!EosInterface.Core.IsInitialized) { return; }
static void trySetConnectCommand(string commandStr)
{
Instance.ConnectCommand = Instance.ConnectCommand.Fallback(Networking.ConnectCommand.Parse(commandStr));
}
EosInterface.Presence.OnJoinGame.Register("onJoinGame".ToIdentifier(), static jgi => trySetConnectCommand(jgi.JoinCommand));
EosInterface.Presence.OnInviteAccepted.Register("onInviteAccepted".ToIdentifier(), static aii => trySetConnectCommand(aii.JoinCommand));
TaskPool.AddWithResult("Eos.GameMain.Load.QueryStats", EosInterface.Achievements.QueryStats(AchievementStat.GameLaunchCount), static result =>
{
result.Match(
success: static stats =>
{
if (!stats.TryGetValue(AchievementStat.GameLaunchCount, out int launchCount)) { return; }
if (launchCount > 0) { return; }
IsFirstLaunch = true;
GameAnalyticsManager.AddDesignEvent("FirstLaunch_Epic");
},
failure: static error => DebugConsole.ThrowError($"Failed to query stats for launch count: {error}")
);
TaskPool.Add("Eos.GameMain.Load.IngestStat", EosInterface.Achievements.IngestStats((AchievementStat.GameLaunchCount, 1)), TaskPool.IgnoredCallback);
});
}
/// <summary>
@@ -587,13 +648,13 @@ namespace Barotrauma
MainThread = null;
}
public void OnInvitedToGame(Steamworks.Friend friend, string connectCommand) => OnInvitedToGame(connectCommand);
private void OnInvitedToSteamGame(Steamworks.Friend friend, string connectCommand) => OnInvitedToSteamGame(connectCommand);
public void OnInvitedToGame(string connectCommand)
private void OnInvitedToSteamGame(string connectCommand)
{
try
{
ConnectCommand = ToolBox.ParseConnectCommand(ToolBox.SplitCommand(connectCommand));
ConnectCommand = Barotrauma.Networking.ConnectCommand.Parse(ToolBox.SplitCommand(connectCommand));
}
catch (IndexOutOfRangeException e)
{
@@ -606,7 +667,7 @@ namespace Barotrauma
}
}
public void OnLobbyJoinRequested(Steamworks.Data.Lobby lobby, Steamworks.SteamId friendId)
private void OnSteamLobbyJoinRequested(Steamworks.Data.Lobby lobby, Steamworks.SteamId friendId)
{
SteamManager.JoinLobby(lobby.Id, true);
}
@@ -661,6 +722,8 @@ namespace Barotrauma
PlayerInput.Update(Timing.Step);
SocialOverlay.Instance?.Update();
if (loadingScreenOpen)
{
//reset accumulator if loading
@@ -735,16 +798,16 @@ namespace Barotrauma
}
MainMenuScreen.Select();
if (connectCommand.EndpointOrLobby.TryGet(out ulong lobbyId))
if (connectCommand.SteamLobbyIdOption.TryUnwrap(out var lobbyId))
{
SteamManager.JoinLobby(lobbyId, joinServer: true);
SteamManager.JoinLobby(lobbyId.Value, joinServer: true);
}
else if (connectCommand.EndpointOrLobby.TryGet(out ConnectCommand.NameAndEndpoint nameAndEndpoint)
&& nameAndEndpoint is { ServerName: var serverName, Endpoint: var endpoint })
else if (connectCommand.NameAndP2PEndpointsOption.TryUnwrap(out var nameAndEndpoint)
&& nameAndEndpoint is { ServerName: var serverName, Endpoints: var endpoints })
{
Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername()),
endpoint,
string.IsNullOrWhiteSpace(serverName) ? endpoint.StringRepresentation : serverName,
endpoints.Cast<Endpoint>().ToImmutableArray(),
string.IsNullOrWhiteSpace(serverName) ? endpoints.First().StringRepresentation : serverName,
Option<int>.None());
}
@@ -753,6 +816,18 @@ namespace Barotrauma
SoundPlayer.Update((float)Timing.Step);
if ((PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
&& (PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift))
&& PlayerInput.KeyHit(Keys.Tab)
&& SocialOverlay.Instance is { } socialOverlay)
{
socialOverlay.IsOpen = !socialOverlay.IsOpen;
if (socialOverlay.IsOpen)
{
socialOverlay.RefreshFriendList();
}
}
if (PlayerInput.KeyHit(Keys.Escape) && WindowActive)
{
// Check if a text input is selected.
@@ -764,15 +839,16 @@ namespace Barotrauma
}
GUI.KeyboardDispatcher.Subscriber = null;
}
else if (SocialOverlay.Instance is { IsOpen: true })
{
SocialOverlay.Instance.IsOpen = false;
}
//if a verification prompt (are you sure you want to x) is open, close it
else if (GUIMessageBox.VisibleBox as GUIMessageBox != null &&
GUIMessageBox.VisibleBox.UserData as string == "verificationprompt")
else if (GUIMessageBox.VisibleBox is GUIMessageBox { UserData: "verificationprompt" })
{
((GUIMessageBox)GUIMessageBox.VisibleBox).Close();
}
else if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary &&
roundSummary.ContinueButton != null &&
roundSummary.ContinueButton.Visible)
else if (GUIMessageBox.VisibleBox?.UserData is RoundSummary { ContinueButton.Visible: true })
{
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
}
@@ -784,8 +860,7 @@ namespace Barotrauma
{
gameSession.ToggleTabMenu();
}
else if (GUIMessageBox.VisibleBox as GUIMessageBox != null &&
GUIMessageBox.VisibleBox.UserData as string == "bugreporter")
else if (GUIMessageBox.VisibleBox is GUIMessageBox { UserData: "bugreporter" })
{
((GUIMessageBox)GUIMessageBox.VisibleBox).Close();
}
@@ -912,6 +987,7 @@ namespace Barotrauma
CoroutineManager.Update(Paused, (float)Timing.Step);
SteamManager.Update((float)Timing.Step);
EosInterface.Core.Update();
TaskPool.Update();
@@ -1109,14 +1185,16 @@ namespace Barotrauma
public void ShowBugReporter()
{
if (GUIMessageBox.VisibleBox != null && GUIMessageBox.VisibleBox.UserData as string == "bugreporter")
if (GUIMessageBox.VisibleBox != null &&
GUIMessageBox.VisibleBox.UserData as string == "bugreporter")
{
return;
}
var msgBox = new GUIMessageBox(TextManager.Get("bugreportbutton"), "")
{
UserData = "bugreporter"
UserData = "bugreporter",
DrawOnTop = true
};
var linkHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), msgBox.Content.RectTransform)) { Stretch = true, RelativeSpacing = 0.025f };
linkHolder.RectTransform.MaxSize = new Point(int.MaxValue, linkHolder.Rect.Height);
@@ -1129,7 +1207,7 @@ namespace Barotrauma
{
if (!SteamManager.OverlayCustomUrl(userdata as string))
{
ShowOpenUrlInWebBrowserPrompt(userdata as string);
ShowOpenUriPrompt(userdata as string);
}
msgBox.Close();
return true;
@@ -1142,7 +1220,7 @@ namespace Barotrauma
UserData = "https://github.com/Regalis11/Barotrauma/issues/new/choose",
OnClicked = (btn, userdata) =>
{
ShowOpenUrlInWebBrowserPrompt(userdata as string);
ShowOpenUriPrompt(userdata as string);
msgBox.Close();
return true;
}
@@ -1185,19 +1263,23 @@ namespace Barotrauma
base.OnExiting(sender, args);
}
public static void ShowOpenUrlInWebBrowserPrompt(string url, string promptExtensionTag = null)
public static GUIMessageBox ShowOpenUriPrompt(string url, string promptTextTag = "openlinkinbrowserprompt", string promptExtensionTag = null)
{
if (string.IsNullOrEmpty(url)) { return; }
if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt") { return; }
LocalizedString text = TextManager.GetWithVariable("openlinkinbrowserprompt", "[link]", url);
LocalizedString text = TextManager.GetWithVariable(promptTextTag, "[link]", url);
LocalizedString extensionText = TextManager.Get(promptExtensionTag);
if (!extensionText.IsNullOrEmpty())
{
text += $"\n\n{extensionText}";
}
return ShowOpenUriPrompt(url, text);
}
var msgBox = new GUIMessageBox("", text, new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
public static GUIMessageBox ShowOpenUriPrompt(string url, LocalizedString promptText)
{
if (string.IsNullOrEmpty(url)) { return null; }
if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt") { return null; }
var msgBox = new GUIMessageBox("", promptText, new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
UserData = "verificationprompt"
};
@@ -1215,6 +1297,7 @@ namespace Barotrauma
return true;
};
msgBox.Buttons[1].OnClicked = msgBox.Close;
return msgBox;
}
/*

View File

@@ -175,13 +175,11 @@ namespace Barotrauma
if (CheatsEnabled)
{
DebugConsole.CheatsEnabled = true;
#if USE_STEAM
if (!SteamAchievementManager.CheatsEnabled)
if (!AchievementManager.CheatsEnabled)
{
SteamAchievementManager.CheatsEnabled = true;
AchievementManager.CheatsEnabled = true;
new GUIMessageBox("Cheats enabled", "Cheat commands have been enabled on the campaign. You will not receive Steam Achievements until you restart the game.");
}
#endif
}
if (map == null)

View File

@@ -93,13 +93,13 @@ namespace Barotrauma.Items.Components
[Editable(0.0f, 10.0f), Serialize(1.0f, IsPropertySaveable.Yes, description: "The scale of the text displayed on the label.", alwaysUseInstanceValues: true)]
public float TextScale
{
get { return textBlock == null ? 1.0f : textBlock.TextScale; }
get { return textBlock == null ? 1.0f : textBlock.TextScale / BaseToRealTextScaleFactor; }
set
{
if (textBlock != null)
{
float prevScale = TextBlock.TextScale;
textBlock.TextScale = MathHelper.Clamp(value, 0.1f, 10.0f);
textBlock.TextScale = MathHelper.Clamp(value * BaseToRealTextScaleFactor, 0.1f, 10.0f);
if (!MathUtils.NearlyEqual(prevScale, TextBlock.TextScale))
{
SetScrollingText();
@@ -210,6 +210,8 @@ namespace Barotrauma.Items.Components
SetScrollingText();
}
private const float BaseTextSize = 12.0f;
private float BaseToRealTextScaleFactor => BaseTextSize / GUIStyle.UnscaledSmallFont.Size;
private void RecreateTextBlock()
{
textBlock = new GUITextBlock(new RectTransform(item.Rect.Size), "",
@@ -217,7 +219,7 @@ namespace Barotrauma.Items.Components
{
TextDepth = item.SpriteDepth - 0.00001f,
RoundToNearestPixel = false,
TextScale = TextScale,
TextScale = TextScale * BaseToRealTextScaleFactor,
Padding = padding * item.Scale
};
}

View File

@@ -785,7 +785,7 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull is { } currentHull && currentHull == hull)
{
Sprite pingCircle = GUIStyle.YouAreHereCircle.Value.Sprite;
Sprite? pingCircle = GUIStyle.YouAreHereCircle.Value?.Sprite;
if (pingCircle is null) { continue; }
Vector2 charPos = item.WorldPosition;
@@ -1241,7 +1241,8 @@ namespace Barotrauma.Items.Components
foreach (Vector2 blip in MiniMapBlips)
{
Vector2 parentSize = miniMapFrame.Rect.Size.ToVector2();
Sprite pingCircle = GUIStyle.PingCircle.Value.Sprite;
Sprite? pingCircle = GUIStyle.PingCircle.Value?.Sprite;
if (pingCircle is null) { continue; }
Vector2 targetSize = new Vector2(parentSize.X / 4f);
Vector2 spriteScale = targetSize / pingCircle.size;
float scale = Math.Min(blipState, maxBlipState / 2f);
@@ -1525,7 +1526,7 @@ namespace Barotrauma.Items.Components
float maxWidth = Math.Max(sizeX, sizeY);
Vector2 drawPos = new Vector2(frame.Rect.Right - sizeX, frame.Rect.Y - sizeY / 2f);
UISprite icon = GUIStyle.IconOverflowIndicator;
UISprite? icon = GUIStyle.IconOverflowIndicator;
if (icon != null)
{
const int iconPadding = 4;

View File

@@ -381,7 +381,7 @@ namespace Barotrauma.Lights
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
Vector3 glowColorHSV = ToolBox.RGBToHSV(AmbientLight);
glowColorHSV.Z = Math.Max(glowColorHSV.Z, 0.4f);
Color glowColor = ToolBox.HSVToRGB(glowColorHSV.X, glowColorHSV.Y, glowColorHSV.Z);
Color glowColor = ToolBoxCore.HSVToRGB(glowColorHSV.X, glowColorHSV.Y, glowColorHSV.Z);
Vector2 glowSpriteSize = new Vector2(gapGlowTexture.Width, gapGlowTexture.Height);
foreach (var gap in Gap.GapList)
{

View File

@@ -184,7 +184,8 @@ namespace Barotrauma
connection.CrackSegments.Clear();
connection.CrackSegments.AddRange(MathUtils.GenerateJaggedLine(
connectionStart, connectionEnd,
iterations, connectionLength * generationParams.ConnectionIndicatorDisplacementMultiplier));
iterations, connectionLength * generationParams.ConnectionIndicatorDisplacementMultiplier,
rng: Rand.GetRNG(Rand.RandSync.ServerAndClient)));
}
private void LocationChanged(Location prevLocation, Location newLocation)

View File

@@ -9,8 +9,8 @@ namespace Barotrauma
{
private static readonly LocalizedString radiationTooltip = TextManager.Get("RadiationTooltip");
private static float spriteIndex;
private readonly SpriteSheet sheet = GUIStyle.RadiationAnimSpriteSheet;
private int maxFrames => sheet.FrameCount + 1;
private readonly SpriteSheet? sheet = GUIStyle.RadiationAnimSpriteSheet;
private int maxFrames => (sheet?.FrameCount ?? 0) + 1;
private bool isHovingOver;
@@ -18,7 +18,7 @@ namespace Barotrauma
{
if (!Enabled) { return; }
UISprite uiSprite = GUIStyle.Radiation;
UISprite? uiSprite = GUIStyle.Radiation;
var (offsetX, offsetY) = Map.DrawOffset * zoom;
var (centerX, centerY) = container.Center.ToVector2();
var (halfSizeX, halfSizeY) = new Vector2(container.Width / 2f, container.Height / 2f) * zoom;
@@ -29,18 +29,21 @@ namespace Barotrauma
Vector2 spriteScale = new Vector2(zoom);
uiSprite.Sprite.DrawTiled(spriteBatch, topLeft, size, color: Params.RadiationAreaColor, startOffset: Vector2.Zero, textureScale: spriteScale);
uiSprite?.Sprite.DrawTiled(spriteBatch, topLeft, size, color: Params.RadiationAreaColor, startOffset: Vector2.Zero, textureScale: spriteScale);
Vector2 topRight = topLeft + Vector2.UnitX * size.X;
int index = 0;
for (float i = 0; i <= size.Y; i += sheet.FrameSize.Y / 2f * zoom)
if (sheet != null)
{
bool isEven = ++index % 2 == 0;
Vector2 origin = new Vector2(0.5f, 0) * sheet.FrameSize.X;
// every other sprite's animation is reversed to make it seem more chaotic
int sprite = (int) MathF.Floor(isEven ? spriteIndex : maxFrames - spriteIndex);
sheet.Draw(spriteBatch, sprite, topRight + new Vector2(0, i), Params.RadiationBorderTint, origin, 0f, spriteScale);
for (float i = 0; i <= size.Y; i += sheet.FrameSize.Y / 2f * zoom)
{
bool isEven = ++index % 2 == 0;
Vector2 origin = new Vector2(0.5f, 0) * sheet.FrameSize.X;
// every other sprite's animation is reversed to make it seem more chaotic
int sprite = (int) MathF.Floor(isEven ? spriteIndex : maxFrames - spriteIndex);
sheet.Draw(spriteBatch, sprite, topRight + new Vector2(0, i), Params.RadiationBorderTint, origin, 0f, spriteScale);
}
}
isHovingOver = container.Contains(PlayerInput.MousePosition) && PlayerInput.MousePosition.X < topLeft.X + size.X;

View File

@@ -888,12 +888,12 @@ namespace Barotrauma
var hsvBase = hsv;
hsvBase.Y *= 4f;
hsvBase.Z *= 0.8f;
btn.Color = ToolBox.HSVToRGB(hsvBase.X, hsvBase.Y, hsvBase.Z);
btn.SelectedColor = ToolBox.HSVToRGB(hsvBase.X, hsvBase.Y, hsvBase.Z);
btn.Color = ToolBoxCore.HSVToRGB(hsvBase.X, hsvBase.Y, hsvBase.Z);
btn.SelectedColor = ToolBoxCore.HSVToRGB(hsvBase.X, hsvBase.Y, hsvBase.Z);
var hsvHover = hsv;
hsvHover.Z *= 1.2f;
btn.HoverColor = ToolBox.HSVToRGB(hsvHover.X, hsvHover.Y, hsvHover.Z);
btn.HoverColor = ToolBoxCore.HSVToRGB(hsvHover.X, hsvHover.Y, hsvHover.Z);
}
public static List<MapEntity> FilteredSelectedList { get; private set; } = new List<MapEntity>();

View File

@@ -26,7 +26,9 @@ namespace Barotrauma.Networking
PrivateStart();
processInfo.Arguments += " -pipes " + writePipe.GetClientHandleAsString() + " " + readPipe.GetClientHandleAsString();
processInfo.ArgumentList.Add("-pipes");
processInfo.ArgumentList.Add(writePipe.GetClientHandleAsString());
processInfo.ArgumentList.Add(readPipe.GetClientHandleAsString());
try
{
Process = Process.Start(processInfo);

View File

@@ -0,0 +1,135 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Steam;
namespace Barotrauma.Networking;
readonly record struct ConnectCommand(
Option<ConnectCommand.NameAndP2PEndpoints> NameAndP2PEndpointsOption,
Option<ConnectCommand.NameAndLidgrenEndpoint> NameAndLidgrenEndpointOption,
Option<ConnectCommand.SteamLobbyId> SteamLobbyIdOption)
{
public bool IsClientConnectedToEndpoint()
{
if (GameMain.Client?.ClientPeer == null) { return false; }
if (NameAndP2PEndpointsOption.TryUnwrap(out var nameAndP2PEndpoints))
{
if (nameAndP2PEndpoints.Endpoints.Any(e => e.Equals(GameMain.Client.ClientPeer.ServerEndpoint))) { return true; }
}
if (NameAndLidgrenEndpointOption.TryUnwrap(out var nameAndLidgrenEndpoint))
{
if (nameAndLidgrenEndpoint.Endpoint.Equals(GameMain.Client.ClientPeer.ServerEndpoint)) { return true; }
}
if (SteamLobbyIdOption.TryUnwrap(out var steamLobbyId))
{
if (SteamManager.CurrentLobbyID == steamLobbyId.Value) { return true; }
}
return false;
}
public readonly record struct NameAndP2PEndpoints(
string ServerName,
ImmutableArray<P2PEndpoint> Endpoints);
public readonly record struct NameAndLidgrenEndpoint(
string ServerName,
LidgrenEndpoint Endpoint);
public readonly record struct SteamLobbyId(ulong Value);
public ConnectCommand(string serverName, Endpoint endpoint)
: this(
NameAndP2PEndpointsOption: endpoint is P2PEndpoint p2pEndpoint
? Option.Some(new NameAndP2PEndpoints(ServerName: serverName, p2pEndpoint.ToEnumerable().ToImmutableArray()))
: Option.None,
NameAndLidgrenEndpointOption: endpoint is LidgrenEndpoint lidgrenEndpoint
? Option.Some(new NameAndLidgrenEndpoint(ServerName: serverName, lidgrenEndpoint))
: 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) { }
public ConnectCommand(string serverName, LidgrenEndpoint endpoint)
: this(
NameAndP2PEndpointsOption: Option.None,
NameAndLidgrenEndpointOption: Option.Some(new NameAndLidgrenEndpoint(ServerName: serverName, Endpoint: endpoint)),
SteamLobbyIdOption: Option.None) { }
public ConnectCommand(SteamLobbyId lobbyId)
: this(
NameAndP2PEndpointsOption: Option.None,
NameAndLidgrenEndpointOption: Option.None,
SteamLobbyIdOption: Option.Some(lobbyId)) { }
public static Option<ConnectCommand> Parse(string str)
=> Parse(ToolBox.SplitCommand(str));
public static Option<ConnectCommand> Parse(IReadOnlyList<string>? args)
{
if (args == null || args.Count < 2) { return Option.None; }
if (args[0].Equals("-connect", StringComparison.OrdinalIgnoreCase))
{
if (args.Count < 3) { return Option.None; }
var serverName = args[1];
var endpointStrs = args[2].Split(",");
var endpoints = endpointStrs.Select(Endpoint.Parse).NotNone().ToImmutableArray();
if (endpoints.Length != endpointStrs.Length) { return Option.None; }
if (endpoints.All(e => e is P2PEndpoint))
{
return Option.Some(
new ConnectCommand(serverName, endpoints.Cast<P2PEndpoint>().ToImmutableArray()));
}
else if (endpoints.Length == 1 && endpoints[0] is LidgrenEndpoint lidgrenEndpoint)
{
return Option.Some(
new ConnectCommand(serverName, lidgrenEndpoint));
}
return Option.None;
}
else if (args[0].Equals("+connect_lobby", StringComparison.OrdinalIgnoreCase))
{
return UInt64.TryParse(args[1], out var lobbyId)
? Option.Some(new ConnectCommand(new SteamLobbyId(lobbyId)))
: Option.None;
}
return Option.None;
}
public override string ToString()
{
if (SteamLobbyIdOption.TryUnwrap(out var steamLobbyId))
{
return $"+connect_lobby {steamLobbyId.Value}";
}
if (NameAndP2PEndpointsOption.TryUnwrap(out var nameAndP2PEndpoints))
{
var escapedName = nameAndP2PEndpoints.ServerName.Replace("\"", "\\\"");
var escapedEndpoints = nameAndP2PEndpoints.Endpoints.Select(e => e.StringRepresentation).JoinEscaped(',');
return $"-connect \"{escapedName}\" {escapedEndpoints}";
}
if (NameAndLidgrenEndpointOption.TryUnwrap(out var nameAndLidgrenEndpoint))
{
var escapedName = nameAndLidgrenEndpoint.ServerName.Replace("\"", "\\\"");
var endpoint = nameAndLidgrenEndpoint.Endpoint.StringRepresentation;
return $"-connect \"{escapedName}\" {endpoint}";
}
return "";
}
}

View File

@@ -162,7 +162,7 @@ namespace Barotrauma.Networking
set;
}
private readonly Endpoint serverEndpoint;
private readonly ImmutableArray<Endpoint> serverEndpoints;
private readonly Option<int> ownerKey;
public bool IsServerOwner => ownerKey.IsSome();
@@ -182,6 +182,9 @@ namespace Barotrauma.Networking
public readonly NamedEvent<PermissionChangedEvent> OnPermissionChanged = new NamedEvent<PermissionChangedEvent>();
public GameClient(string newName, Endpoint endpoint, string serverName, Option<int> ownerKey)
: this(newName, endpoint.ToEnumerable().ToImmutableArray(), serverName, ownerKey) { }
public GameClient(string newName, ImmutableArray<Endpoint> endpoints, string serverName, Option<int> ownerKey)
{
//TODO: gui stuff should probably not be here?
this.ownerKey = ownerKey;
@@ -270,7 +273,7 @@ namespace Barotrauma.Networking
ServerSettings = new ServerSettings(this, "Server", 0, 0, 0, false, false, System.Net.IPAddress.Any);
Voting = new Voting();
serverEndpoint = endpoint;
serverEndpoints = endpoints;
InitiateServerJoin(serverName);
//ServerLog = new ServerLog("");
@@ -281,7 +284,7 @@ namespace Barotrauma.Networking
public ServerInfo CreateServerInfoFromSettings()
{
var serverInfo = ServerInfo.FromServerConnection(ClientPeer.ServerConnection, ServerSettings);
var serverInfo = ServerInfo.FromServerEndpoints(ClientPeer.AllServerEndpoints, ServerSettings);
GameMain.ServerListScreen.UpdateOrAddServerInfo(serverInfo);
return serverInfo;
}
@@ -327,11 +330,14 @@ namespace Barotrauma.Networking
ReadDataMessage,
OnClientPeerDisconnect,
OnConnectionInitializationComplete);
return serverEndpoint switch
{
LidgrenEndpoint lidgrenEndpoint => new LidgrenClientPeer(lidgrenEndpoint, callbacks, ownerKey),
SteamP2PEndpoint _ when ownerKey.TryUnwrap(out var key) => new SteamP2POwnerPeer(callbacks, key),
SteamP2PEndpoint steamP2PServerEndpoint when ownerKey.IsNone() => new SteamP2PClientPeer(steamP2PServerEndpoint, callbacks),
return serverEndpoints.First() switch
{
LidgrenEndpoint lidgrenEndpoint
=> new LidgrenClientPeer(lidgrenEndpoint, callbacks, ownerKey),
P2PEndpoint when ownerKey.TryUnwrap(out int key)
=> new P2POwnerPeer(callbacks, key, serverEndpoints.Cast<P2PEndpoint>().ToImmutableArray()),
P2PEndpoint when ownerKey.IsNone()
=> new P2PClientPeer(serverEndpoints.Cast<P2PEndpoint>().ToImmutableArray(), callbacks),
_ => throw new ArgumentOutOfRangeException()
};
}
@@ -802,6 +808,9 @@ namespace Barotrauma.Networking
case ServerPacketHeader.ACHIEVEMENT:
ReadAchievement(inc);
break;
case ServerPacketHeader.ACHIEVEMENT_STAT:
ReadAchievementStat(inc);
break;
case ServerPacketHeader.CHEATS_ENABLED:
bool cheatsEnabled = inc.ReadBoolean();
inc.ReadPadBits();
@@ -812,7 +821,7 @@ namespace Barotrauma.Networking
else
{
DebugConsole.CheatsEnabled = cheatsEnabled;
SteamAchievementManager.CheatsEnabled = cheatsEnabled;
AchievementManager.CheatsEnabled = cheatsEnabled;
if (cheatsEnabled)
{
var cheatMessageBox = new GUIMessageBox(TextManager.Get("CheatsEnabledTitle"), TextManager.Get("CheatsEnabledDescription"));
@@ -860,7 +869,7 @@ namespace Barotrauma.Networking
private void ReadStartGameFinalize(IReadMessage inc)
{
TaskPool.ListTasks();
TaskPool.ListTasks(DebugConsole.Log);
ushort contentToPreloadCount = inc.ReadUInt16();
List<ContentFile> contentToPreload = new List<ContentFile>();
for (int i = 0; i < contentToPreloadCount; i++)
@@ -997,8 +1006,9 @@ namespace Barotrauma.Networking
}
else
{
if (ClientPeer is SteamP2PClientPeer or SteamP2POwnerPeer)
if (ClientPeer is P2PClientPeer or P2POwnerPeer)
{
Eos.EosSessionManager.LeaveSession();
SteamManager.LeaveLobby();
}
@@ -1007,10 +1017,7 @@ namespace Barotrauma.Networking
GameMain.GameSession?.Campaign?.CancelStartRound();
if (SteamManager.IsInitialized)
{
Steamworks.SteamFriends.ClearRichPresence();
}
UpdatePresence("");
foreach (var fileTransfer in FileReceiver.ActiveTransfers.ToArray())
{
FileReceiver.StopTransfer(fileTransfer, deleteFile: true);
@@ -1099,19 +1106,47 @@ namespace Barotrauma.Networking
ClientPeer.ServerContentPackages = prevContentPackages;
}
}
private void OnConnectionInitializationComplete()
private void UpdatePresence(string connectCommand)
{
#warning TODO: use store localization functionality
var desc = TextManager.GetWithVariable("FriendPlayingOnServer", "[servername]", ServerName);
async Task updateEosPresence()
{
var epicIds = EosInterface.IdQueries.GetLoggedInEpicIds();
if (!epicIds.FirstOrNone().TryUnwrap(out var epicAccountId)) { return; }
var setPresenceResult = await EosInterface.Presence.SetJoinCommand(
epicAccountId: epicAccountId,
desc: desc.Value,
serverName: ServerName,
joinCommand: connectCommand);
DebugConsole.NewMessage($"Set connect command: {connectCommand}, result: {setPresenceResult}");
}
TaskPool.Add(
"UpdateEosPresence",
updateEosPresence(),
static _ => { });
if (SteamManager.IsInitialized)
{
Steamworks.SteamFriends.ClearRichPresence();
Steamworks.SteamFriends.SetRichPresence("servername", ServerName);
#warning TODO: use Steamworks localization functionality
Steamworks.SteamFriends.SetRichPresence("status",
TextManager.GetWithVariable("FriendPlayingOnServer", "[servername]", ServerName).Value);
Steamworks.SteamFriends.SetRichPresence("connect",
$"-connect \"{ToolBox.EscapeCharacters(ServerName)}\" {serverEndpoint}");
if (!connectCommand.IsNullOrWhiteSpace())
{
Steamworks.SteamFriends.SetRichPresence("servername", ServerName);
Steamworks.SteamFriends.SetRichPresence("status",
desc.Value);
Steamworks.SteamFriends.SetRichPresence("connect",
connectCommand);
}
}
}
private void OnConnectionInitializationComplete()
{
UpdatePresence($"-connect \"{ToolBox.EscapeCharacters(ServerName)}\" {string.Join(",", serverEndpoints.Select(e => e.StringRepresentation))}");
canStart = true;
connected = true;
@@ -1169,18 +1204,16 @@ namespace Barotrauma.Networking
}
private void ReadAchievement(IReadMessage inc)
private static void ReadAchievement(IReadMessage inc)
{
Identifier achievementIdentifier = inc.ReadIdentifier();
int amount = inc.ReadInt32();
if (amount == 0)
{
SteamAchievementManager.UnlockAchievement(achievementIdentifier);
}
else
{
SteamAchievementManager.IncrementStat(achievementIdentifier, amount);
}
AchievementManager.UnlockAchievement(achievementIdentifier);
}
private static void ReadAchievementStat(IReadMessage inc)
{
var netStat = INetSerializableStruct.Read<NetIncrementedStat>(inc);
AchievementManager.IncrementStat(netStat.Stat, netStat.Amount);
}
private static void ReadCircuitBoxMessage(IReadMessage inc)
@@ -1887,8 +1920,9 @@ namespace Barotrauma.Networking
}
if (updateClientListId) { LastClientListUpdateID = listId; }
if (ClientPeer is SteamP2POwnerPeer)
if (ClientPeer is P2POwnerPeer)
{
Eos.EosSessionManager.UpdateOwnedSession(ClientPeer.ServerConnection.Endpoint, ServerSettings);
TaskPool.Add("WaitForPingDataAsync (owner)",
Steamworks.SteamNetworkingUtils.WaitForPingDataAsync(), (task) =>
{
@@ -2031,8 +2065,9 @@ namespace Barotrauma.Networking
ServerSettings.AllowSubVoting = allowSubVoting;
ServerSettings.AllowModeVoting = allowModeVoting;
if (ClientPeer is SteamP2POwnerPeer)
if (ClientPeer is P2POwnerPeer)
{
Eos.EosSessionManager.UpdateOwnedSession(ClientPeer.ServerConnection.Endpoint, ServerSettings);
Steam.SteamManager.UpdateLobby(ServerSettings);
}

View File

@@ -0,0 +1,82 @@
#nullable enable
namespace Barotrauma.Networking;
sealed class DualStackP2PSocket : P2PSocket
{
private readonly Option<EosP2PSocket> eosSocket;
private readonly Option<SteamListenSocket> steamSocket;
private DualStackP2PSocket(
Callbacks callbacks,
Option<EosP2PSocket> eosSocket,
Option<SteamListenSocket> steamSocket) :
base(callbacks)
{
this.eosSocket = eosSocket;
this.steamSocket = steamSocket;
}
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
{
var eosP2PSocketResult = EosP2PSocket.Create(callbacks);
var steamP2PSocketResult = SteamListenSocket.Create(callbacks);
if (eosP2PSocketResult.TryUnwrapFailure(out var eosError)
&& steamP2PSocketResult.TryUnwrapFailure(out var steamError))
{
return Result.Failure(new Error(eosError, steamError));
}
return Result.Success((P2PSocket)new DualStackP2PSocket(
callbacks,
eosP2PSocketResult.TryUnwrapSuccess(out var eosP2PSocket)
? Option.Some((EosP2PSocket)eosP2PSocket)
: Option.None,
steamP2PSocketResult.TryUnwrapSuccess(out var steamP2PSocket)
? Option.Some((SteamListenSocket)steamP2PSocket)
: Option.None));
}
public override void ProcessIncomingMessages()
{
if (eosSocket.TryUnwrap(out var eosP2PSocket)) { eosP2PSocket.ProcessIncomingMessages(); }
if (steamSocket.TryUnwrap(out var steamP2PSocket)) { steamP2PSocket.ProcessIncomingMessages(); }
}
public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
{
return endpoint switch
{
EosP2PEndpoint eosP2PEndpoint when eosSocket.TryUnwrap(out var eosP2PSocket)
=> eosP2PSocket.SendMessage(eosP2PEndpoint, outMsg, deliveryMethod),
SteamP2PEndpoint steamP2PEndpoint when steamSocket.TryUnwrap(out var steamP2PSocket)
=> steamP2PSocket.SendMessage(steamP2PEndpoint, outMsg, deliveryMethod),
_
=> false
};
}
public override void CloseConnection(P2PEndpoint endpoint)
{
switch (endpoint)
{
case EosP2PEndpoint eosP2PEndpoint:
if (eosSocket.TryUnwrap(out var eosP2PSocket))
{
eosP2PSocket.CloseConnection(eosP2PEndpoint);
}
break;
case SteamP2PEndpoint steamP2PEndpoint:
if (steamSocket.TryUnwrap(out var steamP2PSocket))
{
steamP2PSocket.CloseConnection(steamP2PEndpoint);
}
break;
}
}
public override void Dispose()
{
if (eosSocket.TryUnwrap(out var eosP2PSocket)) { eosP2PSocket.Dispose(); }
if (steamSocket.TryUnwrap(out var steamP2PSocket)) { steamP2PSocket.Dispose(); }
}
}

View File

@@ -0,0 +1,99 @@
#nullable enable
namespace Barotrauma.Networking;
sealed class EosP2PSocket : P2PSocket
{
private readonly EosInterface.P2PSocket eosSocket;
private EosP2PSocket(
Callbacks callbacks,
EosInterface.P2PSocket eosSocket)
: base(callbacks)
{
this.eosSocket = eosSocket;
}
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
{
if (!EosInterface.Core.IsInitialized) { return Result.Failure(new Error(ErrorCode.EosNotInitialized)); }
var eosSocketId = new EosInterface.SocketId { SocketName = EosP2PEndpoint.SocketName };
if (EosInterface.IdQueries.GetLoggedInPuids() is not { Length: > 0 } puids)
{
return Result.Failure(new Error(ErrorCode.EosNotLoggedIn));
}
var socketCreateResult = EosInterface.P2PSocket.Create(puids[0], eosSocketId);
if (!socketCreateResult.TryUnwrapSuccess(out var eosSocket)) { return Result.Failure(new Error(ErrorCode.FailedToCreateEosP2PSocket, socketCreateResult.ToString())); }
var retVal = new EosP2PSocket(callbacks, eosSocket);
eosSocket.HandleIncomingConnection.Register("Event".ToIdentifier(), retVal.OnIncomingConnection);
eosSocket.HandleClosedConnection.Register("Event".ToIdentifier(), retVal.OnConnectionClosed);
return Result.Success((P2PSocket)retVal);
}
public override void ProcessIncomingMessages()
{
foreach (var msg in eosSocket.GetMessageBatch())
{
callbacks.OnData(new EosP2PEndpoint(msg.Sender), new ReadWriteMessage(msg.Buffer, 0, msg.ByteLength * 8, false));
}
}
public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
{
if (endpoint is not EosP2PEndpoint { ProductUserId: var puid }) { return false; }
var sendResult = eosSocket.SendMessage(new EosInterface.P2PSocket.OutgoingMessage(
Buffer: outMsg.Buffer,
ByteLength: outMsg.LengthBytes,
Destination: puid,
DeliveryMethod: deliveryMethod));
return sendResult.IsSuccess;
}
private void OnIncomingConnection(EosInterface.P2PSocket.IncomingConnectionRequest request)
{
var remoteEndpoint = new EosP2PEndpoint(request.RemoteUserId);
if (callbacks.OnIncomingConnection(remoteEndpoint))
{
request.Accept();
}
}
private void OnConnectionClosed(EosInterface.P2PSocket.RemoteConnectionClosed data)
{
var remoteEndpoint = new EosP2PEndpoint(data.RemoteUserId);
var peerDisconnectPacket = PeerDisconnectPacket.WithReason(data.Reason switch
{
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.Unknown => DisconnectReason.Unknown,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.ClosedByLocalUser => DisconnectReason.Disconnected,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.ClosedByPeer => DisconnectReason.Disconnected,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.TimedOut => DisconnectReason.Timeout,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.TooManyConnections => DisconnectReason.ServerFull,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.InvalidMessage => DisconnectReason.Unknown,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.InvalidData => DisconnectReason.Unknown,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.ConnectionFailed => DisconnectReason.AuthenticationFailed,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.ConnectionClosed => DisconnectReason.Disconnected,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.NegotiationFailed => DisconnectReason.AuthenticationFailed,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.UnexpectedError => DisconnectReason.Unknown,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.Unhandled => DisconnectReason.Unknown,
_ => DisconnectReason.Unknown
});
callbacks.OnConnectionClosed(remoteEndpoint, peerDisconnectPacket);
}
public override void CloseConnection(P2PEndpoint endpoint)
{
if (endpoint is not EosP2PEndpoint { ProductUserId: var puid }) { return; }
eosSocket.CloseConnection(puid);
}
public override void Dispose()
{
eosSocket.Dispose();
}
}

View File

@@ -0,0 +1,56 @@
#nullable enable
using System;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Networking;
abstract class P2PSocket : IDisposable
{
public enum ErrorCode
{
EosNotInitialized,
EosNotLoggedIn,
FailedToCreateEosP2PSocket,
SteamNotInitialized,
FailedToCreateSteamP2PSocket
}
public readonly record struct Error(
ImmutableArray<(ErrorCode Code, string AdditionalInfo)> CodesAndInfo)
{
public Error(ErrorCode code, string? additionalInfo = "") : this((code, additionalInfo ?? "").ToEnumerable().ToImmutableArray()) { }
public Error(params Error[] innerErrors) : this(innerErrors.SelectMany(ie => ie.CodesAndInfo).ToImmutableArray()) { }
public override string? ToString()
{
if (CodesAndInfo.IsDefault)
{
return "default(Error)";
}
return $"Errors({string.Join("; ", CodesAndInfo)})";
}
}
public readonly record struct Callbacks(
Predicate<P2PEndpoint> OnIncomingConnection,
Action<P2PEndpoint, PeerDisconnectPacket> OnConnectionClosed,
Action<P2PEndpoint, IReadMessage> OnData);
protected readonly Callbacks callbacks;
protected P2PSocket(Callbacks callbacks)
{
this.callbacks = callbacks;
}
public abstract void ProcessIncomingMessages();
public abstract bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod);
public abstract void CloseConnection(P2PEndpoint endpoint);
public abstract void Dispose();
}

View File

@@ -0,0 +1,114 @@
using System;
using System.Runtime.InteropServices;
using Barotrauma.Steam;
namespace Barotrauma.Networking;
sealed class SteamConnectSocket : P2PSocket
{
private sealed class ConnectionManager : Steamworks.ConnectionManager, Steamworks.IConnectionManager
{
private SteamP2PEndpoint endpoint;
private Callbacks callbacks;
public void SetEndpointAndCallbacks(SteamP2PEndpoint endpoint, Callbacks callbacks)
{
this.endpoint = endpoint;
this.callbacks = callbacks;
}
public override void OnMessage(IntPtr data, int size, long messageNum, long recvTime, int channel)
{
var dataArray = new byte[size];
Marshal.Copy(source: data, destination: dataArray, startIndex: 0, length: size);
callbacks.OnData(endpoint, new ReadWriteMessage(dataArray, bitPos: 0, lBits: size * 8, copyBuf: false));
}
public override void OnDisconnected(Steamworks.Data.ConnectionInfo info)
{
if (!info.Identity.IsSteamId) { return; }
var remoteEndpoint = new SteamP2PEndpoint(new SteamId((Steamworks.SteamId)info.Identity));
var peerDisconnectPacket = PeerDisconnectPacket.WithReason(info.EndReason switch
{
Steamworks.NetConnectionEnd.App_Generic => DisconnectReason.Disconnected,
Steamworks.NetConnectionEnd.AppException_Generic => DisconnectReason.Unknown,
Steamworks.NetConnectionEnd.Local_OfflineMode => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_ManyRelayConnectivity => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_HostedServerPrimaryRelay => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_NetworkConfig => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_Rights => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_P2P_ICE_NoPublicAddresses => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_Timeout => DisconnectReason.SteamP2PTimeOut,
Steamworks.NetConnectionEnd.Remote_BadCrypt => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_BadCert => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_BadProtocolVersion => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_P2P_ICE_NoPublicAddresses => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_Generic => DisconnectReason.Unknown,
Steamworks.NetConnectionEnd.Misc_InternalError => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_Timeout => DisconnectReason.SteamP2PTimeOut,
Steamworks.NetConnectionEnd.Misc_SteamConnectivity => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_NoRelaySessionsToClient => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_P2P_Rendezvous => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_P2P_NAT_Firewall => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_PeerSentNoConnection => DisconnectReason.SteamP2PError,
_ => DisconnectReason.Unknown
});
callbacks.OnConnectionClosed(remoteEndpoint, peerDisconnectPacket);
base.OnDisconnected(info);
}
}
private readonly SteamP2PEndpoint expectedEndpoint;
private readonly ConnectionManager connectionManager;
private SteamConnectSocket(SteamP2PEndpoint expectedEndpoint, Callbacks callbacks, ConnectionManager connectionManager) : base(callbacks)
{
this.expectedEndpoint = expectedEndpoint;
this.connectionManager = connectionManager;
}
public static Result<P2PSocket, Error> Create(SteamP2PEndpoint endpoint, Callbacks callbacks)
{
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
var connectionManager = Steamworks.SteamNetworkingSockets.ConnectRelay<ConnectionManager>(endpoint.SteamId.Value);
if (connectionManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
connectionManager.SetEndpointAndCallbacks(endpoint, callbacks);
return Result.Success((P2PSocket)new SteamConnectSocket(endpoint, callbacks, connectionManager));
}
public override void ProcessIncomingMessages()
{
connectionManager.Receive();
}
public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
{
if (endpoint != expectedEndpoint) { return false; }
var result = connectionManager.Connection.SendMessage(
data: outMsg.Buffer,
offset: 0,
length: outMsg.LengthBytes,
sendType: deliveryMethod switch
{
DeliveryMethod.Reliable => Steamworks.Data.SendType.Reliable,
_ => Steamworks.Data.SendType.Unreliable
});
return result == Steamworks.Result.OK;
}
public override void CloseConnection(P2PEndpoint endpoint)
{
if (endpoint != expectedEndpoint) { return; }
connectionManager.Close();
}
public override void Dispose()
{
connectionManager.Close();
}
}

View File

@@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Barotrauma.Steam;
namespace Barotrauma.Networking;
sealed class SteamListenSocket : P2PSocket
{
private sealed class SocketManager : Steamworks.SocketManager, Steamworks.ISocketManager
{
private Callbacks callbacks;
private readonly Dictionary<SteamP2PEndpoint, Steamworks.Data.Connection> endpointToConnection = new();
public void SetCallbacks(Callbacks callbacks)
{
this.callbacks = callbacks;
}
public override void OnConnecting(Steamworks.Data.Connection connection, Steamworks.Data.ConnectionInfo info)
{
if (!info.Identity.IsSteamId) { return; }
var remoteEndpoint = new SteamP2PEndpoint(new SteamId((Steamworks.SteamId)info.Identity));
endpointToConnection[remoteEndpoint] = connection;
if (callbacks.OnIncomingConnection(remoteEndpoint))
{
connection.Accept();
}
}
public override void OnDisconnected(Steamworks.Data.Connection connection, Steamworks.Data.ConnectionInfo info)
{
if (!info.Identity.IsSteamId) { return; }
var remoteEndpoint = new SteamP2PEndpoint(new SteamId((Steamworks.SteamId)info.Identity));
endpointToConnection.Remove(remoteEndpoint);
var peerDisconnectPacket = PeerDisconnectPacket.WithReason(info.EndReason switch
{
Steamworks.NetConnectionEnd.App_Generic => DisconnectReason.Disconnected,
Steamworks.NetConnectionEnd.AppException_Generic => DisconnectReason.Unknown,
Steamworks.NetConnectionEnd.Local_OfflineMode => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_ManyRelayConnectivity => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_HostedServerPrimaryRelay => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_NetworkConfig => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_Rights => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_P2P_ICE_NoPublicAddresses => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_Timeout => DisconnectReason.SteamP2PTimeOut,
Steamworks.NetConnectionEnd.Remote_BadCrypt => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_BadCert => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_BadProtocolVersion => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_P2P_ICE_NoPublicAddresses => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_Generic => DisconnectReason.Unknown,
Steamworks.NetConnectionEnd.Misc_InternalError => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_Timeout => DisconnectReason.SteamP2PTimeOut,
Steamworks.NetConnectionEnd.Misc_SteamConnectivity => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_NoRelaySessionsToClient => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_P2P_Rendezvous => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_P2P_NAT_Firewall => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_PeerSentNoConnection => DisconnectReason.SteamP2PError,
_ => DisconnectReason.Unknown
});
callbacks.OnConnectionClosed(remoteEndpoint, peerDisconnectPacket);
base.OnDisconnected(connection, info);
}
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; }
var endpoint = new SteamP2PEndpoint(new SteamId((Steamworks.SteamId)identity));
var dataArray = new byte[size];
Marshal.Copy(source: data, destination: dataArray, startIndex: 0, length: size);
callbacks.OnData(endpoint, new ReadWriteMessage(dataArray, bitPos: 0, lBits: size * 8, copyBuf: false));
}
internal bool SendMessage(SteamP2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
{
if (!endpointToConnection.TryGetValue(endpoint, out var connection))
{
return false;
}
var result = connection.SendMessage(
data: outMsg.Buffer,
offset: 0,
length: outMsg.LengthBytes,
sendType: deliveryMethod switch
{
DeliveryMethod.Reliable => Steamworks.Data.SendType.Reliable,
_ => Steamworks.Data.SendType.Unreliable
});
return result == Steamworks.Result.OK;
}
internal void CloseConnection(SteamP2PEndpoint endpoint)
{
if (!endpointToConnection.TryGetValue(endpoint, out var connection)) { return; }
connection.Close();
}
}
private readonly SocketManager socketManager;
private SteamListenSocket(
Callbacks callbacks,
SocketManager socketManager)
: base(callbacks)
{
this.socketManager = socketManager;
}
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
{
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
var socketManager = Steamworks.SteamNetworkingSockets.CreateRelaySocket<SocketManager>();
if (socketManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
socketManager.SetCallbacks(callbacks);
return Result.Success((P2PSocket)new SteamListenSocket(callbacks, socketManager));
}
public override void ProcessIncomingMessages()
{
socketManager.Receive();
}
public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
{
if (endpoint is not SteamP2PEndpoint steamP2PEndpoint) { return false; }
return socketManager.SendMessage(steamP2PEndpoint, outMsg, deliveryMethod);
}
public override void CloseConnection(P2PEndpoint endpoint)
{
if (endpoint is not SteamP2PEndpoint steamP2PEndpoint) { return; }
socketManager.CloseConnection(steamP2PEndpoint);
}
public override void Dispose()
{
socketManager.Close();
}
}

View File

@@ -1,12 +1,18 @@
#nullable enable
using Barotrauma.Steam;
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
namespace Barotrauma.Networking
{
internal abstract class ClientPeer<TEndpoint> : ClientPeer where TEndpoint : Endpoint
{
public new TEndpoint ServerEndpoint => (base.ServerEndpoint as TEndpoint)!;
protected ClientPeer(TEndpoint serverEndpoint, ImmutableArray<Endpoint> allServerEndpoints, Callbacks callbacks, Option<int> ownerKey)
: base(serverEndpoint, allServerEndpoints, callbacks, ownerKey) { }
}
internal abstract class ClientPeer
{
public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } =
@@ -25,21 +31,22 @@ namespace Barotrauma.Networking
protected readonly Callbacks callbacks;
public readonly Endpoint ServerEndpoint;
public readonly ImmutableArray<Endpoint> AllServerEndpoints;
public NetworkConnection? ServerConnection { get; protected set; }
protected readonly bool isOwner;
protected bool IsOwner => ownerKey.IsSome();
protected readonly Option<int> ownerKey;
public bool IsActive => isActive;
protected bool isActive;
public ClientPeer(Endpoint serverEndpoint, Callbacks callbacks, Option<int> ownerKey)
protected ClientPeer(Endpoint serverEndpoint, ImmutableArray<Endpoint> allServerEndpoints, Callbacks callbacks, Option<int> ownerKey)
{
ServerEndpoint = serverEndpoint;
AllServerEndpoints = allServerEndpoints;
this.callbacks = callbacks;
this.ownerKey = ownerKey;
isOwner = ownerKey.IsSome();
}
public abstract void Start();
@@ -53,7 +60,7 @@ namespace Barotrauma.Networking
protected ConnectionInitialization initializationStep;
public bool ContentPackageOrderReceived { get; set; }
protected int passwordSalt;
protected Option<Steamworks.AuthTicket> steamAuthTicket;
protected Option<AuthenticationTicket> authTicket;
private GUIMessageBox? passwordMsgBox;
public bool WaitingForPassword
@@ -67,43 +74,64 @@ namespace Barotrauma.Networking
public IReadMessage Message;
}
protected abstract Task<Option<AccountId>> GetAccountId();
protected void OnInitializationComplete()
{
passwordMsgBox?.Close();
if (initializationStep == ConnectionInitialization.Success) { return; }
callbacks.OnInitializationComplete.Invoke();
initializationStep = ConnectionInitialization.Success;
}
protected void ReadConnectionInitializationStep(IncomingInitializationMessage inc)
{
if (inc.InitializationStep != ConnectionInitialization.Password)
{
passwordMsgBox?.Close();
}
switch (inc.InitializationStep)
{
case ConnectionInitialization.SteamTicketAndVersion:
case ConnectionInitialization.AuthInfoAndVersion:
{
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
if (initializationStep != ConnectionInitialization.AuthInfoAndVersion) { return; }
PeerPacketHeaders headers = new PeerPacketHeaders
TaskPool.Add($"{GetType().Name}.{nameof(GetAccountId)}", GetAccountId(), t =>
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.SteamTicketAndVersion
};
if (GameMain.Client?.ClientPeer is null) { return; }
if (!t.TryGetResult(out Option<AccountId> accountId))
{
Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
}
var headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.AuthInfoAndVersion
};
if (steamAuthTicket.TryUnwrap(out var authTicket) && authTicket is { Canceled: true })
{
throw new InvalidOperationException("ReadConnectionInitializationStep failed: Steam auth ticket has been cancelled.");
}
var body = new ClientAuthTicketAndVersionPacket
{
Name = GameMain.Client.Name,
OwnerKey = ownerKey,
AccountId = accountId,
AuthTicket = authTicket,
GameVersion = GameMain.Version.ToString(),
Language = GameSettings.CurrentConfig.Language.Value
};
ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket
{
Name = GameMain.Client.Name,
OwnerKey = ownerKey,
SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id),
SteamAuthTicket = steamAuthTicket.Bind(t => t.Data != null ? Option.Some(t.Data) : Option.None),
GameVersion = GameMain.Version.ToString(),
Language = GameSettings.CurrentConfig.Language.Value
};
SendMsgInternal(headers, body);
SendMsgInternal(headers, body);
});
break;
}
case ConnectionInitialization.ContentPackageOrder:
{
if (initializationStep
is ConnectionInitialization.SteamTicketAndVersion
is ConnectionInitialization.AuthInfoAndVersion
or ConnectionInitialization.Password)
{
initializationStep = ConnectionInitialization.ContentPackageOrder;
@@ -136,7 +164,7 @@ namespace Barotrauma.Networking
break;
}
case ConnectionInitialization.Password:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
if (initializationStep == ConnectionInitialization.AuthInfoAndVersion)
{
initializationStep = ConnectionInitialization.Password;
}
@@ -152,6 +180,7 @@ namespace Barotrauma.Networking
LocalizedString pwMsg = TextManager.Get("PasswordRequired");
passwordMsgBox?.Close();
passwordMsgBox = new GUIMessageBox(pwMsg, "", new LocalizedString[] { TextManager.Get("OK"), TextManager.Get("Cancel") },
relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, GUI.IntScale(170)));
var passwordHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), passwordMsgBox.Content.RectTransform), childAnchor: Anchor.TopCenter);

View File

@@ -1,26 +1,25 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Lidgren.Network;
using Barotrauma.Steam;
using System.Net.Sockets;
namespace Barotrauma.Networking
{
internal sealed class LidgrenClientPeer : ClientPeer
internal sealed class LidgrenClientPeer : ClientPeer<LidgrenEndpoint>
{
private NetClient? netClient;
private readonly NetPeerConfiguration netPeerConfiguration;
private readonly List<NetIncomingMessage> incomingLidgrenMessages;
private LidgrenEndpoint lidgrenEndpoint =>
ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result }
? result
: throw new InvalidOperationException();
public LidgrenClientPeer(LidgrenEndpoint endpoint, Callbacks callbacks, Option<int> ownerKey) : base(endpoint, callbacks, ownerKey)
public LidgrenClientPeer(LidgrenEndpoint endpoint, Callbacks callbacks, Option<int> ownerKey) : base(endpoint, ((Endpoint)endpoint).ToEnumerable().ToImmutableArray(), callbacks, ownerKey)
{
ServerConnection = null;
@@ -56,18 +55,9 @@ namespace Barotrauma.Networking
netClient = new NetClient(netPeerConfiguration);
if (SteamManager.IsInitialized)
{
steamAuthTicket = SteamManager.GetAuthSessionTicketForMultiplayer(ServerEndpoint);
if (steamAuthTicket.IsNone())
{
throw new Exception("GetAuthSessionTicket returned null");
}
}
initializationStep = ConnectionInitialization.AuthInfoAndVersion;
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpointValue))
if (ServerEndpoint is not { } lidgrenEndpointValue)
{
throw new InvalidCastException($"Endpoint is not {nameof(LidgrenEndpoint)}");
}
@@ -79,12 +69,25 @@ namespace Barotrauma.Networking
netClient.Start();
var netConnection = netClient.Connect(lidgrenEndpointValue.NetEndpoint);
TaskPool.Add(
$"{nameof(LidgrenClientPeer)}.GetAuthTicket",
AuthenticationTicket.Create(ServerEndpoint),
t =>
{
if (!t.TryGetResult(out Option<AuthenticationTicket> authenticationTicket))
{
Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
return;
}
authTicket = authenticationTicket;
ServerConnection = new LidgrenConnection(netConnection)
{
Status = NetworkConnectionStatus.Connected
};
var netConnection = netClient.Connect(lidgrenEndpointValue.NetEndpoint);
ServerConnection = new LidgrenConnection(netConnection)
{
Status = NetworkConnectionStatus.Connected
};
});
isActive = true;
}
@@ -96,7 +99,7 @@ namespace Barotrauma.Networking
ToolBox.ThrowIfNull(netClient);
ToolBox.ThrowIfNull(incomingLidgrenMessages);
if (isOwner && !ChildServerRelay.IsProcessAlive)
if (IsOwner && !ChildServerRelay.IsProcessAlive)
{
var gameClient = GameMain.Client;
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
@@ -112,9 +115,11 @@ namespace Barotrauma.Networking
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
{
if (!inc.SenderConnection.RemoteEndPoint.EquivalentTo(lidgrenEndpoint.NetEndpoint))
var remoteEndpoint = new LidgrenEndpoint(inc.SenderEndPoint);
if (remoteEndpoint != ServerEndpoint)
{
DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
DebugConsole.AddWarning($"Mismatched endpoint: expected {ServerEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
continue;
}
@@ -152,11 +157,7 @@ namespace Barotrauma.Networking
}
else
{
if (initializationStep != ConnectionInitialization.Success)
{
callbacks.OnInitializationComplete.Invoke();
initializationStep = ConnectionInitialization.Success;
}
OnInitializationComplete();
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
callbacks.OnMessageReceived.Invoke(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection));
@@ -212,9 +213,6 @@ namespace Barotrauma.Networking
netClient.Shutdown(peerDisconnectPacket.ToLidgrenStringRepresentation());
netClient = null;
if (steamAuthTicket.TryUnwrap(out var ticket)) { ticket.Cancel(); }
steamAuthTicket = Option.None;
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
}
@@ -270,7 +268,21 @@ namespace Barotrauma.Networking
return netClient.SendMessage(msg.ToLidgren(netClient), deliveryMethod.ToLidgren());
}
protected override async Task<Option<AccountId>> GetAccountId()
{
if (!EosInterface.Core.IsInitialized) { return SteamManager.GetSteamId().Select(id => (AccountId)id); }
var selfPuids = EosInterface.IdQueries.GetLoggedInPuids();
if (selfPuids.None()) { return Option.None; }
var accountIdsResult = await EosInterface.IdQueries.GetSelfExternalAccountIds(selfPuids.First());
return accountIdsResult.TryUnwrapSuccess(out var accountIds) && accountIds.Length > 0
? Option.Some(accountIds[0])
: Option.None;
}
#if DEBUG
public override void ForceTimeOut()
{
netClient?.ServerConnection?.ForceTimeOut();

View File

@@ -1,136 +1,142 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Barotrauma.Steam;
using System.Threading;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.Steam;
namespace Barotrauma.Networking
{
internal sealed class SteamP2PClientPeer : ClientPeer
internal sealed class P2PClientPeer : ClientPeer<P2PEndpoint>
{
private readonly SteamId hostSteamId;
private double timeout;
private double heartbeatTimer;
private double connectionStatusTimer;
private long sentBytes, receivedBytes;
private readonly List<IncomingInitializationMessage> incomingInitializationMessages = new List<IncomingInitializationMessage>();
private readonly List<IReadMessage> incomingDataMessages = new List<IReadMessage>();
private readonly MessageFragmenter fragmenter = new();
private readonly MessageDefragmenter defragmenter = new();
public SteamP2PClientPeer(SteamP2PEndpoint endpoint, Callbacks callbacks) : base(endpoint, callbacks, Option<int>.None())
private P2PSocket? socket;
private static P2PEndpoint GetPrimaryEndpoint(ImmutableArray<P2PEndpoint> allEndpoints)
{
var steamEndpointOption = allEndpoints.OfType<SteamP2PEndpoint>().FirstOrNone();
var eosEndpointOption = allEndpoints.OfType<EosP2PEndpoint>().FirstOrNone();
if (SteamManager.IsInitialized)
{
if (steamEndpointOption.TryUnwrap(out var steamEndpoint)) { return steamEndpoint; }
}
if (EosInterface.Core.IsInitialized)
{
if (eosEndpointOption.TryUnwrap(out var eosEndpoint)) { return eosEndpoint; }
}
throw new Exception($"Couldn't pick out a primary endpoint: {string.Join(", ", allEndpoints.Select(e => e.GetType().Name))}");
}
public P2PClientPeer(ImmutableArray<P2PEndpoint> allEndpoints, Callbacks callbacks)
: base(
GetPrimaryEndpoint(allEndpoints),
allEndpoints.Cast<Endpoint>().ToImmutableArray(),
callbacks,
Option.None)
{
ServerConnection = null;
isActive = false;
if (!(ServerEndpoint is SteamP2PEndpoint steamIdEndpoint))
{
throw new InvalidCastException("endPoint is not SteamId");
}
hostSteamId = steamIdEndpoint.SteamId;
}
public override void Start()
{
if (isActive) { return; }
ContentPackageOrderReceived = false;
steamAuthTicket = SteamManager.GetAuthSessionTicketForMultiplayer(ServerEndpoint);
//TODO: wait for GetAuthSessionTicketResponse_t
ServerConnection = ServerEndpoint.MakeConnectionFromEndpoint();
if (steamAuthTicket == null)
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnP2PData);
var socketCreateResult = ServerEndpoint switch
{
throw new Exception("GetAuthSessionTicket returned null");
}
Steamworks.SteamNetworking.ResetActions();
Steamworks.SteamNetworking.OnP2PSessionRequest = OnIncomingConnection;
Steamworks.SteamNetworking.OnP2PConnectionFailed = OnConnectionFailed;
Steamworks.SteamNetworking.AllowP2PPacketRelay(true);
ServerConnection = new SteamP2PConnection(hostSteamId);
ServerConnection.SetAccountInfo(new AccountInfo(hostSteamId));
var headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.ConnectionStarted
EosP2PEndpoint => EosP2PSocket.Create(socketCallbacks),
SteamP2PEndpoint steamP2PEndpoint => SteamConnectSocket.Create(steamP2PEndpoint, socketCallbacks),
_ => throw new Exception($"Invalid server endpoint: {ServerEndpoint.GetType()} {ServerEndpoint}")
};
SendMsgInternal(headers, null);
socket = socketCreateResult.TryUnwrapSuccess(out var s)
? s
: throw new Exception($"Failed to create socket for {ServerEndpoint}: {socketCreateResult}");
TaskPool.Add(
$"{nameof(P2PClientPeer)}.GetAuthTicket",
AuthenticationTicket.Create(ServerEndpoint),
t =>
{
if (!t.TryGetResult(out Option<AuthenticationTicket> authenticationTicket))
{
Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
return;
}
authTicket = authenticationTicket;
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
var headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.ConnectionStarted
};
SendMsgInternal(headers, null);
});
initializationStep = ConnectionInitialization.AuthInfoAndVersion;
timeout = NetworkConnection.TimeoutThreshold;
heartbeatTimer = 1.0;
connectionStatusTimer = 0.0;
isActive = true;
}
private void OnIncomingConnection(Steamworks.SteamId steamId)
private bool OnIncomingConnection(P2PEndpoint remoteEndpoint)
{
if (!isActive) { return; }
if (remoteEndpoint == ServerEndpoint)
{
return true;
}
if (steamId == hostSteamId.Value)
if (initializationStep != ConnectionInitialization.Password &&
initializationStep != ConnectionInitialization.ContentPackageOrder &&
initializationStep != ConnectionInitialization.Success)
{
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId);
}
else if (initializationStep != ConnectionInitialization.Password &&
initializationStep != ConnectionInitialization.ContentPackageOrder &&
initializationStep != ConnectionInitialization.Success)
{
DebugConsole.ThrowError("Connection from incorrect SteamID was rejected: " +
$"expected {hostSteamId}," +
$"got {new SteamId(steamId)}");
DebugConsole.AddWarning(
"Connection from incorrect endpoint was rejected: " +
$"expected {ServerEndpoint}, " +
$"got {remoteEndpoint}");
}
return false;
}
private void OnConnectionFailed(Steamworks.SteamId steamId, Steamworks.P2PSessionError error)
private void OnConnectionClosed(P2PEndpoint remoteEndpoint, PeerDisconnectPacket peerDisconnectPacket)
{
if (!isActive) { return; }
if (remoteEndpoint != ServerEndpoint) { return; }
if (steamId != hostSteamId.Value) { return; }
Close(PeerDisconnectPacket.SteamP2PError(error));
Close(peerDisconnectPacket);
}
private void OnP2PData(ulong steamId, byte[] data, int dataLength)
private void OnP2PData(P2PEndpoint senderEndpoint, IReadMessage inc)
{
if (!isActive) { return; }
if (steamId != hostSteamId.Value) { return; }
receivedBytes += inc.LengthBytes;
if (senderEndpoint != ServerEndpoint) { return; }
timeout = Screen.Selected == GameMain.GameScreen
? NetworkConnection.TimeoutThresholdInGame
: NetworkConnection.TimeoutThreshold;
try
{
IReadMessage inc = new ReadOnlyMessage(data, false, 0, dataLength, ServerConnection);
ProcessP2PData(inc);
}
catch (Exception e)
{
string errorMsg = $"Client failed to read an incoming P2P message. {{{e}}}\n{e.StackTrace.CleanupStackTrace()}";
GameAnalyticsManager.AddErrorEventOnce($"SteamP2PClientPeer.OnP2PData:ClientReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
#endif
}
}
private void ProcessP2PData(IReadMessage inc)
{
var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
if (!packetHeader.IsServerMessage()) { return; }
@@ -138,9 +144,8 @@ namespace Barotrauma.Networking
{
if (!initialization.HasValue) { return; }
var relayPacket = INetSerializableStruct.Read<SteamP2PInitializationRelayPacket>(inc);
var relayPacket = INetSerializableStruct.Read<P2PInitializationRelayPacket>(inc);
SteamManager.JoinLobby(relayPacket.LobbyID, false);
if (initializationStep != ConnectionInitialization.Success)
{
incomingInitializationMessages.Add(new IncomingInitializationMessage
@@ -150,6 +155,14 @@ namespace Barotrauma.Networking
});
}
}
else if (packetHeader.IsDataFragment())
{
var completeMessageOption = defragmenter.ProcessIncomingFragment(INetSerializableStruct.Read<MessageFragment>(inc));
if (!completeMessageOption.TryUnwrap(out var completeMessage)) { return; }
int completeMessageLengthBits = completeMessage.Length * 8;
incomingDataMessages.Add(new ReadWriteMessage(completeMessage.ToArray(), 0, completeMessageLengthBits, copyBuf: false));
}
else if (packetHeader.IsHeartbeatMessage())
{
return; //TODO: implement heartbeats
@@ -177,41 +190,7 @@ namespace Barotrauma.Networking
heartbeatTimer -= deltaTime;
if (initializationStep != ConnectionInitialization.Password &&
initializationStep != ConnectionInitialization.ContentPackageOrder &&
initializationStep != ConnectionInitialization.Success)
{
connectionStatusTimer -= deltaTime;
if (connectionStatusTimer <= 0.0)
{
if (Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId.Value) is { } state)
{
if (state.P2PSessionError != Steamworks.P2PSessionError.None)
{
Close(PeerDisconnectPacket.SteamP2PError(state.P2PSessionError));
}
}
else
{
Close(PeerDisconnectPacket.WithReason(DisconnectReason.Timeout));
}
connectionStatusTimer = 1.0f;
}
}
for (int i = 0; i < 100; i++)
{
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
if (packet is { SteamId: var steamId, Data: var data })
{
OnP2PData(steamId, data, data.Length);
if (!isActive) { return; }
receivedBytes += data.Length;
}
}
socket?.ProcessIncomingMessages();
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.ReceivedBytes, receivedBytes);
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.SentBytes, sentBytes);
@@ -258,8 +237,7 @@ namespace Barotrauma.Networking
analyticsTag: "NoContentPackages");
return;
}
callbacks.OnInitializationComplete.Invoke();
initializationStep = ConnectionInitialization.Success;
OnInitializationComplete();
}
else
{
@@ -287,6 +265,21 @@ namespace Barotrauma.Networking
if (!isActive) { return; }
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
if (bufAux.Length > MessageFragment.MaxSize)
{
var fragments = fragmenter.FragmentMessage(msg.Buffer.AsSpan()[..msg.LengthBytes]);
foreach (var fragment in fragments)
{
var fragmentHeaders = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsDataFragment,
Initialization = null
};
SendMsgInternal(fragmentHeaders, fragment);
}
return;
}
var headers = new PeerPacketHeaders
{
@@ -346,8 +339,6 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
SteamManager.LeaveLobby();
isActive = false;
var headers = new PeerPacketHeaders
@@ -360,11 +351,9 @@ namespace Barotrauma.Networking
Thread.Sleep(100);
Steamworks.SteamNetworking.ResetActions();
Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId.Value);
if (steamAuthTicket.TryUnwrap(out var ticket)) { ticket.Cancel(); }
steamAuthTicket = Option.None;
socket?.CloseConnection(ServerEndpoint);
socket?.Dispose();
socket = null;
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
}
@@ -374,33 +363,62 @@ namespace Barotrauma.Networking
IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.WriteNetSerializableStruct(headers);
body?.Write(msgToSend);
ForwardToSteamP2P(msgToSend, headers.DeliveryMethod);
ForwardToRemotePeer(msgToSend, headers.DeliveryMethod);
}
private void ForwardToSteamP2P(IWriteMessage msg, DeliveryMethod deliveryMethod)
private void ForwardToRemotePeer(IWriteMessage msg, DeliveryMethod deliveryMethod)
{
if (!isActive) { return; }
if (socket is null) { return; }
heartbeatTimer = 5.0;
int length = msg.LengthBytes;
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, deliveryMethod.ToSteam());
if (length + 4 >= MsgConstants.MTU)
{
DebugConsole.Log($"WARNING: message length comes close to exceeding MTU, forcing reliable send ({length} bytes)");
deliveryMethod = DeliveryMethod.Reliable;
}
bool success = socket.SendMessage(ServerEndpoint, msg, deliveryMethod);
sentBytes += length;
if (successSend) { return; }
if (success) { return; }
if (deliveryMethod is DeliveryMethod.Unreliable)
{
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, DeliveryMethod.Reliable.ToSteam());
success = socket.SendMessage(ServerEndpoint, msg, DeliveryMethod.Reliable);
sentBytes += length;
}
if (!successSend)
if (!success)
{
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
}
}
protected override async Task<Option<AccountId>> GetAccountId()
{
if (SteamManager.IsInitialized) { return SteamManager.GetSteamId().Select(id => (AccountId)id); }
if (EosInterface.IdQueries.GetLoggedInPuids() is not { Length: > 0 } puids)
{
return Option.None;
}
var externalAccountIdsResult = await EosInterface.IdQueries.GetSelfExternalAccountIds(puids[0]);
if (!externalAccountIdsResult.TryUnwrapSuccess(out var externalAccountIds)
|| externalAccountIds is not { Length: > 0 })
{
return Option.None;
}
return Option.Some(externalAccountIds[0]);
}
#if DEBUG
public override void ForceTimeOut()
{
timeout = 0.0f;

View File

@@ -0,0 +1,557 @@
#nullable enable
using Barotrauma.Extensions;
using Barotrauma.Steam;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Barotrauma.Networking
{
sealed class P2POwnerPeer : ClientPeer<PipeEndpoint>
{
private P2PSocket? socket;
private readonly ImmutableDictionary<AuthenticationTicketKind, Authenticator> authenticators;
private readonly P2PEndpoint selfPrimaryEndpoint;
private AccountInfo selfAccountInfo;
private long sentBytes, receivedBytes;
private sealed class RemotePeer
{
public enum AuthenticationStatus
{
NotAuthenticated,
AuthenticationPending,
SuccessfullyAuthenticated
}
public readonly P2PEndpoint Endpoint;
public AccountInfo AccountInfo;
public readonly record struct DisconnectInfo(
double TimeToGiveUp,
PeerDisconnectPacket Packet);
public Option<DisconnectInfo> PendingDisconnect;
public AuthenticationStatus AuthStatus;
public readonly record struct UnauthedMessage(byte[] Bytes, int LengthBytes);
public readonly List<UnauthedMessage> UnauthedMessages;
public RemotePeer(P2PEndpoint endpoint)
{
Endpoint = endpoint;
AccountInfo = AccountInfo.None;
PendingDisconnect = Option.None;
AuthStatus = AuthenticationStatus.NotAuthenticated;
UnauthedMessages = new List<UnauthedMessage>();
}
}
private readonly List<RemotePeer> remotePeers = new();
public P2POwnerPeer(Callbacks callbacks, int ownerKey, ImmutableArray<P2PEndpoint> allEndpoints) :
base(new PipeEndpoint(), allEndpoints.Cast<Endpoint>().ToImmutableArray(), callbacks, Option<int>.Some(ownerKey))
{
ServerConnection = null;
isActive = false;
var selfSteamEndpoint = allEndpoints.FirstOrNone(e => e is SteamP2PEndpoint);
var selfEosEndpoint = allEndpoints.FirstOrNone(e => e is EosP2PEndpoint);
var selfPrimaryEndpointOption = selfSteamEndpoint.Fallback(selfEosEndpoint);
if (!selfPrimaryEndpointOption.TryUnwrap(out var selfPrimaryEndpointNotNull))
{
throw new Exception("Could not determine endpoint for P2POwnerPeer");
}
selfPrimaryEndpoint = selfPrimaryEndpointNotNull;
selfAccountInfo = AccountInfo.None;
authenticators = Authenticator.GetAuthenticatorsForHost(Option.Some<Endpoint>(selfPrimaryEndpoint));
}
public override void Start()
{
if (isActive) { return; }
initializationStep = ConnectionInitialization.AuthInfoAndVersion;
ServerConnection = new PipeConnection(Option.None)
{
Status = NetworkConnectionStatus.Connected
};
remotePeers.Clear();
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnP2PData);
var socketCreateResult = DualStackP2PSocket.Create(socketCallbacks);
socket = socketCreateResult.TryUnwrapSuccess(out var s)
? s
: throw new Exception($"Failed to create dual-stack socket: {socketCreateResult}");
TaskPool.Add("P2POwnerPeer.GetAccountId", GetAccountId(), t =>
{
if (t.TryGetResult(out Option<AccountId> accountIdOption) && accountIdOption.TryUnwrap(out var accountId))
{
selfAccountInfo = new AccountInfo(accountId);
}
if (selfAccountInfo.IsNone)
{
Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
}
});
isActive = true;
}
private bool OnIncomingConnection(P2PEndpoint remoteEndpoint)
{
if (!isActive) { return false; }
if (remotePeers.None(p => p.Endpoint == remoteEndpoint))
{
remotePeers.Add(new RemotePeer(remoteEndpoint));
}
return true;
}
private void OnConnectionClosed(P2PEndpoint remoteEndpoint, PeerDisconnectPacket disconnectPacket)
{
var remotePeer
= remotePeers.Find(p => p.Endpoint == remoteEndpoint);
if (remotePeer is null) { return; }
CommunicatePeerDisconnectToServerProcess(
remotePeer,
remotePeer.PendingDisconnect.Select(d => d.Packet).Fallback(disconnectPacket));
}
private void OnP2PData(P2PEndpoint senderEndpoint, IReadMessage inc)
{
if (!isActive) { return; }
receivedBytes += inc.LengthBytes;
var remotePeer = remotePeers.Find(p => p.Endpoint == senderEndpoint);
if (remotePeer is null) { return; }
if (remotePeer.PendingDisconnect.IsSome()) { return; }
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
PacketHeader packetHeader = peerPacketHeaders.PacketHeader;
if (packetHeader.IsConnectionInitializationStep())
{
ConnectionInitialization initialization = peerPacketHeaders.Initialization ?? throw new Exception("Initialization step missing");
if (initialization == ConnectionInitialization.AuthInfoAndVersion
&& remotePeer.AuthStatus == RemotePeer.AuthenticationStatus.NotAuthenticated)
{
StartAuthTask(inc, remotePeer);
}
}
if (remotePeer.AuthStatus == RemotePeer.AuthenticationStatus.AuthenticationPending)
{
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage(inc.Buffer, inc.LengthBytes));
}
else
{
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.WriteNetSerializableStruct(new P2POwnerToServerHeader
{
EndpointStr = remotePeer.Endpoint.StringRepresentation,
AccountInfo = remotePeer.AccountInfo
});
outMsg.WriteBytes(inc.Buffer, 0, inc.LengthBytes);
ForwardToServerProcess(outMsg);
}
}
private void StartAuthTask(IReadMessage inc, RemotePeer remotePeer)
{
remotePeer.AuthStatus = RemotePeer.AuthenticationStatus.AuthenticationPending;
var packet = INetSerializableStruct.Read<ClientAuthTicketAndVersionPacket>(inc);
void failAuth()
{
CommunicateDisconnectToRemotePeer(remotePeer, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
}
if (!packet.AuthTicket.TryUnwrap(out var authenticationTicket))
{
failAuth();
return;
}
if (!authenticators.TryGetValue(authenticationTicket.Kind, out var authenticator))
{
failAuth();
return;
}
TaskPool.Add($"P2POwnerPeer.VerifyRemotePeerAccountId",
authenticator.VerifyTicket(authenticationTicket),
t =>
{
if (!t.TryGetResult(out AccountInfo accountInfo)
|| accountInfo.IsNone)
{
failAuth();
return;
}
remotePeer.AccountInfo = accountInfo;
remotePeer.AuthStatus = RemotePeer.AuthenticationStatus.SuccessfullyAuthenticated;
foreach (var unauthedMessage in remotePeer.UnauthedMessages)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteNetSerializableStruct(new P2POwnerToServerHeader
{
EndpointStr = remotePeer.Endpoint.StringRepresentation,
AccountInfo = accountInfo
});
msg.WriteBytes(unauthedMessage.Bytes, 0, unauthedMessage.LengthBytes);
ForwardToServerProcess(msg);
}
remotePeer.UnauthedMessages.Clear();
});
}
public override void Update(float deltaTime)
{
if (!isActive) { return; }
if (ChildServerRelay.HasShutDown || ChildServerRelay.Process is not { HasExited: false })
{
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;
};
return;
}
if (selfAccountInfo.IsNone) { return; }
for (int i = remotePeers.Count - 1; i >= 0; i--)
{
if (remotePeers[i].PendingDisconnect.TryUnwrap(out var pendingDisconnect) && pendingDisconnect.TimeToGiveUp < Timing.TotalTime)
{
CommunicatePeerDisconnectToServerProcess(remotePeers[i], pendingDisconnect.Packet);
}
}
socket?.ProcessIncomingMessages();
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.ReceivedBytes, receivedBytes);
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.SentBytes, sentBytes);
foreach (var incBuf in ChildServerRelay.Read())
{
ChildServerRelay.DisposeLocalHandles();
IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, ServerConnection);
HandleServerMessage(inc);
}
}
private void HandleServerMessage(IReadMessage inc)
{
if (!isActive) { return; }
var recipientInfo = INetSerializableStruct.Read<P2PServerToOwnerHeader>(inc);
if (!recipientInfo.Endpoint.TryUnwrap(out var recipientEndpoint)) { return; }
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
if (recipientEndpoint != selfPrimaryEndpoint)
{
HandleMessageForRemotePeer(peerPacketHeaders, recipientEndpoint, inc);
}
else
{
HandleMessageForOwner(peerPacketHeaders, inc);
}
}
private static byte[] GetRemainingBytes(IReadMessage msg)
{
return msg.Buffer[msg.BytePosition..msg.LengthBytes];
}
private void HandleMessageForRemotePeer(PeerPacketHeaders peerPacketHeaders, P2PEndpoint recipientEndpoint, IReadMessage inc)
{
var (deliveryMethod, packetHeader, initialization) = peerPacketHeaders;
if (!packetHeader.IsServerMessage())
{
DebugConsole.ThrowError("Received non-server message meant for remote peer");
return;
}
RemotePeer? peer = remotePeers.Find(p => p.Endpoint == recipientEndpoint);
if (peer is null) { return; }
if (packetHeader.IsDisconnectMessage())
{
var packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
CommunicateDisconnectToRemotePeer(peer, packet);
return;
}
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = deliveryMethod,
PacketHeader = packetHeader,
Initialization = initialization
});
if (packetHeader.IsConnectionInitializationStep())
{
var initRelayPacket = new P2PInitializationRelayPacket
{
LobbyID = 0,
Message = new PeerPacketMessage
{
Buffer = GetRemainingBytes(inc)
}
};
outMsg.WriteNetSerializableStruct(initRelayPacket);
}
else
{
byte[] userMessage = GetRemainingBytes(inc);
outMsg.WriteBytes(userMessage, 0, userMessage.Length);
}
ForwardToRemotePeer(deliveryMethod, recipientEndpoint, outMsg);
}
private void HandleMessageForOwner(PeerPacketHeaders peerPacketHeaders, IReadMessage inc)
{
var (_, packetHeader, _) = peerPacketHeaders;
if (packetHeader.IsDisconnectMessage())
{
DebugConsole.ThrowError("Received disconnect message from owned server");
return;
}
if (!packetHeader.IsServerMessage())
{
DebugConsole.ThrowError("Received non-server message from owned server");
return;
}
if (packetHeader.IsHeartbeatMessage())
{
return; //no timeout since we're using pipes, ignore this message
}
if (packetHeader.IsConnectionInitializationStep())
{
if (selfAccountInfo.IsNone) { throw new InvalidOperationException($"Cannot initialize {nameof(P2POwnerPeer)} because {nameof(selfAccountInfo)} is not defined"); }
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.WriteNetSerializableStruct(new P2POwnerToServerHeader
{
EndpointStr = selfPrimaryEndpoint.StringRepresentation,
AccountInfo = selfAccountInfo
});
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.AuthInfoAndVersion
});
outMsg.WriteNetSerializableStruct(new P2PInitializationOwnerPacket(
Name: GameMain.Client.Name,
AccountId: selfAccountInfo.AccountId.Fallback(default(AccountId)!)));
ForwardToServerProcess(outMsg);
}
else
{
OnInitializationComplete();
PeerPacketMessage packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, ServerConnection);
callbacks.OnMessageReceived.Invoke(msg);
}
}
private void CommunicateDisconnectToRemotePeer(RemotePeer peer, PeerDisconnectPacket peerDisconnectPacket)
{
if (peer.PendingDisconnect.IsNone())
{
peer.PendingDisconnect = Option.Some(
new RemotePeer.DisconnectInfo(
Timing.TotalTime + 3f,
peerDisconnectPacket));
}
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage
});
outMsg.WriteNetSerializableStruct(peerDisconnectPacket);
ForwardToRemotePeer(DeliveryMethod.Reliable, peer.Endpoint, outMsg);
}
private void CommunicatePeerDisconnectToServerProcess(RemotePeer peer, PeerDisconnectPacket peerDisconnectPacket)
{
if (!remotePeers.Remove(peer)) { return; }
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.WriteNetSerializableStruct(new P2POwnerToServerHeader
{
EndpointStr = peer.Endpoint.StringRepresentation,
AccountInfo = peer.AccountInfo
});
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsDisconnectMessage
});
outMsg.WriteNetSerializableStruct(peerDisconnectPacket);
if (peer.AccountInfo.AccountId.TryUnwrap(out var accountId))
{
authenticators.Values.ForEach(authenticator => authenticator.EndAuthSession(accountId));
}
ForwardToServerProcess(outMsg);
socket?.CloseConnection(peer.Endpoint);
}
public override void SendPassword(string password)
{
//owner doesn't send passwords
}
public override void Close(PeerDisconnectPacket peerDisconnectPacket)
{
if (!isActive) { return; }
isActive = false;
for (int i = remotePeers.Count - 1; i >= 0; i--)
{
CommunicateDisconnectToRemotePeer(remotePeers[i], peerDisconnectPacket);
}
Thread.Sleep(100);
for (int i = remotePeers.Count - 1; i >= 0; i--)
{
CommunicatePeerDisconnectToServerProcess(remotePeers[i], peerDisconnectPacket);
}
socket?.Dispose();
socket = null;
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
}
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
{
if (!isActive) { return; }
IWriteMessage msgToSend = new WriteOnlyMessage();
byte[] msgData = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
msgToSend.WriteNetSerializableStruct(new P2POwnerToServerHeader
{
EndpointStr = selfPrimaryEndpoint.StringRepresentation,
AccountInfo = selfAccountInfo
});
msgToSend.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = deliveryMethod,
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None
});
msgToSend.WriteNetSerializableStruct(new PeerPacketMessage
{
Buffer = msgData
});
ForwardToServerProcess(msgToSend);
}
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
{
//not currently used by P2POwnerPeer
throw new NotImplementedException();
}
private static void ForwardToServerProcess(IWriteMessage msg)
{
byte[] bufToSend = new byte[msg.LengthBytes];
msg.Buffer[..msg.LengthBytes].CopyTo(bufToSend.AsSpan());
ChildServerRelay.Write(bufToSend);
}
private void ForwardToRemotePeer(DeliveryMethod deliveryMethod, P2PEndpoint recipient, IWriteMessage outMsg)
{
if (socket is null) { return; }
int length = outMsg.LengthBytes;
if (length + 4 >= MsgConstants.MTU)
{
DebugConsole.Log($"WARNING: message length comes close to exceeding MTU, forcing reliable send ({length} bytes)");
deliveryMethod = DeliveryMethod.Reliable;
}
var success = socket.SendMessage(recipient, outMsg, deliveryMethod);
sentBytes += length;
if (success) { return; }
if (deliveryMethod is DeliveryMethod.Unreliable)
{
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
success = socket.SendMessage(recipient, outMsg, DeliveryMethod.Reliable);
sentBytes += length;
}
if (!success)
{
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
}
}
protected override async Task<Option<AccountId>> GetAccountId()
{
if (SteamManager.IsInitialized) { return SteamManager.GetSteamId().Select(id => (AccountId)id); }
if (EosInterface.IdQueries.GetLoggedInPuids() is not { Length: > 0 } puids)
{
return Option.None;
}
var externalAccountIdsResult = await EosInterface.IdQueries.GetSelfExternalAccountIds(puids[0]);
if (!externalAccountIdsResult.TryUnwrapSuccess(out var externalAccountIds)
|| externalAccountIds is not { Length: > 0 })
{
return Option.None;
}
return Option.Some(externalAccountIds[0]);
}
#if DEBUG
public override void ForceTimeOut()
{
//TODO: reimplement?
}
#endif
}
}

View File

@@ -1,492 +0,0 @@
#nullable enable
using Barotrauma.Steam;
using System;
using System.Collections.Generic;
using System.Threading;
using Barotrauma.Extensions;
namespace Barotrauma.Networking
{
sealed class SteamP2POwnerPeer : ClientPeer
{
private readonly SteamId selfSteamID;
private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Fallback(0));
private SteamId ReadSteamId(IReadMessage inc) => new SteamId(inc.ReadUInt64() ^ ownerKey64);
private void WriteSteamId(IWriteMessage msg, SteamId val) => msg.WriteUInt64(val.Value ^ ownerKey64);
private long sentBytes, receivedBytes;
private sealed class RemotePeer
{
public readonly SteamId SteamId;
public Option<SteamId> OwnerSteamId;
public double? DisconnectTime;
public bool Authenticating;
public bool Authenticated;
public readonly struct UnauthedMessage
{
public readonly SteamId Sender;
public readonly byte[] Bytes;
public readonly int Length;
public UnauthedMessage(SteamId sender, byte[] bytes)
{
Sender = sender;
Bytes = bytes;
Length = bytes.Length;
}
}
public readonly List<UnauthedMessage> UnauthedMessages;
public RemotePeer(SteamId steamId)
{
SteamId = steamId;
OwnerSteamId = Option<SteamId>.None();
DisconnectTime = null;
Authenticating = false;
Authenticated = false;
UnauthedMessages = new List<UnauthedMessage>();
}
}
private List<RemotePeer> remotePeers = null!;
public SteamP2POwnerPeer(Callbacks callbacks, int ownerKey) : base(new PipeEndpoint(), callbacks, Option<int>.Some(ownerKey))
{
ServerConnection = null;
isActive = false;
selfSteamID = SteamManager.GetSteamId().TryUnwrap(out var steamId)
? steamId
: throw new InvalidOperationException("Steamworks not initialized");
}
public override void Start()
{
if (isActive) { return; }
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
ServerConnection = new PipeConnection(selfSteamID)
{
Status = NetworkConnectionStatus.Connected
};
remotePeers = new List<RemotePeer>();
Steamworks.SteamNetworking.ResetActions();
Steamworks.SteamNetworking.OnP2PSessionRequest = OnIncomingConnection;
Steamworks.SteamUser.OnValidateAuthTicketResponse += OnAuthChange;
Steamworks.SteamNetworking.AllowP2PPacketRelay(true);
isActive = true;
}
private void OnAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
{
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
if (remotePeer == null) { return; }
if (status == Steamworks.AuthResponse.OK)
{
if (remotePeer.Authenticated) { return; }
SteamId ownerSteamId = new SteamId(ownerId);
remotePeer.OwnerSteamId = Option<SteamId>.Some(ownerSteamId);
remotePeer.Authenticated = true;
remotePeer.Authenticating = false;
foreach (var unauthedMessage in remotePeer.UnauthedMessages)
{
IWriteMessage msg = new WriteOnlyMessage();
WriteSteamId(msg, unauthedMessage.Sender);
WriteSteamId(msg, ownerSteamId);
msg.WriteBytes(unauthedMessage.Bytes, 0, unauthedMessage.Length);
ForwardToServerProcess(msg);
}
remotePeer.UnauthedMessages.Clear();
}
else
{
DisconnectPeer(remotePeer, PeerDisconnectPacket.SteamAuthError(status));
}
}
private void OnIncomingConnection(Steamworks.SteamId steamId)
{
if (!isActive) { return; }
if (remotePeers.None(p => p.SteamId.Value == steamId))
{
remotePeers.Add(new RemotePeer(new SteamId(steamId)));
}
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); //accept all connections, the server will figure things out later
}
private void OnP2PData(ulong steamId, IReadMessage inc)
{
if (!isActive) { return; }
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
if (remotePeer == null) { return; }
if (remotePeer.DisconnectTime != null) { return; }
try
{
ProcessP2PData(steamId, remotePeer, inc);
}
catch (Exception e)
{
string errorMsg = $"Server failed to read an incoming P2P message. {{{e}}}\n{e.StackTrace.CleanupStackTrace()}";
GameAnalyticsManager.AddErrorEventOnce($"SteamP2POwnerPeer.OnP2PData:OwnerReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
#endif
}
}
private void ProcessP2PData(ulong steamId, RemotePeer remotePeer, IReadMessage inc)
{
var (deliveryMethod, packetHeader, connectionInitialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
if (remotePeer is { Authenticated: false, Authenticating: false } && packetHeader.IsConnectionInitializationStep())
{
remotePeer.DisconnectTime = null;
ConnectionInitialization initialization = connectionInitialization ?? throw new Exception("Initialization step missing");
if (initialization == ConnectionInitialization.SteamTicketAndVersion)
{
remotePeer.Authenticating = true;
var packet = INetSerializableStruct.Read<ClientSteamTicketAndVersionPacket>(inc);
packet.SteamAuthTicket.TryUnwrap(out var ticket);
Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
{
DisconnectPeer(remotePeer, PeerDisconnectPacket.SteamAuthError(authSessionStartState));
return;
}
}
}
var steamUserId = new SteamId(steamId);
if (remotePeer.Authenticating)
{
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage(steamUserId, inc.Buffer));
}
else
{
IWriteMessage outMsg = new WriteOnlyMessage();
WriteSteamId(outMsg, steamUserId);
WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId));
outMsg.WriteBytes(inc.Buffer, 0, inc.LengthBytes);
ForwardToServerProcess(outMsg);
}
}
public override void Update(float deltaTime)
{
if (!isActive) { return; }
if (ChildServerRelay.HasShutDown || !ChildServerRelay.IsProcessAlive)
{
var gameClient = GameMain.Client;
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
gameClient?.CreateServerCrashMessage();
return;
}
for (int i = remotePeers.Count - 1; i >= 0; i--)
{
if (remotePeers[i].DisconnectTime != null && remotePeers[i].DisconnectTime < Timing.TotalTime)
{
ClosePeerSession(remotePeers[i]);
}
}
for (int i = 0; i < 100; i++)
{
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
if (packet is { SteamId: var steamId, Data: var data })
{
OnP2PData(steamId, new ReadWriteMessage(data, 0, data.Length * 8, false));
receivedBytes += data.Length;
}
}
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.ReceivedBytes, receivedBytes);
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.SentBytes, sentBytes);
while (ChildServerRelay.Read(out byte[] incBuf))
{
ChildServerRelay.DisposeLocalHandles();
IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, ServerConnection);
HandleDataMessage(inc);
}
}
private void HandleDataMessage(IReadMessage inc)
{
if (!isActive) { return; }
SteamId recipientSteamId = ReadSteamId(inc);
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
if (recipientSteamId != selfSteamID)
{
HandleMessageForRemotePeer(peerPacketHeaders, recipientSteamId, inc);
}
else
{
HandleMessageForOwner(peerPacketHeaders, inc);
}
}
private static byte[] GetRemainingBytes(IReadMessage msg)
{
return msg.Buffer[msg.BytePosition..msg.LengthBytes];
}
private void HandleMessageForRemotePeer(PeerPacketHeaders peerPacketHeaders, SteamId recipientSteamId, IReadMessage inc)
{
var (deliveryMethod, packetHeader, initialization) = peerPacketHeaders;
if (!packetHeader.IsServerMessage())
{
DebugConsole.ThrowError("Received non-server message meant for remote peer");
return;
}
RemotePeer? peer = remotePeers.Find(p => p.SteamId == recipientSteamId);
if (peer is null) { return; }
if (packetHeader.IsDisconnectMessage())
{
var packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
DisconnectPeer(peer, packet);
return;
}
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = deliveryMethod,
PacketHeader = packetHeader,
Initialization = initialization
});
if (packetHeader.IsConnectionInitializationStep())
{
var initRelayPacket = new SteamP2PInitializationRelayPacket
{
LobbyID = SteamManager.CurrentLobbyID,
Message = new PeerPacketMessage
{
Buffer = GetRemainingBytes(inc)
}
};
outMsg.WriteNetSerializableStruct(initRelayPacket);
}
else
{
byte[] userMessage = GetRemainingBytes(inc);
outMsg.WriteBytes(userMessage, 0, userMessage.Length);
}
ForwardToRemotePeer(deliveryMethod, recipientSteamId, outMsg);
}
private void HandleMessageForOwner(PeerPacketHeaders peerPacketHeaders, IReadMessage inc)
{
var (_, packetHeader, _) = peerPacketHeaders;
if (packetHeader.IsDisconnectMessage())
{
DebugConsole.ThrowError("Received disconnect message from owned server");
return;
}
if (!packetHeader.IsServerMessage())
{
DebugConsole.ThrowError("Received non-server message from owned server");
return;
}
if (packetHeader.IsHeartbeatMessage())
{
return; //no timeout since we're using pipes, ignore this message
}
if (packetHeader.IsConnectionInitializationStep())
{
IWriteMessage outMsg = new WriteOnlyMessage();
WriteSteamId(outMsg, selfSteamID);
WriteSteamId(outMsg, selfSteamID);
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.SteamTicketAndVersion
});
outMsg.WriteNetSerializableStruct(new SteamP2PInitializationOwnerPacket
{
OwnerName = GameMain.Client.Name
});
ForwardToServerProcess(outMsg);
}
else
{
if (initializationStep != ConnectionInitialization.Success)
{
callbacks.OnInitializationComplete.Invoke();
initializationStep = ConnectionInitialization.Success;
}
PeerPacketMessage packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, ServerConnection);
callbacks.OnMessageReceived.Invoke(msg);
}
}
private void DisconnectPeer(RemotePeer peer, PeerDisconnectPacket peerDisconnectPacket)
{
peer.DisconnectTime ??= Timing.TotalTime + 1.0;
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage
});
outMsg.WriteNetSerializableStruct(peerDisconnectPacket);
Steamworks.SteamNetworking.SendP2PPacket(peer.SteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
}
private void ClosePeerSession(RemotePeer peer)
{
Steamworks.SteamNetworking.CloseP2PSessionWithUser(peer.SteamId.Value);
remotePeers.Remove(peer);
}
public override void SendPassword(string password)
{
//owner doesn't send passwords
}
public override void Close(PeerDisconnectPacket peerDisconnectPacket)
{
if (!isActive) { return; }
isActive = false;
for (int i = remotePeers.Count - 1; i >= 0; i--)
{
DisconnectPeer(remotePeers[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
}
Thread.Sleep(100);
for (int i = remotePeers.Count - 1; i >= 0; i--)
{
ClosePeerSession(remotePeers[i]);
}
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
SteamManager.LeaveLobby();
Steamworks.SteamNetworking.ResetActions();
Steamworks.SteamUser.OnValidateAuthTicketResponse -= OnAuthChange;
}
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
{
if (!isActive) { return; }
IWriteMessage msgToSend = new WriteOnlyMessage();
byte[] msgData = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
WriteSteamId(msgToSend, selfSteamID);
WriteSteamId(msgToSend, selfSteamID);
msgToSend.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = deliveryMethod,
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None
});
msgToSend.WriteNetSerializableStruct(new PeerPacketMessage
{
Buffer = msgData
});
ForwardToServerProcess(msgToSend);
}
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
{
//not currently used by SteamP2POwnerPeer
throw new NotImplementedException();
}
private static void ForwardToServerProcess(IWriteMessage msg)
{
byte[] bufToSend = new byte[msg.LengthBytes];
msg.Buffer[..msg.LengthBytes].CopyTo(bufToSend.AsSpan());
ChildServerRelay.Write(bufToSend);
}
private void ForwardToRemotePeer(DeliveryMethod deliveryMethod, SteamId recipent, IWriteMessage outMsg)
{
byte[] buf = outMsg.PrepareForSending(compressPastThreshold: false, out _, out int length);
if (length + 4 >= MsgConstants.MTU)
{
DebugConsole.Log($"WARNING: message length comes close to exceeding MTU, forcing reliable send ({length} bytes)");
deliveryMethod = DeliveryMethod.Reliable;
}
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, deliveryMethod.ToSteam());
sentBytes += length;
if (successSend) { return; }
if (deliveryMethod is DeliveryMethod.Unreliable)
{
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, DeliveryMethod.Reliable.ToSteam());
sentBytes += length;
}
if (!successSend)
{
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
}
}
#if DEBUG
public override void ForceTimeOut()
{
//TODO: reimplement?
}
#endif
}
}

View File

@@ -1,11 +0,0 @@
#nullable enable
namespace Barotrauma
{
abstract class FriendProvider
{
public abstract ServerListScreen.FriendInfo[] RetrieveFriends();
public abstract void RetrieveAvatar(ServerListScreen.FriendInfo friend, ServerListScreen.AvatarSize avatarSize);
public abstract string GetUserName();
}
}

View File

@@ -1,67 +0,0 @@
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using Barotrauma.Networking;
using Barotrauma.Steam;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
class SteamFriendProvider : FriendProvider
{
private static ServerListScreen.FriendInfo FromSteamFriend(Steamworks.Friend steamFriend)
=> new ServerListScreen.FriendInfo(
steamFriend.Name,
new SteamId(steamFriend.Id),
steamFriend.State switch
{
Steamworks.FriendState.Offline => ServerListScreen.FriendInfo.Status.Offline,
Steamworks.FriendState.Invisible => ServerListScreen.FriendInfo.Status.Offline,
_ when steamFriend.IsPlayingThisGame => ServerListScreen.FriendInfo.Status.PlayingBarotrauma,
_ when steamFriend.GameInfo is { GameID: var gameId } && gameId > 0 => ServerListScreen.FriendInfo.Status.PlayingAnotherGame,
_ => ServerListScreen.FriendInfo.Status.NotPlaying
})
{
ServerName = steamFriend.GetRichPresence("servername"),
ConnectCommand = steamFriend.GetRichPresence("connect") is { } connectCmd
? ToolBox.ParseConnectCommand(ToolBox.SplitCommand(connectCmd))
: Option<ConnectCommand>.None()
};
public override ServerListScreen.FriendInfo[] RetrieveFriends()
=> SteamManager.IsInitialized
? Steamworks.SteamFriends.GetFriends().Select(FromSteamFriend).ToArray()
: Array.Empty<ServerListScreen.FriendInfo>();
public override void RetrieveAvatar(ServerListScreen.FriendInfo friend, ServerListScreen.AvatarSize avatarSize)
{
if (!(friend.Id is SteamId steamId)) { return; }
Func<Steamworks.SteamId, Task<Steamworks.Data.Image?>> avatarFunc = avatarSize switch
{
ServerListScreen.AvatarSize.Small => Steamworks.SteamFriends.GetSmallAvatarAsync,
ServerListScreen.AvatarSize.Medium => Steamworks.SteamFriends.GetMediumAvatarAsync,
ServerListScreen.AvatarSize.Large => Steamworks.SteamFriends.GetLargeAvatarAsync,
};
TaskPool.Add($"Get{avatarSize}AvatarAsync", avatarFunc(steamId.Value), task =>
{
if (!task.TryGetResult(out Steamworks.Data.Image? img)) { return; }
if (!(img is { } avatarImage)) { return; }
if (friend.Avatar.TryUnwrap(out var prevAvatar))
{
prevAvatar.Remove();
}
#warning TODO: create an avatar atlas?
var avatarTexture = new Texture2D(GameMain.Instance.GraphicsDevice, (int)avatarImage.Width, (int)avatarImage.Height);
avatarTexture.SetData(avatarImage.Data);
friend.Avatar = Option<Sprite>.Some(new Sprite(avatarTexture, null, null));
});
}
public override string GetUserName()
=> SteamManager.GetUsername();
}
}

View File

@@ -6,6 +6,7 @@ using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Steamworks.Data;
using Color = Microsoft.Xna.Framework.Color;
using Socket = System.Net.Sockets.Socket;
@@ -34,19 +35,21 @@ namespace Barotrauma.Networking
{
if (CoroutineManager.IsCoroutineRunning("ConnectToServer")) { return; }
switch (serverInfo.Endpoint)
var endpointOption = serverInfo.Endpoints.FirstOrNone(e => e is not EosP2PEndpoint);
if (!endpointOption.TryUnwrap(out var endpoint)) { return; }
switch (endpoint)
{
case LidgrenEndpoint { NetEndpoint: var endPoint }:
GetIPAddressPing(serverInfo, endPoint, onPingDiscovered);
break;
case SteamP2PEndpoint steamP2PEndpoint:
TaskPool.Add($"EstimateSteamLobbyPing ({steamP2PEndpoint.StringRepresentation})",
case SteamP2PEndpoint:
TaskPool.Add($"EstimateSteamLobbyPing ({endpoint.StringRepresentation})",
EstimateSteamLobbyPing(serverInfo),
t =>
{
if (!t.TryGetResult(out Option<int> ping)) { return; }
serverInfo.Ping = ping;
if (!t.TryGetResult(out Result<int, SteamLobbyPingError> ping)) { return; }
serverInfo.Ping = ping.TryUnwrapSuccess(out var ms) ? Option.Some(ms) : Option.None;
onPingDiscovered(serverInfo);
});
break;
@@ -99,35 +102,57 @@ namespace Barotrauma.Networking
return loadedLobby;
}
private static async Task<Option<int>> EstimateSteamLobbyPing(ServerInfo serverInfo)
{
if (!(serverInfo.Endpoint is SteamP2PEndpoint { SteamId: var ownerId })) { return Option<int>.None(); }
while (!steamPingInfoReady) { await Task.Delay(50); }
Lobby lobby;
private enum SteamLobbyPingError
{
SteamPingUnsupported,
FailedToGetHostLocationData,
FailedToParseHostLocationData,
PingEstimationFailed
}
private static async Task<Result<int, SteamLobbyPingError>> EstimateSteamLobbyPing(ServerInfo serverInfo)
{
while (!steamPingInfoReady)
{
if (!SteamManager.IsInitialized) { return Result.Failure(SteamLobbyPingError.SteamPingUnsupported); }
await Task.Delay(50);
}
string pingLocationStr = "";
if (serverInfo.MetadataSource.TryUnwrap(out SteamP2PServerProvider.DataSource src))
{
lobby = src.Lobby;
var lobby = src.Lobby;
pingLocationStr = lobby.GetData("steampinglocation");
if (pingLocationStr.IsNullOrEmpty()) { pingLocationStr = lobby.GetData("pinglocation"); }
}
else
else if (serverInfo.MetadataSource.TryUnwrap(out EosServerProvider.DataSource srcEos))
{
var friendLobby = await GetSteamLobbyForUser(ownerId);
if (friendLobby is null) { return Option<int>.None(); }
lobby = friendLobby.Value;
pingLocationStr = srcEos.SteamPingLocation;
}
else if (serverInfo.Endpoints.OfType<SteamP2PEndpoint>().FirstOrNone().TryUnwrap(out var steamP2PEndpoint))
{
var friendLobby = await GetSteamLobbyForUser(steamP2PEndpoint.SteamId);
pingLocationStr = friendLobby?.GetData("steampinglocation") ?? "";
}
var pingLocation = NetPingLocation.TryParseFromString(lobby.GetData("pinglocation"));
if (pingLocationStr.IsNullOrEmpty())
{
return Result.Failure(SteamLobbyPingError.FailedToGetHostLocationData);
}
var pingLocation = NetPingLocation.TryParseFromString(pingLocationStr);
if (pingLocation.HasValue && Steamworks.SteamNetworkingUtils.LocalPingLocation.HasValue)
{
int ping = Steamworks.SteamNetworkingUtils.LocalPingLocation.Value.EstimatePingTo(pingLocation.Value);
return ping >= 0 ? Option<int>.Some(ping) : Option<int>.None();
if (ping < 0) { return Result.Failure(SteamLobbyPingError.PingEstimationFailed); }
return Result.Success(ping);
}
else
{
return Option<int>.None();
return Result.Failure(SteamLobbyPingError.FailedToParseHostLocationData);
}
}
@@ -173,7 +198,7 @@ namespace Barotrauma.Networking
}
if (endPoint?.Address == null) { return Option<int>.None(); }
//don't attempt to ping if the address is IPv6 and it's not supported
if (endPoint.Address.AddressFamily == AddressFamily.InterNetworkV6 && !Socket.OSSupportsIPv6) { return Option<int>.None(); }

View File

@@ -22,9 +22,9 @@ namespace Barotrauma.Networking
public abstract void Write(XElement element);
}
public Endpoint Endpoint { get; private set; }
public ImmutableArray<Endpoint> Endpoints { get; }
public Option<DataSource> MetadataSource = Option<DataSource>.None();
public Option<DataSource> MetadataSource = Option.None;
[Serialize("", IsPropertySaveable.Yes)]
public string ServerName { get; set; } = "";
@@ -75,6 +75,8 @@ namespace Barotrauma.Networking
[Serialize("", IsPropertySaveable.Yes)]
public LanguageIdentifier Language { get; set; }
public bool EosCrossplay { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public string SelectedSub { get; set; } = string.Empty;
@@ -84,49 +86,30 @@ namespace Barotrauma.Networking
public bool Checked = false;
public readonly struct ContentPackageInfo
{
public readonly string Name;
public readonly string Hash;
public readonly Option<ContentPackageId> Id;
public ContentPackageInfo(string name, string hash, Option<ContentPackageId> id)
{
Name = name;
Hash = hash;
Id = id;
}
public ContentPackageInfo(ContentPackage pkg)
{
Name = pkg.Name;
Hash = pkg.Hash.StringRepresentation;
Id = pkg.UgcId;
}
}
public ImmutableArray<ContentPackageInfo> ContentPackages;
public ImmutableArray<ServerListContentPackageInfo> ContentPackages;
public int ContentPackageCount;
public bool IsModded => ContentPackages.Any(p => !GameMain.VanillaContent.NameMatches(p.Name));
public ServerInfo(Endpoint endpoint)
public ServerInfo(params Endpoint[] endpoint) : this(endpoint.ToImmutableArray()) { }
public ServerInfo(ImmutableArray<Endpoint> endpoints)
{
SerializableProperties = SerializableProperty.GetProperties(this);
Endpoint = endpoint;
ContentPackages = ImmutableArray<ContentPackageInfo>.Empty;
Endpoints = endpoints;
ContentPackages = ImmutableArray<ServerListContentPackageInfo>.Empty;
}
public static ServerInfo FromServerConnection(NetworkConnection connection, ServerSettings serverSettings)
public static ServerInfo FromServerEndpoints(ImmutableArray<Endpoint> endpoints, ServerSettings serverSettings)
{
var serverInfo = new ServerInfo(connection.Endpoint)
var serverInfo = new ServerInfo(endpoints)
{
GameMode = GameMain.NetLobbyScreen.SelectedMode?.Identifier ?? Identifier.Empty,
GameStarted = Screen.Selected != GameMain.NetLobbyScreen,
GameVersion = GameMain.Version,
PlayerCount = GameMain.Client.ConnectedClients.Count,
ContentPackages = ContentPackageManager.EnabledPackages.All.Select(p => new ContentPackageInfo(p)).ToImmutableArray(),
ContentPackages = ContentPackageManager.EnabledPackages.All.Select(p => new ServerListContentPackageInfo(p)).ToImmutableArray(),
Ping = GameMain.Client.Ping,
// -------------------------------------
@@ -225,7 +208,7 @@ namespace Barotrauma.Networking
playStyleName.RectTransform.IsFixedSize = true;
var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
Endpoint?.ServerTypeString ?? string.Empty,
Endpoints.First().ServerTypeString,
textAlignment: Alignment.TopLeft)
{
CanBeFocused = false
@@ -460,8 +443,12 @@ namespace Barotrauma.Networking
GameVersion = version;
}
if (int.TryParse(valueGetter("playercount"), out int playerCount)) { PlayerCount = playerCount; }
if (int.TryParse(valueGetter("maxplayernum"), out int maxPlayers)) { MaxPlayers = maxPlayers; }
if (int.TryParse(valueGetter("maxplayers"), out int maxPlayers)) { MaxPlayers = maxPlayers; }
else if (int.TryParse(valueGetter("maxplayernum"), out maxPlayers)) { MaxPlayers = maxPlayers; }
if (Enum.TryParse(valueGetter("modeselectionmode"), out SelectionMode modeSelectionMode)) { ModeSelectionMode = modeSelectionMode; }
if (Enum.TryParse(valueGetter("subselectionmode"), out SelectionMode subSelectionMode)) { SubSelectionMode = subSelectionMode; }
HasPassword = getBool("haspassword");
@@ -471,6 +458,8 @@ namespace Barotrauma.Networking
AllowSpectating = getBool("allowspectating");
AllowRespawn = getBool("allowrespawn");
VoipEnabled = getBool("voicechatenabled");
EosCrossplay = getBool("eoscrossplay");
GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty;
if (float.TryParse(valueGetter("traitors"), NumberStyles.Any, CultureInfo.InvariantCulture, out float traitorProbability)) { TraitorProbability = traitorProbability; }
if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; }
@@ -488,27 +477,21 @@ namespace Barotrauma.Networking
}
}
private static ContentPackageInfo[] ExtractContentPackageInfo(string serverName, Func<string, string?> valueGetter)
private static ServerListContentPackageInfo[] ExtractContentPackageInfo(string serverName, Func<string, string?> valueGetter)
{
//workaround to ServerRules queries truncating the values to 255 bytes
int individualPackageIndex = 0;
string? individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
if (!individualPackage.IsNullOrEmpty())
{
List<ContentPackageInfo> contentPackages = new List<ContentPackageInfo>();
List<ServerListContentPackageInfo> contentPackages = new List<ServerListContentPackageInfo>();
do
{
string[] splitPackageInfo = individualPackage.Split(',');
if (splitPackageInfo.Length != 3)
if (!ServerListContentPackageInfo.ParseSingleEntry(individualPackage).TryUnwrap(out var info))
{
DebugConsole.Log(
$"Error in a server's content package list: malformed content package info ({individualPackage}).");
return Array.Empty<ContentPackageInfo>();
return Array.Empty<ServerListContentPackageInfo>();
}
string name = splitPackageInfo[0];
string hash = splitPackageInfo[1];
ulong.TryParse(splitPackageInfo[2], out ulong id);
contentPackages.Add(new ContentPackageInfo(name, hash, Option<ContentPackageId>.Some(new SteamWorkshopId(id))));
contentPackages.Add(info);
individualPackageIndex++;
individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
@@ -518,43 +501,58 @@ namespace Barotrauma.Networking
string? joinedNames = valueGetter("contentpackage");
string? joinedHashes = valueGetter("contentpackagehash");
string? joinedWorkshopIds = valueGetter("contentpackageid");
string? joinedUgcIds = valueGetter("contentpackageid");
string[] contentPackageNames = joinedNames.IsNullOrEmpty() ? Array.Empty<string>() : joinedNames.Split(',');
string[] contentPackageHashes = joinedHashes.IsNullOrEmpty() ? Array.Empty<string>() : joinedHashes.Split(',');
#warning TODO: genericize
ulong[] contentPackageIds = joinedWorkshopIds.IsNullOrEmpty() ? new ulong[1] : SteamManager.ParseWorkshopIds(joinedWorkshopIds).ToArray();
var contentPackageNames = joinedNames.IsNullOrEmpty() ? Array.Empty<string>() : joinedNames.SplitEscaped(',');
var contentPackageHashes = joinedHashes.IsNullOrEmpty() ? Array.Empty<string>() : joinedHashes.SplitEscaped(',');
var contentPackageIds = joinedUgcIds.IsNullOrEmpty() ? new string[1] { string.Empty } : joinedUgcIds.SplitEscaped(',');
if (contentPackageNames.Length != contentPackageHashes.Length || contentPackageHashes.Length != contentPackageIds.Length)
if (contentPackageNames.Count != contentPackageHashes.Count || contentPackageHashes.Count != contentPackageIds.Count)
{
DebugConsole.Log(
$"The number of names, hashes and Workshop IDs on server \"{serverName}\"" +
$" doesn't match: {contentPackageNames.Length} names ({string.Join(", ", contentPackageNames)}), {contentPackageHashes.Length} hashes, {contentPackageIds.Length} ids)");
return Array.Empty<ContentPackageInfo>();
$"The number of names, hashes and UGC IDs on server \"{serverName}\"" +
$" doesn't match: {contentPackageNames.Count} names ({string.Join(", ", contentPackageNames)}), {contentPackageHashes.Count} hashes, {contentPackageIds.Count} ids)");
return Array.Empty<ServerListContentPackageInfo>();
}
return contentPackageNames
.Zip(contentPackageHashes, (name, hash) => (name, hash))
.Zip(contentPackageIds, (t1, id) =>
new ContentPackageInfo(
new ServerListContentPackageInfo(
t1.name,
t1.hash,
Option<ContentPackageId>.Some(new SteamWorkshopId(id))))
ContentPackageId.Parse(id)))
.ToArray();
}
public static Option<ServerInfo> FromXElement(XElement element)
{
var endpoints = new List<Endpoint>();
string endpointStr
= element.GetAttributeString("Endpoint", null)
?? element.GetAttributeString("OwnerID", null)
?? $"{element.GetAttributeString("IP", "")}:{element.GetAttributeInt("Port", 0)}";
if (Endpoint.Parse(endpointStr).TryUnwrap(out var endpoint))
{
endpoints.Add(endpoint);
}
else
{
var multipleEndpointStrs
= element.GetAttributeStringArray("Endpoints", Array.Empty<string>());
endpoints.AddRange(
multipleEndpointStrs
.Select(Endpoint.Parse)
.NotNone());
}
if (!Endpoint.Parse(endpointStr).TryUnwrap(out var endpoint)) { return Option<ServerInfo>.None(); }
if (endpoints.Count == 0) { return Option.None; }
var gameVersionStr = element.GetAttributeString("GameVersion", "");
if (!Version.TryParse(gameVersionStr, out var gameVersion)) { gameVersion = GameMain.Version; }
var info = new ServerInfo(endpoint)
var info = new ServerInfo(endpoints.ToImmutableArray())
{
GameVersion = gameVersion
};
@@ -562,14 +560,14 @@ namespace Barotrauma.Networking
info.MetadataSource = DataSource.Parse(element);
return Option<ServerInfo>.Some(info);
return Option.Some(info);
}
public XElement ToXElement()
{
XElement element = new XElement(GetType().Name);
element.SetAttributeValue("Endpoint", Endpoint.ToString());
element.SetAttributeValue("Endpoints", string.Join(",", Endpoints.Select(e => e.StringRepresentation)));
element.SetAttributeValue("GameVersion", GameVersion.ToString());
SerializableProperty.SerializeProperties(this, element, saveIfDefault: true);
@@ -588,9 +586,9 @@ namespace Barotrauma.Networking
}
public bool Equals(ServerInfo other)
=> other.Endpoint == Endpoint;
=> other.Endpoints.Any(e => Endpoints.Contains(e));
public override int GetHashCode() => Endpoint.GetHashCode();
public override int GetHashCode() => Endpoints.First().GetHashCode();
string ISerializableEntity.Name => "ServerInfo";
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; }

View File

@@ -15,7 +15,7 @@ namespace Barotrauma
this.providers = providers.ToImmutableArray();
}
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
protected override void RetrieveServersImpl(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
{
int providersFinished = 0;
void ackFinishedProvider()

View File

@@ -0,0 +1,139 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.Networking;
namespace Barotrauma;
sealed class EosServerProvider : ServerProvider
{
public sealed class DataSource : ServerInfo.DataSource
{
public readonly string SteamPingLocation;
public DataSource(string steamPingLocation)
{
SteamPingLocation = steamPingLocation;
}
public override void Write(XElement element) { /* do nothing */ }
}
protected override void RetrieveServersImpl(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
{
if (EosInterface.IdQueries.GetLoggedInPuids() is not { Length: > 0 } loggedInPuids) { return; }
int finishedTaskCount = 0;
int totalTaskCount = EosInterface.Sessions.MaxBucketIndex + 1 - EosInterface.Sessions.MinBucketIndex;
void countTaskFinished()
{
finishedTaskCount++;
if (finishedTaskCount == totalTaskCount)
{
onQueryCompleted();
}
}
void onTaskFinished(Task t)
{
using var janitor = Janitor.Start();
janitor.AddAction(countTaskFinished);
if (!t.TryGetResult(
out Result<ImmutableArray<EosInterface.Sessions.RemoteSession>, EosInterface.Sessions.RemoteSession.Query.Error>? result))
{
return;
}
if (!result.TryUnwrapSuccess(out var sessions))
{
return;
}
var addedEndpoints = new HashSet<Endpoint>();
foreach (var session in sessions)
{
if (!session.Attributes.TryGetValue("ServerName".ToIdentifier(), out var serverName))
{
continue;
}
var endpointOption = Endpoint.Parse(session.HostAddress);
if (!endpointOption.TryUnwrap(out var primaryEndpoint))
{
continue;
}
var endpoints = new List<Endpoint> { primaryEndpoint };
if (primaryEndpoint is EosP2PEndpoint
&& session.Attributes.TryGetValue("SteamP2PEndpoint".ToIdentifier(), out var steamIdStr)
&& SteamP2PEndpoint.Parse(steamIdStr).TryUnwrap(out var steamP2PEndpoint))
{
endpoints.Add(steamP2PEndpoint);
}
else if (primaryEndpoint is LidgrenEndpoint
{
Address: LidgrenAddress address, Port: NetConfig.DefaultPort
}
&& session.Attributes.TryGetValue("Port".ToIdentifier(), out var portStr)
&& ushort.TryParse(portStr, out var port))
{
// Port isn't included as part of the host address
// because it's filled in by EOS automatically,
// so extract the port from a separate attribute and
// fix up the endpoint here
primaryEndpoint = new LidgrenEndpoint(address.NetAddress, port);
endpoints[0] = primaryEndpoint;
}
// Prevent duplicate entries
if (endpoints.Intersect(addedEndpoints).Any())
{
continue;
}
addedEndpoints.UnionWith(endpoints);
var serverInfo = new ServerInfo(endpoints.ToImmutableArray())
{
ServerName = serverName
};
serverInfo.UpdateInfo(key =>
session.Attributes.TryGetValue(key.ToIdentifier(), out var value) ? value : string.Empty);
serverInfo.EosCrossplay = true;
serverInfo.Checked = true;
if (session.Attributes.TryGetValue("steampinglocation".ToIdentifier(), out var steamPingLocation))
{
serverInfo.MetadataSource = Option.Some((ServerInfo.DataSource)new DataSource(steamPingLocation));
}
onServerDataReceived(serverInfo, this);
}
};
for (int bucketIndex = EosInterface.Sessions.MinBucketIndex; bucketIndex <= EosInterface.Sessions.MaxBucketIndex; bucketIndex++)
{
var query = new EosInterface.Sessions.RemoteSession.Query(
BucketIndex: bucketIndex,
LocalUserId: loggedInPuids.First(),
MaxResults: 200,
Attributes: ImmutableDictionary<Identifier, string>.Empty);
TaskPool.Add(
$"{nameof(EosServerProvider)}.{nameof(RetrieveServersImpl)}",
query.Run(),
onTaskFinished);
}
}
public override void Cancel()
{
}
}

View File

@@ -6,12 +6,12 @@ namespace Barotrauma
{
abstract class ServerProvider
{
public void RetrieveServers(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
public void RetrieveServers(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
{
Cancel();
RetrieveServersImpl(onServerDataReceived, onQueryCompleted);
}
protected abstract void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted);
protected abstract void RetrieveServersImpl(Action<ServerInfo, ServerProvider> action, Action onQueryCompleted);
public abstract void Cancel();
}
}

View File

@@ -46,42 +46,43 @@ namespace Barotrauma
MetadataSource = Option<ServerInfo.DataSource>.Some(new DataSource((UInt16)entry.QueryPort))
});
private static void HandleResponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo> onServerDataReceived)
private void HandleResponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo, ServerProvider> onServerDataReceived)
{
TaskPool.Add($"QueryServerRules (GetServers, {entry.Name}, {entry.Address})", entry.QueryRulesAsync(),
t =>
{
if (t.Status == TaskStatus.Faulted)
{
TaskPool.PrintTaskExceptions(t, $"Failed to retrieve rules for {entry.Name}");
TaskPool.PrintTaskExceptions(t, $"Failed to retrieve rules for {entry.Name}", msg => DebugConsole.ThrowError(msg));
return;
}
if (!t.TryGetResult(out Dictionary<string, string> rules)) { return; }
if (!t.TryGetResult(out Dictionary<string, string>? rules)) { return; }
if (rules is null) { return; }
if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
serverInfo.UpdateInfo(key =>
{
if (rules.TryGetValue(key, out var val)) { return val; }
return null;
});
serverInfo.Checked = true; //rules != null;
serverInfo.Checked = true;
onServerDataReceived(serverInfo);
onServerDataReceived(serverInfo, this);
});
}
private static void HandleUnresponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo> onServerDataReceived)
private void HandleUnresponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo, ServerProvider> onServerDataReceived)
{
//TODO: do we still want to list unresponsive servers?
if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
onServerDataReceived(serverInfo);
onServerDataReceived(serverInfo, this);
}
private Steamworks.ServerList.Internet? serverQuery;
private CoroutineHandle? queryCoroutine;
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
protected override void RetrieveServersImpl(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
{
if (!SteamManager.IsInitialized)
{
@@ -139,7 +140,7 @@ namespace Barotrauma
CoroutineManager.StopCoroutines(selfQueryCoroutine);
dequeue();
if (t.Status == TaskStatus.Faulted) { TaskPool.PrintTaskExceptions(t, "Failed to retrieve servers"); }
if (t.Status == TaskStatus.Faulted) { TaskPool.PrintTaskExceptions(t, "Failed to retrieve servers", msg => DebugConsole.ThrowError(msg)); }
selfServerQuery.Dispose();
}

View File

@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.Networking;
@@ -24,7 +25,7 @@ namespace Barotrauma
private object? queryRef = null;
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
protected override void RetrieveServersImpl(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
{
if (!SteamManager.IsInitialized)
{
@@ -61,7 +62,7 @@ namespace Barotrauma
// If queryRef != selfQueryRef, this query was cancelled
if (!ReferenceEquals(selfQueryRef, queryRef)) { return; }
if (!t.TryGetResult(out Steamworks.Data.Lobby[] lobbies)
if (!t.TryGetResult(out Steamworks.Data.Lobby[]? lobbies)
|| lobbies is null
|| lobbies.Length == 0)
{
@@ -74,16 +75,23 @@ namespace Barotrauma
string lobbyOwnerStr = lobby.GetData("lobbyowner") ?? "";
lobbyQuery = lobbyQuery.WithoutKeyValue("lobbyowner", lobbyOwnerStr);
string serverName = lobby.GetData("name") ?? "";
string serverName = lobby.GetData("servername").FallbackNullOrEmpty(lobby.GetData("name")) ?? "";
if (string.IsNullOrEmpty(serverName)) { continue; }
var ownerId = SteamId.Parse(lobbyOwnerStr);
if (!ownerId.TryUnwrap(out var lobbyOwnerId)) { continue; }
var eosP2PEndpointOption = EosP2PEndpoint
.Parse(lobby.GetData("EosEndpoint") ?? "")
.Select(e => (Endpoint)e);
if (retrieved.Contains(lobbyOwnerId)) { continue; }
retrieved.Add(lobbyOwnerId);
var serverInfo = new ServerInfo(new SteamP2PEndpoint(lobbyOwnerId))
var endpoints = new List<Endpoint> { new SteamP2PEndpoint(lobbyOwnerId) };
if (eosP2PEndpointOption.TryUnwrap(out var eosP2PEndpoint)) { endpoints.Add(eosP2PEndpoint); }
var serverInfo = new ServerInfo(endpoints.ToImmutableArray())
{
ServerName = serverName,
MetadataSource = Option<ServerInfo.DataSource>.Some(new DataSource(lobby))
@@ -91,7 +99,7 @@ namespace Barotrauma
serverInfo.UpdateInfo(key => lobby.GetData(key));
serverInfo.Checked = true;
onServerDataReceived(serverInfo);
onServerDataReceived(serverInfo, this);
}
startQuery();
}

View File

@@ -8,6 +8,7 @@ using Barotrauma.Steam;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using Barotrauma.Debugging;
#if WINDOWS
using SharpDX;
@@ -17,7 +18,6 @@ using SharpDX;
namespace Barotrauma
{
#if WINDOWS || LINUX || OSX
/// <summary>
/// The main class.
/// </summary>
@@ -52,7 +52,10 @@ namespace Barotrauma
Game = null;
executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
Directory.SetCurrentDirectory(executableDir);
SteamManager.Initialize();
DebugConsoleCore.Init(
newMessage: (s, c) => DebugConsole.NewMessage(s, c),
log: DebugConsole.Log);
StoreIntegration.Init(ref args);
EnableNvOptimus();
Game = new GameMain(args);
Game.Run();
@@ -188,6 +191,10 @@ namespace Barotrauma
{
sb.AppendLine("SteamManager initialized");
}
else if (EosInterface.IdQueries.IsLoggedIntoEosConnect)
{
sb.AppendLine("Logged in to EOS connect");
}
if (GameMain.Client != null)
{
@@ -344,6 +351,4 @@ namespace Barotrauma
}
}
#endif
}
}

View File

@@ -49,7 +49,7 @@ namespace Barotrauma
static void UnlockAchievement(string id)
{
SteamAchievementManager.UnlockAchievement(id.ToIdentifier(), unlockClients: true);
AchievementManager.UnlockAchievement(id.ToIdentifier(), unlockClients: true);
}
}

View File

@@ -9,7 +9,7 @@ using System.Xml.Linq;
namespace Barotrauma
{
class SinglePlayerCampaignSetupUI : CampaignSetupUI
sealed class SinglePlayerCampaignSetupUI : CampaignSetupUI
{
private GUIListBox subList;

View File

@@ -550,7 +550,10 @@ namespace Barotrauma
width -= 16;
}
valueText = ToolBox.WrapText(valueText, width, GUIStyle.SubHeadingFont.Value);
if (GUIStyle.SubHeadingFont.Value != null)
{
valueText = ToolBox.WrapText(valueText, width, GUIStyle.SubHeadingFont.Value);
}
wrappedText = valueText;
}
}

View File

@@ -974,7 +974,7 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState);
GUI.Draw(Cam, spriteBatch);
if (!string.IsNullOrWhiteSpace(DrawnTooltip))
if (!string.IsNullOrWhiteSpace(DrawnTooltip) && GUIStyle.SmallFont.Value != null)
{
string tooltip = ToolBox.WrapText(DrawnTooltip, 256.0f, GUIStyle.SmallFont.Value);
GUI.DrawString(spriteBatch, PlayerInput.MousePosition + new Vector2(32, 32), tooltip, Color.White, Color.Black * 0.8f, 4, GUIStyle.SmallFont);

View File

@@ -9,7 +9,7 @@ using Microsoft.Xna.Framework.Graphics;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Collections.Immutable;
using System.Diagnostics;
using Barotrauma.IO;
using System.Linq;
@@ -21,7 +21,7 @@ using Barotrauma.Steam;
namespace Barotrauma
{
class MainMenuScreen : Screen
sealed class MainMenuScreen : Screen
{
private enum Tab
{
@@ -33,7 +33,7 @@ namespace Barotrauma
JoinServer = 5,
CharacterEditor = 6,
SubmarineEditor = 7,
SteamWorkshop = 8,
Mods = 8,
Credits = 9,
Empty = 10
}
@@ -59,6 +59,8 @@ namespace Barotrauma
private GUIImage playstyleBanner;
private GUITextBlock playstyleDescription;
private static string RemoteContentUrl => GameSettings.CurrentConfig.RemoteMainMenuContentUrl;
private readonly GUIComponent remoteContentContainer;
private XDocument remoteContentDoc;
@@ -76,11 +78,76 @@ namespace Barotrauma
private GUITextBlock tutorialHeader, tutorialDescription;
private GUIListBox tutorialList;
private readonly GUITextBlock gameAnalyticsStatusText;
private readonly GUILayoutGroup leftTextFooterLayout;
private readonly GUILayoutGroup rightTextFooterLayout;
private GUIComponent versionMismatchWarning;
#region Creation
public MainMenuScreen(GameMain game)
public MainMenuScreen(GameMain game) : base()
{
leftTextFooterLayout = createTextFooter();
rightTextFooterLayout = createTextFooter();
gameAnalyticsStatusText = createLeftText(TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.Consent.Unknown}"));
createLeftText("Barotrauma v" + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
var privacyPolicyText = createRightText(TextManager.Get("privacypolicy").Fallback("Privacy policy"));
(Rectangle Rect, bool MouseOn) getPrivacyPolicyHoverRect()
{
var textSize = privacyPolicyText.Font.MeasureString(privacyPolicyText.Text);
var bottomRight = privacyPolicyText.Rect.Location.ToVector2()
+ privacyPolicyText.TextPos
+ privacyPolicyText.TextOffset;
var rect = new Rectangle((bottomRight - textSize).ToPoint(), textSize.ToPoint());
bool mouseOn = rect.Contains(PlayerInput.LatestMousePosition) && GUI.IsMouseOn(privacyPolicyText);
return (rect, mouseOn);
}
new GUICustomComponent(new RectTransform(Vector2.One, privacyPolicyText.RectTransform),
onUpdate: (dt, component) =>
{
var (_, mouseOn) = getPrivacyPolicyHoverRect();
if (mouseOn && PlayerInput.PrimaryMouseButtonClicked())
{
GameMain.ShowOpenUriPrompt("https://privacypolicy.daedalic.com");
}
},
onDraw: (sb, component) =>
{
var (rect, mouseOn) = getPrivacyPolicyHoverRect();
Color color = mouseOn ? Color.White : Color.White * 0.7f;
privacyPolicyText.TextColor = color;
GUI.DrawLine(sb, new Vector2(rect.Left, rect.Bottom), new Vector2(rect.Right, rect.Bottom), color);
});
createRightText("© " + DateTime.Now.Year + " Undertow Games & FakeFish. All rights reserved.");
createRightText("© " + DateTime.Now.Year + " Daedalic Entertainment GmbH. The Daedalic logo is a trademark of Daedalic Entertainment GmbH, Germany. All rights reserved.");
GUILayoutGroup createTextFooter()
=> new GUILayoutGroup(new RectTransform((1.0f, 0.06f), Frame.RectTransform, Anchor.BottomCenter))
{
ChildAnchor = Anchor.BottomLeft
};
GUITextBlock createTextInFooter(GUILayoutGroup footer, LocalizedString str, Alignment textAlignment)
{
var textBlock = new GUITextBlock(
rectT: new RectTransform((1.0f, 0.3f), footer.RectTransform),
text: str,
textAlignment: textAlignment,
font: GUIStyle.SmallFont,
textColor: Color.White * 0.7f);
textBlock.RectTransform.SetAsFirstChild();
return textBlock;
}
GUITextBlock createLeftText(LocalizedString str)
=> createTextInFooter(leftTextFooterLayout, str, Alignment.BottomLeft);
GUITextBlock createRightText(LocalizedString str)
=> createTextInFooter(rightTextFooterLayout, str, Alignment.BottomRight);
GameMain.Instance.ResolutionChanged += () =>
{
SetMenuTabPositioning();
@@ -306,7 +373,7 @@ namespace Barotrauma
{
ForceUpperCase = ForceUpperCase.Yes,
Enabled = true,
UserData = Tab.SteamWorkshop,
UserData = Tab.Mods,
OnClicked = SelectTab
};
@@ -321,7 +388,7 @@ namespace Barotrauma
},
Visible = false
};
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SubEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
@@ -378,7 +445,7 @@ namespace Barotrauma
OnClicked = (button, userData) =>
{
string url = TextManager.Get("EditorDisclaimerWikiUrl").Fallback("https://barotraumagame.com/wiki").Value;
GameMain.ShowOpenUrlInWebBrowserPrompt(url, promptExtensionTag: "wikinotice");
GameMain.ShowOpenUriPrompt(url, promptExtensionTag: "wikinotice");
return true;
}
};
@@ -474,39 +541,40 @@ namespace Barotrauma
menuTabs = new Dictionary<Tab, GUIFrame>
{
[Tab.Settings] = new GUIFrame(new RectTransform(new Vector2(relativeSize.X, 0.8f), GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset },
[Tab.Settings] = new GUIFrame(new RectTransform(new Vector2(relativeSize.X, 0.8f), Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset },
style: null)
{
CanBeFocused = false
},
[Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize * new Vector2(1.0f, 1.15f), GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset }),
[Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset })
[Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize * new Vector2(1.0f, 1.15f), Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset }),
[Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset })
};
CreateCampaignSetupUI();
var hostServerScale = new Vector2(0.7f, 1.2f);
menuTabs[Tab.HostServer] = new GUIFrame(new RectTransform(
Vector2.Multiply(relativeSize, hostServerScale), GUI.Canvas, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale))
Vector2.Multiply(relativeSize, hostServerScale), Frame.RectTransform, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale))
{ RelativeOffset = relativeOffset });
CreateHostServerFields();
//----------------------------------------------------------------------
menuTabs[Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset });
menuTabs[Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset });
CreateTutorialTab();
this.game = game;
menuTabs[Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null)
menuTabs[Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, Frame.RectTransform, Anchor.Center), style: null)
{
CanBeFocused = false
};
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, menuTabs[Tab.Credits].RectTransform, Anchor.Center), style: "GUIBackgroundBlocker")
var blockerFrame = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, menuTabs[Tab.Credits].RectTransform, Anchor.Center), style: "GUIBackgroundBlocker")
{
CanBeFocused = false
};
blockerFrame.RectTransform.RelativeOffset = GUI.IsUltrawide ? Vector2.Zero : new Vector2(0.05f, 0.0f);
var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f);
creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml");
@@ -517,6 +585,7 @@ namespace Barotrauma
};
SetMenuTabPositioning();
SelectTab(Tab.Empty);
}
private void SetMenuTabPositioning()
@@ -611,7 +680,7 @@ namespace Barotrauma
GameMain.LuaCs.Stop();
ResetModUpdateButton();
if (WorkshopItemsToUpdate.Any())
{
while (WorkshopItemsToUpdate.TryDequeue(out ulong workshopId))
@@ -636,6 +705,8 @@ namespace Barotrauma
versionMismatchWarning.Visible = GameMain.Version < ContentPackageManager.VanillaCorePackage.GameVersion;
ResetButtonStates(null);
Eos.EosAccount.ExecuteAfterLogin(AchievementManager.SyncBetweenPlatforms);
}
public override void Deselect()
@@ -751,7 +822,7 @@ namespace Barotrauma
case Tab.SubmarineEditor:
CoroutineManager.StartCoroutine(SelectScreenWithWaitCursor(GameMain.SubEditorScreen));
break;
case Tab.SteamWorkshop:
case Tab.Mods:
var settings = SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
settings.SelectTab(SettingsMenu.Tab.Mods);
tab = Tab.Settings;
@@ -767,6 +838,14 @@ namespace Barotrauma
}
selectedTab = tab;
leftTextFooterLayout.Visible = tab != Tab.Credits;
rightTextFooterLayout.Visible = tab != Tab.Credits;
foreach (var tabFrame in menuTabs.Values)
{
tabFrame.Visible = false;
}
if (menuTabs.TryGetValue(selectedTab, out var visibleTab)) { visibleTab.Visible = true; }
return true;
}
@@ -891,6 +970,17 @@ namespace Barotrauma
tutorialSkipWarning.Buttons[1].OnClicked += proceedToTab(Tab.Tutorials);
}
public override void AddToGUIUpdateList()
{
base.AddToGUIUpdateList();
switch (selectedTab)
{
case Tab.NewGame:
campaignSetupUI.CharacterMenus?.ForEach(static m => m.AddToGUIUpdateList());
break;
}
}
private void UpdateTutorialList()
{
foreach (GUITextBlock tutorialText in tutorialList.Content.Children)
@@ -971,35 +1061,55 @@ namespace Barotrauma
#endif
}
string arguments =
"-name \"" + ToolBox.EscapeCharacters(name) + "\"" +
" -public " + isPublicBox.Selected.ToString() +
" -playstyle " + ((PlayStyle)playstyleBanner.UserData).ToString() +
" -banafterwrongpassword " + wrongPasswordBanBox.Selected.ToString() +
" -karmaenabled " + (!karmaBox.Selected).ToString() +
" -maxplayers " + maxPlayersBox.Text +
$" -language \"{(LanguageIdentifier)languageDropdown.SelectedData}\"";
var arguments = new List<string>
{
"-name", name,
"-public", isPublicBox.Selected.ToString(),
"-playstyle", ((PlayStyle)playstyleBanner.UserData).ToString(),
"-banafterwrongpassword", wrongPasswordBanBox.Selected.ToString(),
"-karmaenabled", (!karmaBox.Selected).ToString(),
"-maxplayers", maxPlayersBox.Text,
"-language", languageDropdown.SelectedData.ToString()
};
if (!string.IsNullOrWhiteSpace(passwordBox.Text))
{
arguments += " -password \"" + ToolBox.EscapeCharacters(passwordBox.Text) + "\"";
arguments.Add("-password");
arguments.Add(passwordBox.Text);
}
else
{
arguments += " -nopassword";
arguments.Add("-nopassword");
}
if (SteamManager.GetSteamId().TryUnwrap(out var steamId1))
var puids = EosInterface.IdQueries.GetLoggedInPuids();
var endpoints = new List<Endpoint>();
if (SteamManager.GetSteamId().TryUnwrap(out var steamId))
{
arguments += " -steamid " + steamId1.Value;
endpoints.Add(new SteamP2PEndpoint(steamId));
}
if (puids.Length > 0)
{
endpoints.Add(new EosP2PEndpoint(puids[0]));
}
if (endpoints.Count == 0)
{
endpoints.Add(new LidgrenEndpoint(IPAddress.Loopback, NetConfig.DefaultPort));
}
if (endpoints.First() is P2PEndpoint firstEndpoint)
{
arguments.Add("-endpoint");
arguments.Add(firstEndpoint.StringRepresentation);
}
int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
arguments += " -ownerkey " + ownerKey;
arguments.Add("-ownerkey");
arguments.Add(ownerKey.ToString());
var processInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
WorkingDirectory = Directory.GetCurrentDirectory(),
#if !DEBUG
CreateNoWindow = true,
@@ -1007,16 +1117,15 @@ namespace Barotrauma
WindowStyle = ProcessWindowStyle.Hidden
#endif
};
arguments.ForEach(processInfo.ArgumentList.Add);
ChildServerRelay.Start(processInfo);
Thread.Sleep(1000); //wait until the server is ready before connecting
GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(
SteamManager.GetUsername().FallbackNullOrEmpty(name)),
SteamManager.GetSteamId().TryUnwrap(out var steamId)
? new SteamP2PEndpoint(steamId)
: (Endpoint)new LidgrenEndpoint(IPAddress.Loopback, NetConfig.DefaultPort),
endpoints.ToImmutableArray(),
name,
Option<int>.Some(ownerKey));
Option.Some(ownerKey));
}
catch (Exception e)
{
@@ -1030,21 +1139,6 @@ namespace Barotrauma
return true;
}
public override void AddToGUIUpdateList()
{
Frame.AddToGUIUpdateList();
if (selectedTab < Tab.Empty && menuTabs.TryGetValue(selectedTab, out GUIFrame tab) && tab != null)
{
tab.AddToGUIUpdateList();
switch (selectedTab)
{
case Tab.NewGame:
campaignSetupUI.CharacterMenus?.ForEach(m => m.AddToGUIUpdateList());
break;
}
}
}
private void UpdateOutOfDateWorkshopItemCount()
{
if (DateTime.Now < modUpdateStatus.WhenToRefresh) { return; }
@@ -1079,16 +1173,16 @@ namespace Barotrauma
modUpdateStatus = (DateTime.Now + ModUpdateInterval, count);
}
private static bool CanHostServer()
=> EosInterface.IdQueries.IsLoggedIntoEosConnect
|| SteamManager.IsInitialized
|| AssemblyInfo.CurrentConfiguration == AssemblyInfo.Configuration.Debug;
public override void Update(double deltaTime)
{
#if DEBUG
hostServerButton.Enabled = true;
#else
if (GameSettings.CurrentConfig.UseSteamMatchmaking)
{
hostServerButton.Enabled = SteamManager.IsInitialized;
}
#endif
hostServerButton.Enabled = CanHostServer();
gameAnalyticsStatusText.Text = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}");
UpdateOutOfDateWorkshopItemCount();
modUpdatesButton.Visible = modUpdateStatus.Count > 0;
@@ -1145,13 +1239,6 @@ namespace Barotrauma
}
}
readonly LocalizedString[] legalCrap = new LocalizedString[]
{
TextManager.Get("privacypolicy").Fallback("Privacy policy"),
"© " + DateTime.Now.Year + " Undertow Games & FakeFish. All rights reserved.",
"© " + DateTime.Now.Year + " Daedalic Entertainment GmbH. The Daedalic logo is a trademark of Daedalic Entertainment GmbH, Germany. All rights reserved."
};
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{
spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);
@@ -1160,42 +1247,6 @@ namespace Barotrauma
GUI.Draw(Cam, spriteBatch);
if (selectedTab != Tab.Credits)
{
#if !UNSTABLE
string versionString = "Barotrauma v" + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")";
GUIStyle.SmallFont.DrawString(spriteBatch, versionString, new Vector2(HUDLayoutSettings.Padding, GameMain.GraphicsHeight - GUIStyle.SmallFont.MeasureString(versionString).Y - HUDLayoutSettings.Padding * 0.75f), Color.White * 0.7f);
#endif
LocalizedString gameAnalyticsStatus = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}");
Vector2 textSize = GUIStyle.SmallFont.MeasureString(gameAnalyticsStatus).ToPoint().ToVector2();
GUIStyle.SmallFont.DrawString(spriteBatch, gameAnalyticsStatus, new Vector2(HUDLayoutSettings.Padding, GameMain.GraphicsHeight - GUIStyle.SmallFont.LineHeight * 2 - HUDLayoutSettings.Padding * 0.75f), Color.White * 0.7f);
Vector2 textPos = new Vector2(GameMain.GraphicsWidth - HUDLayoutSettings.Padding, GameMain.GraphicsHeight - HUDLayoutSettings.Padding * 0.75f);
for (int i = legalCrap.Length - 1; i >= 0; i--)
{
textSize = GUIStyle.SmallFont.MeasureString(legalCrap[i])
.ToPoint().ToVector2();
bool mouseOn = i == 0 &&
PlayerInput.MousePosition.X > textPos.X - textSize.X && PlayerInput.MousePosition.X < textPos.X &&
PlayerInput.MousePosition.Y > textPos.Y - textSize.Y && PlayerInput.MousePosition.Y < textPos.Y;
GUIStyle.SmallFont.DrawString(spriteBatch,
legalCrap[i], textPos - textSize,
mouseOn ? Color.White : Color.White * 0.7f);
if (i == 0)
{
GUI.DrawLine(spriteBatch, textPos, textPos - Vector2.UnitX * textSize.X, mouseOn ? Color.White : Color.White * 0.7f);
if (mouseOn && PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null)
{
GameMain.ShowOpenUrlInWebBrowserPrompt("http://privacypolicy.daedalic.com");
}
}
textPos.Y -= textSize.Y;
}
}
spriteBatch.End();
}

View File

@@ -2320,12 +2320,12 @@ namespace Barotrauma
List<ContextMenuOption> options = new List<ContextMenuOption>();
if (client.AccountId.TryUnwrap(out var accountId) && accountId is SteamId steamId)
if (client.AccountId.TryUnwrap(out var accountId))
{
options.Add(new ContextMenuOption("ViewSteamProfile", isEnabled: hasAccountId, onSelected: () =>
{
SteamManager.OverlayProfile(steamId);
}));
options.Add(new ContextMenuOption(accountId.ViewProfileLabel(), isEnabled: hasAccountId, onSelected: () =>
{
accountId.OpenProfile();
}));
}
options.Add(new ContextMenuOption("ModerationMenu.ManagePlayer", isEnabled: true, onSelected: () =>
@@ -2702,17 +2702,17 @@ namespace Barotrauma
}
}
if (selectedClient.AccountId.TryUnwrap(out var accountId) && accountId is SteamId steamId && Steam.SteamManager.IsInitialized)
if (selectedClient.AccountId.TryUnwrap(out var accountId))
{
var viewSteamProfileButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), headerContainer.RectTransform, Anchor.TopCenter) { MaxSize = new Point(int.MaxValue, (int)(40 * GUI.Scale)) },
TextManager.Get("ViewSteamProfile"))
accountId.ViewProfileLabel())
{
UserData = selectedClient
};
viewSteamProfileButton.TextBlock.AutoScaleHorizontal = true;
viewSteamProfileButton.OnClicked = (bt, userdata) =>
{
SteamManager.OverlayProfile(steamId);
accountId.OpenProfile();
return true;
};
}

View File

@@ -7,25 +7,20 @@ namespace Barotrauma
{
abstract partial class Screen
{
private GUIFrame frame;
public GUIFrame Frame
{
get
{
if (frame == null)
{
frame = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas), style: null)
{
CanBeFocused = false
};
public readonly GUIFrame Frame;
}
return frame;
}
protected Screen()
{
Frame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null)
{
CanBeFocused = false
};
}
/// <summary>
/// By default, creates a new frame for the screen and adds all elements to the gui update list.
/// By default, submits the screen's main GUIFrame and,
/// if requested upon construction, the social drawer,
/// to the GUI update list.
/// </summary>
public virtual void AddToGUIUpdateList()
{
@@ -68,9 +63,7 @@ namespace Barotrauma
public virtual void Release()
{
if (frame is null) { return; }
frame.RectTransform.Parent = null;
frame = null;
Frame.RectTransform.Parent = null;
}
}
}

View File

@@ -7,7 +7,9 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.Steam;
namespace Barotrauma
{
@@ -44,70 +46,6 @@ namespace Barotrauma
Disabled
}
//friends list
public sealed class FriendInfo
{
public string Name;
public readonly AccountId Id;
public enum Status
{
Offline,
NotPlaying,
PlayingAnotherGame,
PlayingBarotrauma
}
public readonly Status CurrentStatus;
public string ServerName;
public Option<ConnectCommand> ConnectCommand;
public Option<Sprite> Avatar;
public bool IsInServer
=> CurrentStatus == Status.PlayingBarotrauma && ConnectCommand.IsSome();
public bool IsPlayingBarotrauma
=> CurrentStatus == Status.PlayingBarotrauma;
public bool PlayingAnotherGame
=> CurrentStatus == Status.PlayingAnotherGame;
public bool IsOnline
=> CurrentStatus != Status.Offline;
public LocalizedString StatusText
=> CurrentStatus switch
{
Status.Offline => "",
_ when ConnectCommand.IsSome()
=> TextManager.GetWithVariable("FriendPlayingOnServer", "[servername]", ServerName),
_ => TextManager.Get($"Friend{CurrentStatus}")
};
public FriendInfo(string name, AccountId id, Status status)
{
Name = name;
Id = id;
CurrentStatus = status;
ConnectCommand = Option<ConnectCommand>.None();
Avatar = Option<Sprite>.None();
}
}
private GUILayoutGroup friendsButtonHolder;
private GUIButton friendsDropdownButton;
private GUIListBox friendsDropdown;
private readonly FriendProvider friendProvider = new SteamFriendProvider();
private List<FriendInfo> friendsList;
private GUIFrame friendPopup;
private double friendsListUpdateTime;
public enum TabEnum
{
All,
@@ -115,7 +53,7 @@ namespace Barotrauma
Recent
}
public struct Tab
public readonly struct Tab
{
public readonly string Storage;
public readonly GUIButton Button;
@@ -127,7 +65,7 @@ namespace Barotrauma
{
Storage = storage;
servers = new List<ServerInfo>();
Button = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), tabber.RectTransform),
Button = new GUIButton(new RectTransform(new Vector2(0.33f, 1.0f), tabber.RectTransform),
TextManager.Get($"ServerListTab.{tabEnum}"), style: "GUITabButton")
{
OnClicked = (_,__) =>
@@ -187,8 +125,7 @@ namespace Barotrauma
}
}
private readonly ServerProvider serverProvider
= new CompositeServerProvider(new SteamDedicatedServerProvider(), new SteamP2PServerProvider());
private ServerProvider serverProvider = null;
public GUITextBox ClientNameBox { get; private set; }
@@ -257,16 +194,16 @@ namespace Barotrauma
private bool sortedAscending = true;
private const float sidebarWidth = 0.2f;
public ServerListScreen()
public ServerListScreen() : base()
{
selectedServer = Option<ServerInfo>.None();
GameMain.Instance.ResolutionChanged += CreateUI;
CreateUI();
}
private string GetDefaultUserName()
private static Task<string> GetDefaultUserName()
{
return friendProvider.GetUserName();
return new CompositeFriendProvider(new SteamFriendProvider(), new EpicFriendProvider()).GetSelfUserName();
}
private void AddTernaryFilter(RectTransform parent, float elementHeight, Identifier tag, Action<TernaryOption> valueSetter)
@@ -331,13 +268,32 @@ namespace Barotrauma
var topRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform)) { Stretch = true };
var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.33f), topRow.RectTransform), TextManager.Get("JoinServer"), font: GUIStyle.LargeFont)
var titleContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.995f, 0.33f), topRow.RectTransform), isHorizontal: true) { Stretch = true };
var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), titleContainer.RectTransform), TextManager.Get("JoinServer"), font: GUIStyle.LargeFont)
{
Padding = Vector4.Zero,
ForceUpperCase = ForceUpperCase.Yes,
AutoScaleHorizontal = true
};
var friendsButton = new GUIButton(
new RectTransform(Vector2.One * 0.9f, titleContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
style: "FriendsButton")
{
OnClicked = (_, _) =>
{
if (SocialOverlay.Instance is { } socialOverlay) { socialOverlay.IsOpen = true; }
return false;
},
ToolTip = TextManager.GetWithVariable("SocialOverlayShortcutHint", "[shortcut]", SocialOverlay.ShortcutBindText)
};
new GUIFrame(new RectTransform(Vector2.One, friendsButton.RectTransform, Anchor.Center),
style: "FriendsButtonIcon")
{
CanBeFocused = false
};
var infoHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.33f), topRow.RectTransform), isHorizontal: true, Anchor.BottomLeft) { RelativeSpacing = 0.01f, Stretch = false };
var clientNameHolder = new GUILayoutGroup(new RectTransform(new Vector2(sidebarWidth, 1.0f), infoHolder.RectTransform)) { RelativeSpacing = 0.05f };
@@ -352,7 +308,13 @@ namespace Barotrauma
if (string.IsNullOrEmpty(ClientNameBox.Text))
{
ClientNameBox.Text = GetDefaultUserName();
TaskPool.Add("GetDefaultUserName",
GetDefaultUserName(),
t =>
{
if (!t.TryGetResult(out string name)) { return; }
if (ClientNameBox.Text.IsNullOrEmpty()) { ClientNameBox.Text = name; }
});
}
ClientNameBox.OnTextChanged += (textbox, text) =>
{
@@ -366,14 +328,6 @@ namespace Barotrauma
tabs[TabEnum.Favorites] = new Tab(TabEnum.Favorites, this, tabButtonHolder, "Data/favoriteservers.xml");
tabs[TabEnum.Recent] = new Tab(TabEnum.Recent, this, tabButtonHolder, "Data/recentservers.xml");
var friendsButtonFrame = new GUIFrame(new RectTransform(new Vector2(0.31f, 2.0f), tabButtonHolder.RectTransform, Anchor.BottomRight), style: "InnerFrame")
{
IgnoreLayoutGroups = true
};
friendsButtonHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.9f), friendsButtonFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopLeft) { RelativeSpacing = 0.01f, IsHorizontal = true };
friendsList = new List<FriendInfo>();
//-------------------------------------------------------------------------------------
// Bottom row
//-------------------------------------------------------------------------------------
@@ -740,7 +694,7 @@ namespace Barotrauma
{
if (selectedServer.TryUnwrap(out var serverInfo))
{
JoinServer(serverInfo.Endpoint, serverInfo.ServerName);
JoinServer(serverInfo.Endpoints, serverInfo.ServerName);
}
return true;
},
@@ -778,7 +732,7 @@ namespace Barotrauma
{
GUIComponent existingElement = serverList.Content.FindChild(d =>
d.UserData is ServerInfo existingServerInfo &&
existingServerInfo.Endpoint == serverInfo.Endpoint);
existingServerInfo.Endpoints.Any(serverInfo.Endpoints.Contains));
if (existingElement == null)
{
AddToServerList(serverInfo);
@@ -791,7 +745,7 @@ namespace Barotrauma
public void AddToRecentServers(ServerInfo info)
{
if (info.Endpoint.Address.IsLocalHost) { return; }
if (info.Endpoints.First().Address.IsLocalHost) { return; }
tabs[TabEnum.Recent].AddOrUpdate(info);
tabs[TabEnum.Recent].Save();
}
@@ -924,7 +878,32 @@ namespace Barotrauma
public override void Select()
{
base.Select();
if (EosInterface.IdQueries.IsLoggedIntoEosConnect)
{
if (SteamManager.IsInitialized)
{
serverProvider = new CompositeServerProvider(
new EosServerProvider(),
new SteamDedicatedServerProvider(),
new SteamP2PServerProvider());
}
else
{
serverProvider = new EosServerProvider();
}
}
else if (SteamManager.IsInitialized)
{
serverProvider = new CompositeServerProvider(
new SteamDedicatedServerProvider(),
new SteamP2PServerProvider());
}
else
{
serverProvider = null;
}
Steamworks.SteamMatchmaking.ResetActions();
selectedTab = TabEnum.All;
@@ -957,6 +936,7 @@ namespace Barotrauma
public override void Deselect()
{
base.Deselect();
serverProvider?.Cancel();
GameSettings.SaveCurrentConfig();
}
@@ -964,21 +944,9 @@ namespace Barotrauma
{
base.Update(deltaTime);
UpdateFriendsList();
panelAnimator?.Update();
scanServersButton.Enabled = (DateTime.Now - lastRefreshTime) >= AllowedRefreshInterval;
if (PlayerInput.PrimaryMouseButtonClicked())
{
friendPopup = null;
if (friendsDropdown != null && friendsDropdownButton != null &&
!friendsDropdown.Rect.Contains(PlayerInput.MousePosition) &&
!friendsDropdownButton.Rect.Contains(PlayerInput.MousePosition))
{
friendsDropdown.Visible = false;
}
}
}
public void FilterServers()
@@ -1113,7 +1081,8 @@ namespace Barotrauma
RelativeSpacing = 0.05f
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), TextManager.Get("ServerEndpoint"), textAlignment: Alignment.Center);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform),
SteamManager.IsInitialized ? TextManager.Get("ServerEndpoint") : TextManager.Get("ServerIP"), textAlignment: Alignment.Center);
var endpointBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform));
content.RectTransform.NonScaledSize = new Point(content.Rect.Width, (int)(content.RectTransform.Children.Sum(c => c.Rect.Height)));
@@ -1126,11 +1095,18 @@ namespace Barotrauma
{
if (Endpoint.Parse(endpointBox.Text).TryUnwrap(out var endpoint))
{
JoinServer(endpoint, "");
if (endpoint is SteamP2PEndpoint && !SteamManager.IsInitialized)
{
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("CannotJoinSteamServer.SteamNotInitialized"));
}
else
{
JoinServer(endpoint.ToEnumerable().ToImmutableArray(), "");
}
}
else if (LidgrenEndpoint.ParseFromWithHostNameCheck(endpointBox.Text, tryParseHostName: true).TryUnwrap(out var lidgrenEndpoint))
{
JoinServer(lidgrenEndpoint, "");
JoinServer(((Endpoint)lidgrenEndpoint).ToEnumerable().ToImmutableArray(), "");
}
else
{
@@ -1171,8 +1147,6 @@ namespace Barotrauma
selectedTab = TabEnum.Favorites;
FilterServers();
#warning Interface with server providers to get up-to-date info on the given server
msgBox.Close();
return false;
};
@@ -1187,222 +1161,6 @@ namespace Barotrauma
};
}
private bool JoinFriend(GUIButton button, object userdata)
{
if (!(userdata is FriendInfo { IsInServer: true } info)) { return false; }
GameMain.Instance.ConnectCommand = info.ConnectCommand;
return false;
}
private bool OpenFriendPopup(GUIButton button, object userdata)
{
if (!(userdata is FriendInfo { IsInServer: true } info)) { return false; }
if (info.IsInServer
&& info.ConnectCommand.TryUnwrap(out var command)
&& command.EndpointOrLobby.TryGet(out ConnectCommand.NameAndEndpoint nameAndEndpoint))
{
const int framePadding = 5;
friendPopup = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas));
var serverNameText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), friendPopup.RectTransform, Anchor.CenterLeft), nameAndEndpoint.ServerName ?? "[Unnamed]");
serverNameText.RectTransform.AbsoluteOffset = new Point(framePadding, 0);
var joinButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), friendPopup.RectTransform, Anchor.CenterRight), TextManager.Get("ServerListJoin"))
{
UserData = info
};
joinButton.OnClicked = JoinFriend;
joinButton.RectTransform.AbsoluteOffset = new Point(framePadding, 0);
Point joinButtonTextSize = joinButton.Font.MeasureString(joinButton.Text).ToPoint();
int joinButtonHeight = joinButton.RectTransform.NonScaledSize.Y;
int totalAdditionalTextPadding = (joinButtonHeight - joinButtonTextSize.Y);
// Make the final button sized so that the space between the text and the edges in the X direction is the same as the Y direction.
Point finalButtonSize = new Point(joinButtonTextSize.X + totalAdditionalTextPadding, joinButtonHeight);
// Add padding to the server name to match the padding on the button text.
serverNameText.Padding = new Vector4(totalAdditionalTextPadding / 2);
// Get the dimensions of the text we want to show, plus the extra padding we added.
Point serverNameSize = serverNameText.Font.MeasureString(serverNameText.Text).ToPoint() + new Point(totalAdditionalTextPadding, totalAdditionalTextPadding);
// Now determine how large the parent frame has to be to exactly fit our two controls.
Point frameDims = new Point(serverNameSize.X + finalButtonSize.X + framePadding*2, Math.Max(serverNameSize.Y, finalButtonSize.Y) + framePadding * 2);
var popupPos = PlayerInput.MousePosition.ToPoint();
if(popupPos.X+frameDims.X > GUI.Canvas.NonScaledSize.X)
{
// Prevent the Join button from going off the end of the screen if the server name is long or we click a user towards the edge.
popupPos.X = GUI.Canvas.NonScaledSize.X - frameDims.X;
}
// Apply the size and position changes.
friendPopup.RectTransform.NonScaledSize = frameDims;
friendPopup.RectTransform.RelativeOffset = Vector2.Zero;
friendPopup.RectTransform.AbsoluteOffset = popupPos;
joinButton.RectTransform.NonScaledSize = finalButtonSize;
friendPopup.RectTransform.RecalculateChildren(true);
friendPopup.RectTransform.SetPosition(Anchor.TopLeft);
}
return false;
}
public enum AvatarSize
{
Small,
Medium,
Large
}
private void UpdateFriendsList()
{
if (friendsListUpdateTime > Timing.TotalTime) { return; }
friendsListUpdateTime = Timing.TotalTime + 5.0;
float prevDropdownScroll = friendsDropdown?.ScrollBar.BarScrollValue ?? 0.0f;
friendsDropdown ??= new GUIListBox(new RectTransform(Vector2.One, GUI.Canvas))
{
OutlineColor = Color.Black,
Visible = false
};
friendsDropdown.ClearChildren();
var avatarSize = friendsButtonHolder.RectTransform.Rect.Height switch
{
var h when h <= 24 => AvatarSize.Small,
var h when h <= 48 => AvatarSize.Medium,
_ => AvatarSize.Large
};
FriendInfo[] friends = friendProvider.RetrieveFriends();
foreach (var friend in friends)
{
int existingIndex = friendsList.FindIndex(f => f.Id == friend.Id);
if (existingIndex >= 0)
{
friend.Avatar = friend.Avatar.Fallback(friendsList[existingIndex].Avatar);
}
if (friend.Avatar.IsNone())
{
friendProvider.RetrieveAvatar(friend, avatarSize);
}
}
friendsList.Clear(); friendsList.AddRange(friends.OrderByDescending(f => f.CurrentStatus));
friendsButtonHolder.ClearChildren();
if (friendsList.Count > 0)
{
friendsDropdownButton = new GUIButton(new RectTransform(Vector2.One, friendsButtonHolder.RectTransform, Anchor.BottomRight, Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight), "\u2022 \u2022 \u2022", style: "GUIButtonFriendsDropdown")
{
OnClicked = (button, udt) =>
{
friendsDropdown.RectTransform.NonScaledSize = new Point(friendsButtonHolder.Rect.Height * 5 * 166 / 100, friendsButtonHolder.Rect.Height * 4 * 166 / 100);
friendsDropdown.RectTransform.AbsoluteOffset = new Point(friendsButtonHolder.Rect.X, friendsButtonHolder.Rect.Bottom);
friendsDropdown.RectTransform.RecalculateChildren(true);
friendsDropdown.Visible = !friendsDropdown.Visible;
return false;
}
};
}
else
{
friendsDropdownButton = null;
friendsDropdown.Visible = false;
}
for (int i = 0; i < friendsList.Count; i++)
{
var friend = friendsList[i];
if (i < 5)
{
string style = friend.IsPlayingBarotrauma
? "GUIButtonFriendPlaying"
: "GUIButtonFriendNotPlaying";
var guiButton = new GUIButton(new RectTransform(Vector2.One, friendsButtonHolder.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: style)
{
UserData = friend,
OnClicked = OpenFriendPopup
};
guiButton.ToolTip = friend.Name + "\n" + friend.StatusText;
if (friend.Avatar.TryUnwrap(out Sprite sprite))
{
new GUICustomComponent(new RectTransform(Vector2.One, guiButton.RectTransform, Anchor.Center),
onDraw: (sb, component) =>
{
var destinationRect = component.Rect;
destinationRect.Inflate(-GUI.IntScale(4), -GUI.IntScale(4));
sb.Draw(sprite.Texture, destinationRect, Color.White);
if (!GUI.IsMouseOn(guiButton))
{
return;
}
sb.End();
sb.Begin(
SpriteSortMode.Deferred,
blendState: BlendState.Additive,
samplerState: GUI.SamplerState,
rasterizerState: GameMain.ScissorTestEnable);
sb.Draw(sprite.Texture, destinationRect, Color.White * 0.5f);
sb.End();
sb.Begin(
SpriteSortMode.Deferred,
samplerState: GUI.SamplerState,
rasterizerState: GameMain.ScissorTestEnable);
}) { CanBeFocused = false };
}
}
var friendFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.167f), friendsDropdown.Content.RectTransform), style: "GUIFrameFriendsDropdown");
if (friend.Avatar.TryUnwrap(out var avatar))
{
GUIImage guiImage =
new GUIImage(
new RectTransform(Vector2.One * 0.9f, friendFrame.RectTransform, Anchor.CenterLeft,
scaleBasis: ScaleBasis.BothHeight) { RelativeOffset = new Vector2(0.02f, 0.02f) },
avatar, null, true);
}
var textBlock = new GUITextBlock(new RectTransform(Vector2.One * 0.8f, friendFrame.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight) { RelativeOffset = new Vector2(1.0f / 7.7f, 0.0f) }, friend.Name + "\n" + friend.StatusText)
{
Font = GUIStyle.SmallFont
};
if (friend.IsPlayingBarotrauma) { textBlock.TextColor = GUIStyle.Green; }
if (friend.PlayingAnotherGame) { textBlock.TextColor = GUIStyle.Blue; }
if (friend.IsInServer)
{
var joinButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.6f), friendFrame.RectTransform, Anchor.CenterRight) { RelativeOffset = new Vector2(0.05f, 0.0f) }, TextManager.Get("ServerListJoin"), style: "GUIButtonJoinFriend")
{
UserData = friend,
OnClicked = JoinFriend
};
}
}
friendsDropdown.RectTransform.NonScaledSize = new Point(friendsButtonHolder.Rect.Height * 5 * 166 / 100, friendsButtonHolder.Rect.Height * 4 * 166 / 100);
friendsDropdown.RectTransform.AbsoluteOffset = new Point(friendsButtonHolder.Rect.X, friendsButtonHolder.Rect.Bottom);
friendsDropdown.RectTransform.RecalculateChildren(true);
friendsDropdown.ScrollBar.BarScrollValue = prevDropdownScroll;
}
private void RemoveMsgFromServerList()
{
serverList.Content.Children
@@ -1429,7 +1187,7 @@ namespace Barotrauma
private void RefreshServers()
{
lastRefreshTime = DateTime.Now;
serverProvider.Cancel();
serverProvider?.Cancel();
currentServerDataRecvCallbackObj = null;
PingUtils.QueryPingData();
@@ -1462,7 +1220,7 @@ namespace Barotrauma
}
var (onServerDataReceived, onQueryCompleted) = MakeServerQueryCallbacks();
serverProvider.RetrieveServers(onServerDataReceived, onQueryCompleted);
serverProvider?.RetrieveServers(onServerDataReceived, onQueryCompleted);
}
private GUIComponent FindFrameMatchingServerInfo(ServerInfo serverInfo)
@@ -1474,7 +1232,7 @@ namespace Barotrauma
#if DEBUG
if (serverList.Content.Children.Count(matches) > 1)
{
DebugConsole.ThrowError($"There are several entries in the server list for endpoint {serverInfo.Endpoint}");
DebugConsole.ThrowError($"There are several entries in the server list for endpoints {string.Join(", ", serverInfo.Endpoints)}");
}
#endif
@@ -1482,7 +1240,7 @@ namespace Barotrauma
}
private object currentServerDataRecvCallbackObj = null;
private (Action<ServerInfo> OnServerDataReceived, Action OnQueryCompleted) MakeServerQueryCallbacks()
private (Action<ServerInfo, ServerProvider> OnServerDataReceived, Action OnQueryCompleted) MakeServerQueryCallbacks()
{
var uniqueObject = new object();
currentServerDataRecvCallbackObj = uniqueObject;
@@ -1497,10 +1255,21 @@ namespace Barotrauma
}
return (
serverInfo =>
(serverInfo, serverProvider) =>
{
if (!shouldRunCallback()) { return; }
if (serverProvider is not EosServerProvider
&& EosInterface.IdQueries.IsLoggedIntoEosConnect)
{
if (serverInfo.EosCrossplay)
{
// EosServerProvider should get us this server,
// don't add it again
return;
}
}
if (selectedTab == TabEnum.All)
{
AddToServerList(serverInfo);
@@ -1724,7 +1493,7 @@ namespace Barotrauma
private static void ReportServer(ServerInfo info, IEnumerable<ReportReason> reasons)
{
if (!reasons.Any()) { return; }
GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Info, $"[Spam] Reported server: Name: \"{info.ServerName}\", Message: \"{info.ServerMessage}\", Endpoint: \"{info.Endpoint.StringRepresentation}\". Reason: \"{string.Join(", ", reasons)}\".");
GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Info, $"[Spam] Reported server: Name: \"{info.ServerName}\", Message: \"{info.ServerMessage}\", Endpoint: \"{info.Endpoints.First().StringRepresentation}\". Reason: \"{string.Join(", ", reasons)}\".");
}
private void UpdateServerInfoUI(ServerInfo serverInfo)
@@ -1784,7 +1553,7 @@ namespace Barotrauma
var serverName = new GUITextBlock(columnRT(ColumnLabel.ServerListName),
#if DEBUG
$"[{serverInfo.Endpoint.GetType().Name}] " +
$"[{serverInfo.Endpoints.First().GetType().Name}] " +
#endif
serverInfo.ServerName,
style: "GUIServerListTextBox") { CanBeFocused = false };
@@ -1819,6 +1588,13 @@ namespace Barotrauma
serverPingText.Text = ping.ToString();
serverPingText.TextColor = GetPingTextColor(ping);
}
else if ((serverInfo.Endpoints.Length == 1 && serverInfo.Endpoints.First() is EosP2PEndpoint)
|| (!SteamManager.IsInitialized && serverInfo.Endpoints.Any(e => e is P2PEndpoint)))
{
serverPingText.Text = "-";
serverPingText.ToolTip = TextManager.Get("EosPingUnavailable");
serverPingText.TextAlignment = Alignment.Center;
}
else
{
serverPingText.Text = "?";
@@ -1954,7 +1730,7 @@ namespace Barotrauma
}
}
public void JoinServer(Endpoint endpoint, string serverName)
public void JoinServer(ImmutableArray<Endpoint> endpoints, string serverName)
{
if (string.IsNullOrWhiteSpace(ClientNameBox.Text))
{
@@ -1967,20 +1743,38 @@ namespace Barotrauma
MultiplayerPreferences.Instance.PlayerName = ClientNameBox.Text;
GameSettings.SaveCurrentConfig();
#if !DEBUG
try
if (MultiplayerPreferences.Instance.PlayerName.IsNullOrEmpty())
{
#endif
GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(GetDefaultUserName()), endpoint, serverName, Option<int>.None());
#if !DEBUG
TaskPool.Add("GetDefaultUserName",
GetDefaultUserName(),
t =>
{
if (!t.TryGetResult(out string name)) { return; }
startClient(name);
});
}
catch (Exception e)
else
{
DebugConsole.ThrowError("Failed to start the client", e);
startClient(MultiplayerPreferences.Instance.PlayerName);
}
void startClient(string name)
{
#if !DEBUG
try
{
#endif
GameMain.Client = new GameClient(name, endpoints, serverName, Option.None);
#if !DEBUG
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to start the client", e);
}
#endif
}
}
private static Color GetPingTextColor(int ping)
{
if (ping < 0) { return Color.DarkRed; }
@@ -2000,8 +1794,6 @@ namespace Barotrauma
public override void AddToGUIUpdateList()
{
menu.AddToGUIUpdateList();
friendPopup?.AddToGUIUpdateList();
friendsDropdown?.AddToGUIUpdateList();
}
public void StoreServerFilters()

View File

@@ -1557,7 +1557,6 @@ namespace Barotrauma
autoSaveLabel?.Parent?.RemoveChild(autoSaveLabel);
autoSaveLabel = null;
#if USE_STEAM
if (editorSelectedTime.TryUnwrap(out DateTime selectedTime))
{
TimeSpan timeInEditor = DateTime.Now - selectedTime;
@@ -1571,11 +1570,10 @@ namespace Barotrauma
}
else
{
SteamAchievementManager.IncrementStat("hoursineditor".ToIdentifier(), (float)timeInEditor.TotalHours);
AchievementManager.IncrementStat(AchievementStat.HoursInEditor, (float)timeInEditor.TotalHours);
editorSelectedTime = Option<DateTime>.None();
}
}
#endif
GUI.ForceMouseOn(null);
@@ -4176,7 +4174,7 @@ namespace Barotrauma
Rectangle newColorRect = new Rectangle(rect.Location, areaSize);
Rectangle oldColorRect = new Rectangle(new Point(newColorRect.Left, newColorRect.Bottom), areaSize);
GUI.DrawRectangle(batch, newColorRect, ToolBox.HSVToRGB(colorPicker.SelectedHue, colorPicker.SelectedSaturation, colorPicker.SelectedValue), isFilled: true);
GUI.DrawRectangle(batch, newColorRect, ToolBoxCore.HSVToRGB(colorPicker.SelectedHue, colorPicker.SelectedSaturation, colorPicker.SelectedValue), isFilled: true);
GUI.DrawRectangle(batch, oldColorRect, originalColor, isFilled: true);
GUI.DrawRectangle(batch, rect, Color.Black, isFilled: false);
});
@@ -4296,7 +4294,7 @@ namespace Barotrauma
setValues = true;
}
Color color = ToolBox.HSVToRGB(colorPicker.SelectedHue, colorPicker.SelectedSaturation, colorPicker.SelectedValue);
Color color = ToolBoxCore.HSVToRGB(colorPicker.SelectedHue, colorPicker.SelectedSaturation, colorPicker.SelectedValue);
foreach (var (e, origColor, prop) in entities)
{
if (e is MapEntity { Removed: true }) { continue; }
@@ -4330,7 +4328,7 @@ namespace Barotrauma
void SetHex(Vector3 hsv)
{
Color hexColor = ToolBox.HSVToRGB(hsv.X, hsv.Y, hsv.Z);
Color hexColor = ToolBoxCore.HSVToRGB(hsv.X, hsv.Y, hsv.Z);
hexValueBox!.Text = ColorToHex(hexColor);
}
}

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using Barotrauma.Eos;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Barotrauma.Steam;
@@ -14,7 +15,7 @@ using OpenAL;
namespace Barotrauma
{
class SettingsMenu
sealed class SettingsMenu
{
public static SettingsMenu? Instance { get; private set; }
@@ -682,22 +683,22 @@ namespace Barotrauma
private void CreateGameplayTab()
{
GUIFrame content = CreateNewContentFrame(Tab.Gameplay);
GUILayoutGroup layout = CreateCenterLayout(content);
var (left, right) = CreateSidebars(content);
var languages = TextManager.AvailableLanguages
.OrderBy(l => TextManager.GetTranslatedLanguageName(l).ToIdentifier())
.ToArray();
Label(layout, TextManager.Get("Language"), GUIStyle.SubHeadingFont);
Dropdown(layout, v => TextManager.GetTranslatedLanguageName(v), null, languages, unsavedConfig.Language, v => unsavedConfig.Language = v);
Spacer(layout);
Tickbox(layout, TextManager.Get("PauseOnFocusLost"), TextManager.Get("PauseOnFocusLostTooltip"), unsavedConfig.PauseOnFocusLost, v => unsavedConfig.PauseOnFocusLost = v);
Spacer(layout);
Tickbox(layout, TextManager.Get("DisableInGameHints"), TextManager.Get("DisableInGameHintsTooltip"), unsavedConfig.DisableInGameHints, v => unsavedConfig.DisableInGameHints = v);
Label(left, TextManager.Get("Language"), GUIStyle.SubHeadingFont);
Dropdown(left, v => TextManager.GetTranslatedLanguageName(v), null, languages, unsavedConfig.Language, v => unsavedConfig.Language = v);
Spacer(left);
Tickbox(left, TextManager.Get("PauseOnFocusLost"), TextManager.Get("PauseOnFocusLostTooltip"), unsavedConfig.PauseOnFocusLost, v => unsavedConfig.PauseOnFocusLost = v);
Spacer(left);
Tickbox(left, TextManager.Get("DisableInGameHints"), TextManager.Get("DisableInGameHintsTooltip"), unsavedConfig.DisableInGameHints, v => unsavedConfig.DisableInGameHints = v);
var resetInGameHintsButton =
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), layout.RectTransform),
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), left.RectTransform),
TextManager.Get("ResetInGameHints"), style: "GUIButtonSmall")
{
OnClicked = (button, o) =>
@@ -715,36 +716,22 @@ namespace Barotrauma
return false;
}
};
Spacer(layout);
Spacer(left);
Label(layout, TextManager.Get("ShowEnemyHealthBars"), GUIStyle.SubHeadingFont);
DropdownEnum(layout, v => TextManager.Get($"ShowEnemyHealthBars.{v}"), null, unsavedConfig.ShowEnemyHealthBars, v => unsavedConfig.ShowEnemyHealthBars = v);
Spacer(layout);
Label(left, TextManager.Get("ShowEnemyHealthBars"), GUIStyle.SubHeadingFont);
DropdownEnum(left, v => TextManager.Get($"ShowEnemyHealthBars.{v}"), null, unsavedConfig.ShowEnemyHealthBars, v => unsavedConfig.ShowEnemyHealthBars = v);
Spacer(left);
Label(left, TextManager.Get("HUDScale"), GUIStyle.SubHeadingFont);
Slider(left, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.HUDScale, v => unsavedConfig.Graphics.HUDScale = v);
Label(left, TextManager.Get("InventoryScale"), GUIStyle.SubHeadingFont);
Slider(left, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.InventoryScale, v => unsavedConfig.Graphics.InventoryScale = v);
Label(left, TextManager.Get("TextScale"), GUIStyle.SubHeadingFont);
Slider(left, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.TextScale, v => unsavedConfig.Graphics.TextScale = v);
Label(layout, TextManager.Get("HUDScale"), GUIStyle.SubHeadingFont);
Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.HUDScale, v => unsavedConfig.Graphics.HUDScale = v);
Label(layout, TextManager.Get("InventoryScale"), GUIStyle.SubHeadingFont);
Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.InventoryScale, v => unsavedConfig.Graphics.InventoryScale = v);
Label(layout, TextManager.Get("TextScale"), GUIStyle.SubHeadingFont);
Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.TextScale, v => unsavedConfig.Graphics.TextScale = v);
Spacer(layout);
var resetSpamListFilter =
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), layout.RectTransform),
TextManager.Get("clearserverlistfilters"), style: "GUIButtonSmall")
{
OnClicked = static (_, _) =>
{
GUI.AskForConfirmation(
header: TextManager.Get("clearserverlistfilters"),
body: TextManager.Get("clearserverlistfiltersconfirmation"),
onConfirm: SpamServerFilters.ClearLocalSpamFilter);
return true;
}
};
Spacer(layout);
#if !OSX
Spacer(layout);
var statisticsTickBox = new GUITickBox(NewItemRectT(layout), TextManager.Get("statisticsconsenttickbox"))
Spacer(right);
var statisticsTickBox = new GUITickBox(NewItemRectT(right), TextManager.Get("statisticsconsenttickbox"))
{
OnSelected = tickBox =>
{
@@ -767,7 +754,7 @@ namespace Barotrauma
void updateGATickBoxToolTip()
=> statisticsTickBox.ToolTip = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}");
updateGATickBoxToolTip();
var cachedConsent = GameAnalyticsManager.Consent.Unknown;
var statisticsTickBoxUpdater = new GUICustomComponent(
new RectTransform(Vector2.Zero, statisticsTickBox.RectTransform),
@@ -789,6 +776,37 @@ namespace Barotrauma
statisticsTickBox.Enabled &= GameAnalyticsManager.UserConsented != GameAnalyticsManager.Consent.Error;
});
#endif
//Steam version supports hosting/joining servers using EOS networking
if (SteamManager.IsInitialized)
{
bool shouldCrossplayBeEnabled = unsavedConfig.CrossplayChoice is Eos.EosSteamPrimaryLogin.CrossplayChoice.Enabled;
var crossplayTickBox = Tickbox(right, TextManager.Get("EosAllowCrossplay"), TextManager.Get("EosAllowCrossplayTooltip"), shouldCrossplayBeEnabled, v =>
{
unsavedConfig.CrossplayChoice = v
? Eos.EosSteamPrimaryLogin.CrossplayChoice.Enabled
: Eos.EosSteamPrimaryLogin.CrossplayChoice.Disabled;
});
if (GameMain.NetworkMember != null)
{
crossplayTickBox.Enabled = false;
crossplayTickBox.ToolTip = TextManager.Get("CantAccessEOSSettingsInMP");
}
}
Spacer(right);
var resetSpamListFilter =
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), right.RectTransform),
TextManager.Get("clearserverlistfilters"), style: "GUIButtonSmall")
{
OnClicked = static (_, _) =>
{
GUI.AskForConfirmation(
header: TextManager.Get("clearserverlistfilters"),
body: TextManager.Get("clearserverlistfiltersconfirmation"),
onConfirm: SpamServerFilters.ClearLocalSpamFilter);
return true;
}
};
}
private void CreateModsTab(out WorkshopMenu workshopMenu)
@@ -832,9 +850,9 @@ namespace Barotrauma
public void ApplyInstalledModChanges()
{
EosSteamPrimaryLogin.HandleCrossplayChoiceChange(unsavedConfig.CrossplayChoice);
GameSettings.SetCurrentConfig(unsavedConfig);
if (WorkshopMenu is MutableWorkshopMenu mutableWorkshopMenu &&
mutableWorkshopMenu.CurrentTab == MutableWorkshopMenu.Tab.InstalledMods)
if (WorkshopMenu is MutableWorkshopMenu { CurrentTab: MutableWorkshopMenu.Tab.InstalledMods } mutableWorkshopMenu)
{
mutableWorkshopMenu.Apply();
}

View File

@@ -0,0 +1,73 @@
#nullable enable
using System;
using Barotrauma.Networking;
namespace Barotrauma;
sealed class FriendInfo : IDisposable
{
public readonly string Name;
public readonly AccountId Id;
public readonly FriendStatus CurrentStatus;
public readonly string ServerName;
public readonly Option<ConnectCommand> ConnectCommand;
public readonly FriendProvider Provider;
public Option<Sprite> Avatar { get; set; }
public bool IsInServer
=> CurrentStatus == FriendStatus.PlayingBarotrauma && ConnectCommand.IsSome();
public bool IsOnline
=> CurrentStatus != FriendStatus.Offline;
public LocalizedString StatusText
=> CurrentStatus switch
{
FriendStatus.Offline => "",
_ when ConnectCommand.IsSome()
=> TextManager.GetWithVariable("FriendPlayingOnServer", "[servername]", ServerName),
_ => TextManager.Get($"Friend{CurrentStatus}")
};
public FriendInfo(string name, AccountId id, FriendStatus status, string serverName, Option<ConnectCommand> connectCommand, FriendProvider provider)
{
Name = name;
Id = id;
CurrentStatus = status;
ServerName = serverName;
ConnectCommand = connectCommand;
Provider = provider;
Avatar = Option.None;
}
public void RetrieveOrInheritAvatar(Option<Sprite> inheritableAvatar, int size)
{
if (Avatar.IsSome()) { return; }
if (inheritableAvatar.IsSome())
{
Avatar = inheritableAvatar;
return;
}
TaskPool.Add(
"RetrieveAvatar",
Provider.RetrieveAvatar(this, size),
t =>
{
if (!t.TryGetResult(out Option<Sprite> spr)) { return; }
Avatar = Avatar.Fallback(spr);
});
}
public void Dispose()
{
if (Avatar.TryUnwrap(out var avatar))
{
avatar.Remove();
}
Avatar = Option.None;
}
}

View File

@@ -0,0 +1,46 @@
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma;
sealed class CompositeFriendProvider : FriendProvider
{
private readonly ImmutableArray<FriendProvider> providers;
public CompositeFriendProvider(params FriendProvider[] providers)
{
this.providers = providers.ToImmutableArray();
}
public override async Task<Option<FriendInfo>> RetrieveFriend(AccountId id)
{
return (await Task.WhenAll(providers
.Select(p => p.RetrieveFriend(id))))
.NotNone().FirstOrNone();
}
public override async Task<ImmutableArray<FriendInfo>> RetrieveFriends()
{
var friends = await Task.WhenAll(providers.Select(p => p.RetrieveFriends()));
return friends.SelectMany(a => a).ToImmutableArray();
}
public override async Task<Option<Sprite>> RetrieveAvatar(FriendInfo friend, int avatarSize)
{
var subTasks = await Task.WhenAll(providers.Select(p => p.RetrieveAvatar(friend, avatarSize)));
return subTasks.FirstOrDefault(t => t.IsSome());
}
public override async Task<string> GetSelfUserName()
{
foreach (var provider in providers)
{
string userName = await provider.GetSelfUserName();
if (userName is { Length: > 0 }) { return userName; }
}
return "";
}
}

View File

@@ -0,0 +1,173 @@
using System;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Vector2 = Microsoft.Xna.Framework.Vector2;
namespace Barotrauma;
sealed class EpicFriendProvider : FriendProvider
{
private FriendInfo EgsFriendToFriendInfo(EosInterface.EgsFriend egsFriend)
{
return new FriendInfo(
name: egsFriend.DisplayName,
id: egsFriend.EpicAccountId,
status: egsFriend.Status,
serverName: egsFriend.ServerName,
connectCommand: ConnectCommand.Parse(egsFriend.ConnectCommand),
provider: this);
}
public override async Task<Option<FriendInfo>> RetrieveFriend(AccountId id)
{
if (id is not EpicAccountId friendEaid) { return Option.None; }
var selfEaidOption = Eos.EosAccount.SelfAccountIds.OfType<EpicAccountId>().FirstOrNone();
if (!selfEaidOption.TryUnwrap(out var selfEaid)) { return Option.None; }
var friendResult = await EosInterface.Friends.GetFriend(selfEaid, friendEaid);
if (!friendResult.TryUnwrapSuccess(out var f)) { return Option.None; }
return Option.Some(EgsFriendToFriendInfo(f));
}
public override async Task<ImmutableArray<FriendInfo>> RetrieveFriends()
{
var epicAccountIdOption = Eos.EosAccount.SelfAccountIds.OfType<EpicAccountId>().FirstOrNone();
if (!epicAccountIdOption.TryUnwrap(out var epicAccountId)) { return ImmutableArray<FriendInfo>.Empty; }
var friendsResult = await EosInterface.Friends.GetFriends(epicAccountId);
if (!friendsResult.TryUnwrapSuccess(out var friends)) { return ImmutableArray<FriendInfo>.Empty; }
return friends.Select(EgsFriendToFriendInfo).ToImmutableArray();
}
private static readonly ImmutableArray<Color> egsProfileColors = new[]
{
// Cyan
new Color(0xfff0a950),
// Dark green
new Color(0xff2b9850),
// Yellow-green
new Color(0xff2ba08e),
// Purple
new Color(0xff951249),
// Purple-red
new Color(0xff9a0c71),
// Red
new Color(0xff3e29c6),
// Orange
new Color(0xff3875ed),
// Yellow-orange
new Color(0xff1ea5ed)
}.ToImmutableArray();
public override Task<Option<Sprite>> RetrieveAvatar(FriendInfo friend, int avatarSize)
{
if (friend.Id is not EpicAccountId epicAccount) { return Task.FromResult<Option<Sprite>>(Option.None); }
// EGS doesn't have profile pictures yet.
// Instead, each player gets a color based on their account ID.
// This is an educated guess of how Epic picks that color, and is likely incorrect for IDs nearing the boundaries of ranges:
Color color = Color.Black;
if (ulong.TryParse(epicAccount.EosStringRepresentation[..16], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var mostSignificant64Bits)
&& ulong.TryParse(epicAccount.EosStringRepresentation[16..], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var leastSignificant64Bits))
{
BigInteger fullId = mostSignificant64Bits;
fullId <<= 64;
fullId |= leastSignificant64Bits;
BigInteger idMaxValue = ulong.MaxValue;
idMaxValue <<= 64;
idMaxValue |= ulong.MaxValue;
BigInteger middleRangeSize = idMaxValue / 7;
BigInteger firstRangeSize = middleRangeSize / 2;
if (fullId <= firstRangeSize)
{
color = egsProfileColors[0];
}
else
{
color = egsProfileColors[(int)((fullId - firstRangeSize) / middleRangeSize) + 1];
}
}
char glyphChar = friend.Name.FallbackNullOrEmpty("?")[0];
var font = GUIStyle.UnscaledSmallFont.GetFontForStr(glyphChar.ToString());
Texture2D tex = null;
if (font != null)
{
var (glyphData, glyphTexture) = font.GetGlyphDataAndTextureForChar(glyphChar);
var glyphSize = new Vector2(glyphData.TexCoords.Width, glyphData.TexCoords.Height);
int texSize = (int)Math.Max(
MathUtils.RoundUpToPowerOfTwo((uint)(font.LineHeight * 1.5f)),
MathUtils.RoundUpToPowerOfTwo((uint)(font.LineHeight * 1.5f)));
if (glyphTexture is not null)
{
var glyphTextureData = new Color[(int)glyphSize.X * (int)glyphSize.Y];
glyphTexture.GetData(
level: 0,
rect: glyphData.TexCoords,
data: glyphTextureData,
startIndex: 0,
elementCount: glyphTextureData.Length);
var texData = Enumerable.Range(0, texSize * texSize).Select(_ => color).ToArray();
var start = (new Vector2(texSize, texSize) / 2 - glyphSize / 2).ToPoint();
var end = start + glyphSize.ToPoint();
for (int x = start.X; x < end.X; x++)
{
for (int y = start.Y; y < end.Y; y++)
{
texData[x + y * texSize] =
Color.Lerp(
color,
Color.White,
glyphTextureData[(x - start.X) + (y - start.Y) * (int)glyphSize.X].A / 255f);
}
}
tex = new Texture2D(GameMain.GraphicsDeviceManager.GraphicsDevice, texSize, texSize);
tex.SetData(texData);
}
}
if (tex is null)
{
tex = new Texture2D(GameMain.GraphicsDeviceManager.GraphicsDevice, 2, 2);
tex.SetData(new[] { color, color, color, color });
}
var sprite = new Sprite(tex, null, null);
return Task.FromResult(Option.Some(sprite));
}
public override async Task<string> GetSelfUserName()
{
var epicAccountIdOption = Eos.EosAccount.SelfAccountIds.OfType<EpicAccountId>().FirstOrNone();
if (!epicAccountIdOption.TryUnwrap(out var epicAccountId)) { return ""; }
var selfInfoResult = await EosInterface.Friends.GetSelfUserInfo(epicAccountId);
if (!selfInfoResult.TryUnwrapSuccess(out var selfInfo)) { return ""; }
return selfInfo.DisplayName;
}
}

View File

@@ -0,0 +1,25 @@
#nullable enable
using System.Collections.Immutable;
using System.Threading.Tasks;
using Barotrauma.Networking;
namespace Barotrauma
{
abstract class FriendProvider
{
public async Task<Option<FriendInfo>> RetrieveFriendWithAvatar(AccountId id, int size)
{
var friendOption = await RetrieveFriend(id);
if (!friendOption.TryUnwrap(out var friend)) { return Option.None; }
friend.Avatar = await RetrieveAvatar(friend, size);
return Option.Some(friend);
}
public abstract Task<Option<FriendInfo>> RetrieveFriend(AccountId id);
public abstract Task<ImmutableArray<FriendInfo>> RetrieveFriends();
public abstract Task<Option<Sprite>> RetrieveAvatar(FriendInfo friend, int avatarSize);
public abstract Task<string> GetSelfUserName();
}
}

View File

@@ -0,0 +1,69 @@
#nullable enable
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Barotrauma.Networking;
using Barotrauma.Steam;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
sealed class SteamFriendProvider : FriendProvider
{
private FriendInfo FromSteamFriend(Steamworks.Friend steamFriend)
=> new FriendInfo(
name: steamFriend.Name ?? "",
id: new SteamId(steamFriend.Id),
status: steamFriend.State switch
{
Steamworks.FriendState.Offline => FriendStatus.Offline,
Steamworks.FriendState.Invisible => FriendStatus.Offline,
_ when steamFriend.IsPlayingThisGame => FriendStatus.PlayingBarotrauma,
_ when steamFriend.GameInfo is { GameID: > 0 } => FriendStatus.PlayingAnotherGame,
_ => FriendStatus.NotPlaying
},
serverName: steamFriend.GetRichPresence("servername") ?? "",
connectCommand: steamFriend.GetRichPresence("connect") is { } connectCmd
? ConnectCommand.Parse(ToolBox.SplitCommand(connectCmd))
: Option.None,
this);
public override Task<Option<FriendInfo>> RetrieveFriend(AccountId id)
=> Task.FromResult(id is SteamId steamId
? Option.Some(FromSteamFriend(new Steamworks.Friend(steamId.Value)))
: Option.None);
public override Task<ImmutableArray<FriendInfo>> RetrieveFriends()
=> Task.FromResult(SteamManager.IsInitialized
? Steamworks.SteamFriends.GetFriends().Select(FromSteamFriend).ToImmutableArray()
: ImmutableArray<FriendInfo>.Empty);
public override async Task<Option<Sprite>> RetrieveAvatar(FriendInfo friend, int avatarSize)
{
if (friend.Id is not SteamId steamId) { return Option.None; }
Func<Steamworks.SteamId, Task<Steamworks.Data.Image?>> avatarFunc = avatarSize switch
{
<= 24 => Steamworks.SteamFriends.GetSmallAvatarAsync,
<= 48 => Steamworks.SteamFriends.GetMediumAvatarAsync,
_ => Steamworks.SteamFriends.GetLargeAvatarAsync
};
var img = await avatarFunc(steamId.Value).ToOptionTask();
if (!img.TryUnwrap(out var avatarImage)) { return Option.None; }
if (friend.Avatar.TryUnwrap(out var prevAvatar))
{
prevAvatar.Remove();
}
var avatarTexture = new Texture2D(GameMain.Instance.GraphicsDevice, (int)avatarImage.Width, (int)avatarImage.Height);
avatarTexture.SetData(avatarImage.Data);
return Option.Some(new Sprite(texture: avatarTexture, sourceRectangle: null, newOffset: null));
}
public override Task<string> GetSelfUserName()
=> Task.FromResult(SteamManager.GetUsername());
}
}

View File

@@ -0,0 +1,34 @@
using Barotrauma.Networking;
using Barotrauma.Steam;
namespace Barotrauma;
static class SocialExtensions
{
public static LocalizedString ViewProfileLabel(this AccountId accountId)
=> accountId switch
{
SteamId => TextManager.Get("ViewSteamProfile"),
EpicAccountId => TextManager.Get("ViewEpicProfile"),
_ => "View profile of unknown origin"
};
public static void OpenProfile(this AccountId accountId)
{
string url = accountId switch
{
SteamId steamId => $"https://steamcommunity.com/profiles/{steamId.Value}",
EpicAccountId epicAccountId => $"https://store.epicgames.com/u/{epicAccountId.EosStringRepresentation}",
_ => ""
};
if (SteamManager.IsInitialized)
{
SteamManager.OverlayCustomUrl(url);
}
else
{
GameMain.ShowOpenUriPrompt(url);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -61,7 +61,7 @@ namespace Barotrauma
SpamServerFilterType.MessageEquals => CompareEquals(desc, value),
SpamServerFilterType.MessageContains => CompareContains(desc, value),
SpamServerFilterType.Endpoint => info.Endpoint.StringRepresentation.Equals(value, StringComparison.OrdinalIgnoreCase),
SpamServerFilterType.Endpoint => info.Endpoints.First().StringRepresentation.Equals(value, StringComparison.OrdinalIgnoreCase),
SpamServerFilterType.PlayerCountLarger => info.PlayerCount > parsedInt,
SpamServerFilterType.PlayerCountExact => info.PlayerCount == parsedInt,
@@ -299,7 +299,7 @@ These will hide all servers that have a discord.gg link in their name or descrip
{
try
{
if (!t.TryGetResult(out IRestResponse remoteContentResponse)) { throw new Exception("Task did not return a valid result"); }
if (!t.TryGetResult(out IRestResponse? remoteContentResponse)) { throw new Exception("Task did not return a valid result"); }
if (remoteContentResponse.StatusCode != HttpStatusCode.OK)
{
DebugConsole.AddWarning(

View File

@@ -1,27 +0,0 @@
namespace Barotrauma.Steam
{
static partial class SteamManager
{
public static Steamworks.BeginAuthResult StartAuthSession(byte[] authTicketData, ulong clientSteamID)
{
if (!IsInitialized || !Steamworks.SteamClient.IsValid) return Steamworks.BeginAuthResult.ServerNotConnectedToSteam;
DebugConsole.Log("SteamManager authenticating Steam client " + clientSteamID);
Steamworks.BeginAuthResult startResult = Steamworks.SteamUser.BeginAuthSession(authTicketData, clientSteamID);
if (startResult != Steamworks.BeginAuthResult.OK)
{
DebugConsole.Log("Authentication failed: failed to start auth session (" + startResult.ToString() + ")");
}
return startResult;
}
public static void StopAuthSession(ulong clientSteamID)
{
if (!IsInitialized || !Steamworks.SteamClient.IsValid) return;
DebugConsole.NewMessage("SteamManager ending auth session with Steam client " + clientSteamID);
Steamworks.SteamUser.EndAuthSession(clientSteamID);
}
}
}

View File

@@ -31,8 +31,8 @@ namespace Barotrauma.Steam
t =>
{
msgBox.Close();
if (!t.TryGetResult(out IReadOnlyList<Steamworks.Ugc.Item> items)) { return; }
if (!t.TryGetResult(out IReadOnlyList<Steamworks.Ugc.Item>? items)) { return; }
InitiateDownloads(items);
});
}
@@ -48,7 +48,7 @@ namespace Barotrauma.Steam
t =>
{
msgBox.Close();
if (!t.TryGetResult(out Steamworks.Ugc.Item?[] itemsNullable)) { return; }
if (!t.TryGetResult(out Steamworks.Ugc.Item?[]? itemsNullable)) { return; }
var items = itemsNullable
.Where(it => it.HasValue)
@@ -74,7 +74,7 @@ namespace Barotrauma.Steam
.NotNone()
.OfType<SteamWorkshopId>()
.Select(async id => await SteamManager.Workshop.GetItem(id.Value))))
.Where(p => p.HasValue).Select(p => p ?? default).ToArray();
.NotNone().ToArray();
}
public static void InitiateDownloads(IReadOnlyList<Steamworks.Ugc.Item> itemsToDownload, Action? onComplete = null)

View File

@@ -1,6 +1,6 @@
using Barotrauma.Networking;
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -26,6 +26,7 @@ namespace Barotrauma.Steam
public static void CreateLobby(ServerSettings serverSettings)
{
if (!SteamManager.IsInitialized) { return; }
if (lobbyState != LobbyState.NotConnected) { return; }
lobbyState = LobbyState.Creating;
TaskPool.Add("CreateLobbyAsync", Steamworks.SteamMatchmaking.CreateLobbyAsync(serverSettings.MaxPlayers + 10),
@@ -88,45 +89,35 @@ namespace Barotrauma.Steam
return;
}
var contentPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent);
serverSettings.UpdateServerListInfo(SetServerListInfo);
currentLobby?.SetData("name", serverSettings.ServerName);
currentLobby?.SetData("playercount", (GameMain.Client?.ConnectedClients?.Count ?? 0).ToString());
currentLobby?.SetData("maxplayernum", serverSettings.MaxPlayers.ToString());
//currentLobby?.SetData("hostipaddress", lobbyIP);
string pingLocation = Steamworks.SteamNetworkingUtils.LocalPingLocation?.ToString();
currentLobby?.SetData("pinglocation", pingLocation ?? "");
currentLobby?.SetData("lobbyowner", GetSteamId().TryUnwrap(out var steamId)
? steamId.StringRepresentation
: throw new InvalidOperationException("Steamworks not initialized"));
currentLobby?.SetData("haspassword", serverSettings.HasPassword.ToString());
currentLobby?.SetData("message", serverSettings.ServerMessageText);
currentLobby?.SetData("version", GameMain.Version.ToString());
currentLobby?.SetData("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name)));
currentLobby?.SetData("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.Hash.StringRepresentation)));
currentLobby?.SetData("contentpackageid", string.Join(",", contentPackages.Select(cp
=> cp.UgcId.TryUnwrap(out var ugcId) ? ugcId.StringRepresentation : "")));
currentLobby?.SetData("modeselectionmode", serverSettings.ModeSelectionMode.ToString());
currentLobby?.SetData("subselectionmode", serverSettings.SubSelectionMode.ToString());
currentLobby?.SetData("voicechatenabled", serverSettings.VoiceChatEnabled.ToString());
currentLobby?.SetData("allowspectating", serverSettings.AllowSpectating.ToString());
currentLobby?.SetData("allowrespawn", serverSettings.AllowRespawn.ToString());
currentLobby?.SetData("karmaenabled", serverSettings.KarmaEnabled.ToString());
currentLobby?.SetData("friendlyfireenabled", serverSettings.AllowFriendlyFire.ToString());
currentLobby?.SetData("traitors", serverSettings.TraitorProbability.ToString(CultureInfo.InvariantCulture));
currentLobby?.SetData("gamestarted", GameMain.Client.GameStarted.ToString());
currentLobby?.SetData("playstyle", serverSettings.PlayStyle.ToString());
currentLobby?.SetData("gamemode", GameMain.NetLobbyScreen?.SelectedMode?.Identifier.Value ?? "");
currentLobby?.SetData("language", serverSettings.Language.ToString());
if (GameMain.NetLobbyScreen?.SelectedSub != null)
if (EosInterface.IdQueries.GetLoggedInPuids() is { Length: > 0 } puids)
{
currentLobby?.SetData("submarine", GameMain.NetLobbyScreen.SelectedSub.Name);
currentLobby?.SetData("EosEndpoint", puids[0].Value);
}
DebugConsole.Log("Lobby updated!");
}
private static void SetServerListInfo(Identifier key, object value)
{
switch (value)
{
case IEnumerable<ContentPackage> contentPackages:
currentLobby?.SetData("contentpackage", contentPackages.Select(p => p.Name).JoinEscaped(','));
currentLobby?.SetData("contentpackagehash", contentPackages.Select(p => p.Hash.StringRepresentation).JoinEscaped(','));
currentLobby?.SetData("contentpackageid", contentPackages
.Select(p => p.UgcId.Select(ugcId => ugcId.StringRepresentation).Fallback(""))
.JoinEscaped(','));
return;
}
currentLobby?.SetData(key.Value.ToLowerInvariant(), value.ToString());
}
public static void LeaveLobby()
{

View File

@@ -42,6 +42,9 @@ namespace Barotrauma.Steam
}
Steamworks.SteamNetworkingUtils.OnDebugOutput += LogSteamworksNetworking;
// Needed to detect invites for social overlay
Steamworks.SteamFriends.ListenForFriendsMessages = true;
}
catch (DllNotFoundException)
{
@@ -145,10 +148,5 @@ namespace Barotrauma.Steam
Steamworks.SteamFriends.OpenWebOverlay(url);
return true;
}
public static void OverlayProfile(SteamId steamId)
{
OverlayCustomUrl($"https://steamcommunity.com/profiles/{steamId.Value}");
}
}
}

View File

@@ -195,7 +195,7 @@ namespace Barotrauma.Steam
modProject.Save(stagingFileListPath);
}
public static async Task<ContentPackage?> CreateLocalCopy(ContentPackage contentPackage)
public static async Task<Option<ContentPackage>> CreateLocalCopy(ContentPackage contentPackage)
{
await Task.Yield();
@@ -234,7 +234,7 @@ namespace Barotrauma.Steam
RefreshLocalMods();
return ContentPackageManager.LocalPackages.FirstOrDefault(p => p.UgcId == contentPackage.UgcId);
return ContentPackageManager.LocalPackages.FirstOrNone(p => p.UgcId == contentPackage.UgcId);
}
private struct InstallWaiter

View File

@@ -196,9 +196,9 @@ namespace Barotrauma.Steam
SteamManager.Workshop.GetItemAsap(workshopItem.Id.Value, withLongDescription: true),
t =>
{
if (!t.TryGetResult(out Steamworks.Ugc.Item? workshopItemWithDescription)) { return; }
if (!t.TryGetResult(out Option<Steamworks.Ugc.Item> workshopItemWithDescription)) { return; }
bbCode = workshopItemWithDescription?.Description ?? "";
bbCode = workshopItemWithDescription.TryUnwrap(out var item) ? (item.Description ?? "") : "";
forceReset();
});

View File

@@ -21,14 +21,14 @@ namespace Barotrauma.Steam
private readonly Action<ItemOrPackage> onInstalledInfoButtonHit;
private readonly GUITextBox modsListFilter;
private readonly Dictionary<Filter, GUITickBox> modsListFilterTickboxes;
private readonly GUIButton bulkUpdateButton;
private readonly Option<GUIButton> bulkUpdateButtonOption;
private GUIComponent? draggedElement = null;
private GUIListBox? draggedElementOrigin = null;
private void UpdateSubscribedModInstalls()
{
if (!SteamManager.IsInitialized) { return; }
if (!EnableWorkshopSupport) { return; }
uint numSubscribedMods = SteamManager.GetNumSubscribedItems();
if (numSubscribedMods == memSubscribedModCount) { return; }
@@ -171,7 +171,7 @@ namespace Barotrauma.Steam
out Action<ItemOrPackage> onInstalledInfoButtonHit,
out GUITextBox modsListFilter,
out Dictionary<Filter, GUITickBox> modsListFilterTickboxes,
out GUIButton bulkUpdateButton)
out Option<GUIButton> bulkUpdateButton)
{
GUIFrame content = CreateNewContentFrame(Tab.InstalledMods);
@@ -233,18 +233,22 @@ namespace Barotrauma.Steam
},
ToolTip = TextManager.Get("RefreshModLists")
};
bulkUpdateButton
= new GUIButton(
new RectTransform(Vector2.One, topRightButtons.RectTransform, scaleBasis: ScaleBasis.BothHeight),
text: "", style: "GUIUpdateButton")
{
OnClicked = (b, o) =>
bulkUpdateButton = EnableWorkshopSupport
? Option.Some(
new GUIButton(
new RectTransform(Vector2.One, topRightButtons.RectTransform, scaleBasis: ScaleBasis.BothHeight),
text: "", style: "GUIUpdateButton")
{
BulkDownloader.PrepareUpdates();
return false;
},
Enabled = false
};
OnClicked = (b,
o) =>
{
BulkDownloader.PrepareUpdates();
return false;
},
Enabled = false
})
: Option.None;
padTopRight(width: 0.1f);
var (left, center, right) = CreateSidebars(mainLayout, centerWidth: 0.05f, leftWidth: 0.475f, rightWidth: 0.475f, height: 0.8f);
@@ -405,10 +409,13 @@ namespace Barotrauma.Steam
CanBeFocused = false
};
}
addFilterTickbox(Filter.ShowLocal, "WorkshopMenu.EditButton", selected: true);
addFilterTickbox(Filter.ShowWorkshop, "WorkshopMenu.DownloadedIcon", selected: true);
addFilterTickbox(Filter.ShowPublished, "WorkshopMenu.PublishedIcon", selected: true);
if (EnableWorkshopSupport)
{
addFilterTickbox(Filter.ShowLocal, "WorkshopMenu.EditButton", selected: true);
addFilterTickbox(Filter.ShowWorkshop, "WorkshopMenu.DownloadedIcon", selected: true);
addFilterTickbox(Filter.ShowPublished, "WorkshopMenu.PublishedIcon", selected: true);
}
addFilterTickbox(Filter.ShowOnlySubs, null, selected: false);
addFilterTickbox(Filter.ShowOnlyItemAssemblies, null, selected: false);
@@ -487,14 +494,23 @@ namespace Barotrauma.Steam
var iconBtn = guiItem.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last();
bool matches = false;
matches |= modsListFilterTickboxes[Filter.ShowLocal].Selected
&& ContentPackageManager.LocalPackages.Contains(p);
matches |= modsListFilterTickboxes[Filter.ShowPublished].Selected
&& (ContentPackageManager.WorkshopPackages.Contains(p)
&& iconBtn?.Style?.Identifier == "WorkshopMenu.PublishedIcon");
matches |= modsListFilterTickboxes[Filter.ShowWorkshop].Selected
&& (ContentPackageManager.WorkshopPackages.Contains(p)
&& iconBtn?.Style?.Identifier != "WorkshopMenu.PublishedIcon");
if (EnableWorkshopSupport)
{
matches |= modsListFilterTickboxes[Filter.ShowLocal].Selected
&& ContentPackageManager.LocalPackages.Contains(p);
matches |= modsListFilterTickboxes[Filter.ShowPublished].Selected
&& (ContentPackageManager.WorkshopPackages.Contains(p)
&& iconBtn?.Style?.Identifier == "WorkshopMenu.PublishedIcon");
matches |= modsListFilterTickboxes[Filter.ShowWorkshop].Selected
&& (ContentPackageManager.WorkshopPackages.Contains(p)
&& iconBtn?.Style?.Identifier != "WorkshopMenu.PublishedIcon");
}
else
{
matches = true;
}
if (modsListFilterTickboxes[Filter.ShowOnlySubs].Selected
&& modsListFilterTickboxes[Filter.ShowOnlyItemAssemblies].Selected
@@ -524,17 +540,20 @@ namespace Barotrauma.Steam
TaskPool.Add($"PrepareToShow{mod.UgcId}Info", SteamManager.Workshop.GetItem(workshopId.Value),
t =>
{
if (!t.TryGetResult(out Steamworks.Ugc.Item? item)) { return; }
if (item is null) { return; }
onInstalledInfoButtonHit(item.Value);
if (!t.TryGetResult(out Option<Steamworks.Ugc.Item> itemOption)) { return; }
if (!itemOption.TryUnwrap(out var item)) { return; }
onInstalledInfoButtonHit(item);
});
}
public void PopulateInstalledModLists(bool forceRefreshEnabled = false, bool refreshDisabled = true)
{
ViewingItemDetails = false;
bulkUpdateButton.Enabled = false;
bulkUpdateButton.ToolTip = "";
if (bulkUpdateButtonOption.TryUnwrap(out var bulkUpdateButton))
{
bulkUpdateButton.Enabled = false;
bulkUpdateButton.ToolTip = "";
}
ContentPackageManager.UpdateContentPackageList();
var corePackages = ContentPackageManager.CorePackages.ToArray();
@@ -583,7 +602,7 @@ namespace Barotrauma.Steam
return false;
}
};
if (!SteamManager.IsInitialized)
if (!EnableWorkshopSupport)
{
infoButton.Enabled = false;
}
@@ -599,8 +618,11 @@ namespace Barotrauma.Steam
infoButton.CanBeSelected = true;
infoButton.ApplyStyle(GUIStyle.ComponentStyles["WorkshopMenu.InfoButtonUpdate"]);
infoButton.ToolTip = TextManager.Get("ViewModDetailsUpdateAvailable");
bulkUpdateButton.Enabled = true;
bulkUpdateButton.ToolTip = TextManager.Get("ModUpdatesAvailable");
if (bulkUpdateButtonOption.TryUnwrap(out var bulkUpdateButton))
{
bulkUpdateButton.Enabled = true;
bulkUpdateButton.ToolTip = TextManager.Get("ModUpdatesAvailable");
}
});
}
}
@@ -705,7 +727,7 @@ namespace Barotrauma.Steam
TaskPool.AddIfNotFound($"UnsubFromSelected", Task.WhenAll(workshopIds.Select(SteamManager.Workshop.GetItem)),
t =>
{
if (!t.TryGetResult(out Steamworks.Ugc.Item?[] items)) { return; }
if (!t.TryGetResult(out Steamworks.Ugc.Item?[]? items)) { return; }
items.ForEach(it =>
{
if (!(it is { } item)) { return; }
@@ -761,7 +783,7 @@ namespace Barotrauma.Steam
SteamManager.Workshop.GetPublishedItems(),
t =>
{
if (!t.TryGetResult(out ISet<Steamworks.Ugc.Item> items)) { return; }
if (!t.TryGetResult(out ISet<Steamworks.Ugc.Item>? items)) { return; }
var ids = items.Select(it => it.Id).ToHashSet();
foreach (var child in enabledRegularModsList.Content.Children

View File

@@ -176,6 +176,8 @@ namespace Barotrauma.Steam
private void AddUnpublishedMods(ISet<Steamworks.Ugc.Item> workshopItems)
{
if (!selfModsListOption.TryUnwrap(out var selfModsList)) { return; }
//Users that don't have a proper license cannot publish Workshop items
//(see https://partner.steamgames.com/doc/features/workshop#15)
void clearWithMessage(LocalizedString message)
@@ -347,7 +349,7 @@ namespace Barotrauma.Steam
workshopItem.Subscribe();
TaskPool.Add($"DownloadSubscribedItem{workshopItem.Id}",
SteamManager.Workshop.ForceRedownload(workshopItem),
t => { });
TaskPool.IgnoredCallback);
}
else
{

View File

@@ -27,7 +27,7 @@ namespace Barotrauma.Steam
}
public Tab CurrentTab { get; private set; }
private readonly GUILayoutGroup tabber;
private readonly Dictionary<Tab, (GUIButton Button, GUIFrame Content)> tabContents;
@@ -36,11 +36,13 @@ namespace Barotrauma.Steam
private CancellationTokenSource taskCancelSrc = new CancellationTokenSource();
private readonly HashSet<SteamManager.Workshop.ItemThumbnail> itemThumbnails = new HashSet<SteamManager.Workshop.ItemThumbnail>();
private readonly GUIListBox popularModsList;
private readonly GUIListBox selfModsList;
private readonly Option<GUIListBox> popularModsListOption;
private readonly Option<GUIListBox> selfModsListOption;
private uint memSubscribedModCount = 0;
private static bool EnableWorkshopSupport => SteamManager.IsInitialized;
public MutableWorkshopMenu(GUIFrame parent) : base(parent)
{
var mainLayout
@@ -50,25 +52,34 @@ namespace Barotrauma.Steam
AbsoluteSpacing = GUI.IntScale(4)
};
tabber = new GUILayoutGroup(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform), isHorizontal: true)
Vector2 tabberSize = EnableWorkshopSupport ? (1.0f, 0.05f) : Vector2.Zero;
tabber = new GUILayoutGroup(new RectTransform(tabberSize, mainLayout.RectTransform), isHorizontal: true)
{ Stretch = true };
tabContents = new Dictionary<Tab, (GUIButton Button, GUIFrame Content)>();
new GUIButton(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform, Anchor.BottomLeft),
style: "GUIButtonSmall", text: TextManager.Get("FindModsButton"))
if (EnableWorkshopSupport)
{
OnClicked = (button, o) =>
new GUIButton(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform, Anchor.BottomLeft),
style: "GUIButtonSmall", text: TextManager.Get("FindModsButton"))
{
SteamManager.OverlayCustomUrl($"https://steamcommunity.com/app/{SteamManager.AppID}/workshop/");
return false;
}
};
OnClicked = (button, o) =>
{
SteamManager.OverlayCustomUrl($"https://steamcommunity.com/app/{SteamManager.AppID}/workshop/");
return false;
}
};
}
else
{
tabber.Visible = false;
}
contentFrame = new GUIFrame(new RectTransform((1.0f, 0.95f), mainLayout.RectTransform), style: null);
new GUICustomComponent(new RectTransform(Vector2.Zero, mainLayout.RectTransform),
onUpdate: (f, component) => UpdateSubscribedModInstalls());
CreateInstalledModsTab(
out enabledCoreDropdown,
out enabledRegularModsList,
@@ -76,9 +87,21 @@ namespace Barotrauma.Steam
out onInstalledInfoButtonHit,
out modsListFilter,
out modsListFilterTickboxes,
out bulkUpdateButton);
CreatePopularModsTab(out popularModsList);
CreatePublishTab(out selfModsList);
out bulkUpdateButtonOption);
if (EnableWorkshopSupport)
{
CreatePopularModsTab(out GUIListBox popularModList);
CreatePublishTab(out GUIListBox selfModsList);
popularModsListOption = Option<GUIListBox>.Some(popularModList);
selfModsListOption = Option<GUIListBox>.Some(selfModsList);
}
else
{
popularModsListOption = Option.None;
selfModsListOption = Option.None;
}
SelectTab(Tab.InstalledMods);
}
@@ -105,10 +128,10 @@ namespace Barotrauma.Steam
case Tab.InstalledMods:
PopulateInstalledModLists();
break;
case Tab.PopularMods:
case Tab.PopularMods when popularModsListOption.TryUnwrap(out var popularModsList):
PopulateItemList(popularModsList, SteamManager.Workshop.GetPopularItems(), includeSubscribeButton: true);
break;
case Tab.Publish:
case Tab.Publish when selfModsListOption.TryUnwrap(out var selfModsList):
PopulateItemList(selfModsList, SteamManager.Workshop.GetPublishedItems(), includeSubscribeButton: false, onFill: AddUnpublishedMods);
break;
}

View File

@@ -88,17 +88,21 @@ namespace Barotrauma.Steam
private void DeselectPublishedItem()
{
var deselectCarrier = selfModsList.Parent.FindChild(c => c.UserData is ActionCarrier { Id: var id } && id == "deselect");
Action? deselectAction = deselectCarrier.UserData is ActionCarrier { Action: var action }
? action
: null;
deselectAction?.Invoke();
if (selfModsListOption.TryUnwrap(out var selfModsList))
{
var deselectCarrier = selfModsList.Parent.FindChild(c => c.UserData is ActionCarrier { Id: var id } && id == "deselect");
Action? deselectAction = deselectCarrier.UserData is ActionCarrier { Action: var action }
? action
: null;
deselectAction?.Invoke();
}
SelectTab(Tab.Publish);
}
private static bool PackageMatchesItem(ContentPackage p, Steamworks.Ugc.Item workshopItem)
=> p.TryExtractSteamWorkshopId(out var workshopId) && workshopId.Value == workshopItem.Id;
private void PopulatePublishTab(ItemOrPackage itemOrPackage, GUIFrame parentFrame)
{
ContentPackageManager.LocalPackages.Refresh();
@@ -226,9 +230,12 @@ namespace Barotrauma.Steam
SteamManager.Workshop.GetItemAsap(workshopItem.Id.Value, withLongDescription: true),
t =>
{
if (!t.TryGetResult(out Steamworks.Ugc.Item? itemWithDescription)) { return; }
if (!t.TryGetResult(out Option<Steamworks.Ugc.Item> itemWithDescriptionOption)) { return; }
descriptionTextBox.Text = itemWithDescription?.Description ?? descriptionTextBox.Text;
descriptionTextBox.Text =
itemWithDescriptionOption.TryUnwrap(out var itemWithDescription)
? itemWithDescription.Description ?? descriptionTextBox.Text
: descriptionTextBox.Text;
descriptionTextBox.Deselect();
});
}
@@ -296,7 +303,7 @@ namespace Barotrauma.Steam
var fileInfoLabel = Label(rightBottom, "", GUIStyle.Font, heightScale: 1.0f);
fileInfoLabel.TextAlignment = Alignment.CenterRight;
TaskPool.Add($"FileInfoLabel{workshopItem.Id}", GetModDirInfo(localPackage.Dir, fileInfoLabel), t => { });
TaskPool.AddWithResult($"FileInfoLabel{workshopItem.Id}", GetModDirInfo(localPackage.Dir, fileInfoLabel), t => { });
GUILayoutGroup buttonLayout = new GUILayoutGroup(NewItemRectT(rightBottom), isHorizontal: true, childAnchor: Anchor.CenterRight);
@@ -351,7 +358,7 @@ namespace Barotrauma.Steam
buttons: new[] { TextManager.Get("Yes"), TextManager.Get("No") });
confirmDeletion.Buttons[0].OnClicked = (yesBuffer, o1) =>
{
TaskPool.Add($"Delete{workshopItem.Id}", Steamworks.SteamUGC.DeleteFileAsync(workshopItem.Id),
TaskPool.AddWithResult($"Delete{workshopItem.Id}", Steamworks.SteamUGC.DeleteFileAsync(workshopItem.Id),
t =>
{
SteamManager.Workshop.Uninstall(workshopItem);
@@ -452,7 +459,7 @@ namespace Barotrauma.Steam
}
bool localCopyMade = false;
TaskPool.Add($"Create local copy {workshopItem.Title}",
TaskPool.AddWithResult($"Create local copy {workshopItem.Title}",
SteamManager.Workshop.CreateLocalCopy(workshopCopy),
(t) =>
{

View File

@@ -0,0 +1,70 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Barotrauma.Steam;
namespace Barotrauma;
static class StoreIntegration
{
public enum Store
{
None,
Steam,
Epic
}
public static Store CurrentStore { get; private set; } = Store.None;
public static void Init(ref string[] programArgs)
{
#if DEBUG
if (EosInterface.Login.ParseEgsExchangeCode(programArgs).IsNone())
{
// If the dev tool is running on port 8730 with a credential of name localdev,
// we can ask it to give us an exchange code so we can test the launcher args parsing
try
{
var devAuthToolHttp = new HttpClient();
devAuthToolHttp.BaseAddress = new UriBuilder(scheme: "http", host: "127.0.0.1", portNumber: 8730).Uri;
var response = devAuthToolHttp.Send(new HttpRequestMessage(HttpMethod.Get, "localdev/exchange_code"));
if (response.IsSuccessStatusCode)
{
string responseContent = response.Content.ReadAsStringAsync().Result;
var match = Regex.Match(input: responseContent,
@"\s*{\s*""exchange_code""\s*:\s*""([0-9a-fA-F]+)""\s*}\s*");
if (match.Groups.Count > 1)
{
programArgs = programArgs.Concat(new[]
{
$"-AUTH_PASSWORD={match.Groups[1].Value}",
"-AUTH_TYPE=exchangecode"
}).ToArray();
}
}
}
catch { /* do nothing */ }
}
#endif
if (EosInterface.Login.ParseEgsExchangeCode(programArgs).IsNone() && SteamManager.SteamworksLibExists)
{
// Didn't get EGS exchange code, assume we're on Steam
// and do not initialize EOS SDK until player consent is confirmed
SteamManager.Initialize();
CurrentStore = Store.Steam;
}
else
{
// Got an EGS exchange code or Steamworks is not present in the files,
// assume we're on EGS and initialize EOS SDK immediately.
if (EosInterface.Core.Init(EosInterface.ApplicationCredentials.Client, enableOverlay: RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
.TryUnwrapFailure(out var initError))
{
DebugConsole.ThrowError($"EOS failed to initialize: {initError}");
}
CurrentStore = Store.Epic;
}
}
}

View File

@@ -19,12 +19,14 @@ namespace Barotrauma
public override bool Loaded => nestedStr.Loaded;
protected override bool MustRetrieveValue()
{
return base.MustRetrieveValue() || cachedFont != font.Value || cachedFont.Size != font.Size;
return base.MustRetrieveValue() || cachedFont != font.Value || cachedFont?.Size != font.Size;
}
public override void RetrieveValue()
{
cachedValue = ToolBox.LimitString(nestedStr.Value, font.Value, maxWidth);
cachedValue = font.Value != null
? ToolBox.LimitString(nestedStr.Value, font.Value, maxWidth)
: nestedStr.Value;
cachedFont = font.Value;
UpdateLanguage();
}

View File

@@ -19,7 +19,10 @@ namespace Barotrauma
public override bool Loaded => nestedStr.Loaded;
public override void RetrieveValue()
{
cachedValue = ToolBox.WrapText(nestedStr.Value, lineLength, font.GetFontForStr(nestedStr.Value), textScale);
cachedValue =
font.GetFontForStr(nestedStr.Value) is ScalableFont scalableFont
? ToolBox.WrapText(nestedStr.Value, lineLength, scalableFont, textScale)
: nestedStr.Value;
UpdateLanguage();
}
}

View File

@@ -1,32 +0,0 @@
#nullable enable
using Barotrauma.Networking;
namespace Barotrauma
{
readonly struct ConnectCommand
{
public readonly struct NameAndEndpoint
{
public readonly string ServerName;
public readonly Endpoint Endpoint;
public NameAndEndpoint(string serverName, Endpoint endpoint)
{
ServerName = serverName;
Endpoint = endpoint;
}
}
public readonly Either<NameAndEndpoint, ulong> EndpointOrLobby;
public ConnectCommand(string serverName, Endpoint endpoint)
{
EndpointOrLobby = new NameAndEndpoint(serverName, endpoint);
}
public ConnectCommand(ulong lobbyId)
{
EndpointOrLobby = lobbyId;
}
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
#nullable enable
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -451,28 +452,6 @@ namespace Barotrauma
public static string WrapText(string text, float lineLength, ScalableFont font, float textScale = 1.0f)
=> font.WrapText(text, lineLength / textScale);
public static Option<ConnectCommand> ParseConnectCommand(string[] args)
{
if (args == null || args.Length < 2) { return Option<ConnectCommand>.None(); }
if (args[0].Equals("-connect", StringComparison.OrdinalIgnoreCase))
{
if (args.Length < 3) { return Option<ConnectCommand>.None(); }
if (!(Endpoint.Parse(args[2]).TryUnwrap(out var endpoint))) { return Option<ConnectCommand>.None(); }
return Option<ConnectCommand>.Some(
new ConnectCommand(
serverName: args[1],
endpoint: endpoint));
}
else if (args[0].Equals("+connect_lobby", StringComparison.OrdinalIgnoreCase))
{
return UInt64.TryParse(args[1], out var lobbyId)
? Option<ConnectCommand>.Some(new ConnectCommand(lobbyId))
: Option<ConnectCommand>.None();
}
return Option<ConnectCommand>.None();
}
public static bool VersionNewerIgnoreRevision(Version a, Version b)
{
if (b.Major > a.Major) { return true; }

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.2.8.0</Version>
<Version>1.3.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>
@@ -17,38 +17,38 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE;CLIENT;LINUX;USE_STEAM</DefineConstants>
<DefineConstants>DEBUG;TRACE;CLIENT;LINUX</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DefineConstants>TRACE;DEBUG;CLIENT;LINUX;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;DEBUG;CLIENT;LINUX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;CLIENT;LINUX;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;CLIENT;LINUX</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|AnyCPU'">
<DefineConstants>TRACE;CLIENT;LINUX;USE_STEAM;UNSTABLE</DefineConstants>
<DefineConstants>TRACE;CLIENT;LINUX;UNSTABLE</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DefineConstants>TRACE;CLIENT;LINUX;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;CLIENT;LINUX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|x64'">
<DefineConstants>TRACE;CLIENT;LINUX;X64;USE_STEAM;UNSTABLE</DefineConstants>
<DefineConstants>TRACE;CLIENT;LINUX;X64;UNSTABLE</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
<Optimize>true</Optimize>
@@ -62,7 +62,6 @@
</ItemGroup>
<ItemGroup>
<Content Remove="..\BarotraumaShared\**\EnglishVanillaROS.xml" />
<Content Remove="..\BarotraumaShared\**\*.cs" />
<Content Remove="..\BarotraumaShared\**\*.props" />
<Compile Include="..\BarotraumaShared\**\*.cs" />
@@ -136,6 +135,11 @@
<PackageReference Include="RestSharp" Version="106.13.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\BarotraumaCore\BarotraumaCore.csproj" />
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\EosInterface\EosInterface.csproj" />
</ItemGroup>
<!-- Sourced from https://stackoverflow.com/a/45248069 -->
<Target Name="GetGitRevision" BeforeTargets="WriteGitRevision" Condition="'$(BuildHash)' == ''">
<PropertyGroup>
@@ -201,8 +205,10 @@
</Target>
<PropertyGroup>
<GARuntime>linux-x64</GARuntime>
<ManualDeployRuntime>linux-x64</ManualDeployRuntime>
<ProjectFileNamePlatformSuffix>Linux</ProjectFileNamePlatformSuffix>
</PropertyGroup>
<Import Project="../BarotraumaShared/DeployEosPrivate.props" />
<Import Project="../BarotraumaShared/DeployGameAnalytics.props" />
<Import Project="../BarotraumaShared/Luatrauma.props" />

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.2.8.0</Version>
<Version>1.3.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>
@@ -18,26 +18,26 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;CLIENT;OSX;USE_STEAM;DEBUG;NETCOREAPP;NETCOREAPP3_0</DefineConstants>
<DefineConstants>TRACE;CLIENT;OSX;DEBUG;NETCOREAPP;NETCOREAPP3_0</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Mac</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DefineConstants>TRACE;DEBUG;CLIENT;OSX;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;DEBUG;CLIENT;OSX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Mac\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;CLIENT;OSX;USE_STEAM;RELEASE;NETCOREAPP;NETCOREAPP3_0</DefineConstants>
<DefineConstants>TRACE;CLIENT;OSX;RELEASE;NETCOREAPP;NETCOREAPP3_0</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<DebugType></DebugType>
<OutputPath>..\bin\$(Configuration)Mac</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|AnyCPU'">
<DefineConstants>TRACE;CLIENT;OSX;USE_STEAM;RELEASE;NETCOREAPP;NETCOREAPP3_0;UNSTABLE</DefineConstants>
<DefineConstants>TRACE;CLIENT;OSX;RELEASE;NETCOREAPP;NETCOREAPP3_0;UNSTABLE</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<DebugType />
<OutputPath>..\bin\$(Configuration)Mac</OutputPath>
@@ -45,13 +45,13 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DefineConstants>TRACE;CLIENT;OSX;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;CLIENT;OSX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Mac\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|x64'">
<DefineConstants>TRACE;CLIENT;OSX;X64;USE_STEAM;UNSTABLE</DefineConstants>
<DefineConstants>TRACE;CLIENT;OSX;X64;UNSTABLE</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Mac\</OutputPath>
<Optimize>true</Optimize>
@@ -65,7 +65,6 @@
</ItemGroup>
<ItemGroup>
<Content Remove="..\BarotraumaShared\**\EnglishVanillaROS.xml" />
<Content Remove="..\BarotraumaShared\**\*.cs" />
<Content Remove="..\BarotraumaShared\**\*.props" />
<Compile Include="..\BarotraumaShared\**\*.cs" />
@@ -141,6 +140,10 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\BarotraumaCore\BarotraumaCore.csproj" />
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\EosInterface\EosInterface.csproj" />
</ItemGroup>
<!-- Sourced from https://stackoverflow.com/a/45248069 -->
<Target Name="GetGitRevision" BeforeTargets="WriteGitRevision" Condition="'$(BuildHash)' == ''">
@@ -207,8 +210,10 @@
</Target>
<PropertyGroup>
<GARuntime>osx-x64</GARuntime>
<ManualDeployRuntime>osx-x64</ManualDeployRuntime>
<ProjectFileNamePlatformSuffix>MacOS</ProjectFileNamePlatformSuffix>
</PropertyGroup>
<Import Project="../BarotraumaShared/DeployEosPrivate.props" />
<Import Project="../BarotraumaShared/DeployGameAnalytics.props" />
<Import Project="../BarotraumaShared/Luatrauma.props" />

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.2.8.0</Version>
<Version>1.3.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>
@@ -18,13 +18,13 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE;CLIENT;WINDOWS;USE_STEAM</DefineConstants>
<DefineConstants>DEBUG;TRACE;CLIENT;WINDOWS</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Windows\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DefineConstants>TRACE;DEBUG;CLIENT;WINDOWS;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;DEBUG;CLIENT;WINDOWS;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Windows\</OutputPath>
<DebugType>full</DebugType>
@@ -33,20 +33,20 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;CLIENT;WINDOWS;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;CLIENT;WINDOWS</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Windows\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|AnyCPU'">
<DefineConstants>TRACE;CLIENT;WINDOWS;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;CLIENT;WINDOWS</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Windows\</OutputPath>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DefineConstants>TRACE;CLIENT;WINDOWS;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;CLIENT;WINDOWS;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Windows\</OutputPath>
<DebugType>full</DebugType>
@@ -54,7 +54,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|x64'">
<DefineConstants>TRACE;CLIENT;WINDOWS;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;CLIENT;WINDOWS;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Windows\</OutputPath>
<DebugType>full</DebugType>
@@ -70,7 +70,6 @@
</ItemGroup>
<ItemGroup>
<Content Remove="..\BarotraumaShared\**\EnglishVanillaROS.xml" />
<Content Remove="..\BarotraumaShared\**\*.cs" />
<Content Remove="..\BarotraumaShared\**\*.props" />
<Compile Include="..\BarotraumaShared\**\*.cs" />
@@ -168,6 +167,11 @@
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\BarotraumaCore\BarotraumaCore.csproj" />
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\EosInterface\EosInterface.csproj" />
</ItemGroup>
<!-- Sourced from https://stackoverflow.com/a/45248069 -->
<Target Name="GetGitRevision" BeforeTargets="WriteGitRevision" Condition="'$(BuildHash)' == ''">
<PropertyGroup>
@@ -233,8 +237,10 @@
</Target>
<PropertyGroup>
<GARuntime>win-x64</GARuntime>
<ManualDeployRuntime>win-x64</ManualDeployRuntime>
<ProjectFileNamePlatformSuffix>Win64</ProjectFileNamePlatformSuffix>
</PropertyGroup>
<Import Project="../BarotraumaShared/DeployEosPrivate.props" />
<Import Project="../BarotraumaShared/DeployGameAnalytics.props" />
<Import Project="../BarotraumaShared/Luatrauma.props" />

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -6,49 +6,49 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.2.8.0</Version>
<Version>1.3.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations>
<InvariantGlobalization>true</InvariantGlobalization>
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE;SERVER;LINUX;USE_STEAM</DefineConstants>
<DefineConstants>DEBUG;TRACE;SERVER;LINUX</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DefineConstants>TRACE;DEBUG;SERVER;LINUX;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;DEBUG;SERVER;LINUX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;SERVER;LINUX;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;SERVER;LINUX</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|AnyCPU'">
<DefineConstants>TRACE;SERVER;LINUX;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;SERVER;LINUX</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DefineConstants>TRACE;SERVER;LINUX;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;SERVER;LINUX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|x64'">
<DefineConstants>TRACE;SERVER;LINUX;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;SERVER;LINUX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
<Optimize>true</Optimize>
@@ -79,6 +79,11 @@
<ItemGroup>
<PackageReference Include="RestSharp" Version="106.13.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\BarotraumaCore\BarotraumaCore.csproj" />
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\EosInterface\EosInterface.csproj" />
</ItemGroup>
<!-- Sourced from https://stackoverflow.com/a/45248069 -->
<Target Name="GetGitRevision" BeforeTargets="WriteGitRevision" Condition="'$(BuildHash)' == ''">
@@ -144,9 +149,14 @@
<WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
</Target>
<PropertyGroup>
<ManualDeployRuntime>linux-x64</ManualDeployRuntime>
<ProjectFileNamePlatformSuffix>Linux</ProjectFileNamePlatformSuffix>
</PropertyGroup>
<Import Project="../BarotraumaShared/DeployEosPrivate.props" />
<Import Project="../BarotraumaShared/Luatrauma.props" />
<ItemGroup>
<None Include="../BarotraumaShared/Luatrauma.props" />
</ItemGroup>
</Project>

View File

@@ -6,18 +6,18 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.2.8.0</Version>
<Version>1.3.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations>
<InvariantGlobalization>true</InvariantGlobalization>
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;SERVER;OSX;USE_STEAM;DEBUG;NETCOREAPP;NETCOREAPP3_0</DefineConstants>
<DefineConstants>TRACE;SERVER;OSX;DEBUG;NETCOREAPP;NETCOREAPP3_0</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\DebugMac</OutputPath>
<ConsolePause>true</ConsolePause>
@@ -25,20 +25,20 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DefineConstants>TRACE;DEBUG;SERVER;OSX;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;DEBUG;SERVER;OSX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Mac\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;SERVER;OSX;USE_STEAM;RELEASE;NETCOREAPP;NETCOREAPP3_0</DefineConstants>
<DefineConstants>TRACE;SERVER;OSX;RELEASE;NETCOREAPP;NETCOREAPP3_0</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<DebugType></DebugType>
<OutputPath>..\bin\ReleaseMac</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|AnyCPU'">
<DefineConstants>TRACE;SERVER;OSX;USE_STEAM;RELEASE;NETCOREAPP;NETCOREAPP3_0;UNSTABLE</DefineConstants>
<DefineConstants>TRACE;SERVER;OSX;RELEASE;NETCOREAPP;NETCOREAPP3_0;UNSTABLE</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<DebugType />
<OutputPath>..\bin\ReleaseMac</OutputPath>
@@ -46,13 +46,13 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DefineConstants>TRACE;SERVER;OSX;X64;USE_STEAM</DefineConstants>
<DefineConstants>TRACE;SERVER;OSX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Mac\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|x64'">
<DefineConstants>TRACE;SERVER;OSX;X64;USE_STEAM;UNSTABLE</DefineConstants>
<DefineConstants>TRACE;SERVER;OSX;X64;UNSTABLE</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Mac\</OutputPath>
<Optimize>true</Optimize>
@@ -85,7 +85,11 @@
<ItemGroup>
<PackageReference Include="RestSharp" Version="106.13.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\BarotraumaCore\BarotraumaCore.csproj" />
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\EosInterface\EosInterface.csproj" />
</ItemGroup>
<!-- Sourced from https://stackoverflow.com/a/45248069 -->
<Target Name="GetGitRevision" BeforeTargets="WriteGitRevision" Condition="'$(BuildHash)' == ''">
<PropertyGroup>
@@ -150,9 +154,14 @@
<WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
</Target>
<PropertyGroup>
<ManualDeployRuntime>osx-x64</ManualDeployRuntime>
<ProjectFileNamePlatformSuffix>MacOS</ProjectFileNamePlatformSuffix>
</PropertyGroup>
<Import Project="../BarotraumaShared/DeployEosPrivate.props" />
<Import Project="../BarotraumaShared/Luatrauma.props" />
<ItemGroup>
<None Include="../BarotraumaShared/Luatrauma.props" />
</ItemGroup>
</Project>

View File

@@ -72,7 +72,7 @@ namespace Barotrauma
msg.WriteString(ragdollFileName);
msg.WriteIdentifier(HumanPrefabIds.NpcIdentifier);
msg.WriteIdentifier(MinReputationToHire.factionId);
if (MinReputationToHire.factionId != default)
if (!MinReputationToHire.factionId.IsEmpty)
{
msg.WriteSingle(MinReputationToHire.reputation);
}

View File

@@ -38,10 +38,8 @@ namespace Barotrauma
NewMessage("Client \"" + client.Name + "\" attempted to use the command \"" + Names[0] + "\". Cheats must be enabled using \"enablecheats\" before the command can be used.", Color.Red);
GameMain.Server.SendConsoleMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + Names[0] + "\".", client, Color.Red);
#if USE_STEAM
NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red);
GameMain.Server.SendConsoleMessage("Enabling cheats will disable Steam achievements during this play session.", client, Color.Red);
#endif
return;
}
@@ -1063,21 +1061,17 @@ namespace Barotrauma
commands.Add(new Command("enablecheats", "enablecheats: Enables cheat commands and disables Steam achievements during this play session.", (string[] args) =>
{
CheatsEnabled = true;
SteamAchievementManager.CheatsEnabled = true;
AchievementManager.CheatsEnabled = true;
NewMessage("Enabled cheat commands.", Color.Red);
#if USE_STEAM
NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
#endif
GameMain.Server?.UpdateCheatsEnabled();
}));
AssignOnClientRequestExecute("enablecheats", (client, cursorPos, args) =>
{
CheatsEnabled = true;
SteamAchievementManager.CheatsEnabled = true;
AchievementManager.CheatsEnabled = true;
NewMessage("Cheat commands have been enabled by \"" + client.Name + "\".", Color.Red);
#if USE_STEAM
NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
#endif
GameMain.Server?.UpdateCheatsEnabled();
});

View File

@@ -20,7 +20,7 @@ partial class EventLogAction : EventAction
if (target is Character character)
{
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
if (ownerClient != null && eventLog != null)
if (ownerClient != null)
{
targetClients.Add(ownerClient);
}
@@ -38,7 +38,7 @@ partial class EventLogAction : EventAction
}
else
{
if (eventLog != null && eventLog.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, GameMain.Server.ConnectedClients) && ShowInServerLog)
if (eventLog.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, GameMain.Server.ConnectedClients) && ShowInServerLog)
{
Log(targetClients: null);
}

View File

@@ -88,8 +88,21 @@ namespace Barotrauma
Console.WriteLine("Loading game settings");
GameSettings.Init();
Console.WriteLine("Initializing SteamManager");
SteamManager.Initialize();
//no owner key = dedicated server
if (!CommandLineArgs.Any(a => a.Trim().Equals("-ownerkey", StringComparison.OrdinalIgnoreCase)))
{
Console.WriteLine("Initializing SteamManager");
SteamManager.Initialize();
if (!SteamManager.SteamworksLibExists)
{
Console.WriteLine("Initializing EosManager");
if (EosInterface.Core.Init(EosInterface.ApplicationCredentials.Server, enableOverlay: false).TryUnwrapFailure(out var initError))
{
Console.WriteLine($"EOS failed to initialize: {initError}");
}
}
}
//TODO: figure out how consent is supposed to work for servers
//Console.WriteLine("Initializing GameAnalytics");
@@ -143,8 +156,8 @@ namespace Barotrauma
bool enableUpnp = false;
int maxPlayers = 10;
Option<int> ownerKey = Option<int>.None();
Option<SteamId> steamId = Option<SteamId>.None();
Option<int> ownerKey = Option.None;
Option<P2PEndpoint> ownerEndpoint = Option.None;
IPAddress listenIp = IPAddress.Any;
XDocument doc = XMLExtensions.TryLoadXml(ServerSettings.SettingsFile);
@@ -220,8 +233,8 @@ namespace Barotrauma
}
i++;
break;
case "-steamid":
steamId = SteamId.Parse(CommandLineArgs[i + 1]);
case "-endpoint":
ownerEndpoint = P2PEndpoint.Parse(CommandLineArgs[i + 1]);
i++;
break;
case "-pipes":
@@ -241,7 +254,7 @@ namespace Barotrauma
enableUpnp,
maxPlayers,
ownerKey,
steamId);
ownerEndpoint);
Server.StartServer();
for (int i = 0; i < CommandLineArgs.Length; i++)
@@ -339,6 +352,7 @@ namespace Barotrauma
Server.Update((float)Timing.Step);
if (Server == null) { break; }
SteamManager.Update((float)Timing.Step);
EosInterface.Core.Update();
TaskPool.Update();
CoroutineManager.Update(paused: false, (float)Timing.Step);

View File

@@ -174,6 +174,22 @@ namespace Barotrauma.Networking
return bannedPlayer != null;
}
public bool IsBanned(AccountInfo accountInfo, out string reason)
{
if (accountInfo.AccountId.TryUnwrap(out var accountId) && IsBanned(accountId, out reason))
{
return true;
}
foreach (var otherId in accountInfo.OtherMatchingIds)
{
if (IsBanned(otherId, out reason)) { return true; }
}
reason = "";
return false;
}
public void BanPlayer(string name, Endpoint endpoint, string reason, TimeSpan? duration)
=> BanPlayer(name, endpoint.Address, reason, duration);
@@ -305,7 +321,7 @@ namespace Barotrauma.Networking
else
{
outMsg.WriteBoolean(false); outMsg.WritePadBits();
outMsg.WriteString(((SteamId)bannedPlayer.AddressOrAccountId).StringRepresentation);
outMsg.WriteString(((AccountId)bannedPlayer.AddressOrAccountId).StringRepresentation);
}
}
}

View File

@@ -54,7 +54,7 @@ namespace Barotrauma.Networking
private DateTime refreshMasterTimer;
private readonly TimeSpan refreshMasterInterval = new TimeSpan(0, 0, 60);
private bool registeredToMaster;
private bool registeredToSteamMaster;
private DateTime roundStartTime;
@@ -123,7 +123,7 @@ namespace Barotrauma.Networking
public NetworkConnection OwnerConnection { get; private set; }
private readonly Option<int> ownerKey;
private readonly Option<SteamId> ownerSteamId;
private readonly Option<P2PEndpoint> ownerEndpoint;
public GameServer(
string name,
@@ -135,7 +135,7 @@ namespace Barotrauma.Networking
bool attemptUPnP,
int maxPlayers,
Option<int> ownerKey,
Option<SteamId> ownerSteamId)
Option<P2PEndpoint> ownerEndpoint)
{
if (name.Length > NetConfig.ServerNameMaxLength)
{
@@ -153,7 +153,7 @@ namespace Barotrauma.Networking
this.ownerKey = ownerKey;
this.ownerSteamId = ownerSteamId;
this.ownerEndpoint = ownerEndpoint;
entityEventManager = new ServerEntityEventManager(this);
}
@@ -168,16 +168,18 @@ namespace Barotrauma.Networking
OnInitializationComplete,
GameMain.Instance.CloseServer,
OnOwnerDetermined);
if (ownerSteamId.TryUnwrap(out var steamId))
if (ownerEndpoint.TryUnwrap(out var endpoint))
{
Log("Using SteamP2P networking.", ServerLog.MessageType.ServerMessage);
serverPeer = new SteamP2PServerPeer(steamId, ownerKey.Fallback(0), ServerSettings, callbacks);
Log("Using P2P networking.", ServerLog.MessageType.ServerMessage);
serverPeer = new P2PServerPeer(endpoint, ownerKey.Fallback(0), ServerSettings, callbacks);
}
else
{
Log("Using Lidgren networking. Manual port forwarding may be required. If players cannot connect to the server, you may want to use the in-game hosting menu (which uses SteamP2P networking and does not require port forwarding).", ServerLog.MessageType.ServerMessage);
Log("Using Lidgren networking. Manual port forwarding may be required. If players cannot connect to the server, you may want to use the in-game hosting menu (which uses Steamworks and EOS networking and does not require port forwarding).", ServerLog.MessageType.ServerMessage);
serverPeer = new LidgrenServerPeer(ownerKey, ServerSettings, callbacks);
registeredToSteamMaster = SteamManager.CreateServer(this, ServerSettings.IsPublic);
Eos.EosSessionManager.UpdateOwnedSession(Option.None, ServerSettings);
}
FileSender = new FileSender(serverPeer, MsgConstants.MTU);
@@ -190,15 +192,7 @@ namespace Barotrauma.Networking
VoipServer = new VoipServer(serverPeer);
if (serverPeer is LidgrenServerPeer)
{
#if USE_STEAM
registeredToMaster = SteamManager.CreateServer(this, ServerSettings.IsPublic);
#endif
}
GameMain.LuaCs.Initialize();
Log("Server started", ServerLog.MessageType.ServerMessage);
GameMain.NetLobbyScreen.Select();
@@ -658,20 +652,23 @@ namespace Barotrauma.Networking
updateTimer = DateTime.Now + UpdateInterval;
}
if (registeredToMaster && (DateTime.Now > refreshMasterTimer || ServerSettings.ServerDetailsChanged))
if (DateTime.Now > refreshMasterTimer || ServerSettings.ServerDetailsChanged)
{
if (GameSettings.CurrentConfig.UseSteamMatchmaking)
if (registeredToSteamMaster)
{
bool refreshSuccessful = SteamManager.RefreshServerDetails(this);
if (GameSettings.CurrentConfig.VerboseLogging)
{
Log(refreshSuccessful ?
"Refreshed server info on the server list." :
"Refreshing server info on the server list failed.", ServerLog.MessageType.ServerMessage);
"Refreshed server info on the Steam server list." :
"Refreshing server info on the Steam server list failed.", ServerLog.MessageType.ServerMessage);
}
}
refreshMasterTimer = DateTime.Now + refreshMasterInterval;
Eos.EosSessionManager.UpdateOwnedSession(Option.None, ServerSettings);
ServerSettings.ServerDetailsChanged = false;
refreshMasterTimer = DateTime.Now + refreshMasterInterval;
}
}
@@ -3082,8 +3079,6 @@ namespace Barotrauma.Networking
client.WaitForNextRoundRespawn = null;
client.InGame = false;
if (client.AccountId.TryUnwrap<SteamId>(out var steamId)) { SteamManager.StopAuthSession(steamId); }
var previousPlayer = previousPlayers.Find(p => p.MatchesClient(client));
if (previousPlayer == null)
{
@@ -3416,7 +3411,7 @@ namespace Barotrauma.Networking
msg.WriteByte((byte)ServerPacketHeader.FILE_TRANSFER);
msg.WriteByte((byte)FileTransferMessageType.Cancel);
msg.WriteByte((byte)transfer.ID);
serverPeer.Send(msg, transfer.Connection, DeliveryMethod.ReliableOrdered);
serverPeer.Send(msg, transfer.Connection, DeliveryMethod.Reliable);
}
public void UpdateVoteStatus(bool checkActiveVote = true)
@@ -3602,13 +3597,13 @@ namespace Barotrauma.Networking
}
}
public void IncrementStat(Character character, Identifier achievementIdentifier, int amount)
public void IncrementStat(Character character, AchievementStat stat, int amount)
{
foreach (Client client in connectedClients)
{
if (client.Character == character)
{
IncrementStat(client, achievementIdentifier, amount);
IncrementStat(client, stat, amount);
return;
}
}
@@ -3622,19 +3617,17 @@ namespace Barotrauma.Networking
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ServerPacketHeader.ACHIEVEMENT);
msg.WriteIdentifier(achievementIdentifier);
msg.WriteInt32(0);
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
}
public void IncrementStat(Client client, Identifier achievementIdentifier, int amount)
public void IncrementStat(Client client, AchievementStat stat, int amount)
{
if (client.GivenAchievements.Contains(achievementIdentifier)) { return; }
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ServerPacketHeader.ACHIEVEMENT);
msg.WriteIdentifier(achievementIdentifier);
msg.WriteInt32(amount);
msg.WriteByte((byte)ServerPacketHeader.ACHIEVEMENT_STAT);
INetSerializableStruct incrementedStat = new NetIncrementedStat(stat, amount);
incrementedStat.Write(msg);
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
}
@@ -3642,7 +3635,7 @@ namespace Barotrauma.Networking
public void SendTraitorMessage(WriteOnlyMessage msg, Client client)
{
if (client == null) { return; };
serverPeer.Send(msg, client.Connection, DeliveryMethod.ReliableOrdered);
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
}
public void UpdateCheatsEnabled()

View File

@@ -1,24 +1,26 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Net;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Steam;
using Lidgren.Network;
namespace Barotrauma.Networking
{
internal sealed class LidgrenServerPeer : ServerPeer
internal sealed class LidgrenServerPeer : ServerPeer<LidgrenConnection>
{
private readonly NetPeerConfiguration netPeerConfiguration;
private ImmutableDictionary<AuthenticationTicketKind, Authenticator>? authenticators;
private NetServer? netServer;
private readonly List<NetIncomingMessage> incomingLidgrenMessages;
public LidgrenServerPeer(Option<int> ownKey, ServerSettings settings, Callbacks callbacks) : base(callbacks)
public LidgrenServerPeer(Option<int> ownKey, ServerSettings settings, Callbacks callbacks) : base(callbacks, settings)
{
serverSettings = settings;
authenticators = null;
netServer = null;
netPeerConfiguration = new NetPeerConfiguration("barotrauma")
@@ -42,9 +44,6 @@ namespace Barotrauma.Networking
netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
connectedClients = new List<NetworkConnection>();
pendingClients = new List<PendingClient>();
incomingLidgrenMessages = new List<NetIncomingMessage>();
ownerKey = ownKey;
@@ -54,6 +53,8 @@ namespace Barotrauma.Networking
{
if (netServer != null) { return; }
authenticators = Authenticator.GetAuthenticatorsForHost(Option.None);
incomingLidgrenMessages.Clear();
netServer = new NetServer(netPeerConfiguration);
@@ -81,7 +82,7 @@ namespace Barotrauma.Networking
for (int i = connectedClients.Count - 1; i >= 0; i--)
{
Disconnect(connectedClients[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
Disconnect(connectedClients[i].Connection, PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
}
netServer.Shutdown(PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown).ToLidgrenStringRepresentation());
@@ -91,8 +92,6 @@ namespace Barotrauma.Networking
netServer = null;
Steamworks.SteamServer.OnValidateAuthTicketResponse -= OnAuthChange;
callbacks.OnShutdown.Invoke();
}
@@ -166,9 +165,10 @@ namespace Barotrauma.Networking
ToolBox.ThrowIfNull(netPeerConfiguration);
netServer.UPnP.ForwardPort(netPeerConfiguration.Port, "barotrauma");
#if USE_STEAM
netServer.UPnP.ForwardPort(serverSettings.QueryPort, "barotrauma");
#endif
if (SteamManager.IsInitialized)
{
netServer.UPnP.ForwardPort(serverSettings.QueryPort, "barotrauma");
}
}
private bool DiscoveringUPnP()
@@ -209,7 +209,7 @@ namespace Barotrauma.Networking
return;
}
PendingClient? pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == inc.SenderConnection);
PendingClient? pendingClient = pendingClients.Find(c => c.Connection.NetConnection == inc.SenderConnection);
if (pendingClient is null)
{
@@ -224,7 +224,7 @@ namespace Barotrauma.Networking
{
if (netServer == null) { return; }
PendingClient? pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == lidgrenMsg.SenderConnection);
PendingClient? pendingClient = pendingClients.Find(c => c.Connection.NetConnection == lidgrenMsg.SenderConnection);
IReadMessage inc = lidgrenMsg.ToReadMessage();
@@ -236,7 +236,7 @@ namespace Barotrauma.Networking
}
else if (!packetHeader.IsConnectionInitializationStep())
{
if (connectedClients.Find(c => c is LidgrenConnection l && l.NetConnection == lidgrenMsg.SenderConnection) is not LidgrenConnection conn)
if (connectedClients.Find(c => c.Connection.NetConnection == lidgrenMsg.SenderConnection) is not { Connection: LidgrenConnection conn })
{
if (pendingClient != null)
{
@@ -273,7 +273,7 @@ namespace Barotrauma.Networking
switch (inc.SenderConnection.Status)
{
case NetConnectionStatus.Disconnected:
LidgrenConnection? conn = connectedClients.Cast<LidgrenConnection>().FirstOrDefault(c => c.NetConnection == inc.SenderConnection);
LidgrenConnection? conn = connectedClients.Select(c => c.Connection).FirstOrDefault(c => c.NetConnection == inc.SenderConnection);
if (conn != null)
{
if (conn == OwnerConnection)
@@ -300,12 +300,7 @@ namespace Barotrauma.Networking
}
}
public override void InitializeSteamServerCallbacks()
{
Steamworks.SteamServer.OnValidateAuthTicketResponse += OnAuthChange;
}
private void OnAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
private void OnSteamAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
{
if (netServer == null) { return; }
@@ -317,8 +312,8 @@ namespace Barotrauma.Networking
if (status == Steamworks.AuthResponse.OK) { return; }
if (connectedClients.Find(c
=> c.AccountInfo.AccountId.TryUnwrap<SteamId>(out var id) && id.Value == steamId)
is LidgrenConnection connection)
=> c.Connection.AccountInfo.AccountId.TryUnwrap<SteamId>(out var id) && id.Value == steamId)
is { Connection: LidgrenConnection connection })
{
Disconnect(connection, PeerDisconnectPacket.SteamAuthError(status));
}
@@ -351,9 +346,15 @@ namespace Barotrauma.Networking
{
if (netServer == null) { return; }
if (!connectedClients.Contains(conn))
if (conn is not LidgrenConnection lidgrenConnection)
{
DebugConsole.ThrowError($"Tried to send message to unauthenticated connection: {conn.Endpoint.StringRepresentation}");
DebugConsole.ThrowError($"Tried to send message to connection of incorrect type: expected {nameof(LidgrenConnection)}, got {conn.GetType().Name}");
return;
}
if (!connectedClients.Any(cc => cc.Connection == lidgrenConnection))
{
DebugConsole.ThrowError($"Tried to send message to unauthenticated connection: {lidgrenConnection.Endpoint.StringRepresentation}");
return;
}
@@ -377,7 +378,7 @@ namespace Barotrauma.Networking
{
Buffer = bufAux
};
SendMsgInternal(conn, headers, body);
SendMsgInternal(lidgrenConnection, headers, body);
}
public override void Disconnect(NetworkConnection conn, PeerDisconnectPacket peerDisconnectPacket)
@@ -386,18 +387,21 @@ namespace Barotrauma.Networking
if (conn is not LidgrenConnection lidgrenConn) { return; }
if (connectedClients.Contains(lidgrenConn))
if (connectedClients.FindIndex(cc => cc.Connection == lidgrenConn) is >= 0 and var ccIndex)
{
lidgrenConn.Status = NetworkConnectionStatus.Disconnected;
connectedClients.Remove(lidgrenConn);
connectedClients.RemoveAt(ccIndex);
callbacks.OnDisconnect.Invoke(conn, peerDisconnectPacket);
if (conn.AccountInfo.AccountId.TryUnwrap<SteamId>(out var steamId)) { SteamManager.StopAuthSession(steamId); }
if (conn.AccountInfo.AccountId.TryUnwrap(out var accountId))
{
authenticators?.Values.ForEach(authenticator => authenticator.EndAuthSession(accountId));
}
}
lidgrenConn.NetConnection.Disconnect(peerDisconnectPacket.ToLidgrenStringRepresentation());
}
protected override void SendMsgInternal(NetworkConnection conn, PeerPacketHeaders headers, INetSerializableStruct? body)
protected override void SendMsgInternal(LidgrenConnection conn, PeerPacketHeaders headers, INetSerializableStruct? body)
{
IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.WriteNetSerializableStruct(headers);
@@ -412,75 +416,74 @@ namespace Barotrauma.Networking
protected override void CheckOwnership(PendingClient pendingClient)
{
if (OwnerConnection == null
&& pendingClient.Connection is LidgrenConnection l
&& IPAddress.IsLoopback(l.NetConnection.RemoteEndPoint.Address)
&& ownerKey.IsSome() && pendingClient.OwnerKey == ownerKey)
if (OwnerConnection != null
|| pendingClient.Connection is not LidgrenConnection l
|| !IPAddress.IsLoopback(l.NetConnection.RemoteEndPoint.Address)
|| !ownerKey.IsSome() || pendingClient.OwnerKey != ownerKey)
{
ownerKey = Option<int>.None();
OwnerConnection = pendingClient.Connection;
callbacks.OnOwnerDetermined.Invoke(OwnerConnection);
return;
}
ownerKey = Option.None;
OwnerConnection = pendingClient.Connection;
callbacks.OnOwnerDetermined.Invoke(OwnerConnection);
}
protected override void ProcessAuthTicket(ClientSteamTicketAndVersionPacket packet, PendingClient pendingClient)
private enum AuthResult
{
if (pendingClient.AccountInfo.AccountId.IsNone())
Success,
Failure
}
protected override void ProcessAuthTicket(ClientAuthTicketAndVersionPacket packet, PendingClient pendingClient)
{
if (pendingClient.AccountInfo.AccountId.IsSome())
{
if (pendingClient.AccountInfo.AccountId != packet.AccountId)
{
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
}
return;
}
void acceptClient(AccountInfo accountInfo)
{
pendingClient.Connection.SetAccountInfo(accountInfo);
pendingClient.Name = packet.Name;
pendingClient.OwnerKey = packet.OwnerKey;
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
}
void rejectClient()
{
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
}
if (authenticators is null
|| !packet.AuthTicket.TryUnwrap(out var authTicket)
|| !authenticators.TryGetValue(authTicket.Kind, out var authenticator))
{
bool requireSteamAuth = GameSettings.CurrentConfig.RequireSteamAuthentication;
#if DEBUG
requireSteamAuth = false;
DebugConsole.NewMessage($"Debug server accepts unauthenticated connections", Microsoft.Xna.Framework.Color.Yellow);
acceptClient(new AccountInfo(packet.AccountId));
#else
rejectClient();
#endif
bool hasSteamAuth = packet.SteamAuthTicket.TryUnwrap(out var ticket);
//steam auth cannot be done (SteamManager not initialized or no ticket given),
//but it's not required either -> let the client join without auth
if ((!SteamManager.IsInitialized || !hasSteamAuth) && !requireSteamAuth)
{
pendingClient.Name = packet.Name;
pendingClient.OwnerKey = packet.OwnerKey;
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
}
else
{
if (!packet.SteamId.TryUnwrap(out var id) || id is not SteamId steamId)
{
if (requireSteamAuth)
{
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.SteamAuthenticationFailed));
return;
}
}
else
{
Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
{
if (requireSteamAuth)
{
RemovePendingClient(pendingClient, PeerDisconnectPacket.SteamAuthError(authSessionStartState));
}
else
{
packet.SteamId = Option<AccountId>.None();
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
}
}
}
pendingClient.Connection.SetAccountInfo(new AccountInfo(packet.SteamId.Select(uid => (AccountId)uid)));
pendingClient.Name = packet.Name;
pendingClient.OwnerKey = packet.OwnerKey;
pendingClient.AuthSessionStarted = true;
}
return;
}
else
pendingClient.AuthSessionStarted = true;
TaskPool.Add($"{nameof(LidgrenServerPeer)}.ProcessAuth", authenticator.VerifyTicket(authTicket), t =>
{
if (pendingClient.AccountInfo.AccountId != packet.SteamId.Select(uid => (AccountId)uid))
if (!t.TryGetResult(out AccountInfo accountInfo)
|| accountInfo.IsNone)
{
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.SteamAuthenticationFailed));
rejectClient();
return;
}
}
acceptClient(accountInfo);
});
}
private NetSendResult ForwardToLidgren(IWriteMessage msg, NetworkConnection connection, DeliveryMethod deliveryMethod)

Some files were not shown because too many files have changed in this diff Show More