From 2e8c7e06c5c46834b570b667cb7497fe4cc408ac Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 20 Dec 2020 22:34:59 -0500 Subject: update for split-screen mode This includes splitting GameRunner (the main game instance) from Game1 (now a per-screen game state), adding a PerScreen utility to simplify per-screen values, adding separate per-screen input handling and events, adding new Context fields for split-screen, and logging the screen ID in split-screen mode to distinguish log entries. --- src/SMAPI/Framework/SGameRunner.cs | 156 +++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/SMAPI/Framework/SGameRunner.cs (limited to 'src/SMAPI/Framework/SGameRunner.cs') diff --git a/src/SMAPI/Framework/SGameRunner.cs b/src/SMAPI/Framework/SGameRunner.cs new file mode 100644 index 00000000..ae06f513 --- /dev/null +++ b/src/SMAPI/Framework/SGameRunner.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +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 + *********/ + /// 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 SInputState(); + return new SGame(playerIndex, instanceIndex, this.Monitor, this.Reflection, this.Events, inputState, this.ModHooks, this.Multiplayer, this.ExitGameImmediately, this.OnPlayerInstanceUpdating); + } + + /// + public override void AddGameInstance(PlayerIndex playerIndex) + { + base.AddGameInstance(playerIndex); + + EarlyConstants.LogScreenId = Context.ScreenId; + this.UpdateForSplitScreenChanges(); + } + + /// + public override void RemoveGameInstance(Game1 instance) + { + base.RemoveGameInstance(instance); + + if (this.gameInstances.Count <= 1) + EarlyConstants.LogScreenId = null; + this.UpdateForSplitScreenChanges(); + } + + + /********* + ** Protected methods + *********/ + /// Load content when the game is launched. + protected override void LoadContent() + { + base.LoadContent(); + + this.OnGameContentLoaded(); + } + + /// 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)); + } + + private void UpdateForSplitScreenChanges() + { + HashSet oldScreenIds = new HashSet(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; + } + } + } +} -- cgit