using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Events;
using StardewModdingAPI.Framework.Input;
using StardewModdingAPI.Framework.Reflection;
using StardewValley;
namespace StardewModdingAPI.Framework
{
/// SMAPI's extension of the game's core , used to inject SMAPI components.
internal class SGameRunner : GameRunner
{
/*********
** Fields
*********/
/// Encapsulates monitoring and logging for SMAPI.
private readonly Monitor Monitor;
/// Manages SMAPI events for mods.
private readonly EventManager Events;
/// Simplifies access to private game code.
private readonly Reflector Reflection;
/// Immediately exit the game without saving. This should only be invoked when an irrecoverable fatal error happens that risks save corruption or game-breaking bugs.
private readonly Action ExitGameImmediately;
/// The core SMAPI mod hooks.
private readonly SModHooks ModHooks;
/// The core multiplayer logic.
private readonly SMultiplayer Multiplayer;
/// Raised after the game finishes loading its initial content.
private readonly Action OnGameContentLoaded;
/// Raised when XNA is updating (roughly 60 times per second).
private readonly Action OnGameUpdating;
/// Raised when the game instance for a local split-screen player is updating (once per per player).
private readonly Action OnPlayerInstanceUpdating;
/// Raised before the game exits.
private readonly Action OnGameExiting;
/*********
** Public methods
*********/
/// The singleton instance.
public static SGameRunner Instance => (SGameRunner)GameRunner.instance;
/*********
** Public methods
*********/
/// Construct an instance.
/// Encapsulates monitoring and logging for SMAPI.
/// Simplifies access to private game code.
/// Manages SMAPI events for mods.
/// Handles mod hooks provided by the game.
/// The core multiplayer logic.
/// Immediately exit the game without saving. This should only be invoked when an irrecoverable fatal error happens that risks save corruption or game-breaking bugs.
/// Raised after the game finishes loading its initial content.
/// Raised when XNA is updating its state (roughly 60 times per second).
/// Raised when the game instance for a local split-screen player is updating (once per per player).
/// Raised before the game exits.
public SGameRunner(Monitor monitor, Reflector reflection, EventManager eventManager, SModHooks modHooks, SMultiplayer multiplayer, Action exitGameImmediately, Action onGameContentLoaded, Action onGameUpdating, Action onPlayerInstanceUpdating, Action onGameExiting)
{
// init XNA
Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef;
// hook into game
this.ModHooks = modHooks;
// init SMAPI
this.Monitor = monitor;
this.Events = eventManager;
this.Reflection = reflection;
this.Multiplayer = multiplayer;
this.ExitGameImmediately = exitGameImmediately;
this.OnGameContentLoaded = onGameContentLoaded;
this.OnGameUpdating = onGameUpdating;
this.OnPlayerInstanceUpdating = onPlayerInstanceUpdating;
this.OnGameExiting = onGameExiting;
}
/// Create a game instance for a local player.
/// The player index.
/// The instance index.
public override Game1 CreateGameInstance(PlayerIndex playerIndex = PlayerIndex.One, int instanceIndex = 0)
{
SInputState inputState = new();
return new SGame(playerIndex, instanceIndex, this.Monitor, this.Reflection, this.Events, inputState, this.ModHooks, this.Multiplayer, this.ExitGameImmediately, this.OnPlayerInstanceUpdating, this.OnGameContentLoaded);
}
///
public override void AddGameInstance(PlayerIndex playerIndex)
{
base.AddGameInstance(playerIndex);
EarlyConstants.LogScreenId = Context.ScreenId;
this.UpdateForSplitScreenChanges();
}
///
public override void RemoveGameInstance(Game1 gameInstance)
{
base.RemoveGameInstance(gameInstance);
if (this.gameInstances.Count <= 1)
EarlyConstants.LogScreenId = null;
this.UpdateForSplitScreenChanges();
}
/// Get the screen ID for a given player ID, if the player is local.
/// The player ID to check.
public int? GetScreenId(long playerId)
{
return this.gameInstances
.FirstOrDefault(p => ((SGame)p).PlayerId == playerId)
?.instanceId;
}
/*********
** Protected methods
*********/
/// Perform cleanup logic when the game exits.
/// The event sender.
/// The event args.
/// This overrides the logic in to let SMAPI clean up before exit.
protected override void OnExiting(object sender, EventArgs args)
{
this.OnGameExiting();
}
/// The method called when the game is updating its state (roughly 60 times per second).
/// A snapshot of the game timing state.
protected override void Update(GameTime gameTime)
{
this.OnGameUpdating(gameTime, () => base.Update(gameTime));
}
/// Update metadata when a split screen is added or removed.
private void UpdateForSplitScreenChanges()
{
HashSet oldScreenIds = new(Context.ActiveScreenIds);
// track active screens
Context.ActiveScreenIds.Clear();
foreach (var screen in this.gameInstances)
Context.ActiveScreenIds.Add(screen.instanceId);
// remember last removed screen
foreach (int id in oldScreenIds)
{
if (!Context.ActiveScreenIds.Contains(id))
Context.LastRemovedScreenId = id;
}
}
}
}