add split-screen info to multiplayer peer

This commit is contained in:
Jesse Plamondon-Willard 2021-01-15 18:48:32 -05:00
parent 812251e7ae
commit 56ca0f5e81
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
6 changed files with 84 additions and 8 deletions

View File

@ -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:

View File

@ -24,9 +24,15 @@ namespace StardewModdingAPI.Framework.Networking
/// <inheritdoc />
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; }
@ -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)
{

View File

@ -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
{

View File

@ -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;
@ -46,6 +47,13 @@ namespace StardewModdingAPI.Framework
private readonly Action OnGameExiting;
/*********
** Public methods
*********/
/// <summary>The singleton instance.</summary>
public static SGameRunner Instance => (SGameRunner)GameRunner.instance;
/*********
** Public methods
*********/
@ -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);

View File

@ -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()
{

View File

@ -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; }