diff options
author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2018-10-31 17:29:32 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2018-10-31 17:29:32 -0400 |
commit | e5e4ce411cc5a5e5066552978517904b21900066 (patch) | |
tree | 66f9d33d37fbe3cc12f13581490411da0b30357f /src/SMAPI/Framework/Networking | |
parent | 688ee69ee64e03aee7a693e6c15092daf229ac5e (diff) | |
download | SMAPI-e5e4ce411cc5a5e5066552978517904b21900066.tar.gz SMAPI-e5e4ce411cc5a5e5066552978517904b21900066.tar.bz2 SMAPI-e5e4ce411cc5a5e5066552978517904b21900066.zip |
sync SMAPI context between players in multiplayer (#480)
Diffstat (limited to 'src/SMAPI/Framework/Networking')
-rw-r--r-- | src/SMAPI/Framework/Networking/MultiplayerPeer.cs | 128 | ||||
-rw-r--r-- | src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs | 30 | ||||
-rw-r--r-- | src/SMAPI/Framework/Networking/RemoteContextModModel.cs | 15 | ||||
-rw-r--r-- | src/SMAPI/Framework/Networking/RemoteContextModel.cs | 24 | ||||
-rw-r--r-- | src/SMAPI/Framework/Networking/SLidgrenClient.cs | 58 | ||||
-rw-r--r-- | src/SMAPI/Framework/Networking/SLidgrenServer.cs | 36 |
6 files changed, 291 insertions, 0 deletions
diff --git a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs new file mode 100644 index 00000000..e97e36bc --- /dev/null +++ b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Lidgren.Network; +using StardewValley; +using StardewValley.Network; + +namespace StardewModdingAPI.Framework.Networking +{ + /// <summary>Metadata about a connected player.</summary> + internal class MultiplayerPeer : IMultiplayerPeer + { + /********* + ** Properties + *********/ + /// <summary>The server through which to send messages, if this is an incoming farmhand.</summary> + private readonly SLidgrenServer Server; + + /// <summary>The client through which to send messages, if this is the host player.</summary> + private readonly SLidgrenClient Client; + + /// <summary>The network connection to the player.</summary> + private readonly NetConnection ServerConnection; + + + /********* + ** Accessors + *********/ + /// <summary>The player's unique ID.</summary> + public long PlayerID { get; } + + /// <summary>Whether this is a connection to the host player.</summary> + public bool IsHostPlayer => this.PlayerID == Game1.MasterPlayer.UniqueMultiplayerID; + + /// <summary>Whether the player has SMAPI installed.</summary> + public bool HasSmapi => this.ApiVersion != null; + + /// <summary>The player's OS platform, if <see cref="HasSmapi"/> is true.</summary> + public GamePlatform? Platform { get; } + + /// <summary>The installed version of Stardew Valley, if <see cref="HasSmapi"/> is true.</summary> + public ISemanticVersion GameVersion { get; } + + /// <summary>The installed version of SMAPI, if <see cref="HasSmapi"/> is true.</summary> + public ISemanticVersion ApiVersion { get; } + + /// <summary>The installed mods, if <see cref="HasSmapi"/> is true.</summary> + public IEnumerable<IMultiplayerPeerMod> Mods { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="playerID">The player's unique ID.</param> + /// <param name="model">The metadata to copy.</param> + /// <param name="server">The server through which to send messages.</param> + /// <param name="serverConnection">The server connection through which to send messages.</param> + /// <param name="client">The client through which to send messages.</param> + public MultiplayerPeer(long playerID, RemoteContextModel model, SLidgrenServer server, NetConnection serverConnection, SLidgrenClient client) + { + this.PlayerID = playerID; + if (model != null) + { + this.Platform = model.Platform; + this.GameVersion = model.GameVersion; + this.ApiVersion = model.ApiVersion; + this.Mods = model.Mods.Select(mod => new MultiplayerPeerMod(mod)).ToArray(); + } + this.Server = server; + this.ServerConnection = serverConnection; + this.Client = client; + } + + /// <summary>Construct an instance for a connection to an incoming farmhand.</summary> + /// <param name="playerID">The player's unique ID.</param> + /// <param name="model">The metadata to copy, if available.</param> + /// <param name="server">The server through which to send messages.</param> + /// <param name="serverConnection">The server connection through which to send messages.</param> + public static MultiplayerPeer ForConnectionToFarmhand(long playerID, RemoteContextModel model, SLidgrenServer server, NetConnection serverConnection) + { + return new MultiplayerPeer( + playerID: playerID, + model: model, + server: server, + serverConnection: serverConnection, + client: null + ); + } + + /// <summary>Construct an instance for a connection to the host player.</summary> + /// <param name="playerID">The player's unique ID.</param> + /// <param name="model">The metadata to copy.</param> + /// <param name="client">The client through which to send messages.</param> + public static MultiplayerPeer ForConnectionToHost(long playerID, RemoteContextModel model, SLidgrenClient client) + { + return new MultiplayerPeer( + playerID: playerID, + model: model, + server: null, + serverConnection: null, + client: client + ); + } + + /// <summary>Get metadata for a mod installed by the player.</summary> + /// <param name="id">The unique mod ID.</param> + /// <returns>Returns the mod info, or <c>null</c> if the player doesn't have that mod.</returns> + public IMultiplayerPeerMod GetMod(string id) + { + if (string.IsNullOrWhiteSpace(id)) + return null; + + id = id.Trim(); + return this.Mods.FirstOrDefault(mod => mod.ID != null && mod.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase)); + } + + /// <summary>Send a message to the given peer, bypassing the game's normal validation to allow messages before the connection is approved.</summary> + /// <param name="message">The message to send.</param> + public void SendMessage(OutgoingMessage message) + { + if (this.IsHostPlayer) + this.Client.sendMessage(message); + else + this.Server.SendMessage(this.ServerConnection, message); + } + } +} diff --git a/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs b/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs new file mode 100644 index 00000000..1b324bcd --- /dev/null +++ b/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs @@ -0,0 +1,30 @@ +namespace StardewModdingAPI.Framework.Networking +{ + internal class MultiplayerPeerMod : IMultiplayerPeerMod + { + /********* + ** Accessors + *********/ + /// <summary>The mod's display name.</summary> + public string Name { get; } + + /// <summary>The unique mod ID.</summary> + public string ID { get; } + + /// <summary>The mod version.</summary> + public ISemanticVersion Version { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="mod">The mod metadata.</param> + public MultiplayerPeerMod(RemoteContextModModel mod) + { + this.Name = mod.Name; + this.ID = mod.ID?.Trim(); + this.Version = mod.Version; + } + } +} diff --git a/src/SMAPI/Framework/Networking/RemoteContextModModel.cs b/src/SMAPI/Framework/Networking/RemoteContextModModel.cs new file mode 100644 index 00000000..9795d971 --- /dev/null +++ b/src/SMAPI/Framework/Networking/RemoteContextModModel.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Framework.Networking +{ + /// <summary>Metadata about an installed mod exchanged with connected computers.</summary> + public class RemoteContextModModel + { + /// <summary>The mod's display name.</summary> + public string Name { get; set; } + + /// <summary>The unique mod ID.</summary> + public string ID { get; set; } + + /// <summary>The mod version.</summary> + public ISemanticVersion Version { get; set; } + } +} diff --git a/src/SMAPI/Framework/Networking/RemoteContextModel.cs b/src/SMAPI/Framework/Networking/RemoteContextModel.cs new file mode 100644 index 00000000..7befb151 --- /dev/null +++ b/src/SMAPI/Framework/Networking/RemoteContextModel.cs @@ -0,0 +1,24 @@ +namespace StardewModdingAPI.Framework.Networking +{ + /// <summary>Metadata about the game, SMAPI, and installed mods exchanged with connected computers.</summary> + internal class RemoteContextModel + { + /********* + ** Accessors + *********/ + /// <summary>Whether this player is the host player.</summary> + public bool IsHost { get; set; } + + /// <summary>The game's platform version.</summary> + public GamePlatform Platform { get; set; } + + /// <summary>The installed version of Stardew Valley.</summary> + public ISemanticVersion GameVersion { get; set; } + + /// <summary>The installed version of SMAPI.</summary> + public ISemanticVersion ApiVersion { get; set; } + + /// <summary>The installed mods.</summary> + public RemoteContextModModel[] Mods { get; set; } + } +} diff --git a/src/SMAPI/Framework/Networking/SLidgrenClient.cs b/src/SMAPI/Framework/Networking/SLidgrenClient.cs new file mode 100644 index 00000000..9dfdba15 --- /dev/null +++ b/src/SMAPI/Framework/Networking/SLidgrenClient.cs @@ -0,0 +1,58 @@ +using System; +using StardewValley; +using StardewValley.Network; + +namespace StardewModdingAPI.Framework.Networking +{ + /// <summary>A multiplayer client used to connect to a hosted server. This is an implementation of <see cref="LidgrenClient"/> that adds support for SMAPI's metadata context exchange.</summary> + internal class SLidgrenClient : LidgrenClient + { + /********* + ** Properties + *********/ + /// <summary>Get the metadata to include in a metadata message sent to other players.</summary> + private readonly Func<object[]> GetMetadataMessageFields; + + /// <summary>The method to call when receiving a custom SMAPI message from the server, which returns whether the message was processed.</summary> + private readonly Func<SLidgrenClient, IncomingMessage, bool> TryProcessMessage; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="address">The remote address being connected.</param> + /// <param name="getMetadataMessageFields">Get the metadata to include in a metadata message sent to other players.</param> + /// <param name="tryProcessMessage">The method to call when receiving a custom SMAPI message from the server, which returns whether the message was processed..</param> + public SLidgrenClient(string address, Func<object[]> getMetadataMessageFields, Func<SLidgrenClient, IncomingMessage, bool> tryProcessMessage) + : base(address) + { + this.GetMetadataMessageFields = getMetadataMessageFields; + this.TryProcessMessage = tryProcessMessage; + } + + /// <summary>Send the metadata needed to connect with a remote server.</summary> + public override void sendPlayerIntroduction() + { + // send custom intro + if (this.getUserID() != "") + Game1.player.userID.Value = this.getUserID(); + this.sendMessage(SMultiplayer.ContextSyncMessageID, this.GetMetadataMessageFields()); + base.sendPlayerIntroduction(); + } + + + /********* + ** Protected methods + *********/ + /// <summary>Process an incoming network message.</summary> + /// <param name="message">The message to process.</param> + protected override void processIncomingMessage(IncomingMessage message) + { + if (this.TryProcessMessage(this, message)) + return; + + base.processIncomingMessage(message); + } + } +} diff --git a/src/SMAPI/Framework/Networking/SLidgrenServer.cs b/src/SMAPI/Framework/Networking/SLidgrenServer.cs new file mode 100644 index 00000000..971eb66d --- /dev/null +++ b/src/SMAPI/Framework/Networking/SLidgrenServer.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using Lidgren.Network; +using StardewValley.Network; + +namespace StardewModdingAPI.Framework.Networking +{ + /// <summary>A multiplayer server used to connect to an incoming player. This is an implementation of <see cref="LidgrenServer"/> that adds support for SMAPI's metadata context exchange.</summary> + internal class SLidgrenServer : LidgrenServer + { + /********* + ** Properties + *********/ + /// <summary>A method which sends a message through a specific connection.</summary> + private readonly MethodInfo SendMessageToConnectionMethod; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="gameServer">The underlying game server.</param> + public SLidgrenServer(IGameServer gameServer) + : base(gameServer) + { + this.SendMessageToConnectionMethod = typeof(LidgrenServer).GetMethod(nameof(LidgrenServer.sendMessage), BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(NetConnection), typeof(OutgoingMessage) }, null); + } + + /// <summary>Send a message to a remote server.</summary> + /// <param name="connection">The network connection.</param> + /// <param name="message">The message to send.</param> + public void SendMessage(NetConnection connection, OutgoingMessage message) + { + this.SendMessageToConnectionMethod.Invoke(this, new object[] { connection, message }); + } + } +} |