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 title: Bug Report
description: Found a bug? Help us squash it by making a bug report! labels: Found a bug? Help us squash it by making a bug report!
body: body:
- type: markdown - type: markdown
attributes: 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. 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: options:
- v1.2.8.0 (Winter Update hotfix 2) - v1.2.8.0 (Winter Update hotfix 2)
- v1.2.13.0 (unstable)
- v1.2.8.0 (EOS test build)
- Other - Other
validations: validations:
required: true 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(); Identifier factionId = inc.ReadIdentifier();
float minReputationToHire = 0.0f; float minReputationToHire = 0.0f;
if (factionId != default) if (!factionId.IsEmpty)
{ {
minReputationToHire = inc.ReadSingle(); minReputationToHire = inc.ReadSingle();
} }

View File

@@ -73,13 +73,16 @@ namespace Barotrauma
if (isMouseOver) if (isMouseOver)
{ {
var glowSprite = GUIStyle.UIGlowCircular.Value.Sprite; var glowSprite = GUIStyle.UIGlowCircular.Value?.Sprite;
float glowScale = 40f / glowSprite.size.X; if (glowSprite is not null)
if (isScrewed)
{ {
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; tooltip = Option.None;

View File

@@ -24,7 +24,7 @@ namespace Barotrauma
private GUIFrame? selectedWireFrame; private GUIFrame? selectedWireFrame;
private GUIListBox? componentList; private GUIListBox? componentList;
private GUITextBlock? inventoryIndicatorText; 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 Option<RectangleF> selection = Option.None;
private string searchTerm = string.Empty; 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()); DebugConsole.ThrowError("There was an error transferring mods", t2.Exception.GetInnermost());
} }
ContentPackageManager.LocalPackages.Refresh(); ContentPackageManager.LocalPackages.Refresh();
if (t2.TryGetResult(out string[] modsToEnable)) if (t2.TryGetResult(out string[]? modsToEnable))
{ {
var newRegular = ContentPackageManager.EnabledPackages.Regular.ToList(); var newRegular = ContentPackageManager.EnabledPackages.Regular.ToList();
newRegular.AddRange(ContentPackageManager.LocalPackages.Regular newRegular.AddRange(ContentPackageManager.LocalPackages.Regular

View File

@@ -9,10 +9,12 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using static Barotrauma.FabricationRecipe; using static Barotrauma.FabricationRecipe;
@@ -35,9 +37,7 @@ namespace Barotrauma
if (!allowCheats && !CheatsEnabled && IsCheat) if (!allowCheats && !CheatsEnabled && IsCheat)
{ {
NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + Names[0] + "\".", Color.Red); 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 achievements during this play session.", Color.Red);
NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red);
#endif
return; return;
} }
@@ -66,8 +66,12 @@ namespace Barotrauma
private static GUIFrame frame; private static GUIFrame frame;
private static GUIListBox listBox; private static GUIListBox listBox;
private static GUITextBox textBox; private static GUITextBox textBox;
#if DEBUG
private const int maxLength = 100000;
#else
private const int maxLength = 1000; private const int maxLength = 1000;
#endif
public static GUITextBox TextBox => textBox; public static GUITextBox TextBox => textBox;
private static readonly ChatManager chatManager = new ChatManager(true, 64); private static readonly ChatManager chatManager = new ChatManager(true, 64);
@@ -157,7 +161,7 @@ namespace Barotrauma
activeQuestionText?.SetAsLastChild(); activeQuestionText?.SetAsLastChild();
if (PlayerInput.KeyHit(Keys.F3)) if (PlayerInput.KeyHit(Keys.F3) && !PlayerInput.KeyDown(Keys.LeftControl) && !PlayerInput.KeyDown(Keys.RightControl))
{ {
Toggle(); Toggle();
} }
@@ -249,6 +253,9 @@ namespace Barotrauma
case "unbindkey": case "unbindkey":
case "wikiimage_character": case "wikiimage_character":
case "wikiimage_sub": case "wikiimage_sub":
case "eosStat":
case "eosUnlink":
case "eosLoginEpicViaSteam":
return true; return true;
default: default:
return client.HasConsoleCommandPermission(command); return client.HasConsoleCommandPermission(command);
@@ -391,6 +398,87 @@ namespace Barotrauma
private static void InitProjectSpecific() 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) => commands.Add(new Command("copyitemnames", "", (string[] args) =>
{ {
StringBuilder sb = new StringBuilder(); 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; CheatsEnabled = true;
SteamAchievementManager.CheatsEnabled = true; AchievementManager.CheatsEnabled = true;
if (GameMain.GameSession?.Campaign is CampaignMode campaign) if (GameMain.GameSession?.Campaign is CampaignMode campaign)
{ {
campaign.CheatsEnabled = true; campaign.CheatsEnabled = true;
} }
NewMessage("Enabled cheat commands.", Color.Red); NewMessage("Enabled cheat commands.", Color.Red);
#if USE_STEAM NewMessage("Achievements have been disabled during this play session.", Color.Red);
NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
#endif
})); }));
AssignRelayToServer("enablecheats", true); 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 int texDims;
private uint baseChar; private uint baseChar;
private readonly struct GlyphData public readonly record struct GlyphData(
{ int TexIndex = default,
public readonly int TexIndex; Vector2 DrawOffset = default,
public readonly Vector2 DrawOffset; float Advance = default,
public readonly float Advance; Rectangle TexCoords = default);
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 static TextManager.SpeciallyHandledCharCategory ExtractShccFromXElement(XElement element) public static TextManager.SpeciallyHandledCharCategory ExtractShccFromXElement(XElement element)
=> TextManager.SpeciallyHandledCharCategories => TextManager.SpeciallyHandledCharCategories
@@ -94,7 +80,7 @@ namespace Barotrauma
// For backwards compatibility, we assume that Cyrillic is supported by default // For backwards compatibility, we assume that Cyrillic is supported by default
TextManager.SpeciallyHandledCharCategory.Cyrillic => true, TextManager.SpeciallyHandledCharCategory.Cyrillic => true,
_ => throw new NotImplementedException($"nameof{category} not implemented.") _ => throw new NotImplementedException($"nameof{category} not implemented.")
})) }))
.Aggregate(TextManager.SpeciallyHandledCharCategory.None, (current, category) => current | category); .Aggregate(TextManager.SpeciallyHandledCharCategory.None, (current, category) => current | category);
@@ -209,8 +195,8 @@ namespace Barotrauma
if (glyphIndex == 0) if (glyphIndex == 0)
{ {
texCoords.Add(j, new GlyphData( texCoords.Add(j, new GlyphData(
advance: 0, Advance: 0,
texIndex: -1)); TexIndex: -1));
continue; continue;
} }
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal); face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
@@ -218,8 +204,8 @@ namespace Barotrauma
{ {
//glyph is empty, but char might still apply advance //glyph is empty, but char might still apply advance
GlyphData blankData = new GlyphData( GlyphData blankData = new GlyphData(
advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f), Advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f),
texIndex: -1); //indicates no texture because the glyph is empty TexIndex: -1); //indicates no texture because the glyph is empty
texCoords.Add(j, blankData); texCoords.Add(j, blankData);
continue; continue;
@@ -262,10 +248,10 @@ namespace Barotrauma
} }
GlyphData newData = new GlyphData( GlyphData newData = new GlyphData(
advance: (float)face.Glyph.Metrics.HorizontalAdvance, Advance: (float)face.Glyph.Metrics.HorizontalAdvance,
texIndex: texIndex, TexIndex: texIndex,
texCoords: new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight), TexCoords: new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight),
drawOffset: new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop) DrawOffset: new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop)
); );
texCoords.Add(j, newData); texCoords.Add(j, newData);
@@ -354,8 +340,8 @@ namespace Barotrauma
if (glyphIndex == 0) if (glyphIndex == 0)
{ {
texCoords.Add(character, new GlyphData( texCoords.Add(character, new GlyphData(
advance: 0, Advance: 0,
texIndex: -1)); TexIndex: -1));
continue; continue;
} }
@@ -365,8 +351,8 @@ namespace Barotrauma
{ {
//glyph is empty, but char might still apply advance //glyph is empty, but char might still apply advance
GlyphData blankData = new GlyphData( GlyphData blankData = new GlyphData(
advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f), Advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f),
texIndex: -1); //indicates no texture because the glyph is empty TexIndex: -1); //indicates no texture because the glyph is empty
texCoords.Add(character, blankData); texCoords.Add(character, blankData);
continue; continue;
} }
@@ -403,10 +389,10 @@ namespace Barotrauma
} }
GlyphData newData = new GlyphData( GlyphData newData = new GlyphData(
advance: (float)horizontalAdvance, Advance: (float)horizontalAdvance,
texIndex: textures.Count - 1, TexIndex: textures.Count - 1,
texCoords: new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight), TexCoords: new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight),
drawOffset: drawOffset DrawOffset: drawOffset
); );
texCoords.Add(character, newData); texCoords.Add(character, newData);
@@ -490,7 +476,7 @@ namespace Barotrauma
return gd; 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) 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; Vector2 retVal = Vector2.Zero;
retVal.Y = LineHeight; 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)) if (DynamicLoading && !texCoords.ContainsKey(c))
{ {
DynamicRenderAtlas(graphicsDevice, c); DynamicRenderAtlas(graphicsDevice, c);
} }
GlyphData gd = GetGlyphData(c); GlyphData gd = GetGlyphData(c);
retVal.X = gd.Advance; var tex = gd.TexIndex >= 0 ? textures[gd.TexIndex] : null;
return retVal; return (gd, tex);
} }
public void Dispose() public void Dispose()

View File

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

View File

