add split-screen info to multiplayer peer
This commit is contained in:
parent
812251e7ae
commit
56ca0f5e81
|
@ -10,6 +10,7 @@
|
||||||
## Upcoming release
|
## Upcoming release
|
||||||
* For modders:
|
* 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 `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.
|
* 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:
|
* For the Error Handler mod:
|
||||||
|
|
|
@ -24,9 +24,15 @@ namespace StardewModdingAPI.Framework.Networking
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsHost { get; }
|
public bool IsHost { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsSplitScreen => this.ScreenID != null;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool HasSmapi => this.ApiVersion != null;
|
public bool HasSmapi => this.ApiVersion != null;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int? ScreenID { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public GamePlatform? Platform { get; }
|
public GamePlatform? Platform { get; }
|
||||||
|
|
||||||
|
@ -45,12 +51,14 @@ namespace StardewModdingAPI.Framework.Networking
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="playerID">The player's unique ID.</param>
|
/// <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="model">The metadata to copy.</param>
|
||||||
/// <param name="sendMessage">A method which sends a message to the peer.</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>
|
/// <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.PlayerID = playerID;
|
||||||
|
this.ScreenID = screenID;
|
||||||
this.IsHost = isHost;
|
this.IsHost = isHost;
|
||||||
if (model != null)
|
if (model != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
/// <summary>Whether the game is creating the save file and SMAPI has already raised <see cref="IGameLoopEvents.SaveCreating"/>.</summary>
|
||||||
public bool IsBetweenCreateEvents { get; set; }
|
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>
|
/// <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>
|
/// <remarks>This must be static because the game accesses it before the <see cref="SGame"/> constructor is called.</remarks>
|
||||||
[NonInstancedStatic]
|
[NonInstancedStatic]
|
||||||
|
@ -167,6 +170,7 @@ namespace StardewModdingAPI.Framework
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.OnUpdating(this, gameTime, () => base.Update(gameTime));
|
this.OnUpdating(this, gameTime, () => base.Update(gameTime));
|
||||||
|
this.PlayerId = Game1.player?.UniqueMultiplayerID;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using StardewModdingAPI.Framework.Events;
|
using StardewModdingAPI.Framework.Events;
|
||||||
|
@ -46,6 +47,13 @@ namespace StardewModdingAPI.Framework
|
||||||
private readonly Action OnGameExiting;
|
private readonly Action OnGameExiting;
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>The singleton instance.</summary>
|
||||||
|
public static SGameRunner Instance => (SGameRunner)GameRunner.instance;
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
||||||
*********/
|
*********/
|
||||||
|
@ -99,15 +107,24 @@ namespace StardewModdingAPI.Framework
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void RemoveGameInstance(Game1 instance)
|
public override void RemoveGameInstance(Game1 gameInstance)
|
||||||
{
|
{
|
||||||
base.RemoveGameInstance(instance);
|
base.RemoveGameInstance(gameInstance);
|
||||||
|
|
||||||
if (this.gameInstances.Count <= 1)
|
if (this.gameInstances.Count <= 1)
|
||||||
EarlyConstants.LogScreenId = null;
|
EarlyConstants.LogScreenId = null;
|
||||||
this.UpdateForSplitScreenChanges();
|
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
|
** Protected methods
|
||||||
|
@ -136,6 +153,7 @@ namespace StardewModdingAPI.Framework
|
||||||
this.OnGameUpdating(gameTime, () => base.Update(gameTime));
|
this.OnGameUpdating(gameTime, () => base.Update(gameTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Update metadata when a split screen is added or removed.</summary>
|
||||||
private void UpdateForSplitScreenChanges()
|
private void UpdateForSplitScreenChanges()
|
||||||
{
|
{
|
||||||
HashSet<int> oldScreenIds = new HashSet<int>(Context.ActiveScreenIds);
|
HashSet<int> oldScreenIds = new HashSet<int>(Context.ActiveScreenIds);
|
||||||
|
|
|
@ -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);
|
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
|
// 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))
|
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);
|
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))
|
if (!this.Peers.ContainsKey(message.FarmerID))
|
||||||
{
|
{
|
||||||
this.Monitor.Log($"Received connection for vanilla player {message.FarmerID}.", LogLevel.Trace);
|
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);
|
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);
|
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
|
// 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)
|
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);
|
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)
|
if (!this.Peers.ContainsKey(message.FarmerID) && this.HostPeer == null)
|
||||||
{
|
{
|
||||||
this.Monitor.Log($"Received connection for vanilla host {message.FarmerID}.", LogLevel.Trace);
|
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();
|
resume();
|
||||||
break;
|
break;
|
||||||
|
@ -309,7 +334,13 @@ namespace StardewModdingAPI.Framework
|
||||||
// store peer
|
// store peer
|
||||||
if (!this.Peers.TryGetValue(message.FarmerID, out MultiplayerPeer 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.Monitor.Log($"Received connection for vanilla {(peer.IsHost ? "host" : "farmhand")} {message.FarmerID}.", LogLevel.Trace);
|
||||||
this.AddPeer(peer, canBeHost: true);
|
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>
|
/// <summary>Get all connected player IDs, including the current player.</summary>
|
||||||
private IEnumerable<long> GetKnownPlayerIDs()
|
private IEnumerable<long> GetKnownPlayerIDs()
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,9 +14,16 @@ namespace StardewModdingAPI
|
||||||
/// <summary>Whether this is a connection to the host player.</summary>
|
/// <summary>Whether this is a connection to the host player.</summary>
|
||||||
bool IsHost { get; }
|
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>
|
/// <summary>Whether the player has SMAPI installed.</summary>
|
||||||
bool HasSmapi { get; }
|
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>
|
/// <summary>The player's OS platform, if <see cref="HasSmapi"/> is true.</summary>
|
||||||
GamePlatform? Platform { get; }
|
GamePlatform? Platform { get; }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue