From a144c0e13ead182c92a37ad4b30d71aa54673de8 Mon Sep 17 00:00:00 2001 From: mibac138 <5672750+mibac138@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:39:52 +0100 Subject: [PATCH] Introduce IConnector for a unified way to handle initiating connections Allows greater flexibility, avoids having to pass around three variables for reconnecting (addr, port, steamHost) --- Source/Client/MultiplayerStatic.cs | 38 +++---- Source/Client/Networking/ClientUtil.cs | 34 ++---- Source/Client/Networking/JoinData.cs | 4 +- Source/Client/Networking/NetworkingSteam.cs | 7 +- .../Networking/State/ClientJoiningState.cs | 4 +- Source/Client/Networking/SteamIntegration.cs | 3 +- Source/Client/Session/MultiplayerSession.cs | 10 +- Source/Client/Util/ConnectorRegistry.cs | 102 ++++++++++++++++++ Source/Client/Windows/JoinDataWindow.cs | 6 +- Source/Client/Windows/ServerBrowser.cs | 6 +- 10 files changed, 139 insertions(+), 75 deletions(-) create mode 100644 Source/Client/Util/ConnectorRegistry.cs diff --git a/Source/Client/MultiplayerStatic.cs b/Source/Client/MultiplayerStatic.cs index 9d4d9c9c..c378d4ee 100644 --- a/Source/Client/MultiplayerStatic.cs +++ b/Source/Client/MultiplayerStatic.cs @@ -14,7 +14,6 @@ using Multiplayer.Common; using RimWorld; using RimWorld.Planet; -using Steamworks; using UnityEngine; using Verse; using Verse.Sound; @@ -154,41 +153,30 @@ private static void SetUsername() private static void HandleRestartConnect() { - if (Multiplayer.restartConnect == null) - return; + var connString = Multiplayer.restartConnect; + if (connString == null) return; - // No colon means the connect string is a steam user id - if (!Multiplayer.restartConnect.Contains(':')) + if (!ConnectorRegistry.TryParse(connString, out var connector)) { - if (ulong.TryParse(Multiplayer.restartConnect, out ulong steamUser)) - DoubleLongEvent(() => ClientUtil.TrySteamConnectWithWindow((CSteamID)steamUser, false), "MpConnecting"); - + Log.Error($"Failed to parse connection string from restartConnect: {connString}"); return; } - var split = Multiplayer.restartConnect.Split(new[]{':'}, StringSplitOptions.RemoveEmptyEntries); - if (split.Length == 2 && int.TryParse(split[1], out int port)) - DoubleLongEvent(() => ClientUtil.TryConnectWithWindow(split[0], port, false), "MpConnecting"); + DoubleLongEvent(() => ClientUtil.TryConnectWithWindow(connector, false), "MpConnecting"); } private static void HandleCommandLine() { if (GenCommandLine.TryGetCommandLineArg("connect", out string addressPort) && Multiplayer.restartConnect == null) { - int port = MultiplayerServer.DefaultPort; - - string address = null; - var split = addressPort.Split(':'); - - if (split.Length == 0) - address = "127.0.0.1"; - else if (split.Length >= 1) - address = split[0]; - - if (split.Length == 2) - int.TryParse(split[1], out port); - - DoubleLongEvent(() => ClientUtil.TryConnectWithWindow(address, port, false), "Connecting"); + if (LiteNetConnector.TryParse(addressPort, out var connector)) + { + DoubleLongEvent(() => ClientUtil.TryConnectWithWindow(connector, false), "Connecting"); + } + else + { + Log.Error($"Failed to parse connection string from command line: {addressPort}"); + } } if (GenCommandLine.CommandLineArgPassed("arbiter")) diff --git a/Source/Client/Networking/ClientUtil.cs b/Source/Client/Networking/ClientUtil.cs index 7e6bb23a..6373b9e2 100644 --- a/Source/Client/Networking/ClientUtil.cs +++ b/Source/Client/Networking/ClientUtil.cs @@ -1,7 +1,5 @@ -using Multiplayer.Common; -using Steamworks; +using Multiplayer.Client.Util; using Verse; -using Multiplayer.Client.Networking; namespace Multiplayer.Client { @@ -12,36 +10,20 @@ public interface ITickableConnection public static class ClientUtil { - public static void TryConnectWithWindow(string address, int port, bool returnToServerBrowser = true) + public static void TryConnectWithWindow(IConnector connector, bool returnToServerBrowser = true) { - Find.WindowStack.Add(new ConnectingWindow(address, port) { returnToServerBrowser = returnToServerBrowser }); - - Multiplayer.session = new MultiplayerSession - { - address = address, - port = port - }; - - var conn = ClientLiteNetConnection.Connect(address, port); + var (conn, window) = connector.Connect(); conn.username = Multiplayer.username; - Multiplayer.session.client = conn; - Multiplayer.session.ReapplyPrefs(); - } - - public static void TrySteamConnectWithWindow(CSteamID user, bool returnToServerBrowser = true) - { - Log.Message("Connecting through Steam"); Multiplayer.session = new MultiplayerSession { - client = new SteamClientConn(user) { username = Multiplayer.username }, - steamHost = user + client = conn, + connector = connector }; - - Find.WindowStack.Add(new SteamConnectingWindow(user) { returnToServerBrowser = returnToServerBrowser }); - Multiplayer.session.ReapplyPrefs(); - Multiplayer.Client.ChangeState(ConnectionStateEnum.ClientSteam); + + window.returnToServerBrowser = returnToServerBrowser; + Find.WindowStack.Add(window); } } } diff --git a/Source/Client/Networking/JoinData.cs b/Source/Client/Networking/JoinData.cs index 71fca8f2..31b30679 100644 --- a/Source/Client/Networking/JoinData.cs +++ b/Source/Client/Networking/JoinData.cs @@ -290,9 +290,7 @@ public class RemoteData public IEnumerable RemoteModIds => remoteMods.Select(m => m.packageId); - public string remoteAddress; - public int remotePort; - public CSteamID? remoteSteamHost; + public string connectionString; public ModListDiff CompareMods(List localMods) { diff --git a/Source/Client/Networking/NetworkingSteam.cs b/Source/Client/Networking/NetworkingSteam.cs index 90192f5c..a1519337 100644 --- a/Source/Client/Networking/NetworkingSteam.cs +++ b/Source/Client/Networking/NetworkingSteam.cs @@ -48,8 +48,13 @@ protected override void OnClose() public override string ToString() => $"SteamP2P ({remoteId}:{username})"; } - public class SteamClientConn(CSteamID remoteId) : SteamBaseConn(remoteId, RandomChannelId(), 0), ITickableConnection + public class SteamClientConn : SteamBaseConn, ITickableConnection { + public SteamClientConn(CSteamID remoteId) : base(remoteId, RandomChannelId(), 0) + { + ChangeState(ConnectionStateEnum.ClientSteam); + } + static ushort RandomChannelId() => (ushort)new Random().Next(); public void Tick() diff --git a/Source/Client/Networking/State/ClientJoiningState.cs b/Source/Client/Networking/State/ClientJoiningState.cs index 5bf363ec..3765ac75 100644 --- a/Source/Client/Networking/State/ClientJoiningState.cs +++ b/Source/Client/Networking/State/ClientJoiningState.cs @@ -76,9 +76,7 @@ public void HandleJoinData(ServerJoinDataPacket packet) { remoteRwVersion = packet.rwVersion, remoteMpVersion = packet.mpVersion, - remoteAddress = Multiplayer.session.address, - remotePort = Multiplayer.session.port, - remoteSteamHost = Multiplayer.session.steamHost + connectionString = Multiplayer.session.connector.GetConnectionString() }; var defDiff = false; diff --git a/Source/Client/Networking/SteamIntegration.cs b/Source/Client/Networking/SteamIntegration.cs index 1a340a18..667669e0 100644 --- a/Source/Client/Networking/SteamIntegration.cs +++ b/Source/Client/Networking/SteamIntegration.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using Multiplayer.Client.Networking; +using Multiplayer.Client.Util; using Multiplayer.Client.Windows; using RimWorld; using Steamworks; @@ -56,7 +57,7 @@ public static void InitCallbacks() { if (Current.Game == null) { - ClientUtil.TrySteamConnectWithWindow(req.m_steamIDFriend, false); + ClientUtil.TryConnectWithWindow(ConnectorRegistry.Steam(req.m_steamIDFriend), false); } else { diff --git a/Source/Client/Session/MultiplayerSession.cs b/Source/Client/Session/MultiplayerSession.cs index 21b1c908..7a818c70 100644 --- a/Source/Client/Session/MultiplayerSession.cs +++ b/Source/Client/Session/MultiplayerSession.cs @@ -52,9 +52,7 @@ public class MultiplayerSession : IConnectionStatusListener public Process arbiter; public bool ArbiterPlaying => players.Any(p => p.type == PlayerType.Arbiter && p.status == PlayerStatus.Playing); - public string address; - public int port; - public CSteamID? steamHost; + public IConnector connector; public void Stop() { @@ -102,11 +100,7 @@ public void NotifyChat() public void Reconnect(string username) { Multiplayer.username = username; - - if (steamHost is { } host) - ClientUtil.TrySteamConnectWithWindow(host); - else - ClientUtil.TryConnectWithWindow(address, port); + ClientUtil.TryConnectWithWindow(connector); } public void Connected() diff --git a/Source/Client/Util/ConnectorRegistry.cs b/Source/Client/Util/ConnectorRegistry.cs new file mode 100644 index 00000000..d80c6d3d --- /dev/null +++ b/Source/Client/Util/ConnectorRegistry.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using Multiplayer.Client.Networking; +using Multiplayer.Common; +using Steamworks; + +namespace Multiplayer.Client.Util; + +/// Contains all the data required to create a connection for the transport type (i.e., IP and port for LiteNetLib, and +/// the host's steam id for Steam) +public interface IConnector +{ + /// Does include the connector's prefix. + public string GetConnectionString(); + public (ConnectionBase, BaseConnectingWindow) Connect(); +} + +/// Utility class for creating connectors and parsing them from a string based on their unique prefix. +public static class ConnectorRegistry +{ + private delegate bool ConnStringParser(string connString, out IConnector connector); + + private static readonly List<(string prefix, ConnStringParser parser)> ConnStringParsers = + [ + (SteamConnector.Prefix, SteamConnector.TryParse), + (LiteNetConnector.Prefix, LiteNetConnector.TryParse) + ]; + + public static bool TryParse(string connString, out IConnector connector) + { + connector = null; + foreach (var (prefix, parser) in ConnStringParsers) + { + var prefixColon = $"{prefix}:"; + if (connString.StartsWith(prefixColon)) return parser(connString.RemovePrefix(prefixColon), out connector); + } + + return false; + } + + public static IConnector Steam(CSteamID remoteId) => new SteamConnector(remoteId); + public static IConnector LiteNet(string addr, int port) => new LiteNetConnector(addr, port); +} + +public class SteamConnector(CSteamID remoteId) : IConnector +{ + public static string Prefix => "steam"; + + /// Does not check if Prefix matches. Use ConnectorRegistry instead if that's desired. + public static bool TryParse(string connString, out IConnector connector) + { + if (ulong.TryParse(connString, out ulong steamUser)) + { + connector = new SteamConnector((CSteamID)steamUser); + return true; + } + + connector = null; + return false; + } + + public string GetConnectionString() => $"{Prefix}:{remoteId}"; + + public (ConnectionBase, BaseConnectingWindow) Connect() => + (new SteamClientConn(remoteId), new SteamConnectingWindow(remoteId)); +} + +public class LiteNetConnector(string address, int port) : IConnector +{ + public static string Prefix => "lnl"; + + /// Does not check if Prefix matches. Use ConnectorRegistry instead if that's desired. + public static bool TryParse(string connString, out IConnector connector) + { + var split = connString.Split(':', StringSplitOptions.RemoveEmptyEntries); + if (split.Length == 0) + { + connector = new LiteNetConnector("127.0.0.1", MultiplayerServer.DefaultPort); + return true; + } + + if (split.Length == 1) + { + connector = new LiteNetConnector(split[0], MultiplayerServer.DefaultPort); + return true; + } + + if (split.Length == 2 && int.TryParse(split[1], out int port)) + { + connector = new LiteNetConnector(split[0], port); + return true; + } + + connector = null; + return false; + } + + public string GetConnectionString() => $"{Prefix}:{address}:{port}"; + + public (ConnectionBase, BaseConnectingWindow) Connect() => + (ClientLiteNetConnection.Connect(address, port), new ConnectingWindow(address, port)); +} diff --git a/Source/Client/Windows/JoinDataWindow.cs b/Source/Client/Windows/JoinDataWindow.cs index ffd2aff2..dc9d98f5 100644 --- a/Source/Client/Windows/JoinDataWindow.cs +++ b/Source/Client/Windows/JoinDataWindow.cs @@ -759,12 +759,8 @@ private void DoRestart() File.WriteAllText(Path.Combine(tempPath, $"{config.ModId}-{config.FileName}"), config.Contents); } - var connectTo = data.remoteSteamHost != null - ? $"{data.remoteSteamHost}" - : $"{data.remoteAddress}:{data.remotePort}"; - // The env variables will get inherited by the child process started in GenCommandLine.Restart - Environment.SetEnvironmentVariable(EarlyInit.RestartConnectVariable, connectTo); + Environment.SetEnvironmentVariable(EarlyInit.RestartConnectVariable, data.connectionString); Environment.SetEnvironmentVariable(EarlyInit.RestartConfigsVariable, applyConfigs ? "true" : "false"); GenCommandLine.Restart(); diff --git a/Source/Client/Windows/ServerBrowser.cs b/Source/Client/Windows/ServerBrowser.cs index 968f5c7f..51946150 100644 --- a/Source/Client/Windows/ServerBrowser.cs +++ b/Source/Client/Windows/ServerBrowser.cs @@ -473,7 +473,7 @@ private void DrawSteam(Rect inRect) if (Widgets.ButtonText(playButton, "MpJoinButton".Translate())) { Close(false); - ClientUtil.TrySteamConnectWithWindow(friend.serverHost); + ClientUtil.TryConnectWithWindow(ConnectorRegistry.Steam(friend.serverHost)); } } else @@ -523,7 +523,7 @@ private static bool DirectConnect(string addr) try { - ClientUtil.TryConnectWithWindow(addr, port); + ClientUtil.TryConnectWithWindow(ConnectorRegistry.LiteNet(addr, port)); Multiplayer.settings.Write(); return true; } @@ -570,7 +570,7 @@ private void DrawLan(Rect inRect) var address = server.endpoint.Address.ToString(); var port = server.endpoint.Port; - ClientUtil.TryConnectWithWindow(address, port); + ClientUtil.TryConnectWithWindow(ConnectorRegistry.LiteNet(address, port)); } y += entryRect.height;