@@ -168,7 +168,7 @@ namespace Barotrauma
public override bool PlaySoundOnSelect { get; set; } = true; 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) 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); 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); 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 DrawHVArea(float x, float y) => ToolBoxCore.HSVToRGB(SelectedHue, x, 1.0f - y);
private Color DrawHueArea(float x, float y) => ToolBox.HSVToRGB(y * 360f, 1f, 1f); 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 Barotrauma.IO;
using RestSharp; using RestSharp;
using System.Net; using System.Net;
using System.Collections.Immutable; using Barotrauma.Steam;
using Barotrauma.Tutorials;
namespace Barotrauma namespace Barotrauma
{ {
@@ -1174,11 +1173,14 @@ namespace Barotrauma
{ {
try try
{ {
#if USE_STEAM if (SteamManager.IsInitialized)
Steam.SteamManager.OverlayCustomUrl(url); {
#else SteamManager.OverlayCustomUrl(url);
ToolBox.OpenFileWithShell(url); }
#endif else
{
ToolBox.OpenFileWithShell(url);
}
} }
catch (Exception e) catch (Exception e)
{ {

View File

@@ -76,12 +76,12 @@ namespace Barotrauma
if (hasHeader) if (hasHeader)
{ {
InflateSize(ref estimatedSize, header, headerFont); InflateSize(ref estimatedSize, header, headerFont!);
} }
foreach (ContextMenuOption option in options) 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); optionsAndSizes.Add(option, optionSize);
} }
@@ -104,7 +104,7 @@ namespace Barotrauma
if (hasHeader) if (hasHeader)
{ {
Point sz = Point.Zero; Point sz = Point.Zero;
InflateSize(ref sz, header, headerFont); InflateSize(ref sz, header, headerFont!);
listSize.Y -= sz.Y; listSize.Y -= sz.Y;
HeaderLabel = new GUITextBlock(new RectTransform(sz, background.RectTransform), header, font: headerFont) { Padding = headerPadding }; 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) 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; HoverCursor = CursorState.Hand;
CanBeFocused = true; CanBeFocused = true;

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
using Barotrauma.Extensions; #nullable enable
using Barotrauma.Extensions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -54,8 +55,8 @@ namespace Barotrauma
public class GUIFontPrefab : GUIPrefab public class GUIFontPrefab : GUIPrefab
{ {
private readonly ContentXElement element; private readonly ContentXElement element;
private ScalableFont font; private ScalableFont? font;
public ScalableFont Font public ScalableFont? Font
{ {
get 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 (Language != GameSettings.CurrentConfig.Language) { LoadFont(); }
if (font is null) { return null; }
if (specialHandlingFonts is null) { return font; }
if (font.SpeciallyHandledCharCategory.HasFlag(category)) { return font; } if (font.SpeciallyHandledCharCategory.HasFlag(category)) { return font; }
return specialHandlingFonts.TryGetValue(category, out var resultFont) return specialHandlingFonts.TryGetValue(category, out var resultFont)
? resultFont ? resultFont
@@ -87,7 +90,7 @@ namespace Barotrauma
public void LoadFont() public void LoadFont()
{ {
string fontPath = GetFontFilePath(element); string? fontPath = GetFontFilePath(element);
uint size = GetFontSize(element); uint size = GetFontSize(element);
bool dynamicLoading = GetFontDynamicLoading(element); bool dynamicLoading = GetFontDynamicLoading(element);
var shcc = GetShcc(element); var shcc = GetShcc(element);
@@ -125,21 +128,21 @@ namespace Barotrauma
specialHandlingFonts = null; 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()) foreach (var subElement in element.Elements().Reverse())
{ {
if (subElement.NameAsIdentifier() != "override") { continue; } if (subElement.NameAsIdentifier() != "override") { continue; }
if (ScalableFont.ExtractShccFromXElement(subElement).HasFlag(flag)) 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) ScalableFont hardcodedFallback(string path)
=> new ScalableFont( => new ScalableFont(
path, path,
font.Size, font?.Size ?? 0,
GameMain.Instance.GraphicsDevice, GameMain.Instance.GraphicsDevice,
dynamicLoading: true, dynamicLoading: true,
speciallyHandledCharCategory: flag); speciallyHandledCharCategory: flag);
@@ -154,7 +157,7 @@ namespace Barotrauma
}; };
} }
private string GetFontFilePath(ContentXElement element) private string? GetFontFilePath(ContentXElement element)
{ {
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
@@ -227,20 +230,20 @@ namespace Barotrauma
{ {
public GUIFont(string identifier) : base(identifier) { } public GUIFont(string identifier) : base(identifier) { }
public bool HasValue => !Prefabs.IsEmpty; public bool HasValue => Value is not null;
public ScalableFont Value => Prefabs.ActivePrefab.Font;
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 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) => public ScalableFont? GetFontForStr(string str) =>
Prefabs.ActivePrefab.GetFontForCategory(TextManager.GetSpeciallyHandledCategories(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) 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) 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) 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) 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) 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) 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) 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) 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) public Vector2 MeasureChar(char c)
{ {
return GetFontForStr($"{c}").MeasureChar(c); return GetFontForStr($"{c}")?.MeasureChar(c) ?? Vector2.Zero;
} }
public string WrapText(string text, float width) 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) public string WrapText(string text, float width, int requestCharPos, out Vector2 requestedCharPos)
=> GetFontForStr(text).WrapText(text, width, requestCharPos, out requestedCharPos); {
requestedCharPos = default;
public string WrapText(string text, float width, out Vector2[] allCharPositions) return GetFontForStr(text)?.WrapText(text, width, requestCharPos, out requestedCharPos) ?? text;
=> GetFontForStr(text).WrapText(text, width, out allCharPositions); }
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 public class GUIColorPrefab : GUIPrefab
@@ -355,19 +370,19 @@ namespace Barotrauma
{ {
public GUISprite(string identifier) : base(identifier) { } public GUISprite(string identifier) : base(identifier) { }
public UISprite Value public UISprite? Value
{ {
get 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) 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 GUISpriteSheet(string identifier) : base(identifier) { }
public SpriteSheet Value public SpriteSheet? Value
{ {
get get
{ {
return Prefabs.ActivePrefab.SpriteSheet; return Prefabs.ActivePrefab?.SpriteSheet;
} }
} }
public int FrameCount => Value.FrameCount; public int FrameCount => Value?.FrameCount ?? 1;
public Point FrameSize => Value.FrameSize; public Point FrameSize => Value?.FrameSize ?? Point.Zero;
public void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate = 0, float scale = 1, SpriteEffects spriteEffects = SpriteEffects.None) 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) 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) 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 public class GUICursorPrefab : GUIPrefab
@@ -446,6 +461,6 @@ namespace Barotrauma
{ {
public GUICursor(string identifier) : base(identifier) { } 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(" "); return Font.MeasureString(" ");
} }
Vector2 size = Vector2.Zero; return Font.MeasureString(string.IsNullOrEmpty(text) ? " " : text);
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;
} }
protected override void SetAlpha(float a) 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) 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) 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() static partial void CreateConsentPrompt()
{ {
if (consentTextAvailable) if (ConsentTextAvailable)
{ {
var background = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas), style: "GUIBackgroundBlocker"); 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) }); 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) 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); 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) foreach (var data in mainText.RichTextData)
{ {
@@ -30,7 +35,7 @@ namespace Barotrauma
Data = data, Data = data,
OnClick = (GUITextBlock component, GUITextBlock.ClickableArea area) => 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; yield return CoroutineStatus.Success;
} }
buttonContainerSpacing(0.2f); buttonContainerSpacing(0.2f);
var noBtn = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("No")); 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 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 GameScreen GameScreen;
public static MainMenuScreen MainMenuScreen; public static MainMenuScreen MainMenuScreen;
@@ -215,6 +217,10 @@ namespace Barotrauma
public static ChatMode ActiveChatMode { get; set; } = ChatMode.Radio; public static ChatMode ActiveChatMode { get; set; } = ChatMode.Radio;
private static bool contentLoaded;
private static readonly Queue<Action> postContentLoadActions = new();
public GameMain(string[] args) public GameMain(string[] args)
{ {
Content.RootDirectory = "Content"; Content.RootDirectory = "Content";
@@ -242,11 +248,13 @@ namespace Barotrauma
GameSettings.Init(); GameSettings.Init();
CreatureMetrics.Init(); CreatureMetrics.Init();
ConsoleArguments = args; ConsoleArguments = args.ToImmutableArray();
EgsExchangeCode = EosInterface.Login.ParseEgsExchangeCode(args);
try try
{ {
ConnectCommand = ToolBox.ParseConnectCommand(ConsoleArguments); ConnectCommand = Barotrauma.Networking.ConnectCommand.Parse(ConsoleArguments);
} }
catch (IndexOutOfRangeException e) catch (IndexOutOfRangeException e)
{ {
@@ -273,6 +281,16 @@ namespace Barotrauma
Window.FileDropped += OnFileDropped; Window.FileDropped += OnFileDropped;
} }
public static void ExecuteAfterContentFinishedLoading(Action action)
{
if (contentLoaded)
{
action();
return;
}
postContentLoadActions.Enqueue(action);
}
public static void OnFileDropped(object sender, FileDropEventArgs args) public static void OnFileDropped(object sender, FileDropEventArgs args)
{ {
if (!(Screen.Selected is { } screen)) { return; } if (!(Screen.Selected is { } screen)) { return; }
@@ -419,6 +437,8 @@ namespace Barotrauma
WaitForLanguageSelection = GameSettings.CurrentConfig.Language == LanguageIdentifier.None WaitForLanguageSelection = GameSettings.CurrentConfig.Language == LanguageIdentifier.None
}; };
Eos.EosAccount.LoginPlatformSpecific();
initialLoadingThread = new Thread(Load); initialLoadingThread = new Thread(Load);
initialLoadingThread.Start(); initialLoadingThread.Start();
} }
@@ -486,6 +506,7 @@ namespace Barotrauma
TextManager.VerifyLanguageAvailable(); TextManager.VerifyLanguageAvailable();
SocialOverlay.Init();
DebugConsole.Init(); DebugConsole.Init();
ContentPackageManager.LogEnabledRegularPackageErrors(); ContentPackageManager.LogEnabledRegularPackageErrors();
@@ -517,25 +538,27 @@ namespace Barotrauma
TitleScreen.LoadState = 85.0f; TitleScreen.LoadState = 85.0f;
#if USE_STEAM
if (SteamManager.IsInitialized) if (SteamManager.IsInitialized)
{ {
Steamworks.SteamFriends.OnGameRichPresenceJoinRequested += OnInvitedToGame; Steamworks.SteamFriends.OnGameRichPresenceJoinRequested += OnInvitedToSteamGame;
Steamworks.SteamFriends.OnGameLobbyJoinRequested += OnLobbyJoinRequested; Steamworks.SteamFriends.OnGameLobbyJoinRequested += OnSteamLobbyJoinRequested;
if (SteamManager.TryGetUnlockedAchievements(out List<Steamworks.Data.Achievement> achievements)) 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 //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) //(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; IsFirstLaunch = true;
GameAnalyticsManager.AddDesignEvent("FirstLaunch"); GameAnalyticsManager.AddDesignEvent("FirstLaunch");
} }
} }
SteamManager.IncrementStat("gamelaunchcount".ToIdentifier(), 1); SteamManager.IncrementStat(AchievementStat.GameLaunchCount, 1);
} }
#endif
Eos.EosAccount.ExecuteAfterLogin(ProcessLaunchCountEos);
SubEditorScreen = new SubEditorScreen(); SubEditorScreen = new SubEditorScreen();
TestScreen = new TestScreen(); TestScreen = new TestScreen();
@@ -567,10 +590,48 @@ namespace Barotrauma
TitleScreen.LoadState = 100.0f; TitleScreen.LoadState = 100.0f;
HasLoaded = true; HasLoaded = true;
log("LOADING COROUTINE FINISHED"); log("LOADING COROUTINE FINISHED");
#if CLIENT #if CLIENT
LuaCsInstaller.CheckUpdate(); LuaCsInstaller.CheckUpdate();
#endif #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> /// <summary>
@@ -587,13 +648,13 @@ namespace Barotrauma
MainThread = null; 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 try
{ {
ConnectCommand = ToolBox.ParseConnectCommand(ToolBox.SplitCommand(connectCommand)); ConnectCommand = Barotrauma.Networking.ConnectCommand.Parse(ToolBox.SplitCommand(connectCommand));
} }
catch (IndexOutOfRangeException e) 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); SteamManager.JoinLobby(lobby.Id, true);
} }
@@ -661,6 +722,8 @@ namespace Barotrauma
PlayerInput.Update(Timing.Step); PlayerInput.Update(Timing.Step);
SocialOverlay.Instance?.Update();
if (loadingScreenOpen) if (loadingScreenOpen)
{ {
//reset accumulator if loading //reset accumulator if loading
@@ -735,16 +798,16 @@ namespace Barotrauma
} }
MainMenuScreen.Select(); 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) else if (connectCommand.NameAndP2PEndpointsOption.TryUnwrap(out var nameAndEndpoint)
&& nameAndEndpoint is { ServerName: var serverName, Endpoint: var endpoint }) && nameAndEndpoint is { ServerName: var serverName, Endpoints: var endpoints })
{ {
Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername()), Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername()),
endpoint, endpoints.Cast<Endpoint>().ToImmutableArray(),
string.IsNullOrWhiteSpace(serverName) ? endpoint.StringRepresentation : serverName, string.IsNullOrWhiteSpace(serverName) ? endpoints.First().StringRepresentation : serverName,
Option<int>.None()); Option<int>.None());
} }
@@ -753,6 +816,18 @@ namespace Barotrauma
SoundPlayer.Update((float)Timing.Step); 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) if (PlayerInput.KeyHit(Keys.Escape) && WindowActive)
{ {
// Check if a text input is selected. // Check if a text input is selected.
@@ -764,15 +839,16 @@ namespace Barotrauma
} }
GUI.KeyboardDispatcher.Subscriber = null; 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 //if a verification prompt (are you sure you want to x) is open, close it
else if (GUIMessageBox.VisibleBox as GUIMessageBox != null && else if (GUIMessageBox.VisibleBox is GUIMessageBox { UserData: "verificationprompt" })
GUIMessageBox.VisibleBox.UserData as string == "verificationprompt")
{ {
((GUIMessageBox)GUIMessageBox.VisibleBox).Close(); ((GUIMessageBox)GUIMessageBox.VisibleBox).Close();
} }
else if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary && else if (GUIMessageBox.VisibleBox?.UserData is RoundSummary { ContinueButton.Visible: true })
roundSummary.ContinueButton != null &&
roundSummary.ContinueButton.Visible)
{ {
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox); GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
} }
@@ -784,8 +860,7 @@ namespace Barotrauma
{ {
gameSession.ToggleTabMenu(); gameSession.ToggleTabMenu();
} }
else if (GUIMessageBox.VisibleBox as GUIMessageBox != null && else if (GUIMessageBox.VisibleBox is GUIMessageBox { UserData: "bugreporter" })
GUIMessageBox.VisibleBox.UserData as string == "bugreporter")
{ {
((GUIMessageBox)GUIMessageBox.VisibleBox).Close(); ((GUIMessageBox)GUIMessageBox.VisibleBox).Close();
} }
@@ -912,6 +987,7 @@ namespace Barotrauma
CoroutineManager.Update(Paused, (float)Timing.Step); CoroutineManager.Update(Paused, (float)Timing.Step);
SteamManager.Update((float)Timing.Step); SteamManager.Update((float)Timing.Step);
EosInterface.Core.Update();
TaskPool.Update(); TaskPool.Update();
@@ -1109,14 +1185,16 @@ namespace Barotrauma
public void ShowBugReporter() public void ShowBugReporter()
{ {
if (GUIMessageBox.VisibleBox != null && GUIMessageBox.VisibleBox.UserData as string == "bugreporter") if (GUIMessageBox.VisibleBox != null &&
GUIMessageBox.VisibleBox.UserData as string == "bugreporter")
{ {
return; return;
} }
var msgBox = new GUIMessageBox(TextManager.Get("bugreportbutton"), "") 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 }; 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); linkHolder.RectTransform.MaxSize = new Point(int.MaxValue, linkHolder.Rect.Height);
@@ -1129,7 +1207,7 @@ namespace Barotrauma
{ {
if (!SteamManager.OverlayCustomUrl(userdata as string)) if (!SteamManager.OverlayCustomUrl(userdata as string))
{ {
ShowOpenUrlInWebBrowserPrompt(userdata as string); ShowOpenUriPrompt(userdata as string);
} }
msgBox.Close(); msgBox.Close();
return true; return true;
@@ -1142,7 +1220,7 @@ namespace Barotrauma
UserData = "https://github.com/Regalis11/Barotrauma/issues/new/choose", UserData = "https://github.com/Regalis11/Barotrauma/issues/new/choose",
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
ShowOpenUrlInWebBrowserPrompt(userdata as string); ShowOpenUriPrompt(userdata as string);
msgBox.Close(); msgBox.Close();
return true; return true;
} }
@@ -1185,19 +1263,23 @@ namespace Barotrauma
base.OnExiting(sender, args); 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; } LocalizedString text = TextManager.GetWithVariable(promptTextTag, "[link]", url);
if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt") { return; }
LocalizedString text = TextManager.GetWithVariable("openlinkinbrowserprompt", "[link]", url);
LocalizedString extensionText = TextManager.Get(promptExtensionTag); LocalizedString extensionText = TextManager.Get(promptExtensionTag);
if (!extensionText.IsNullOrEmpty()) if (!extensionText.IsNullOrEmpty())
{ {
text += $"\n\n{extensionText}"; 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" UserData = "verificationprompt"
}; };
@@ -1215,6 +1297,7 @@ namespace Barotrauma
return true; return true;
}; };
msgBox.Buttons[1].OnClicked = msgBox.Close; msgBox.Buttons[1].OnClicked = msgBox.Close;
return msgBox;
} }
/* /*

View File

@@ -175,13 +175,11 @@ namespace Barotrauma
if (CheatsEnabled) if (CheatsEnabled)
{ {
DebugConsole.CheatsEnabled = true; DebugConsole.CheatsEnabled = true;
#if USE_STEAM if (!AchievementManager.CheatsEnabled)
if (!SteamAchievementManager.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."); 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) 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)] [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 public float TextScale
{ {
get { return textBlock == null ? 1.0f : textBlock.TextScale; } get { return textBlock == null ? 1.0f : textBlock.TextScale / BaseToRealTextScaleFactor; }
set set
{ {
if (textBlock != null) if (textBlock != null)
{ {
float prevScale = TextBlock.TextScale; 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)) if (!MathUtils.NearlyEqual(prevScale, TextBlock.TextScale))
{ {
SetScrollingText(); SetScrollingText();
@@ -210,6 +210,8 @@ namespace Barotrauma.Items.Components
SetScrollingText(); SetScrollingText();
} }
private const float BaseTextSize = 12.0f;
private float BaseToRealTextScaleFactor => BaseTextSize / GUIStyle.UnscaledSmallFont.Size;
private void RecreateTextBlock() private void RecreateTextBlock()
{ {
textBlock = new GUITextBlock(new RectTransform(item.Rect.Size), "", textBlock = new GUITextBlock(new RectTransform(item.Rect.Size), "",
@@ -217,7 +219,7 @@ namespace Barotrauma.Items.Components
{ {
TextDepth = item.SpriteDepth - 0.00001f, TextDepth = item.SpriteDepth - 0.00001f,
RoundToNearestPixel = false, RoundToNearestPixel = false,
TextScale = TextScale, TextScale = TextScale * BaseToRealTextScaleFactor,
Padding = padding * item.Scale Padding = padding * item.Scale
}; };
} }

View File

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

View File

@@ -381,7 +381,7 @@ namespace Barotrauma.Lights
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
Vector3 glowColorHSV = ToolBox.RGBToHSV(AmbientLight); Vector3 glowColorHSV = ToolBox.RGBToHSV(AmbientLight);
glowColorHSV.Z = Math.Max(glowColorHSV.Z, 0.4f); 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); Vector2 glowSpriteSize = new Vector2(gapGlowTexture.Width, gapGlowTexture.Height);
foreach (var gap in Gap.GapList) foreach (var gap in Gap.GapList)
{ {

View File

@@ -184,7 +184,8 @@ namespace Barotrauma
connection.CrackSegments.Clear(); connection.CrackSegments.Clear();
connection.CrackSegments.AddRange(MathUtils.GenerateJaggedLine( connection.CrackSegments.AddRange(MathUtils.GenerateJaggedLine(
connectionStart, connectionEnd, connectionStart, connectionEnd,
iterations, connectionLength * generationParams.ConnectionIndicatorDisplacementMultiplier)); iterations, connectionLength * generationParams.ConnectionIndicatorDisplacementMultiplier,
rng: Rand.GetRNG(Rand.RandSync.ServerAndClient)));
} }
private void LocationChanged(Location prevLocation, Location newLocation) 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 readonly LocalizedString radiationTooltip = TextManager.Get("RadiationTooltip");
private static float spriteIndex; private static float spriteIndex;
private readonly SpriteSheet sheet = GUIStyle.RadiationAnimSpriteSheet; private readonly SpriteSheet? sheet = GUIStyle.RadiationAnimSpriteSheet;
private int maxFrames => sheet.FrameCount + 1; private int maxFrames => (sheet?.FrameCount ?? 0) + 1;
private bool isHovingOver; private bool isHovingOver;
@@ -18,7 +18,7 @@ namespace Barotrauma
{ {
if (!Enabled) { return; } if (!Enabled) { return; }
UISprite uiSprite = GUIStyle.Radiation; UISprite? uiSprite = GUIStyle.Radiation;
var (offsetX, offsetY) = Map.DrawOffset * zoom; var (offsetX, offsetY) = Map.DrawOffset * zoom;
var (centerX, centerY) = container.Center.ToVector2(); var (centerX, centerY) = container.Center.ToVector2();
var (halfSizeX, halfSizeY) = new Vector2(container.Width / 2f, container.Height / 2f) * zoom; var (halfSizeX, halfSizeY) = new Vector2(container.Width / 2f, container.Height / 2f) * zoom;
@@ -29,18 +29,21 @@ namespace Barotrauma
Vector2 spriteScale = new Vector2(zoom); 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; Vector2 topRight = topLeft + Vector2.UnitX * size.X;
int index = 0; int index = 0;
for (float i = 0; i <= size.Y; i += sheet.FrameSize.Y / 2f * zoom) if (sheet != null)
{ {
bool isEven = ++index % 2 == 0; for (float i = 0; i <= size.Y; i += sheet.FrameSize.Y / 2f * zoom)
Vector2 origin = new Vector2(0.5f, 0) * sheet.FrameSize.X; {
// every other sprite's animation is reversed to make it seem more chaotic bool isEven = ++index % 2 == 0;
int sprite = (int) MathF.Floor(isEven ? spriteIndex : maxFrames - spriteIndex); Vector2 origin = new Vector2(0.5f, 0) * sheet.FrameSize.X;
sheet.Draw(spriteBatch, sprite, topRight + new Vector2(0, i), Params.RadiationBorderTint, origin, 0f, spriteScale); // 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; isHovingOver = container.Contains(PlayerInput.MousePosition) && PlayerInput.MousePosition.X < topLeft.X + size.X;

View File

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

View File

@@ -26,7 +26,9 @@ namespace Barotrauma.Networking
PrivateStart(); PrivateStart();
processInfo.Arguments += " -pipes " + writePipe.GetClientHandleAsString() + " " + readPipe.GetClientHandleAsString(); processInfo.ArgumentList.Add("-pipes");
processInfo.ArgumentList.Add(writePipe.GetClientHandleAsString());
processInfo.ArgumentList.Add(readPipe.GetClientHandleAsString());
try try
{ {
Process = Process.Start(processInfo); 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; set;
} }
private readonly Endpoint serverEndpoint; private readonly ImmutableArray<Endpoint> serverEndpoints;
private readonly Option<int> ownerKey; private readonly Option<int> ownerKey;
public bool IsServerOwner => ownerKey.IsSome(); public bool IsServerOwner => ownerKey.IsSome();
@@ -182,6 +182,9 @@ namespace Barotrauma.Networking
public readonly NamedEvent<PermissionChangedEvent> OnPermissionChanged = new NamedEvent<PermissionChangedEvent>(); public readonly NamedEvent<PermissionChangedEvent> OnPermissionChanged = new NamedEvent<PermissionChangedEvent>();
public GameClient(string newName, Endpoint endpoint, string serverName, Option<int> ownerKey) 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? //TODO: gui stuff should probably not be here?
this.ownerKey = ownerKey; this.ownerKey = ownerKey;
@@ -270,7 +273,7 @@ namespace Barotrauma.Networking
ServerSettings = new ServerSettings(this, "Server", 0, 0, 0, false, false, System.Net.IPAddress.Any); ServerSettings = new ServerSettings(this, "Server", 0, 0, 0, false, false, System.Net.IPAddress.Any);
Voting = new Voting(); Voting = new Voting();
serverEndpoint = endpoint; serverEndpoints = endpoints;
InitiateServerJoin(serverName); InitiateServerJoin(serverName);
//ServerLog = new ServerLog(""); //ServerLog = new ServerLog("");
@@ -281,7 +284,7 @@ namespace Barotrauma.Networking
public ServerInfo CreateServerInfoFromSettings() public ServerInfo CreateServerInfoFromSettings()
{ {
var serverInfo = ServerInfo.FromServerConnection(ClientPeer.ServerConnection, ServerSettings); var serverInfo = ServerInfo.FromServerEndpoints(ClientPeer.AllServerEndpoints, ServerSettings);
GameMain.ServerListScreen.UpdateOrAddServerInfo(serverInfo); GameMain.ServerListScreen.UpdateOrAddServerInfo(serverInfo);
return serverInfo; return serverInfo;
} }
@@ -327,11 +330,14 @@ namespace Barotrauma.Networking
ReadDataMessage, ReadDataMessage,
OnClientPeerDisconnect, OnClientPeerDisconnect,
OnConnectionInitializationComplete); OnConnectionInitializationComplete);
return serverEndpoint switch return serverEndpoints.First() switch
{ {
LidgrenEndpoint lidgrenEndpoint => new LidgrenClientPeer(lidgrenEndpoint, callbacks, ownerKey), LidgrenEndpoint lidgrenEndpoint
SteamP2PEndpoint _ when ownerKey.TryUnwrap(out var key) => new SteamP2POwnerPeer(callbacks, key), => new LidgrenClientPeer(lidgrenEndpoint, callbacks, ownerKey),
SteamP2PEndpoint steamP2PServerEndpoint when ownerKey.IsNone() => new SteamP2PClientPeer(steamP2PServerEndpoint, callbacks), 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() _ => throw new ArgumentOutOfRangeException()
}; };
} }
@@ -802,6 +808,9 @@ namespace Barotrauma.Networking
case ServerPacketHeader.ACHIEVEMENT: case ServerPacketHeader.ACHIEVEMENT:
ReadAchievement(inc); ReadAchievement(inc);
break; break;
case ServerPacketHeader.ACHIEVEMENT_STAT:
ReadAchievementStat(inc);
break;
case ServerPacketHeader.CHEATS_ENABLED: case ServerPacketHeader.CHEATS_ENABLED:
bool cheatsEnabled = inc.ReadBoolean(); bool cheatsEnabled = inc.ReadBoolean();
inc.ReadPadBits(); inc.ReadPadBits();
@@ -812,7 +821,7 @@ namespace Barotrauma.Networking
else else
{ {
DebugConsole.CheatsEnabled = cheatsEnabled; DebugConsole.CheatsEnabled = cheatsEnabled;
SteamAchievementManager.CheatsEnabled = cheatsEnabled; AchievementManager.CheatsEnabled = cheatsEnabled;
if (cheatsEnabled) if (cheatsEnabled)
{ {
var cheatMessageBox = new GUIMessageBox(TextManager.Get("CheatsEnabledTitle"), TextManager.Get("CheatsEnabledDescription")); var cheatMessageBox = new GUIMessageBox(TextManager.Get("CheatsEnabledTitle"), TextManager.Get("CheatsEnabledDescription"));
@@ -860,7 +869,7 @@ namespace Barotrauma.Networking
private void ReadStartGameFinalize(IReadMessage inc) private void ReadStartGameFinalize(IReadMessage inc)
{ {
TaskPool.ListTasks(); TaskPool.ListTasks(DebugConsole.Log);
ushort contentToPreloadCount = inc.ReadUInt16(); ushort contentToPreloadCount = inc.ReadUInt16();
List<ContentFile> contentToPreload = new List<ContentFile>(); List<ContentFile> contentToPreload = new List<ContentFile>();
for (int i = 0; i < contentToPreloadCount; i++) for (int i = 0; i < contentToPreloadCount; i++)
@@ -997,8 +1006,9 @@ namespace Barotrauma.Networking
} }
else else
{ {
if (ClientPeer is SteamP2PClientPeer or SteamP2POwnerPeer) if (ClientPeer is P2PClientPeer or P2POwnerPeer)
{ {
Eos.EosSessionManager.LeaveSession();
SteamManager.LeaveLobby(); SteamManager.LeaveLobby();
} }
@@ -1007,10 +1017,7 @@ namespace Barotrauma.Networking
GameMain.GameSession?.Campaign?.CancelStartRound(); GameMain.GameSession?.Campaign?.CancelStartRound();
if (SteamManager.IsInitialized) UpdatePresence("");
{
Steamworks.SteamFriends.ClearRichPresence();
}
foreach (var fileTransfer in FileReceiver.ActiveTransfers.ToArray()) foreach (var fileTransfer in FileReceiver.ActiveTransfers.ToArray())
{ {
FileReceiver.StopTransfer(fileTransfer, deleteFile: true); FileReceiver.StopTransfer(fileTransfer, deleteFile: true);
@@ -1099,19 +1106,47 @@ namespace Barotrauma.Networking
ClientPeer.ServerContentPackages = prevContentPackages; 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) if (SteamManager.IsInitialized)
{ {
Steamworks.SteamFriends.ClearRichPresence(); Steamworks.SteamFriends.ClearRichPresence();
Steamworks.SteamFriends.SetRichPresence("servername", ServerName); if (!connectCommand.IsNullOrWhiteSpace())
#warning TODO: use Steamworks localization functionality {
Steamworks.SteamFriends.SetRichPresence("status", Steamworks.SteamFriends.SetRichPresence("servername", ServerName);
TextManager.GetWithVariable("FriendPlayingOnServer", "[servername]", ServerName).Value); Steamworks.SteamFriends.SetRichPresence("status",
Steamworks.SteamFriends.SetRichPresence("connect", desc.Value);
$"-connect \"{ToolBox.EscapeCharacters(ServerName)}\" {serverEndpoint}"); Steamworks.SteamFriends.SetRichPresence("connect",
connectCommand);
}
} }
}
private void OnConnectionInitializationComplete()
{
UpdatePresence($"-connect \"{ToolBox.EscapeCharacters(ServerName)}\" {string.Join(",", serverEndpoints.Select(e => e.StringRepresentation))}");
canStart = true; canStart = true;
connected = 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(); Identifier achievementIdentifier = inc.ReadIdentifier();
int amount = inc.ReadInt32(); AchievementManager.UnlockAchievement(achievementIdentifier);
if (amount == 0) }
{
SteamAchievementManager.UnlockAchievement(achievementIdentifier); private static void ReadAchievementStat(IReadMessage inc)
} {
else var netStat = INetSerializableStruct.Read<NetIncrementedStat>(inc);
{ AchievementManager.IncrementStat(netStat.Stat, netStat.Amount);
SteamAchievementManager.IncrementStat(achievementIdentifier, amount);
}
} }
private static void ReadCircuitBoxMessage(IReadMessage inc) private static void ReadCircuitBoxMessage(IReadMessage inc)
@@ -1887,8 +1920,9 @@ namespace Barotrauma.Networking
} }
if (updateClientListId) { LastClientListUpdateID = listId; } if (updateClientListId) { LastClientListUpdateID = listId; }
if (ClientPeer is SteamP2POwnerPeer) if (ClientPeer is P2POwnerPeer)
{ {
Eos.EosSessionManager.UpdateOwnedSession(ClientPeer.ServerConnection.Endpoint, ServerSettings);
TaskPool.Add("WaitForPingDataAsync (owner)", TaskPool.Add("WaitForPingDataAsync (owner)",
Steamworks.SteamNetworkingUtils.WaitForPingDataAsync(), (task) => Steamworks.SteamNetworkingUtils.WaitForPingDataAsync(), (task) =>
{ {
@@ -2031,8 +2065,9 @@ namespace Barotrauma.Networking
ServerSettings.AllowSubVoting = allowSubVoting; ServerSettings.AllowSubVoting = allowSubVoting;
ServerSettings.AllowModeVoting = allowModeVoting; ServerSettings.AllowModeVoting = allowModeVoting;
if (ClientPeer is SteamP2POwnerPeer) if (ClientPeer is P2POwnerPeer)
{ {
Eos.EosSessionManager.UpdateOwnedSession(ClientPeer.ServerConnection.Endpoint, ServerSettings);
Steam.SteamManager.UpdateLobby(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 #nullable enable
using Barotrauma.Steam;
using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
namespace Barotrauma.Networking 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 internal abstract class ClientPeer
{ {
public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } = public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } =
@@ -25,21 +31,22 @@ namespace Barotrauma.Networking
protected readonly Callbacks callbacks; protected readonly Callbacks callbacks;
public readonly Endpoint ServerEndpoint; public readonly Endpoint ServerEndpoint;
public readonly ImmutableArray<Endpoint> AllServerEndpoints;
public NetworkConnection? ServerConnection { get; protected set; } public NetworkConnection? ServerConnection { get; protected set; }
protected readonly bool isOwner; protected bool IsOwner => ownerKey.IsSome();
protected readonly Option<int> ownerKey; protected readonly Option<int> ownerKey;
public bool IsActive => isActive; public bool IsActive => isActive;
protected bool 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; ServerEndpoint = serverEndpoint;
AllServerEndpoints = allServerEndpoints;
this.callbacks = callbacks; this.callbacks = callbacks;
this.ownerKey = ownerKey; this.ownerKey = ownerKey;
isOwner = ownerKey.IsSome();
} }
public abstract void Start(); public abstract void Start();
@@ -53,7 +60,7 @@ namespace Barotrauma.Networking
protected ConnectionInitialization initializationStep; protected ConnectionInitialization initializationStep;
public bool ContentPackageOrderReceived { get; set; } public bool ContentPackageOrderReceived { get; set; }
protected int passwordSalt; protected int passwordSalt;
protected Option<Steamworks.AuthTicket> steamAuthTicket; protected Option<AuthenticationTicket> authTicket;
private GUIMessageBox? passwordMsgBox; private GUIMessageBox? passwordMsgBox;
public bool WaitingForPassword public bool WaitingForPassword
@@ -67,43 +74,64 @@ namespace Barotrauma.Networking
public IReadMessage Message; 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) protected void ReadConnectionInitializationStep(IncomingInitializationMessage inc)
{ {
if (inc.InitializationStep != ConnectionInitialization.Password)
{
passwordMsgBox?.Close();
}
switch (inc.InitializationStep) 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, if (GameMain.Client?.ClientPeer is null) { return; }
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.SteamTicketAndVersion 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 }) var body = new ClientAuthTicketAndVersionPacket
{ {
throw new InvalidOperationException("ReadConnectionInitializationStep failed: Steam auth ticket has been cancelled."); Name = GameMain.Client.Name,
} OwnerKey = ownerKey,
AccountId = accountId,
AuthTicket = authTicket,
GameVersion = GameMain.Version.ToString(),
Language = GameSettings.CurrentConfig.Language.Value
};
ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket SendMsgInternal(headers, body);
{ });
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);
break; break;
} }
case ConnectionInitialization.ContentPackageOrder: case ConnectionInitialization.ContentPackageOrder:
{ {
if (initializationStep if (initializationStep
is ConnectionInitialization.SteamTicketAndVersion is ConnectionInitialization.AuthInfoAndVersion
or ConnectionInitialization.Password) or ConnectionInitialization.Password)
{ {
initializationStep = ConnectionInitialization.ContentPackageOrder; initializationStep = ConnectionInitialization.ContentPackageOrder;
@@ -136,7 +164,7 @@ namespace Barotrauma.Networking
break; break;
} }
case ConnectionInitialization.Password: case ConnectionInitialization.Password:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) if (initializationStep == ConnectionInitialization.AuthInfoAndVersion)
{ {
initializationStep = ConnectionInitialization.Password; initializationStep = ConnectionInitialization.Password;
} }
@@ -152,6 +180,7 @@ namespace Barotrauma.Networking
LocalizedString pwMsg = TextManager.Get("PasswordRequired"); LocalizedString pwMsg = TextManager.Get("PasswordRequired");
passwordMsgBox?.Close();
passwordMsgBox = new GUIMessageBox(pwMsg, "", new LocalizedString[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, 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))); 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); 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 #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Lidgren.Network; using Lidgren.Network;
using Barotrauma.Steam; using Barotrauma.Steam;
using System.Net.Sockets; using System.Net.Sockets;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
internal sealed class LidgrenClientPeer : ClientPeer internal sealed class LidgrenClientPeer : ClientPeer<LidgrenEndpoint>
{ {
private NetClient? netClient; private NetClient? netClient;
private readonly NetPeerConfiguration netPeerConfiguration; private readonly NetPeerConfiguration netPeerConfiguration;
private readonly List<NetIncomingMessage> incomingLidgrenMessages; private readonly List<NetIncomingMessage> incomingLidgrenMessages;
private LidgrenEndpoint lidgrenEndpoint => public LidgrenClientPeer(LidgrenEndpoint endpoint, Callbacks callbacks, Option<int> ownerKey) : base(endpoint, ((Endpoint)endpoint).ToEnumerable().ToImmutableArray(), callbacks, ownerKey)
ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result }
? result
: throw new InvalidOperationException();
public LidgrenClientPeer(LidgrenEndpoint endpoint, Callbacks callbacks, Option<int> ownerKey) : base(endpoint, callbacks, ownerKey)
{ {
ServerConnection = null; ServerConnection = null;
@@ -56,18 +55,9 @@ namespace Barotrauma.Networking
netClient = new NetClient(netPeerConfiguration); netClient = new NetClient(netPeerConfiguration);
if (SteamManager.IsInitialized) initializationStep = ConnectionInitialization.AuthInfoAndVersion;
{
steamAuthTicket = SteamManager.GetAuthSessionTicketForMultiplayer(ServerEndpoint);
if (steamAuthTicket.IsNone())
{
throw new Exception("GetAuthSessionTicket returned null");
}
}
initializationStep = ConnectionInitialization.SteamTicketAndVersion; if (ServerEndpoint is not { } lidgrenEndpointValue)
if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpointValue))
{ {
throw new InvalidCastException($"Endpoint is not {nameof(LidgrenEndpoint)}"); throw new InvalidCastException($"Endpoint is not {nameof(LidgrenEndpoint)}");
} }
@@ -79,12 +69,25 @@ namespace Barotrauma.Networking
netClient.Start(); 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) var netConnection = netClient.Connect(lidgrenEndpointValue.NetEndpoint);
{
Status = NetworkConnectionStatus.Connected ServerConnection = new LidgrenConnection(netConnection)
}; {
Status = NetworkConnectionStatus.Connected
};
});
isActive = true; isActive = true;
} }
@@ -96,7 +99,7 @@ namespace Barotrauma.Networking
ToolBox.ThrowIfNull(netClient); ToolBox.ThrowIfNull(netClient);
ToolBox.ThrowIfNull(incomingLidgrenMessages); ToolBox.ThrowIfNull(incomingLidgrenMessages);
if (isOwner && !ChildServerRelay.IsProcessAlive) if (IsOwner && !ChildServerRelay.IsProcessAlive)
{ {
var gameClient = GameMain.Client; var gameClient = GameMain.Client;
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed)); Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
@@ -112,9 +115,11 @@ namespace Barotrauma.Networking
foreach (NetIncomingMessage inc in incomingLidgrenMessages) 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; continue;
} }
@@ -152,11 +157,7 @@ namespace Barotrauma.Networking
} }
else else
{ {
if (initializationStep != ConnectionInitialization.Success) OnInitializationComplete();
{
callbacks.OnInitializationComplete.Invoke();
initializationStep = ConnectionInitialization.Success;
}
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc); var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
callbacks.OnMessageReceived.Invoke(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection)); callbacks.OnMessageReceived.Invoke(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection));
@@ -212,9 +213,6 @@ namespace Barotrauma.Networking
netClient.Shutdown(peerDisconnectPacket.ToLidgrenStringRepresentation()); netClient.Shutdown(peerDisconnectPacket.ToLidgrenStringRepresentation());
netClient = null; netClient = null;
if (steamAuthTicket.TryUnwrap(out var ticket)) { ticket.Cancel(); }
steamAuthTicket = Option.None;
callbacks.OnDisconnect.Invoke(peerDisconnectPacket); callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
} }
@@ -270,7 +268,21 @@ namespace Barotrauma.Networking
return netClient.SendMessage(msg.ToLidgren(netClient), deliveryMethod.ToLidgren()); 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 #if DEBUG
public override void ForceTimeOut() public override void ForceTimeOut()
{ {
netClient?.ServerConnection?.ForceTimeOut(); netClient?.ServerConnection?.ForceTimeOut();

View File

@@ -1,136 +1,142 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Barotrauma.Steam;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.Steam;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
internal sealed class SteamP2PClientPeer : ClientPeer internal sealed class P2PClientPeer : ClientPeer<P2PEndpoint>
{ {
private readonly SteamId hostSteamId;
private double timeout; private double timeout;
private double heartbeatTimer; private double heartbeatTimer;
private double connectionStatusTimer;
private long sentBytes, receivedBytes; private long sentBytes, receivedBytes;
private readonly List<IncomingInitializationMessage> incomingInitializationMessages = new List<IncomingInitializationMessage>(); private readonly List<IncomingInitializationMessage> incomingInitializationMessages = new List<IncomingInitializationMessage>();
private readonly List<IReadMessage> incomingDataMessages = new List<IReadMessage>(); 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; ServerConnection = null;
isActive = false; isActive = false;
if (!(ServerEndpoint is SteamP2PEndpoint steamIdEndpoint))
{
throw new InvalidCastException("endPoint is not SteamId");
}
hostSteamId = steamIdEndpoint.SteamId;
} }
public override void Start() public override void Start()
{ {
if (isActive) { return; }
ContentPackageOrderReceived = false; ContentPackageOrderReceived = false;
steamAuthTicket = SteamManager.GetAuthSessionTicketForMultiplayer(ServerEndpoint); ServerConnection = ServerEndpoint.MakeConnectionFromEndpoint();
//TODO: wait for GetAuthSessionTicketResponse_t
if (steamAuthTicket == null) var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnP2PData);
var socketCreateResult = ServerEndpoint switch
{ {
throw new Exception("GetAuthSessionTicket returned null"); EosP2PEndpoint => EosP2PSocket.Create(socketCallbacks),
} SteamP2PEndpoint steamP2PEndpoint => SteamConnectSocket.Create(steamP2PEndpoint, socketCallbacks),
_ => throw new Exception($"Invalid server endpoint: {ServerEndpoint.GetType()} {ServerEndpoint}")
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
}; };
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; timeout = NetworkConnection.TimeoutThreshold;
heartbeatTimer = 1.0; heartbeatTimer = 1.0;
connectionStatusTimer = 0.0;
isActive = true; 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); DebugConsole.AddWarning(
} "Connection from incorrect endpoint was rejected: " +
else if (initializationStep != ConnectionInitialization.Password && $"expected {ServerEndpoint}, " +
initializationStep != ConnectionInitialization.ContentPackageOrder && $"got {remoteEndpoint}");
initializationStep != ConnectionInitialization.Success)
{
DebugConsole.ThrowError("Connection from incorrect SteamID was rejected: " +
$"expected {hostSteamId}," +
$"got {new SteamId(steamId)}");
} }
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);
Close(PeerDisconnectPacket.SteamP2PError(error));
} }
private void OnP2PData(ulong steamId, byte[] data, int dataLength) private void OnP2PData(P2PEndpoint senderEndpoint, IReadMessage inc)
{ {
if (!isActive) { return; } if (!isActive) { return; }
if (steamId != hostSteamId.Value) { return; } receivedBytes += inc.LengthBytes;
if (senderEndpoint != ServerEndpoint) { return; }
timeout = Screen.Selected == GameMain.GameScreen timeout = Screen.Selected == GameMain.GameScreen
? NetworkConnection.TimeoutThresholdInGame ? NetworkConnection.TimeoutThresholdInGame
: NetworkConnection.TimeoutThreshold; : NetworkConnection.TimeoutThreshold;
try var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
{
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);
if (!packetHeader.IsServerMessage()) { return; } if (!packetHeader.IsServerMessage()) { return; }
@@ -138,9 +144,8 @@ namespace Barotrauma.Networking
{ {
if (!initialization.HasValue) { return; } 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) if (initializationStep != ConnectionInitialization.Success)
{ {
incomingInitializationMessages.Add(new IncomingInitializationMessage 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()) else if (packetHeader.IsHeartbeatMessage())
{ {
return; //TODO: implement heartbeats return; //TODO: implement heartbeats
@@ -177,41 +190,7 @@ namespace Barotrauma.Networking
heartbeatTimer -= deltaTime; heartbeatTimer -= deltaTime;
if (initializationStep != ConnectionInitialization.Password && socket?.ProcessIncomingMessages();
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;
}
}
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.ReceivedBytes, receivedBytes); GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.ReceivedBytes, receivedBytes);
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.SentBytes, sentBytes); GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.SentBytes, sentBytes);
@@ -258,8 +237,7 @@ namespace Barotrauma.Networking
analyticsTag: "NoContentPackages"); analyticsTag: "NoContentPackages");
return; return;
} }
callbacks.OnInitializationComplete.Invoke(); OnInitializationComplete();
initializationStep = ConnectionInitialization.Success;
} }
else else
{ {
@@ -287,6 +265,21 @@ namespace Barotrauma.Networking
if (!isActive) { return; } if (!isActive) { return; }
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _); 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 var headers = new PeerPacketHeaders
{ {
@@ -346,8 +339,6 @@ namespace Barotrauma.Networking
{ {
if (!isActive) { return; } if (!isActive) { return; }
SteamManager.LeaveLobby();
isActive = false; isActive = false;
var headers = new PeerPacketHeaders var headers = new PeerPacketHeaders
@@ -360,11 +351,9 @@ namespace Barotrauma.Networking
Thread.Sleep(100); Thread.Sleep(100);
Steamworks.SteamNetworking.ResetActions(); socket?.CloseConnection(ServerEndpoint);
Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId.Value); socket?.Dispose();
socket = null;
if (steamAuthTicket.TryUnwrap(out var ticket)) { ticket.Cancel(); }
steamAuthTicket = Option.None;
callbacks.OnDisconnect.Invoke(peerDisconnectPacket); callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
} }
@@ -374,33 +363,62 @@ namespace Barotrauma.Networking
IWriteMessage msgToSend = new WriteOnlyMessage(); IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.WriteNetSerializableStruct(headers); msgToSend.WriteNetSerializableStruct(headers);
body?.Write(msgToSend); 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; heartbeatTimer = 5.0;
int length = msg.LengthBytes; 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; sentBytes += length;
if (successSend) { return; } if (success) { return; }
if (deliveryMethod is DeliveryMethod.Unreliable) if (deliveryMethod is DeliveryMethod.Unreliable)
{ {
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)"); 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; sentBytes += length;
} }
if (!successSend) if (!success)
{ {
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)"); 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 #if DEBUG
public override void ForceTimeOut() public override void ForceTimeOut()
{ {
timeout = 0.0f; 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.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Barotrauma.Extensions;
using Steamworks.Data; using Steamworks.Data;
using Color = Microsoft.Xna.Framework.Color; using Color = Microsoft.Xna.Framework.Color;
using Socket = System.Net.Sockets.Socket; using Socket = System.Net.Sockets.Socket;
@@ -34,19 +35,21 @@ namespace Barotrauma.Networking
{ {
if (CoroutineManager.IsCoroutineRunning("ConnectToServer")) { return; } 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 }: case LidgrenEndpoint { NetEndpoint: var endPoint }:
GetIPAddressPing(serverInfo, endPoint, onPingDiscovered); GetIPAddressPing(serverInfo, endPoint, onPingDiscovered);
break; break;
case SteamP2PEndpoint steamP2PEndpoint: case SteamP2PEndpoint:
TaskPool.Add($"EstimateSteamLobbyPing ({steamP2PEndpoint.StringRepresentation})", TaskPool.Add($"EstimateSteamLobbyPing ({endpoint.StringRepresentation})",
EstimateSteamLobbyPing(serverInfo), EstimateSteamLobbyPing(serverInfo),
t => t =>
{ {
if (!t.TryGetResult(out Option<int> ping)) { return; } if (!t.TryGetResult(out Result<int, SteamLobbyPingError> ping)) { return; }
serverInfo.Ping = ping; serverInfo.Ping = ping.TryUnwrapSuccess(out var ms) ? Option.Some(ms) : Option.None;
onPingDiscovered(serverInfo); onPingDiscovered(serverInfo);
}); });
break; break;
@@ -99,35 +102,57 @@ namespace Barotrauma.Networking
return loadedLobby; 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)) 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); pingLocationStr = srcEos.SteamPingLocation;
if (friendLobby is null) { return Option<int>.None(); } }
lobby = friendLobby.Value; 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) if (pingLocation.HasValue && Steamworks.SteamNetworkingUtils.LocalPingLocation.HasValue)
{ {
int ping = Steamworks.SteamNetworkingUtils.LocalPingLocation.Value.EstimatePingTo(pingLocation.Value); 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 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(); } if (endPoint?.Address == null) { return Option<int>.None(); }
//don't attempt to ping if the address is IPv6 and it's not supported //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(); } 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 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)] [Serialize("", IsPropertySaveable.Yes)]
public string ServerName { get; set; } = ""; public string ServerName { get; set; } = "";
@@ -75,6 +75,8 @@ namespace Barotrauma.Networking
[Serialize("", IsPropertySaveable.Yes)] [Serialize("", IsPropertySaveable.Yes)]
public LanguageIdentifier Language { get; set; } public LanguageIdentifier Language { get; set; }
public bool EosCrossplay { get; set; }
[Serialize("", IsPropertySaveable.Yes)] [Serialize("", IsPropertySaveable.Yes)]
public string SelectedSub { get; set; } = string.Empty; public string SelectedSub { get; set; } = string.Empty;
@@ -84,49 +86,30 @@ namespace Barotrauma.Networking
public bool Checked = false; public bool Checked = false;
public readonly struct ContentPackageInfo public ImmutableArray<ServerListContentPackageInfo> ContentPackages;
{
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 int ContentPackageCount; public int ContentPackageCount;
public bool IsModded => ContentPackages.Any(p => !GameMain.VanillaContent.NameMatches(p.Name)); 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); SerializableProperties = SerializableProperty.GetProperties(this);
Endpoint = endpoint; Endpoints = endpoints;
ContentPackages = ImmutableArray<ContentPackageInfo>.Empty; 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, GameMode = GameMain.NetLobbyScreen.SelectedMode?.Identifier ?? Identifier.Empty,
GameStarted = Screen.Selected != GameMain.NetLobbyScreen, GameStarted = Screen.Selected != GameMain.NetLobbyScreen,
GameVersion = GameMain.Version, GameVersion = GameMain.Version,
PlayerCount = GameMain.Client.ConnectedClients.Count, 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, Ping = GameMain.Client.Ping,
// ------------------------------------- // -------------------------------------
@@ -225,7 +208,7 @@ namespace Barotrauma.Networking
playStyleName.RectTransform.IsFixedSize = true; playStyleName.RectTransform.IsFixedSize = true;
var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
Endpoint?.ServerTypeString ?? string.Empty, Endpoints.First().ServerTypeString,
textAlignment: Alignment.TopLeft) textAlignment: Alignment.TopLeft)
{ {
CanBeFocused = false CanBeFocused = false
@@ -460,8 +443,12 @@ namespace Barotrauma.Networking
GameVersion = version; GameVersion = version;
} }
if (int.TryParse(valueGetter("playercount"), out int playerCount)) { PlayerCount = playerCount; } 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("modeselectionmode"), out SelectionMode modeSelectionMode)) { ModeSelectionMode = modeSelectionMode; }
if (Enum.TryParse(valueGetter("subselectionmode"), out SelectionMode subSelectionMode)) { SubSelectionMode = subSelectionMode; } if (Enum.TryParse(valueGetter("subselectionmode"), out SelectionMode subSelectionMode)) { SubSelectionMode = subSelectionMode; }
HasPassword = getBool("haspassword"); HasPassword = getBool("haspassword");
@@ -471,6 +458,8 @@ namespace Barotrauma.Networking
AllowSpectating = getBool("allowspectating"); AllowSpectating = getBool("allowspectating");
AllowRespawn = getBool("allowrespawn"); AllowRespawn = getBool("allowrespawn");
VoipEnabled = getBool("voicechatenabled"); VoipEnabled = getBool("voicechatenabled");
EosCrossplay = getBool("eoscrossplay");
GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty; GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty;
if (float.TryParse(valueGetter("traitors"), NumberStyles.Any, CultureInfo.InvariantCulture, out float traitorProbability)) { TraitorProbability = traitorProbability; } if (float.TryParse(valueGetter("traitors"), NumberStyles.Any, CultureInfo.InvariantCulture, out float traitorProbability)) { TraitorProbability = traitorProbability; }
if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; } 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 //workaround to ServerRules queries truncating the values to 255 bytes
int individualPackageIndex = 0; int individualPackageIndex = 0;
string? individualPackage = valueGetter($"contentpackage{individualPackageIndex}"); string? individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
if (!individualPackage.IsNullOrEmpty()) if (!individualPackage.IsNullOrEmpty())
{ {
List<ContentPackageInfo> contentPackages = new List<ContentPackageInfo>(); List<ServerListContentPackageInfo> contentPackages = new List<ServerListContentPackageInfo>();
do do
{ {
string[] splitPackageInfo = individualPackage.Split(','); if (!ServerListContentPackageInfo.ParseSingleEntry(individualPackage).TryUnwrap(out var info))
if (splitPackageInfo.Length != 3)
{ {
DebugConsole.Log( return Array.Empty<ServerListContentPackageInfo>();
$"Error in a server's content package list: malformed content package info ({individualPackage}).");
return Array.Empty<ContentPackageInfo>();
} }
string name = splitPackageInfo[0]; contentPackages.Add(info);
string hash = splitPackageInfo[1];
ulong.TryParse(splitPackageInfo[2], out ulong id);
contentPackages.Add(new ContentPackageInfo(name, hash, Option<ContentPackageId>.Some(new SteamWorkshopId(id))));
individualPackageIndex++; individualPackageIndex++;
individualPackage = valueGetter($"contentpackage{individualPackageIndex}"); individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
@@ -518,43 +501,58 @@ namespace Barotrauma.Networking
string? joinedNames = valueGetter("contentpackage"); string? joinedNames = valueGetter("contentpackage");
string? joinedHashes = valueGetter("contentpackagehash"); string? joinedHashes = valueGetter("contentpackagehash");
string? joinedWorkshopIds = valueGetter("contentpackageid"); string? joinedUgcIds = valueGetter("contentpackageid");
string[] contentPackageNames = joinedNames.IsNullOrEmpty() ? Array.Empty<string>() : joinedNames.Split(','); var contentPackageNames = joinedNames.IsNullOrEmpty() ? Array.Empty<string>() : joinedNames.SplitEscaped(',');
string[] contentPackageHashes = joinedHashes.IsNullOrEmpty() ? Array.Empty<string>() : joinedHashes.Split(','); var contentPackageHashes = joinedHashes.IsNullOrEmpty() ? Array.Empty<string>() : joinedHashes.SplitEscaped(',');
#warning TODO: genericize var contentPackageIds = joinedUgcIds.IsNullOrEmpty() ? new string[1] { string.Empty } : joinedUgcIds.SplitEscaped(',');
ulong[] contentPackageIds = joinedWorkshopIds.IsNullOrEmpty() ? new ulong[1] : SteamManager.ParseWorkshopIds(joinedWorkshopIds).ToArray();
if (contentPackageNames.Length != contentPackageHashes.Length || contentPackageHashes.Length != contentPackageIds.Length) if (contentPackageNames.Count != contentPackageHashes.Count || contentPackageHashes.Count != contentPackageIds.Count)
{ {
DebugConsole.Log( DebugConsole.Log(
$"The number of names, hashes and Workshop IDs on server \"{serverName}\"" + $"The number of names, hashes and UGC IDs on server \"{serverName}\"" +
$" doesn't match: {contentPackageNames.Length} names ({string.Join(", ", contentPackageNames)}), {contentPackageHashes.Length} hashes, {contentPackageIds.Length} ids)"); $" doesn't match: {contentPackageNames.Count} names ({string.Join(", ", contentPackageNames)}), {contentPackageHashes.Count} hashes, {contentPackageIds.Count} ids)");
return Array.Empty<ContentPackageInfo>(); return Array.Empty<ServerListContentPackageInfo>();
} }
return contentPackageNames return contentPackageNames
.Zip(contentPackageHashes, (name, hash) => (name, hash)) .Zip(contentPackageHashes, (name, hash) => (name, hash))
.Zip(contentPackageIds, (t1, id) => .Zip(contentPackageIds, (t1, id) =>
new ContentPackageInfo( new ServerListContentPackageInfo(
t1.name, t1.name,
t1.hash, t1.hash,
Option<ContentPackageId>.Some(new SteamWorkshopId(id)))) ContentPackageId.Parse(id)))
.ToArray(); .ToArray();
} }
public static Option<ServerInfo> FromXElement(XElement element) public static Option<ServerInfo> FromXElement(XElement element)
{ {
var endpoints = new List<Endpoint>();
string endpointStr string endpointStr
= element.GetAttributeString("Endpoint", null) = element.GetAttributeString("Endpoint", null)
?? element.GetAttributeString("OwnerID", null) ?? element.GetAttributeString("OwnerID", null)
?? $"{element.GetAttributeString("IP", "")}:{element.GetAttributeInt("Port", 0)}"; ?? $"{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", ""); var gameVersionStr = element.GetAttributeString("GameVersion", "");
if (!Version.TryParse(gameVersionStr, out var gameVersion)) { gameVersion = GameMain.Version; } if (!Version.TryParse(gameVersionStr, out var gameVersion)) { gameVersion = GameMain.Version; }
var info = new ServerInfo(endpoint) var info = new ServerInfo(endpoints.ToImmutableArray())
{ {
GameVersion = gameVersion GameVersion = gameVersion
}; };
@@ -562,14 +560,14 @@ namespace Barotrauma.Networking
info.MetadataSource = DataSource.Parse(element); info.MetadataSource = DataSource.Parse(element);
return Option<ServerInfo>.Some(info); return Option.Some(info);
} }
public XElement ToXElement() public XElement ToXElement()
{ {
XElement element = new XElement(GetType().Name); 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()); element.SetAttributeValue("GameVersion", GameVersion.ToString());
SerializableProperty.SerializeProperties(this, element, saveIfDefault: true); SerializableProperty.SerializeProperties(this, element, saveIfDefault: true);
@@ -588,9 +586,9 @@ namespace Barotrauma.Networking
} }
public bool Equals(ServerInfo other) 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"; string ISerializableEntity.Name => "ServerInfo";
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; } public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; }

View File

@@ -15,7 +15,7 @@ namespace Barotrauma
this.providers = providers.ToImmutableArray(); 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; int providersFinished = 0;
void ackFinishedProvider() 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 abstract class ServerProvider
{ {
public void RetrieveServers(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted) public void RetrieveServers(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
{ {
Cancel(); Cancel();
RetrieveServersImpl(onServerDataReceived, onQueryCompleted); 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(); public abstract void Cancel();
} }
} }

View File

@@ -46,42 +46,43 @@ namespace Barotrauma
MetadataSource = Option<ServerInfo.DataSource>.Some(new DataSource((UInt16)entry.QueryPort)) 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(), TaskPool.Add($"QueryServerRules (GetServers, {entry.Name}, {entry.Address})", entry.QueryRulesAsync(),
t => t =>
{ {
if (t.Status == TaskStatus.Faulted) 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; 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 (rules is null) { return; }
if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; } if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
serverInfo.UpdateInfo(key => serverInfo.UpdateInfo(key =>
{ {
if (rules.TryGetValue(key, out var val)) { return val; } if (rules.TryGetValue(key, out var val)) { return val; }
return null; 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? //TODO: do we still want to list unresponsive servers?
if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; } if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
onServerDataReceived(serverInfo); onServerDataReceived(serverInfo, this);
} }
private Steamworks.ServerList.Internet? serverQuery; private Steamworks.ServerList.Internet? serverQuery;
private CoroutineHandle? queryCoroutine; 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) if (!SteamManager.IsInitialized)
{ {
@@ -139,7 +140,7 @@ namespace Barotrauma
CoroutineManager.StopCoroutines(selfQueryCoroutine); CoroutineManager.StopCoroutines(selfQueryCoroutine);
dequeue(); 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(); selfServerQuery.Dispose();
} }

View File

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

View File

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

View File

@@ -49,7 +49,7 @@ namespace Barotrauma
static void UnlockAchievement(string id) 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 namespace Barotrauma
{ {
class SinglePlayerCampaignSetupUI : CampaignSetupUI sealed class SinglePlayerCampaignSetupUI : CampaignSetupUI
{ {
private GUIListBox subList; private GUIListBox subList;

View File

@@ -550,7 +550,10 @@ namespace Barotrauma
width -= 16; 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; wrappedText = valueText;
} }
} }

View File

@@ -974,7 +974,7 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState);
GUI.Draw(Cam, spriteBatch); 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); 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); 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 RestSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.Common; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using Barotrauma.IO; using Barotrauma.IO;
using System.Linq; using System.Linq;
@@ -21,7 +21,7 @@ using Barotrauma.Steam;
namespace Barotrauma namespace Barotrauma
{ {
class MainMenuScreen : Screen sealed class MainMenuScreen : Screen
{ {
private enum Tab private enum Tab
{ {
@@ -33,7 +33,7 @@ namespace Barotrauma
JoinServer = 5, JoinServer = 5,
CharacterEditor = 6, CharacterEditor = 6,
SubmarineEditor = 7, SubmarineEditor = 7,
SteamWorkshop = 8, Mods = 8,
Credits = 9, Credits = 9,
Empty = 10 Empty = 10
} }
@@ -59,6 +59,8 @@ namespace Barotrauma
private GUIImage playstyleBanner; private GUIImage playstyleBanner;
private GUITextBlock playstyleDescription; private GUITextBlock playstyleDescription;
private static string RemoteContentUrl => GameSettings.CurrentConfig.RemoteMainMenuContentUrl;
private readonly GUIComponent remoteContentContainer; private readonly GUIComponent remoteContentContainer;
private XDocument remoteContentDoc; private XDocument remoteContentDoc;
@@ -76,11 +78,76 @@ namespace Barotrauma
private GUITextBlock tutorialHeader, tutorialDescription; private GUITextBlock tutorialHeader, tutorialDescription;
private GUIListBox tutorialList; private GUIListBox tutorialList;
private readonly GUITextBlock gameAnalyticsStatusText;
private readonly GUILayoutGroup leftTextFooterLayout;
private readonly GUILayoutGroup rightTextFooterLayout;
private GUIComponent versionMismatchWarning; private GUIComponent versionMismatchWarning;
#region Creation #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 += () => GameMain.Instance.ResolutionChanged += () =>
{ {
SetMenuTabPositioning(); SetMenuTabPositioning();
@@ -306,7 +373,7 @@ namespace Barotrauma
{ {
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
Enabled = true, Enabled = true,
UserData = Tab.SteamWorkshop, UserData = Tab.Mods,
OnClicked = SelectTab OnClicked = SelectTab
}; };
@@ -321,7 +388,7 @@ namespace Barotrauma
}, },
Visible = false Visible = false
}; };
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SubEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SubEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{ {
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
@@ -378,7 +445,7 @@ namespace Barotrauma
OnClicked = (button, userData) => OnClicked = (button, userData) =>
{ {
string url = TextManager.Get("EditorDisclaimerWikiUrl").Fallback("https://barotraumagame.com/wiki").Value; string url = TextManager.Get("EditorDisclaimerWikiUrl").Fallback("https://barotraumagame.com/wiki").Value;
GameMain.ShowOpenUrlInWebBrowserPrompt(url, promptExtensionTag: "wikinotice"); GameMain.ShowOpenUriPrompt(url, promptExtensionTag: "wikinotice");
return true; return true;
} }
}; };
@@ -474,39 +541,40 @@ namespace Barotrauma
menuTabs = new Dictionary<Tab, GUIFrame> 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) style: null)
{ {
CanBeFocused = false CanBeFocused = false
}, },
[Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize * new Vector2(1.0f, 1.15f), 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, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset }) [Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset })
}; };
CreateCampaignSetupUI(); CreateCampaignSetupUI();
var hostServerScale = new Vector2(0.7f, 1.2f); var hostServerScale = new Vector2(0.7f, 1.2f);
menuTabs[Tab.HostServer] = new GUIFrame(new RectTransform( 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 }); { RelativeOffset = relativeOffset });
CreateHostServerFields(); 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(); CreateTutorialTab();
this.game = game; 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 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 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); 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"); creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml");
@@ -517,6 +585,7 @@ namespace Barotrauma
}; };
SetMenuTabPositioning(); SetMenuTabPositioning();
SelectTab(Tab.Empty);
} }
private void SetMenuTabPositioning() private void SetMenuTabPositioning()
@@ -611,7 +680,7 @@ namespace Barotrauma
GameMain.LuaCs.Stop(); GameMain.LuaCs.Stop();
ResetModUpdateButton(); ResetModUpdateButton();
if (WorkshopItemsToUpdate.Any()) if (WorkshopItemsToUpdate.Any())
{ {
while (WorkshopItemsToUpdate.TryDequeue(out ulong workshopId)) while (WorkshopItemsToUpdate.TryDequeue(out ulong workshopId))
@@ -636,6 +705,8 @@ namespace Barotrauma
versionMismatchWarning.Visible = GameMain.Version < ContentPackageManager.VanillaCorePackage.GameVersion; versionMismatchWarning.Visible = GameMain.Version < ContentPackageManager.VanillaCorePackage.GameVersion;
ResetButtonStates(null); ResetButtonStates(null);
Eos.EosAccount.ExecuteAfterLogin(AchievementManager.SyncBetweenPlatforms);
} }
public override void Deselect() public override void Deselect()
@@ -751,7 +822,7 @@ namespace Barotrauma
case Tab.SubmarineEditor: case Tab.SubmarineEditor:
CoroutineManager.StartCoroutine(SelectScreenWithWaitCursor(GameMain.SubEditorScreen)); CoroutineManager.StartCoroutine(SelectScreenWithWaitCursor(GameMain.SubEditorScreen));
break; break;
case Tab.SteamWorkshop: case Tab.Mods:
var settings = SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform); var settings = SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
settings.SelectTab(SettingsMenu.Tab.Mods); settings.SelectTab(SettingsMenu.Tab.Mods);
tab = Tab.Settings; tab = Tab.Settings;
@@ -767,6 +838,14 @@ namespace Barotrauma
} }
selectedTab = tab; 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; return true;
} }
@@ -891,6 +970,17 @@ namespace Barotrauma
tutorialSkipWarning.Buttons[1].OnClicked += proceedToTab(Tab.Tutorials); 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() private void UpdateTutorialList()
{ {
foreach (GUITextBlock tutorialText in tutorialList.Content.Children) foreach (GUITextBlock tutorialText in tutorialList.Content.Children)
@@ -971,35 +1061,55 @@ namespace Barotrauma
#endif #endif
} }
string arguments = var arguments = new List<string>
"-name \"" + ToolBox.EscapeCharacters(name) + "\"" + {
" -public " + isPublicBox.Selected.ToString() + "-name", name,
" -playstyle " + ((PlayStyle)playstyleBanner.UserData).ToString() + "-public", isPublicBox.Selected.ToString(),
" -banafterwrongpassword " + wrongPasswordBanBox.Selected.ToString() + "-playstyle", ((PlayStyle)playstyleBanner.UserData).ToString(),
" -karmaenabled " + (!karmaBox.Selected).ToString() + "-banafterwrongpassword", wrongPasswordBanBox.Selected.ToString(),
" -maxplayers " + maxPlayersBox.Text + "-karmaenabled", (!karmaBox.Selected).ToString(),
$" -language \"{(LanguageIdentifier)languageDropdown.SelectedData}\""; "-maxplayers", maxPlayersBox.Text,
"-language", languageDropdown.SelectedData.ToString()
};
if (!string.IsNullOrWhiteSpace(passwordBox.Text)) if (!string.IsNullOrWhiteSpace(passwordBox.Text))
{ {
arguments += " -password \"" + ToolBox.EscapeCharacters(passwordBox.Text) + "\""; arguments.Add("-password");
arguments.Add(passwordBox.Text);
} }
else 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); int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
arguments += " -ownerkey " + ownerKey; arguments.Add("-ownerkey");
arguments.Add(ownerKey.ToString());
var processInfo = new ProcessStartInfo var processInfo = new ProcessStartInfo
{ {
FileName = fileName, FileName = fileName,
Arguments = arguments,
WorkingDirectory = Directory.GetCurrentDirectory(), WorkingDirectory = Directory.GetCurrentDirectory(),
#if !DEBUG #if !DEBUG
CreateNoWindow = true, CreateNoWindow = true,
@@ -1007,16 +1117,15 @@ namespace Barotrauma
WindowStyle = ProcessWindowStyle.Hidden WindowStyle = ProcessWindowStyle.Hidden
#endif #endif
}; };
arguments.ForEach(processInfo.ArgumentList.Add);
ChildServerRelay.Start(processInfo); ChildServerRelay.Start(processInfo);
Thread.Sleep(1000); //wait until the server is ready before connecting Thread.Sleep(1000); //wait until the server is ready before connecting
GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty( GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(
SteamManager.GetUsername().FallbackNullOrEmpty(name)), SteamManager.GetUsername().FallbackNullOrEmpty(name)),
SteamManager.GetSteamId().TryUnwrap(out var steamId) endpoints.ToImmutableArray(),
? new SteamP2PEndpoint(steamId)
: (Endpoint)new LidgrenEndpoint(IPAddress.Loopback, NetConfig.DefaultPort),
name, name,
Option<int>.Some(ownerKey)); Option.Some(ownerKey));
} }
catch (Exception e) catch (Exception e)
{ {
@@ -1030,21 +1139,6 @@ namespace Barotrauma
return true; 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() private void UpdateOutOfDateWorkshopItemCount()
{ {
if (DateTime.Now < modUpdateStatus.WhenToRefresh) { return; } if (DateTime.Now < modUpdateStatus.WhenToRefresh) { return; }
@@ -1079,16 +1173,16 @@ namespace Barotrauma
modUpdateStatus = (DateTime.Now + ModUpdateInterval, count); 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) public override void Update(double deltaTime)
{ {
#if DEBUG hostServerButton.Enabled = CanHostServer();
hostServerButton.Enabled = true;
#else gameAnalyticsStatusText.Text = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}");
if (GameSettings.CurrentConfig.UseSteamMatchmaking)
{
hostServerButton.Enabled = SteamManager.IsInitialized;
}
#endif
UpdateOutOfDateWorkshopItemCount(); UpdateOutOfDateWorkshopItemCount();
modUpdatesButton.Visible = modUpdateStatus.Count > 0; 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) public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{ {
spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);
@@ -1160,42 +1247,6 @@ namespace Barotrauma
GUI.Draw(Cam, spriteBatch); 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(); spriteBatch.End();
} }

View File

@@ -2320,12 +2320,12 @@ namespace Barotrauma
List<ContextMenuOption> options = new List<ContextMenuOption>(); 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: () => options.Add(new ContextMenuOption(accountId.ViewProfileLabel(), isEnabled: hasAccountId, onSelected: () =>
{ {
SteamManager.OverlayProfile(steamId); accountId.OpenProfile();
})); }));
} }
options.Add(new ContextMenuOption("ModerationMenu.ManagePlayer", isEnabled: true, onSelected: () => 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)) }, 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 UserData = selectedClient
}; };
viewSteamProfileButton.TextBlock.AutoScaleHorizontal = true; viewSteamProfileButton.TextBlock.AutoScaleHorizontal = true;
viewSteamProfileButton.OnClicked = (bt, userdata) => viewSteamProfileButton.OnClicked = (bt, userdata) =>
{ {
SteamManager.OverlayProfile(steamId); accountId.OpenProfile();
return true; return true;
}; };
} }

View File

@@ -7,25 +7,20 @@ namespace Barotrauma
{ {
abstract partial class Screen abstract partial class Screen
{ {
private GUIFrame frame; public readonly GUIFrame Frame;
public GUIFrame Frame
{
get
{
if (frame == null)
{
frame = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas), style: null)
{
CanBeFocused = false
};
} protected Screen()
return frame; {
} Frame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null)
{
CanBeFocused = false
};
} }
/// <summary> /// <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> /// </summary>
public virtual void AddToGUIUpdateList() public virtual void AddToGUIUpdateList()
{ {
@@ -68,9 +63,7 @@ namespace Barotrauma
public virtual void Release() public virtual void Release()
{ {
if (frame is null) { return; } Frame.RectTransform.Parent = null;
frame.RectTransform.Parent = null;
frame = null;
} }
} }
} }

View File

@@ -7,7 +7,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Steam;
namespace Barotrauma namespace Barotrauma
{ {
@@ -44,70 +46,6 @@ namespace Barotrauma
Disabled 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 public enum TabEnum
{ {
All, All,
@@ -115,7 +53,7 @@ namespace Barotrauma
Recent Recent
} }
public struct Tab public readonly struct Tab
{ {
public readonly string Storage; public readonly string Storage;
public readonly GUIButton Button; public readonly GUIButton Button;
@@ -127,7 +65,7 @@ namespace Barotrauma
{ {
Storage = storage; Storage = storage;
servers = new List<ServerInfo>(); 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") TextManager.Get($"ServerListTab.{tabEnum}"), style: "GUITabButton")
{ {
OnClicked = (_,__) => OnClicked = (_,__) =>
@@ -187,8 +125,7 @@ namespace Barotrauma
} }
} }
private readonly ServerProvider serverProvider private ServerProvider serverProvider = null;
= new CompositeServerProvider(new SteamDedicatedServerProvider(), new SteamP2PServerProvider());
public GUITextBox ClientNameBox { get; private set; } public GUITextBox ClientNameBox { get; private set; }
@@ -257,16 +194,16 @@ namespace Barotrauma
private bool sortedAscending = true; private bool sortedAscending = true;
private const float sidebarWidth = 0.2f; private const float sidebarWidth = 0.2f;
public ServerListScreen() public ServerListScreen() : base()
{ {
selectedServer = Option<ServerInfo>.None(); selectedServer = Option<ServerInfo>.None();
GameMain.Instance.ResolutionChanged += CreateUI; GameMain.Instance.ResolutionChanged += CreateUI;
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) 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 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, Padding = Vector4.Zero,
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
AutoScaleHorizontal = true 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 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 }; 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)) 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) => ClientNameBox.OnTextChanged += (textbox, text) =>
{ {
@@ -366,14 +328,6 @@ namespace Barotrauma
tabs[TabEnum.Favorites] = new Tab(TabEnum.Favorites, this, tabButtonHolder, "Data/favoriteservers.xml"); tabs[TabEnum.Favorites] = new Tab(TabEnum.Favorites, this, tabButtonHolder, "Data/favoriteservers.xml");
tabs[TabEnum.Recent] = new Tab(TabEnum.Recent, this, tabButtonHolder, "Data/recentservers.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 // Bottom row
//------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------
@@ -740,7 +694,7 @@ namespace Barotrauma
{ {
if (selectedServer.TryUnwrap(out var serverInfo)) if (selectedServer.TryUnwrap(out var serverInfo))
{ {
JoinServer(serverInfo.Endpoint, serverInfo.ServerName); JoinServer(serverInfo.Endpoints, serverInfo.ServerName);
} }
return true; return true;
}, },
@@ -778,7 +732,7 @@ namespace Barotrauma
{ {
GUIComponent existingElement = serverList.Content.FindChild(d => GUIComponent existingElement = serverList.Content.FindChild(d =>
d.UserData is ServerInfo existingServerInfo && d.UserData is ServerInfo existingServerInfo &&
existingServerInfo.Endpoint == serverInfo.Endpoint); existingServerInfo.Endpoints.Any(serverInfo.Endpoints.Contains));
if (existingElement == null) if (existingElement == null)
{ {
AddToServerList(serverInfo); AddToServerList(serverInfo);
@@ -791,7 +745,7 @@ namespace Barotrauma
public void AddToRecentServers(ServerInfo info) 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].AddOrUpdate(info);
tabs[TabEnum.Recent].Save(); tabs[TabEnum.Recent].Save();
} }
@@ -924,7 +878,32 @@ namespace Barotrauma
public override void Select() public override void Select()
{ {
base.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(); Steamworks.SteamMatchmaking.ResetActions();
selectedTab = TabEnum.All; selectedTab = TabEnum.All;
@@ -957,6 +936,7 @@ namespace Barotrauma
public override void Deselect() public override void Deselect()
{ {
base.Deselect(); base.Deselect();
serverProvider?.Cancel();
GameSettings.SaveCurrentConfig(); GameSettings.SaveCurrentConfig();
} }
@@ -964,21 +944,9 @@ namespace Barotrauma
{ {
base.Update(deltaTime); base.Update(deltaTime);
UpdateFriendsList();
panelAnimator?.Update(); panelAnimator?.Update();
scanServersButton.Enabled = (DateTime.Now - lastRefreshTime) >= AllowedRefreshInterval; 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() public void FilterServers()
@@ -1113,7 +1081,8 @@ namespace Barotrauma
RelativeSpacing = 0.05f 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)); 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))); 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)) 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)) else if (LidgrenEndpoint.ParseFromWithHostNameCheck(endpointBox.Text, tryParseHostName: true).TryUnwrap(out var lidgrenEndpoint))
{ {
JoinServer(lidgrenEndpoint, ""); JoinServer(((Endpoint)lidgrenEndpoint).ToEnumerable().ToImmutableArray(), "");
} }
else else
{ {
@@ -1171,8 +1147,6 @@ namespace Barotrauma
selectedTab = TabEnum.Favorites; selectedTab = TabEnum.Favorites;
FilterServers(); FilterServers();
#warning Interface with server providers to get up-to-date info on the given server
msgBox.Close(); msgBox.Close();
return false; 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() private void RemoveMsgFromServerList()
{ {
serverList.Content.Children serverList.Content.Children
@@ -1429,7 +1187,7 @@ namespace Barotrauma
private void RefreshServers() private void RefreshServers()
{ {
lastRefreshTime = DateTime.Now; lastRefreshTime = DateTime.Now;
serverProvider.Cancel(); serverProvider?.Cancel();
currentServerDataRecvCallbackObj = null; currentServerDataRecvCallbackObj = null;
PingUtils.QueryPingData(); PingUtils.QueryPingData();
@@ -1462,7 +1220,7 @@ namespace Barotrauma
} }
var (onServerDataReceived, onQueryCompleted) = MakeServerQueryCallbacks(); var (onServerDataReceived, onQueryCompleted) = MakeServerQueryCallbacks();
serverProvider.RetrieveServers(onServerDataReceived, onQueryCompleted); serverProvider?.RetrieveServers(onServerDataReceived, onQueryCompleted);
} }
private GUIComponent FindFrameMatchingServerInfo(ServerInfo serverInfo) private GUIComponent FindFrameMatchingServerInfo(ServerInfo serverInfo)
@@ -1474,7 +1232,7 @@ namespace Barotrauma
#if DEBUG #if DEBUG
if (serverList.Content.Children.Count(matches) > 1) 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 #endif
@@ -1482,7 +1240,7 @@ namespace Barotrauma
} }
private object currentServerDataRecvCallbackObj = null; private object currentServerDataRecvCallbackObj = null;
private (Action<ServerInfo> OnServerDataReceived, Action OnQueryCompleted) MakeServerQueryCallbacks() private (Action<ServerInfo, ServerProvider> OnServerDataReceived, Action OnQueryCompleted) MakeServerQueryCallbacks()
{ {
var uniqueObject = new object(); var uniqueObject = new object();
currentServerDataRecvCallbackObj = uniqueObject; currentServerDataRecvCallbackObj = uniqueObject;
@@ -1497,10 +1255,21 @@ namespace Barotrauma
} }
return ( return (
serverInfo => (serverInfo, serverProvider) =>
{ {
if (!shouldRunCallback()) { return; } 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) if (selectedTab == TabEnum.All)
{ {
AddToServerList(serverInfo); AddToServerList(serverInfo);
@@ -1724,7 +1493,7 @@ namespace Barotrauma
private static void ReportServer(ServerInfo info, IEnumerable<ReportReason> reasons) private static void ReportServer(ServerInfo info, IEnumerable<ReportReason> reasons)
{ {
if (!reasons.Any()) { return; } 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) private void UpdateServerInfoUI(ServerInfo serverInfo)
@@ -1784,7 +1553,7 @@ namespace Barotrauma
var serverName = new GUITextBlock(columnRT(ColumnLabel.ServerListName), var serverName = new GUITextBlock(columnRT(ColumnLabel.ServerListName),
#if DEBUG #if DEBUG
$"[{serverInfo.Endpoint.GetType().Name}] " + $"[{serverInfo.Endpoints.First().GetType().Name}] " +
#endif #endif
serverInfo.ServerName, serverInfo.ServerName,
style: "GUIServerListTextBox") { CanBeFocused = false }; style: "GUIServerListTextBox") { CanBeFocused = false };
@@ -1819,6 +1588,13 @@ namespace Barotrauma
serverPingText.Text = ping.ToString(); serverPingText.Text = ping.ToString();
serverPingText.TextColor = GetPingTextColor(ping); 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 else
{ {
serverPingText.Text = "?"; 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)) if (string.IsNullOrWhiteSpace(ClientNameBox.Text))
{ {
@@ -1967,20 +1743,38 @@ namespace Barotrauma
MultiplayerPreferences.Instance.PlayerName = ClientNameBox.Text; MultiplayerPreferences.Instance.PlayerName = ClientNameBox.Text;
GameSettings.SaveCurrentConfig(); GameSettings.SaveCurrentConfig();
#if !DEBUG if (MultiplayerPreferences.Instance.PlayerName.IsNullOrEmpty())
try
{ {
#endif TaskPool.Add("GetDefaultUserName",
GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(GetDefaultUserName()), endpoint, serverName, Option<int>.None()); GetDefaultUserName(),
#if !DEBUG 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 #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) private static Color GetPingTextColor(int ping)
{ {
if (ping < 0) { return Color.DarkRed; } if (ping < 0) { return Color.DarkRed; }
@@ -2000,8 +1794,6 @@ namespace Barotrauma
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList()
{ {
menu.AddToGUIUpdateList(); menu.AddToGUIUpdateList();
friendPopup?.AddToGUIUpdateList();
friendsDropdown?.AddToGUIUpdateList();
} }
public void StoreServerFilters() public void StoreServerFilters()

View File

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

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Barotrauma.Eos;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Steam; using Barotrauma.Steam;
@@ -14,7 +15,7 @@ using OpenAL;
namespace Barotrauma namespace Barotrauma
{ {
class SettingsMenu sealed class SettingsMenu
{ {
public static SettingsMenu? Instance { get; private set; } public static SettingsMenu? Instance { get; private set; }
@@ -682,22 +683,22 @@ namespace Barotrauma
private void CreateGameplayTab() private void CreateGameplayTab()
{ {
GUIFrame content = CreateNewContentFrame(Tab.Gameplay); GUIFrame content = CreateNewContentFrame(Tab.Gameplay);
GUILayoutGroup layout = CreateCenterLayout(content); var (left, right) = CreateSidebars(content);
var languages = TextManager.AvailableLanguages var languages = TextManager.AvailableLanguages
.OrderBy(l => TextManager.GetTranslatedLanguageName(l).ToIdentifier()) .OrderBy(l => TextManager.GetTranslatedLanguageName(l).ToIdentifier())
.ToArray(); .ToArray();
Label(layout, TextManager.Get("Language"), GUIStyle.SubHeadingFont); Label(left, TextManager.Get("Language"), GUIStyle.SubHeadingFont);
Dropdown(layout, v => TextManager.GetTranslatedLanguageName(v), null, languages, unsavedConfig.Language, v => unsavedConfig.Language = v); Dropdown(left, v => TextManager.GetTranslatedLanguageName(v), null, languages, unsavedConfig.Language, v => unsavedConfig.Language = v);
Spacer(layout); Spacer(left);
Tickbox(layout, TextManager.Get("PauseOnFocusLost"), TextManager.Get("PauseOnFocusLostTooltip"), unsavedConfig.PauseOnFocusLost, v => unsavedConfig.PauseOnFocusLost = v); Tickbox(left, TextManager.Get("PauseOnFocusLost"), TextManager.Get("PauseOnFocusLostTooltip"), unsavedConfig.PauseOnFocusLost, v => unsavedConfig.PauseOnFocusLost = v);
Spacer(layout); Spacer(left);
Tickbox(layout, TextManager.Get("DisableInGameHints"), TextManager.Get("DisableInGameHintsTooltip"), unsavedConfig.DisableInGameHints, v => unsavedConfig.DisableInGameHints = v); Tickbox(left, TextManager.Get("DisableInGameHints"), TextManager.Get("DisableInGameHintsTooltip"), unsavedConfig.DisableInGameHints, v => unsavedConfig.DisableInGameHints = v);
var resetInGameHintsButton = 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") TextManager.Get("ResetInGameHints"), style: "GUIButtonSmall")
{ {
OnClicked = (button, o) => OnClicked = (button, o) =>
@@ -715,36 +716,22 @@ namespace Barotrauma
return false; return false;
} }
}; };
Spacer(layout); Spacer(left);
Label(layout, TextManager.Get("ShowEnemyHealthBars"), GUIStyle.SubHeadingFont); Label(left, TextManager.Get("ShowEnemyHealthBars"), GUIStyle.SubHeadingFont);
DropdownEnum(layout, v => TextManager.Get($"ShowEnemyHealthBars.{v}"), null, unsavedConfig.ShowEnemyHealthBars, v => unsavedConfig.ShowEnemyHealthBars = v); DropdownEnum(left, v => TextManager.Get($"ShowEnemyHealthBars.{v}"), null, unsavedConfig.ShowEnemyHealthBars, v => unsavedConfig.ShowEnemyHealthBars = v);
Spacer(layout); 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 #if !OSX
Spacer(layout); Spacer(right);
var statisticsTickBox = new GUITickBox(NewItemRectT(layout), TextManager.Get("statisticsconsenttickbox")) var statisticsTickBox = new GUITickBox(NewItemRectT(right), TextManager.Get("statisticsconsenttickbox"))
{ {
OnSelected = tickBox => OnSelected = tickBox =>
{ {
@@ -767,7 +754,7 @@ namespace Barotrauma
void updateGATickBoxToolTip() void updateGATickBoxToolTip()
=> statisticsTickBox.ToolTip = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}"); => statisticsTickBox.ToolTip = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}");
updateGATickBoxToolTip(); updateGATickBoxToolTip();
var cachedConsent = GameAnalyticsManager.Consent.Unknown; var cachedConsent = GameAnalyticsManager.Consent.Unknown;
var statisticsTickBoxUpdater = new GUICustomComponent( var statisticsTickBoxUpdater = new GUICustomComponent(
new RectTransform(Vector2.Zero, statisticsTickBox.RectTransform), new RectTransform(Vector2.Zero, statisticsTickBox.RectTransform),
@@ -789,6 +776,37 @@ namespace Barotrauma
statisticsTickBox.Enabled &= GameAnalyticsManager.UserConsented != GameAnalyticsManager.Consent.Error; statisticsTickBox.Enabled &= GameAnalyticsManager.UserConsented != GameAnalyticsManager.Consent.Error;
}); });
#endif #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) private void CreateModsTab(out WorkshopMenu workshopMenu)
@@ -832,9 +850,9 @@ namespace Barotrauma
public void ApplyInstalledModChanges() public void ApplyInstalledModChanges()
{ {
EosSteamPrimaryLogin.HandleCrossplayChoiceChange(unsavedConfig.CrossplayChoice);
GameSettings.SetCurrentConfig(unsavedConfig); GameSettings.SetCurrentConfig(unsavedConfig);
if (WorkshopMenu is MutableWorkshopMenu mutableWorkshopMenu && if (WorkshopMenu is MutableWorkshopMenu { CurrentTab: MutableWorkshopMenu.Tab.InstalledMods } mutableWorkshopMenu)
mutableWorkshopMenu.CurrentTab == MutableWorkshopMenu.Tab.InstalledMods)
{ {
mutableWorkshopMenu.Apply(); 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.MessageEquals => CompareEquals(desc, value),
SpamServerFilterType.MessageContains => CompareContains(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.PlayerCountLarger => info.PlayerCount > parsedInt,
SpamServerFilterType.PlayerCountExact => 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 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) if (remoteContentResponse.StatusCode != HttpStatusCode.OK)
{ {
DebugConsole.AddWarning( 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 => t =>
{ {
msgBox.Close(); msgBox.Close();
if (!t.TryGetResult(out IReadOnlyList<Steamworks.Ugc.Item> items)) { return; } if (!t.TryGetResult(out IReadOnlyList<Steamworks.Ugc.Item>? items)) { return; }
InitiateDownloads(items); InitiateDownloads(items);
}); });
} }
@@ -48,7 +48,7 @@ namespace Barotrauma.Steam
t => t =>
{ {
msgBox.Close(); msgBox.Close();
if (!t.TryGetResult(out Steamworks.Ugc.Item?[] itemsNullable)) { return; } if (!t.TryGetResult(out Steamworks.Ugc.Item?[]? itemsNullable)) { return; }
var items = itemsNullable var items = itemsNullable
.Where(it => it.HasValue) .Where(it => it.HasValue)
@@ -74,7 +74,7 @@ namespace Barotrauma.Steam
.NotNone() .NotNone()
.OfType<SteamWorkshopId>() .OfType<SteamWorkshopId>()
.Select(async id => await SteamManager.Workshop.GetItem(id.Value)))) .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) public static void InitiateDownloads(IReadOnlyList<Steamworks.Ugc.Item> itemsToDownload, Action? onComplete = null)

View File

@@ -1,6 +1,6 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using System; using System;
using System.Globalization; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -26,6 +26,7 @@ namespace Barotrauma.Steam
public static void CreateLobby(ServerSettings serverSettings) public static void CreateLobby(ServerSettings serverSettings)
{ {
if (!SteamManager.IsInitialized) { return; }
if (lobbyState != LobbyState.NotConnected) { return; } if (lobbyState != LobbyState.NotConnected) { return; }
lobbyState = LobbyState.Creating; lobbyState = LobbyState.Creating;
TaskPool.Add("CreateLobbyAsync", Steamworks.SteamMatchmaking.CreateLobbyAsync(serverSettings.MaxPlayers + 10), TaskPool.Add("CreateLobbyAsync", Steamworks.SteamMatchmaking.CreateLobbyAsync(serverSettings.MaxPlayers + 10),
@@ -88,45 +89,35 @@ namespace Barotrauma.Steam
return; 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) currentLobby?.SetData("lobbyowner", GetSteamId().TryUnwrap(out var steamId)
? steamId.StringRepresentation ? steamId.StringRepresentation
: throw new InvalidOperationException("Steamworks not initialized")); : throw new InvalidOperationException("Steamworks not initialized"));
currentLobby?.SetData("haspassword", serverSettings.HasPassword.ToString());
currentLobby?.SetData("message", serverSettings.ServerMessageText); if (EosInterface.IdQueries.GetLoggedInPuids() is { Length: > 0 } puids)
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)
{ {
currentLobby?.SetData("submarine", GameMain.NetLobbyScreen.SelectedSub.Name); currentLobby?.SetData("EosEndpoint", puids[0].Value);
} }
DebugConsole.Log("Lobby updated!"); 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() public static void LeaveLobby()
{ {

View File

@@ -42,6 +42,9 @@ namespace Barotrauma.Steam
} }
Steamworks.SteamNetworkingUtils.OnDebugOutput += LogSteamworksNetworking; Steamworks.SteamNetworkingUtils.OnDebugOutput += LogSteamworksNetworking;
// Needed to detect invites for social overlay
Steamworks.SteamFriends.ListenForFriendsMessages = true;
} }
catch (DllNotFoundException) catch (DllNotFoundException)
{ {
@@ -145,10 +148,5 @@ namespace Barotrauma.Steam
Steamworks.SteamFriends.OpenWebOverlay(url); Steamworks.SteamFriends.OpenWebOverlay(url);
return true; 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); modProject.Save(stagingFileListPath);
} }
public static async Task<ContentPackage?> CreateLocalCopy(ContentPackage contentPackage) public static async Task<Option<ContentPackage>> CreateLocalCopy(ContentPackage contentPackage)
{ {
await Task.Yield(); await Task.Yield();
@@ -234,7 +234,7 @@ namespace Barotrauma.Steam
RefreshLocalMods(); RefreshLocalMods();
return ContentPackageManager.LocalPackages.FirstOrDefault(p => p.UgcId == contentPackage.UgcId); return ContentPackageManager.LocalPackages.FirstOrNone(p => p.UgcId == contentPackage.UgcId);
} }
private struct InstallWaiter private struct InstallWaiter

View File

@@ -196,9 +196,9 @@ namespace Barotrauma.Steam
SteamManager.Workshop.GetItemAsap(workshopItem.Id.Value, withLongDescription: true), SteamManager.Workshop.GetItemAsap(workshopItem.Id.Value, withLongDescription: true),
t => 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(); forceReset();
}); });

View File

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

View File

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

View File

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

View File

@@ -88,17 +88,21 @@ namespace Barotrauma.Steam
private void DeselectPublishedItem() private void DeselectPublishedItem()
{ {
var deselectCarrier = selfModsList.Parent.FindChild(c => c.UserData is ActionCarrier { Id: var id } && id == "deselect"); if (selfModsListOption.TryUnwrap(out var selfModsList))
Action? deselectAction = deselectCarrier.UserData is ActionCarrier { Action: var action } {
? action var deselectCarrier = selfModsList.Parent.FindChild(c => c.UserData is ActionCarrier { Id: var id } && id == "deselect");
: null; Action? deselectAction = deselectCarrier.UserData is ActionCarrier { Action: var action }
deselectAction?.Invoke(); ? action
: null;
deselectAction?.Invoke();
}
SelectTab(Tab.Publish); SelectTab(Tab.Publish);
} }
private static bool PackageMatchesItem(ContentPackage p, Steamworks.Ugc.Item workshopItem) private static bool PackageMatchesItem(ContentPackage p, Steamworks.Ugc.Item workshopItem)
=> p.TryExtractSteamWorkshopId(out var workshopId) && workshopId.Value == workshopItem.Id; => p.TryExtractSteamWorkshopId(out var workshopId) && workshopId.Value == workshopItem.Id;
private void PopulatePublishTab(ItemOrPackage itemOrPackage, GUIFrame parentFrame) private void PopulatePublishTab(ItemOrPackage itemOrPackage, GUIFrame parentFrame)
{ {
ContentPackageManager.LocalPackages.Refresh(); ContentPackageManager.LocalPackages.Refresh();
@@ -226,9 +230,12 @@ namespace Barotrauma.Steam
SteamManager.Workshop.GetItemAsap(workshopItem.Id.Value, withLongDescription: true), SteamManager.Workshop.GetItemAsap(workshopItem.Id.Value, withLongDescription: true),
t => 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(); descriptionTextBox.Deselect();
}); });
} }
@@ -296,7 +303,7 @@ namespace Barotrauma.Steam
var fileInfoLabel = Label(rightBottom, "", GUIStyle.Font, heightScale: 1.0f); var fileInfoLabel = Label(rightBottom, "", GUIStyle.Font, heightScale: 1.0f);
fileInfoLabel.TextAlignment = Alignment.CenterRight; 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); 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") }); buttons: new[] { TextManager.Get("Yes"), TextManager.Get("No") });
confirmDeletion.Buttons[0].OnClicked = (yesBuffer, o1) => 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 => t =>
{ {
SteamManager.Workshop.Uninstall(workshopItem); SteamManager.Workshop.Uninstall(workshopItem);
@@ -452,7 +459,7 @@ namespace Barotrauma.Steam
} }
bool localCopyMade = false; bool localCopyMade = false;
TaskPool.Add($"Create local copy {workshopItem.Title}", TaskPool.AddWithResult($"Create local copy {workshopItem.Title}",
SteamManager.Workshop.CreateLocalCopy(workshopCopy), SteamManager.Workshop.CreateLocalCopy(workshopCopy),
(t) => (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; public override bool Loaded => nestedStr.Loaded;
protected override bool MustRetrieveValue() 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() 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; cachedFont = font.Value;
UpdateLanguage(); UpdateLanguage();
} }

View File

@@ -19,7 +19,10 @@ namespace Barotrauma
public override bool Loaded => nestedStr.Loaded; public override bool Loaded => nestedStr.Loaded;
public override void RetrieveValue() 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(); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -451,28 +452,6 @@ namespace Barotrauma
public static string WrapText(string text, float lineLength, ScalableFont font, float textScale = 1.0f) public static string WrapText(string text, float lineLength, ScalableFont font, float textScale = 1.0f)
=> font.WrapText(text, lineLength / textScale); => 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) public static bool VersionNewerIgnoreRevision(Version a, Version b)
{ {
if (b.Major > a.Major) { return true; } if (b.Major > a.Major) { return true; }

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@@ -6,49 +6,49 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>1.2.8.0</Version> <Version>1.3.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright> <Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon> <ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations> <Configurations>Debug;Release;Unstable</Configurations>
<InvariantGlobalization>true</InvariantGlobalization> <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>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE;SERVER;LINUX;USE_STEAM</DefineConstants> <DefineConstants>DEBUG;TRACE;SERVER;LINUX</DefineConstants>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath> <OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <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> <PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath> <OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;SERVER;LINUX;USE_STEAM</DefineConstants> <DefineConstants>TRACE;SERVER;LINUX</DefineConstants>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath> <OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|AnyCPU'">
<DefineConstants>TRACE;SERVER;LINUX;USE_STEAM</DefineConstants> <DefineConstants>TRACE;SERVER;LINUX</DefineConstants>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath> <OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
<Optimize>true</Optimize> <Optimize>true</Optimize>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DefineConstants>TRACE;SERVER;LINUX;X64;USE_STEAM</DefineConstants> <DefineConstants>TRACE;SERVER;LINUX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath> <OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|x64'">
<DefineConstants>TRACE;SERVER;LINUX;X64;USE_STEAM</DefineConstants> <DefineConstants>TRACE;SERVER;LINUX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath> <OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
<Optimize>true</Optimize> <Optimize>true</Optimize>
@@ -79,6 +79,11 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="RestSharp" Version="106.13.0" /> <PackageReference Include="RestSharp" Version="106.13.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\BarotraumaCore\BarotraumaCore.csproj" />
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\EosInterface\EosInterface.csproj" />
</ItemGroup>
<!-- Sourced from https://stackoverflow.com/a/45248069 --> <!-- Sourced from https://stackoverflow.com/a/45248069 -->
<Target Name="GetGitRevision" BeforeTargets="WriteGitRevision" Condition="'$(BuildHash)' == ''"> <Target Name="GetGitRevision" BeforeTargets="WriteGitRevision" Condition="'$(BuildHash)' == ''">
@@ -144,9 +149,14 @@
<WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" /> <WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
</Target> </Target>
<PropertyGroup>
<ManualDeployRuntime>linux-x64</ManualDeployRuntime>
<ProjectFileNamePlatformSuffix>Linux</ProjectFileNamePlatformSuffix>
</PropertyGroup>
<Import Project="../BarotraumaShared/DeployEosPrivate.props" />
<Import Project="../BarotraumaShared/Luatrauma.props" /> <Import Project="../BarotraumaShared/Luatrauma.props" />
<ItemGroup> <ItemGroup>
<None Include="../BarotraumaShared/Luatrauma.props" /> <None Include="../BarotraumaShared/Luatrauma.props" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -6,18 +6,18 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>1.2.8.0</Version> <Version>1.3.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright> <Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon> <ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations> <Configurations>Debug;Release;Unstable</Configurations>
<InvariantGlobalization>true</InvariantGlobalization> <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>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <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> <PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\DebugMac</OutputPath> <OutputPath>..\bin\DebugMac</OutputPath>
<ConsolePause>true</ConsolePause> <ConsolePause>true</ConsolePause>
@@ -25,20 +25,20 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <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> <PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Mac\</OutputPath> <OutputPath>..\bin\$(Configuration)Mac\</OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <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> <PlatformTarget>x64</PlatformTarget>
<DebugType></DebugType> <DebugType></DebugType>
<OutputPath>..\bin\ReleaseMac</OutputPath> <OutputPath>..\bin\ReleaseMac</OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|AnyCPU'"> <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> <PlatformTarget>x64</PlatformTarget>
<DebugType /> <DebugType />
<OutputPath>..\bin\ReleaseMac</OutputPath> <OutputPath>..\bin\ReleaseMac</OutputPath>
@@ -46,13 +46,13 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DefineConstants>TRACE;SERVER;OSX;X64;USE_STEAM</DefineConstants> <DefineConstants>TRACE;SERVER;OSX;X64</DefineConstants>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Mac\</OutputPath> <OutputPath>..\bin\$(Configuration)Mac\</OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unstable|x64'"> <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> <PlatformTarget>x64</PlatformTarget>
<OutputPath>..\bin\$(Configuration)Mac\</OutputPath> <OutputPath>..\bin\$(Configuration)Mac\</OutputPath>
<Optimize>true</Optimize> <Optimize>true</Optimize>
@@ -85,7 +85,11 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="RestSharp" Version="106.13.0" /> <PackageReference Include="RestSharp" Version="106.13.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\BarotraumaCore\BarotraumaCore.csproj" />
<ProjectReference Include="..\..\Libraries\BarotraumaLibs\EosInterface\EosInterface.csproj" />
</ItemGroup>
<!-- Sourced from https://stackoverflow.com/a/45248069 --> <!-- Sourced from https://stackoverflow.com/a/45248069 -->
<Target Name="GetGitRevision" BeforeTargets="WriteGitRevision" Condition="'$(BuildHash)' == ''"> <Target Name="GetGitRevision" BeforeTargets="WriteGitRevision" Condition="'$(BuildHash)' == ''">
<PropertyGroup> <PropertyGroup>
@@ -150,9 +154,14 @@
<WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" /> <WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
</Target> </Target>
<PropertyGroup>
<ManualDeployRuntime>osx-x64</ManualDeployRuntime>
<ProjectFileNamePlatformSuffix>MacOS</ProjectFileNamePlatformSuffix>
</PropertyGroup>
<Import Project="../BarotraumaShared/DeployEosPrivate.props" />
<Import Project="../BarotraumaShared/Luatrauma.props" /> <Import Project="../BarotraumaShared/Luatrauma.props" />
<ItemGroup> <ItemGroup>
<None Include="../BarotraumaShared/Luatrauma.props" /> <None Include="../BarotraumaShared/Luatrauma.props" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -72,7 +72,7 @@ namespace Barotrauma
msg.WriteString(ragdollFileName); msg.WriteString(ragdollFileName);
msg.WriteIdentifier(HumanPrefabIds.NpcIdentifier); msg.WriteIdentifier(HumanPrefabIds.NpcIdentifier);
msg.WriteIdentifier(MinReputationToHire.factionId); msg.WriteIdentifier(MinReputationToHire.factionId);
if (MinReputationToHire.factionId != default) if (!MinReputationToHire.factionId.IsEmpty)
{ {
msg.WriteSingle(MinReputationToHire.reputation); 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); 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); 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); 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); GameMain.Server.SendConsoleMessage("Enabling cheats will disable Steam achievements during this play session.", client, Color.Red);
#endif
return; 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) => commands.Add(new Command("enablecheats", "enablecheats: Enables cheat commands and disables Steam achievements during this play session.", (string[] args) =>
{ {
CheatsEnabled = true; CheatsEnabled = true;
SteamAchievementManager.CheatsEnabled = true; AchievementManager.CheatsEnabled = true;
NewMessage("Enabled cheat commands.", Color.Red); NewMessage("Enabled cheat commands.", Color.Red);
#if USE_STEAM
NewMessage("Steam achievements have been disabled during this play session.", Color.Red); NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
#endif
GameMain.Server?.UpdateCheatsEnabled(); GameMain.Server?.UpdateCheatsEnabled();
})); }));
AssignOnClientRequestExecute("enablecheats", (client, cursorPos, args) => AssignOnClientRequestExecute("enablecheats", (client, cursorPos, args) =>
{ {
CheatsEnabled = true; CheatsEnabled = true;
SteamAchievementManager.CheatsEnabled = true; AchievementManager.CheatsEnabled = true;
NewMessage("Cheat commands have been enabled by \"" + client.Name + "\".", Color.Red); 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); NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
#endif
GameMain.Server?.UpdateCheatsEnabled(); GameMain.Server?.UpdateCheatsEnabled();
}); });

View File

@@ -20,7 +20,7 @@ partial class EventLogAction : EventAction
if (target is Character character) if (target is Character character)
{ {
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character); var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
if (ownerClient != null && eventLog != null) if (ownerClient != null)
{ {
targetClients.Add(ownerClient); targetClients.Add(ownerClient);
} }
@@ -38,7 +38,7 @@ partial class EventLogAction : EventAction
} }
else 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); Log(targetClients: null);
} }

View File

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

View File

@@ -174,6 +174,22 @@ namespace Barotrauma.Networking
return bannedPlayer != null; 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) public void BanPlayer(string name, Endpoint endpoint, string reason, TimeSpan? duration)
=> BanPlayer(name, endpoint.Address, reason, duration); => BanPlayer(name, endpoint.Address, reason, duration);
@@ -305,7 +321,7 @@ namespace Barotrauma.Networking
else else
{ {
outMsg.WriteBoolean(false); outMsg.WritePadBits(); 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 DateTime refreshMasterTimer;
private readonly TimeSpan refreshMasterInterval = new TimeSpan(0, 0, 60); private readonly TimeSpan refreshMasterInterval = new TimeSpan(0, 0, 60);
private bool registeredToMaster; private bool registeredToSteamMaster;
private DateTime roundStartTime; private DateTime roundStartTime;
@@ -123,7 +123,7 @@ namespace Barotrauma.Networking
public NetworkConnection OwnerConnection { get; private set; } public NetworkConnection OwnerConnection { get; private set; }
private readonly Option<int> ownerKey; private readonly Option<int> ownerKey;
private readonly Option<SteamId> ownerSteamId; private readonly Option<P2PEndpoint> ownerEndpoint;
public GameServer( public GameServer(
string name, string name,
@@ -135,7 +135,7 @@ namespace Barotrauma.Networking
bool attemptUPnP, bool attemptUPnP,
int maxPlayers, int maxPlayers,
Option<int> ownerKey, Option<int> ownerKey,
Option<SteamId> ownerSteamId) Option<P2PEndpoint> ownerEndpoint)
{ {
if (name.Length > NetConfig.ServerNameMaxLength) if (name.Length > NetConfig.ServerNameMaxLength)
{ {
@@ -153,7 +153,7 @@ namespace Barotrauma.Networking
this.ownerKey = ownerKey; this.ownerKey = ownerKey;
this.ownerSteamId = ownerSteamId; this.ownerEndpoint = ownerEndpoint;
entityEventManager = new ServerEntityEventManager(this); entityEventManager = new ServerEntityEventManager(this);
} }
@@ -168,16 +168,18 @@ namespace Barotrauma.Networking
OnInitializationComplete, OnInitializationComplete,
GameMain.Instance.CloseServer, GameMain.Instance.CloseServer,
OnOwnerDetermined); OnOwnerDetermined);
if (ownerSteamId.TryUnwrap(out var steamId)) if (ownerEndpoint.TryUnwrap(out var endpoint))
{ {
Log("Using SteamP2P networking.", ServerLog.MessageType.ServerMessage); Log("Using P2P networking.", ServerLog.MessageType.ServerMessage);
serverPeer = new SteamP2PServerPeer(steamId, ownerKey.Fallback(0), ServerSettings, callbacks); serverPeer = new P2PServerPeer(endpoint, ownerKey.Fallback(0), ServerSettings, callbacks);
} }
else 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); serverPeer = new LidgrenServerPeer(ownerKey, ServerSettings, callbacks);
registeredToSteamMaster = SteamManager.CreateServer(this, ServerSettings.IsPublic);
Eos.EosSessionManager.UpdateOwnedSession(Option.None, ServerSettings);
} }
FileSender = new FileSender(serverPeer, MsgConstants.MTU); FileSender = new FileSender(serverPeer, MsgConstants.MTU);
@@ -190,15 +192,7 @@ namespace Barotrauma.Networking
VoipServer = new VoipServer(serverPeer); VoipServer = new VoipServer(serverPeer);
if (serverPeer is LidgrenServerPeer)
{
#if USE_STEAM
registeredToMaster = SteamManager.CreateServer(this, ServerSettings.IsPublic);
#endif
}
GameMain.LuaCs.Initialize(); GameMain.LuaCs.Initialize();
Log("Server started", ServerLog.MessageType.ServerMessage); Log("Server started", ServerLog.MessageType.ServerMessage);
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
@@ -658,20 +652,23 @@ namespace Barotrauma.Networking
updateTimer = DateTime.Now + UpdateInterval; 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); bool refreshSuccessful = SteamManager.RefreshServerDetails(this);
if (GameSettings.CurrentConfig.VerboseLogging) if (GameSettings.CurrentConfig.VerboseLogging)
{ {
Log(refreshSuccessful ? Log(refreshSuccessful ?
"Refreshed server info on the server list." : "Refreshed server info on the Steam server list." :
"Refreshing server info on the server list failed.", ServerLog.MessageType.ServerMessage); "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; ServerSettings.ServerDetailsChanged = false;
refreshMasterTimer = DateTime.Now + refreshMasterInterval;
} }
} }
@@ -3082,8 +3079,6 @@ namespace Barotrauma.Networking
client.WaitForNextRoundRespawn = null; client.WaitForNextRoundRespawn = null;
client.InGame = false; client.InGame = false;
if (client.AccountId.TryUnwrap<SteamId>(out var steamId)) { SteamManager.StopAuthSession(steamId); }
var previousPlayer = previousPlayers.Find(p => p.MatchesClient(client)); var previousPlayer = previousPlayers.Find(p => p.MatchesClient(client));
if (previousPlayer == null) if (previousPlayer == null)
{ {
@@ -3416,7 +3411,7 @@ namespace Barotrauma.Networking
msg.WriteByte((byte)ServerPacketHeader.FILE_TRANSFER); msg.WriteByte((byte)ServerPacketHeader.FILE_TRANSFER);
msg.WriteByte((byte)FileTransferMessageType.Cancel); msg.WriteByte((byte)FileTransferMessageType.Cancel);
msg.WriteByte((byte)transfer.ID); 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) 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) foreach (Client client in connectedClients)
{ {
if (client.Character == character) if (client.Character == character)
{ {
IncrementStat(client, achievementIdentifier, amount); IncrementStat(client, stat, amount);
return; return;
} }
} }
@@ -3622,19 +3617,17 @@ namespace Barotrauma.Networking
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ServerPacketHeader.ACHIEVEMENT); msg.WriteByte((byte)ServerPacketHeader.ACHIEVEMENT);
msg.WriteIdentifier(achievementIdentifier); msg.WriteIdentifier(achievementIdentifier);
msg.WriteInt32(0);
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); 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(); IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ServerPacketHeader.ACHIEVEMENT); msg.WriteByte((byte)ServerPacketHeader.ACHIEVEMENT_STAT);
msg.WriteIdentifier(achievementIdentifier);
msg.WriteInt32(amount); INetSerializableStruct incrementedStat = new NetIncrementedStat(stat, amount);
incrementedStat.Write(msg);
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
} }
@@ -3642,7 +3635,7 @@ namespace Barotrauma.Networking
public void SendTraitorMessage(WriteOnlyMessage msg, Client client) public void SendTraitorMessage(WriteOnlyMessage msg, Client client)
{ {
if (client == null) { return; }; if (client == null) { return; };
serverPeer.Send(msg, client.Connection, DeliveryMethod.ReliableOrdered); serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
} }
public void UpdateCheatsEnabled() public void UpdateCheatsEnabled()

View File

@@ -1,24 +1,26 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Net; using System.Net;
using System.Linq; using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Steam; using Barotrauma.Steam;
using Lidgren.Network; using Lidgren.Network;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
internal sealed class LidgrenServerPeer : ServerPeer internal sealed class LidgrenServerPeer : ServerPeer<LidgrenConnection>
{ {
private readonly NetPeerConfiguration netPeerConfiguration; private readonly NetPeerConfiguration netPeerConfiguration;
private ImmutableDictionary<AuthenticationTicketKind, Authenticator>? authenticators;
private NetServer? netServer; private NetServer? netServer;
private readonly List<NetIncomingMessage> incomingLidgrenMessages; 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; netServer = null;
netPeerConfiguration = new NetPeerConfiguration("barotrauma") netPeerConfiguration = new NetPeerConfiguration("barotrauma")
@@ -42,9 +44,6 @@ namespace Barotrauma.Networking
netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval); netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
connectedClients = new List<NetworkConnection>();
pendingClients = new List<PendingClient>();
incomingLidgrenMessages = new List<NetIncomingMessage>(); incomingLidgrenMessages = new List<NetIncomingMessage>();
ownerKey = ownKey; ownerKey = ownKey;
@@ -54,6 +53,8 @@ namespace Barotrauma.Networking
{ {
if (netServer != null) { return; } if (netServer != null) { return; }
authenticators = Authenticator.GetAuthenticatorsForHost(Option.None);
incomingLidgrenMessages.Clear(); incomingLidgrenMessages.Clear();
netServer = new NetServer(netPeerConfiguration); netServer = new NetServer(netPeerConfiguration);
@@ -81,7 +82,7 @@ namespace Barotrauma.Networking
for (int i = connectedClients.Count - 1; i >= 0; i--) 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()); netServer.Shutdown(PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown).ToLidgrenStringRepresentation());
@@ -91,8 +92,6 @@ namespace Barotrauma.Networking
netServer = null; netServer = null;
Steamworks.SteamServer.OnValidateAuthTicketResponse -= OnAuthChange;
callbacks.OnShutdown.Invoke(); callbacks.OnShutdown.Invoke();
} }
@@ -166,9 +165,10 @@ namespace Barotrauma.Networking
ToolBox.ThrowIfNull(netPeerConfiguration); ToolBox.ThrowIfNull(netPeerConfiguration);
netServer.UPnP.ForwardPort(netPeerConfiguration.Port, "barotrauma"); netServer.UPnP.ForwardPort(netPeerConfiguration.Port, "barotrauma");
#if USE_STEAM if (SteamManager.IsInitialized)
netServer.UPnP.ForwardPort(serverSettings.QueryPort, "barotrauma"); {
#endif netServer.UPnP.ForwardPort(serverSettings.QueryPort, "barotrauma");
}
} }
private bool DiscoveringUPnP() private bool DiscoveringUPnP()
@@ -209,7 +209,7 @@ namespace Barotrauma.Networking
return; 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) if (pendingClient is null)
{ {
@@ -224,7 +224,7 @@ namespace Barotrauma.Networking
{ {
if (netServer == null) { return; } 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(); IReadMessage inc = lidgrenMsg.ToReadMessage();
@@ -236,7 +236,7 @@ namespace Barotrauma.Networking
} }
else if (!packetHeader.IsConnectionInitializationStep()) 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) if (pendingClient != null)
{ {
@@ -273,7 +273,7 @@ namespace Barotrauma.Networking
switch (inc.SenderConnection.Status) switch (inc.SenderConnection.Status)
{ {
case NetConnectionStatus.Disconnected: 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 != null)
{ {
if (conn == OwnerConnection) if (conn == OwnerConnection)
@@ -300,12 +300,7 @@ namespace Barotrauma.Networking
} }
} }
public override void InitializeSteamServerCallbacks() private void OnSteamAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
{
Steamworks.SteamServer.OnValidateAuthTicketResponse += OnAuthChange;
}
private void OnAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
{ {
if (netServer == null) { return; } if (netServer == null) { return; }
@@ -317,8 +312,8 @@ namespace Barotrauma.Networking
if (status == Steamworks.AuthResponse.OK) { return; } if (status == Steamworks.AuthResponse.OK) { return; }
if (connectedClients.Find(c if (connectedClients.Find(c
=> c.AccountInfo.AccountId.TryUnwrap<SteamId>(out var id) && id.Value == steamId) => c.Connection.AccountInfo.AccountId.TryUnwrap<SteamId>(out var id) && id.Value == steamId)
is LidgrenConnection connection) is { Connection: LidgrenConnection connection })
{ {
Disconnect(connection, PeerDisconnectPacket.SteamAuthError(status)); Disconnect(connection, PeerDisconnectPacket.SteamAuthError(status));
} }
@@ -351,9 +346,15 @@ namespace Barotrauma.Networking
{ {
if (netServer == null) { return; } 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; return;
} }
@@ -377,7 +378,7 @@ namespace Barotrauma.Networking
{ {
Buffer = bufAux Buffer = bufAux
}; };
SendMsgInternal(conn, headers, body); SendMsgInternal(lidgrenConnection, headers, body);
} }
public override void Disconnect(NetworkConnection conn, PeerDisconnectPacket peerDisconnectPacket) public override void Disconnect(NetworkConnection conn, PeerDisconnectPacket peerDisconnectPacket)
@@ -386,18 +387,21 @@ namespace Barotrauma.Networking
if (conn is not LidgrenConnection lidgrenConn) { return; } 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; lidgrenConn.Status = NetworkConnectionStatus.Disconnected;
connectedClients.Remove(lidgrenConn); connectedClients.RemoveAt(ccIndex);
callbacks.OnDisconnect.Invoke(conn, peerDisconnectPacket); 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()); 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(); IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.WriteNetSerializableStruct(headers); msgToSend.WriteNetSerializableStruct(headers);
@@ -412,75 +416,74 @@ namespace Barotrauma.Networking
protected override void CheckOwnership(PendingClient pendingClient) protected override void CheckOwnership(PendingClient pendingClient)
{ {
if (OwnerConnection == null if (OwnerConnection != null
&& pendingClient.Connection is LidgrenConnection l || pendingClient.Connection is not LidgrenConnection l
&& IPAddress.IsLoopback(l.NetConnection.RemoteEndPoint.Address) || !IPAddress.IsLoopback(l.NetConnection.RemoteEndPoint.Address)
&& ownerKey.IsSome() && pendingClient.OwnerKey == ownerKey) || !ownerKey.IsSome() || pendingClient.OwnerKey != ownerKey)
{ {
ownerKey = Option<int>.None(); return;
OwnerConnection = pendingClient.Connection;
callbacks.OnOwnerDetermined.Invoke(OwnerConnection);
} }
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 #if DEBUG
requireSteamAuth = false; DebugConsole.NewMessage($"Debug server accepts unauthenticated connections", Microsoft.Xna.Framework.Color.Yellow);
acceptClient(new AccountInfo(packet.AccountId));
#else
rejectClient();
#endif #endif
bool hasSteamAuth = packet.SteamAuthTicket.TryUnwrap(out var ticket); return;
//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;
}
} }
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) 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