From 0a2b15d3c36a0a5db4d8fb6e48b592507dc74c14 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 2 Feb 2020 14:20:41 -0500 Subject: add support for self-broadcasts, optimize network messages --- src/SMAPI/Framework/Networking/ModMessageModel.cs | 2 +- src/SMAPI/Framework/SMultiplayer.cs | 84 +++++++++++++++-------- 2 files changed, 58 insertions(+), 28 deletions(-) (limited to 'src/SMAPI') diff --git a/src/SMAPI/Framework/Networking/ModMessageModel.cs b/src/SMAPI/Framework/Networking/ModMessageModel.cs index 7ee39863..4f694f9c 100644 --- a/src/SMAPI/Framework/Networking/ModMessageModel.cs +++ b/src/SMAPI/Framework/Networking/ModMessageModel.cs @@ -21,7 +21,7 @@ namespace StardewModdingAPI.Framework.Networking /**** ** Destination ****/ - /// The players who should receive the message, or null for all players. + /// The players who should receive the message. public long[] ToPlayerIDs { get; set; } /// The mods which should receive the message, or null for all mods. diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs index e04205c8..821c343f 100644 --- a/src/SMAPI/Framework/SMultiplayer.cs +++ b/src/SMAPI/Framework/SMultiplayer.cs @@ -338,64 +338,94 @@ namespace StardewModdingAPI.Framework /// The values for the players who should receive the message, or null for all players. If you don't need to broadcast to all players, specifying player IDs is recommended to reduce latency. public void BroadcastModMessage(TMessage message, string messageType, string fromModID, string[] toModIDs, long[] toPlayerIDs) { - // validate + // validate input if (message == null) throw new ArgumentNullException(nameof(message)); if (string.IsNullOrWhiteSpace(messageType)) throw new ArgumentNullException(nameof(messageType)); if (string.IsNullOrWhiteSpace(fromModID)) throw new ArgumentNullException(nameof(fromModID)); - if (!this.Peers.Any()) + + // get target players + long curPlayerId = Game1.player.UniqueMultiplayerID; + bool sendToSelf = false; + List sendToPeers = new List(); + if (toPlayerIDs == null) { - this.Monitor.VerboseLog($"Ignored '{messageType}' broadcast from mod {fromModID}: not connected to any players."); - return; + sendToSelf = true; + sendToPeers.AddRange(this.Peers.Values); } - - // filter player IDs - HashSet playerIDs = null; - if (toPlayerIDs != null && toPlayerIDs.Any()) + else { - playerIDs = new HashSet(toPlayerIDs); - playerIDs.RemoveWhere(id => !this.Peers.ContainsKey(id)); - if (!playerIDs.Any()) + foreach (long id in toPlayerIDs.Distinct()) { - this.Monitor.VerboseLog($"Ignored '{messageType}' broadcast from mod {fromModID}: none of the specified player IDs are connected."); - return; + if (id == curPlayerId) + sendToSelf = true; + else if (this.Peers.TryGetValue(id, out MultiplayerPeer peer) && peer.HasSmapi) + sendToPeers.Add(peer); } } + // filter by mod ID + if (toModIDs != null) + { + HashSet sendToMods = new HashSet(toModIDs, StringComparer.InvariantCultureIgnoreCase); + if (sendToSelf && toModIDs.All(id => this.ModRegistry.Get(id) == null)) + sendToSelf = false; + + sendToPeers.RemoveAll(peer => peer.Mods.All(mod => !sendToMods.Contains(mod.ID))); + } + + // validate recipients + if (!sendToSelf && !sendToPeers.Any()) + { + this.Monitor.VerboseLog($"Ignored '{messageType}' broadcast from mod {fromModID}: none of the specified player IDs can receive this message."); + return; + } + // get data to send ModMessageModel model = new ModMessageModel( fromPlayerID: Game1.player.UniqueMultiplayerID, fromModID: fromModID, toModIDs: toModIDs, - toPlayerIDs: playerIDs?.ToArray(), + toPlayerIDs: sendToPeers.Select(p => p.PlayerID).ToArray(), type: messageType, data: JToken.FromObject(message) ); string data = JsonConvert.SerializeObject(model, Formatting.None); - // log message - if (this.LogNetworkTraffic) - this.Monitor.Log($"Broadcasting '{messageType}' message: {data}.", LogLevel.Trace); + // send self-message + if (sendToSelf) + { + if (this.LogNetworkTraffic) + this.Monitor.Log($"Broadcasting '{messageType}' message to self: {data}.", LogLevel.Trace); + + this.OnModMessageReceived(model); + } - // send message - if (Context.IsMainPlayer) + // send message to peers + if (sendToPeers.Any()) { - foreach (MultiplayerPeer peer in this.Peers.Values) + if (Context.IsMainPlayer) { - if (playerIDs == null || playerIDs.Contains(peer.PlayerID)) + foreach (MultiplayerPeer peer in sendToPeers) { - model.ToPlayerIDs = new[] { peer.PlayerID }; + if (this.LogNetworkTraffic) + this.Monitor.Log($"Broadcasting '{messageType}' message to farmhand {peer.PlayerID}: {data}.", LogLevel.Trace); + peer.SendMessage(new OutgoingMessage((byte)MessageType.ModMessage, peer.PlayerID, data)); } } - } - else if (this.HostPeer != null && this.HostPeer.HasSmapi) - this.HostPeer.SendMessage(new OutgoingMessage((byte)MessageType.ModMessage, this.HostPeer.PlayerID, data)); - else - this.Monitor.VerboseLog(" Can't send message because no valid connections were found."); + else if (this.HostPeer?.HasSmapi == true) + { + if (this.LogNetworkTraffic) + this.Monitor.Log($"Broadcasting '{messageType}' message to host {this.HostPeer.PlayerID}: {data}.", LogLevel.Trace); + this.HostPeer.SendMessage(new OutgoingMessage((byte)MessageType.ModMessage, this.HostPeer.PlayerID, data)); + } + else + this.Monitor.VerboseLog(" Can't send message because no valid connections were found."); + } } -- cgit