summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--release-notes.md21
-rw-r--r--src/StardewModdingAPI.Installer/InteractiveInstaller.cs39
-rw-r--r--src/StardewModdingAPI/Constants.cs19
-rw-r--r--src/StardewModdingAPI/Events/ContentEvents.cs1
-rw-r--r--src/StardewModdingAPI/Events/EventArgsClickableMenuChanged.cs4
-rw-r--r--src/StardewModdingAPI/Events/EventArgsClickableMenuClosed.cs2
-rw-r--r--src/StardewModdingAPI/Events/EventArgsCommand.cs2
-rw-r--r--src/StardewModdingAPI/Events/EventArgsControllerButtonPressed.cs4
-rw-r--r--src/StardewModdingAPI/Events/EventArgsControllerButtonReleased.cs4
-rw-r--r--src/StardewModdingAPI/Events/EventArgsControllerTriggerPressed.cs6
-rw-r--r--src/StardewModdingAPI/Events/EventArgsControllerTriggerReleased.cs6
-rw-r--r--src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs4
-rw-r--r--src/StardewModdingAPI/Events/EventArgsGameLocationsChanged.cs2
-rw-r--r--src/StardewModdingAPI/Events/EventArgsInventoryChanged.cs8
-rw-r--r--src/StardewModdingAPI/Events/EventArgsKeyPressed.cs2
-rw-r--r--src/StardewModdingAPI/Events/EventArgsKeyboardStateChanged.cs4
-rw-r--r--src/StardewModdingAPI/Events/EventArgsLevelUp.cs4
-rw-r--r--src/StardewModdingAPI/Events/EventArgsLoadedGameChanged.cs2
-rw-r--r--src/StardewModdingAPI/Events/EventArgsLocationObjectsChanged.cs2
-rw-r--r--src/StardewModdingAPI/Events/EventArgsMineLevelChanged.cs4
-rw-r--r--src/StardewModdingAPI/Events/EventArgsMouseStateChanged.cs8
-rw-r--r--src/StardewModdingAPI/Events/EventArgsNewDay.cs6
-rw-r--r--src/StardewModdingAPI/Events/EventArgsStringChanged.cs4
-rw-r--r--src/StardewModdingAPI/Events/GameEvents.cs55
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyLoader.cs2
-rw-r--r--src/StardewModdingAPI/Framework/InternalExtensions.cs12
-rw-r--r--src/StardewModdingAPI/Framework/Monitor.cs7
-rw-r--r--src/StardewModdingAPI/Framework/SGame.cs1061
-rw-r--r--src/StardewModdingAPI/ISemanticVersion.cs21
-rw-r--r--src/StardewModdingAPI/Program.cs371
-rw-r--r--src/StardewModdingAPI/SemanticVersion.cs35
-rw-r--r--src/StardewModdingAPI/StardewModdingAPI.config.json85
-rw-r--r--src/TrainerMod/TrainerMod.cs82
34 files changed, 1218 insertions, 673 deletions
diff --git a/README.md b/README.md
index 74388144..4eaba9b4 100644
--- a/README.md
+++ b/README.md
@@ -150,5 +150,5 @@ testing and may change without warning.
argument | purpose
-------- | -------
-`--log path "path"` | The relative or absolute path of the log file SMAPI should write.
+`--log-path "path"` | The relative or absolute path of the log file SMAPI should write.
`--no-terminal` | SMAPI won't write anything to the console window. (Messages will still be written to the log file.)
diff --git a/release-notes.md b/release-notes.md
index a5ef4a0b..d600db57 100644
--- a/release-notes.md
+++ b/release-notes.md
@@ -8,12 +8,27 @@ For mod developers:
* Added `ContentEvents.AssetLoading` event with a helper which lets you intercept the XNB content
load, and dynamically adjust or replace the content being loaded (including support for patching
images).
+-->
## 1.10
See [log](https://github.com/Pathoschild/SMAPI/compare/1.9...1.10).
-* Updated for Stardew Valley 1.2.
-* SMAPI now rewrites many mods for compatibility with game updates, but some mods will need an update.
--->
+
+For players:
+* Updated to Stardew Valley 1.2.
+* Added logic to rewrite many mods for compatibility with game updates, though some mods may still need an update.
+* Fixed `SEHException` errors affecting some players.
+* Fixed issue where SMAPI didn't unlock some files on exit.
+* Fixed rare issue where the installer would crash trying to delete a bundled mod from `%appdata%`.
+* Improved TrainerMod commands:
+ * Added `world_setyear` to change the current year.
+ * Replaced `player_addmelee` with `player_addweapon` with support for non-melee weapons.
+
+For mod developers:
+* Mods are now initialised after the `Initialize`/`LoadContent` phase, which means the `GameEvents.Initialize` and `GameEvents.LoadContent` events are deprecated. You can move any logic in those methods to your mod's `Entry` method.
+* Added `IsBetween` and string overloads to the `ISemanticVersion` methods.
+* Fixed mouse-changed event never updating prior mouse position.
+* Fixed `monitor.ExitGameImmediately` not working correctly.
+* Fixed `Constants.SaveFolderName` not set for a new game until the save is created.
## 1.9
See [log](https://github.com/Pathoschild/SMAPI/compare/1.8...1.9).
diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs
index fffba30f..38c19d2b 100644
--- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs
+++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs
@@ -65,30 +65,30 @@ namespace StardewModdingApi.Installer
/// <param name="modsDir">The folder for SMAPI mods.</param>
private IEnumerable<string> GetUninstallPaths(DirectoryInfo installDir, DirectoryInfo modsDir)
{
- Func<string, string> installPath = path => Path.Combine(installDir.FullName, path);
+ string GetInstallPath(string path) => Path.Combine(installDir.FullName, path);
// common
- yield return installPath("Mono.Cecil.dll");
- yield return installPath("Newtonsoft.Json.dll");
- yield return installPath("StardewModdingAPI.exe");
- yield return installPath("StardewModdingAPI.config.json");
- yield return installPath("StardewModdingAPI.data.json");
- yield return installPath("StardewModdingAPI.AssemblyRewriters.dll");
- yield return installPath("steam_appid.txt");
+ yield return GetInstallPath("Mono.Cecil.dll");
+ yield return GetInstallPath("Newtonsoft.Json.dll");
+ yield return GetInstallPath("StardewModdingAPI.exe");
+ yield return GetInstallPath("StardewModdingAPI.config.json");
+ yield return GetInstallPath("StardewModdingAPI.data.json");
+ yield return GetInstallPath("StardewModdingAPI.AssemblyRewriters.dll");
+ yield return GetInstallPath("steam_appid.txt");
// Linux/Mac only
- yield return installPath("StardewModdingAPI");
- yield return installPath("StardewModdingAPI.exe.mdb");
- yield return installPath("System.Numerics.dll");
- yield return installPath("System.Runtime.Caching.dll");
+ yield return GetInstallPath("StardewModdingAPI");
+ yield return GetInstallPath("StardewModdingAPI.exe.mdb");
+ yield return GetInstallPath("System.Numerics.dll");
+ yield return GetInstallPath("System.Runtime.Caching.dll");
// Windows only
- yield return installPath("StardewModdingAPI.pdb");
+ yield return GetInstallPath("StardewModdingAPI.pdb");
// obsolete
- yield return installPath("Mods/.cache"); // 1.3-1.4
- yield return installPath("Mono.Cecil.Rocks.dll"); // 1.3–1.8
- yield return installPath("StardewModdingAPI-settings.json"); // 1.0-1.4
+ yield return GetInstallPath("Mods/.cache"); // 1.3-1.4
+ yield return GetInstallPath("Mono.Cecil.Rocks.dll"); // 1.3–1.8
+ yield return GetInstallPath("StardewModdingAPI-settings.json"); // 1.0-1.4
if (modsDir.Exists)
{
foreach (DirectoryInfo modDir in modsDir.EnumerateDirectories())
@@ -435,7 +435,7 @@ namespace StardewModdingApi.Installer
catch (Exception ex)
{
this.PrintError($"Oops! The installer couldn't delete {path}: [{ex.GetType().Name}] {ex.Message}.");
- this.PrintError("Please delete it yourself, then press any key to retry.");
+ this.PrintError("Try rebooting your computer and then run the installer again. If that doesn't work, try deleting it yourself then press any key to retry.");
Console.ReadKey();
}
}
@@ -592,7 +592,7 @@ namespace StardewModdingApi.Installer
if (isDir && packagedModNames.Contains(entry.Name, StringComparer.InvariantCultureIgnoreCase))
{
this.PrintDebug($" Deleting {entry.Name} because it's bundled into SMAPI...");
- entry.Delete();
+ this.InteractivelyDelete(entry.FullName);
continue;
}
@@ -626,9 +626,8 @@ namespace StardewModdingApi.Installer
private void Move(FileSystemInfo entry, string newPath)
{
// file
- if (entry is FileInfo)
+ if (entry is FileInfo file)
{
- FileInfo file = (FileInfo)entry;
file.CopyTo(newPath);
file.Delete();
}
diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs
index 4a036cd0..6ba16935 100644
--- a/src/StardewModdingAPI/Constants.cs
+++ b/src/StardewModdingAPI/Constants.cs
@@ -33,13 +33,13 @@ namespace StardewModdingAPI
** Public
****/
/// <summary>SMAPI's current semantic version.</summary>
- public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 9, 0);
+ public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 10, 0);
/// <summary>The minimum supported version of Stardew Valley.</summary>
- public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.1");
+ public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.15");
/// <summary>The maximum supported version of Stardew Valley.</summary>
- public static ISemanticVersion MaximumGameVersion { get; } = new SemanticVersion("1.1.1");
+ public static ISemanticVersion MaximumGameVersion { get; } = null;
/// <summary>The path to the game folder.</summary>
public static string ExecutionPath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
@@ -54,7 +54,7 @@ namespace StardewModdingAPI
public static string SavesPath { get; } = Path.Combine(Constants.DataPath, "Saves");
/// <summary>The directory name containing the current save's data (if a save is loaded and the directory exists).</summary>
- public static string SaveFolderName => Constants.SavePathReady ? Constants.GetSaveFolderName() : "";
+ public static string SaveFolderName => Constants.IsSaveLoaded ? Constants.GetSaveFolderName() : "";
/// <summary>The directory path containing the current save's data (if a save is loaded and the directory exists).</summary>
public static string CurrentSavePath => Constants.SavePathReady ? Path.Combine(Constants.SavesPath, Constants.GetSaveFolderName()) : "";
@@ -146,6 +146,9 @@ namespace StardewModdingAPI
/****
** Finders throw an exception when incompatible code is found.
****/
+ // changes in Stardew Valley 1.2 (with no rewriters)
+ new FieldFinder("StardewValley.Item", "set_Name"),
+
// APIs removed in SMAPI 1.9
new TypeFinder("StardewModdingAPI.Advanced.ConfigFile"),
new TypeFinder("StardewModdingAPI.Advanced.IConfigFile"),
@@ -169,6 +172,14 @@ namespace StardewModdingAPI
// crossplatform
new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchWrapper), onlyIfPlatformChanged: true),
+ // Stardew Valley 1.2
+ new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.activeClickableMenu)),
+ new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.currentMinigame)),
+ new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.gameMode)),
+ new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.player)),
+ new FieldReplaceRewriter(typeof(Game1), "borderFont", nameof(Game1.smallFont)),
+ new FieldReplaceRewriter(typeof(Game1), "smoothFont", nameof(Game1.smallFont)),
+
// SMAPI 1.9
new TypeReferenceRewriter("StardewModdingAPI.Inheritance.ItemStackChange", typeof(ItemStackChange))
};
diff --git a/src/StardewModdingAPI/Events/ContentEvents.cs b/src/StardewModdingAPI/Events/ContentEvents.cs
index 9418673a..5b4146c5 100644
--- a/src/StardewModdingAPI/Events/ContentEvents.cs
+++ b/src/StardewModdingAPI/Events/ContentEvents.cs
@@ -5,7 +5,6 @@ using StardewModdingAPI.Framework;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when the game loads content.</summary>
- [Obsolete("This is an undocumented experimental API and may change without warning.")]
public static class ContentEvents
{
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsClickableMenuChanged.cs b/src/StardewModdingAPI/Events/EventArgsClickableMenuChanged.cs
index 708c02e0..2a2aa163 100644
--- a/src/StardewModdingAPI/Events/EventArgsClickableMenuChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsClickableMenuChanged.cs
@@ -10,10 +10,10 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The previous menu.</summary>
- public IClickableMenu NewMenu { get; private set; }
+ public IClickableMenu NewMenu { get; }
/// <summary>The current menu.</summary>
- public IClickableMenu PriorMenu { get; private set; }
+ public IClickableMenu PriorMenu { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsClickableMenuClosed.cs b/src/StardewModdingAPI/Events/EventArgsClickableMenuClosed.cs
index 1a62432f..5e6585f0 100644
--- a/src/StardewModdingAPI/Events/EventArgsClickableMenuClosed.cs
+++ b/src/StardewModdingAPI/Events/EventArgsClickableMenuClosed.cs
@@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The menu that was closed.</summary>
- public IClickableMenu PriorMenu { get; private set; }
+ public IClickableMenu PriorMenu { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsCommand.cs b/src/StardewModdingAPI/Events/EventArgsCommand.cs
index bae13694..88a9e5a3 100644
--- a/src/StardewModdingAPI/Events/EventArgsCommand.cs
+++ b/src/StardewModdingAPI/Events/EventArgsCommand.cs
@@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The triggered command.</summary>
- public Command Command { get; private set; }
+ public Command Command { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsControllerButtonPressed.cs b/src/StardewModdingAPI/Events/EventArgsControllerButtonPressed.cs
index 87c96678..3243b80b 100644
--- a/src/StardewModdingAPI/Events/EventArgsControllerButtonPressed.cs
+++ b/src/StardewModdingAPI/Events/EventArgsControllerButtonPressed.cs
@@ -11,10 +11,10 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The player who pressed the button.</summary>
- public PlayerIndex PlayerIndex { get; private set; }
+ public PlayerIndex PlayerIndex { get; }
/// <summary>The controller button that was pressed.</summary>
- public Buttons ButtonPressed { get; private set; }
+ public Buttons ButtonPressed { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsControllerButtonReleased.cs b/src/StardewModdingAPI/Events/EventArgsControllerButtonReleased.cs
index cb53b545..e05a080b 100644
--- a/src/StardewModdingAPI/Events/EventArgsControllerButtonReleased.cs
+++ b/src/StardewModdingAPI/Events/EventArgsControllerButtonReleased.cs
@@ -11,10 +11,10 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The player who pressed the button.</summary>
- public PlayerIndex PlayerIndex { get; private set; }
+ public PlayerIndex PlayerIndex { get; }
/// <summary>The controller button that was pressed.</summary>
- public Buttons ButtonReleased { get; private set; }
+ public Buttons ButtonReleased { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsControllerTriggerPressed.cs b/src/StardewModdingAPI/Events/EventArgsControllerTriggerPressed.cs
index 72b73040..a2087733 100644
--- a/src/StardewModdingAPI/Events/EventArgsControllerTriggerPressed.cs
+++ b/src/StardewModdingAPI/Events/EventArgsControllerTriggerPressed.cs
@@ -11,13 +11,13 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The player who pressed the button.</summary>
- public PlayerIndex PlayerIndex { get; private set; }
+ public PlayerIndex PlayerIndex { get; }
/// <summary>The controller button that was pressed.</summary>
- public Buttons ButtonPressed { get; private set; }
+ public Buttons ButtonPressed { get; }
/// <summary>The current trigger value.</summary>
- public float Value { get; private set; }
+ public float Value { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsControllerTriggerReleased.cs b/src/StardewModdingAPI/Events/EventArgsControllerTriggerReleased.cs
index de28a159..d2eecbec 100644
--- a/src/StardewModdingAPI/Events/EventArgsControllerTriggerReleased.cs
+++ b/src/StardewModdingAPI/Events/EventArgsControllerTriggerReleased.cs
@@ -11,13 +11,13 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The player who pressed the button.</summary>
- public PlayerIndex PlayerIndex { get; private set; }
+ public PlayerIndex PlayerIndex { get; }
/// <summary>The controller button that was released.</summary>
- public Buttons ButtonReleased { get; private set; }
+ public Buttons ButtonReleased { get; }
/// <summary>The current trigger value.</summary>
- public float Value { get; private set; }
+ public float Value { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs b/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs
index aa0bb377..25d3ebf3 100644
--- a/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs
@@ -10,10 +10,10 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The player's current location.</summary>
- public GameLocation NewLocation { get; private set; }
+ public GameLocation NewLocation { get; }
/// <summary>The player's previous location.</summary>
- public GameLocation PriorLocation { get; private set; }
+ public GameLocation PriorLocation { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsGameLocationsChanged.cs b/src/StardewModdingAPI/Events/EventArgsGameLocationsChanged.cs
index c68951ce..fb8c821e 100644
--- a/src/StardewModdingAPI/Events/EventArgsGameLocationsChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsGameLocationsChanged.cs
@@ -11,7 +11,7 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The current list of game locations.</summary>
- public List<GameLocation> NewLocations { get; private set; }
+ public List<GameLocation> NewLocations { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsInventoryChanged.cs b/src/StardewModdingAPI/Events/EventArgsInventoryChanged.cs
index 11cbcedf..1ee02842 100644
--- a/src/StardewModdingAPI/Events/EventArgsInventoryChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsInventoryChanged.cs
@@ -12,16 +12,16 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The player's inventory.</summary>
- public List<Item> Inventory { get; private set; }
+ public List<Item> Inventory { get; }
/// <summary>The added items.</summary>
- public List<ItemStackChange> Added { get; private set; }
+ public List<ItemStackChange> Added { get; }
/// <summary>The removed items.</summary>
- public List<ItemStackChange> Removed { get; private set; }
+ public List<ItemStackChange> Removed { get; }
/// <summary>The items whose stack sizes changed.</summary>
- public List<ItemStackChange> QuantityChanged { get; private set; }
+ public List<ItemStackChange> QuantityChanged { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsKeyPressed.cs b/src/StardewModdingAPI/Events/EventArgsKeyPressed.cs
index 82a593be..d9d81e10 100644
--- a/src/StardewModdingAPI/Events/EventArgsKeyPressed.cs
+++ b/src/StardewModdingAPI/Events/EventArgsKeyPressed.cs
@@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The keyboard button that was pressed.</summary>
- public Keys KeyPressed { get; private set; }
+ public Keys KeyPressed { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsKeyboardStateChanged.cs b/src/StardewModdingAPI/Events/EventArgsKeyboardStateChanged.cs
index 2e314731..14e397ce 100644
--- a/src/StardewModdingAPI/Events/EventArgsKeyboardStateChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsKeyboardStateChanged.cs
@@ -10,10 +10,10 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The previous keyboard state.</summary>
- public KeyboardState NewState { get; private set; }
+ public KeyboardState NewState { get; }
/// <summary>The current keyboard state.</summary>
- public KeyboardState PriorState { get; private set; }
+ public KeyboardState PriorState { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsLevelUp.cs b/src/StardewModdingAPI/Events/EventArgsLevelUp.cs
index 826914da..fe6696d4 100644
--- a/src/StardewModdingAPI/Events/EventArgsLevelUp.cs
+++ b/src/StardewModdingAPI/Events/EventArgsLevelUp.cs
@@ -9,10 +9,10 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The player skill that leveled up.</summary>
- public LevelType Type { get; private set; }
+ public LevelType Type { get; }
/// <summary>The new skill level.</summary>
- public int NewLevel { get; private set; }
+ public int NewLevel { get; }
/// <summary>The player skill types.</summary>
public enum LevelType
diff --git a/src/StardewModdingAPI/Events/EventArgsLoadedGameChanged.cs b/src/StardewModdingAPI/Events/EventArgsLoadedGameChanged.cs
index cd7a366f..51d64016 100644
--- a/src/StardewModdingAPI/Events/EventArgsLoadedGameChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsLoadedGameChanged.cs
@@ -9,7 +9,7 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>Whether the save has been loaded. This is always true.</summary>
- public bool LoadedGame { get; private set; }
+ public bool LoadedGame { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsLocationObjectsChanged.cs b/src/StardewModdingAPI/Events/EventArgsLocationObjectsChanged.cs
index f708ab6b..058999e9 100644
--- a/src/StardewModdingAPI/Events/EventArgsLocationObjectsChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsLocationObjectsChanged.cs
@@ -12,7 +12,7 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The current list of objects in the current location.</summary>
- public SerializableDictionary<Vector2, Object> NewObjects { get; private set; }
+ public SerializableDictionary<Vector2, Object> NewObjects { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsMineLevelChanged.cs b/src/StardewModdingAPI/Events/EventArgsMineLevelChanged.cs
index a02921d2..c82fed35 100644
--- a/src/StardewModdingAPI/Events/EventArgsMineLevelChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsMineLevelChanged.cs
@@ -9,10 +9,10 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The previous mine level.</summary>
- public int PreviousMineLevel { get; private set; }
+ public int PreviousMineLevel { get; }
/// <summary>The current mine level.</summary>
- public int CurrentMineLevel { get; private set; }
+ public int CurrentMineLevel { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsMouseStateChanged.cs b/src/StardewModdingAPI/Events/EventArgsMouseStateChanged.cs
index a589e29d..57298164 100644
--- a/src/StardewModdingAPI/Events/EventArgsMouseStateChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsMouseStateChanged.cs
@@ -11,16 +11,16 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The previous mouse state.</summary>
- public MouseState PriorState { get; private set; }
+ public MouseState PriorState { get; }
/// <summary>The current mouse state.</summary>
- public MouseState NewState { get; private set; }
+ public MouseState NewState { get; }
/// <summary>The previous mouse position on the screen adjusted for the zoom level.</summary>
- public Point PriorPosition { get; private set; }
+ public Point PriorPosition { get; }
/// <summary>The current mouse position on the screen adjusted for the zoom level.</summary>
- public Point NewPosition { get; private set; }
+ public Point NewPosition { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsNewDay.cs b/src/StardewModdingAPI/Events/EventArgsNewDay.cs
index 5088cb5c..aba837e4 100644
--- a/src/StardewModdingAPI/Events/EventArgsNewDay.cs
+++ b/src/StardewModdingAPI/Events/EventArgsNewDay.cs
@@ -9,13 +9,13 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The previous day value.</summary>
- public int PreviousDay { get; private set; }
+ public int PreviousDay { get; }
/// <summary>The current day value.</summary>
- public int CurrentDay { get; private set; }
+ public int CurrentDay { get; }
/// <summary>Whether the game just started the transition (<c>true</c>) or finished it (<c>false</c>).</summary>
- public bool IsNewDay { get; private set; }
+ public bool IsNewDay { get; }
/*********
diff --git a/src/StardewModdingAPI/Events/EventArgsStringChanged.cs b/src/StardewModdingAPI/Events/EventArgsStringChanged.cs
index f91951ae..85b6fab5 100644
--- a/src/StardewModdingAPI/Events/EventArgsStringChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsStringChanged.cs
@@ -9,10 +9,10 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The previous value.</summary>
- public string NewString { get; private set; }
+ public string NewString { get; }
/// <summary>The current value.</summary>
- public string PriorString { get; private set; }
+ public string PriorString { get; }
/*********
** Public methods
diff --git a/src/StardewModdingAPI/Events/GameEvents.cs b/src/StardewModdingAPI/Events/GameEvents.cs
index 715083b9..029ec1f9 100644
--- a/src/StardewModdingAPI/Events/GameEvents.cs
+++ b/src/StardewModdingAPI/Events/GameEvents.cs
@@ -7,17 +7,29 @@ namespace StardewModdingAPI.Events
public static class GameEvents
{
/*********
+ ** Properties
+ *********/
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
+
+ /*********
** Events
*********/
- /// <summary>Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called during <see cref="Microsoft.Xna.Framework.Game.Initialize"/>.</summary>
- public static event EventHandler Initialize;
+ /// <summary>Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after <see cref="Microsoft.Xna.Framework.Game.Initialize"/>.</summary>
+ internal static event EventHandler InitializeInternal;
- /// <summary>Raised during launch after configuring Stardew Valley, loading it into memory, and opening the game window. The window is still blank by this point.</summary>
- public static event EventHandler GameLoaded;
+ /// <summary>Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after <see cref="Microsoft.Xna.Framework.Game.Initialize"/>.</summary>
+ [Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the " + nameof(GameEvents.Initialize) + " event, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")]
+ public static event EventHandler Initialize;
/// <summary>Raised before XNA loads or reloads graphics resources. Called during <see cref="Microsoft.Xna.Framework.Game.LoadContent"/>.</summary>
+ [Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the " + nameof(GameEvents.LoadContent) + " event, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")]
public static event EventHandler LoadContent;
+ /// <summary>Raised during launch after configuring Stardew Valley, loading it into memory, and opening the game window. The window is still blank by this point.</summary>
+ public static event EventHandler GameLoaded;
+
/// <summary>Raised during the first game update tick.</summary>
public static event EventHandler FirstUpdateTick;
@@ -46,25 +58,48 @@ namespace StardewModdingAPI.Events
/*********
** Internal methods
*********/
- /// <summary>Raise a <see cref="GameLoaded"/> event.</summary>
- /// <param name="monitor">Encapsulates monitoring and logging.</param>
- internal static void InvokeGameLoaded(IMonitor monitor)
+ /// <summary>Injects types required for backwards compatibility.</summary>
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Shim(DeprecationManager deprecationManager)
{
- monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoaded)}", GameEvents.GameLoaded?.GetInvocationList());
+ GameEvents.DeprecationManager = deprecationManager;
}
/// <summary>Raise an <see cref="Initialize"/> event.</summary>
/// <param name="monitor">Encapsulates logging and monitoring.</param>
internal static void InvokeInitialize(IMonitor monitor)
{
- monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.Initialize)}", GameEvents.Initialize?.GetInvocationList());
+ // notify SMAPI
+ monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.InitializeInternal)}", GameEvents.InitializeInternal?.GetInvocationList());
+
+ // notify mods
+ if (GameEvents.Initialize == null)
+ return;
+ string name = $"{nameof(GameEvents)}.{nameof(GameEvents.Initialize)}";
+ Delegate[] handlers = GameEvents.Initialize.GetInvocationList();
+ GameEvents.DeprecationManager.WarnForEvent(handlers, name, "1.10", DeprecationLevel.Info);
+ monitor.SafelyRaisePlainEvent(name, handlers);
}
/// <summary>Raise a <see cref="LoadContent"/> event.</summary>
/// <param name="monitor">Encapsulates logging and monitoring.</param>
internal static void InvokeLoadContent(IMonitor monitor)
{
- monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.LoadContent)}", GameEvents.LoadContent?.GetInvocationList());
+ if (GameEvents.LoadContent == null)
+ return;
+
+ string name = $"{nameof(GameEvents)}.{nameof(GameEvents.LoadContent)}";
+ Delegate[] handlers = GameEvents.LoadContent.GetInvocationList();
+
+ GameEvents.DeprecationManager.WarnForEvent(handlers, name, "1.10", DeprecationLevel.Info);
+ monitor.SafelyRaisePlainEvent(name, handlers);
+ }
+
+ /// <summary>Raise a <see cref="GameLoaded"/> event.</summary>
+ /// <param name="monitor">Encapsulates monitoring and logging.</param>
+ internal static void InvokeGameLoaded(IMonitor monitor)
+ {
+ monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoaded)}", GameEvents.GameLoaded?.GetInvocationList());
}
/// <summary>Raise an <see cref="UpdateTick"/> event.</summary>
diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs
index f6fe89f5..2c9973c1 100644
--- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs
+++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs
@@ -284,7 +284,7 @@ namespace StardewModdingAPI.Framework
{
if (!hash.Contains(message))
{
- this.Monitor.Log(message, level);
+ monitor.Log(message, level);
hash.Add(message);
}
}
diff --git a/src/StardewModdingAPI/Framework/InternalExtensions.cs b/src/StardewModdingAPI/Framework/InternalExtensions.cs
index 4ca79518..a2d589ff 100644
--- a/src/StardewModdingAPI/Framework/InternalExtensions.cs
+++ b/src/StardewModdingAPI/Framework/InternalExtensions.cs
@@ -39,7 +39,7 @@ namespace StardewModdingAPI.Framework
if (handlers == null)
return;
- foreach (EventHandler handler in Enumerable.Cast<EventHandler>(handlers))
+ foreach (EventHandler handler in handlers.Cast<EventHandler>())
{
try
{
@@ -64,7 +64,7 @@ namespace StardewModdingAPI.Framework
if (handlers == null)
return;
- foreach (EventHandler<TEventArgs> handler in Enumerable.Cast<EventHandler<TEventArgs>>(handlers))
+ foreach (EventHandler<TEventArgs> handler in handlers.Cast<EventHandler<TEventArgs>>())
{
try
{
@@ -85,14 +85,14 @@ namespace StardewModdingAPI.Framework
public static string GetLogSummary(this Exception exception)
{
// type load exception
- if (exception is TypeLoadException)
- return $"Failed loading type: {((TypeLoadException)exception).TypeName}: {exception}";
+ if (exception is TypeLoadException typeLoadEx)
+ return $"Failed loading type: {typeLoadEx.TypeName}: {exception}";
// reflection type load exception
- if (exception is ReflectionTypeLoadException)
+ if (exception is ReflectionTypeLoadException reflectionTypeLoadEx)
{
string summary = exception.ToString();
- foreach (Exception childEx in ((ReflectionTypeLoadException)exception).LoaderExceptions)
+ foreach (Exception childEx in reflectionTypeLoadEx.LoaderExceptions)
summary += $"\n\n{childEx.GetLogSummary()}";
return summary;
}
diff --git a/src/StardewModdingAPI/Framework/Monitor.cs b/src/StardewModdingAPI/Framework/Monitor.cs
index 64075f2f..51feff78 100644
--- a/src/StardewModdingAPI/Framework/Monitor.cs
+++ b/src/StardewModdingAPI/Framework/Monitor.cs
@@ -21,7 +21,7 @@ namespace StardewModdingAPI.Framework
private readonly LogFileManager LogFile;
/// <summary>The maximum length of the <see cref="LogLevel"/> values.</summary>
- private static readonly int MaxLevelLength = (from level in Enumerable.Cast<LogLevel>(Enum.GetValues(typeof(LogLevel))) select level.ToString().Length).Max();
+ private static readonly int MaxLevelLength = (from level in Enum.GetValues(typeof(LogLevel)).Cast<LogLevel>() select level.ToString().Length).Max();
/// <summary>The console text color for each log level.</summary>
private static readonly Dictionary<LogLevel, ConsoleColor> Colors = new Dictionary<LogLevel, ConsoleColor>
@@ -35,7 +35,7 @@ namespace StardewModdingAPI.Framework
};
/// <summary>A delegate which requests that SMAPI immediately exit the game. This should only be invoked when an irrecoverable fatal error happens that risks save corruption or game-breaking bugs.</summary>
- private RequestExitDelegate RequestExit;
+ private readonly RequestExitDelegate RequestExit;
/*********
@@ -71,6 +71,7 @@ namespace StardewModdingAPI.Framework
this.Source = source;
this.LogFile = logFile;
this.ConsoleManager = consoleManager;
+ this.RequestExit = requestExitDelegate;
}
/// <summary>Log a message for the player or developer.</summary>
@@ -129,6 +130,8 @@ namespace StardewModdingAPI.Framework
{
if (this.ConsoleManager.SupportsColor)
{
+ if (background.HasValue)
+ Console.BackgroundColor = background.Value;
Console.ForegroundColor = color;
Console.WriteLine(message);
Console.ResetColor();
diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs
index 5f265139..61493e87 100644
--- a/src/StardewModdingAPI/Framework/SGame.cs
+++ b/src/StardewModdingAPI/Framework/SGame.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
@@ -14,7 +16,7 @@ using StardewValley.Locations;
using StardewValley.Menus;
using StardewValley.Tools;
using xTile.Dimensions;
-using Rectangle = Microsoft.Xna.Framework.Rectangle;
+using xTile.Layers;
using SFarmer = StardewValley.Farmer;
namespace StardewModdingAPI.Framework
@@ -48,7 +50,7 @@ namespace StardewModdingAPI.Framework
** Game state
****/
/// <summary>Arrays of pressed controller buttons indexed by <see cref="PlayerIndex"/>.</summary>
- private Buttons[][] PreviouslyPressedButtons;
+ private readonly Buttons[][] PreviouslyPressedButtons = { new Buttons[0], new Buttons[0], new Buttons[0], new Buttons[0] };
/// <summary>A record of the keyboard state (i.e. the up/down state for each button) as of the latest tick.</summary>
private KeyboardState KStateNow;
@@ -134,6 +136,9 @@ namespace StardewModdingAPI.Framework
/// <summary>The player character at last check.</summary>
private SFarmer PreviousFarmer;
+ /// <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;
@@ -149,12 +154,20 @@ namespace StardewModdingAPI.Framework
// ReSharper disable ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming
/// <summary>Used to access private fields and methods.</summary>
private static readonly IReflectionHelper Reflection = new ReflectionHelper();
+ 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.GetPrivateField<RenderTarget2D>(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop
+ 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(new object[0]);
private readonly Action drawHUD = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(drawHUD)).Invoke(new object[0]);
private readonly Action drawDialogueBox = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(drawDialogueBox)).Invoke(new object[0]);
+ private readonly Action renderScreenBuffer = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(renderScreenBuffer)).Invoke(new object[0]);
// ReSharper restore ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming
@@ -168,36 +181,48 @@ namespace StardewModdingAPI.Framework
this.Monitor = monitor;
this.FirstUpdate = true;
SGame.Instance = this;
+
+ Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; // required by Stardew Valley
}
/****
** Intercepted methods & events
****/
- /// <summary>The method called during game launch after configuring XNA or MonoGame. The game window hasn't been opened by this point.</summary>
- protected override void Initialize()
- {
- this.PreviouslyPressedButtons = new Buttons[4][];
- for (var i = 0; i < 4; ++i)
- this.PreviouslyPressedButtons[i] = new Buttons[0];
-
- base.Initialize();
- GameEvents.InvokeInitialize(this.Monitor);
- }
-
- /// <summary>The method called before XNA or MonoGame loads or reloads graphics resources.</summary>
- protected override void LoadContent()
+ /// <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)
{
- base.LoadContent();
- GameEvents.InvokeLoadContent(this.Monitor);
+ return new SContentManager(this.Content.ServiceProvider, this.Content.RootDirectory, this.Monitor);
}
/// <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)
{
+ // 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;
+ }
+
// raise game loaded
if (this.FirstUpdate)
+ {
+ GameEvents.InvokeInitialize(this.Monitor);
+ GameEvents.InvokeLoadContent(this.Monitor);
GameEvents.InvokeGameLoaded(this.Monitor);
+ }
// update SMAPI events
this.UpdateEventCalls();
@@ -258,412 +283,649 @@ namespace StardewModdingAPI.Framework
{
try
{
- if (!this.ZoomLevelIsOne)
- this.GraphicsDevice.SetRenderTarget(this.screenWrapper);
-
- this.GraphicsDevice.Clear(this.bgColor);
- if (Game1.options.showMenuBackground && Game1.activeClickableMenu != null && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet())
+ if (Game1.debugMode)
{
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- try
- {
- Game1.activeClickableMenu.drawBackground(Game1.spriteBatch);
- }
- catch (Exception ex)
- {
- this.Monitor.Log($"The {Game1.activeClickableMenu.GetType().FullName} menu crashed while drawing its background. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error);
- Game1.activeClickableMenu.exitThisMenu();
- }
- GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor);
- try
- {
- Game1.activeClickableMenu.draw(Game1.spriteBatch);
- }
- 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();
- }
- GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor);
- Game1.spriteBatch.End();
- if (!this.ZoomLevelIsOne)
+ if (SGame._fpsStopwatch.IsRunning)
{
- this.GraphicsDevice.SetRenderTarget(null);
- this.GraphicsDevice.Clear(this.bgColor);
- Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone);
- Game1.spriteBatch.Draw(this.screenWrapper, Vector2.Zero, this.screenWrapper.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f);
- Game1.spriteBatch.End();
+ 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));
}
- return;
+ SGame._fpsStopwatch.Restart();
}
- if (Game1.gameMode == 11)
+ else
{
- Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- Game1.spriteBatch.DrawString(Game1.smoothFont, "Stardew Valley has crashed...", new Vector2(16f, 16f), Color.HotPink);
- Game1.spriteBatch.DrawString(Game1.smoothFont, "Please send the error report or a screenshot of this message to @ConcernedApe. (http://stardewvalley.net/contact/)", new Vector2(16f, 32f), new Color(0, 255, 0));
- Game1.spriteBatch.DrawString(Game1.smoothFont, Game1.parseText(Game1.errorMessage, Game1.smoothFont, Game1.graphics.GraphicsDevice.Viewport.Width), new Vector2(16f, 48f), Color.White);
- Game1.spriteBatch.End();
- return;
+ if (SGame._fpsStopwatch.IsRunning)
+ SGame._fpsStopwatch.Reset();
+ SGame._fps = 0.0f;
+ SGame._fpsList.Clear();
}
- if (Game1.currentMinigame != null)
+ if (SGame._newDayTask != null)
{
- Game1.currentMinigame.draw(Game1.spriteBatch);
- if (Game1.globalFade && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause))
- {
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * ((Game1.gameMode == 0) ? (1f - Game1.fadeToBlackAlpha) : Game1.fadeToBlackAlpha));
- Game1.spriteBatch.End();
- }
- if (!this.ZoomLevelIsOne)
- {
- this.GraphicsDevice.SetRenderTarget(null);
- this.GraphicsDevice.Clear(this.bgColor);
- Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone);
- Game1.spriteBatch.Draw(this.screenWrapper, Vector2.Zero, this.screenWrapper.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f);
- Game1.spriteBatch.End();
- }
- return;
- }
- if (Game1.showingEndOfNightStuff)
- {
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- try
- {
- Game1.activeClickableMenu?.draw(Game1.spriteBatch);
- }
- 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 (!this.ZoomLevelIsOne)
- {
- this.GraphicsDevice.SetRenderTarget(null);
- this.GraphicsDevice.Clear(this.bgColor);
- Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone);
- Game1.spriteBatch.Draw(this.screenWrapper, Vector2.Zero, this.screenWrapper.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f);
- Game1.spriteBatch.End();
- }
- return;
- }
- if (Game1.gameMode == 6)
- {
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- string text = "";
- int num = 0;
- while (num < gameTime.TotalGameTime.TotalMilliseconds % 999.0 / 333.0)
- {
- text += ".";
- num++;
- }
- SpriteText.drawString(Game1.spriteBatch, "Loading" + text, 64, Game1.graphics.GraphicsDevice.Viewport.Height - 64, 999, -1, 999, 1f, 1f, false, 0, "Loading...");
- Game1.spriteBatch.End();
- if (!this.ZoomLevelIsOne)
- {
- this.GraphicsDevice.SetRenderTarget(null);
- this.GraphicsDevice.Clear(this.bgColor);
- Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone);
- Game1.spriteBatch.Draw(this.screenWrapper, Vector2.Zero, this.screenWrapper.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f);
- Game1.spriteBatch.End();
- }
- return;
+ this.GraphicsDevice.Clear(this.bgColor);
+ //base.Draw(gameTime);
}
- if (Game1.gameMode == 0)
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
else
{
- if (Game1.drawLighting)
+ if ((double)Game1.options.zoomLevel != 1.0)
+ this.GraphicsDevice.SetRenderTarget(this.screenWrapper);
+ if (this.IsSaving)
{
- this.GraphicsDevice.SetRenderTarget(Game1.lightmap);
- this.GraphicsDevice.Clear(Color.White * 0f);
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null);
- Game1.spriteBatch.Draw(Game1.staminaRect, Game1.lightmap.Bounds, Game1.currentLocation.name.Equals("UndergroundMine") ? Game1.mine.getLightingColor(gameTime) : ((!Game1.ambientLight.Equals(Color.White) && (!Game1.isRaining || !Game1.currentLocation.isOutdoors)) ? Game1.ambientLight : Game1.outdoorLight));
- for (int i = 0; i < Game1.currentLightSources.Count; i++)
- {
- if (Utility.isOnScreen(Game1.currentLightSources.ElementAt(i).position, (int)(Game1.currentLightSources.ElementAt(i).radius * Game1.tileSize * 4f)))
- Game1.spriteBatch.Draw(Game1.currentLightSources.ElementAt(i).lightTexture, Game1.GlobalToLocal(Game1.viewport, Game1.currentLightSources.ElementAt(i).position) / Game1.options.lightingQuality, Game1.currentLightSources.ElementAt(i).lightTexture.Bounds, Game1.currentLightSources.ElementAt(i).color, 0f, new Vector2(Game1.currentLightSources.ElementAt(i).lightTexture.Bounds.Center.X, Game1.currentLightSources.ElementAt(i).lightTexture.Bounds.Center.Y), Game1.currentLightSources.ElementAt(i).radius / Game1.options.lightingQuality, SpriteEffects.None, 0.9f);
- }
- Game1.spriteBatch.End();
- this.GraphicsDevice.SetRenderTarget(this.ZoomLevelIsOne ? null : this.screenWrapper);
- }
- if (Game1.bloomDay)
- Game1.bloom?.BeginDraw();
- this.GraphicsDevice.Clear(this.bgColor);
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- GraphicsEvents.InvokeOnPreRenderEvent(this.Monitor);
- Game1.background?.draw(Game1.spriteBatch);
- Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
- Game1.currentLocation.Map.GetLayer("Back").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, Game1.pixelZoom);
- Game1.currentLocation.drawWater(Game1.spriteBatch);
- if (Game1.CurrentEvent == null)
- {
- using (List<NPC>.Enumerator enumerator = Game1.currentLocation.characters.GetEnumerator())
+ this.GraphicsDevice.Clear(this.bgColor);
+ IClickableMenu activeClickableMenu = Game1.activeClickableMenu;
+ if (activeClickableMenu != null)
{
- while (enumerator.MoveNext())
+ 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)
{
- NPC current = enumerator.Current;
- if (current != null && !current.swimming && !current.hideShadow && !current.IsMonster && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(current.getTileLocation()))
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, current.position + new Vector2(current.sprite.spriteWidth * Game1.pixelZoom / 2f, current.GetBoundingBox().Height + (current.IsMonster ? 0 : (Game1.pixelZoom * 3)))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), (Game1.pixelZoom + current.yJumpOffset / 40f) * current.scale, SpriteEffects.None, Math.Max(0f, current.getStandingY() / 10000f) - 1E-06f);
+ 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();
}
- goto IL_B30;
+ Game1.spriteBatch.End();
}
+ //base.Draw(gameTime);
+ this.renderScreenBuffer();
}
- foreach (NPC current2 in Game1.CurrentEvent.actors)
+ else
{
- if (!current2.swimming && !current2.hideShadow && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(current2.getTileLocation()))
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, current2.position + new Vector2(current2.sprite.spriteWidth * Game1.pixelZoom / 2f, current2.GetBoundingBox().Height + (current2.IsMonster ? 0 : (Game1.pixelZoom * 3)))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), (Game1.pixelZoom + current2.yJumpOffset / 40f) * current2.scale, SpriteEffects.None, Math.Max(0f, current2.getStandingY() / 10000f) - 1E-06f);
- }
- IL_B30:
- if (!Game1.player.swimming && !Game1.player.isRidingHorse() && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(Game1.player.getTileLocation()))
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.player.position + new Vector2(32f, 24f)), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 4f - (((Game1.player.running || Game1.player.usingTool) && Game1.player.FarmerSprite.indexInCurrentAnimation > 1) ? (Math.Abs(FarmerRenderer.featureYOffsetPerFrame[Game1.player.FarmerSprite.CurrentFrame]) * 0.5f) : 0f), SpriteEffects.None, 0f);
- Game1.currentLocation.Map.GetLayer("Buildings").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, Game1.pixelZoom);
- Game1.mapDisplayDevice.EndScene();
- Game1.spriteBatch.End();
- Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- if (Game1.CurrentEvent == null)
- {
- using (List<NPC>.Enumerator enumerator3 = Game1.currentLocation.characters.GetEnumerator())
+ this.GraphicsDevice.Clear(this.bgColor);
+ if (Game1.activeClickableMenu != null && Game1.options.showMenuBackground && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet())
{
- while (enumerator3.MoveNext())
+ 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)
{
- NPC current3 = enumerator3.Current;
- if (current3 != null && !current3.swimming && !current3.hideShadow && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(current3.getTileLocation()))
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, current3.position + new Vector2(current3.sprite.spriteWidth * Game1.pixelZoom / 2f, current3.GetBoundingBox().Height + (current3.IsMonster ? 0 : (Game1.pixelZoom * 3)))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), (Game1.pixelZoom + current3.yJumpOffset / 40f) * current3.scale, SpriteEffects.None, Math.Max(0f, current3.getStandingY() / 10000f) - 1E-06f);
+ 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();
}
- goto IL_F5F;
+ 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();
}
- }
- foreach (NPC current4 in Game1.CurrentEvent.actors)
- {
- if (!current4.swimming && !current4.hideShadow && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(current4.getTileLocation()))
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, current4.position + new Vector2(current4.sprite.spriteWidth * Game1.pixelZoom / 2f, current4.GetBoundingBox().Height + (current4.IsMonster ? 0 : (Game1.pixelZoom * 3)))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), (Game1.pixelZoom + current4.yJumpOffset / 40f) * current4.scale, SpriteEffects.None, Math.Max(0f, current4.getStandingY() / 10000f) - 1E-06f);
- }
- IL_F5F:
- if (!Game1.player.swimming && !Game1.player.isRidingHorse() && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(Game1.player.getTileLocation()))
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.player.position + new Vector2(32f, 24f)), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 4f - (((Game1.player.running || Game1.player.usingTool) && Game1.player.FarmerSprite.indexInCurrentAnimation > 1) ? (Math.Abs(FarmerRenderer.featureYOffsetPerFrame[Game1.player.FarmerSprite.CurrentFrame]) * 0.5f) : 0f), SpriteEffects.None, Math.Max(0.0001f, Game1.player.getStandingY() / 10000f + 0.00011f) - 0.0001f);
- if (Game1.displayFarmer)
- Game1.player.draw(Game1.spriteBatch);
- if ((Game1.eventUp || Game1.killScreen) && !Game1.killScreen)
- Game1.currentLocation.currentEvent?.draw(Game1.spriteBatch);
- if (Game1.player.currentUpgrade != null && Game1.player.currentUpgrade.daysLeftTillUpgradeDone <= 3 && Game1.currentLocation.Name.Equals("Farm"))
- Game1.spriteBatch.Draw(Game1.player.currentUpgrade.workerTexture, Game1.GlobalToLocal(Game1.viewport, Game1.player.currentUpgrade.positionOfCarpenter), Game1.player.currentUpgrade.getSourceRectangle(), Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, (Game1.player.currentUpgrade.positionOfCarpenter.Y + Game1.tileSize * 3 / 4) / 10000f);
- Game1.currentLocation.draw(Game1.spriteBatch);
- if (Game1.eventUp && Game1.currentLocation.currentEvent?.messageToScreen != null)
- Game1.drawWithBorder(Game1.currentLocation.currentEvent.messageToScreen, Color.Black, Color.White, new Vector2(Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Width / 2 - Game1.borderFont.MeasureString(Game1.currentLocation.currentEvent.messageToScreen).X / 2f, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Height - Game1.tileSize), 0f, 1f, 0.999f);
- if (Game1.player.ActiveObject == null && (Game1.player.UsingTool || Game1.pickingTool) && Game1.player.CurrentTool != null && (!Game1.player.CurrentTool.Name.Equals("Seeds") || Game1.pickingTool))
- Game1.drawTool(Game1.player);
- if (Game1.currentLocation.Name.Equals("Farm"))
- this.drawFarmBuildings();
- if (Game1.tvStation >= 0)
- Game1.spriteBatch.Draw(Game1.tvStationTexture, Game1.GlobalToLocal(Game1.viewport, new Vector2(6 * Game1.tileSize + Game1.tileSize / 4, 2 * Game1.tileSize + Game1.tileSize / 2)), new Rectangle(Game1.tvStation * 24, 0, 24, 15), Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, 1E-08f);
- if (Game1.panMode)
- {
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Rectangle((int)Math.Floor((Game1.getOldMouseX() + Game1.viewport.X) / (double)Game1.tileSize) * Game1.tileSize - Game1.viewport.X, (int)Math.Floor((Game1.getOldMouseY() + Game1.viewport.Y) / (double)Game1.tileSize) * Game1.tileSize - Game1.viewport.Y, Game1.tileSize, Game1.tileSize), Color.Lime * 0.75f);
- foreach (Warp current5 in Game1.currentLocation.warps)
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Rectangle(current5.X * Game1.tileSize - Game1.viewport.X, current5.Y * Game1.tileSize - Game1.viewport.Y, Game1.tileSize, Game1.tileSize), Color.Red * 0.75f);
- }
- Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
- Game1.currentLocation.Map.GetLayer("Front").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, Game1.pixelZoom);
- Game1.mapDisplayDevice.EndScene();
- Game1.currentLocation.drawAboveFrontLayer(Game1.spriteBatch);
- Game1.spriteBatch.End();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- if (Game1.currentLocation.Name.Equals("Farm") && Game1.stats.SeedsSown >= 200u)
- {
- Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2(3 * Game1.tileSize + Game1.tileSize / 4, Game1.tileSize + Game1.tileSize / 3)), Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16), Color.White);
- Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2(4 * Game1.tileSize + Game1.tileSize, 2 * Game1.tileSize + Game1.tileSize)), Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16), Color.White);
- Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2(5 * Game1.tileSize, 2 * Game1.tileSize)), Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16), Color.White);
- Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2(3 * Game1.tileSize + Game1.tileSize / 2, 3 * Game1.tileSize)), Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16), Color.White);
- Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2(5 * Game1.tileSize - Game1.tileSize / 4, Game1.tileSize)), Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16), Color.White);
- Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2(4 * Game1.tileSize, 3 * Game1.tileSize + Game1.tileSize / 6)), Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16), Color.White);
- Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2(4 * Game1.tileSize + Game1.tileSize / 5, 2 * Game1.tileSize + Game1.tileSize / 3)), Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16), Color.White);
- }
- if (Game1.displayFarmer && Game1.player.ActiveObject != null && Game1.player.ActiveObject.bigCraftable && this.checkBigCraftableBoundariesForFrontLayer() && Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), Game1.player.getStandingY()), Game1.viewport.Size) == null)
- Game1.drawPlayerHeldObject(Game1.player);
- else if (Game1.displayFarmer && Game1.player.ActiveObject != null && ((Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location((int)Game1.player.position.X, (int)Game1.player.position.Y - Game1.tileSize * 3 / 5), Game1.viewport.Size) != null && !Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location((int)Game1.player.position.X, (int)Game1.player.position.Y - Game1.tileSize * 3 / 5), Game1.viewport.Size).TileIndexProperties.ContainsKey("FrontAlways")) || (Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.GetBoundingBox().Right, (int)Game1.player.position.Y - Game1.tileSize * 3 / 5), Game1.viewport.Size) != null && !Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.GetBoundingBox().Right, (int)Game1.player.position.Y - Game1.tileSize * 3 / 5), Game1.viewport.Size).TileIndexProperties.ContainsKey("FrontAlways"))))
- Game1.drawPlayerHeldObject(Game1.player);
- if ((Game1.player.UsingTool || Game1.pickingTool) && Game1.player.CurrentTool != null && (!Game1.player.CurrentTool.Name.Equals("Seeds") || Game1.pickingTool) && Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), (int)Game1.player.position.Y - Game1.tileSize * 3 / 5), Game1.viewport.Size) != null && Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), Game1.player.getStandingY()), Game1.viewport.Size) == null)
- Game1.drawTool(Game1.player);
- if (Game1.currentLocation.Map.GetLayer("AlwaysFront") != null)
- {
- Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
- Game1.currentLocation.Map.GetLayer("AlwaysFront").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, Game1.pixelZoom);
- Game1.mapDisplayDevice.EndScene();
- }
- if (Game1.toolHold > 400f && Game1.player.CurrentTool.UpgradeLevel >= 1 && Game1.player.canReleaseTool)
- {
- Color color = Color.White;
- switch ((int)(Game1.toolHold / 600f) + 2)
+ else if ((int)Game1.gameMode == 11)
{
- case 1:
- color = Tool.copperColor;
- break;
- case 2:
- color = Tool.steelColor;
- break;
- case 3:
- color = Tool.goldColor;
- break;
- case 4:
- color = Tool.iridiumColor;
- break;
+ 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.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3686"), new Vector2(16f, 32f), new Color(0, (int)byte.MaxValue, 0));
+ Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.parseText(Game1.errorMessage, Game1.dialogueFont, Game1.graphics.GraphicsDevice.Viewport.Width), new Vector2(16f, 48f), Color.White);
+ Game1.spriteBatch.End();
}
- Game1.spriteBatch.Draw(Game1.littleEffect, new Rectangle((int)Game1.player.getLocalPosition(Game1.viewport).X - 2, (int)Game1.player.getLocalPosition(Game1.viewport).Y - (Game1.player.CurrentTool.Name.Equals("Watering Can") ? 0 : Game1.tileSize) - 2, (int)(Game1.toolHold % 600f * 0.08f) + 4, Game1.tileSize / 8 + 4), Color.Black);
- Game1.spriteBatch.Draw(Game1.littleEffect, new Rectangle((int)Game1.player.getLocalPosition(Game1.viewport).X, (int)Game1.player.getLocalPosition(Game1.viewport).Y - (Game1.player.CurrentTool.Name.Equals("Watering Can") ? 0 : Game1.tileSize), (int)(Game1.toolHold % 600f * 0.08f), Game1.tileSize / 8), color);
- }
- if (Game1.isDebrisWeather && Game1.currentLocation.IsOutdoors && !Game1.currentLocation.ignoreDebrisWeather && !Game1.currentLocation.Name.Equals("Desert") && Game1.viewport.X > -10)
- {
- foreach (WeatherDebris current6 in Game1.debrisWeather)
- current6.draw(Game1.spriteBatch);
- }
- Game1.farmEvent?.draw(Game1.spriteBatch);
- if (Game1.currentLocation.LightLevel > 0f && Game1.timeOfDay < 2000)
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * Game1.currentLocation.LightLevel);
- if (Game1.screenGlow)
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Game1.screenGlowColor * Game1.screenGlowAlpha);
- Game1.currentLocation.drawAboveAlwaysFrontLayer(Game1.spriteBatch);
- if (Game1.player.CurrentTool is FishingRod && ((Game1.player.CurrentTool as FishingRod).isTimingCast || (Game1.player.CurrentTool as FishingRod).castingChosenCountdown > 0f || (Game1.player.CurrentTool as FishingRod).fishCaught || (Game1.player.CurrentTool as FishingRod).showingTreasure))
- Game1.player.CurrentTool.draw(Game1.spriteBatch);
- if (Game1.isRaining && Game1.currentLocation.IsOutdoors && !Game1.currentLocation.Name.Equals("Desert") && !(Game1.currentLocation is Summit) && (!Game1.eventUp || Game1.currentLocation.isTileOnMap(new Vector2(Game1.viewport.X / Game1.tileSize, Game1.viewport.Y / Game1.tileSize))))
- {
- for (int j = 0; j < Game1.rainDrops.Length; j++)
- Game1.spriteBatch.Draw(Game1.rainTexture, Game1.rainDrops[j].position, Game1.getSourceRectForStandardTileSheet(Game1.rainTexture, Game1.rainDrops[j].frame), Color.White);
- }
-
- Game1.spriteBatch.End();
-
- //base.Draw(gameTime);
-
- Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- if (Game1.eventUp && Game1.currentLocation.currentEvent != null)
- {
- foreach (NPC current7 in Game1.currentLocation.currentEvent.actors)
+ else if (Game1.currentMinigame != null)
{
- if (current7.isEmoting)
+ Game1.currentMinigame.draw(Game1.spriteBatch);
+ if (Game1.globalFade && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause))
{
- Vector2 localPosition = current7.getLocalPosition(Game1.viewport);
- localPosition.Y -= Game1.tileSize * 2 + Game1.pixelZoom * 3;
- if (current7.age == 2)
- localPosition.Y += Game1.tileSize / 2;
- else if (current7.gender == 1)
- localPosition.Y += Game1.tileSize / 6;
- Game1.spriteBatch.Draw(Game1.emoteSpriteSheet, localPosition, new Rectangle(current7.CurrentEmoteIndex * (Game1.tileSize / 4) % Game1.emoteSpriteSheet.Width, current7.CurrentEmoteIndex * (Game1.tileSize / 4) / Game1.emoteSpriteSheet.Width * (Game1.tileSize / 4), Game1.tileSize / 4, Game1.tileSize / 4), Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, current7.getStandingY() / 10000f);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * ((int)Game1.gameMode == 0 ? 1f - Game1.fadeToBlackAlpha : Game1.fadeToBlackAlpha));
+ 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();
}
- }
- Game1.spriteBatch.End();
- if (Game1.drawLighting)
- {
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, new BlendState
+ else if (Game1.showingEndOfNightStuff)
{
- ColorBlendFunction = BlendFunction.ReverseSubtract,
- ColorDestinationBlend = Blend.One,
- ColorSourceBlend = Blend.SourceColor
- }, SamplerState.LinearClamp, null, null);
- Game1.spriteBatch.Draw(Game1.lightmap, Vector2.Zero, Game1.lightmap.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.lightingQuality, SpriteEffects.None, 1f);
- if (Game1.isRaining && Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert))
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ if (Game1.activeClickableMenu != null)
+ {
+ try
+ {
+ 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 during end-of-night-stuff. 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 == 6)
{
- Game1.spriteBatch.Draw(Game1.staminaRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.OrangeRed * 0.45f);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ string str1 = "";
+ for (int index = 0; (double)index < gameTime.TotalGameTime.TotalMilliseconds % 999.0 / 333.0; ++index)
+ str1 += ".";
+ string str2 = Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3688");
+ string str3 = str1;
+ string s = str2 + str3;
+ string str4 = "...";
+ string str5 = str2 + str4;
+ int widthOfString = SpriteText.getWidthOfString(str5);
+ int height = 64;
+ int x = 64;
+ int y = Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - height;
+ SpriteText.drawString(Game1.spriteBatch, s, x, y, 999999, widthOfString, height, 1f, 0.88f, false, 0, str5, -1);
+ 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();
}
- Game1.spriteBatch.End();
- }
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- if (Game1.drawGrid)
- {
- int num2 = -Game1.viewport.X % Game1.tileSize;
- float num3 = -(float)Game1.viewport.Y % Game1.tileSize;
- for (int k = num2; k < Game1.graphics.GraphicsDevice.Viewport.Width; k += Game1.tileSize)
- Game1.spriteBatch.Draw(Game1.staminaRect, new Rectangle(k, (int)num3, 1, Game1.graphics.GraphicsDevice.Viewport.Height), Color.Red * 0.5f);
- for (float num4 = num3; num4 < (float)Game1.graphics.GraphicsDevice.Viewport.Height; num4 += (float)Game1.tileSize)
- Game1.spriteBatch.Draw(Game1.staminaRect, new Rectangle(num2, (int)num4, Game1.graphics.GraphicsDevice.Viewport.Width, 1), Color.Red * 0.5f);
- }
- if (Game1.currentBillboard != 0)
- this.drawBillboard();
-
- if ((Game1.displayHUD || Game1.eventUp) && Game1.currentBillboard == 0 && Game1.gameMode == 3 && !Game1.freezeControls && !Game1.panMode)
- {
- GraphicsEvents.InvokeOnPreRenderHudEvent(this.Monitor);
- this.drawHUD();
- GraphicsEvents.InvokeOnPostRenderHudEvent(this.Monitor);
- }
- else if (Game1.activeClickableMenu == null && Game1.farmEvent == null)
- Game1.spriteBatch.Draw(Game1.mouseCursors, new Vector2(Game1.getOldMouseX(), Game1.getOldMouseY()), Game1.getSourceRectForStandardTileSheet(Game1.mouseCursors, 0, 16, 16), Color.White, 0f, Vector2.Zero, 4f + Game1.dialogueButtonScale / 150f, SpriteEffects.None, 1f);
+ else
+ {
+ Microsoft.Xna.Framework.Rectangle rectangle;
+ if ((int)Game1.gameMode == 0)
+ {
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ }
+ else
+ {
+ if (Game1.drawLighting)
+ {
+ this.GraphicsDevice.SetRenderTarget(Game1.lightmap);
+ this.GraphicsDevice.Clear(Color.White * 0.0f);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ Game1.spriteBatch.Draw(Game1.staminaRect, Game1.lightmap.Bounds, Game1.currentLocation.name.Equals("UndergroundMine") ? Game1.mine.getLightingColor(gameTime) : (Game1.ambientLight.Equals(Color.White) || Game1.isRaining && Game1.currentLocation.isOutdoors ? Game1.outdoorLight : Game1.ambientLight));
+ for (int index = 0; index < Game1.currentLightSources.Count; ++index)
+ {
+ if (Utility.isOnScreen(Game1.currentLightSources.ElementAt<LightSource>(index).position, (int)((double)Game1.currentLightSources.ElementAt<LightSource>(index).radius * (double)Game1.tileSize * 4.0)))
+ Game1.spriteBatch.Draw(Game1.currentLightSources.ElementAt<LightSource>(index).lightTexture, Game1.GlobalToLocal(Game1.viewport, Game1.currentLightSources.ElementAt<LightSource>(index).position) / (float)(Game1.options.lightingQuality / 2), new Microsoft.Xna.Framework.Rectangle?(Game1.currentLightSources.ElementAt<LightSource>(index).lightTexture.Bounds), Game1.currentLightSources.ElementAt<LightSource>(index).color, 0.0f, new Vector2((float)Game1.currentLightSources.ElementAt<LightSource>(index).lightTexture.Bounds.Center.X, (float)Game1.currentLightSources.ElementAt<LightSource>(index).lightTexture.Bounds.Center.Y), Game1.currentLightSources.ElementAt<LightSource>(index).radius / (float)(Game1.options.lightingQuality / 2), SpriteEffects.None, 0.9f);
+ }
+ Game1.spriteBatch.End();
+ this.GraphicsDevice.SetRenderTarget((double)Game1.options.zoomLevel == 1.0 ? (RenderTarget2D)null : this.screenWrapper);
+ }
+ if (Game1.bloomDay && Game1.bloom != null)
+ Game1.bloom.BeginDraw();
+ this.GraphicsDevice.Clear(this.bgColor);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ GraphicsEvents.InvokeOnPreRenderEvent(this.Monitor);
+ if (Game1.background != null)
+ Game1.background.draw(Game1.spriteBatch);
+ Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
+ Game1.currentLocation.Map.GetLayer("Back").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, Game1.pixelZoom);
+ Game1.currentLocation.drawWater(Game1.spriteBatch);
+ if (Game1.CurrentEvent == null)
+ {
+ foreach (NPC character in Game1.currentLocation.characters)
+ {
+ if (!character.swimming && !character.hideShadow && (!character.isInvisible && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(character.getTileLocation())))
+ Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, character.position + new Vector2((float)(character.sprite.spriteWidth * Game1.pixelZoom) / 2f, (float)(character.GetBoundingBox().Height + (character.IsMonster ? 0 : Game1.pixelZoom * 3)))), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), ((float)Game1.pixelZoom + (float)character.yJumpOffset / 40f) * character.scale, SpriteEffects.None, Math.Max(0.0f, (float)character.getStandingY() / 10000f) - 1E-06f);
+ }
+ }
+ else
+ {
+ foreach (NPC actor in Game1.CurrentEvent.actors)
+ {
+ if (!actor.swimming && !actor.hideShadow && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(actor.getTileLocation()))
+ Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, actor.position + new Vector2((float)(actor.sprite.spriteWidth * Game1.pixelZoom) / 2f, (float)(actor.GetBoundingBox().Height + (actor.IsMonster ? 0 : (actor.sprite.spriteHeight <= 16 ? -Game1.pixelZoom : Game1.pixelZoom * 3))))), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), ((float)Game1.pixelZoom + (float)actor.yJumpOffset / 40f) * actor.scale, SpriteEffects.None, Math.Max(0.0f, (float)actor.getStandingY() / 10000f) - 1E-06f);
+ }
+ }
+ Microsoft.Xna.Framework.Rectangle bounds;
+ if (Game1.displayFarmer && !Game1.player.swimming && (!Game1.player.isRidingHorse() && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(Game1.player.getTileLocation())))
+ {
+ SpriteBatch spriteBatch = Game1.spriteBatch;
+ Texture2D shadowTexture = Game1.shadowTexture;
+ Vector2 local = Game1.GlobalToLocal(Game1.player.position + new Vector2(32f, 24f));
+ Microsoft.Xna.Framework.Rectangle? sourceRectangle = new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds);
+ Color white = Color.White;
+ double num1 = 0.0;
+ double x = (double)Game1.shadowTexture.Bounds.Center.X;
+ bounds = Game1.shadowTexture.Bounds;
+ double y = (double)bounds.Center.Y;
+ Vector2 origin = new Vector2((float)x, (float)y);
+ double num2 = 4.0 - (!Game1.player.running && !Game1.player.usingTool || Game1.player.FarmerSprite.indexInCurrentAnimation <= 1 ? 0.0 : (double)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[Game1.player.FarmerSprite.CurrentFrame]) * 0.5);
+ int num3 = 0;
+ double num4 = 0.0;
+ spriteBatch.Draw(shadowTexture, local, sourceRectangle, white, (float)num1, origin, (float)num2, (SpriteEffects)num3, (float)num4);
+ }
+ Game1.currentLocation.Map.GetLayer("Buildings").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, Game1.pixelZoom);
+ Game1.mapDisplayDevice.EndScene();
+ Game1.spriteBatch.End();
+ Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ if (Game1.CurrentEvent == null)
+ {
+ foreach (NPC character in Game1.currentLocation.characters)
+ {
+ if (!character.swimming && !character.hideShadow && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(character.getTileLocation()))
+ {
+ SpriteBatch spriteBatch = Game1.spriteBatch;
+ Texture2D shadowTexture = Game1.shadowTexture;
+ Vector2 local = Game1.GlobalToLocal(Game1.viewport, character.position + new Vector2((float)(character.sprite.spriteWidth * Game1.pixelZoom) / 2f, (float)(character.GetBoundingBox().Height + (character.IsMonster ? 0 : Game1.pixelZoom * 3))));
+ Microsoft.Xna.Framework.Rectangle? sourceRectangle = new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds);
+ Color white = Color.White;
+ double num1 = 0.0;
+ bounds = Game1.shadowTexture.Bounds;
+ double x = (double)bounds.Center.X;
+ bounds = Game1.shadowTexture.Bounds;
+ double y = (double)bounds.Center.Y;
+ Vector2 origin = new Vector2((float)x, (float)y);
+ double num2 = ((double)Game1.pixelZoom + (double)character.yJumpOffset / 40.0) * (double)character.scale;
+ int num3 = 0;
+ double num4 = (double)Math.Max(0.0f, (float)character.getStandingY() / 10000f) - 9.99999997475243E-07;
+ spriteBatch.Draw(shadowTexture, local, sourceRectangle, white, (float)num1, origin, (float)num2, (SpriteEffects)num3, (float)num4);
+ }
+ }
+ }
+ else
+ {
+ foreach (NPC actor in Game1.CurrentEvent.actors)
+ {
+ if (!actor.swimming && !actor.hideShadow && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(actor.getTileLocation()))
+ {
+ SpriteBatch spriteBatch = Game1.spriteBatch;
+ Texture2D shadowTexture = Game1.shadowTexture;
+ Vector2 local = Game1.GlobalToLocal(Game1.viewport, actor.position + new Vector2((float)(actor.sprite.spriteWidth * Game1.pixelZoom) / 2f, (float)(actor.GetBoundingBox().Height + (actor.IsMonster ? 0 : Game1.pixelZoom * 3))));
+ Microsoft.Xna.Framework.Rectangle? sourceRectangle = new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds);
+ Color white = Color.White;
+ double num1 = 0.0;
+ bounds = Game1.shadowTexture.Bounds;
+ double x = (double)bounds.Center.X;
+ bounds = Game1.shadowTexture.Bounds;
+ double y = (double)bounds.Center.Y;
+ Vector2 origin = new Vector2((float)x, (float)y);
+ double num2 = ((double)Game1.pixelZoom + (double)actor.yJumpOffset / 40.0) * (double)actor.scale;
+ int num3 = 0;
+ double num4 = (double)Math.Max(0.0f, (float)actor.getStandingY() / 10000f) - 9.99999997475243E-07;
+ spriteBatch.Draw(shadowTexture, local, sourceRectangle, white, (float)num1, origin, (float)num2, (SpriteEffects)num3, (float)num4);
+ }
+ }
+ }
+ if (Game1.displayFarmer && !Game1.player.swimming && (!Game1.player.isRidingHorse() && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(Game1.player.getTileLocation())))
+ {
+ SpriteBatch spriteBatch = Game1.spriteBatch;
+ Texture2D shadowTexture = Game1.shadowTexture;
+ Vector2 local = Game1.GlobalToLocal(Game1.player.position + new Vector2(32f, 24f));
+ Microsoft.Xna.Framework.Rectangle? sourceRectangle = new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds);
+ Color white = Color.White;
+ double num1 = 0.0;
+ double x = (double)Game1.shadowTexture.Bounds.Center.X;
+ rectangle = Game1.shadowTexture.Bounds;
+ double y = (double)rectangle.Center.Y;
+ Vector2 origin = new Vector2((float)x, (float)y);
+ double num2 = 4.0 - (!Game1.player.running && !Game1.player.usingTool || Game1.player.FarmerSprite.indexInCurrentAnimation <= 1 ? 0.0 : (double)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[Game1.player.FarmerSprite.CurrentFrame]) * 0.5);
+ int num3 = 0;
+ double num4 = (double)Math.Max(0.0001f, (float)((double)Game1.player.getStandingY() / 10000.0 + 0.000110000000859145)) - 9.99999974737875E-05;
+ spriteBatch.Draw(shadowTexture, local, sourceRectangle, white, (float)num1, origin, (float)num2, (SpriteEffects)num3, (float)num4);
+ }
+ if (Game1.displayFarmer)
+ Game1.player.draw(Game1.spriteBatch);
+ if ((Game1.eventUp || Game1.killScreen) && (!Game1.killScreen && Game1.currentLocation.currentEvent != null))
+ Game1.currentLocation.currentEvent.draw(Game1.spriteBatch);
+ if (Game1.player.currentUpgrade != null && Game1.player.currentUpgrade.daysLeftTillUpgradeDone <= 3 && Game1.currentLocation.Name.Equals("Farm"))
+ Game1.spriteBatch.Draw(Game1.player.currentUpgrade.workerTexture, Game1.GlobalToLocal(Game1.viewport, Game1.player.currentUpgrade.positionOfCarpenter), new Microsoft.Xna.Framework.Rectangle?(Game1.player.currentUpgrade.getSourceRectangle()), Color.White, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, (float)(((double)Game1.player.currentUpgrade.positionOfCarpenter.Y + (double)(Game1.tileSize * 3 / 4)) / 10000.0));
+ Game1.currentLocation.draw(Game1.spriteBatch);
+ if (Game1.eventUp && Game1.currentLocation.currentEvent != null)
+ {
+ string messageToScreen = Game1.currentLocation.currentEvent.messageToScreen;
+ }
+ if (Game1.player.ActiveObject == null && (Game1.player.UsingTool || Game1.pickingTool) && (Game1.player.CurrentTool != null && (!Game1.player.CurrentTool.Name.Equals("Seeds") || Game1.pickingTool)))
+ Game1.drawTool(Game1.player);
+ if (Game1.currentLocation.Name.Equals("Farm"))
+ this.drawFarmBuildings();
+ if (Game1.tvStation >= 0)
+ Game1.spriteBatch.Draw(Game1.tvStationTexture, Game1.GlobalToLocal(Game1.viewport, new Vector2((float)(6 * Game1.tileSize + Game1.tileSize / 4), (float)(2 * Game1.tileSize + Game1.tileSize / 2))), new Microsoft.Xna.Framework.Rectangle?(new Microsoft.Xna.Framework.Rectangle(Game1.tvStation * 24, 0, 24, 15)), Color.White, 0.0f, Vector2.Zero, 4f, SpriteEffects.None, 1E-08f);
+ if (Game1.panMode)
+ {
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle((int)Math.Floor((double)(Game1.getOldMouseX() + Game1.viewport.X) / (double)Game1.tileSize) * Game1.tileSize - Game1.viewport.X, (int)Math.Floor((double)(Game1.getOldMouseY() + Game1.viewport.Y) / (double)Game1.tileSize) * Game1.tileSize - Game1.viewport.Y, Game1.tileSize, Game1.tileSize), Color.Lime * 0.75f);
+ foreach (Warp warp in Game1.currentLocation.warps)
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(warp.X * Game1.tileSize - Game1.viewport.X, warp.Y * Game1.tileSize - Game1.viewport.Y, Game1.tileSize, Game1.tileSize), Color.Red * 0.75f);
+ }
+ Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
+ Game1.currentLocation.Map.GetLayer("Front").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, Game1.pixelZoom);
+ Game1.mapDisplayDevice.EndScene();
+ Game1.currentLocation.drawAboveFrontLayer(Game1.spriteBatch);
+ Game1.spriteBatch.End();
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ if (Game1.currentLocation.Name.Equals("Farm") && Game1.stats.SeedsSown >= 200U)
+ {
+ Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2((float)(3 * Game1.tileSize + Game1.tileSize / 4), (float)(Game1.tileSize + Game1.tileSize / 3))), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16, -1, -1)), Color.White);
+ Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2((float)(4 * Game1.tileSize + Game1.tileSize), (float)(2 * Game1.tileSize + Game1.tileSize))), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16, -1, -1)), Color.White);
+ Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2((float)(5 * Game1.tileSize), (float)(2 * Game1.tileSize))), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16, -1, -1)), Color.White);
+ Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2((float)(3 * Game1.tileSize + Game1.tileSize / 2), (float)(3 * Game1.tileSize))), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16, -1, -1)), Color.White);
+ Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2((float)(5 * Game1.tileSize - Game1.tileSize / 4), (float)Game1.tileSize)), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16, -1, -1)), Color.White);
+ Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2((float)(4 * Game1.tileSize), (float)(3 * Game1.tileSize + Game1.tileSize / 6))), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16, -1, -1)), Color.White);
+ Game1.spriteBatch.Draw(Game1.debrisSpriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2((float)(4 * Game1.tileSize + Game1.tileSize / 5), (float)(2 * Game1.tileSize + Game1.tileSize / 3))), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.debrisSpriteSheet, 16, -1, -1)), Color.White);
+ }
+ if (Game1.displayFarmer && Game1.player.ActiveObject != null && (Game1.player.ActiveObject.bigCraftable && this.checkBigCraftableBoundariesForFrontLayer()) && Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), Game1.player.getStandingY()), Game1.viewport.Size) == null)
+ Game1.drawPlayerHeldObject(Game1.player);
+ else if (Game1.displayFarmer && Game1.player.ActiveObject != null)
+ {
+ if (Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location((int)Game1.player.position.X, (int)Game1.player.position.Y - Game1.tileSize * 3 / 5), Game1.viewport.Size) == null || Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location((int)Game1.player.position.X, (int)Game1.player.position.Y - Game1.tileSize * 3 / 5), Game1.viewport.Size).TileIndexProperties.ContainsKey("FrontAlways"))
+ {
+ Layer layer1 = Game1.currentLocation.Map.GetLayer("Front");
+ rectangle = Game1.player.GetBoundingBox();
+ Location mapDisplayLocation1 = new Location(rectangle.Right, (int)Game1.player.position.Y - Game1.tileSize * 3 / 5);
+ Size size1 = Game1.viewport.Size;
+ if (layer1.PickTile(mapDisplayLocation1, size1) != null)
+ {
+ Layer layer2 = Game1.currentLocation.Map.GetLayer("Front");
+ rectangle = Game1.player.GetBoundingBox();
+ Location mapDisplayLocation2 = new Location(rectangle.Right, (int)Game1.player.position.Y - Game1.tileSize * 3 / 5);
+ Size size2 = Game1.viewport.Size;
+ if (layer2.PickTile(mapDisplayLocation2, size2).TileIndexProperties.ContainsKey("FrontAlways"))
+ goto label_127;
+ }
+ else
+ goto label_127;
+ }
+ Game1.drawPlayerHeldObject(Game1.player);
+ }
+ label_127:
+ if ((Game1.player.UsingTool || Game1.pickingTool) && Game1.player.CurrentTool != null && ((!Game1.player.CurrentTool.Name.Equals("Seeds") || Game1.pickingTool) && (Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), (int)Game1.player.position.Y - Game1.tileSize * 3 / 5), Game1.viewport.Size) != null && Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), Game1.player.getStandingY()), Game1.viewport.Size) == null)))
+ Game1.drawTool(Game1.player);
+ if (Game1.currentLocation.Map.GetLayer("AlwaysFront") != null)
+ {
+ Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
+ Game1.currentLocation.Map.GetLayer("AlwaysFront").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, Game1.pixelZoom);
+ Game1.mapDisplayDevice.EndScene();
+ }
+ if ((double)Game1.toolHold > 400.0 && Game1.player.CurrentTool.UpgradeLevel >= 1 && Game1.player.canReleaseTool)
+ {
+ Color color = Color.White;
+ switch ((int)((double)Game1.toolHold / 600.0) + 2)
+ {
+ case 1:
+ color = Tool.copperColor;
+ break;
+ case 2:
+ color = Tool.steelColor;
+ break;
+ case 3:
+ color = Tool.goldColor;
+ break;
+ case 4:
+ color = Tool.iridiumColor;
+ break;
+ }
+ Game1.spriteBatch.Draw(Game1.littleEffect, new Microsoft.Xna.Framework.Rectangle((int)Game1.player.getLocalPosition(Game1.viewport).X - 2, (int)Game1.player.getLocalPosition(Game1.viewport).Y - (Game1.player.CurrentTool.Name.Equals("Watering Can") ? 0 : Game1.tileSize) - 2, (int)((double)Game1.toolHold % 600.0 * 0.0799999982118607) + 4, Game1.tileSize / 8 + 4), Color.Black);
+ Game1.spriteBatch.Draw(Game1.littleEffect, new Microsoft.Xna.Framework.Rectangle((int)Game1.player.getLocalPosition(Game1.viewport).X, (int)Game1.player.getLocalPosition(Game1.viewport).Y - (Game1.player.CurrentTool.Name.Equals("Watering Can") ? 0 : Game1.tileSize), (int)((double)Game1.toolHold % 600.0 * 0.0799999982118607), Game1.tileSize / 8), color);
+ }
+ if (Game1.isDebrisWeather && Game1.currentLocation.IsOutdoors && (!Game1.currentLocation.ignoreDebrisWeather && !Game1.currentLocation.Name.Equals("Desert")) && Game1.viewport.X > -10)
+ {
+ foreach (WeatherDebris weatherDebris in Game1.debrisWeather)
+ weatherDebris.draw(Game1.spriteBatch);
+ }
+ if (Game1.farmEvent != null)
+ Game1.farmEvent.draw(Game1.spriteBatch);
+ if ((double)Game1.currentLocation.LightLevel > 0.0 && Game1.timeOfDay < 2000)
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * Game1.currentLocation.LightLevel);
+ if (Game1.screenGlow)
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Game1.screenGlowColor * Game1.screenGlowAlpha);
+ Game1.currentLocation.drawAboveAlwaysFrontLayer(Game1.spriteBatch);
+ if (Game1.player.CurrentTool != null && Game1.player.CurrentTool is FishingRod && ((Game1.player.CurrentTool as FishingRod).isTimingCast || (double)(Game1.player.CurrentTool as FishingRod).castingChosenCountdown > 0.0 || ((Game1.player.CurrentTool as FishingRod).fishCaught || (Game1.player.CurrentTool as FishingRod).showingTreasure)))
+ Game1.player.CurrentTool.draw(Game1.spriteBatch);
+ if (Game1.isRaining && Game1.currentLocation.IsOutdoors && (!Game1.currentLocation.Name.Equals("Desert") && !(Game1.currentLocation is Summit)) && (!Game1.eventUp || Game1.currentLocation.isTileOnMap(new Vector2((float)(Game1.viewport.X / Game1.tileSize), (float)(Game1.viewport.Y / Game1.tileSize)))))
+ {
+ for (int index = 0; index < Game1.rainDrops.Length; ++index)
+ Game1.spriteBatch.Draw(Game1.rainTexture, Game1.rainDrops[index].position, new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.rainTexture, Game1.rainDrops[index].frame, -1, -1)), Color.White);
+ }
+ Game1.spriteBatch.End();
+ //base.Draw(gameTime);
+ Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ if (Game1.eventUp && Game1.currentLocation.currentEvent != null)
+ {
+ foreach (NPC actor in Game1.currentLocation.currentEvent.actors)
+ {
+ if (actor.isEmoting)
+ {
+ Vector2 localPosition = actor.getLocalPosition(Game1.viewport);
+ localPosition.Y -= (float)(Game1.tileSize * 2 + Game1.pixelZoom * 3);
+ if (actor.age == 2)
+ localPosition.Y += (float)(Game1.tileSize / 2);
+ else if (actor.gender == 1)
+ localPosition.Y += (float)(Game1.tileSize / 6);
+ Game1.spriteBatch.Draw(Game1.emoteSpriteSheet, localPosition, new Microsoft.Xna.Framework.Rectangle?(new Microsoft.Xna.Framework.Rectangle(actor.CurrentEmoteIndex * (Game1.tileSize / 4) % Game1.emoteSpriteSheet.Width, actor.CurrentEmoteIndex * (Game1.tileSize / 4) / Game1.emoteSpriteSheet.Width * (Game1.tileSize / 4), Game1.tileSize / 4, Game1.tileSize / 4)), Color.White, 0.0f, Vector2.Zero, 4f, SpriteEffects.None, (float)actor.getStandingY() / 10000f);
+ }
+ }
+ }
+ Game1.spriteBatch.End();
+ if (Game1.drawLighting)
+ {
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, this.lightingBlend, SamplerState.LinearClamp, (DepthStencilState)null, (RasterizerState)null);
+ Game1.spriteBatch.Draw((Texture2D)Game1.lightmap, Vector2.Zero, new Microsoft.Xna.Framework.Rectangle?(Game1.lightmap.Bounds), Color.White, 0.0f, Vector2.Zero, (float)(Game1.options.lightingQuality / 2), SpriteEffects.None, 1f);
+ if (Game1.isRaining && Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert))
+ Game1.spriteBatch.Draw(Game1.staminaRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.OrangeRed * 0.45f);
+ Game1.spriteBatch.End();
+ }
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ if (Game1.drawGrid)
+ {
+ int x1 = -Game1.viewport.X % Game1.tileSize;
+ float num1 = (float)(-Game1.viewport.Y % Game1.tileSize);
+ int x2 = x1;
+ while (x2 < Game1.graphics.GraphicsDevice.Viewport.Width)
+ {
+ Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(x2, (int)num1, 1, Game1.graphics.GraphicsDevice.Viewport.Height), Color.Red * 0.5f);
+ x2 += Game1.tileSize;
+ }
+ float num2 = num1;
+ while ((double)num2 < (double)Game1.graphics.GraphicsDevice.Viewport.Height)
+ {
+ Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(x1, (int)num2, Game1.graphics.GraphicsDevice.Viewport.Width, 1), Color.Red * 0.5f);
+ num2 += (float)Game1.tileSize;
+ }
+ }
+ if (Game1.currentBillboard != 0)
+ this.drawBillboard();
+ if ((Game1.displayHUD || Game1.eventUp) && (Game1.currentBillboard == 0 && (int)Game1.gameMode == 3) && (!Game1.freezeControls && !Game1.panMode))
+ {
+ GraphicsEvents.InvokeOnPreRenderHudEvent(this.Monitor);
+ this.drawHUD();
+ GraphicsEvents.InvokeOnPostRenderHudEvent(this.Monitor);
+ }
+ else if (Game1.activeClickableMenu == null && Game1.farmEvent == null)
+ Game1.spriteBatch.Draw(Game1.mouseCursors, new Vector2((float)Game1.getOldMouseX(), (float)Game1.getOldMouseY()), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.mouseCursors, 0, 16, 16)), Color.White, 0.0f, Vector2.Zero, (float)(4.0 + (double)Game1.dialogueButtonScale / 150.0), SpriteEffects.None, 1f);
+ if (Game1.hudMessages.Count > 0 && (!Game1.eventUp || Game1.isFestival()))
+ {
+ for (int i = Game1.hudMessages.Count - 1; i >= 0; --i)
+ Game1.hudMessages[i].draw(Game1.spriteBatch, i);
+ }
+ }
+ if (Game1.farmEvent != null)
+ Game1.farmEvent.draw(Game1.spriteBatch);
+ if (Game1.dialogueUp && !Game1.nameSelectUp && !Game1.messagePause && (Game1.activeClickableMenu == null || !(Game1.activeClickableMenu is DialogueBox)))
+ this.drawDialogueBox();
+ Viewport viewport;
+ if (Game1.progressBar)
+ {
+ SpriteBatch spriteBatch1 = Game1.spriteBatch;
+ Texture2D fadeToBlackRect = Game1.fadeToBlackRect;
+ int x1 = (Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Width - Game1.dialogueWidth) / 2;
+ rectangle = Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea;
+ int y1 = rectangle.Bottom - Game1.tileSize * 2;
+ int dialogueWidth = Game1.dialogueWidth;
+ int height1 = Game1.tileSize / 2;
+ Microsoft.Xna.Framework.Rectangle destinationRectangle1 = new Microsoft.Xna.Framework.Rectangle(x1, y1, dialogueWidth, height1);
+ Color lightGray = Color.LightGray;
+ spriteBatch1.Draw(fadeToBlackRect, destinationRectangle1, lightGray);
+ SpriteBatch spriteBatch2 = Game1.spriteBatch;
+ Texture2D staminaRect = Game1.staminaRect;
+ viewport = Game1.graphics.GraphicsDevice.Viewport;
+ int x2 = (viewport.TitleSafeArea.Width - Game1.dialogueWidth) / 2;
+ viewport = Game1.graphics.GraphicsDevice.Viewport;
+ rectangle = viewport.TitleSafeArea;
+ int y2 = rectangle.Bottom - Game1.tileSize * 2;
+ int width = (int)((double)Game1.pauseAccumulator / (double)Game1.pauseTime * (double)Game1.dialogueWidth);
+ int height2 = Game1.tileSize / 2;
+ Microsoft.Xna.Framework.Rectangle destinationRectangle2 = new Microsoft.Xna.Framework.Rectangle(x2, y2, width, height2);
+ Color dimGray = Color.DimGray;
+ spriteBatch2.Draw(staminaRect, destinationRectangle2, dimGray);
+ }
+ if (Game1.eventUp && Game1.currentLocation != null && Game1.currentLocation.currentEvent != null)
+ Game1.currentLocation.currentEvent.drawAfterMap(Game1.spriteBatch);
+ if (Game1.isRaining && Game1.currentLocation != null && (Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert)))
+ {
+ SpriteBatch spriteBatch = Game1.spriteBatch;
+ Texture2D staminaRect = Game1.staminaRect;
+ viewport = Game1.graphics.GraphicsDevice.Viewport;
+ Microsoft.Xna.Framework.Rectangle bounds = viewport.Bounds;
+ Color color = Color.Blue * 0.2f;
+ spriteBatch.Draw(staminaRect, bounds, color);
+ }
+ if ((Game1.fadeToBlack || Game1.globalFade) && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause))
+ {
+ SpriteBatch spriteBatch = Game1.spriteBatch;
+ Texture2D fadeToBlackRect = Game1.fadeToBlackRect;
+ viewport = Game1.graphics.GraphicsDevice.Viewport;
+ Microsoft.Xna.Framework.Rectangle bounds = viewport.Bounds;
+ Color color = Color.Black * ((int)Game1.gameMode == 0 ? 1f - Game1.fadeToBlackAlpha : Game1.fadeToBlackAlpha);
+ spriteBatch.Draw(fadeToBlackRect, bounds, color);
+ }
+ else if ((double)Game1.flashAlpha > 0.0)
+ {
+ if (Game1.options.screenFlash)
+ {
+ SpriteBatch spriteBatch = Game1.spriteBatch;
+ Texture2D fadeToBlackRect = Game1.fadeToBlackRect;
+ viewport = Game1.graphics.GraphicsDevice.Viewport;
+ Microsoft.Xna.Framework.Rectangle bounds = viewport.Bounds;
+ Color color = Color.White * Math.Min(1f, Game1.flashAlpha);
+ spriteBatch.Draw(fadeToBlackRect, bounds, color);
+ }
+ Game1.flashAlpha -= 0.1f;
+ }
+ if ((Game1.messagePause || Game1.globalFade) && Game1.dialogueUp)
+ this.drawDialogueBox();
+ foreach (TemporaryAnimatedSprite overlayTempSprite in Game1.screenOverlayTempSprites)
+ overlayTempSprite.draw(Game1.spriteBatch, true, 0, 0);
+ if (Game1.debugMode)
+ {
+ SpriteBatch spriteBatch = Game1.spriteBatch;
+ SpriteFont smallFont = Game1.smallFont;
+ object[] objArray = new object[10];
+ int index1 = 0;
+ string str1;
+ if (!Game1.panMode)
+ str1 = "player: " + (object)(Game1.player.getStandingX() / Game1.tileSize) + ", " + (object)(Game1.player.getStandingY() / Game1.tileSize);
+ else
+ str1 = ((Game1.getOldMouseX() + Game1.viewport.X) / Game1.tileSize).ToString() + "," + (object)((Game1.getOldMouseY() + Game1.viewport.Y) / Game1.tileSize);
+ objArray[index1] = (object)str1;
+ int index2 = 1;
+ string str2 = " mouseTransparency: ";
+ objArray[index2] = (object)str2;
+ int index3 = 2;
+ float cursorTransparency = Game1.mouseCursorTransparency;
+ objArray[index3] = (object)cursorTransparency;
+ int index4 = 3;
+ string str3 = " mousePosition: ";
+ objArray[index4] = (object)str3;
+ int index5 = 4;
+ int mouseX = Game1.getMouseX();
+ objArray[index5] = (object)mouseX;
+ int index6 = 5;
+ string str4 = ",";
+ objArray[index6] = (object)str4;
+ int index7 = 6;
+ int mouseY = Game1.getMouseY();
+ objArray[index7] = (object)mouseY;
+ int index8 = 7;
+ string newLine = Environment.NewLine;
+ objArray[index8] = (object)newLine;
+ int index9 = 8;
+ string str5 = "debugOutput: ";
+ objArray[index9] = (object)str5;
+ int index10 = 9;
+ string debugOutput = Game1.debugOutput;
+ objArray[index10] = (object)debugOutput;
+ string text = string.Concat(objArray);
+ Vector2 position = new Vector2((float)this.GraphicsDevice.Viewport.TitleSafeArea.X, (float)this.GraphicsDevice.Viewport.TitleSafeArea.Y);
+ Color red = Color.Red;
+ double num1 = 0.0;
+ Vector2 zero = Vector2.Zero;
+ double num2 = 1.0;
+ int num3 = 0;
+ double num4 = 0.99999988079071;
+ spriteBatch.DrawString(smallFont, text, position, red, (float)num1, zero, (float)num2, (SpriteEffects)num3, (float)num4);
+ }
+ if (Game1.showKeyHelp)
+ Game1.spriteBatch.DrawString(Game1.smallFont, Game1.keyHelpString, new Vector2((float)Game1.tileSize, (float)(Game1.viewport.Height - Game1.tileSize - (Game1.dialogueUp ? Game1.tileSize * 3 + (Game1.isQuestion ? Game1.questionChoices.Count * Game1.tileSize : 0) : 0)) - Game1.smallFont.MeasureString(Game1.keyHelpString).Y), Color.LightGray, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9999999f);
+ if (Game1.activeClickableMenu != null)
+ {
+ try
+ {
+ 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();
+ }
+ }
+ else if (Game1.farmEvent != null)
+ Game1.farmEvent.drawAboveEverything(Game1.spriteBatch);
+ Game1.spriteBatch.End();
+ if (Game1.overlayMenu != null)
+ {
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ Game1.overlayMenu.draw(Game1.spriteBatch);
+ Game1.spriteBatch.End();
+ }
- if (Game1.hudMessages.Any() && (!Game1.eventUp || Game1.isFestival()))
- {
- for (int l = Game1.hudMessages.Count - 1; l >= 0; l--)
- Game1.hudMessages[l].draw(Game1.spriteBatch, l);
- }
- }
- Game1.farmEvent?.draw(Game1.spriteBatch);
- if (Game1.dialogueUp && !Game1.nameSelectUp && !Game1.messagePause && !(Game1.activeClickableMenu is DialogueBox))
- this.drawDialogueBox();
- if (Game1.progressBar)
- {
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Rectangle((Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - Game1.tileSize * 2, Game1.dialogueWidth, Game1.tileSize / 2), Color.LightGray);
- Game1.spriteBatch.Draw(Game1.staminaRect, new Rectangle((Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - Game1.tileSize * 2, (int)(Game1.pauseAccumulator / Game1.pauseTime * Game1.dialogueWidth), Game1.tileSize / 2), Color.DimGray);
- }
- if (Game1.eventUp)
- Game1.currentLocation.currentEvent?.drawAfterMap(Game1.spriteBatch);
- if (Game1.isRaining && Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert))
- Game1.spriteBatch.Draw(Game1.staminaRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Blue * 0.2f);
- if ((Game1.fadeToBlack || Game1.globalFade) && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause))
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * ((Game1.gameMode == 0) ? (1f - Game1.fadeToBlackAlpha) : Game1.fadeToBlackAlpha));
- else if (Game1.flashAlpha > 0f)
- {
- if (Game1.options.screenFlash)
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.White * Math.Min(1f, Game1.flashAlpha));
- Game1.flashAlpha -= 0.1f;
- }
- if ((Game1.messagePause || Game1.globalFade) && Game1.dialogueUp)
- this.drawDialogueBox();
- foreach (TemporaryAnimatedSprite current8 in Game1.screenOverlayTempSprites)
- current8.draw(Game1.spriteBatch, true);
- if (Game1.debugMode)
- {
- Game1.spriteBatch.DrawString(Game1.smallFont, string.Concat(new object[]
- {
- Game1.panMode ? ((Game1.getOldMouseX() + Game1.viewport.X) / Game1.tileSize + "," + (Game1.getOldMouseY() + Game1.viewport.Y) / Game1.tileSize) : string.Concat("aplayer: ", Game1.player.getStandingX() / Game1.tileSize, ", ", Game1.player.getStandingY() / Game1.tileSize),
- Environment.NewLine,
- "debugOutput: ",
- Game1.debugOutput
- }), new Vector2(this.GraphicsDevice.Viewport.TitleSafeArea.X, this.GraphicsDevice.Viewport.TitleSafeArea.Y), Color.Red, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9999999f);
- }
- /*if (inputMode)
- {
- spriteBatch.DrawString(smallFont, "Input: " + debugInput, new Vector2(tileSize, tileSize * 3), Color.Purple);
- }*/
- if (Game1.showKeyHelp)
- Game1.spriteBatch.DrawString(Game1.smallFont, Game1.keyHelpString, new Vector2(Game1.tileSize, Game1.viewport.Height - Game1.tileSize - (Game1.dialogueUp ? (Game1.tileSize * 3 + (Game1.isQuestion ? (Game1.questionChoices.Count * Game1.tileSize) : 0)) : 0) - Game1.smallFont.MeasureString(Game1.keyHelpString).Y), Color.LightGray, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9999999f);
+ if (GraphicsEvents.HasPostRenderListeners())
+ {
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor);
+ Game1.spriteBatch.End();
+ }
- if (Game1.activeClickableMenu != null)
- {
- GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor);
- try
- {
- Game1.activeClickableMenu.draw(Game1.spriteBatch);
- }
- 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();
+ this.renderScreenBuffer();
+ }
}
- GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor);
- }
- else
- Game1.farmEvent?.drawAboveEverything(Game1.spriteBatch);
-
- GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor);
- Game1.spriteBatch.End();
-
- if (!this.ZoomLevelIsOne)
- {
- this.GraphicsDevice.SetRenderTarget(null);
- this.GraphicsDevice.Clear(this.bgColor);
- Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone);
- Game1.spriteBatch.Draw(this.screenWrapper, Vector2.Zero, this.screenWrapper.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f);
- Game1.spriteBatch.End();
}
}
catch (Exception ex)
@@ -801,8 +1063,19 @@ namespace StardewModdingAPI.Framework
/// <summary>Detect changes since the last update ticket and trigger mod events.</summary>
private void UpdateEventCalls()
{
+ // content locale changed event
+ if (this.PreviousLocale != LocalizedContentManager.CurrentLanguageCode)
+ {
+ var oldValue = this.PreviousLocale;
+ var newValue = LocalizedContentManager.CurrentLanguageCode;
+
+ if (oldValue != null)
+ ContentEvents.InvokeAfterLocaleChanged(this.Monitor, oldValue.ToString(), newValue.ToString());
+ this.PreviousLocale = newValue;
+ }
+
// save loaded event
- if (Constants.IsSaveLoaded && this.AfterLoadTimer >= 0)
+ if (Constants.IsSaveLoaded && !SaveGame.IsProcessing/*still loading save*/ && this.AfterLoadTimer >= 0)
{
if (this.AfterLoadTimer == 0)
{
@@ -874,7 +1147,7 @@ namespace StardewModdingAPI.Framework
{
ControlEvents.InvokeMouseChanged(this.Monitor, this.MStatePrior, this.MStateNow, this.MPositionPrior, this.MPositionNow);
this.MStatePrior = this.MStateNow;
- this.MPositionPrior = this.MPositionPrior;
+ this.MPositionPrior = this.MPositionNow;
}
}
@@ -1060,4 +1333,4 @@ namespace StardewModdingAPI.Framework
return hash;
}
}
-} \ No newline at end of file
+}
diff --git a/src/StardewModdingAPI/ISemanticVersion.cs b/src/StardewModdingAPI/ISemanticVersion.cs
index 3b9bdb44..27a2f67d 100644
--- a/src/StardewModdingAPI/ISemanticVersion.cs
+++ b/src/StardewModdingAPI/ISemanticVersion.cs
@@ -28,10 +28,31 @@ namespace StardewModdingAPI
/// <param name="other">The version to compare with this instance.</param>
bool IsOlderThan(ISemanticVersion other);
+ /// <summary>Get whether this version is older than the specified version.</summary>
+ /// <param name="other">The version to compare with this instance.</param>
+ /// <exception cref="FormatException">The specified version is not a valid semantic version.</exception>
+ bool IsOlderThan(string other);
+
/// <summary>Get whether this version is newer than the specified version.</summary>
/// <param name="other">The version to compare with this instance.</param>
bool IsNewerThan(ISemanticVersion other);
+ /// <summary>Get whether this version is newer than the specified version.</summary>
+ /// <param name="other">The version to compare with this instance.</param>
+ /// <exception cref="FormatException">The specified version is not a valid semantic version.</exception>
+ bool IsNewerThan(string other);
+
+ /// <summary>Get whether this version is between two specified versions (inclusively).</summary>
+ /// <param name="min">The minimum version.</param>
+ /// <param name="max">The maximum version.</param>
+ bool IsBetween(ISemanticVersion min, ISemanticVersion max);
+
+ /// <summary>Get whether this version is between two specified versions (inclusively).</summary>
+ /// <param name="min">The minimum version.</param>
+ /// <param name="max">The maximum version.</param>
+ /// <exception cref="FormatException">One of the specified versions is not a valid semantic version.</exception>
+ bool IsBetween(string min, string max);
+
/// <summary>Get a string representation of the version.</summary>
string ToString();
}
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs
index 58850dc3..31aeb3a6 100644
--- a/src/StardewModdingAPI/Program.cs
+++ b/src/StardewModdingAPI/Program.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -10,7 +9,6 @@ using System.Threading;
using System.Management;
using System.Windows.Forms;
#endif
-using Microsoft.Xna.Framework.Graphics;
using Newtonsoft.Json;
using StardewModdingAPI.AssemblyRewriters;
using StardewModdingAPI.Events;
@@ -18,13 +16,12 @@ using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Logging;
using StardewModdingAPI.Framework.Models;
using StardewModdingAPI.Framework.Serialisation;
-using StardewValley;
using Monitor = StardewModdingAPI.Framework.Monitor;
namespace StardewModdingAPI
{
/// <summary>The main entry point for SMAPI, responsible for hooking into and launching the game.</summary>
- internal class Program
+ internal class Program : IDisposable
{
/*********
** Properties
@@ -38,26 +35,33 @@ namespace StardewModdingAPI
/// <summary>The core logger for SMAPI.</summary>
private readonly Monitor Monitor;
- /// <summary>The SMAPI configuration settings.</summary>
- private readonly SConfig Settings;
-
/// <summary>Tracks whether the game should exit immediately and any pending initialisation should be cancelled.</summary>
private readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();
- /// <summary>Whether the game is currently running.</summary>
- private bool IsGameRunning;
-
/// <summary>The underlying game instance.</summary>
private SGame GameInstance;
+ /// <summary>The SMAPI configuration settings.</summary>
+ /// <remarks>This is initialised after the game starts.</remarks>
+ private SConfig Settings;
+
/// <summary>Tracks the installed mods.</summary>
- private readonly ModRegistry ModRegistry;
+ /// <remarks>This is initialised after the game starts.</remarks>
+ private ModRegistry ModRegistry;
/// <summary>Manages deprecation warnings.</summary>
- private readonly DeprecationManager DeprecationManager;
+ /// <remarks>This is initialised after the game starts.</remarks>
+ private DeprecationManager DeprecationManager;
/// <summary>Manages console commands.</summary>
- private readonly CommandManager CommandManager = new CommandManager();
+ /// <remarks>This is initialised after the game starts.</remarks>
+ private CommandManager CommandManager;
+
+ /// <summary>Whether the game is currently running.</summary>
+ private bool IsGameRunning;
+
+ /// <summary>Whether the program has been disposed.</summary>
+ private bool IsDisposed;
/*********
@@ -65,7 +69,7 @@ namespace StardewModdingAPI
*********/
/// <summary>The main entry point which hooks into and launches the game.</summary>
/// <param name="args">The command-line arguments.</param>
- private static void Main(string[] args)
+ public static void Main(string[] args)
{
// get flags from arguments
bool writeToConsole = !args.Contains("--no-terminal");
@@ -85,71 +89,35 @@ namespace StardewModdingAPI
logPath = Constants.DefaultLogPath;
// load SMAPI
- new Program(writeToConsole, logPath)
- .LaunchInteractively();
+ using (Program program = new Program(writeToConsole, logPath))
+ program.RunInteractively();
}
/// <summary>Construct an instance.</summary>
/// <param name="writeToConsole">Whether to output log messages to the console.</param>
/// <param name="logPath">The full file path to which to write log messages.</param>
- internal Program(bool writeToConsole, string logPath)
+ public Program(bool writeToConsole, string logPath)
{
- // load settings
- this.Settings = JsonConvert.DeserializeObject<SConfig>(File.ReadAllText(Constants.ApiConfigPath));
-
- // initialise
this.LogFile = new LogFileManager(logPath);
this.Monitor = new Monitor("SMAPI", this.ConsoleManager, this.LogFile, this.ExitGameImmediately) { WriteToConsole = writeToConsole };
- this.ModRegistry = new ModRegistry(this.Settings.ModCompatibility);
- this.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry);
}
/// <summary>Launch SMAPI.</summary>
- internal void LaunchInteractively()
+ public void RunInteractively()
{
- // initialise logging
- Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); // for consistent log formatting
- this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} on {this.GetFriendlyPlatformName()}", LogLevel.Info);
- Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)}";
-
- // inject compatibility shims
-#pragma warning disable 618
- Command.Shim(this.CommandManager, this.DeprecationManager, this.ModRegistry);
- Config.Shim(this.DeprecationManager);
- InternalExtensions.Shim(this.ModRegistry);
- Log.Shim(this.DeprecationManager, this.GetSecondaryMonitor("legacy mod"), this.ModRegistry);
- Mod.Shim(this.DeprecationManager);
- ContentEvents.Shim(this.ModRegistry, this.Monitor);
- PlayerEvents.Shim(this.DeprecationManager);
- TimeEvents.Shim(this.DeprecationManager);
-#pragma warning restore 618
-
- // redirect direct console output
- {
- Monitor monitor = this.GetSecondaryMonitor("Console.Out");
- monitor.WriteToFile = false; // not useful for troubleshooting mods per discussion
- if (monitor.WriteToConsole)
- this.ConsoleManager.OnLineIntercepted += line => monitor.Log(line, LogLevel.Trace);
- }
-
- // add warning headers
- if (this.Settings.DeveloperMode)
+ // initialise SMAPI
+ try
{
- this.Monitor.ShowTraceInConsole = true;
- this.Monitor.Log($"You configured SMAPI to run in developer mode. The console may be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing {Constants.ApiConfigPath}.", LogLevel.Warn);
- }
- if (!this.Settings.CheckForUpdates)
- this.Monitor.Log($"You configured SMAPI to not check for updates. Running an old version of SMAPI is not recommended. You can enable update checks by reinstalling SMAPI or editing {Constants.ApiConfigPath}.", LogLevel.Warn);
- if (!this.Monitor.WriteToConsole)
- this.Monitor.Log("Writing to the terminal is disabled because the --no-terminal argument was received. This usually means launching the terminal failed.", LogLevel.Warn);
+ // init logging
+ this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} on {this.GetFriendlyPlatformName()}", LogLevel.Info);
+ this.Monitor.Log($"Mods go here: {Constants.ModPath}");
+ this.Monitor.Log("Preparing SMAPI...");
- // print file paths
- this.Monitor.Log($"Mods go here: {Constants.ModPath}");
+ // validate paths
+ this.VerifyPath(Constants.ModPath);
+ this.VerifyPath(Constants.LogDir);
- // hook into & launch the game
- try
- {
- // verify version
+ // validate game version
if (Constants.GameVersion.IsOlderThan(Constants.MinimumGameVersion))
{
this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)}, but the oldest supported version is {Constants.GetGameDisplayVersion(Constants.MinimumGameVersion)}. Please update your game before using SMAPI. If you have the beta version on Steam, you may need to opt out to get the latest non-beta updates.", LogLevel.Error);
@@ -163,29 +131,59 @@ namespace StardewModdingAPI
return;
}
- // initialise folders
- this.Monitor.Log("Loading SMAPI...");
- this.VerifyPath(Constants.ModPath);
- this.VerifyPath(Constants.LogDir);
+ // add error handlers
+#if SMAPI_FOR_WINDOWS
+ Application.ThreadException += (sender, e) => this.Monitor.Log($"Critical thread exception: {e.Exception.GetLogSummary()}", LogLevel.Error);
+ Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
+#endif
+ AppDomain.CurrentDomain.UnhandledException += (sender, e) => this.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error);
+
+ // override game
+ this.GameInstance = new SGame(this.Monitor);
+ StardewValley.Program.gamePtr = this.GameInstance;
+
+ // hook into game events
+#if SMAPI_FOR_WINDOWS
+ ((Form)Control.FromHandle(this.GameInstance.Window.Handle)).FormClosing += (sender, args) => this.Dispose();
+#endif
+ this.GameInstance.Exiting += (sender, e) => this.Dispose();
+ this.GameInstance.Window.ClientSizeChanged += (sender, e) => GraphicsEvents.InvokeResize(this.Monitor, sender, e);
+ GameEvents.InitializeInternal += (sender, e) => this.InitialiseAfterGameStart();
+ GameEvents.GameLoaded += (sender, e) => this.CheckForUpdateAsync();
- // check for update when game loads
- if (this.Settings.CheckForUpdates)
- GameEvents.GameLoaded += (sender, e) => this.CheckForUpdateAsync();
+ // set window titles
+ this.GameInstance.Window.Title = $"Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} - running SMAPI {Constants.ApiVersion}";
+ Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)}";
+ }
+ catch (Exception ex)
+ {
+ this.Monitor.Log($"SMAPI failed to initialise: {ex.GetLogSummary()}", LogLevel.Error);
+ this.PressAnyKeyToExit();
+ return;
+ }
- // launch game
- this.StartGame();
+ // start game
+ this.Monitor.Log("Starting game...");
+ try
+ {
+ this.IsGameRunning = true;
+ this.GameInstance.Run();
}
catch (Exception ex)
{
- this.Monitor.Log($"Critical error: {ex.GetLogSummary()}", LogLevel.Error);
+ this.Monitor.Log($"The game failed unexpectedly: {ex.GetLogSummary()}", LogLevel.Error);
+ this.PressAnyKeyToExit();
+ }
+ finally
+ {
+ this.Dispose();
}
- this.PressAnyKeyToExit();
}
/// <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>
/// <param name="module">The module which requested an immediate exit.</param>
/// <param name="reason">The reason provided for the shutdown.</param>
- internal void ExitGameImmediately(string module, string reason)
+ public void ExitGameImmediately(string module, string reason)
{
this.Monitor.LogFatal($"{module} requested an immediate game shutdown: {reason}");
this.CancellationTokenSource.Cancel();
@@ -204,13 +202,124 @@ namespace StardewModdingAPI
return this.GetSecondaryMonitor(modName);
}
+ /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
+ public void Dispose()
+ {
+ if (this.IsDisposed)
+ return;
+ this.IsDisposed = true;
+
+ this.IsGameRunning = false;
+ this.LogFile?.Dispose();
+ this.ConsoleManager?.Dispose();
+ this.CancellationTokenSource?.Dispose();
+ this.GameInstance?.Dispose();
+ }
+
/*********
** Private methods
*********/
+ /// <summary>Initialise SMAPI and mods after the game starts.</summary>
+ private void InitialiseAfterGameStart()
+ {
+ // load settings
+ this.Settings = JsonConvert.DeserializeObject<SConfig>(File.ReadAllText(Constants.ApiConfigPath));
+
+ // load core components
+ this.ModRegistry = new ModRegistry(this.Settings.ModCompatibility);
+ this.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry);
+ this.CommandManager = new CommandManager();
+
+ // inject compatibility shims
+#pragma warning disable 618
+ Command.Shim(this.CommandManager, this.DeprecationManager, this.ModRegistry);
+ Config.Shim(this.DeprecationManager);
+ InternalExtensions.Shim(this.ModRegistry);
+ Log.Shim(this.DeprecationManager, this.GetSecondaryMonitor("legacy mod"), this.ModRegistry);
+ Mod.Shim(this.DeprecationManager);
+ ContentEvents.Shim(this.ModRegistry, this.Monitor);
+ GameEvents.Shim(this.DeprecationManager);
+ PlayerEvents.Shim(this.DeprecationManager);
+ TimeEvents.Shim(this.DeprecationManager);
+#pragma warning restore 618
+
+ // redirect direct console output
+ {
+ Monitor monitor = this.GetSecondaryMonitor("Console.Out");
+ monitor.WriteToFile = false; // not useful for troubleshooting mods per discussion
+ if (monitor.WriteToConsole)
+ this.ConsoleManager.OnLineIntercepted += line => monitor.Log(line, LogLevel.Trace);
+ }
+
+ // add warning headers
+ if (this.Settings.DeveloperMode)
+ {
+ this.Monitor.ShowTraceInConsole = true;
+ this.Monitor.Log($"You configured SMAPI to run in developer mode. The console may be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing {Constants.ApiConfigPath}.", LogLevel.Warn);
+ }
+ if (!this.Settings.CheckForUpdates)
+ this.Monitor.Log($"You configured SMAPI to not check for updates. Running an old version of SMAPI is not recommended. You can enable update checks by reinstalling SMAPI or editing {Constants.ApiConfigPath}.", LogLevel.Warn);
+ if (!this.Monitor.WriteToConsole)
+ this.Monitor.Log("Writing to the terminal is disabled because the --no-terminal argument was received. This usually means launching the terminal failed.", LogLevel.Warn);
+
+ // load mods
+ int modsLoaded = this.LoadMods();
+ if (this.CancellationTokenSource.IsCancellationRequested)
+ {
+ this.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error);
+ return;
+ }
+
+ // update window titles
+ this.GameInstance.Window.Title = $"Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} - running SMAPI {Constants.ApiVersion} with {modsLoaded} mods";
+ Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} with {modsLoaded} mods";
+
+ // start SMAPI console
+ new Thread(this.RunConsoleLoop).Start();
+ }
+
+ /// <summary>Run a loop handling console input.</summary>
+ [SuppressMessage("ReSharper", "FunctionNeverReturns", Justification = "The thread is aborted when the game exits.")]
+ private void RunConsoleLoop()
+ {
+ // prepare help command
+ this.Monitor.Log("Starting console...");
+ this.Monitor.Log("Type 'help' for help, or 'help <cmd>' for a command's usage", LogLevel.Info);
+ this.CommandManager.Add("SMAPI", "help", "Lists all commands | 'help <cmd>' returns command description", this.HandleHelpCommand);
+
+ // start handling command line input
+ Thread inputThread = new Thread(() =>
+ {
+ while (true)
+ {
+ string input = Console.ReadLine();
+ try
+ {
+ if (!string.IsNullOrWhiteSpace(input) && !this.CommandManager.Trigger(input))
+ this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error);
+ }
+ catch (Exception ex)
+ {
+ this.Monitor.Log($"The handler registered for that command failed:\n{ex.GetLogSummary()}", LogLevel.Error);
+ }
+ }
+ });
+ inputThread.Start();
+
+ // keep console thread alive while the game is running
+ while (this.IsGameRunning)
+ Thread.Sleep(1000 / 10);
+ if (inputThread.ThreadState == ThreadState.Running)
+ inputThread.Abort();
+ }
+
/// <summary>Asynchronously check for a new version of SMAPI, and print a message to the console if an update is available.</summary>
private void CheckForUpdateAsync()
{
+ if (!this.Settings.CheckForUpdates)
+ return;
+
new Thread(() =>
{
try
@@ -227,87 +336,6 @@ namespace StardewModdingAPI
}).Start();
}
- /// <summary>Hook into Stardew Valley and launch the game.</summary>
- private void StartGame()
- {
- try
- {
- this.Monitor.Log("Loading game...");
-
- // add error handlers
-#if SMAPI_FOR_WINDOWS
- Application.ThreadException += (sender, e) => this.Monitor.Log($"Critical thread exception: {e.Exception.GetLogSummary()}", LogLevel.Error);
- Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
-#endif
- AppDomain.CurrentDomain.UnhandledException += (sender, e) => this.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error);
-
- // override Game1 instance
- this.GameInstance = new SGame(this.Monitor);
- this.GameInstance.Exiting += (sender, e) => this.IsGameRunning = false;
- this.GameInstance.Window.ClientSizeChanged += (sender, e) => GraphicsEvents.InvokeResize(this.Monitor, sender, e);
- this.GameInstance.Window.Title = $"Stardew Valley {Constants.GameVersion} with SMAPI {Constants.ApiVersion}";
- {
- Type type = typeof(Game1).Assembly.GetType("StardewValley.Program", true);
- type.GetField("gamePtr").SetValue(null, this.GameInstance);
- }
-
- // configure
- Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef;
-
- // load mods
- this.LoadMods();
- if (this.CancellationTokenSource.IsCancellationRequested)
- {
- this.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error);
- return;
- }
-
- // initialise console after game launches
- new Thread(() =>
- {
- // wait for the game to load up
- while (!this.IsGameRunning)
- Thread.Sleep(1000);
-
- // register help command
- this.CommandManager.Add("SMAPI", "help", "Lists all commands | 'help <cmd>' returns command description", this.HandleHelpCommand);
-
- // listen for command line input
- this.Monitor.Log("Starting console...");
- this.Monitor.Log("Type 'help' for help, or 'help <cmd>' for a command's usage", LogLevel.Info);
- Thread consoleInputThread = new Thread(this.ConsoleInputLoop);
- consoleInputThread.Start();
- while (this.IsGameRunning)
- Thread.Sleep(1000 / 10); // Check if the game is still running 10 times a second
-
- // abort the console thread, we're closing
- if (consoleInputThread.ThreadState == ThreadState.Running)
- consoleInputThread.Abort();
- }).Start();
-
- // start game loop
- this.Monitor.Log("Starting game...");
- if (this.CancellationTokenSource.IsCancellationRequested)
- {
- this.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error);
- return;
- }
- try
- {
- this.IsGameRunning = true;
- this.GameInstance.Run();
- }
- finally
- {
- this.IsGameRunning = false;
- }
- }
- catch (Exception ex)
- {
- this.Monitor.Log($"The game encountered a fatal error:\n{ex.GetLogSummary()}", LogLevel.Error);
- }
- }
-
/// <summary>Create a directory path if it doesn't exist.</summary>
/// <param name="path">The directory path.</param>
private void VerifyPath(string path)
@@ -324,7 +352,8 @@ namespace StardewModdingAPI
}
/// <summary>Load and hook up all mods in the mod directory.</summary>
- private void LoadMods()
+ /// <returns>Returns the number of mods loaded.</returns>
+ private int LoadMods()
{
this.Monitor.Log("Loading mods...");
@@ -349,7 +378,7 @@ namespace StardewModdingAPI
if (this.CancellationTokenSource.IsCancellationRequested)
{
this.Monitor.Log("Shutdown requested; interrupting mod loading.", LogLevel.Error);
- return;
+ return modsLoaded;
}
// get manifest path
@@ -529,12 +558,12 @@ namespace StardewModdingAPI
}
// initialise mods
- foreach (Mod mod in this.ModRegistry.GetMods())
+ foreach (IMod mod in this.ModRegistry.GetMods())
{
try
{
// call entry methods
- mod.Entry(); // deprecated since 1.0
+ (mod as Mod)?.Entry(); // deprecated since 1.0
mod.Entry(mod.Helper);
// raise deprecation warning for old Entry() methods
@@ -551,26 +580,8 @@ namespace StardewModdingAPI
this.Monitor.Log($"Loaded {modsLoaded} mods.");
foreach (Action warning in deprecationWarnings)
warning();
- Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} with {modsLoaded} mods";
- }
- /// <summary>Run a loop handling console input.</summary>
- [SuppressMessage("ReSharper", "FunctionNeverReturns", Justification = "The thread is aborted when the game exits.")]
- private void ConsoleInputLoop()
- {
- while (true)
- {
- string input = Console.ReadLine();
- try
- {
- if (!string.IsNullOrWhiteSpace(input) && !this.CommandManager.Trigger(input))
- this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error);
- }
- catch (Exception ex)
- {
- this.Monitor.Log($"The handler registered for that command failed:\n{ex.GetLogSummary()}", LogLevel.Error);
- }
- }
+ return modsLoaded;
}
/// <summary>The method called when the user submits the help command in the console.</summary>
diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs
index 9610562f..db25dc11 100644
--- a/src/StardewModdingAPI/SemanticVersion.cs
+++ b/src/StardewModdingAPI/SemanticVersion.cs
@@ -67,7 +67,7 @@ namespace StardewModdingAPI
/// <remarks>The implementation is defined by Semantic Version 2.0 (http://semver.org/).</remarks>
public int CompareTo(ISemanticVersion other)
{
- if(other == null)
+ if (other == null)
throw new ArgumentNullException(nameof(other));
const int same = 0;
@@ -127,6 +127,14 @@ namespace StardewModdingAPI
return this.CompareTo(other) < 0;
}
+ /// <summary>Get whether this version is older than the specified version.</summary>
+ /// <param name="other">The version to compare with this instance.</param>
+ /// <exception cref="FormatException">The specified version is not a valid semantic version.</exception>
+ public bool IsOlderThan(string other)
+ {
+ return this.IsOlderThan(new SemanticVersion(other));
+ }
+
/// <summary>Get whether this version is newer than the specified version.</summary>
/// <param name="other">The version to compare with this instance.</param>
public bool IsNewerThan(ISemanticVersion other)
@@ -134,6 +142,31 @@ namespace StardewModdingAPI
return this.CompareTo(other) > 0;
}
+ /// <summary>Get whether this version is newer than the specified version.</summary>
+ /// <param name="other">The version to compare with this instance.</param>
+ /// <exception cref="FormatException">The specified version is not a valid semantic version.</exception>
+ public bool IsNewerThan(string other)
+ {
+ return this.IsNewerThan(new SemanticVersion(other));
+ }
+
+ /// <summary>Get whether this version is between two specified versions (inclusively).</summary>
+ /// <param name="min">The minimum version.</param>
+ /// <param name="max">The maximum version.</param>
+ public bool IsBetween(ISemanticVersion min, ISemanticVersion max)
+ {
+ return this.CompareTo(min) >= 0 && this.CompareTo(max) <= 0;
+ }
+
+ /// <summary>Get whether this version is between two specified versions (inclusively).</summary>
+ /// <param name="min">The minimum version.</param>
+ /// <param name="max">The maximum version.</param>
+ /// <exception cref="FormatException">One of the specified versions is not a valid semantic version.</exception>
+ public bool IsBetween(string min, string max)
+ {
+ return this.IsBetween(new SemanticVersion(min), new SemanticVersion(max));
+ }
+
/// <summary>Get a string representation of the version.</summary>
public override string ToString()
{
diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json
index 0b6f3a37..9438c621 100644
--- a/src/StardewModdingAPI/StardewModdingAPI.config.json
+++ b/src/StardewModdingAPI/StardewModdingAPI.config.json
@@ -28,6 +28,15 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha
*/
"ModCompatibility": [
{
+ "Name": "AccessChestAnywhere",
+ "ID": "AccessChestAnywhere",
+ "UpperVersion": "1.1",
+ "Compatibility": "AssumeBroken",
+ "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/257",
+ "UnofficialUpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/518",
+ "Notes": "Crashes with 'Method not found: Void StardewValley.Item.set_Name(System.String)'."
+ },
+ {
"Name": "Almighty Tool",
"ID": "AlmightyTool.dll",
"UpperVersion": "1.1.1",
@@ -49,7 +58,7 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha
"UpperVersion": "2.3",
"Compatibility": "AssumeBroken",
"UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/41",
- "Notes": "Uses obsolete StardewModdingAPI.Extensions."
+ "Notes": "ID changed in 2.3. Uses obsolete StardewModdingAPI.Extensions."
},
{
"Name": "Chest Label System",
@@ -60,6 +69,30 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha
"Notes": "Not compatible with Stardew Valley 1.1+"
},
{
+ "Name": "Chests Anywhere",
+ "ID": "ChestsAnywhere",
+ "UpperVersion": "1.8.2",
+ "Compatibility": "AssumeBroken",
+ "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/518",
+ "Notes": "Crashes with 'Method not found: Void StardewValley.Menus.TextBox.set_Highlighted(Boolean)'."
+ },
+ {
+ "Name": "Chests Anywhere",
+ "ID": "Pathoschild.ChestsAnywhere",
+ "UpperVersion": "1.9-beta",
+ "Compatibility": "AssumeBroken",
+ "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/518",
+ "Notes": "ID changed in 1.9. Crashes with InvalidOperationException: 'The menu doesn't seem to have a player inventory'."
+ },
+ {
+ "Name": "CJB Automation",
+ "ID": "CJBAutomation",
+ "UpperVersion": "1.4",
+ "Compatibility": "AssumeBroken",
+ "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/211",
+ "Notes": "Crashes with 'Method not found: Void StardewValley.Item.set_Name(System.String)'."
+ },
+ {
"Name": "CJB Cheats Menu",
"ID": "CJBCheatsMenu",
"UpperVersion": "1.12",
@@ -82,6 +115,14 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha
"Notes": "Uses SMAPI's internal SGame class."
},
{
+ "Name": "Cooking Skill",
+ "ID": "CookingSkill",
+ "UpperVersion": "1.0.3",
+ "Compatibility": "AssumeBroken",
+ "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/522",
+ "Notes": "Crashes with 'Method not found: Void StardewValley.Buff..ctor(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, System.String)'."
+ },
+ {
"Name": "Enemy Health Bars",
"ID": "SPDHealthBar",
"UpperVersion": "1.7",
@@ -98,6 +139,38 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha
"Notes": "Uses obsolete StardewModdingAPI.Inheritance.SObject until 1.6.1; then crashes until 1.6.4 ('Entoarox Framework requested an immediate game shutdown: Fatal error attempting to update player tick properties System.NullReferenceException: Object reference not set to an instance of an object. at Entoarox.Framework.PlayerHelper.Update(Object s, EventArgs e)')."
},
{
+ "Name": "Extended Fridge",
+ "ID": "Mystra007ExtendedFridge",
+ "UpperVersion": "1.0",
+ "Compatibility": "AssumeBroken",
+ "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/485",
+ "Notes": "Actual upper version is 0.94, but mod incorrectly sets it to 1.0 in the manifest. Crashes with 'Field not found: StardewValley.Game1.mouseCursorTransparency'."
+ },
+ {
+ "Name": "Get Dressed",
+ "ID": "GetDressed.dll",
+ "UpperVersion": "3.2",
+ "Compatibility": "AssumeBroken",
+ "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/331",
+ "Notes": "Crashes with NullReferenceException in GameEvents.UpdateTick."
+ },
+ {
+ "Name": "Lookup Anything",
+ "ID": "LookupAnything",
+ "UpperVersion": "1.10",
+ "Compatibility": "AssumeBroken",
+ "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/541",
+ "Notes": "Crashes with FormatException when looking up NPCs."
+ },
+ {
+ "Name": "Lookup Anything",
+ "ID": "Pathoschild.LookupAnything",
+ "UpperVersion": "1.10.1",
+ "Compatibility": "AssumeBroken",
+ "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/541",
+ "Notes": "ID changed in 1.10.1. Crashes with FormatException when looking up NPCs."
+ },
+ {
"Name": "Makeshift Multiplayer",
"ID": "StardewValleyMP",
"Compatibility": "AssumeBroken",
@@ -111,7 +184,7 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha
"UpperVersion": "0.5",
"Compatibility": "AssumeBroken",
"UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/237",
- "Notes": "Uses obsolete StardewModdingAPI.Extensions."
+ "Notes": "Uses obsolete StardewModdingAPI.Extensions and Assembly.GetExecutingAssembly().Location."
},
{
"Name": "NPC Map Locations",
@@ -155,6 +228,14 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha
"Notes": "Crashes with 'Method not found: Void StardewModdingAPI.Command.CallCommand(System.String)'."
},
{
+ "Name": "Teleporter",
+ "ID": "Teleporter",
+ "UpperVersion": "1.0.2",
+ "Compatibility": "AssumeBroken",
+ "UpdateUrl": "http://community.playstarbound.com/resources/4374",
+ "Notes": "Crashes with 'InvalidOperationException: The StardewValley.Menus.MapPage object doesn't have a private 'points' instance field'."
+ },
+ {
"Name": "Zoryn's Better RNG",
"ID": "76b6d1e1-f7ba-4d72-8c32-5a1e6d2716f6",
"UpperVersion": "1.5",
diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs
index 7187a358..168b7e8e 100644
--- a/src/TrainerMod/TrainerMod.cs
+++ b/src/TrainerMod/TrainerMod.cs
@@ -97,15 +97,16 @@ namespace TrainerMod
.Add("player_changestyle", "Sets the style of a player feature.\n\nUsage: player_changecolor <target> <value>.\n- target: what to change (one of 'hair', 'shirt', 'skin', 'acc', 'shoe', 'swim', or 'gender').\n- value: the integer style ID.", this.HandleCommand)
.Add("player_additem", $"Gives the player an item.\n\nUsage: player_additem <item> [count] [quality]\n- item: the item ID (use the 'list_items' command to see a list).\n- count (optional): how many of the item to give.\n- quality (optional): one of {Object.lowQuality} (normal), {Object.medQuality} (silver), {Object.highQuality} (gold), or {Object.bestQuality} (iridium).", this.HandleCommand)
- .Add("player_addmelee", "Gives the player a melee weapon.\n\nUsage: player_addmelee <item>\n- item: the melee weapon ID (use the 'list_items' command to see a list).", this.HandleCommand)
+ .Add("player_addweapon", "Gives the player a weapon.\n\nUsage: player_addweapon <item>\n- item: the weapon ID (use the 'list_items' command to see a list).", this.HandleCommand)
.Add("player_addring", "Gives the player a ring.\n\nUsage: player_addring <item>\n- item: the ring ID (use the 'list_items' command to see a list).", this.HandleCommand)
.Add("list_items", "Lists and searches items in the game data.\n\nUsage: list_items [search]\n- search (optional): an arbitrary search string to filter by.", this.HandleCommand)
- .Add("world_settime", "Sets the time to the specified value.\n\nUsage: world_settime <value>\n- value: the target time in military time (like 0600 for 6am and 1800 for 6pm)", this.HandleCommand)
.Add("world_freezetime", "Freezes or resumes time.\n\nUsage: world_freezetime [value]\n- value: one of 0 (resume), 1 (freeze), or blank (toggle).", this.HandleCommand)
+ .Add("world_settime", "Sets the time to the specified value.\n\nUsage: world_settime <value>\n- value: the target time in military time (like 0600 for 6am and 1800 for 6pm)", this.HandleCommand)
.Add("world_setday", "Sets the day to the specified value.\n\nUsage: world_setday <value>.\n- value: the target day (a number from 1 to 28).", this.HandleCommand)
- .Add("world_setseason", "Sets the season to the specified value.\n\nUsage: world_setseason <season>\n- value: the target season (one of 'spring', 'summer', 'fall', 'winter').", this.HandleCommand)
+ .Add("world_setseason", "Sets the season to the specified value.\n\nUsage: world_setseason <season>\n- season: the target season (one of 'spring', 'summer', 'fall', 'winter').", this.HandleCommand)
+ .Add("world_setyear", "Sets the year to the specified value.\n\nUsage: world_setyear <year>\n- year: the target year (a number starting from 1).", this.HandleCommand)
.Add("world_downminelevel", "Goes down one mine level?", this.HandleCommand)
.Add("world_setminelevel", "Sets the mine level?\n\nUsage: world_setminelevel <value>\n- value: The target level (a number between 1 and 120).", this.HandleCommand)
@@ -489,6 +490,27 @@ namespace TrainerMod
this.Monitor.Log($"The current season is {Game1.currentSeason}. Specify a value to change it.", LogLevel.Info);
break;
+ case "world_setyear":
+ if (args.Any())
+ {
+ int year;
+ if (int.TryParse(args[0], out year))
+ {
+ if (year >= 1)
+ {
+ Game1.year = year;
+ this.Monitor.Log($"OK, the year is now {Game1.year}.", LogLevel.Info);
+ }
+ else
+ this.LogUsageError("That isn't a valid year.", command);
+ }
+ else
+ this.LogArgumentNotInt(command);
+ }
+ else
+ this.Monitor.Log($"The current year is {Game1.year}. Specify a value to change the year.", LogLevel.Info);
+ break;
+
case "player_sethealth":
if (args.Any())
{
@@ -587,20 +609,62 @@ namespace TrainerMod
this.LogArgumentsInvalid(command);
break;
- case "player_addmelee":
+ case "player_addweapon":
if (args.Any())
{
int weaponID;
if (int.TryParse(args[0], out weaponID))
{
- MeleeWeapon weapon = new MeleeWeapon(weaponID);
- if (weapon.Name == null)
+ // get raw weapon data
+ string data;
+ if (!Game1.content.Load<Dictionary<int, string>>("Data\\weapons").TryGetValue(weaponID, out data))
+ {
this.Monitor.Log("There is no such weapon ID.", LogLevel.Error);
- else
+ return;
+ }
+
+ // get raw weapon type
+ int type;
+ {
+ string[] fields = data.Split('/');
+ string typeStr = fields.Length > 8 ? fields[8] : null;
+ if (!int.TryParse(typeStr, out type))
+ {
+ this.Monitor.Log("Could not parse the data for the weapon with that ID.", LogLevel.Error);
+ return;
+ }
+ }
+
+ // get weapon
+ Tool weapon;
+ switch (type)
+ {
+ case MeleeWeapon.stabbingSword:
+ case MeleeWeapon.dagger:
+ case MeleeWeapon.club:
+ case MeleeWeapon.defenseSword:
+ weapon = new MeleeWeapon(weaponID);
+ break;
+
+ case 4:
+ weapon = new Slingshot(weaponID);
+ break;
+
+ default:
+ this.Monitor.Log($"The specified weapon has unknown type '{type}' in the game data.", LogLevel.Error);
+ return;
+ }
+
+ // validate
+ if (weapon.Name == null)
{
- Game1.player.addItemByMenuIfNecessary(weapon);
- this.Monitor.Log($"OK, added {weapon.Name} to your inventory.", LogLevel.Info);
+ this.Monitor.Log("That weapon doesn't seem to be valid.", LogLevel.Error);
+ return;
}
+
+ // add weapon
+ Game1.player.addItemByMenuIfNecessary(weapon);
+ this.Monitor.Log($"OK, added {weapon.Name} to your inventory.", LogLevel.Info);
}
else
this.LogUsageError("The weapon ID must be an integer.", command);