Pathoschild / SMAPI

The modding API for Stardew Valley.
https://smapi.io/
GNU Lesser General Public License v3.0
1.93k stars 267 forks source link

Add multiplayer API #480

Closed Pathoschild closed 6 years ago

Pathoschild commented 6 years ago

Add an API to let mods perform common multiplayer tasks, like getting a multiplayer ID or broadcasting a message.

To do

SMAPI 2.6

SMAPI 2.8-beta.6

SMAPI 2.8-beta.7

Pathoschild commented 6 years ago

A few modders implemented their own networking code, documented here for reference.

PyTK

PyTK provides net APIs for other mods to use.

APIs

Notes

References

SpaceCore

SpaceCore provides low-level net APIs for other mods to use. These are undocumented and presumably intended for spacechase0's mods (though Equivalent Exchange uses them too).

APIs

Notes

References

TehPers.Core.Multiplayer

TehPers.Core.Multiplayer is an upcoming framework that provides net APIs for other mods to use.

APIs

Notes

Ad-hoc

A few mods use the game's underlying APIs directly.

APIs

Notes

Pathoschild commented 6 years ago

Proposed implementation

Here are the tentative APIs in the initial implementation. More features beyond these (like synchronised fields) may be added in future versions.

Send/receive messages

Mods will send data using helper.Multiplayer.SendMessage. The destination can range from very narrow (e.g. one mod on one player computer) to very broad (all mods on all computers).

Method signature:

/// <summary>Send a message to mods installed by connected players.</summary>
/// <typeparam name="T">The data type. This can be a class with a default constructor, or a value type.</typeparam>
/// <param name="message">The data to send over the network.</param>
/// <param name="channel">A message type which receiving mods can use to decide whether it's the one they want to handle, like <c>SetPlayerLocation</c>. This doesn't need to be globally unique, since mods should check the originating mod ID.</param>
/// <param name="modIDs">The mod IDs which should receive the message on the destination computers, or <c>null</c> for all mods. Specifying mod IDs is recommended to improve performance, unless it's a general-purpose broadcast.</param>
/// <param name="playerIDs">The <see cref="Farmer.UniqueMultiplayerID" /> values for the players who should receive the message, or <c>null</c> for all players. If you don't need to broadcast to all players, specifying player IDs is recommended to reduce latency.</param>
/// <exception cref="ArgumentNullException">The <paramref="message" /> or <paramref="messageType" /> is null.</exception>
void SendMessage<TMessage>(TMessage message, string messageType, string[] modIDs = null, long[] playerIDs = null);

For example, this will send the current player's position to the same mod on all other computers:

ExampleData data = new ExampleData(Game1.player.getTileLocation());
helper.Multiplayer.SendMessage(data, "SetPlayerPosition", modIDs: new[] { this.Manifest.UniqueID });

Mods will receive data by hooking into the multiplayer events:

helper.Events.Multiplayer.MessageReceived += this.OnMessageReceived;

private void OnMessageReceived(object sender, MessageReceivedEventArgs e)
{
    if (e.FromModID == this.Manifest.UniqueID && e.MessageType == "SetPlayerPosition")
    {
        ExampleData data = e.ReadAs<ExampleData>();
        Vector2 position = data.Position;
    }
};

Fetch remote installed mods

When a player joins a game, it will broadcast a message containing their basic modding metadata (game/SMAPI versions, OS, mod IDs/versions, etc). Thanks to the game already using ReliableOrdered message mode, SMAPI can guarantee that the info is available before the player finishes connecting. This information will be available to mods using this method:

/// <summary>Get the modding metadata for a connected player.</summary>
/// <param name="playerID">The <see cref="Farmer.UniqueMultiplayerID" /> value for the player whose metadata to fetch.</param>
/// <exception cref="InvalidOperationException">There's no player connected with the given ID.</exception>
IModContext GetRemoteMetadata(long playerID);
Pathoschild commented 6 years ago

Note that syncing mod lists has some privacy implications. For example, someone might have installed a potentially embarrassing mod (e.g. a nudity mod) before playing with friends/family, not realising they may see the mod is installed.

To address that, I suggest adding a message to the co-op screen to the effect of 'other players may see what mods you have', and potentially have a UI to let them uncheck mods in-game they want hidden. That way most mod integrations work, but if they have sensitive mods they can keep those hidden.

Pathoschild commented 6 years ago

Done in develop for the upcoming SMAPI 2.8, and documented on the wiki.