diff options
Diffstat (limited to 'src/SMAPI/Framework/SGameRunner.cs')
-rw-r--r-- | src/SMAPI/Framework/SGameRunner.cs | 156 |
1 files changed, 156 insertions, 0 deletions
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 +{ + /// <summary>SMAPI's extension of the game's core <see cref="GameRunner"/>, used to inject SMAPI components.</summary> + internal class SGameRunner : GameRunner + { + /********* + ** Fields + *********/ + /// <summary>Encapsulates monitoring and logging for SMAPI.</summary> + private readonly Monitor Monitor; + + /// <summary>Manages SMAPI events for mods.</summary> + private readonly EventManager Events; + + /// <summary>Simplifies access to private game code.</summary> + private readonly Reflector Reflection; + + /// <summary>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.</summary> + private readonly Action<string> ExitGameImmediately; + + /// <summary>The core SMAPI mod hooks.</summary> + private readonly SModHooks ModHooks; + + /// <summary>The core multiplayer logic.</summary> + private readonly SMultiplayer Multiplayer; + + /// <summary>Raised after the game finishes loading its initial content.</summary> + private readonly Action OnGameContentLoaded; + + /// <summary>Raised when XNA is updating (roughly 60 times per second).</summary> + private readonly Action<GameTime, Action> OnGameUpdating; + + /// <summary>Raised when the game instance for a local split-screen player is updating (once per <see cref="OnGameUpdating"/> per player).</summary> + private readonly Action<SGame, GameTime, Action> OnPlayerInstanceUpdating; + + /// <summary>Raised before the game exits.</summary> + private readonly Action OnGameExiting; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="monitor">Encapsulates monitoring and logging for SMAPI.</param> + /// <param name="reflection">Simplifies access to private game code.</param> + /// <param name="eventManager">Manages SMAPI events for mods.</param> + /// <param name="modHooks">Handles mod hooks provided by the game.</param> + /// <param name="multiplayer">The core multiplayer logic.</param> + /// <param name="exitGameImmediately">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.</param> + /// <param name="onGameContentLoaded">Raised after the game finishes loading its initial content.</param> + /// <param name="onGameUpdating">Raised when XNA is updating its state (roughly 60 times per second).</param> + /// <param name="onPlayerInstanceUpdating">Raised when the game instance for a local split-screen player is updating (once per <see cref="OnGameUpdating"/> per player).</param> + /// <param name="onGameExiting">Raised before the game exits.</param> + public SGameRunner(Monitor monitor, Reflector reflection, EventManager eventManager, SModHooks modHooks, SMultiplayer multiplayer, Action<string> exitGameImmediately, Action onGameContentLoaded, Action<GameTime, Action> onGameUpdating, Action<SGame, GameTime, 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; + } + + /// <summary>Create a game instance for a local player.</summary> + /// <param name="playerIndex">The player index.</param> + /// <param name="instanceIndex">The instance index.</param> + 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); + } + + /// <inheritdoc /> + public override void AddGameInstance(PlayerIndex playerIndex) + { + base.AddGameInstance(playerIndex); + + EarlyConstants.LogScreenId = Context.ScreenId; + this.UpdateForSplitScreenChanges(); + } + + /// <inheritdoc /> + public override void RemoveGameInstance(Game1 instance) + { + base.RemoveGameInstance(instance); + + if (this.gameInstances.Count <= 1) + EarlyConstants.LogScreenId = null; + this.UpdateForSplitScreenChanges(); + } + + + /********* + ** Protected methods + *********/ + /// <summary>Load content when the game is launched.</summary> + protected override void LoadContent() + { + base.LoadContent(); + + this.OnGameContentLoaded(); + } + + /// <summary>Perform cleanup logic when the game exits.</summary> + /// <param name="sender">The event sender.</param> + /// <param name="args">The event args.</param> + /// <remarks>This overrides the logic in <see cref="Game1.exitEvent"/> to let SMAPI clean up before exit.</remarks> + protected override void OnExiting(object sender, EventArgs args) + { + this.OnGameExiting(); + } + + /// <summary>The method called when the game is updating its state (roughly 60 times per second).</summary> + /// <param name="gameTime">A snapshot of the game timing state.</param> + protected override void Update(GameTime gameTime) + { + this.OnGameUpdating(gameTime, () => base.Update(gameTime)); + } + + private void UpdateForSplitScreenChanges() + { + HashSet<int> oldScreenIds = new HashSet<int>(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; + } + } + } +} |