diff options
-rw-r--r-- | docs/release-notes.md | 1 | ||||
-rw-r--r-- | src/SMAPI/Framework/Networking/MultiplayerPeer.cs | 10 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGame.cs | 4 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGameRunner.cs | 22 | ||||
-rw-r--r-- | src/SMAPI/Framework/SMultiplayer.cs | 48 | ||||
-rw-r--r-- | src/SMAPI/IMultiplayerPeer.cs | 7 |
6 files changed, 84 insertions, 8 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md index c36d80ed..49ee219a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ ## Upcoming release * For modders: * Expanded `PerScreen<T>` API: you can now get/set the value for any screen, get all active values, or clear all values. + * Expanded player info received from multiplayer API/events with new `IsSplitScreen` and `ScreenID` fields. * Added an option to disable rewriting mods for compatibility (thanks to Bpendragon!). This may prevent older mods from loading, but bypasses a Visual Studio crash when debugging. * For the Error Handler mod: diff --git a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs index 5eda71f6..3923700f 100644 --- a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs +++ b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs @@ -25,9 +25,15 @@ namespace StardewModdingAPI.Framework.Networking public bool IsHost { get; } /// <inheritdoc /> + public bool IsSplitScreen => this.ScreenID != null; + + /// <inheritdoc /> public bool HasSmapi => this.ApiVersion != null; /// <inheritdoc /> + public int? ScreenID { get; } + + /// <inheritdoc /> public GamePlatform? Platform { get; } /// <inheritdoc /> @@ -45,12 +51,14 @@ namespace StardewModdingAPI.Framework.Networking *********/ /// <summary>Construct an instance.</summary> /// <param name="playerID">The player's unique ID.</param> + /// <param name="screenID">The player's screen ID, if applicable.</param> /// <param name="model">The metadata to copy.</param> /// <param name="sendMessage">A method which sends a message to the peer.</param> /// <param name="isHost">Whether this is a connection to the host player.</param> - public MultiplayerPeer(long playerID, RemoteContextModel model, Action<OutgoingMessage> sendMessage, bool isHost) + public MultiplayerPeer(long playerID, int? screenID, RemoteContextModel model, Action<OutgoingMessage> sendMessage, bool isHost) { this.PlayerID = playerID; + this.ScreenID = screenID; this.IsHost = isHost; if (model != null) { diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 42a712ee..634680a0 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -81,6 +81,9 @@ namespace StardewModdingAPI.Framework /// <summary>Whether the game is creating the save file and SMAPI has already raised <see cref="IGameLoopEvents.SaveCreating"/>.</summary> public bool IsBetweenCreateEvents { get; set; } + /// <summary>The cached <see cref="Farmer.UniqueMultiplayerID"/> value for this instance's player.</summary> + public long? PlayerId { get; private set; } + /// <summary>Construct a content manager to read game content files.</summary> /// <remarks>This must be static because the game accesses it before the <see cref="SGame"/> constructor is called.</remarks> [NonInstancedStatic] @@ -167,6 +170,7 @@ namespace StardewModdingAPI.Framework try { this.OnUpdating(this, gameTime, () => base.Update(gameTime)); + this.PlayerId = Game1.player?.UniqueMultiplayerID; } finally { diff --git a/src/SMAPI/Framework/SGameRunner.cs b/src/SMAPI/Framework/SGameRunner.cs index ae06f513..45e7369c 100644 --- a/src/SMAPI/Framework/SGameRunner.cs +++ b/src/SMAPI/Framework/SGameRunner.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Events; @@ -49,6 +50,13 @@ namespace StardewModdingAPI.Framework /********* ** Public methods *********/ + /// <summary>The singleton instance.</summary> + public static SGameRunner Instance => (SGameRunner)GameRunner.instance; + + + /********* + ** Public methods + *********/ /// <summary>Construct an instance.</summary> /// <param name="monitor">Encapsulates monitoring and logging for SMAPI.</param> /// <param name="reflection">Simplifies access to private game code.</param> @@ -99,15 +107,24 @@ namespace StardewModdingAPI.Framework } /// <inheritdoc /> - public override void RemoveGameInstance(Game1 instance) + public override void RemoveGameInstance(Game1 gameInstance) { - base.RemoveGameInstance(instance); + base.RemoveGameInstance(gameInstance); if (this.gameInstances.Count <= 1) EarlyConstants.LogScreenId = null; this.UpdateForSplitScreenChanges(); } + /// <summary>Get the screen ID for a given player ID, if the player is local.</summary> + /// <param name="playerId">The player ID to check.</param> + public int? GetScreenId(long playerId) + { + return this.gameInstances + .FirstOrDefault(p => ((SGame)p).PlayerId == playerId) + ?.instanceId; + } + /********* ** Protected methods @@ -136,6 +153,7 @@ namespace StardewModdingAPI.Framework this.OnGameUpdating(gameTime, () => base.Update(gameTime)); } + /// <summary>Update metadata when a split screen is added or removed.</summary> private void UpdateForSplitScreenChanges() { HashSet<int> oldScreenIds = new HashSet<int>(Context.ActiveScreenIds); diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs index 2f89fce9..b2257286 100644 --- a/src/SMAPI/Framework/SMultiplayer.cs +++ b/src/SMAPI/Framework/SMultiplayer.cs @@ -196,7 +196,13 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Received context for farmhand {message.FarmerID} running {(model != null ? $"SMAPI {model.ApiVersion} with {model.Mods.Length} mods" : "vanilla")}.", LogLevel.Trace); // store peer - MultiplayerPeer newPeer = new MultiplayerPeer(message.FarmerID, model, sendMessage, isHost: false); + MultiplayerPeer newPeer = new MultiplayerPeer( + playerID: message.FarmerID, + screenID: this.GetScreenId(message.FarmerID), + model: model, + sendMessage: sendMessage, + isHost: false + ); if (this.Peers.ContainsKey(message.FarmerID)) { this.Monitor.Log($"Received mod context from farmhand {message.FarmerID}, but the game didn't see them disconnect. This may indicate issues with the network connection.", LogLevel.Info); @@ -238,7 +244,13 @@ namespace StardewModdingAPI.Framework if (!this.Peers.ContainsKey(message.FarmerID)) { this.Monitor.Log($"Received connection for vanilla player {message.FarmerID}.", LogLevel.Trace); - MultiplayerPeer peer = new MultiplayerPeer(message.FarmerID, null, sendMessage, isHost: false); + MultiplayerPeer peer = new MultiplayerPeer( + playerID: message.FarmerID, + screenID: this.GetScreenId(message.FarmerID), + model: null, + sendMessage: sendMessage, + isHost: false + ); this.AddPeer(peer, canBeHost: false); } @@ -280,7 +292,13 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Received context for {(model?.IsHost == true ? "host" : "farmhand")} {message.FarmerID} running {(model != null ? $"SMAPI {model.ApiVersion} with {model.Mods.Length} mods" : "vanilla")}.", LogLevel.Trace); // store peer - MultiplayerPeer peer = new MultiplayerPeer(message.FarmerID, model, sendMessage, isHost: model?.IsHost ?? this.HostPeer == null); + MultiplayerPeer peer = new MultiplayerPeer( + playerID: message.FarmerID, + screenID: this.GetScreenId(message.FarmerID), + model: model, + sendMessage: sendMessage, + isHost: model?.IsHost ?? this.HostPeer == null + ); if (peer.IsHost && this.HostPeer != null) { this.Monitor.Log($"Rejected mod context from host player {peer.PlayerID}: already received host data from {(peer.PlayerID == this.HostPeer.PlayerID ? "that player" : $"player {peer.PlayerID}")}.", LogLevel.Error); @@ -297,7 +315,14 @@ namespace StardewModdingAPI.Framework if (!this.Peers.ContainsKey(message.FarmerID) && this.HostPeer == null) { this.Monitor.Log($"Received connection for vanilla host {message.FarmerID}.", LogLevel.Trace); - this.AddPeer(new MultiplayerPeer(message.FarmerID, null, sendMessage, isHost: true), canBeHost: false); + var peer = new MultiplayerPeer( + playerID: message.FarmerID, + screenID: this.GetScreenId(message.FarmerID), + model: null, + sendMessage: sendMessage, + isHost: true + ); + this.AddPeer(peer, canBeHost: false); } resume(); break; @@ -309,7 +334,13 @@ namespace StardewModdingAPI.Framework // store peer if (!this.Peers.TryGetValue(message.FarmerID, out MultiplayerPeer peer)) { - peer = new MultiplayerPeer(message.FarmerID, null, sendMessage, isHost: this.HostPeer == null); + peer = new MultiplayerPeer( + playerID: message.FarmerID, + screenID: this.GetScreenId(message.FarmerID), + model: null, + sendMessage: sendMessage, + isHost: this.HostPeer == null + ); this.Monitor.Log($"Received connection for vanilla {(peer.IsHost ? "host" : "farmhand")} {message.FarmerID}.", LogLevel.Trace); this.AddPeer(peer, canBeHost: true); } @@ -505,6 +536,13 @@ namespace StardewModdingAPI.Framework } } + /// <summary>Get the screen ID for a given player ID, if the player is local.</summary> + /// <param name="playerId">The player ID to check.</param> + private int? GetScreenId(long playerId) + { + return SGameRunner.Instance.GetScreenId(playerId); + } + /// <summary>Get all connected player IDs, including the current player.</summary> private IEnumerable<long> GetKnownPlayerIDs() { diff --git a/src/SMAPI/IMultiplayerPeer.cs b/src/SMAPI/IMultiplayerPeer.cs index 0d4d3261..47084174 100644 --- a/src/SMAPI/IMultiplayerPeer.cs +++ b/src/SMAPI/IMultiplayerPeer.cs @@ -14,9 +14,16 @@ namespace StardewModdingAPI /// <summary>Whether this is a connection to the host player.</summary> bool IsHost { get; } + /// <summary>Whether this a local player on the same computer in split-screen mote.</summary> + bool IsSplitScreen { get; } + /// <summary>Whether the player has SMAPI installed.</summary> bool HasSmapi { get; } + /// <summary>The player's screen ID, if applicable.</summary> + /// <remarks>See <see cref="Context.ScreenId"/> for details. This is only visible to players in split-screen mode. A remote player won't see this value, even if the other players are in split-screen mode.</remarks> + int? ScreenID { get; } + /// <summary>The player's OS platform, if <see cref="HasSmapi"/> is true.</summary> GamePlatform? Platform { get; } |