summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI/Framework/SGame.cs
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2017-10-07 23:20:36 -0400
committerJesse Plamondon-Willard <github@jplamondonw.com>2017-10-07 23:20:36 -0400
commitd0dd2f7ba729de6be749d326a2fed78988ba9d7b (patch)
treea22127da6a8900e9f29bbb847bfd5d3347f6b952 /src/StardewModdingAPI/Framework/SGame.cs
parent7889676ea24cafc945899bf25608784e3f5bc9e0 (diff)
parent5928f5f86c4493ddb6b89bce0b7d0fb73a884c09 (diff)
downloadSMAPI-d0dd2f7ba729de6be749d326a2fed78988ba9d7b.tar.gz
SMAPI-d0dd2f7ba729de6be749d326a2fed78988ba9d7b.tar.bz2
SMAPI-d0dd2f7ba729de6be749d326a2fed78988ba9d7b.zip
Merge branch 'add-mod-build-config' into develop
Diffstat (limited to 'src/StardewModdingAPI/Framework/SGame.cs')
-rw-r--r--src/StardewModdingAPI/Framework/SGame.cs1403
1 files changed, 0 insertions, 1403 deletions
diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs
deleted file mode 100644
index 7287cab7..00000000
--- a/src/StardewModdingAPI/Framework/SGame.cs
+++ /dev/null
@@ -1,1403 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-using Microsoft.Xna.Framework.Input;
-using StardewModdingAPI.Events;
-using StardewModdingAPI.Framework.Reflection;
-using StardewModdingAPI.Framework.Utilities;
-using StardewModdingAPI.Utilities;
-using StardewValley;
-using StardewValley.BellsAndWhistles;
-using StardewValley.Locations;
-using StardewValley.Menus;
-using StardewValley.Tools;
-using xTile.Dimensions;
-using xTile.Layers;
-
-namespace StardewModdingAPI.Framework
-{
- /// <summary>SMAPI's extension of the game's core <see cref="Game1"/>, used to inject events.</summary>
- internal class SGame : Game1
- {
- /*********
- ** Properties
- *********/
- /****
- ** SMAPI state
- ****/
- /// <summary>Encapsulates monitoring and logging.</summary>
- private readonly IMonitor Monitor;
-
- /// <summary>The maximum number of consecutive attempts SMAPI should make to recover from a draw error.</summary>
- private readonly Countdown DrawCrashTimer = new Countdown(60); // 60 ticks = roughly one second
-
- /// <summary>The maximum number of consecutive attempts SMAPI should make to recover from an update error.</summary>
- private readonly Countdown UpdateCrashTimer = new Countdown(60); // 60 ticks = roughly one second
-
- /// <summary>The number of ticks until SMAPI should notify mods that the game has loaded.</summary>
- /// <remarks>Skipping a few frames ensures the game finishes initialising the world before mods try to change it.</remarks>
- private int AfterLoadTimer = 5;
-
- /// <summary>Whether the game is returning to the menu.</summary>
- private bool IsExitingToTitle;
-
- /// <summary>Whether the game is saving and SMAPI has already raised <see cref="SaveEvents.BeforeSave"/>.</summary>
- private bool IsBetweenSaveEvents;
-
- /****
- ** Game state
- ****/
- /// <summary>A record of the buttons pressed as of the previous tick.</summary>
- private SButton[] PreviousPressedButtons = new SButton[0];
-
- /// <summary>A record of the keyboard state (i.e. the up/down state for each button) as of the previous tick.</summary>
- private KeyboardState PreviousKeyState;
-
- /// <summary>A record of the controller state (i.e. the up/down state for each button) as of the previous tick.</summary>
- private GamePadState PreviousControllerState;
-
- /// <summary>A record of the mouse state (i.e. the cursor position, scroll amount, and the up/down state for each button) as of the previous tick.</summary>
- private MouseState PreviousMouseState;
-
- /// <summary>The previous mouse position on the screen adjusted for the zoom level.</summary>
- private Point PreviousMousePosition;
-
- /// <summary>The window size value at last check.</summary>
- private Point PreviousWindowSize;
-
- /// <summary>The save ID at last check.</summary>
- private ulong PreviousSaveID;
-
- /// <summary>A hash of <see cref="Game1.locations"/> at last check.</summary>
- private int PreviousGameLocations;
-
- /// <summary>A hash of the current location's <see cref="GameLocation.objects"/> at last check.</summary>
- private int PreviousLocationObjects;
-
- /// <summary>The player's inventory at last check.</summary>
- private IDictionary<Item, int> PreviousItems;
-
- /// <summary>The player's combat skill level at last check.</summary>
- private int PreviousCombatLevel;
-
- /// <summary>The player's farming skill level at last check.</summary>
- private int PreviousFarmingLevel;
-
- /// <summary>The player's fishing skill level at last check.</summary>
- private int PreviousFishingLevel;
-
- /// <summary>The player's foraging skill level at last check.</summary>
- private int PreviousForagingLevel;
-
- /// <summary>The player's mining skill level at last check.</summary>
- private int PreviousMiningLevel;
-
- /// <summary>The player's luck skill level at last check.</summary>
- private int PreviousLuckLevel;
-
- /// <summary>The player's location at last check.</summary>
- private GameLocation PreviousGameLocation;
-
- /// <summary>The active game menu at last check.</summary>
- private IClickableMenu PreviousActiveMenu;
-
- /// <summary>The mine level at last check.</summary>
- private int PreviousMineLevel;
-
- /// <summary>The time of day (in 24-hour military format) at last check.</summary>
- private int PreviousTime;
-
- /// <summary>The previous content locale.</summary>
- private LocalizedContentManager.LanguageCode? PreviousLocale;
-
- /// <summary>An index incremented on every tick and reset every 60th tick (0–59).</summary>
- private int CurrentUpdateTick;
-
- /// <summary>Whether this is the very first update tick since the game started.</summary>
- private bool FirstUpdate;
-
- /// <summary>The current game instance.</summary>
- private static SGame Instance;
-
- /****
- ** Private wrappers
- ****/
- /// <summary>Simplifies access to private game code.</summary>
- private static Reflector Reflection;
-
- // ReSharper disable ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming
- /// <summary>Used to access private fields and methods.</summary>
- private static List<float> _fpsList => SGame.Reflection.GetPrivateField<List<float>>(typeof(Game1), nameof(_fpsList)).GetValue();
- private static Stopwatch _fpsStopwatch => SGame.Reflection.GetPrivateField<Stopwatch>(typeof(Game1), nameof(SGame._fpsStopwatch)).GetValue();
- private static float _fps
- {
- set => SGame.Reflection.GetPrivateField<float>(typeof(Game1), nameof(_fps)).SetValue(value);
- }
- private static Task _newDayTask => SGame.Reflection.GetPrivateField<Task>(typeof(Game1), nameof(_newDayTask)).GetValue();
- private Color bgColor => SGame.Reflection.GetPrivateField<Color>(this, nameof(bgColor)).GetValue();
- public RenderTarget2D screenWrapper => SGame.Reflection.GetPrivateProperty<RenderTarget2D>(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop
- public BlendState lightingBlend => SGame.Reflection.GetPrivateField<BlendState>(this, nameof(lightingBlend)).GetValue();
- private readonly Action drawFarmBuildings = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(drawFarmBuildings)).Invoke();
- private readonly Action drawHUD = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(drawHUD)).Invoke();
- private readonly Action drawDialogueBox = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(drawDialogueBox)).Invoke();
- private readonly Action renderScreenBuffer = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(renderScreenBuffer)).Invoke();
- // ReSharper restore ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>SMAPI's content manager.</summary>
- public SContentManager SContentManager { get; }
-
- /// <summary>Whether SMAPI should log more information about the game context.</summary>
- public bool VerboseLogging { get; set; }
-
-
- /*********
- ** Protected methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="monitor">Encapsulates monitoring and logging.</param>
- /// <param name="reflection">Simplifies access to private game code.</param>
- internal SGame(IMonitor monitor, Reflector reflection)
- {
- // initialise
- this.Monitor = monitor;
- this.FirstUpdate = true;
- SGame.Instance = this;
- SGame.Reflection = reflection;
-
- // set XNA option required by Stardew Valley
- Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef;
-
- // override content manager
- this.Monitor?.Log("Overriding content manager...", LogLevel.Trace);
- this.SContentManager = new SContentManager(this.Content.ServiceProvider, this.Content.RootDirectory, Thread.CurrentThread.CurrentUICulture, null, this.Monitor);
- this.Content = new ContentManagerShim(this.SContentManager, "SGame.Content");
- Game1.content = new ContentManagerShim(this.SContentManager, "Game1.content");
- reflection.GetPrivateField<LocalizedContentManager>(typeof(Game1), "_temporaryContent").SetValue(new ContentManagerShim(this.SContentManager, "Game1._temporaryContent")); // regenerate value with new content manager
- }
-
- /****
- ** Intercepted methods & events
- ****/
- /// <summary>Constructor a content manager to read XNB files.</summary>
- /// <param name="serviceProvider">The service provider to use to locate services.</param>
- /// <param name="rootDirectory">The root directory to search for content.</param>
- protected override LocalizedContentManager CreateContentManager(IServiceProvider serviceProvider, string rootDirectory)
- {
- // return default if SMAPI's content manager isn't initialised yet
- if (this.SContentManager == null)
- {
- this.Monitor?.Log("SMAPI's content manager isn't initialised; skipping content manager interception.", LogLevel.Trace);
- return base.CreateContentManager(serviceProvider, rootDirectory);
- }
-
- // return single instance if valid
- if (serviceProvider != this.Content.ServiceProvider)
- throw new InvalidOperationException("SMAPI uses a single content manager internally. You can't get a new content manager with a different service provider.");
- if (rootDirectory != this.Content.RootDirectory)
- throw new InvalidOperationException($"SMAPI uses a single content manager internally. You can't get a new content manager with a different root directory (current is {this.Content.RootDirectory}, requested {rootDirectory}).");
- return new ContentManagerShim(this.SContentManager, "(generated instance)");
- }
-
- /// <summary>The method called when the game is updating its state. This happens roughly 60 times per second.</summary>
- /// <param name="gameTime">A snapshot of the game timing state.</param>
- protected override void Update(GameTime gameTime)
- {
- try
- {
- /*********
- ** Skip conditions
- *********/
- // SMAPI exiting, stop processing game updates
- if (this.Monitor.IsExiting)
- {
- this.Monitor.Log("SMAPI shutting down: aborting update.", LogLevel.Trace);
- return;
- }
-
- // While a background new-day task is in progress, the game skips its own update logic
- // and defers to the XNA Update method. Running mod code in parallel to the background
- // update is risky, because data changes can conflict (e.g. collection changed during
- // enumeration errors) and data may change unexpectedly from one mod instruction to the
- // next.
- //
- // Therefore we can just run Game1.Update here without raising any SMAPI events. There's
- // a small chance that the task will finish after we defer but before the game checks,
- // which means technically events should be raised, but the effects of missing one
- // update tick are neglible and not worth the complications of bypassing Game1.Update.
- if (SGame._newDayTask != null)
- {
- base.Update(gameTime);
- return;
- }
-
- // While the game is writing to the save file in the background, mods can unexpectedly
- // fail since they don't have exclusive access to resources (e.g. collection changed
- // during enumeration errors). To avoid problems, events are not invoked while a save
- // is in progress. It's safe to raise SaveEvents.BeforeSave as soon as the menu is
- // opened (since the save hasn't started yet), but all other events should be suppressed.
- if (Context.IsSaving)
- {
- // raise before-save
- if (!this.IsBetweenSaveEvents)
- {
- this.IsBetweenSaveEvents = true;
- this.Monitor.Log("Context: before save.", LogLevel.Trace);
- SaveEvents.InvokeBeforeSave(this.Monitor);
- }
-
- // suppress non-save events
- base.Update(gameTime);
- return;
- }
- if (this.IsBetweenSaveEvents)
- {
- // raise after-save
- this.IsBetweenSaveEvents = false;
- this.Monitor.Log($"Context: after save, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
- SaveEvents.InvokeAfterSave(this.Monitor);
- TimeEvents.InvokeAfterDayStarted(this.Monitor);
- }
-
- /*********
- ** Game loaded events
- *********/
- if (this.FirstUpdate)
- {
- GameEvents.InvokeInitialize(this.Monitor);
- }
-
- /*********
- ** Locale changed events
- *********/
- if (this.PreviousLocale != LocalizedContentManager.CurrentLanguageCode)
- {
- var oldValue = this.PreviousLocale;
- var newValue = LocalizedContentManager.CurrentLanguageCode;
-
- this.Monitor.Log($"Context: locale set to {newValue}.", LogLevel.Trace);
-
- if (oldValue != null)
- ContentEvents.InvokeAfterLocaleChanged(this.Monitor, oldValue.ToString(), newValue.ToString());
- this.PreviousLocale = newValue;
- }
-
- /*********
- ** After load events
- *********/
- if (Context.IsSaveLoaded && !SaveGame.IsProcessing /*still loading save*/ && this.AfterLoadTimer >= 0)
- {
- if (Game1.dayOfMonth != 0) // wait until new-game intro finishes (world not fully initialised yet)
- this.AfterLoadTimer--;
-
- if (this.AfterLoadTimer == 0)
- {
- this.Monitor.Log($"Context: loaded saved game '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
- Context.IsWorldReady = true;
-
- SaveEvents.InvokeAfterLoad(this.Monitor);
- TimeEvents.InvokeAfterDayStarted(this.Monitor);
- }
- }
-
- /*********
- ** Exit to title events
- *********/
- // before exit to title
- if (Game1.exitToTitle)
- this.IsExitingToTitle = true;
-
- // after exit to title
- if (Context.IsWorldReady && this.IsExitingToTitle && Game1.activeClickableMenu is TitleMenu)
- {
- this.Monitor.Log("Context: returned to title", LogLevel.Trace);
-
- this.IsExitingToTitle = false;
- this.CleanupAfterReturnToTitle();
- SaveEvents.InvokeAfterReturnToTitle(this.Monitor);
- }
-
- /*********
- ** Window events
- *********/
- // Here we depend on the game's viewport instead of listening to the Window.Resize
- // event because we need to notify mods after the game handles the resize, so the
- // game's metadata (like Game1.viewport) are updated. That's a bit complicated
- // since the game adds & removes its own handler on the fly.
- if (Game1.viewport.Width != this.PreviousWindowSize.X || Game1.viewport.Height != this.PreviousWindowSize.Y)
- {
- Point size = new Point(Game1.viewport.Width, Game1.viewport.Height);
- GraphicsEvents.InvokeResize(this.Monitor);
- this.PreviousWindowSize = size;
- }
-
- /*********
- ** Input events (if window has focus)
- *********/
- if (Game1.game1.IsActive)
- {
- // get latest state
- KeyboardState keyState;
- GamePadState controllerState;
- MouseState mouseState;
- Point mousePosition;
- try
- {
- keyState = Keyboard.GetState();
- controllerState = GamePad.GetState(PlayerIndex.One);
- mouseState = Mouse.GetState();
- mousePosition = new Point(Game1.getMouseX(), Game1.getMouseY());
- }
- catch (InvalidOperationException) // GetState() may crash for some players if window doesn't have focus but game1.IsActive == true
- {
- keyState = this.PreviousKeyState;
- controllerState = this.PreviousControllerState;
- mouseState = this.PreviousMouseState;
- mousePosition = this.PreviousMousePosition;
- }
-
- // analyse state
- SButton[] currentlyPressedKeys = this.GetPressedButtons(keyState, mouseState, controllerState).ToArray();
- SButton[] previousPressedKeys = this.PreviousPressedButtons;
- SButton[] framePressedKeys = currentlyPressedKeys.Except(previousPressedKeys).ToArray();
- SButton[] frameReleasedKeys = previousPressedKeys.Except(currentlyPressedKeys).ToArray();
- bool isClick = framePressedKeys.Contains(SButton.MouseLeft) || (framePressedKeys.Contains(SButton.ControllerA) && !currentlyPressedKeys.Contains(SButton.ControllerX));
-
- // get cursor position
- ICursorPosition cursor;
- {
- // cursor position
- Vector2 screenPixels = new Vector2(Game1.getMouseX(), Game1.getMouseY());
- Vector2 tile = new Vector2((Game1.viewport.X + screenPixels.X) / Game1.tileSize, (Game1.viewport.Y + screenPixels.Y) / Game1.tileSize);
- Vector2 grabTile = (Game1.mouseCursorTransparency > 0 && Utility.tileWithinRadiusOfPlayer((int)tile.X, (int)tile.Y, 1, Game1.player)) // derived from Game1.pressActionButton
- ? tile
- : Game1.player.GetGrabTile();
- cursor = new CursorPosition(screenPixels, tile, grabTile);
- }
-
- // raise button pressed
- foreach (SButton button in framePressedKeys)
- {
- InputEvents.InvokeButtonPressed(this.Monitor, button, cursor, isClick);
-
- // legacy events
- if (button.TryGetKeyboard(out Keys key))
- {
- if (key != Keys.None)
- ControlEvents.InvokeKeyPressed(this.Monitor, key);
- }
- else if (button.TryGetController(out Buttons controllerButton))
- {
- if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger)
- ControlEvents.InvokeTriggerPressed(this.Monitor, controllerButton, controllerButton == Buttons.LeftTrigger ? controllerState.Triggers.Left : controllerState.Triggers.Right);
- else
- ControlEvents.InvokeButtonPressed(this.Monitor, controllerButton);
- }
- }
-
- // raise button released
- foreach (SButton button in frameReleasedKeys)
- {
- bool wasClick =
- (button == SButton.MouseLeft && previousPressedKeys.Contains(SButton.MouseLeft)) // released left click
- || (button == SButton.ControllerA && previousPressedKeys.Contains(SButton.ControllerA) && !previousPressedKeys.Contains(SButton.ControllerX));
- InputEvents.InvokeButtonReleased(this.Monitor, button, cursor, wasClick);
-
- // legacy events
- if (button.TryGetKeyboard(out Keys key))
- {
- if (key != Keys.None)
- ControlEvents.InvokeKeyReleased(this.Monitor, key);
- }
- else if (button.TryGetController(out Buttons controllerButton))
- {
- if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger)
- ControlEvents.InvokeTriggerReleased(this.Monitor, controllerButton, controllerButton == Buttons.LeftTrigger ? controllerState.Triggers.Left : controllerState.Triggers.Right);
- else
- ControlEvents.InvokeButtonReleased(this.Monitor, controllerButton);
- }
- }
-
- // raise legacy state-changed events
- if (keyState != this.PreviousKeyState)
- ControlEvents.InvokeKeyboardChanged(this.Monitor, this.PreviousKeyState, keyState);
- if (mouseState != this.PreviousMouseState)
- ControlEvents.InvokeMouseChanged(this.Monitor, this.PreviousMouseState, mouseState, this.PreviousMousePosition, mousePosition);
-
- // track state
- this.PreviousMouseState = mouseState;
- this.PreviousMousePosition = mousePosition;
- this.PreviousKeyState = keyState;
- this.PreviousControllerState = controllerState;
- this.PreviousPressedButtons = currentlyPressedKeys;
- }
-
- /*********
- ** Menu events
- *********/
- if (Game1.activeClickableMenu != this.PreviousActiveMenu)
- {
- IClickableMenu previousMenu = this.PreviousActiveMenu;
- IClickableMenu newMenu = Game1.activeClickableMenu;
-
- // log context
- if (this.VerboseLogging)
- {
- if (previousMenu == null)
- this.Monitor.Log($"Context: opened menu {newMenu?.GetType().FullName ?? "(none)"}.", LogLevel.Trace);
- else if (newMenu == null)
- this.Monitor.Log($"Context: closed menu {previousMenu.GetType().FullName}.", LogLevel.Trace);
- else
- this.Monitor.Log($"Context: changed menu from {previousMenu.GetType().FullName} to {newMenu.GetType().FullName}.", LogLevel.Trace);
- }
-
- // raise menu events
- if (newMenu != null)
- MenuEvents.InvokeMenuChanged(this.Monitor, previousMenu, newMenu);
- else
- MenuEvents.InvokeMenuClosed(this.Monitor, previousMenu);
-
- // update previous menu
- // (if the menu was changed in one of the handlers, deliberately defer detection until the next update so mods can be notified of the new menu change)
- this.PreviousActiveMenu = newMenu;
- }
-
- /*********
- ** World & player events
- *********/
- if (Context.IsWorldReady)
- {
- // raise current location changed
- if (Game1.currentLocation != this.PreviousGameLocation)
- {
- if (this.VerboseLogging)
- this.Monitor.Log($"Context: set location to {Game1.currentLocation?.Name ?? "(none)"}.", LogLevel.Trace);
- LocationEvents.InvokeCurrentLocationChanged(this.Monitor, this.PreviousGameLocation, Game1.currentLocation);
- }
-
- // raise location list changed
- if (this.GetHash(Game1.locations) != this.PreviousGameLocations)
- LocationEvents.InvokeLocationsChanged(this.Monitor, Game1.locations);
-
- // raise events that shouldn't be triggered on initial load
- if (Game1.uniqueIDForThisGame == this.PreviousSaveID)
- {
- // raise player leveled up a skill
- if (Game1.player.combatLevel != this.PreviousCombatLevel)
- PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Combat, Game1.player.combatLevel);
- if (Game1.player.farmingLevel != this.PreviousFarmingLevel)
- PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Farming, Game1.player.farmingLevel);
- if (Game1.player.fishingLevel != this.PreviousFishingLevel)
- PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Fishing, Game1.player.fishingLevel);
- if (Game1.player.foragingLevel != this.PreviousForagingLevel)
- PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Foraging, Game1.player.foragingLevel);
- if (Game1.player.miningLevel != this.PreviousMiningLevel)
- PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Mining, Game1.player.miningLevel);
- if (Game1.player.luckLevel != this.PreviousLuckLevel)
- PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Luck, Game1.player.luckLevel);
-
- // raise player inventory changed
- ItemStackChange[] changedItems = this.GetInventoryChanges(Game1.player.items, this.PreviousItems).ToArray();
- if (changedItems.Any())
- PlayerEvents.InvokeInventoryChanged(this.Monitor, Game1.player.items, changedItems);
-
- // raise current location's object list changed
- if (this.GetHash(Game1.currentLocation.objects) != this.PreviousLocationObjects)
- LocationEvents.InvokeOnNewLocationObject(this.Monitor, Game1.currentLocation.objects);
-
- // raise time changed
- if (Game1.timeOfDay != this.PreviousTime)
- TimeEvents.InvokeTimeOfDayChanged(this.Monitor, this.PreviousTime, Game1.timeOfDay);
-
- // raise mine level changed
- if (Game1.mine != null && Game1.mine.mineLevel != this.PreviousMineLevel)
- MineEvents.InvokeMineLevelChanged(this.Monitor, this.PreviousMineLevel, Game1.mine.mineLevel);
- }
-
- // update state
- this.PreviousGameLocations = this.GetHash(Game1.locations);
- this.PreviousGameLocation = Game1.currentLocation;
- this.PreviousCombatLevel = Game1.player.combatLevel;
- this.PreviousFarmingLevel = Game1.player.farmingLevel;
- this.PreviousFishingLevel = Game1.player.fishingLevel;
- this.PreviousForagingLevel = Game1.player.foragingLevel;
- this.PreviousMiningLevel = Game1.player.miningLevel;
- this.PreviousLuckLevel = Game1.player.luckLevel;
- this.PreviousItems = Game1.player.items.Where(n => n != null).ToDictionary(n => n, n => n.Stack);
- this.PreviousLocationObjects = this.GetHash(Game1.currentLocation.objects);
- this.PreviousTime = Game1.timeOfDay;
- this.PreviousMineLevel = Game1.mine?.mineLevel ?? 0;
- this.PreviousSaveID = Game1.uniqueIDForThisGame;
- }
-
- /*********
- ** Game update
- *********/
- try
- {
- base.Update(gameTime);
- }
- catch (Exception ex)
- {
- this.Monitor.Log($"An error occured in the base update loop: {ex.GetLogSummary()}", LogLevel.Error);
- }
-
- /*********
- ** Update events
- *********/
- GameEvents.InvokeUpdateTick(this.Monitor);
- if (this.FirstUpdate)
- this.FirstUpdate = false;
- if (this.CurrentUpdateTick % 2 == 0)
- GameEvents.InvokeSecondUpdateTick(this.Monitor);
- if (this.CurrentUpdateTick % 4 == 0)
- GameEvents.InvokeFourthUpdateTick(this.Monitor);
- if (this.CurrentUpdateTick % 8 == 0)
- GameEvents.InvokeEighthUpdateTick(this.Monitor);
- if (this.CurrentUpdateTick % 15 == 0)
- GameEvents.InvokeQuarterSecondTick(this.Monitor);
- if (this.CurrentUpdateTick % 30 == 0)
- GameEvents.InvokeHalfSecondTick(this.Monitor);
- if (this.CurrentUpdateTick % 60 == 0)
- GameEvents.InvokeOneSecondTick(this.Monitor);
- this.CurrentUpdateTick += 1;
- if (this.CurrentUpdateTick >= 60)
- this.CurrentUpdateTick = 0;
-
- this.UpdateCrashTimer.Reset();
- }
- catch (Exception ex)
- {
- // log error
- this.Monitor.Log($"An error occured in the overridden update loop: {ex.GetLogSummary()}", LogLevel.Error);
-
- // exit if irrecoverable
- if (!this.UpdateCrashTimer.Decrement())
- this.Monitor.ExitGameImmediately("the game crashed when updating, and SMAPI was unable to recover the game.");
- }
- }
-
- /// <summary>The method called to draw everything to the screen.</summary>
- /// <param name="gameTime">A snapshot of the game timing state.</param>
- protected override void Draw(GameTime gameTime)
- {
- Context.IsInDrawLoop = true;
- try
- {
- this.DrawImpl(gameTime);
- this.DrawCrashTimer.Reset();
- }
- catch (Exception ex)
- {
- // log error
- this.Monitor.Log($"An error occured in the overridden draw loop: {ex.GetLogSummary()}", LogLevel.Error);
-
- // exit if irrecoverable
- if (!this.DrawCrashTimer.Decrement())
- {
- this.Monitor.ExitGameImmediately("the game crashed when drawing, and SMAPI was unable to recover the game.");
- return;
- }
-
- // recover sprite batch
- try
- {
- if (Game1.spriteBatch.IsOpen(SGame.Reflection))
- {
- this.Monitor.Log("Recovering sprite batch from error...", LogLevel.Trace);
- Game1.spriteBatch.End();
- }
- }
- catch (Exception innerEx)
- {
- this.Monitor.Log($"Could not recover sprite batch state: {innerEx.GetLogSummary()}", LogLevel.Error);
- }
- }
- Context.IsInDrawLoop = false;
- }
-
- /// <summary>Replicate the game's draw logic with some changes for SMAPI.</summary>
- /// <param name="gameTime">A snapshot of the game timing state.</param>
- /// <remarks>This implementation is identical to <see cref="Game1.Draw"/>, except for try..catch around menu draw code, private field references replaced by wrappers, and added events.</remarks>
- [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "copied from game code as-is")]
- [SuppressMessage("ReSharper", "LocalVariableHidesMember", Justification = "copied from game code as-is")]
- [SuppressMessage("ReSharper", "PossibleLossOfFraction", Justification = "copied from game code as-is")]
- [SuppressMessage("ReSharper", "RedundantArgumentDefaultValue", Justification = "copied from game code as-is")]
- [SuppressMessage("ReSharper", "RedundantCast", Justification = "copied from game code as-is")]
- [SuppressMessage("ReSharper", "RedundantExplicitNullableCreation", Justification = "copied from game code as-is")]
- [SuppressMessage("ReSharper", "RedundantTypeArgumentsOfMethod", Justification = "copied from game code as-is")]
- private void DrawImpl(GameTime gameTime)
- {
- if (Game1.debugMode)
- {
- if (SGame._fpsStopwatch.IsRunning)
- {
- float totalSeconds = (float)SGame._fpsStopwatch.Elapsed.TotalSeconds;
- SGame._fpsList.Add(totalSeconds);
- while (SGame._fpsList.Count >= 120)
- SGame._fpsList.RemoveAt(0);
- float num = 0.0f;
- foreach (float fps in SGame._fpsList)
- num += fps;
- SGame._fps = (float)(1.0 / ((double)num / (double)SGame._fpsList.Count));
- }
- SGame._fpsStopwatch.Restart();
- }
- else
- {
- if (SGame._fpsStopwatch.IsRunning)
- SGame._fpsStopwatch.Reset();
- SGame._fps = 0.0f;
- SGame._fpsList.Clear();
- }
- if (SGame._newDayTask != null)
- {
- this.GraphicsDevice.Clear(this.bgColor);
- //base.Draw(gameTime);
- }
- else
- {
- if ((double)Game1.options.zoomLevel != 1.0)
- this.GraphicsDevice.SetRenderTarget(this.screenWrapper);
- if (this.IsSaving)
- {
- this.GraphicsDevice.Clear(this.bgColor);
- IClickableMenu activeClickableMenu = Game1.activeClickableMenu;
- if (activeClickableMenu != null)
- {
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
- try
- {
- GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor);
- activeClickableMenu.draw(Game1.spriteBatch);
- GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor);
- }
- catch (Exception ex)
- {
- this.Monitor.Log($"The {activeClickableMenu.GetType().FullName} menu crashed while drawing itself during save. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error);
- activeClickableMenu.exitThisMenu();
- }
- Game1.spriteBatch.End();
- }
- //base.Draw(gameTime);
- this.renderScreenBuffer();
- }
- else
- {
- this.GraphicsDevice.Clear(this.bgColor);
- if (Game1.activeClickableMenu != null && Game1.options.showMenuBackground && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet())
- {
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
- try
- {
- Game1.activeClickableMenu.drawBackground(Game1.spriteBatch);
- GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor);
- Game1.activeClickableMenu.draw(Game1.spriteBatch);
- GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor);
- }
- catch (Exception ex)
- {
- this.Monitor.Log($"The {Game1.activeClickableMenu.GetType().FullName} menu crashed while drawing itself. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error);
- Game1.activeClickableMenu.exitThisMenu();
- }
- Game1.spriteBatch.End();
- if ((double)Game1.options.zoomLevel != 1.0)
- {
- this.GraphicsDevice.SetRenderTarget((RenderTarget2D)null);
- this.GraphicsDevice.Clear(this.bgColor);
- Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone);
- Game1.spriteBatch.Draw((Texture2D)this.screenWrapper, Vector2.Zero, new Microsoft.Xna.Framework.Rectangle?(this.screenWrapper.Bounds), Color.White, 0.0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f);
- Game1.spriteBatch.End();
- }
- if (Game1.overlayMenu == null)
- return;
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
- Game1.overlayMenu.draw(Game1.spriteBatch);
- Game1.spriteBatch.End();
- }
- else if ((int)Game1.gameMode == 11)
- {
- Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
- Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3685"), new Vector2(16f, 16f), Color.HotPink);
- Game1.spriteBatch.DrawString(Game1.dialog