From 99d0450b2cb291d565cb836de9f132ca657472c1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 4 Feb 2017 16:50:09 -0500 Subject: fix install error when the mods folder doesn't exist (#229) --- src/StardewModdingAPI.Installer/InteractiveInstaller.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 5abcfc8f..551c648c 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -80,8 +80,11 @@ namespace StardewModdingApi.Installer // obsolete yield return installPath("Mods/.cache"); // 1.3-1.4 yield return installPath("StardewModdingAPI-settings.json"); // 1.0-1.4 - foreach (DirectoryInfo modDir in modsDir.EnumerateDirectories()) - yield return Path.Combine(modDir.FullName, ".cache"); // 1.4–1.7 + if (modsDir.Exists) + { + foreach (DirectoryInfo modDir in modsDir.EnumerateDirectories()) + yield return Path.Combine(modDir.FullName, ".cache"); // 1.4–1.7 + } } /// Whether the current console supports color formatting. -- cgit From 95a93a05b39d2b27b538ecdb0e6a18f28096c5c2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 7 Feb 2017 20:50:41 -0500 Subject: remove oldest deprecated code (#231) Since Stardew Valley 1.2 breaks most mods anyway, this commits removes the oldest deprecations and fixes the issues that are easiest for mods to update. See documentation for details. --- release-notes.md | 10 + src/StardewModdingAPI/Command.cs | 9 - src/StardewModdingAPI/Constants.cs | 6 +- src/StardewModdingAPI/Entities/SPlayer.cs | 59 - src/StardewModdingAPI/Events/ChangeType.cs | 15 + .../Events/EventArgsInventoryChanged.cs | 1 - src/StardewModdingAPI/Events/GraphicsEvents.cs | 2 +- src/StardewModdingAPI/Events/ItemStackChange.cs | 20 + src/StardewModdingAPI/Events/PlayerEvents.cs | 1 - src/StardewModdingAPI/Extensions.cs | 194 ---- src/StardewModdingAPI/Framework/SGame.cs | 1133 +++++++++++++++++++ .../Serialisation/SemanticVersionConverter.cs | 51 + src/StardewModdingAPI/Inheritance/ChangeType.cs | 15 - .../Inheritance/ItemStackChange.cs | 20 - src/StardewModdingAPI/Inheritance/SGame.cs | 1134 -------------------- src/StardewModdingAPI/Inheritance/SObject.cs | 249 ----- src/StardewModdingAPI/LogWriter.cs | 66 -- src/StardewModdingAPI/Manifest.cs | 39 +- src/StardewModdingAPI/Mod.cs | 18 +- src/StardewModdingAPI/Program.cs | 46 +- src/StardewModdingAPI/SemanticVersion.cs | 19 +- src/StardewModdingAPI/StardewModdingAPI.csproj | 12 +- src/StardewModdingAPI/Version.cs | 121 --- src/TrainerMod/TrainerMod.cs | 12 - 24 files changed, 1263 insertions(+), 1989 deletions(-) delete mode 100644 src/StardewModdingAPI/Entities/SPlayer.cs create mode 100644 src/StardewModdingAPI/Events/ChangeType.cs create mode 100644 src/StardewModdingAPI/Events/ItemStackChange.cs delete mode 100644 src/StardewModdingAPI/Extensions.cs create mode 100644 src/StardewModdingAPI/Framework/SGame.cs create mode 100644 src/StardewModdingAPI/Framework/Serialisation/SemanticVersionConverter.cs delete mode 100644 src/StardewModdingAPI/Inheritance/ChangeType.cs delete mode 100644 src/StardewModdingAPI/Inheritance/ItemStackChange.cs delete mode 100644 src/StardewModdingAPI/Inheritance/SGame.cs delete mode 100644 src/StardewModdingAPI/Inheritance/SObject.cs delete mode 100644 src/StardewModdingAPI/LogWriter.cs delete mode 100644 src/StardewModdingAPI/Version.cs (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 1ff868a4..aff7631e 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,15 @@ # Release notes +## 1.9 +See [log](https://github.com/Pathoschild/SMAPI/compare/1.8...1.9). + +For players: +* Updated for Stardew Valley 1.2. + +For mod developers: +* Many deprecated APIs have been removed; see [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod) + for more information. + ## 1.8 See [log](https://github.com/Pathoschild/SMAPI/compare/1.7...1.8). diff --git a/src/StardewModdingAPI/Command.cs b/src/StardewModdingAPI/Command.cs index 1fa18d49..6195bd8b 100644 --- a/src/StardewModdingAPI/Command.cs +++ b/src/StardewModdingAPI/Command.cs @@ -67,15 +67,6 @@ namespace StardewModdingAPI /**** ** SMAPI ****/ - /// Parse a command string and invoke it if valid. - /// The command to run, including the command name and any arguments. - [Obsolete("Use the overload which passes in your mod's monitor")] - public static void CallCommand(string input) - { - Program.DeprecationManager.Warn($"an old version of {nameof(Command)}.{nameof(Command.CallCommand)}", "1.1", DeprecationLevel.Notice); - Command.CallCommand(input, Program.GetLegacyMonitorForMod()); - } - /// Parse a command string and invoke it if valid. /// The command to run, including the command name and any arguments. /// Encapsulates monitoring and logging. diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index a62a0d58..d3c2ddcc 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -26,11 +26,7 @@ namespace StardewModdingAPI ** Accessors *********/ /// SMAPI's current semantic version. - [Obsolete("Use " + nameof(Constants) + "." + nameof(ApiVersion))] - public static readonly Version Version = (Version)Constants.ApiVersion; - - /// SMAPI's current semantic version. - public static ISemanticVersion ApiVersion => new Version(1, 8, 0, null, suppressDeprecationWarning: true); + public static ISemanticVersion ApiVersion => new SemanticVersion(1, 8, 0, null); /// The minimum supported version of Stardew Valley. public const string MinimumGameVersion = "1.1"; diff --git a/src/StardewModdingAPI/Entities/SPlayer.cs b/src/StardewModdingAPI/Entities/SPlayer.cs deleted file mode 100644 index 66c7ba44..00000000 --- a/src/StardewModdingAPI/Entities/SPlayer.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using StardewModdingAPI.Framework; -using StardewValley; - -namespace StardewModdingAPI.Entities -{ - /// Static class for integrating with the player. - [Obsolete("This API was never officially documented and will be removed soon.")] - public class SPlayer - { - /********* - ** Accessors - *********/ - /// Obsolete. - [Obsolete("Use " + nameof(Game1) + "." + nameof(Game1.getAllFarmers) + " instead")] - public static List AllFarmers - { - get - { - Program.DeprecationManager.Warn(nameof(SPlayer), "1.0", DeprecationLevel.Info); - return Game1.getAllFarmers(); - } - } - - /// Obsolete. - [Obsolete("Use " + nameof(Game1) + "." + nameof(Game1.player) + " instead")] - public static Farmer CurrentFarmer - { - get - { - Program.DeprecationManager.Warn(nameof(SPlayer), "1.0", DeprecationLevel.Info); - return Game1.player; - } - } - - /// Obsolete. - [Obsolete("Use " + nameof(Game1) + "." + nameof(Game1.player) + " instead")] - public static Farmer Player - { - get - { - Program.DeprecationManager.Warn(nameof(SPlayer), "1.0", DeprecationLevel.Info); - return Game1.player; - } - } - - /// Obsolete. - [Obsolete("Use " + nameof(Game1) + "." + nameof(Game1.player) + "." + nameof(Farmer.currentLocation) + " instead")] - public static GameLocation CurrentFarmerLocation - { - get - { - Program.DeprecationManager.Warn(nameof(SPlayer), "1.0", DeprecationLevel.Info); - return Game1.player.currentLocation; - } - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/Events/ChangeType.cs b/src/StardewModdingAPI/Events/ChangeType.cs new file mode 100644 index 00000000..4b207f08 --- /dev/null +++ b/src/StardewModdingAPI/Events/ChangeType.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Events +{ + /// Indicates how an inventory item changed. + public enum ChangeType + { + /// The entire stack was removed. + Removed, + + /// The entire stack was added. + Added, + + /// The stack size changed. + StackChange + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Events/EventArgsInventoryChanged.cs b/src/StardewModdingAPI/Events/EventArgsInventoryChanged.cs index 40c77419..11cbcedf 100644 --- a/src/StardewModdingAPI/Events/EventArgsInventoryChanged.cs +++ b/src/StardewModdingAPI/Events/EventArgsInventoryChanged.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using StardewModdingAPI.Inheritance; using StardewValley; namespace StardewModdingAPI.Events diff --git a/src/StardewModdingAPI/Events/GraphicsEvents.cs b/src/StardewModdingAPI/Events/GraphicsEvents.cs index 5f4feeac..c3236c0a 100644 --- a/src/StardewModdingAPI/Events/GraphicsEvents.cs +++ b/src/StardewModdingAPI/Events/GraphicsEvents.cs @@ -15,7 +15,7 @@ namespace StardewModdingAPI.Events /// Raised after the game window is resized. public static event EventHandler Resize; - /// Raised when drawing debug information to the screen (when is true). This is called after the sprite batch is begun. If you just want to add debug info, use in your update loop. + /// Raised when drawing debug information to the screen (when is true). This is called after the sprite batch is begun. If you just want to add debug info, use in your update loop. public static event EventHandler DrawDebug; /// Obsolete. diff --git a/src/StardewModdingAPI/Events/ItemStackChange.cs b/src/StardewModdingAPI/Events/ItemStackChange.cs new file mode 100644 index 00000000..f9ae6df6 --- /dev/null +++ b/src/StardewModdingAPI/Events/ItemStackChange.cs @@ -0,0 +1,20 @@ +using StardewValley; + +namespace StardewModdingAPI.Events +{ + /// Represents an inventory slot that changed. + public class ItemStackChange + { + /********* + ** Accessors + *********/ + /// The item in the slot. + public Item Item { get; set; } + + /// The amount by which the item's stack size changed. + public int StackChange { get; set; } + + /// How the inventory slot changed. + public ChangeType ChangeType { get; set; } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Events/PlayerEvents.cs b/src/StardewModdingAPI/Events/PlayerEvents.cs index dd3ff220..6e65f5ae 100644 --- a/src/StardewModdingAPI/Events/PlayerEvents.cs +++ b/src/StardewModdingAPI/Events/PlayerEvents.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Framework; -using StardewModdingAPI.Inheritance; using StardewValley; namespace StardewModdingAPI.Events diff --git a/src/StardewModdingAPI/Extensions.cs b/src/StardewModdingAPI/Extensions.cs deleted file mode 100644 index 0e9dbbf7..00000000 --- a/src/StardewModdingAPI/Extensions.cs +++ /dev/null @@ -1,194 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; -using StardewModdingAPI.Framework; - -namespace StardewModdingAPI -{ - /// Provides general utility extensions. - public static class Extensions - { - /********* - ** Properties - *********/ - /// The backing field for . - private static readonly Random _random = new Random(); - - - /********* - ** Accessors - *********/ - /// A pseudo-random number generator. - public static Random Random - { - get - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.Random)}", "1.0", DeprecationLevel.PendingRemoval); - return Extensions._random; - } - } - - - /********* - ** Public methods - *********/ - /// Get whether the given key is currently being pressed. - /// The key to check. - public static bool IsKeyDown(this Keys key) - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsKeyDown)}", "1.0", DeprecationLevel.PendingRemoval); - - return Keyboard.GetState().IsKeyDown(key); - } - - /// Get a random color. - public static Color RandomColour() - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.RandomColour)}", "1.0", DeprecationLevel.PendingRemoval); - - return new Color(Extensions.Random.Next(0, 255), Extensions.Random.Next(0, 255), Extensions.Random.Next(0, 255)); - } - - /// Concatenate an enumeration into a delimiter-separated string. - /// The values to concatenate. - /// The value separator. - [Obsolete("The usage of ToSingular has changed. Please update your call to use ToSingular")] - public static string ToSingular(this IEnumerable ienum, string split = ", ") - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.ToSingular)}", "0.39.3", DeprecationLevel.PendingRemoval); - return ""; - } - - /// Concatenate an enumeration into a delimiter-separated string. - /// The enumerated value type. - /// The values to concatenate. - /// The value separator. - public static string ToSingular(this IEnumerable ienum, string split = ", ") - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.ToSingular)}", "1.0", DeprecationLevel.PendingRemoval); - - //Apparently Keys[] won't split normally :l - if (typeof(T) == typeof(Keys)) - { - return string.Join(split, ienum.ToArray()); - } - return string.Join(split, ienum); - } - - /// Get whether the value can be parsed as a number. - /// The value. - public static bool IsInt32(this object o) - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsInt32)}", "1.0", DeprecationLevel.PendingRemoval); - - int i; - return int.TryParse(o.ToString(), out i); - } - - /// Get the numeric representation of a value. - /// The value. - public static int AsInt32(this object o) - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.AsInt32)}", "1.0", DeprecationLevel.PendingRemoval); - - return int.Parse(o.ToString()); - } - - /// Get whether the value can be parsed as a boolean. - /// The value. - public static bool IsBool(this object o) - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsBool)}", "1.0", DeprecationLevel.PendingRemoval); - - bool b; - return bool.TryParse(o.ToString(), out b); - } - - /// Get the boolean representation of a value. - /// The value. - public static bool AsBool(this object o) - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.AsBool)}", "1.0", DeprecationLevel.PendingRemoval); - - return bool.Parse(o.ToString()); - } - - /// Get a list hash calculated from the hashes of the values it contains. - /// The values to hash. - public static int GetHash(this IEnumerable enumerable) - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetHash)}", "1.0", DeprecationLevel.PendingRemoval); - - var hash = 0; - foreach (var v in enumerable) - hash ^= v.GetHashCode(); - return hash; - } - - /// Cast a value to the given type. This returns null if the value can't be cast. - /// The type to which to cast. - /// The value. - public static T Cast(this object o) where T : class - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.Cast)}", "1.0", DeprecationLevel.PendingRemoval); - - return o as T; - } - - /// Get all private types on an object. - /// The object to scan. - public static FieldInfo[] GetPrivateFields(this object o) - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetPrivateFields)}", "1.0", DeprecationLevel.PendingRemoval); - return o.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); - } - - /// Get metadata for a private field. - /// The type to scan. - /// The name of the field to find. - public static FieldInfo GetBaseFieldInfo(this Type t, string name) - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetBaseFieldValue)}", "1.0", DeprecationLevel.PendingRemoval); - return t.GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); - } - - /// Get the value of a private field. - /// The type to scan. - /// The instance for which to get a value. - /// The name of the field to find. - public static T GetBaseFieldValue(this Type t, object o, string name) where T : class - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetBaseFieldValue)}", "1.0", DeprecationLevel.PendingRemoval); - return t.GetBaseFieldInfo(name).GetValue(o) as T; - } - - /// Set the value of a private field. - /// The type to scan. - /// The instance for which to set a value. - /// The name of the field to find. - /// The value to set. - public static void SetBaseFieldValue(this Type t, object o, string name, object newValue) where T : class - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.SetBaseFieldValue)}", "1.0", DeprecationLevel.PendingRemoval); - t.GetBaseFieldInfo(name).SetValue(o, newValue as T); - } - - /// Get a copy of the string with only alphanumeric characters. (Numbers are not removed, despite the name.) - /// The string to copy. - public static string RemoveNumerics(this string st) - { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.RemoveNumerics)}", "1.0", DeprecationLevel.PendingRemoval); - var s = st; - foreach (var c in s) - { - if (!char.IsLetterOrDigit(c)) - s = s.Replace(c.ToString(), ""); - } - return s; - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs new file mode 100644 index 00000000..2486e376 --- /dev/null +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -0,0 +1,1133 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using StardewModdingAPI.Events; +using StardewValley; +using StardewValley.BellsAndWhistles; +using StardewValley.Locations; +using StardewValley.Menus; +using StardewValley.Tools; +using xTile.Dimensions; +using Rectangle = Microsoft.Xna.Framework.Rectangle; + +namespace StardewModdingAPI.Framework +{ + /// SMAPI's extension of the game's core , used to inject events. + internal class SGame : Game1 + { + /********* + ** Properties + *********/ + /// The number of ticks until SMAPI should notify mods when is set. + /// Skipping a few frames ensures the game finishes initialising the world before mods try to change it. + private int AfterLoadTimer = 5; + + /// Whether the player has loaded a save and the world has finished initialising. + private bool IsWorldReady => this.AfterLoadTimer < 0; + + /// The debug messages to add to the next debug output. + internal static Queue DebugMessageQueue { get; private set; } + + /// Whether the game's zoom level is at 100% (i.e. nothing should be scaled). + public bool ZoomLevelIsOne => Game1.options.zoomLevel.Equals(1.0f); + + /// Encapsulates monitoring and logging. + private readonly IMonitor Monitor; + + + /********* + ** Accessors + *********/ + /// Arrays of pressed controller buttons indexed by . + public Buttons[][] PreviouslyPressedButtons; + + /// A record of the keyboard state (i.e. the up/down state for each button) as of the latest tick. + public KeyboardState KStateNow { get; private set; } + + /// A record of the keyboard state (i.e. the up/down state for each button) as of the previous tick. + public KeyboardState KStatePrior { get; private set; } + + /// A record of the mouse state (i.e. the cursor position, scroll amount, and the up/down state for each button) as of the latest tick. + public MouseState MStateNow { get; private set; } + + /// A record of the mouse state (i.e. the cursor position, scroll amount, and the up/down state for each button) as of the previous tick. + public MouseState MStatePrior { get; private set; } + + /// The current mouse position on the screen adjusted for the zoom level. + public Point MPositionNow { get; private set; } + + /// The previous mouse position on the screen adjusted for the zoom level. + public Point MPositionPrior { get; private set; } + + /// The keys that were pressed as of the latest tick. + public Keys[] CurrentlyPressedKeys => this.KStateNow.GetPressedKeys(); + + /// The keys that were pressed as of the previous tick. + public Keys[] PreviouslyPressedKeys => this.KStatePrior.GetPressedKeys(); + + /// The keys that just entered the down state. + public Keys[] FramePressedKeys => this.CurrentlyPressedKeys.Except(this.PreviouslyPressedKeys).ToArray(); + + /// The keys that just entered the up state. + public Keys[] FrameReleasedKeys => this.PreviouslyPressedKeys.Except(this.CurrentlyPressedKeys).ToArray(); + + /// Whether a save is currently loaded at last check. + public bool PreviouslyLoadedGame { get; private set; } + + /// A hash of at last check. + public int PreviousGameLocations { get; private set; } + + /// A hash of the current location's at last check. + public int PreviousLocationObjects { get; private set; } + + /// The player's inventory at last check. + public Dictionary PreviousItems { get; private set; } + + /// The player's combat skill level at last check. + public int PreviousCombatLevel { get; private set; } + + /// The player's farming skill level at last check. + public int PreviousFarmingLevel { get; private set; } + + /// The player's fishing skill level at last check. + public int PreviousFishingLevel { get; private set; } + + /// The player's foraging skill level at last check. + public int PreviousForagingLevel { get; private set; } + + /// The player's mining skill level at last check. + public int PreviousMiningLevel { get; private set; } + + /// The player's luck skill level at last check. + public int PreviousLuckLevel { get; private set; } + + /// The player's location at last check. + public GameLocation PreviousGameLocation { get; private set; } + + /// The active game menu at last check. + public IClickableMenu PreviousActiveMenu { get; private set; } + + /// The mine level at last check. + public int PreviousMineLevel { get; private set; } + + /// The time of day (in 24-hour military format) at last check. + public int PreviousTimeOfDay { get; private set; } + + /// The day of month (1–28) at last check. + public int PreviousDayOfMonth { get; private set; } + + /// The season name (winter, spring, summer, or fall) at last check. + public string PreviousSeasonOfYear { get; private set; } + + /// The year number at last check. + public int PreviousYearOfGame { get; private set; } + + /// Whether the game was transitioning to a new day at last check. + public bool PreviousIsNewDay { get; private set; } + + /// The player character at last check. + public Farmer PreviousFarmer { get; private set; } + + /// An index incremented on every tick and reset every 60th tick (0–59). + public int CurrentUpdateTick { get; private set; } + + /// Whether this is the very first update tick since the game started. + public bool FirstUpdate { get; private set; } + + /// The game's current render target. + public RenderTarget2D Screen + { + get { return this.GetBaseFieldValue("screen"); } + set { this.SetBaseFieldValue("screen", value); } + } + + /// The game's current background color. + public Color BgColour + { + get { return (Color)this.GetBaseFieldValue("bgColor"); } + set { this.SetBaseFieldValue("bgColor", value); } + } + + /// The current game instance. + public static SGame Instance { get; private set; } + + /// The game's current frame rate, recalculated on each draw update. + public static float FramesPerSecond { get; private set; } + + /// Whether we're in pseudo-debug mode, which shows information like FPS. + public static bool Debug { get; private set; } + + /// The current player. + [Obsolete("Use Game1.player instead")] + public Farmer CurrentFarmer => Game1.player; + + /// The game method which draws the farm buildings. + public static MethodInfo DrawFarmBuildings = typeof(Game1).GetMethod("drawFarmBuildings", BindingFlags.NonPublic | BindingFlags.Instance); + + /// The game method which draws the game HUD. + public static MethodInfo DrawHUD = typeof(Game1).GetMethod("drawHUD", BindingFlags.NonPublic | BindingFlags.Instance); + + /// The game method which draws the current dialogue box, if any. + public static MethodInfo DrawDialogueBox = typeof(Game1).GetMethod("drawDialogueBox", BindingFlags.NonPublic | BindingFlags.Instance); + + + /********* + ** Public methods + *********/ + /// Get the controller buttons which are currently pressed. + /// The controller to check. + public Buttons[] GetButtonsDown(PlayerIndex index) + { + var state = GamePad.GetState(index); + var buttons = new List(); + if (state.IsConnected) + { + if (state.Buttons.A == ButtonState.Pressed) buttons.Add(Buttons.A); + if (state.Buttons.B == ButtonState.Pressed) buttons.Add(Buttons.B); + if (state.Buttons.Back == ButtonState.Pressed) buttons.Add(Buttons.Back); + if (state.Buttons.BigButton == ButtonState.Pressed) buttons.Add(Buttons.BigButton); + if (state.Buttons.LeftShoulder == ButtonState.Pressed) buttons.Add(Buttons.LeftShoulder); + if (state.Buttons.LeftStick == ButtonState.Pressed) buttons.Add(Buttons.LeftStick); + if (state.Buttons.RightShoulder == ButtonState.Pressed) buttons.Add(Buttons.RightShoulder); + if (state.Buttons.RightStick == ButtonState.Pressed) buttons.Add(Buttons.RightStick); + if (state.Buttons.Start == ButtonState.Pressed) buttons.Add(Buttons.Start); + if (state.Buttons.X == ButtonState.Pressed) buttons.Add(Buttons.X); + if (state.Buttons.Y == ButtonState.Pressed) buttons.Add(Buttons.Y); + if (state.DPad.Up == ButtonState.Pressed) buttons.Add(Buttons.DPadUp); + if (state.DPad.Down == ButtonState.Pressed) buttons.Add(Buttons.DPadDown); + if (state.DPad.Left == ButtonState.Pressed) buttons.Add(Buttons.DPadLeft); + if (state.DPad.Right == ButtonState.Pressed) buttons.Add(Buttons.DPadRight); + if (state.Triggers.Left > 0.2f) buttons.Add(Buttons.LeftTrigger); + if (state.Triggers.Right > 0.2f) buttons.Add(Buttons.RightTrigger); + } + return buttons.ToArray(); + } + + /// Get the controller buttons which were pressed after the last update. + /// The controller to check. + public Buttons[] GetFramePressedButtons(PlayerIndex index) + { + var state = GamePad.GetState(index); + var buttons = new List(); + if (state.IsConnected) + { + if (this.WasButtonJustPressed(Buttons.A, state.Buttons.A, index)) buttons.Add(Buttons.A); + if (this.WasButtonJustPressed(Buttons.B, state.Buttons.B, index)) buttons.Add(Buttons.B); + if (this.WasButtonJustPressed(Buttons.Back, state.Buttons.Back, index)) buttons.Add(Buttons.Back); + if (this.WasButtonJustPressed(Buttons.BigButton, state.Buttons.BigButton, index)) buttons.Add(Buttons.BigButton); + if (this.WasButtonJustPressed(Buttons.LeftShoulder, state.Buttons.LeftShoulder, index)) buttons.Add(Buttons.LeftShoulder); + if (this.WasButtonJustPressed(Buttons.LeftStick, state.Buttons.LeftStick, index)) buttons.Add(Buttons.LeftStick); + if (this.WasButtonJustPressed(Buttons.RightShoulder, state.Buttons.RightShoulder, index)) buttons.Add(Buttons.RightShoulder); + if (this.WasButtonJustPressed(Buttons.RightStick, state.Buttons.RightStick, index)) buttons.Add(Buttons.RightStick); + if (this.WasButtonJustPressed(Buttons.Start, state.Buttons.Start, index)) buttons.Add(Buttons.Start); + if (this.WasButtonJustPressed(Buttons.X, state.Buttons.X, index)) buttons.Add(Buttons.X); + if (this.WasButtonJustPressed(Buttons.Y, state.Buttons.Y, index)) buttons.Add(Buttons.Y); + if (this.WasButtonJustPressed(Buttons.DPadUp, state.DPad.Up, index)) buttons.Add(Buttons.DPadUp); + if (this.WasButtonJustPressed(Buttons.DPadDown, state.DPad.Down, index)) buttons.Add(Buttons.DPadDown); + if (this.WasButtonJustPressed(Buttons.DPadLeft, state.DPad.Left, index)) buttons.Add(Buttons.DPadLeft); + if (this.WasButtonJustPressed(Buttons.DPadRight, state.DPad.Right, index)) buttons.Add(Buttons.DPadRight); + if (this.WasButtonJustPressed(Buttons.LeftTrigger, state.Triggers.Left, index)) buttons.Add(Buttons.LeftTrigger); + if (this.WasButtonJustPressed(Buttons.RightTrigger, state.Triggers.Right, index)) buttons.Add(Buttons.RightTrigger); + } + return buttons.ToArray(); + } + + /// Get the controller buttons which were released after the last update. + /// The controller to check. + public Buttons[] GetFrameReleasedButtons(PlayerIndex index) + { + var state = GamePad.GetState(index); + var buttons = new List(); + if (state.IsConnected) + { + if (this.WasButtonJustReleased(Buttons.A, state.Buttons.A, index)) buttons.Add(Buttons.A); + if (this.WasButtonJustReleased(Buttons.B, state.Buttons.B, index)) buttons.Add(Buttons.B); + if (this.WasButtonJustReleased(Buttons.Back, state.Buttons.Back, index)) buttons.Add(Buttons.Back); + if (this.WasButtonJustReleased(Buttons.BigButton, state.Buttons.BigButton, index)) buttons.Add(Buttons.BigButton); + if (this.WasButtonJustReleased(Buttons.LeftShoulder, state.Buttons.LeftShoulder, index)) buttons.Add(Buttons.LeftShoulder); + if (this.WasButtonJustReleased(Buttons.LeftStick, state.Buttons.LeftStick, index)) buttons.Add(Buttons.LeftStick); + if (this.WasButtonJustReleased(Buttons.RightShoulder, state.Buttons.RightShoulder, index)) buttons.Add(Buttons.RightShoulder); + if (this.WasButtonJustReleased(Buttons.RightStick, state.Buttons.RightStick, index)) buttons.Add(Buttons.RightStick); + if (this.WasButtonJustReleased(Buttons.Start, state.Buttons.Start, index)) buttons.Add(Buttons.Start); + if (this.WasButtonJustReleased(Buttons.X, state.Buttons.X, index)) buttons.Add(Buttons.X); + if (this.WasButtonJustReleased(Buttons.Y, state.Buttons.Y, index)) buttons.Add(Buttons.Y); + if (this.WasButtonJustReleased(Buttons.DPadUp, state.DPad.Up, index)) buttons.Add(Buttons.DPadUp); + if (this.WasButtonJustReleased(Buttons.DPadDown, state.DPad.Down, index)) buttons.Add(Buttons.DPadDown); + if (this.WasButtonJustReleased(Buttons.DPadLeft, state.DPad.Left, index)) buttons.Add(Buttons.DPadLeft); + if (this.WasButtonJustReleased(Buttons.DPadRight, state.DPad.Right, index)) buttons.Add(Buttons.DPadRight); + if (this.WasButtonJustReleased(Buttons.LeftTrigger, state.Triggers.Left, index)) buttons.Add(Buttons.LeftTrigger); + if (this.WasButtonJustReleased(Buttons.RightTrigger, state.Triggers.Right, index)) buttons.Add(Buttons.RightTrigger); + } + return buttons.ToArray(); + } + + /// Queue a message to be added to the debug output. + /// The message to add. + /// Returns whether the message was successfully queued. + public static bool QueueDebugMessage(string message) + { + if (!SGame.Debug) + return false; + if (SGame.DebugMessageQueue.Count > 32) + return false; + + SGame.DebugMessageQueue.Enqueue(message); + return true; + } + + + /********* + ** Protected methods + *********/ + /// Construct an instance. + /// Encapsulates monitoring and logging. + internal SGame(IMonitor monitor) + { + this.Monitor = monitor; + this.FirstUpdate = true; + SGame.Instance = this; + } + + /// The method called during game launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. + protected override void Initialize() + { + //ModItems = new Dictionary(); + SGame.DebugMessageQueue = new Queue(); + this.PreviouslyPressedButtons = new Buttons[4][]; + for (var i = 0; i < 4; ++i) + this.PreviouslyPressedButtons[i] = new Buttons[0]; + + base.Initialize(); + GameEvents.InvokeInitialize(this.Monitor); + } + + /// The method called before XNA or MonoGame loads or reloads graphics resources. + protected override void LoadContent() + { + base.LoadContent(); + GameEvents.InvokeLoadContent(this.Monitor); + } + + /// The method called when the game is updating its state. This happens roughly 60 times per second. + /// A snapshot of the game timing state. + protected override void Update(GameTime gameTime) + { + // add FPS to debug output + SGame.QueueDebugMessage($"FPS: {SGame.FramesPerSecond}"); + + // raise game loaded + if (this.FirstUpdate) + GameEvents.InvokeGameLoaded(this.Monitor); + + // update SMAPI events + this.UpdateEventCalls(); + + // toggle debug output + if (this.FramePressedKeys.Contains(Keys.F3)) + SGame.Debug = !SGame.Debug; + + // let game update + try + { + base.Update(gameTime); + } + catch (Exception ex) + { + this.Monitor.Log($"An error occured in the base update loop: {ex.GetLogSummary()}", LogLevel.Error); + Console.ReadKey(); + } + + // raise update events + GameEvents.InvokeUpdateTick(this.Monitor); + if (this.FirstUpdate) + { + GameEvents.InvokeFirstUpdateTick(this.Monitor); + this.FirstUpdate = false; + } + if (this.CurrentUpdateTick % 2 == 0) + GameEvents.InvokeSecondUpdateTick(this.Monitor); + if (this.CurrentUpdateTick % 4 == 0) + GameEvents.InvokeFourthUpdateTick(this.Monitor); + if (this.CurrentUpdateTick % 8 == 0) + GameEvents.InvokeEighthUpdateTick(this.Monitor); + if (this.CurrentUpdateTick % 15 == 0) + GameEvents.InvokeQuarterSecondTick(this.Monitor); + if (this.CurrentUpdateTick % 30 == 0) + GameEvents.InvokeHalfSecondTick(this.Monitor); + if (this.CurrentUpdateTick % 60 == 0) + GameEvents.InvokeOneSecondTick(this.Monitor); + this.CurrentUpdateTick += 1; + if (this.CurrentUpdateTick >= 60) + this.CurrentUpdateTick = 0; + + // track keyboard state + if (this.KStatePrior != this.KStateNow) + this.KStatePrior = this.KStateNow; + + // track controller button state + for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) + this.PreviouslyPressedButtons[(int)i] = this.GetButtonsDown(i); + } + + /// The method called to draw everything to the screen. + /// A snapshot of the game timing state. + /// This implementation is identical to , except for try..catch around menu draw code, minor formatting, and added events. + protected override void Draw(GameTime gameTime) + { + // track frame rate + SGame.FramesPerSecond = 1 / (float)gameTime.ElapsedGameTime.TotalSeconds; + + try + { + if (!this.ZoomLevelIsOne) + this.GraphicsDevice.SetRenderTarget(this.Screen); + + this.GraphicsDevice.Clear(this.BgColour); + if (Game1.options.showMenuBackground && Game1.activeClickableMenu != null && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet()) + { + 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) + { + this.GraphicsDevice.SetRenderTarget(null); + this.GraphicsDevice.Clear(this.BgColour); + Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); + Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); + Game1.spriteBatch.End(); + } + return; + } + if (Game1.gameMode == 11) + { + 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 (Game1.currentMinigame != 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.BgColour); + Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); + Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.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.BgColour); + Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); + Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.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.BgColour); + Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); + Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); + Game1.spriteBatch.End(); + } + return; + } + if (Game1.gameMode == 0) + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); + else + { + if (Game1.drawLighting) + { + 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.Screen); + } + if (Game1.bloomDay) + Game1.bloom?.BeginDraw(); + this.GraphicsDevice.Clear(this.BgColour); + 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.Enumerator enumerator = Game1.currentLocation.characters.GetEnumerator()) + { + while (enumerator.MoveNext()) + { + 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); + } + goto IL_B30; + } + } + foreach (NPC current2 in Game1.CurrentEvent.actors) + { + 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.Enumerator enumerator3 = Game1.currentLocation.characters.GetEnumerator()) + { + while (enumerator3.MoveNext()) + { + 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); + } + goto IL_F5F; + } + } + 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")) + SGame.DrawFarmBuildings.Invoke(Program.gamePtr, null); + 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) + { + 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 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) + { + if (current7.isEmoting) + { + 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.End(); + if (Game1.drawLighting) + { + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, new BlendState + { + 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.Draw(Game1.staminaRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.OrangeRed * 0.45f); + } + 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(); + + GraphicsEvents.InvokeOnPreRenderHudEventNoCheck(this.Monitor); + if ((Game1.displayHUD || Game1.eventUp) && Game1.currentBillboard == 0 && Game1.gameMode == 3 && !Game1.freezeControls && !Game1.panMode) + { + GraphicsEvents.InvokeOnPreRenderHudEvent(this.Monitor); + SGame.DrawHUD.Invoke(Program.gamePtr, null); + 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); + GraphicsEvents.InvokeOnPostRenderHudEventNoCheck(this.Monitor); + + 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)) + SGame.DrawDialogueBox.Invoke(Program.gamePtr, null); + 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) + SGame.DrawDialogueBox.Invoke(Program.gamePtr, null); + 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); + + GraphicsEvents.InvokeOnPreRenderGuiEventNoCheck(this.Monitor); + 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(); + } + GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); + } + else + Game1.farmEvent?.drawAboveEverything(Game1.spriteBatch); + GraphicsEvents.InvokeOnPostRenderGuiEventNoCheck(this.Monitor); + + GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor); + Game1.spriteBatch.End(); + + GraphicsEvents.InvokeDrawInRenderTargetTick(this.Monitor); + + if (!this.ZoomLevelIsOne) + { + this.GraphicsDevice.SetRenderTarget(null); + this.GraphicsDevice.Clear(this.BgColour); + Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); + Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); + Game1.spriteBatch.End(); + } + + GraphicsEvents.InvokeDrawTick(this.Monitor); + } + catch (Exception ex) + { + this.Monitor.Log($"An error occured in the overridden draw loop: {ex.GetLogSummary()}", LogLevel.Error); + } + + if (SGame.Debug) + { + Game1.spriteBatch.Begin(); + + int i = 0; + while (SGame.DebugMessageQueue.Any()) + { + string message = SGame.DebugMessageQueue.Dequeue(); + Game1.spriteBatch.DrawString(Game1.smoothFont, message, new Vector2(0, i * 14), Color.CornflowerBlue); + i++; + } + GraphicsEvents.InvokeDrawDebug(this.Monitor); + + Game1.spriteBatch.End(); + } + else + SGame.DebugMessageQueue.Clear(); + } + + /// Get whether a controller button was pressed since the last check. + /// The controller button to check. + /// The last known state. + /// The player whose controller to check. + private bool WasButtonJustPressed(Buttons button, ButtonState buttonState, PlayerIndex stateIndex) + { + return buttonState == ButtonState.Pressed && !this.PreviouslyPressedButtons[(int)stateIndex].Contains(button); + } + + /// Get whether a controller button was released since the last check. + /// The controller button to check. + /// The last known state. + /// The player whose controller to check. + private bool WasButtonJustReleased(Buttons button, ButtonState buttonState, PlayerIndex stateIndex) + { + return buttonState == ButtonState.Released && this.PreviouslyPressedButtons[(int)stateIndex].Contains(button); + } + + /// Get whether an analogue controller button was pressed since the last check. + /// The controller button to check. + /// The last known value. + /// The player whose controller to check. + private bool WasButtonJustPressed(Buttons button, float value, PlayerIndex stateIndex) + { + return this.WasButtonJustPressed(button, value > 0.2f ? ButtonState.Pressed : ButtonState.Released, stateIndex); + } + + /// Get whether an analogue controller button was released since the last check. + /// The controller button to check. + /// The last known value. + /// The player whose controller to check. + private bool WasButtonJustReleased(Buttons button, float value, PlayerIndex stateIndex) + { + return this.WasButtonJustReleased(button, value > 0.2f ? ButtonState.Pressed : ButtonState.Released, stateIndex); + } + + /// Detect changes since the last update ticket and trigger mod events. + private void UpdateEventCalls() + { + // save loaded event + if (Game1.hasLoadedGame && this.AfterLoadTimer >= 0) + { + if (this.AfterLoadTimer == 0) + { + SaveEvents.InvokeAfterLoad(this.Monitor); + PlayerEvents.InvokeLoadedGame(this.Monitor, new EventArgsLoadedGameChanged(Game1.hasLoadedGame)); + } + this.AfterLoadTimer--; + } + + // input events + { + // get latest state + this.KStateNow = Keyboard.GetState(); + this.MStateNow = Mouse.GetState(); + this.MPositionNow = new Point(Game1.getMouseX(), Game1.getMouseY()); + + // raise key pressed + foreach (var key in this.FramePressedKeys) + ControlEvents.InvokeKeyPressed(this.Monitor, key); + + // raise key released + foreach (var key in this.FrameReleasedKeys) + ControlEvents.InvokeKeyReleased(this.Monitor, key); + + // raise controller button pressed + for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) + { + var buttons = this.GetFramePressedButtons(i); + foreach (var button in buttons) + { + if (button == Buttons.LeftTrigger || button == Buttons.RightTrigger) + ControlEvents.InvokeTriggerPressed(this.Monitor, i, button, button == Buttons.LeftTrigger ? GamePad.GetState(i).Triggers.Left : GamePad.GetState(i).Triggers.Right); + else + ControlEvents.InvokeButtonPressed(this.Monitor, i, button); + } + } + + // raise controller button released + for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) + { + foreach (var button in this.GetFrameReleasedButtons(i)) + { + if (button == Buttons.LeftTrigger || button == Buttons.RightTrigger) + ControlEvents.InvokeTriggerReleased(this.Monitor, i, button, button == Buttons.LeftTrigger ? GamePad.GetState(i).Triggers.Left : GamePad.GetState(i).Triggers.Right); + else + ControlEvents.InvokeButtonReleased(this.Monitor, i, button); + } + } + + // raise keyboard state changed + if (this.KStateNow != this.KStatePrior) + ControlEvents.InvokeKeyboardChanged(this.Monitor, this.KStatePrior, this.KStateNow); + + // raise mouse state changed + if (this.MStateNow != this.MStatePrior) + { + ControlEvents.InvokeMouseChanged(this.Monitor, this.MStatePrior, this.MStateNow, this.MPositionPrior, this.MPositionNow); + this.MStatePrior = this.MStateNow; + this.MPositionPrior = this.MPositionPrior; + } + } + + // menu events + if (Game1.activeClickableMenu != this.PreviousActiveMenu) + { + IClickableMenu previousMenu = this.PreviousActiveMenu; + IClickableMenu newMenu = Game1.activeClickableMenu; + + // raise save events + // (saving is performed by SaveGameMenu; on days when the player shipping something, ShippingMenu wraps SaveGameMenu) + if (newMenu is SaveGameMenu || newMenu is ShippingMenu) + SaveEvents.InvokeBeforeSave(this.Monitor); + else if (previousMenu is SaveGameMenu || previousMenu is ShippingMenu) + SaveEvents.InvokeAfterSave(this.Monitor); + + // raise menu events + if (newMenu != null) + MenuEvents.InvokeMenuChanged(this.Monitor, previousMenu, newMenu); + else + MenuEvents.InvokeMenuClosed(this.Monitor, previousMenu); + + // update previous menu + // (if the menu was changed in one of the handlers, deliberately defer detection until the next update so mods can be notified of the new menu change) + this.PreviousActiveMenu = newMenu; + } + + // world & player events + if (this.IsWorldReady) + { + // raise location list changed + if (this.GetHash(Game1.locations) != this.PreviousGameLocations) + { + LocationEvents.InvokeLocationsChanged(this.Monitor, Game1.locations); + this.PreviousGameLocations = this.GetHash(Game1.locations); + } + + // raise current location changed + if (Game1.currentLocation != this.PreviousGameLocation) + { + LocationEvents.InvokeCurrentLocationChanged(this.Monitor, this.PreviousGameLocation, Game1.currentLocation); + this.PreviousGameLocation = Game1.currentLocation; + } + + // raise player changed + if (Game1.player != this.PreviousFarmer) + { + PlayerEvents.InvokeFarmerChanged(this.Monitor, this.PreviousFarmer, Game1.player); + this.PreviousFarmer = Game1.player; + } + + // raise player leveled up a skill + if (Game1.player.combatLevel != this.PreviousCombatLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Combat, Game1.player.combatLevel); + this.PreviousCombatLevel = Game1.player.combatLevel; + } + if (Game1.player.farmingLevel != this.PreviousFarmingLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Farming, Game1.player.farmingLevel); + this.PreviousFarmingLevel = Game1.player.farmingLevel; + } + if (Game1.player.fishingLevel != this.PreviousFishingLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Fishing, Game1.player.fishingLevel); + this.PreviousFishingLevel = Game1.player.fishingLevel; + } + if (Game1.player.foragingLevel != this.PreviousForagingLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Foraging, Game1.player.foragingLevel); + this.PreviousForagingLevel = Game1.player.foragingLevel; + } + if (Game1.player.miningLevel != this.PreviousMiningLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Mining, Game1.player.miningLevel); + this.PreviousMiningLevel = Game1.player.miningLevel; + } + if (Game1.player.luckLevel != this.PreviousLuckLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Luck, Game1.player.luckLevel); + this.PreviousLuckLevel = Game1.player.luckLevel; + } + + // raise player inventory changed + ItemStackChange[] changedItems = this.GetInventoryChanges(Game1.player.items, this.PreviousItems).ToArray(); + if (changedItems.Any()) + { + PlayerEvents.InvokeInventoryChanged(this.Monitor, Game1.player.items, changedItems); + this.PreviousItems = Game1.player.items.Where(n => n != null).ToDictionary(n => n, n => n.Stack); + } + + // raise current location's object list changed + { + int? objectHash = Game1.currentLocation?.objects != null ? this.GetHash(Game1.currentLocation.objects) : (int?)null; + if (objectHash != null && this.PreviousLocationObjects != objectHash) + { + LocationEvents.InvokeOnNewLocationObject(this.Monitor, Game1.currentLocation.objects); + this.PreviousLocationObjects = objectHash.Value; + } + } + + // raise time changed + if (Game1.timeOfDay != this.PreviousTimeOfDay) + { + TimeEvents.InvokeTimeOfDayChanged(this.Monitor, this.PreviousTimeOfDay, Game1.timeOfDay); + this.PreviousTimeOfDay = Game1.timeOfDay; + } + if (Game1.dayOfMonth != this.PreviousDayOfMonth) + { + TimeEvents.InvokeDayOfMonthChanged(this.Monitor, this.PreviousDayOfMonth, Game1.dayOfMonth); + this.PreviousDayOfMonth = Game1.dayOfMonth; + } + if (Game1.currentSeason != this.PreviousSeasonOfYear) + { + TimeEvents.InvokeSeasonOfYearChanged(this.Monitor, this.PreviousSeasonOfYear, Game1.currentSeason); + this.PreviousSeasonOfYear = Game1.currentSeason; + } + if (Game1.year != this.PreviousYearOfGame) + { + TimeEvents.InvokeYearOfGameChanged(this.Monitor, this.PreviousYearOfGame, Game1.year); + this.PreviousYearOfGame = Game1.year; + } + + // raise mine level changed + if (Game1.mine != null && Game1.mine.mineLevel != this.PreviousMineLevel) + { + MineEvents.InvokeMineLevelChanged(this.Monitor, this.PreviousMineLevel, Game1.mine.mineLevel); + this.PreviousMineLevel = Game1.mine.mineLevel; + } + } + + // raise game day transition event (obsolete) + if (Game1.newDay != this.PreviousIsNewDay) + { + TimeEvents.InvokeOnNewDay(this.Monitor, this.PreviousDayOfMonth, Game1.dayOfMonth, Game1.newDay); + this.PreviousIsNewDay = Game1.newDay; + } + } + + /// Get the player inventory changes between two states. + /// The player's current inventory. + /// The player's previous inventory. + private IEnumerable GetInventoryChanges(IEnumerable current, IDictionary previous) + { + current = current.Where(n => n != null).ToArray(); + foreach (Item item in current) + { + // stack size changed + if (previous != null && previous.ContainsKey(item)) + { + if (previous[item] != item.Stack) + yield return new ItemStackChange { Item = item, StackChange = item.Stack - previous[item], ChangeType = ChangeType.StackChange }; + } + + // new item + else + yield return new ItemStackChange { Item = item, StackChange = item.Stack, ChangeType = ChangeType.Added }; + } + + // removed items + if (previous != null) + { + foreach (var entry in previous) + { + if (current.Any(i => i == entry.Key)) + continue; + + yield return new ItemStackChange { Item = entry.Key, StackChange = -entry.Key.Stack, ChangeType = ChangeType.Removed }; + } + } + } + + /// Get a hash value for an enumeration. + /// The enumeration of items to hash. + private int GetHash(IEnumerable enumerable) + { + var hash = 0; + foreach (var v in enumerable) + hash ^= v.GetHashCode(); + return hash; + } + + /// Get reflection metadata for a private field. + /// The field name. + private FieldInfo GetBaseFieldInfo(string name) + { + return typeof(Game1).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); + } + + /// Get the value of a private field. + /// The expected value type. + /// The field name. + private TValue GetBaseFieldValue(string name) where TValue : class + { + return this.GetBaseFieldInfo(name).GetValue(Program.gamePtr) as TValue; + } + + /// Set the value of a private field. + /// The expected value type. + /// The field name. + /// The value to set. + public void SetBaseFieldValue(string name, object value) where TValue : class + { + this.GetBaseFieldInfo(name).SetValue(Program.gamePtr, value as TValue); + } + } +} diff --git a/src/StardewModdingAPI/Framework/Serialisation/SemanticVersionConverter.cs b/src/StardewModdingAPI/Framework/Serialisation/SemanticVersionConverter.cs new file mode 100644 index 00000000..52ec999e --- /dev/null +++ b/src/StardewModdingAPI/Framework/Serialisation/SemanticVersionConverter.cs @@ -0,0 +1,51 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace StardewModdingAPI.Framework.Serialisation +{ + /// Overrides how SMAPI reads and writes . + internal class SemanticVersionConverter : JsonConverter + { + /********* + ** Accessors + *********/ + /// Whether this converter can write JSON. + public override bool CanWrite => false; + + + /********* + ** Public methods + *********/ + /// Get whether this instance can convert the specified object type. + /// The object type. + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ISemanticVersion); + } + + /// Reads the JSON representation of the object. + /// The JSON reader. + /// The object type. + /// The object being read. + /// The calling serializer. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JObject obj = JObject.Load(reader); + int major = obj.Value("MajorVersion"); + int minor = obj.Value("MinorVersion"); + int patch = obj.Value("PatchVersion"); + string build = obj.Value("Build"); + return new SemanticVersion(major, minor, patch, build); + } + + /// Writes the JSON representation of the object. + /// The JSON writer. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new InvalidOperationException("This converter does not write JSON."); + } + } +} diff --git a/src/StardewModdingAPI/Inheritance/ChangeType.cs b/src/StardewModdingAPI/Inheritance/ChangeType.cs deleted file mode 100644 index 94eb33ed..00000000 --- a/src/StardewModdingAPI/Inheritance/ChangeType.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace StardewModdingAPI.Inheritance -{ - /// Indicates how an inventory item changed. - public enum ChangeType - { - /// The entire stack was removed. - Removed, - - /// The entire stack was added. - Added, - - /// The stack size changed. - StackChange - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/Inheritance/ItemStackChange.cs b/src/StardewModdingAPI/Inheritance/ItemStackChange.cs deleted file mode 100644 index 8d15b894..00000000 --- a/src/StardewModdingAPI/Inheritance/ItemStackChange.cs +++ /dev/null @@ -1,20 +0,0 @@ -using StardewValley; - -namespace StardewModdingAPI.Inheritance -{ - /// Represents an inventory slot that changed. - public class ItemStackChange - { - /********* - ** Accessors - *********/ - /// The item in the slot. - public Item Item { get; set; } - - /// The amount by which the item's stack size changed. - public int StackChange { get; set; } - - /// How the inventory slot changed. - public ChangeType ChangeType { get; set; } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/Inheritance/SGame.cs b/src/StardewModdingAPI/Inheritance/SGame.cs deleted file mode 100644 index 69c20244..00000000 --- a/src/StardewModdingAPI/Inheritance/SGame.cs +++ /dev/null @@ -1,1134 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Input; -using StardewModdingAPI.Events; -using StardewModdingAPI.Framework; -using StardewValley; -using StardewValley.BellsAndWhistles; -using StardewValley.Locations; -using StardewValley.Menus; -using StardewValley.Tools; -using xTile.Dimensions; -using Rectangle = Microsoft.Xna.Framework.Rectangle; - -namespace StardewModdingAPI.Inheritance -{ - /// SMAPI's extension of the game's core , used to inject events. - public class SGame : Game1 - { - /********* - ** Properties - *********/ - /// The number of ticks until SMAPI should notify mods when is set. - /// Skipping a few frames ensures the game finishes initialising the world before mods try to change it. - private int AfterLoadTimer = 5; - - /// Whether the player has loaded a save and the world has finished initialising. - private bool IsWorldReady => this.AfterLoadTimer < 0; - - /// The debug messages to add to the next debug output. - internal static Queue DebugMessageQueue { get; private set; } - - /// Whether the game's zoom level is at 100% (i.e. nothing should be scaled). - public bool ZoomLevelIsOne => Game1.options.zoomLevel.Equals(1.0f); - - /// Encapsulates monitoring and logging. - private readonly IMonitor Monitor; - - - /********* - ** Accessors - *********/ - /// Arrays of pressed controller buttons indexed by . - public Buttons[][] PreviouslyPressedButtons; - - /// A record of the keyboard state (i.e. the up/down state for each button) as of the latest tick. - public KeyboardState KStateNow { get; private set; } - - /// A record of the keyboard state (i.e. the up/down state for each button) as of the previous tick. - public KeyboardState KStatePrior { get; private set; } - - /// A record of the mouse state (i.e. the cursor position, scroll amount, and the up/down state for each button) as of the latest tick. - public MouseState MStateNow { get; private set; } - - /// A record of the mouse state (i.e. the cursor position, scroll amount, and the up/down state for each button) as of the previous tick. - public MouseState MStatePrior { get; private set; } - - /// The current mouse position on the screen adjusted for the zoom level. - public Point MPositionNow { get; private set; } - - /// The previous mouse position on the screen adjusted for the zoom level. - public Point MPositionPrior { get; private set; } - - /// The keys that were pressed as of the latest tick. - public Keys[] CurrentlyPressedKeys => this.KStateNow.GetPressedKeys(); - - /// The keys that were pressed as of the previous tick. - public Keys[] PreviouslyPressedKeys => this.KStatePrior.GetPressedKeys(); - - /// The keys that just entered the down state. - public Keys[] FramePressedKeys => this.CurrentlyPressedKeys.Except(this.PreviouslyPressedKeys).ToArray(); - - /// The keys that just entered the up state. - public Keys[] FrameReleasedKeys => this.PreviouslyPressedKeys.Except(this.CurrentlyPressedKeys).ToArray(); - - /// Whether a save is currently loaded at last check. - public bool PreviouslyLoadedGame { get; private set; } - - /// A hash of at last check. - public int PreviousGameLocations { get; private set; } - - /// A hash of the current location's at last check. - public int PreviousLocationObjects { get; private set; } - - /// The player's inventory at last check. - public Dictionary PreviousItems { get; private set; } - - /// The player's combat skill level at last check. - public int PreviousCombatLevel { get; private set; } - - /// The player's farming skill level at last check. - public int PreviousFarmingLevel { get; private set; } - - /// The player's fishing skill level at last check. - public int PreviousFishingLevel { get; private set; } - - /// The player's foraging skill level at last check. - public int PreviousForagingLevel { get; private set; } - - /// The player's mining skill level at last check. - public int PreviousMiningLevel { get; private set; } - - /// The player's luck skill level at last check. - public int PreviousLuckLevel { get; private set; } - - /// The player's location at last check. - public GameLocation PreviousGameLocation { get; private set; } - - /// The active game menu at last check. - public IClickableMenu PreviousActiveMenu { get; private set; } - - /// The mine level at last check. - public int PreviousMineLevel { get; private set; } - - /// The time of day (in 24-hour military format) at last check. - public int PreviousTimeOfDay { get; private set; } - - /// The day of month (1–28) at last check. - public int PreviousDayOfMonth { get; private set; } - - /// The season name (winter, spring, summer, or fall) at last check. - public string PreviousSeasonOfYear { get; private set; } - - /// The year number at last check. - public int PreviousYearOfGame { get; private set; } - - /// Whether the game was transitioning to a new day at last check. - public bool PreviousIsNewDay { get; private set; } - - /// The player character at last check. - public Farmer PreviousFarmer { get; private set; } - - /// An index incremented on every tick and reset every 60th tick (0–59). - public int CurrentUpdateTick { get; private set; } - - /// Whether this is the very first update tick since the game started. - public bool FirstUpdate { get; private set; } - - /// The game's current render target. - public RenderTarget2D Screen - { - get { return this.GetBaseFieldValue("screen"); } - set { this.SetBaseFieldValue("screen", value); } - } - - /// The game's current background color. - public Color BgColour - { - get { return (Color)this.GetBaseFieldValue("bgColor"); } - set { this.SetBaseFieldValue("bgColor", value); } - } - - /// The current game instance. - public static SGame Instance { get; private set; } - - /// The game's current frame rate, recalculated on each draw update. - public static float FramesPerSecond { get; private set; } - - /// Whether we're in pseudo-debug mode, which shows information like FPS. - public static bool Debug { get; private set; } - - /// The current player. - [Obsolete("Use Game1.player instead")] - public Farmer CurrentFarmer => Game1.player; - - /// The game method which draws the farm buildings. - public static MethodInfo DrawFarmBuildings = typeof(Game1).GetMethod("drawFarmBuildings", BindingFlags.NonPublic | BindingFlags.Instance); - - /// The game method which draws the game HUD. - public static MethodInfo DrawHUD = typeof(Game1).GetMethod("drawHUD", BindingFlags.NonPublic | BindingFlags.Instance); - - /// The game method which draws the current dialogue box, if any. - public static MethodInfo DrawDialogueBox = typeof(Game1).GetMethod("drawDialogueBox", BindingFlags.NonPublic | BindingFlags.Instance); - - - /********* - ** Public methods - *********/ - /// Get the controller buttons which are currently pressed. - /// The controller to check. - public Buttons[] GetButtonsDown(PlayerIndex index) - { - var state = GamePad.GetState(index); - var buttons = new List(); - if (state.IsConnected) - { - if (state.Buttons.A == ButtonState.Pressed) buttons.Add(Buttons.A); - if (state.Buttons.B == ButtonState.Pressed) buttons.Add(Buttons.B); - if (state.Buttons.Back == ButtonState.Pressed) buttons.Add(Buttons.Back); - if (state.Buttons.BigButton == ButtonState.Pressed) buttons.Add(Buttons.BigButton); - if (state.Buttons.LeftShoulder == ButtonState.Pressed) buttons.Add(Buttons.LeftShoulder); - if (state.Buttons.LeftStick == ButtonState.Pressed) buttons.Add(Buttons.LeftStick); - if (state.Buttons.RightShoulder == ButtonState.Pressed) buttons.Add(Buttons.RightShoulder); - if (state.Buttons.RightStick == ButtonState.Pressed) buttons.Add(Buttons.RightStick); - if (state.Buttons.Start == ButtonState.Pressed) buttons.Add(Buttons.Start); - if (state.Buttons.X == ButtonState.Pressed) buttons.Add(Buttons.X); - if (state.Buttons.Y == ButtonState.Pressed) buttons.Add(Buttons.Y); - if (state.DPad.Up == ButtonState.Pressed) buttons.Add(Buttons.DPadUp); - if (state.DPad.Down == ButtonState.Pressed) buttons.Add(Buttons.DPadDown); - if (state.DPad.Left == ButtonState.Pressed) buttons.Add(Buttons.DPadLeft); - if (state.DPad.Right == ButtonState.Pressed) buttons.Add(Buttons.DPadRight); - if (state.Triggers.Left > 0.2f) buttons.Add(Buttons.LeftTrigger); - if (state.Triggers.Right > 0.2f) buttons.Add(Buttons.RightTrigger); - } - return buttons.ToArray(); - } - - /// Get the controller buttons which were pressed after the last update. - /// The controller to check. - public Buttons[] GetFramePressedButtons(PlayerIndex index) - { - var state = GamePad.GetState(index); - var buttons = new List(); - if (state.IsConnected) - { - if (this.WasButtonJustPressed(Buttons.A, state.Buttons.A, index)) buttons.Add(Buttons.A); - if (this.WasButtonJustPressed(Buttons.B, state.Buttons.B, index)) buttons.Add(Buttons.B); - if (this.WasButtonJustPressed(Buttons.Back, state.Buttons.Back, index)) buttons.Add(Buttons.Back); - if (this.WasButtonJustPressed(Buttons.BigButton, state.Buttons.BigButton, index)) buttons.Add(Buttons.BigButton); - if (this.WasButtonJustPressed(Buttons.LeftShoulder, state.Buttons.LeftShoulder, index)) buttons.Add(Buttons.LeftShoulder); - if (this.WasButtonJustPressed(Buttons.LeftStick, state.Buttons.LeftStick, index)) buttons.Add(Buttons.LeftStick); - if (this.WasButtonJustPressed(Buttons.RightShoulder, state.Buttons.RightShoulder, index)) buttons.Add(Buttons.RightShoulder); - if (this.WasButtonJustPressed(Buttons.RightStick, state.Buttons.RightStick, index)) buttons.Add(Buttons.RightStick); - if (this.WasButtonJustPressed(Buttons.Start, state.Buttons.Start, index)) buttons.Add(Buttons.Start); - if (this.WasButtonJustPressed(Buttons.X, state.Buttons.X, index)) buttons.Add(Buttons.X); - if (this.WasButtonJustPressed(Buttons.Y, state.Buttons.Y, index)) buttons.Add(Buttons.Y); - if (this.WasButtonJustPressed(Buttons.DPadUp, state.DPad.Up, index)) buttons.Add(Buttons.DPadUp); - if (this.WasButtonJustPressed(Buttons.DPadDown, state.DPad.Down, index)) buttons.Add(Buttons.DPadDown); - if (this.WasButtonJustPressed(Buttons.DPadLeft, state.DPad.Left, index)) buttons.Add(Buttons.DPadLeft); - if (this.WasButtonJustPressed(Buttons.DPadRight, state.DPad.Right, index)) buttons.Add(Buttons.DPadRight); - if (this.WasButtonJustPressed(Buttons.LeftTrigger, state.Triggers.Left, index)) buttons.Add(Buttons.LeftTrigger); - if (this.WasButtonJustPressed(Buttons.RightTrigger, state.Triggers.Right, index)) buttons.Add(Buttons.RightTrigger); - } - return buttons.ToArray(); - } - - /// Get the controller buttons which were released after the last update. - /// The controller to check. - public Buttons[] GetFrameReleasedButtons(PlayerIndex index) - { - var state = GamePad.GetState(index); - var buttons = new List(); - if (state.IsConnected) - { - if (this.WasButtonJustReleased(Buttons.A, state.Buttons.A, index)) buttons.Add(Buttons.A); - if (this.WasButtonJustReleased(Buttons.B, state.Buttons.B, index)) buttons.Add(Buttons.B); - if (this.WasButtonJustReleased(Buttons.Back, state.Buttons.Back, index)) buttons.Add(Buttons.Back); - if (this.WasButtonJustReleased(Buttons.BigButton, state.Buttons.BigButton, index)) buttons.Add(Buttons.BigButton); - if (this.WasButtonJustReleased(Buttons.LeftShoulder, state.Buttons.LeftShoulder, index)) buttons.Add(Buttons.LeftShoulder); - if (this.WasButtonJustReleased(Buttons.LeftStick, state.Buttons.LeftStick, index)) buttons.Add(Buttons.LeftStick); - if (this.WasButtonJustReleased(Buttons.RightShoulder, state.Buttons.RightShoulder, index)) buttons.Add(Buttons.RightShoulder); - if (this.WasButtonJustReleased(Buttons.RightStick, state.Buttons.RightStick, index)) buttons.Add(Buttons.RightStick); - if (this.WasButtonJustReleased(Buttons.Start, state.Buttons.Start, index)) buttons.Add(Buttons.Start); - if (this.WasButtonJustReleased(Buttons.X, state.Buttons.X, index)) buttons.Add(Buttons.X); - if (this.WasButtonJustReleased(Buttons.Y, state.Buttons.Y, index)) buttons.Add(Buttons.Y); - if (this.WasButtonJustReleased(Buttons.DPadUp, state.DPad.Up, index)) buttons.Add(Buttons.DPadUp); - if (this.WasButtonJustReleased(Buttons.DPadDown, state.DPad.Down, index)) buttons.Add(Buttons.DPadDown); - if (this.WasButtonJustReleased(Buttons.DPadLeft, state.DPad.Left, index)) buttons.Add(Buttons.DPadLeft); - if (this.WasButtonJustReleased(Buttons.DPadRight, state.DPad.Right, index)) buttons.Add(Buttons.DPadRight); - if (this.WasButtonJustReleased(Buttons.LeftTrigger, state.Triggers.Left, index)) buttons.Add(Buttons.LeftTrigger); - if (this.WasButtonJustReleased(Buttons.RightTrigger, state.Triggers.Right, index)) buttons.Add(Buttons.RightTrigger); - } - return buttons.ToArray(); - } - - /// Queue a message to be added to the debug output. - /// The message to add. - /// Returns whether the message was successfully queued. - public static bool QueueDebugMessage(string message) - { - if (!SGame.Debug) - return false; - if (SGame.DebugMessageQueue.Count > 32) - return false; - - SGame.DebugMessageQueue.Enqueue(message); - return true; - } - - - /********* - ** Protected methods - *********/ - /// Construct an instance. - /// Encapsulates monitoring and logging. - internal SGame(IMonitor monitor) - { - this.Monitor = monitor; - this.FirstUpdate = true; - SGame.Instance = this; - } - - /// The method called during game launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. - protected override void Initialize() - { - //ModItems = new Dictionary(); - SGame.DebugMessageQueue = new Queue(); - this.PreviouslyPressedButtons = new Buttons[4][]; - for (var i = 0; i < 4; ++i) - this.PreviouslyPressedButtons[i] = new Buttons[0]; - - base.Initialize(); - GameEvents.InvokeInitialize(this.Monitor); - } - - /// The method called before XNA or MonoGame loads or reloads graphics resources. - protected override void LoadContent() - { - base.LoadContent(); - GameEvents.InvokeLoadContent(this.Monitor); - } - - /// The method called when the game is updating its state. This happens roughly 60 times per second. - /// A snapshot of the game timing state. - protected override void Update(GameTime gameTime) - { - // add FPS to debug output - SGame.QueueDebugMessage($"FPS: {SGame.FramesPerSecond}"); - - // raise game loaded - if (this.FirstUpdate) - GameEvents.InvokeGameLoaded(this.Monitor); - - // update SMAPI events - this.UpdateEventCalls(); - - // toggle debug output - if (this.FramePressedKeys.Contains(Keys.F3)) - SGame.Debug = !SGame.Debug; - - // let game update - try - { - base.Update(gameTime); - } - catch (Exception ex) - { - this.Monitor.Log($"An error occured in the base update loop: {ex.GetLogSummary()}", LogLevel.Error); - Console.ReadKey(); - } - - // raise update events - GameEvents.InvokeUpdateTick(this.Monitor); - if (this.FirstUpdate) - { - GameEvents.InvokeFirstUpdateTick(this.Monitor); - this.FirstUpdate = false; - } - if (this.CurrentUpdateTick % 2 == 0) - GameEvents.InvokeSecondUpdateTick(this.Monitor); - if (this.CurrentUpdateTick % 4 == 0) - GameEvents.InvokeFourthUpdateTick(this.Monitor); - if (this.CurrentUpdateTick % 8 == 0) - GameEvents.InvokeEighthUpdateTick(this.Monitor); - if (this.CurrentUpdateTick % 15 == 0) - GameEvents.InvokeQuarterSecondTick(this.Monitor); - if (this.CurrentUpdateTick % 30 == 0) - GameEvents.InvokeHalfSecondTick(this.Monitor); - if (this.CurrentUpdateTick % 60 == 0) - GameEvents.InvokeOneSecondTick(this.Monitor); - this.CurrentUpdateTick += 1; - if (this.CurrentUpdateTick >= 60) - this.CurrentUpdateTick = 0; - - // track keyboard state - if (this.KStatePrior != this.KStateNow) - this.KStatePrior = this.KStateNow; - - // track controller button state - for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) - this.PreviouslyPressedButtons[(int)i] = this.GetButtonsDown(i); - } - - /// The method called to draw everything to the screen. - /// A snapshot of the game timing state. - /// This implementation is identical to , except for try..catch around menu draw code, minor formatting, and added events. - protected override void Draw(GameTime gameTime) - { - // track frame rate - SGame.FramesPerSecond = 1 / (float)gameTime.ElapsedGameTime.TotalSeconds; - - try - { - if (!this.ZoomLevelIsOne) - this.GraphicsDevice.SetRenderTarget(this.Screen); - - this.GraphicsDevice.Clear(this.BgColour); - if (Game1.options.showMenuBackground && Game1.activeClickableMenu != null && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet()) - { - 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) - { - this.GraphicsDevice.SetRenderTarget(null); - this.GraphicsDevice.Clear(this.BgColour); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); - Game1.spriteBatch.End(); - } - return; - } - if (Game1.gameMode == 11) - { - 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 (Game1.currentMinigame != 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.BgColour); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.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.BgColour); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.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.BgColour); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); - Game1.spriteBatch.End(); - } - return; - } - if (Game1.gameMode == 0) - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); - else - { - if (Game1.drawLighting) - { - 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.Screen); - } - if (Game1.bloomDay) - Game1.bloom?.BeginDraw(); - this.GraphicsDevice.Clear(this.BgColour); - 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.Enumerator enumerator = Game1.currentLocation.characters.GetEnumerator()) - { - while (enumerator.MoveNext()) - { - 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); - } - goto IL_B30; - } - } - foreach (NPC current2 in Game1.CurrentEvent.actors) - { - 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.Enumerator enumerator3 = Game1.currentLocation.characters.GetEnumerator()) - { - while (enumerator3.MoveNext()) - { - 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); - } - goto IL_F5F; - } - } - 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")) - SGame.DrawFarmBuildings.Invoke(Program.gamePtr, null); - 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) - { - 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 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) - { - if (current7.isEmoting) - { - 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.End(); - if (Game1.drawLighting) - { - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, new BlendState - { - 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.Draw(Game1.staminaRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.OrangeRed * 0.45f); - } - 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(); - - GraphicsEvents.InvokeOnPreRenderHudEventNoCheck(this.Monitor); - if ((Game1.displayHUD || Game1.eventUp) && Game1.currentBillboard == 0 && Game1.gameMode == 3 && !Game1.freezeControls && !Game1.panMode) - { - GraphicsEvents.InvokeOnPreRenderHudEvent(this.Monitor); - SGame.DrawHUD.Invoke(Program.gamePtr, null); - 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); - GraphicsEvents.InvokeOnPostRenderHudEventNoCheck(this.Monitor); - - 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)) - SGame.DrawDialogueBox.Invoke(Program.gamePtr, null); - 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) - SGame.DrawDialogueBox.Invoke(Program.gamePtr, null); - 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); - - GraphicsEvents.InvokeOnPreRenderGuiEventNoCheck(this.Monitor); - 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(); - } - GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); - } - else - Game1.farmEvent?.drawAboveEverything(Game1.spriteBatch); - GraphicsEvents.InvokeOnPostRenderGuiEventNoCheck(this.Monitor); - - GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor); - Game1.spriteBatch.End(); - - GraphicsEvents.InvokeDrawInRenderTargetTick(this.Monitor); - - if (!this.ZoomLevelIsOne) - { - this.GraphicsDevice.SetRenderTarget(null); - this.GraphicsDevice.Clear(this.BgColour); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); - Game1.spriteBatch.End(); - } - - GraphicsEvents.InvokeDrawTick(this.Monitor); - } - catch (Exception ex) - { - this.Monitor.Log($"An error occured in the overridden draw loop: {ex.GetLogSummary()}", LogLevel.Error); - } - - if (SGame.Debug) - { - Game1.spriteBatch.Begin(); - - int i = 0; - while (SGame.DebugMessageQueue.Any()) - { - string message = SGame.DebugMessageQueue.Dequeue(); - Game1.spriteBatch.DrawString(Game1.smoothFont, message, new Vector2(0, i * 14), Color.CornflowerBlue); - i++; - } - GraphicsEvents.InvokeDrawDebug(this.Monitor); - - Game1.spriteBatch.End(); - } - else - SGame.DebugMessageQueue.Clear(); - } - - /// Get whether a controller button was pressed since the last check. - /// The controller button to check. - /// The last known state. - /// The player whose controller to check. - private bool WasButtonJustPressed(Buttons button, ButtonState buttonState, PlayerIndex stateIndex) - { - return buttonState == ButtonState.Pressed && !this.PreviouslyPressedButtons[(int)stateIndex].Contains(button); - } - - /// Get whether a controller button was released since the last check. - /// The controller button to check. - /// The last known state. - /// The player whose controller to check. - private bool WasButtonJustReleased(Buttons button, ButtonState buttonState, PlayerIndex stateIndex) - { - return buttonState == ButtonState.Released && this.PreviouslyPressedButtons[(int)stateIndex].Contains(button); - } - - /// Get whether an analogue controller button was pressed since the last check. - /// The controller button to check. - /// The last known value. - /// The player whose controller to check. - private bool WasButtonJustPressed(Buttons button, float value, PlayerIndex stateIndex) - { - return this.WasButtonJustPressed(button, value > 0.2f ? ButtonState.Pressed : ButtonState.Released, stateIndex); - } - - /// Get whether an analogue controller button was released since the last check. - /// The controller button to check. - /// The last known value. - /// The player whose controller to check. - private bool WasButtonJustReleased(Buttons button, float value, PlayerIndex stateIndex) - { - return this.WasButtonJustReleased(button, value > 0.2f ? ButtonState.Pressed : ButtonState.Released, stateIndex); - } - - /// Detect changes since the last update ticket and trigger mod events. - private void UpdateEventCalls() - { - // save loaded event - if (Game1.hasLoadedGame && this.AfterLoadTimer >= 0) - { - if (this.AfterLoadTimer == 0) - { - SaveEvents.InvokeAfterLoad(this.Monitor); - PlayerEvents.InvokeLoadedGame(this.Monitor, new EventArgsLoadedGameChanged(Game1.hasLoadedGame)); - } - this.AfterLoadTimer--; - } - - // input events - { - // get latest state - this.KStateNow = Keyboard.GetState(); - this.MStateNow = Mouse.GetState(); - this.MPositionNow = new Point(Game1.getMouseX(), Game1.getMouseY()); - - // raise key pressed - foreach (var key in this.FramePressedKeys) - ControlEvents.InvokeKeyPressed(this.Monitor, key); - - // raise key released - foreach (var key in this.FrameReleasedKeys) - ControlEvents.InvokeKeyReleased(this.Monitor, key); - - // raise controller button pressed - for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) - { - var buttons = this.GetFramePressedButtons(i); - foreach (var button in buttons) - { - if (button == Buttons.LeftTrigger || button == Buttons.RightTrigger) - ControlEvents.InvokeTriggerPressed(this.Monitor, i, button, button == Buttons.LeftTrigger ? GamePad.GetState(i).Triggers.Left : GamePad.GetState(i).Triggers.Right); - else - ControlEvents.InvokeButtonPressed(this.Monitor, i, button); - } - } - - // raise controller button released - for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) - { - foreach (var button in this.GetFrameReleasedButtons(i)) - { - if (button == Buttons.LeftTrigger || button == Buttons.RightTrigger) - ControlEvents.InvokeTriggerReleased(this.Monitor, i, button, button == Buttons.LeftTrigger ? GamePad.GetState(i).Triggers.Left : GamePad.GetState(i).Triggers.Right); - else - ControlEvents.InvokeButtonReleased(this.Monitor, i, button); - } - } - - // raise keyboard state changed - if (this.KStateNow != this.KStatePrior) - ControlEvents.InvokeKeyboardChanged(this.Monitor, this.KStatePrior, this.KStateNow); - - // raise mouse state changed - if (this.MStateNow != this.MStatePrior) - { - ControlEvents.InvokeMouseChanged(this.Monitor, this.MStatePrior, this.MStateNow, this.MPositionPrior, this.MPositionNow); - this.MStatePrior = this.MStateNow; - this.MPositionPrior = this.MPositionPrior; - } - } - - // menu events - if (Game1.activeClickableMenu != this.PreviousActiveMenu) - { - IClickableMenu previousMenu = this.PreviousActiveMenu; - IClickableMenu newMenu = Game1.activeClickableMenu; - - // raise save events - // (saving is performed by SaveGameMenu; on days when the player shipping something, ShippingMenu wraps SaveGameMenu) - if (newMenu is SaveGameMenu || newMenu is ShippingMenu) - SaveEvents.InvokeBeforeSave(this.Monitor); - else if (previousMenu is SaveGameMenu || previousMenu is ShippingMenu) - SaveEvents.InvokeAfterSave(this.Monitor); - - // raise menu events - if (newMenu != null) - MenuEvents.InvokeMenuChanged(this.Monitor, previousMenu, newMenu); - else - MenuEvents.InvokeMenuClosed(this.Monitor, previousMenu); - - // update previous menu - // (if the menu was changed in one of the handlers, deliberately defer detection until the next update so mods can be notified of the new menu change) - this.PreviousActiveMenu = newMenu; - } - - // world & player events - if (this.IsWorldReady) - { - // raise location list changed - if (this.GetHash(Game1.locations) != this.PreviousGameLocations) - { - LocationEvents.InvokeLocationsChanged(this.Monitor, Game1.locations); - this.PreviousGameLocations = this.GetHash(Game1.locations); - } - - // raise current location changed - if (Game1.currentLocation != this.PreviousGameLocation) - { - LocationEvents.InvokeCurrentLocationChanged(this.Monitor, this.PreviousGameLocation, Game1.currentLocation); - this.PreviousGameLocation = Game1.currentLocation; - } - - // raise player changed - if (Game1.player != this.PreviousFarmer) - { - PlayerEvents.InvokeFarmerChanged(this.Monitor, this.PreviousFarmer, Game1.player); - this.PreviousFarmer = Game1.player; - } - - // raise player leveled up a skill - if (Game1.player.combatLevel != this.PreviousCombatLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Combat, Game1.player.combatLevel); - this.PreviousCombatLevel = Game1.player.combatLevel; - } - if (Game1.player.farmingLevel != this.PreviousFarmingLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Farming, Game1.player.farmingLevel); - this.PreviousFarmingLevel = Game1.player.farmingLevel; - } - if (Game1.player.fishingLevel != this.PreviousFishingLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Fishing, Game1.player.fishingLevel); - this.PreviousFishingLevel = Game1.player.fishingLevel; - } - if (Game1.player.foragingLevel != this.PreviousForagingLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Foraging, Game1.player.foragingLevel); - this.PreviousForagingLevel = Game1.player.foragingLevel; - } - if (Game1.player.miningLevel != this.PreviousMiningLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Mining, Game1.player.miningLevel); - this.PreviousMiningLevel = Game1.player.miningLevel; - } - if (Game1.player.luckLevel != this.PreviousLuckLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Luck, Game1.player.luckLevel); - this.PreviousLuckLevel = Game1.player.luckLevel; - } - - // raise player inventory changed - ItemStackChange[] changedItems = this.GetInventoryChanges(Game1.player.items, this.PreviousItems).ToArray(); - if (changedItems.Any()) - { - PlayerEvents.InvokeInventoryChanged(this.Monitor, Game1.player.items, changedItems); - this.PreviousItems = Game1.player.items.Where(n => n != null).ToDictionary(n => n, n => n.Stack); - } - - // raise current location's object list changed - { - int? objectHash = Game1.currentLocation?.objects != null ? this.GetHash(Game1.currentLocation.objects) : (int?)null; - if (objectHash != null && this.PreviousLocationObjects != objectHash) - { - LocationEvents.InvokeOnNewLocationObject(this.Monitor, Game1.currentLocation.objects); - this.PreviousLocationObjects = objectHash.Value; - } - } - - // raise time changed - if (Game1.timeOfDay != this.PreviousTimeOfDay) - { - TimeEvents.InvokeTimeOfDayChanged(this.Monitor, this.PreviousTimeOfDay, Game1.timeOfDay); - this.PreviousTimeOfDay = Game1.timeOfDay; - } - if (Game1.dayOfMonth != this.PreviousDayOfMonth) - { - TimeEvents.InvokeDayOfMonthChanged(this.Monitor, this.PreviousDayOfMonth, Game1.dayOfMonth); - this.PreviousDayOfMonth = Game1.dayOfMonth; - } - if (Game1.currentSeason != this.PreviousSeasonOfYear) - { - TimeEvents.InvokeSeasonOfYearChanged(this.Monitor, this.PreviousSeasonOfYear, Game1.currentSeason); - this.PreviousSeasonOfYear = Game1.currentSeason; - } - if (Game1.year != this.PreviousYearOfGame) - { - TimeEvents.InvokeYearOfGameChanged(this.Monitor, this.PreviousYearOfGame, Game1.year); - this.PreviousYearOfGame = Game1.year; - } - - // raise mine level changed - if (Game1.mine != null && Game1.mine.mineLevel != this.PreviousMineLevel) - { - MineEvents.InvokeMineLevelChanged(this.Monitor, this.PreviousMineLevel, Game1.mine.mineLevel); - this.PreviousMineLevel = Game1.mine.mineLevel; - } - } - - // raise game day transition event (obsolete) - if (Game1.newDay != this.PreviousIsNewDay) - { - TimeEvents.InvokeOnNewDay(this.Monitor, this.PreviousDayOfMonth, Game1.dayOfMonth, Game1.newDay); - this.PreviousIsNewDay = Game1.newDay; - } - } - - /// Get the player inventory changes between two states. - /// The player's current inventory. - /// The player's previous inventory. - private IEnumerable GetInventoryChanges(IEnumerable current, IDictionary previous) - { - current = current.Where(n => n != null).ToArray(); - foreach (Item item in current) - { - // stack size changed - if (previous != null && previous.ContainsKey(item)) - { - if (previous[item] != item.Stack) - yield return new ItemStackChange { Item = item, StackChange = item.Stack - previous[item], ChangeType = ChangeType.StackChange }; - } - - // new item - else - yield return new ItemStackChange { Item = item, StackChange = item.Stack, ChangeType = ChangeType.Added }; - } - - // removed items - if (previous != null) - { - foreach (var entry in previous) - { - if (current.Any(i => i == entry.Key)) - continue; - - yield return new ItemStackChange { Item = entry.Key, StackChange = -entry.Key.Stack, ChangeType = ChangeType.Removed }; - } - } - } - - /// Get a hash value for an enumeration. - /// The enumeration of items to hash. - private int GetHash(IEnumerable enumerable) - { - var hash = 0; - foreach (var v in enumerable) - hash ^= v.GetHashCode(); - return hash; - } - - /// Get reflection metadata for a private field. - /// The field name. - private FieldInfo GetBaseFieldInfo(string name) - { - return typeof(Game1).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); - } - - /// Get the value of a private field. - /// The expected value type. - /// The field name. - private TValue GetBaseFieldValue(string name) where TValue : class - { - return this.GetBaseFieldInfo(name).GetValue(Program.gamePtr) as TValue; - } - - /// Set the value of a private field. - /// The expected value type. - /// The field name. - /// The value to set. - public void SetBaseFieldValue(string name, object value) where TValue : class - { - this.GetBaseFieldInfo(name).SetValue(Program.gamePtr, value as TValue); - } - } -} diff --git a/src/StardewModdingAPI/Inheritance/SObject.cs b/src/StardewModdingAPI/Inheritance/SObject.cs deleted file mode 100644 index 0b0a7ec9..00000000 --- a/src/StardewModdingAPI/Inheritance/SObject.cs +++ /dev/null @@ -1,249 +0,0 @@ -using System; -using System.Xml.Serialization; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using StardewModdingAPI.Framework; -using StardewValley; -using Object = StardewValley.Object; - -#pragma warning disable 1591 -namespace StardewModdingAPI.Inheritance -{ - /// Provides access to the game's internals. - [Obsolete("This class is deprecated and will be removed in a future version.")] - public class SObject : Object - { - /********* - ** Accessors - *********/ - public string Description { get; set; } - public Texture2D Texture { get; set; } - public string CategoryName { get; set; } - public Color CategoryColour { get; set; } - public bool IsPassable { get; set; } - public bool IsPlaceable { get; set; } - public bool HasBeenRegistered { get; set; } - public int RegisteredId { get; set; } - - public int MaxStackSize { get; set; } - - public bool WallMounted { get; set; } - public Vector2 DrawPosition { get; set; } - - public bool FlaggedForPickup { get; set; } - - [XmlIgnore] - public Vector2 CurrentMouse { get; protected set; } - - [XmlIgnore] - public Vector2 PlacedAt { get; protected set; } - - public override int Stack - { - get { return this.stack; } - set { this.stack = value; } - } - - /********* - ** Public methods - *********/ - public SObject() - { - Program.DeprecationManager.Warn(nameof(SObject), "0.39.3", DeprecationLevel.PendingRemoval); - - this.name = "Modded Item Name"; - this.Description = "Modded Item Description"; - this.CategoryName = "Modded Item Category"; - this.Category = 4163; - this.CategoryColour = Color.White; - this.IsPassable = false; - this.IsPlaceable = false; - this.boundingBox = new Rectangle(0, 0, 64, 64); - this.MaxStackSize = 999; - - this.type = "interactive"; - } - - public override string Name - { - get { return this.name; } - set { this.name = value; } - } - - public override string getDescription() - { - return this.Description; - } - - public override void draw(SpriteBatch spriteBatch, int x, int y, float alpha = 1) - { - if (this.Texture != null) - { - spriteBatch.Draw(this.Texture, Game1.GlobalToLocal(Game1.viewport, new Vector2(x * Game1.tileSize + Game1.tileSize / 2 + (this.shakeTimer > 0 ? Game1.random.Next(-1, 2) : 0), y * Game1.tileSize + Game1.tileSize / 2 + (this.shakeTimer > 0 ? Game1.random.Next(-1, 2) : 0))), Game1.currentLocation.getSourceRectForObject(this.ParentSheetIndex), Color.White * alpha, 0f, new Vector2(8f, 8f), this.scale.Y > 1f ? this.getScale().Y : Game1.pixelZoom, this.flipped ? SpriteEffects.FlipHorizontally : SpriteEffects.None, (this.isPassable() ? this.getBoundingBox(new Vector2(x, y)).Top : this.getBoundingBox(new Vector2(x, y)).Bottom) / 10000f); - } - } - - public new void drawAsProp(SpriteBatch b) - { - } - - public override void drawInMenu(SpriteBatch spriteBatch, Vector2 location, float scaleSize, float transparency, float layerDepth, bool drawStackNumber) - { - if (this.isRecipe) - { - transparency = 0.5f; - scaleSize *= 0.75f; - } - - if (this.Texture != null) - { - var targSize = (int) (64 * scaleSize * 0.9f); - var midX = (int) (location.X + 32); - var midY = (int) (location.Y + 32); - - var targX = midX - targSize / 2; - var targY = midY - targSize / 2; - - spriteBatch.Draw(this.Texture, new Rectangle(targX, targY, targSize, targSize), null, new Color(255, 255, 255, transparency), 0, Vector2.Zero, SpriteEffects.None, layerDepth); - } - if (drawStackNumber) - { - var _scale = 0.5f + scaleSize; - Game1.drawWithBorder(this.stack.ToString(), Color.Black, Color.White, location + new Vector2(Game1.tileSize - Game1.tinyFont.MeasureString(string.Concat(this.stack.ToString())).X * _scale, Game1.tileSize - (float) ((double) Game1.tinyFont.MeasureString(string.Concat(this.stack.ToString())).Y * 3.0f / 4.0f) * _scale), 0.0f, _scale, 1f, true); - } - } - - public override void drawWhenHeld(SpriteBatch spriteBatch, Vector2 objectPosition, Farmer f) - { - if (this.Texture != null) - { - var targSize = 64; - var midX = (int) (objectPosition.X + 32); - var midY = (int) (objectPosition.Y + 32); - - var targX = midX - targSize / 2; - var targY = midY - targSize / 2; - - spriteBatch.Draw(this.Texture, new Rectangle(targX, targY, targSize, targSize), null, Color.White, 0, Vector2.Zero, SpriteEffects.None, (f.getStandingY() + 2) / 10000f); - } - } - - public override Color getCategoryColor() - { - return this.CategoryColour; - } - - public override string getCategoryName() - { - if (string.IsNullOrEmpty(this.CategoryName)) - return "Modded Item"; - return this.CategoryName; - } - - public override bool isPassable() - { - return this.IsPassable; - } - - public override bool isPlaceable() - { - return this.IsPlaceable; - } - - public override int maximumStackSize() - { - return this.MaxStackSize; - } - - public SObject Clone() - { - var toRet = new SObject - { - Name = this.Name, - CategoryName = this.CategoryName, - Description = this.Description, - Texture = this.Texture, - IsPassable = this.IsPassable, - IsPlaceable = this.IsPlaceable, - quality = this.quality, - scale = this.scale, - isSpawnedObject = this.isSpawnedObject, - isRecipe = this.isRecipe, - questItem = this.questItem, - stack = 1, - HasBeenRegistered = this.HasBeenRegistered, - RegisteredId = this.RegisteredId - }; - - - return toRet; - } - - public override Item getOne() - { - return this.Clone(); - } - - public override void actionWhenBeingHeld(Farmer who) - { - var x = Game1.oldMouseState.X + Game1.viewport.X; - var y = Game1.oldMouseState.Y + Game1.viewport.Y; - - x = x / Game1.tileSize; - y = y / Game1.tileSize; - - this.CurrentMouse = new Vector2(x, y); - //Program.LogDebug(canBePlacedHere(Game1.currentLocation, CurrentMouse)); - base.actionWhenBeingHeld(who); - } - - public override bool canBePlacedHere(GameLocation l, Vector2 tile) - { - //Program.LogDebug(CurrentMouse.ToString().Replace("{", "").Replace("}", "")); - if (!l.objects.ContainsKey(tile)) - return true; - - return false; - } - - public override bool placementAction(GameLocation location, int x, int y, Farmer who = null) - { - if (Game1.didPlayerJustRightClick()) - return false; - - x = x / Game1.tileSize; - y = y / Game1.tileSize; - - var key = new Vector2(x, y); - - if (!this.canBePlacedHere(location, key)) - return false; - - var s = this.Clone(); - - s.PlacedAt = key; - s.boundingBox = new Rectangle(x / Game1.tileSize * Game1.tileSize, y / Game1.tileSize * Game1.tileSize, this.boundingBox.Width, this.boundingBox.Height); - - location.objects.Add(key, s); - - return true; - } - - public override void actionOnPlayerEntry() - { - //base.actionOnPlayerEntry(); - } - - public override void drawPlacementBounds(SpriteBatch spriteBatch, GameLocation location) - { - if (this.canBePlacedHere(location, this.CurrentMouse)) - { - var targSize = Game1.tileSize; - - var x = Game1.oldMouseState.X + Game1.viewport.X; - var y = Game1.oldMouseState.Y + Game1.viewport.Y; - spriteBatch.Draw(Game1.mouseCursors, new Vector2(x / Game1.tileSize * Game1.tileSize - Game1.viewport.X, y / Game1.tileSize * Game1.tileSize - Game1.viewport.Y), new Rectangle(Utility.playerCanPlaceItemHere(location, this, x, y, Game1.player) ? 194 : 210, 388, 16, 16), Color.White, 0.0f, Vector2.Zero, Game1.pixelZoom, SpriteEffects.None, 0.01f); - } - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/LogWriter.cs b/src/StardewModdingAPI/LogWriter.cs deleted file mode 100644 index e22759a7..00000000 --- a/src/StardewModdingAPI/LogWriter.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using StardewModdingAPI.Framework; - -namespace StardewModdingAPI -{ - /// A log writer which queues messages for output, and periodically flushes them to the console and log file. - /// Only one instance should be created. - [Obsolete("This class is internal and should not be referenced outside SMAPI. It will no longer be exposed in a future version.")] - public class LogWriter - { - /********* - ** Properties - *********/ - /// Manages reading and writing to the log file. - private readonly LogFileManager LogFile; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Manages reading and writing to the log file. - internal LogWriter(LogFileManager logFile) - { - this.WarnDeprecated(); - this.LogFile = logFile; - } - - /// Queue a message for output. - /// The message to log. - public void WriteToLog(string message) - { - this.WarnDeprecated(); - this.WriteToLog(new LogInfo(message)); - } - - /// Queue a message for output. - /// The message to log. - public void WriteToLog(LogInfo message) - { - this.WarnDeprecated(); - string output = $"[{message.LogTime}] {message.Message}"; - if (message.PrintConsole) - { - if (Monitor.ConsoleSupportsColor) - { - Console.ForegroundColor = message.Colour; - Console.WriteLine(message); - Console.ResetColor(); - } - else - Console.WriteLine(message); - } - this.LogFile.WriteLine(output); - } - - /********* - ** Private methods - *********/ - /// Raise a deprecation warning. - private void WarnDeprecated() - { - Program.DeprecationManager.Warn($"the {nameof(LogWriter)} class", "1.0", DeprecationLevel.PendingRemoval); - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/Manifest.cs b/src/StardewModdingAPI/Manifest.cs index 07dd3541..baacf954 100644 --- a/src/StardewModdingAPI/Manifest.cs +++ b/src/StardewModdingAPI/Manifest.cs @@ -1,23 +1,11 @@ using System; using Newtonsoft.Json; +using StardewModdingAPI.Framework.Serialisation; namespace StardewModdingAPI { - /// Wraps so it can implement without breaking backwards compatibility. - [Obsolete("Use " + nameof(IManifest) + " or " + nameof(Mod) + "." + nameof(Mod.ModManifest) + " instead")] - internal class ManifestImpl : Manifest, IManifest - { - /// The mod version. - [JsonProperty("Version", ObjectCreationHandling = ObjectCreationHandling.Auto/* avoids issue where Json.NET can't determine concrete type for interface */)] - public new ISemanticVersion Version - { - get { return base.Version; } - set { base.Version = (Version)value; } - } - } - /// A manifest which describes a mod for SMAPI. - public class Manifest + public class Manifest : IManifest { /********* ** Accessors @@ -32,7 +20,8 @@ namespace StardewModdingAPI public string Author { get; set; } /// The mod version. - public Version Version { get; set; } = new Version(0, 0, 0, "", suppressDeprecationWarning: true); + [JsonConverter(typeof(SemanticVersionConverter))] + public ISemanticVersion Version { get; set; } /// The minimum SMAPI version required by this mod, if any. public string MinimumApiVersion { get; set; } @@ -43,26 +32,6 @@ namespace StardewModdingAPI /// The unique mod ID. public string UniqueID { get; set; } - - /**** - ** Obsolete - ****/ - /// Whether the manifest defined the deprecated field. - [JsonIgnore] - internal bool UsedAuthourField { get; private set; } - - /// Obsolete. - [Obsolete("Use " + nameof(Manifest) + "." + nameof(Manifest.Author) + ".")] - public virtual string Authour - { - get { return this.Author; } - set - { - this.UsedAuthourField = true; - this.Author = value; - } - } - /// Whether the mod uses per-save config files. [Obsolete("Use " + nameof(Mod) + "." + nameof(Mod.Helper) + "." + nameof(IModHelper.ReadConfig) + " instead")] public bool PerSaveConfigs { get; set; } diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs index 0d35939d..45534e16 100644 --- a/src/StardewModdingAPI/Mod.cs +++ b/src/StardewModdingAPI/Mod.cs @@ -23,17 +23,6 @@ namespace StardewModdingAPI /// Writes messages to the console and log file. public IMonitor Monitor { get; internal set; } - /// The mod's manifest. - [Obsolete("Use " + nameof(Mod) + "." + nameof(ModManifest))] - public Manifest Manifest - { - get - { - Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Manifest)}", "1.5", DeprecationLevel.Notice); - return (Manifest)this.ModManifest; - } - } - /// The mod's manifest. public IManifest ModManifest { get; internal set; } @@ -85,11 +74,6 @@ namespace StardewModdingAPI [Obsolete("This overload is obsolete since SMAPI 1.0.")] public virtual void Entry(params object[] objects) { } - /// The mod entry point, called after the mod is first loaded. - /// Provides simplified APIs for writing mods. - [Obsolete("This overload is obsolete since SMAPI 1.1.")] - public virtual void Entry(ModHelper helper) { } - /// The mod entry point, called after the mod is first loaded. /// Provides simplified APIs for writing mods. public virtual void Entry(IModHelper helper) { } @@ -105,7 +89,7 @@ namespace StardewModdingAPI Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigFolder)}", "1.0", DeprecationLevel.Notice); Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0"); // avoid redundant warnings - if (!((Manifest)this.Manifest).PerSaveConfigs) + if (!((Manifest)this.ModManifest).PerSaveConfigs) { this.Monitor.Log("Tried to fetch the per-save config folder, but this mod isn't configured to use per-save config files.", LogLevel.Error); return ""; diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 45bf1238..c0a05e2d 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -14,14 +14,13 @@ using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.Events; using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.Models; -using StardewModdingAPI.Inheritance; using StardewValley; using Monitor = StardewModdingAPI.Framework.Monitor; namespace StardewModdingAPI { /// The main entry point for SMAPI, responsible for hooking into and launching the game. - public class Program + internal class Program { /********* ** Properties @@ -52,31 +51,27 @@ namespace StardewModdingAPI /// Tracks whether the game should exit immediately and any pending initialisation should be cancelled. private static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); - - /********* - ** Accessors - *********/ - /// The number of mods currently loaded by SMAPI. - public static int ModsLoaded; - - /// The underlying game instance. - public static SGame gamePtr; - /// Whether the game is currently running. - public static bool ready; + private static bool ready; /// The underlying game assembly. - public static Assembly StardewAssembly; + private static Assembly StardewAssembly; /// The underlying type. - public static Type StardewProgramType; + private static Type StardewProgramType; /// The field containing game's main instance. - public static FieldInfo StardewGameInfo; + private static FieldInfo StardewGameInfo; - // ReSharper disable once PossibleNullReferenceException - /// The game's build type (i.e. GOG vs Steam). - public static int BuildType => (int)Program.StardewProgramType.GetField("buildType", BindingFlags.Public | BindingFlags.Static).GetValue(null); + + /********* + ** Accessors + *********/ + /// The underlying game instance. + internal static SGame gamePtr; + + /// The number of mods currently loaded by SMAPI. + internal static int ModsLoaded; /// Tracks the installed mods. internal static readonly ModRegistry ModRegistry = new ModRegistry(); @@ -358,7 +353,7 @@ namespace StardewModdingAPI string errorPrefix = $"Couldn't load mod for manifest '{manifestPath}'"; // read manifest - ManifestImpl manifest; + Manifest manifest; try { // read manifest text @@ -370,7 +365,7 @@ namespace StardewModdingAPI } // deserialise manifest - manifest = helper.ReadJsonFile("manifest.json"); + manifest = helper.ReadJsonFile("manifest.json"); if (manifest == null) { Program.Monitor.Log($"{errorPrefix}: the manifest file does not exist.", LogLevel.Error); @@ -381,10 +376,6 @@ namespace StardewModdingAPI Program.Monitor.Log($"{errorPrefix}: manifest doesn't specify an entry DLL.", LogLevel.Error); continue; } - - // log deprecated fields - if (manifest.UsedAuthourField) - deprecationWarnings.Add(() => Program.DeprecationManager.Warn(manifest.Name, $"{nameof(Manifest)}.{nameof(Manifest.Authour)}", "1.0", DeprecationLevel.Notice)); } catch (Exception ex) { @@ -424,7 +415,7 @@ namespace StardewModdingAPI } catch (FormatException ex) when (ex.Message.Contains("not a valid semantic version")) { - Program.Monitor.Log($"{errorPrefix}: the mod specified an invalid minimum SMAPI version '{manifest.MinimumApiVersion}'. This should be a semantic version number like {Constants.Version}.", LogLevel.Error); + Program.Monitor.Log($"{errorPrefix}: the mod specified an invalid minimum SMAPI version '{manifest.MinimumApiVersion}'. This should be a semantic version number like {Constants.ApiVersion}.", LogLevel.Error); continue; } } @@ -528,14 +519,11 @@ namespace StardewModdingAPI { // call entry methods mod.Entry(); // deprecated since 1.0 - mod.Entry((ModHelper)mod.Helper); // deprecated since 1.1 mod.Entry(mod.Helper); // raise deprecation warning for old Entry() methods if (Program.DeprecationManager.IsVirtualMethodImplemented(mod.GetType(), typeof(Mod), nameof(Mod.Entry), new[] { typeof(object[]) })) Program.DeprecationManager.Warn(mod.ModManifest.Name, $"{nameof(Mod)}.{nameof(Mod.Entry)}(object[]) instead of {nameof(Mod)}.{nameof(Mod.Entry)}({nameof(IModHelper)})", "1.0", DeprecationLevel.Notice); - if (Program.DeprecationManager.IsVirtualMethodImplemented(mod.GetType(), typeof(Mod), nameof(Mod.Entry), new[] { typeof(ModHelper) })) - Program.DeprecationManager.Warn(mod.ModManifest.Name, $"{nameof(Mod)}.{nameof(Mod.Entry)}({nameof(ModHelper)}) instead of {nameof(Mod)}.{nameof(Mod.Entry)}({nameof(IModHelper)})", "1.1", DeprecationLevel.PendingRemoval); } catch (Exception ex) { diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs index c29f2cf7..3cb592e2 100644 --- a/src/StardewModdingAPI/SemanticVersion.cs +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -13,28 +13,21 @@ namespace StardewModdingAPI /// Derived from https://github.com/maxhauser/semver. private static readonly Regex Regex = new Regex(@"^(?\d+)(\.(?\d+))?(\.(?\d+))?(?.*)$", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture); - /// The backing field for . - private string _build; - /********* ** Accessors *********/ /// The major version incremented for major API changes. - public int MajorVersion { get; set; } + public int MajorVersion { get; } /// The minor version incremented for backwards-compatible changes. - public int MinorVersion { get; set; } + public int MinorVersion { get; } /// The patch version for backwards-compatible bug fixes. - public int PatchVersion { get; set; } + public int PatchVersion { get; } /// An optional build tag. - public string Build - { - get { return this._build; } - set { this._build = this.GetNormalisedTag(value); } - } + public string Build { get; } /********* @@ -50,7 +43,7 @@ namespace StardewModdingAPI this.MajorVersion = major; this.MinorVersion = minor; this.PatchVersion = patch; - this.Build = build; + this.Build = this.GetNormalisedTag(build); } /// Construct an instance. @@ -65,7 +58,7 @@ namespace StardewModdingAPI this.MajorVersion = int.Parse(match.Groups["major"].Value); this.MinorVersion = match.Groups["minor"].Success ? int.Parse(match.Groups["minor"].Value) : 0; this.PatchVersion = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0; - this.Build = match.Groups["build"].Success ? match.Groups["build"].Value : null; + this.Build = match.Groups["build"].Success ? this.GetNormalisedTag(match.Groups["build"].Value) : null; } /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 337929e2..aa8cad34 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -123,7 +123,6 @@ - @@ -150,6 +149,7 @@ + @@ -157,7 +157,6 @@ - @@ -178,24 +177,21 @@ - - - + + - - + - diff --git a/src/StardewModdingAPI/Version.cs b/src/StardewModdingAPI/Version.cs deleted file mode 100644 index e66d7be5..00000000 --- a/src/StardewModdingAPI/Version.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using Newtonsoft.Json; -using StardewModdingAPI.Framework; - -namespace StardewModdingAPI -{ - /// A semantic version with an optional release tag. - [Obsolete("Use " + nameof(SemanticVersion) + " or " + nameof(Manifest) + "." + nameof(Manifest.Version) + " instead")] - public struct Version : ISemanticVersion - { - /********* - ** Accessors - *********/ - /// The major version incremented for major API changes. - public int MajorVersion { get; set; } - - /// The minor version incremented for backwards-compatible changes. - public int MinorVersion { get; set; } - - /// The patch version for backwards-compatible bug fixes. - public int PatchVersion { get; set; } - - /// An optional build tag. - public string Build { get; set; } - - /// Obsolete. - [JsonIgnore] - [Obsolete("Use " + nameof(Version) + "." + nameof(Version.ToString) + " instead.")] - public string VersionString - { - get - { - Program.DeprecationManager.Warn($"{nameof(Version)}.{nameof(Version.VersionString)}", "1.0", DeprecationLevel.Info); - return this.GetSemanticVersion().ToString(); - } - } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The major version incremented for major API changes. - /// The minor version incremented for backwards-compatible changes. - /// The patch version for backwards-compatible bug fixes. - /// An optional build tag. - public Version(int major, int minor, int patch, string build) - : this(major, minor, patch, build, suppressDeprecationWarning: false) - { } - - /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. - /// The version to compare with this instance. - public int CompareTo(Version other) - { - return this.GetSemanticVersion().CompareTo(other); - } - - /// Get whether this version is newer than the specified version. - /// The version to compare with this instance. - [Obsolete("Use " + nameof(ISemanticVersion) + "." + nameof(ISemanticVersion.IsNewerThan) + " instead")] - public bool IsNewerThan(Version other) - { - return this.GetSemanticVersion().IsNewerThan(other); - } - - /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. - /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes in the sort order. Zero This instance occurs in the same position in the sort order as . Greater than zero This instance follows in the sort order. - /// An object to compare with this instance. - int IComparable.CompareTo(ISemanticVersion other) - { - return this.GetSemanticVersion().CompareTo(other); - } - - /// Get whether this version is older than the specified version. - /// The version to compare with this instance. - bool ISemanticVersion.IsOlderThan(ISemanticVersion other) - { - return this.GetSemanticVersion().IsOlderThan(other); - } - - /// Get whether this version is newer than the specified version. - /// The version to compare with this instance. - bool ISemanticVersion.IsNewerThan(ISemanticVersion other) - { - return this.GetSemanticVersion().IsNewerThan(other); - } - - /// Get a string representation of the version. - public override string ToString() - { - return this.GetSemanticVersion().ToString(); - } - - /********* - ** Private methods - *********/ - /// Construct an instance. - /// The major version incremented for major API changes. - /// The minor version incremented for backwards-compatible changes. - /// The patch version for backwards-compatible bug fixes. - /// An optional build tag. - /// Whether to suppress the deprecation warning. - internal Version(int major, int minor, int patch, string build, bool suppressDeprecationWarning) - { - if (!suppressDeprecationWarning) - Program.DeprecationManager.Warn($"{nameof(Version)}", "1.5", DeprecationLevel.Notice); - - this.MajorVersion = major; - this.MinorVersion = minor; - this.PatchVersion = patch; - this.Build = build; - } - - /// Get the equivalent semantic version. - /// This is a hack so the struct can wrap without a mutable backing field, which would cause a due to recreating the struct value on each change. - private SemanticVersion GetSemanticVersion() - { - return new SemanticVersion(this.MajorVersion, this.MinorVersion, this.PatchVersion, this.Build); - } - } -} diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index c76bb78c..a7cedb6a 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -81,9 +81,6 @@ namespace TrainerMod Command.RegisterCommand("save", "Saves the game? Doesn't seem to work. | save").CommandFired += this.HandleSave; Command.RegisterCommand("load", "Shows the load screen | load").CommandFired += this.HandleLoad; - Command.RegisterCommand("exit", "Closes the game | exit").CommandFired += this.HandleExit; - Command.RegisterCommand("stop", "Closes the game | stop").CommandFired += this.HandleExit; - Command.RegisterCommand("player_setname", "Sets the player's name | player_setname ", new[] { "(player, pet, farm) (String) The target name" }).CommandFired += this.HandlePlayerSetName; Command.RegisterCommand("player_setmoney", "Sets the player's money | player_setmoney |inf", new[] { "(Int32) The target money" }).CommandFired += this.HandlePlayerSetMoney; Command.RegisterCommand("player_setstamina", "Sets the player's stamina | player_setstamina |inf", new[] { "(Int32) The target stamina" }).CommandFired += this.HandlePlayerSetStamina; @@ -142,15 +139,6 @@ namespace TrainerMod Game1.activeClickableMenu = new LoadGameMenu(); } - /// The event raised when the 'exit' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleExit(object sender, EventArgsCommand e) - { - Program.gamePtr.Exit(); - Environment.Exit(0); - } - /// The event raised when the 'player_setName' command is triggered. /// The event sender. /// The event arguments. -- cgit From cec74697866da2d67a62c3a4ef10d605592e4152 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 7 Feb 2017 20:51:22 -0500 Subject: disambiguate references to Farmer (#231) --- src/StardewModdingAPI/Events/EventArgsFarmerChanged.cs | 8 ++++---- src/StardewModdingAPI/Events/PlayerEvents.cs | 3 ++- src/StardewModdingAPI/Framework/SGame.cs | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Events/EventArgsFarmerChanged.cs b/src/StardewModdingAPI/Events/EventArgsFarmerChanged.cs index 273f9d25..699d90be 100644 --- a/src/StardewModdingAPI/Events/EventArgsFarmerChanged.cs +++ b/src/StardewModdingAPI/Events/EventArgsFarmerChanged.cs @@ -1,5 +1,5 @@ using System; -using StardewValley; +using SFarmer = StardewValley.Farmer; namespace StardewModdingAPI.Events { @@ -10,10 +10,10 @@ namespace StardewModdingAPI.Events ** Accessors *********/ /// The previous player character. - public Farmer NewFarmer { get; } + public SFarmer NewFarmer { get; } /// The new player character. - public Farmer PriorFarmer { get; } + public SFarmer PriorFarmer { get; } /********* @@ -22,7 +22,7 @@ namespace StardewModdingAPI.Events /// Construct an instance. /// The previous player character. /// The new player character. - public EventArgsFarmerChanged(Farmer priorFarmer, Farmer newFarmer) + public EventArgsFarmerChanged(SFarmer priorFarmer, SFarmer newFarmer) { this.PriorFarmer = priorFarmer; this.NewFarmer = newFarmer; diff --git a/src/StardewModdingAPI/Events/PlayerEvents.cs b/src/StardewModdingAPI/Events/PlayerEvents.cs index 6e65f5ae..99bdac16 100644 --- a/src/StardewModdingAPI/Events/PlayerEvents.cs +++ b/src/StardewModdingAPI/Events/PlayerEvents.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Framework; using StardewValley; +using SFarmer = StardewValley.Farmer; namespace StardewModdingAPI.Events { @@ -49,7 +50,7 @@ namespace StardewModdingAPI.Events /// Encapsulates monitoring and logging. /// The previous player character. /// The new player character. - internal static void InvokeFarmerChanged(IMonitor monitor, Farmer priorFarmer, Farmer newFarmer) + internal static void InvokeFarmerChanged(IMonitor monitor, SFarmer priorFarmer, SFarmer newFarmer) { if (PlayerEvents.FarmerChanged == null) return; diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 2486e376..2aaca9af 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -14,6 +14,7 @@ using StardewValley.Menus; using StardewValley.Tools; using xTile.Dimensions; using Rectangle = Microsoft.Xna.Framework.Rectangle; +using SFarmer = StardewValley.Farmer; namespace StardewModdingAPI.Framework { @@ -131,7 +132,7 @@ namespace StardewModdingAPI.Framework public bool PreviousIsNewDay { get; private set; } /// The player character at last check. - public Farmer PreviousFarmer { get; private set; } + public SFarmer PreviousFarmer { get; private set; } /// An index incremented on every tick and reset every 60th tick (0–59). public int CurrentUpdateTick { get; private set; } @@ -164,7 +165,7 @@ namespace StardewModdingAPI.Framework /// The current player. [Obsolete("Use Game1.player instead")] - public Farmer CurrentFarmer => Game1.player; + public SFarmer CurrentFarmer => Game1.player; /// The game method which draws the farm buildings. public static MethodInfo DrawFarmBuildings = typeof(Game1).GetMethod("drawFarmBuildings", BindingFlags.NonPublic | BindingFlags.Instance); @@ -296,7 +297,6 @@ namespace StardewModdingAPI.Framework /// The method called during game launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. protected override void Initialize() { - //ModItems = new Dictionary(); SGame.DebugMessageQueue = new Queue(); this.PreviouslyPressedButtons = new Buttons[4][]; for (var i = 0; i < 4; ++i) -- cgit From 8efa5f32c1721a1ee60f3783022453c1dfb69d91 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 7 Feb 2017 21:07:57 -0500 Subject: add reflectionHelper.GetPrivateProperty (#231) --- release-notes.md | 1 + .../Framework/Reflection/PrivateProperty.cs | 93 ++++++++++++++++++++++ .../Framework/Reflection/ReflectionHelper.cs | 57 +++++++++++++ src/StardewModdingAPI/IPrivateProperty.cs | 26 ++++++ src/StardewModdingAPI/IReflectionHelper.cs | 14 ++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 2 + 6 files changed, 193 insertions(+) create mode 100644 src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs create mode 100644 src/StardewModdingAPI/IPrivateProperty.cs (limited to 'src') diff --git a/release-notes.md b/release-notes.md index aff7631e..b67030e8 100644 --- a/release-notes.md +++ b/release-notes.md @@ -7,6 +7,7 @@ For players: * Updated for Stardew Valley 1.2. For mod developers: +* Added `GetPrivateProperty` to reflection helper. * Many deprecated APIs have been removed; see [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod) for more information. diff --git a/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs b/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs new file mode 100644 index 00000000..08204b7e --- /dev/null +++ b/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs @@ -0,0 +1,93 @@ +using System; +using System.Reflection; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// A private property obtained through reflection. + /// The property value type. + internal class PrivateProperty : IPrivateProperty + { + /********* + ** Properties + *********/ + /// The type that has the field. + private readonly Type ParentType; + + /// The object that has the instance field (if applicable). + private readonly object Parent; + + /// The display name shown in error messages. + private string DisplayName => $"{this.ParentType.FullName}::{this.PropertyInfo.Name}"; + + + /********* + ** Accessors + *********/ + /// The reflection metadata. + public PropertyInfo PropertyInfo { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The type that has the field. + /// The object that has the instance field (if applicable). + /// The reflection metadata. + /// Whether the field is static. + /// The or is null. + /// The is null for a non-static field, or not null for a static field. + public PrivateProperty(Type parentType, object obj, PropertyInfo property, bool isStatic) + { + // validate + if (parentType == null) + throw new ArgumentNullException(nameof(parentType)); + if (property == null) + throw new ArgumentNullException(nameof(property)); + if (isStatic && obj != null) + throw new ArgumentException("A static property cannot have an object instance."); + if (!isStatic && obj == null) + throw new ArgumentException("A non-static property must have an object instance."); + + // save + this.ParentType = parentType; + this.Parent = obj; + this.PropertyInfo = property; + } + + /// Get the property value. + public TValue GetValue() + { + try + { + return (TValue)this.PropertyInfo.GetValue(this.Parent); + } + catch (InvalidCastException) + { + throw new InvalidCastException($"Can't convert the private {this.DisplayName} property from {this.PropertyInfo.PropertyType.FullName} to {typeof(TValue).FullName}."); + } + catch (Exception ex) + { + throw new Exception($"Couldn't get the value of the private {this.DisplayName} property", ex); + } + } + + /// Set the property value. + //// The value to set. + public void SetValue(TValue value) + { + try + { + this.PropertyInfo.SetValue(this.Parent, value); + } + catch (InvalidCastException) + { + throw new InvalidCastException($"Can't assign the private {this.DisplayName} property a {typeof(TValue).FullName} value, must be compatible with {this.PropertyInfo.PropertyType.FullName}."); + } + catch (Exception ex) + { + throw new Exception($"Couldn't set the value of the private {this.DisplayName} property", ex); + } + } + } +} diff --git a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs index edf59b81..7a5789dc 100644 --- a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs +++ b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs @@ -58,6 +58,41 @@ namespace StardewModdingAPI.Framework.Reflection return field; } + /**** + ** Properties + ****/ + /// Get a private instance property. + /// The property type. + /// The object which has the property. + /// The property name. + /// Whether to throw an exception if the private property is not found. + public IPrivateProperty GetPrivateProperty(object obj, string name, bool required = true) + { + // validate + if (obj == null) + throw new ArgumentNullException(nameof(obj), "Can't get a private instance property from a null object."); + + // get property from hierarchy + IPrivateProperty property = this.GetPropertyFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic); + if (required && property == null) + throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance property."); + return property; + } + + /// Get a private static property. + /// The property type. + /// The type which has the property. + /// The property name. + /// Whether to throw an exception if the private property is not found. + public IPrivateProperty GetPrivateProperty(Type type, string name, bool required = true) + { + // get field from hierarchy + IPrivateProperty property = this.GetPropertyFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static); + if (required && property == null) + throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static property."); + return property; + } + /**** ** Field values ** (shorthand since this is the most common case) @@ -192,6 +227,28 @@ namespace StardewModdingAPI.Framework.Reflection : null; } + /// Get a property from the type hierarchy. + /// The expected property type. + /// The type which has the property. + /// The object which has the property. + /// The property name. + /// The reflection binding which flags which indicates what type of property to find. + private IPrivateProperty GetPropertyFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) + { + bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); + PropertyInfo property = this.GetCached($"property::{isStatic}::{type.FullName}::{name}", () => + { + PropertyInfo propertyInfo = null; + for (; type != null && propertyInfo == null; type = type.BaseType) + propertyInfo = type.GetProperty(name, bindingFlags); + return propertyInfo; + }); + + return property != null + ? new PrivateProperty(type, obj, property, isStatic) + : null; + } + /// Get a method from the type hierarchy. /// The type which has the method. /// The object which has the method. diff --git a/src/StardewModdingAPI/IPrivateProperty.cs b/src/StardewModdingAPI/IPrivateProperty.cs new file mode 100644 index 00000000..8d67fa7a --- /dev/null +++ b/src/StardewModdingAPI/IPrivateProperty.cs @@ -0,0 +1,26 @@ +using System.Reflection; + +namespace StardewModdingAPI +{ + /// A private property obtained through reflection. + /// The property value type. + public interface IPrivateProperty + { + /********* + ** Accessors + *********/ + /// The reflection metadata. + PropertyInfo PropertyInfo { get; } + + + /********* + ** Public methods + *********/ + /// Get the property value. + TValue GetValue(); + + /// Set the property value. + //// The value to set. + void SetValue(TValue value); + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/IReflectionHelper.cs b/src/StardewModdingAPI/IReflectionHelper.cs index 5d747eda..77943c6c 100644 --- a/src/StardewModdingAPI/IReflectionHelper.cs +++ b/src/StardewModdingAPI/IReflectionHelper.cs @@ -22,6 +22,20 @@ namespace StardewModdingAPI /// Whether to throw an exception if the private field is not found. IPrivateField GetPrivateField(Type type, string name, bool required = true); + /// Get a private instance property. + /// The property type. + /// The object which has the property. + /// The property name. + /// Whether to throw an exception if the private property is not found. + IPrivateProperty GetPrivateProperty(object obj, string name, bool required = true); + + /// Get a private static property. + /// The property type. + /// The type which has the property. + /// The property name. + /// Whether to throw an exception if the private property is not found. + IPrivateProperty GetPrivateProperty(Type type, string name, bool required = true); + /// Get the value of a private instance field. /// The field type. /// The object which has the field. diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index aa8cad34..cecfc831 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -149,6 +149,7 @@ + @@ -170,6 +171,7 @@ + -- cgit From 98cf6a2766b1571089fede5be5f13cfb57cea34f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 7 Feb 2017 21:54:29 -0500 Subject: update SGame.Draw with base code (#231) --- src/StardewModdingAPI/Events/GraphicsEvents.cs | 24 - src/StardewModdingAPI/Framework/SGame.cs | 1050 ++++++++++++++---------- 2 files changed, 629 insertions(+), 445 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Events/GraphicsEvents.cs b/src/StardewModdingAPI/Events/GraphicsEvents.cs index c3236c0a..fc25d2b8 100644 --- a/src/StardewModdingAPI/Events/GraphicsEvents.cs +++ b/src/StardewModdingAPI/Events/GraphicsEvents.cs @@ -18,14 +18,6 @@ namespace StardewModdingAPI.Events /// Raised when drawing debug information to the screen (when is true). This is called after the sprite batch is begun. If you just want to add debug info, use in your update loop. public static event EventHandler DrawDebug; - /// Obsolete. - [Obsolete("Use the other Pre/Post render events instead.")] - public static event EventHandler DrawTick; - - /// Obsolete. - [Obsolete("Use the other Pre/Post render events instead. All of them will automatically be drawn into the render target if needed.")] - public static event EventHandler DrawInRenderTargetTick; - /**** ** Main render events ****/ @@ -88,22 +80,6 @@ namespace StardewModdingAPI.Events monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.DrawDebug)}", GraphicsEvents.DrawDebug?.GetInvocationList()); } - /// Raise a event. - /// Encapsulates logging and monitoring. - [Obsolete("Should not be used.")] - public static void InvokeDrawTick(IMonitor monitor) - { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.DrawTick)}", GraphicsEvents.DrawTick?.GetInvocationList()); - } - - /// Raise a event. - /// Encapsulates monitoring and logging. - [Obsolete("Should not be used.")] - public static void InvokeDrawInRenderTargetTick(IMonitor monitor) - { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.DrawInRenderTargetTick)}", GraphicsEvents.DrawInRenderTargetTick?.GetInvocationList()); - } - /**** ** Main render events ****/ diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 2aaca9af..aa48c9b2 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -1,19 +1,21 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; -using System.Reflection; +using System.Threading.Tasks; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using StardewModdingAPI.Events; +using StardewModdingAPI.Framework.Reflection; using StardewValley; using StardewValley.BellsAndWhistles; 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 @@ -24,6 +26,9 @@ namespace StardewModdingAPI.Framework /********* ** Properties *********/ + /**** + ** SMAPI state + ****/ /// The number of ticks until SMAPI should notify mods when is set. /// Skipping a few frames ensures the game finishes initialising the world before mods try to change it. private int AfterLoadTimer = 5; @@ -40,6 +45,28 @@ namespace StardewModdingAPI.Framework /// Encapsulates monitoring and logging. private readonly IMonitor Monitor; + /**** + ** Private wrappers + ****/ + // ReSharper disable InconsistentNaming + /// Used to access private fields and methods. + private static readonly IReflectionHelper Reflection = new ReflectionHelper(); + private static List _fpsList => SGame.Reflection.GetPrivateField>(typeof(Game1), nameof(_fpsList)).GetValue(); + private static Stopwatch _fpsStopwatch => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(SGame._fpsStopwatch)).GetValue(); + private static float _fps + { + get { return SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_fps)).GetValue(); } + set { SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_fps)).SetValue(value); } + } + private static Task _newDayTask => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_newDayTask)).GetValue(); + private Color bgColor => SGame.Reflection.GetPrivateField(this, nameof(bgColor)).GetValue(); + public RenderTarget2D screenWrapper => SGame.Reflection.GetPrivateProperty(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop + public BlendState lightingBlend => SGame.Reflection.GetPrivateField(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]); + // ReSharper restore InconsistentNaming + /********* ** Accessors @@ -140,20 +167,6 @@ namespace StardewModdingAPI.Framework /// Whether this is the very first update tick since the game started. public bool FirstUpdate { get; private set; } - /// The game's current render target. - public RenderTarget2D Screen - { - get { return this.GetBaseFieldValue("screen"); } - set { this.SetBaseFieldValue("screen", value); } - } - - /// The game's current background color. - public Color BgColour - { - get { return (Color)this.GetBaseFieldValue("bgColor"); } - set { this.SetBaseFieldValue("bgColor", value); } - } - /// The current game instance. public static SGame Instance { get; private set; } @@ -163,19 +176,6 @@ namespace StardewModdingAPI.Framework /// Whether we're in pseudo-debug mode, which shows information like FPS. public static bool Debug { get; private set; } - /// The current player. - [Obsolete("Use Game1.player instead")] - public SFarmer CurrentFarmer => Game1.player; - - /// The game method which draws the farm buildings. - public static MethodInfo DrawFarmBuildings = typeof(Game1).GetMethod("drawFarmBuildings", BindingFlags.NonPublic | BindingFlags.Instance); - - /// The game method which draws the game HUD. - public static MethodInfo DrawHUD = typeof(Game1).GetMethod("drawHUD", BindingFlags.NonPublic | BindingFlags.Instance); - - /// The game method which draws the current dialogue box, if any. - public static MethodInfo DrawDialogueBox = typeof(Game1).GetMethod("drawDialogueBox", BindingFlags.NonPublic | BindingFlags.Instance); - /********* ** Public methods @@ -384,421 +384,653 @@ namespace StardewModdingAPI.Framework try { - if (!this.ZoomLevelIsOne) - this.GraphicsDevice.SetRenderTarget(this.Screen); - - this.GraphicsDevice.Clear(this.BgColour); - 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 + if (SGame._fpsStopwatch.IsRunning) { - Game1.activeClickableMenu.drawBackground(Game1.spriteBatch); + 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)); } - 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) - { - this.GraphicsDevice.SetRenderTarget(null); - this.GraphicsDevice.Clear(this.BgColour); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); - Game1.spriteBatch.End(); - } - 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.BgColour); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.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.showingEndOfNightStuff) + else if (this.IsSaving) { - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); - try + this.GraphicsDevice.Clear(this.bgColor); + IClickableMenu activeClickableMenu = Game1.activeClickableMenu; + if (activeClickableMenu != null) { - 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.BgColour); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); + activeClickableMenu.draw(Game1.spriteBatch); Game1.spriteBatch.End(); } - return; + base.Draw(gameTime); } - if (Game1.gameMode == 6) + else { - 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) + if ((double)Game1.options.zoomLevel != 1.0) + this.GraphicsDevice.SetRenderTarget(this.screenWrapper); + this.GraphicsDevice.Clear(this.bgColor); + if (Game1.activeClickableMenu != null && Game1.options.showMenuBackground && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet()) { - text += "."; - num++; + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); + Game1.activeClickableMenu.drawBackground(Game1.spriteBatch); + Game1.activeClickableMenu.draw(Game1.spriteBatch); + 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(); } - 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) + else if ((int)Game1.gameMode == 11) { - this.GraphicsDevice.SetRenderTarget(null); - this.GraphicsDevice.Clear(this.BgColour); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); + Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); + Game1.spriteBatch.DrawString(Game1.smoothFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3685"), new Vector2(16f, 16f), Color.HotPink); + Game1.spriteBatch.DrawString(Game1.smoothFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3686"), new Vector2(16f, 32f), new Color(0, (int)byte.MaxValue, 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 (Game1.gameMode == 0) - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); - else - { - if (Game1.drawLighting) + else if (Game1.currentMinigame != null) { - 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++) + Game1.currentMinigame.draw(Game1.spriteBatch); + if (Game1.globalFade && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause)) + { + 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) { - 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); + 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(); - this.GraphicsDevice.SetRenderTarget(this.ZoomLevelIsOne ? null : this.Screen); } - if (Game1.bloomDay) - Game1.bloom?.BeginDraw(); - this.GraphicsDevice.Clear(this.BgColour); - 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) + else if (Game1.showingEndOfNightStuff) { - using (List.Enumerator enumerator = Game1.currentLocation.characters.GetEnumerator()) + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); + if (Game1.activeClickableMenu != null) + Game1.activeClickableMenu.draw(Game1.spriteBatch); + Game1.spriteBatch.End(); + if ((double)Game1.options.zoomLevel != 1.0) { - while (enumerator.MoveNext()) - { - 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); - } - goto IL_B30; + 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 current2 in Game1.CurrentEvent.actors) + else if ((int)Game1.gameMode == 6) { - 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); + 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 s = Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3688") + str1; + string str2 = Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3689"); + int widthOfString = SpriteText.getWidthOfString(str2); + 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, str2, -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(); } - 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) + else { - using (List.Enumerator enumerator3 = Game1.currentLocation.characters.GetEnumerator()) + Microsoft.Xna.Framework.Rectangle rectangle; + Viewport viewport; + if ((int)Game1.gameMode == 0) + { + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); + } + else { - while (enumerator3.MoveNext()) + 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(index).position, (int)((double)Game1.currentLightSources.ElementAt(index).radius * (double)Game1.tileSize * 4.0))) + Game1.spriteBatch.Draw(Game1.currentLightSources.ElementAt(index).lightTexture, Game1.GlobalToLocal(Game1.viewport, Game1.currentLightSources.ElementAt(index).position) / (float)(Game1.options.lightingQuality / 2), new Microsoft.Xna.Framework.Rectangle?(Game1.currentLightSources.ElementAt(index).lightTexture.Bounds), Game1.currentLightSources.ElementAt(index).color, 0.0f, new Vector2((float)Game1.currentLightSources.ElementAt(index).lightTexture.Bounds.Center.X, (float)Game1.currentLightSources.ElementAt(index).lightTexture.Bounds.Center.Y), Game1.currentLightSources.ElementAt(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); + 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 { - 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); + 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 bounds1; + 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; + bounds1 = Game1.shadowTexture.Bounds; + double y = (double)bounds1.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; + bounds1 = Game1.shadowTexture.Bounds; + double x = (double)bounds1.Center.X; + bounds1 = Game1.shadowTexture.Bounds; + double y = (double)bounds1.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; + bounds1 = Game1.shadowTexture.Bounds; + double x = (double)bounds1.Center.X; + bounds1 = Game1.shadowTexture.Bounds; + double y = (double)bounds1.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 && Game1.currentLocation.currentEvent.messageToScreen != null) + { + string messageToScreen = Game1.currentLocation.currentEvent.messageToScreen; + Color black = Color.Black; + Color white = Color.White; + double num1 = (double)(Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Width / 2) - (double)Game1.borderFont.MeasureString(Game1.currentLocation.currentEvent.messageToScreen).X / 2.0; + viewport = Game1.graphics.GraphicsDevice.Viewport; + double num2 = (double)(viewport.TitleSafeArea.Height - Game1.tileSize); + Vector2 position = new Vector2((float)num1, (float)num2); + double num3 = 0.0; + double num4 = 1.0; + double num5 = 0.999000012874603; + Game1.drawWithBorder(messageToScreen, black, white, position, (float)num3, (float)num4, (float)num5); + } + 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_128; + } + else + goto label_128; + } + Game1.drawPlayerHeldObject(Game1.player); + } + label_128: + 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) + { + SpriteBatch spriteBatch = Game1.spriteBatch; + Texture2D fadeToBlackRect = Game1.fadeToBlackRect; + viewport = Game1.graphics.GraphicsDevice.Viewport; + Microsoft.Xna.Framework.Rectangle bounds2 = viewport.Bounds; + Color color = Color.Black * Game1.currentLocation.LightLevel; + spriteBatch.Draw(fadeToBlackRect, bounds2, color); + } + if (Game1.screenGlow) + { + SpriteBatch spriteBatch = Game1.spriteBatch; + Texture2D fadeToBlackRect = Game1.fadeToBlackRect; + viewport = Game1.graphics.GraphicsDevice.Viewport; + Microsoft.Xna.Framework.Rectangle bounds2 = viewport.Bounds; + Color color = Game1.screenGlowColor * Game1.screenGlowAlpha; + spriteBatch.Draw(fadeToBlackRect, bounds2, color); + } + 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)) + { + SpriteBatch spriteBatch = Game1.spriteBatch; + Texture2D staminaRect = Game1.staminaRect; + viewport = Game1.graphics.GraphicsDevice.Viewport; + Microsoft.Xna.Framework.Rectangle bounds2 = viewport.Bounds; + Color color = Color.OrangeRed * 0.45f; + spriteBatch.Draw(staminaRect, bounds2, color); + } + Game1.spriteBatch.End(); + } + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); + if (Game1.drawGrid) + { + int num1 = -Game1.viewport.X % Game1.tileSize; + float num2 = (float)(-Game1.viewport.Y % Game1.tileSize); + int num3 = num1; + while (true) + { + int num4 = num3; + viewport = Game1.graphics.GraphicsDevice.Viewport; + int width1 = viewport.Width; + if (num4 < width1) + { + SpriteBatch spriteBatch = Game1.spriteBatch; + Texture2D staminaRect = Game1.staminaRect; + int x = num3; + int y = (int)num2; + int width2 = 1; + viewport = Game1.graphics.GraphicsDevice.Viewport; + int height = viewport.Height; + Microsoft.Xna.Framework.Rectangle destinationRectangle = new Microsoft.Xna.Framework.Rectangle(x, y, width2, height); + Color color = Color.Red * 0.5f; + spriteBatch.Draw(staminaRect, destinationRectangle, color); + num3 += Game1.tileSize; + } + else + break; + } + float num5 = num2; + while (true) + { + double num4 = (double)num5; + viewport = Game1.graphics.GraphicsDevice.Viewport; + double height1 = (double)viewport.Height; + if (num4 < height1) + { + SpriteBatch spriteBatch = Game1.spriteBatch; + Texture2D staminaRect = Game1.staminaRect; + int x = num1; + int y = (int)num5; + viewport = Game1.graphics.GraphicsDevice.Viewport; + int width = viewport.Width; + int height2 = 1; + Microsoft.Xna.Framework.Rectangle destinationRectangle = new Microsoft.Xna.Framework.Rectangle(x, y, width, height2); + Color color = Color.Red * 0.5f; + spriteBatch.Draw(staminaRect, destinationRectangle, color); + num5 += (float)Game1.tileSize; + } + else + break; + } + } + if (Game1.currentBillboard != 0) + this.drawBillboard(); + if ((Game1.displayHUD || Game1.eventUp) && (Game1.currentBillboard == 0 && (int)Game1.gameMode == 3) && (!Game1.freezeControls && !Game1.panMode)) + this.drawHUD(); + 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); } - goto IL_F5F; } - } - 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")) - SGame.DrawFarmBuildings.Invoke(Program.gamePtr, null); - 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) + 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(); + if (Game1.progressBar) { - case 1: - color = Tool.copperColor; - break; - case 2: - color = Tool.steelColor; - break; - case 3: - color = Tool.goldColor; - break; - case 4: - color = Tool.iridiumColor; - break; + SpriteBatch spriteBatch1 = Game1.spriteBatch; + Texture2D fadeToBlackRect = Game1.fadeToBlackRect; + viewport = Game1.graphics.GraphicsDevice.Viewport; + int x1 = (viewport.TitleSafeArea.Width - Game1.dialogueWidth) / 2; + viewport = Game1.graphics.GraphicsDevice.Viewport; + rectangle = 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); } - 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) + if (Game1.eventUp && (object)Game1.currentLocation != null && Game1.currentLocation.currentEvent != null) + Game1.currentLocation.currentEvent.drawAfterMap(Game1.spriteBatch); + if (Game1.isRaining && (object)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 (current7.isEmoting) + if (Game1.options.screenFlash) { - 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); + 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; } - } - Game1.spriteBatch.End(); - if (Game1.drawLighting) - { - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, new BlendState + 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) { - 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)) + SpriteBatch spriteBatch = Game1.spriteBatch; + SpriteFont smallFont = Game1.smallFont; + object[] objArray = new object[8]; + 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 = " wasMouseVisibleThisFrame: "; + objArray[index4] = (object)str3; + int index5 = 4; + string str4 = Game1.wasMouseVisibleThisFrame.ToString(); + objArray[index5] = (object)str4; + int index6 = 5; + string newLine = Environment.NewLine; + objArray[index6] = (object)newLine; + int index7 = 6; + string str5 = "debugOutput: "; + objArray[index7] = (object)str5; + int index8 = 7; + string debugOutput = Game1.debugOutput; + objArray[index8] = (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) + Game1.activeClickableMenu.draw(Game1.spriteBatch); + else if (Game1.farmEvent != null) + Game1.farmEvent.drawAboveEverything(Game1.spriteBatch); + Game1.spriteBatch.End(); + if (Game1.overlayMenu != null) { - 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); + Game1.overlayMenu.draw(Game1.spriteBatch); + Game1.spriteBatch.End(); } + if ((double)Game1.options.zoomLevel == 1.0) + return; + this.GraphicsDevice.SetRenderTarget((RenderTarget2D)null); + this.GraphicsDevice.Clear(this.bgColor); + Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, 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(); } - 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(); - - GraphicsEvents.InvokeOnPreRenderHudEventNoCheck(this.Monitor); - if ((Game1.displayHUD || Game1.eventUp) && Game1.currentBillboard == 0 && Game1.gameMode == 3 && !Game1.freezeControls && !Game1.panMode) - { - GraphicsEvents.InvokeOnPreRenderHudEvent(this.Monitor); - SGame.DrawHUD.Invoke(Program.gamePtr, null); - 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); - GraphicsEvents.InvokeOnPostRenderHudEventNoCheck(this.Monitor); - - 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)) - SGame.DrawDialogueBox.Invoke(Program.gamePtr, null); - 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) - SGame.DrawDialogueBox.Invoke(Program.gamePtr, null); - 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); - - GraphicsEvents.InvokeOnPreRenderGuiEventNoCheck(this.Monitor); - 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(); - } - GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); - } - else - Game1.farmEvent?.drawAboveEverything(Game1.spriteBatch); - GraphicsEvents.InvokeOnPostRenderGuiEventNoCheck(this.Monitor); - - GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor); - Game1.spriteBatch.End(); - - GraphicsEvents.InvokeDrawInRenderTargetTick(this.Monitor); - - if (!this.ZoomLevelIsOne) - { - this.GraphicsDevice.SetRenderTarget(null); - this.GraphicsDevice.Clear(this.BgColour); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw(this.Screen, Vector2.Zero, this.Screen.Bounds, Color.White, 0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); - Game1.spriteBatch.End(); } - - GraphicsEvents.InvokeDrawTick(this.Monitor); } catch (Exception ex) { @@ -1105,29 +1337,5 @@ namespace StardewModdingAPI.Framework hash ^= v.GetHashCode(); return hash; } - - /// Get reflection metadata for a private field. - /// The field name. - private FieldInfo GetBaseFieldInfo(string name) - { - return typeof(Game1).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); - } - - /// Get the value of a private field. - /// The expected value type. - /// The field name. - private TValue GetBaseFieldValue(string name) where TValue : class - { - return this.GetBaseFieldInfo(name).GetValue(Program.gamePtr) as TValue; - } - - /// Set the value of a private field. - /// The expected value type. - /// The field name. - /// The value to set. - public void SetBaseFieldValue(string name, object value) where TValue : class - { - this.GetBaseFieldInfo(name).SetValue(Program.gamePtr, value as TValue); - } } } -- cgit From 8b0e54a777ab596c0d8ec571ac0bc973b31a6bbf Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 7 Feb 2017 22:10:00 -0500 Subject: ignore code analysis warnings in game code (#231) --- src/StardewModdingAPI/Framework/SGame.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index aa48c9b2..8acb5e58 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -2,6 +2,7 @@ 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; @@ -48,14 +49,13 @@ namespace StardewModdingAPI.Framework /**** ** Private wrappers ****/ - // ReSharper disable InconsistentNaming + // ReSharper disable ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming /// Used to access private fields and methods. private static readonly IReflectionHelper Reflection = new ReflectionHelper(); private static List _fpsList => SGame.Reflection.GetPrivateField>(typeof(Game1), nameof(_fpsList)).GetValue(); private static Stopwatch _fpsStopwatch => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(SGame._fpsStopwatch)).GetValue(); private static float _fps { - get { return SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_fps)).GetValue(); } set { SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_fps)).SetValue(value); } } private static Task _newDayTask => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_newDayTask)).GetValue(); @@ -65,7 +65,7 @@ namespace StardewModdingAPI.Framework 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]); - // ReSharper restore InconsistentNaming + // ReSharper restore ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming /********* @@ -377,6 +377,13 @@ namespace StardewModdingAPI.Framework /// The method called to draw everything to the screen. /// A snapshot of the game timing state. /// This implementation is identical to , except for try..catch around menu draw code, minor formatting, and added events. + [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "copied from game code as-is")] + [SuppressMessage("ReSharper", "LocalVariableHidesMember", Justification = "copied from game code as-is")] + [SuppressMessage("ReSharper", "PossibleLossOfFraction", Justification = "copied from game code as-is")] + [SuppressMessage("ReSharper", "RedundantArgumentDefaultValue", Justification = "copied from game code as-is")] + [SuppressMessage("ReSharper", "RedundantCast", Justification = "copied from game code as-is")] + [SuppressMessage("ReSharper", "RedundantExplicitNullableCreation", Justification = "copied from game code as-is")] + [SuppressMessage("ReSharper", "RedundantTypeArgumentsOfMethod", Justification = "copied from game code as-is")] protected override void Draw(GameTime gameTime) { // track frame rate -- cgit From 84bc32c3f19f0835e5a12f61fa707221d33b4820 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 7 Feb 2017 23:04:16 -0500 Subject: reimplement graphics events (#231) --- src/StardewModdingAPI/Events/GraphicsEvents.cs | 48 ++--------------- src/StardewModdingAPI/Framework/SGame.cs | 73 +++++++++++++++++++++----- 2 files changed, 64 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Events/GraphicsEvents.cs b/src/StardewModdingAPI/Events/GraphicsEvents.cs index fc25d2b8..03dabb85 100644 --- a/src/StardewModdingAPI/Events/GraphicsEvents.cs +++ b/src/StardewModdingAPI/Events/GraphicsEvents.cs @@ -21,10 +21,10 @@ namespace StardewModdingAPI.Events /**** ** Main render events ****/ - /// Raised before drawing everything to the screen during a draw loop. + /// Raised before drawing the world to the screen. public static event EventHandler OnPreRenderEvent; - /// Raised after drawing everything to the screen during a draw loop. + /// Raised after drawing the world to the screen. public static event EventHandler OnPostRenderEvent; /**** @@ -33,30 +33,18 @@ namespace StardewModdingAPI.Events /// Raised before drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.) public static event EventHandler OnPreRenderHudEvent; - /// Equivalent to , but invoked even if the HUD isn't available. - public static event EventHandler OnPreRenderHudEventNoCheck; - /// Raised after drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.) public static event EventHandler OnPostRenderHudEvent; - /// Equivalent to , but invoked even if the HUD isn't available. - public static event EventHandler OnPostRenderHudEventNoCheck; - /**** ** GUI events ****/ /// Raised before drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen. public static event EventHandler OnPreRenderGuiEvent; - /// Equivalent to , but invoked even if there's no menu being drawn. - public static event EventHandler OnPreRenderGuiEventNoCheck; - /// Raised after drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen. public static event EventHandler OnPostRenderGuiEvent; - /// Equivalent to , but invoked even if there's no menu being drawn. - public static event EventHandler OnPostRenderGuiEventNoCheck; - /********* ** Internal methods @@ -98,7 +86,7 @@ namespace StardewModdingAPI.Events } /**** - ** HUD events + ** GUI events ****/ /// Raise an event. /// Encapsulates monitoring and logging. @@ -107,13 +95,6 @@ namespace StardewModdingAPI.Events monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPreRenderGuiEvent)}", GraphicsEvents.OnPreRenderGuiEvent?.GetInvocationList()); } - /// Raise an event. - /// Encapsulates monitoring and logging. - internal static void InvokeOnPreRenderGuiEventNoCheck(IMonitor monitor) - { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPreRenderGuiEventNoCheck)}", GraphicsEvents.OnPreRenderGuiEventNoCheck?.GetInvocationList()); - } - /// Raise an event. /// Encapsulates monitoring and logging. internal static void InvokeOnPostRenderGuiEvent(IMonitor monitor) @@ -121,15 +102,8 @@ namespace StardewModdingAPI.Events monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPostRenderGuiEvent)}", GraphicsEvents.OnPostRenderGuiEvent?.GetInvocationList()); } - /// Raise an event. - /// Encapsulates monitoring and logging. - internal static void InvokeOnPostRenderGuiEventNoCheck(IMonitor monitor) - { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPostRenderGuiEventNoCheck)}", GraphicsEvents.OnPostRenderGuiEventNoCheck?.GetInvocationList()); - } - /**** - ** GUI events + ** HUD events ****/ /// Raise an event. /// Encapsulates monitoring and logging. @@ -138,25 +112,11 @@ namespace StardewModdingAPI.Events monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPreRenderHudEvent)}", GraphicsEvents.OnPreRenderHudEvent?.GetInvocationList()); } - /// Raise an event. - /// Encapsulates monitoring and logging. - internal static void InvokeOnPreRenderHudEventNoCheck(IMonitor monitor) - { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPreRenderHudEventNoCheck)}", GraphicsEvents.OnPreRenderHudEventNoCheck?.GetInvocationList()); - } - /// Raise an event. /// Encapsulates monitoring and logging. internal static void InvokeOnPostRenderHudEvent(IMonitor monitor) { monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPostRenderHudEvent)}", GraphicsEvents.OnPostRenderHudEvent?.GetInvocationList()); } - - /// Raise an event. - /// Encapsulates monitoring and logging. - internal static void InvokeOnPostRenderHudEventNoCheck(IMonitor monitor) - { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPostRenderHudEventNoCheck)}", GraphicsEvents.OnPostRenderHudEventNoCheck?.GetInvocationList()); - } } } diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 8acb5e58..24abd4fd 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -104,9 +104,6 @@ namespace StardewModdingAPI.Framework /// The keys that just entered the up state. public Keys[] FrameReleasedKeys => this.PreviouslyPressedKeys.Except(this.CurrentlyPressedKeys).ToArray(); - /// Whether a save is currently loaded at last check. - public bool PreviouslyLoadedGame { get; private set; } - /// A hash of at last check. public int PreviousGameLocations { get; private set; } @@ -416,7 +413,7 @@ namespace StardewModdingAPI.Framework if (SGame._newDayTask != null) { this.GraphicsDevice.Clear(this.bgColor); - base.Draw(gameTime); + //base.Draw(gameTime); } else if (this.IsSaving) { @@ -425,10 +422,20 @@ namespace StardewModdingAPI.Framework if (activeClickableMenu != null) { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - activeClickableMenu.draw(Game1.spriteBatch); + try + { + GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor); + activeClickableMenu.draw(Game1.spriteBatch); + GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); + } + catch (Exception ex) + { + this.Monitor.Log($"The {activeClickableMenu.GetType().FullName} menu crashed while drawing itself during save. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); + activeClickableMenu.exitThisMenu(); + } Game1.spriteBatch.End(); } - base.Draw(gameTime); + //base.Draw(gameTime); } else { @@ -438,8 +445,18 @@ namespace StardewModdingAPI.Framework if (Game1.activeClickableMenu != null && Game1.options.showMenuBackground && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet()) { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - Game1.activeClickableMenu.drawBackground(Game1.spriteBatch); - Game1.activeClickableMenu.draw(Game1.spriteBatch); + try + { + Game1.activeClickableMenu.drawBackground(Game1.spriteBatch); + GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor); + Game1.activeClickableMenu.draw(Game1.spriteBatch); + GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); + } + catch (Exception ex) + { + this.Monitor.Log($"The {Game1.activeClickableMenu.GetType().FullName} menu crashed while drawing itself. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); + Game1.activeClickableMenu.exitThisMenu(); + } Game1.spriteBatch.End(); if ((double)Game1.options.zoomLevel != 1.0) { @@ -490,7 +507,19 @@ namespace StardewModdingAPI.Framework { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); if (Game1.activeClickableMenu != null) - Game1.activeClickableMenu.draw(Game1.spriteBatch); + { + 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) { @@ -562,6 +591,7 @@ namespace StardewModdingAPI.Framework 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); @@ -807,7 +837,7 @@ namespace StardewModdingAPI.Framework 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); + //base.Draw(gameTime); Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); if (Game1.eventUp && Game1.currentLocation.currentEvent != null) { @@ -895,10 +925,14 @@ namespace StardewModdingAPI.Framework } if (Game1.currentBillboard != 0) this.drawBillboard(); - if ((Game1.displayHUD || Game1.eventUp) && (Game1.currentBillboard == 0 && (int)Game1.gameMode == 3) && (!Game1.freezeControls && !Game1.panMode)) + 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); + 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) @@ -1019,7 +1053,19 @@ namespace StardewModdingAPI.Framework 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) - Game1.activeClickableMenu.draw(Game1.spriteBatch); + { + 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(); @@ -1035,6 +1081,7 @@ namespace StardewModdingAPI.Framework this.GraphicsDevice.Clear(this.bgColor); Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, 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); + GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor); Game1.spriteBatch.End(); } } -- cgit From 2b7abc3af5d9fb636123b734cd60dbd38448abd2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 7 Feb 2017 23:34:52 -0500 Subject: clean up more obsolete code (#231) --- src/StardewModdingAPI/Framework/ModHelper.cs | 134 ++++++++++++++++++++++++ src/StardewModdingAPI/LogInfo.cs | 44 -------- src/StardewModdingAPI/ModHelper.cs | 135 ------------------------- src/StardewModdingAPI/StardewModdingAPI.csproj | 3 +- 4 files changed, 135 insertions(+), 181 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/ModHelper.cs delete mode 100644 src/StardewModdingAPI/LogInfo.cs delete mode 100644 src/StardewModdingAPI/ModHelper.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/ModHelper.cs b/src/StardewModdingAPI/Framework/ModHelper.cs new file mode 100644 index 00000000..2b562b4f --- /dev/null +++ b/src/StardewModdingAPI/Framework/ModHelper.cs @@ -0,0 +1,134 @@ +using System; +using System.IO; +using Newtonsoft.Json; +using StardewModdingAPI.Advanced; +using StardewModdingAPI.Framework.Reflection; + +namespace StardewModdingAPI.Framework +{ + /// Provides simplified APIs for writing mods. + internal class ModHelper : IModHelper + { + /********* + ** Properties + *********/ + /// The JSON settings to use when serialising and deserialising files. + private readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + ObjectCreationHandling = ObjectCreationHandling.Replace // avoid issue where default ICollection values are duplicated each time the config is loaded + }; + + + /********* + ** Accessors + *********/ + /// The mod directory path. + public string DirectoryPath { get; } + + /// Simplifies access to private game code. + public IReflectionHelper Reflection { get; } = new ReflectionHelper(); + + /// Metadata about loaded mods. + public IModRegistry ModRegistry { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The mod directory path. + /// Metadata about loaded mods. + /// An argument is null or invalid. + /// The path does not exist on disk. + public ModHelper(string modDirectory, IModRegistry modRegistry) + { + // validate + if (modRegistry == null) + throw new ArgumentException("The mod registry cannot be null."); + if (string.IsNullOrWhiteSpace(modDirectory)) + throw new ArgumentException("The mod directory cannot be empty."); + if (!Directory.Exists(modDirectory)) + throw new InvalidOperationException("The specified mod directory does not exist."); + + // initialise + this.DirectoryPath = modDirectory; + this.ModRegistry = modRegistry; + } + + /**** + ** Mod config file + ****/ + /// Read the mod's configuration file (and create it if needed). + /// The config class type. This should be a plain class that has public properties for the settings you want. These can be complex types. + public TConfig ReadConfig() + where TConfig : class, new() + { + var config = this.ReadJsonFile("config.json") ?? new TConfig(); + this.WriteConfig(config); // create file or fill in missing fields + return config; + } + + /// Save to the mod's configuration file. + /// The config class type. + /// The config settings to save. + public void WriteConfig(TConfig config) + where TConfig : class, new() + { + this.WriteJsonFile("config.json", config); + } + + /**** + ** Generic JSON files + ****/ + /// Read a JSON file. + /// The model type. + /// The file path relative to the mod directory. + /// Returns the deserialised model, or null if the file doesn't exist or is empty. + public TModel ReadJsonFile(string path) + where TModel : class + { + // read file + string fullPath = Path.Combine(this.DirectoryPath, path); + string json; + try + { + json = File.ReadAllText(fullPath); + } + catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException) + { + return null; + } + + // deserialise model + TModel model = JsonConvert.DeserializeObject(json, this.JsonSettings); + if (model is IConfigFile) + { + var wrapper = (IConfigFile)model; + wrapper.ModHelper = this; + wrapper.FilePath = path; + } + + return model; + } + + /// Save to a JSON file. + /// The model type. + /// The file path relative to the mod directory. + /// The model to save. + public void WriteJsonFile(string path, TModel model) + where TModel : class + { + path = Path.Combine(this.DirectoryPath, path); + + // create directory if needed + string dir = Path.GetDirectoryName(path); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + // write file + string json = JsonConvert.SerializeObject(model, this.JsonSettings); + File.WriteAllText(path, json); + } + } +} diff --git a/src/StardewModdingAPI/LogInfo.cs b/src/StardewModdingAPI/LogInfo.cs deleted file mode 100644 index ffef7cef..00000000 --- a/src/StardewModdingAPI/LogInfo.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; - -namespace StardewModdingAPI -{ - /// A message queued for log output. - public struct LogInfo - { - /********* - ** Accessors - *********/ - /// The message to log. - public string Message { get; set; } - - /// The log date. - public string LogDate { get; set; } - - /// The log time. - public string LogTime { get; set; } - - /// The message color. - public ConsoleColor Colour { get; set; } - - /// Whether the message should be printed to the console. - internal bool PrintConsole { get; set; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The message to log. - /// The message color. - public LogInfo(string message, ConsoleColor color = ConsoleColor.Gray) - { - if (string.IsNullOrEmpty(message)) - message = "[null]"; - this.Message = message; - this.LogDate = DateTime.Now.ToString("yyyy-MM-dd"); - this.LogTime = DateTime.Now.ToString("HH:mm:ss"); - this.Colour = color; - this.PrintConsole = true; - } - } -} diff --git a/src/StardewModdingAPI/ModHelper.cs b/src/StardewModdingAPI/ModHelper.cs deleted file mode 100644 index c20130cf..00000000 --- a/src/StardewModdingAPI/ModHelper.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.IO; -using Newtonsoft.Json; -using StardewModdingAPI.Advanced; -using StardewModdingAPI.Framework.Reflection; - -namespace StardewModdingAPI -{ - /// Provides simplified APIs for writing mods. - [Obsolete("Use " + nameof(IModHelper) + " instead.")] // only direct mod access to this class is obsolete - public class ModHelper : IModHelper - { - /********* - ** Properties - *********/ - /// The JSON settings to use when serialising and deserialising files. - private readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings - { - Formatting = Formatting.Indented, - ObjectCreationHandling = ObjectCreationHandling.Replace // avoid issue where default ICollection values are duplicated each time the config is loaded - }; - - - /********* - ** Accessors - *********/ - /// The mod directory path. - public string DirectoryPath { get; } - - /// Simplifies access to private game code. - public IReflectionHelper Reflection { get; } = new ReflectionHelper(); - - /// Metadata about loaded mods. - public IModRegistry ModRegistry { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The mod directory path. - /// Metadata about loaded mods. - /// An argument is null or invalid. - /// The path does not exist on disk. - public ModHelper(string modDirectory, IModRegistry modRegistry) - { - // validate - if (modRegistry == null) - throw new ArgumentException("The mod registry cannot be null."); - if (string.IsNullOrWhiteSpace(modDirectory)) - throw new ArgumentException("The mod directory cannot be empty."); - if (!Directory.Exists(modDirectory)) - throw new InvalidOperationException("The specified mod directory does not exist."); - - // initialise - this.DirectoryPath = modDirectory; - this.ModRegistry = modRegistry; - } - - /**** - ** Mod config file - ****/ - /// Read the mod's configuration file (and create it if needed). - /// The config class type. This should be a plain class that has public properties for the settings you want. These can be complex types. - public TConfig ReadConfig() - where TConfig : class, new() - { - var config = this.ReadJsonFile("config.json") ?? new TConfig(); - this.WriteConfig(config); // create file or fill in missing fields - return config; - } - - /// Save to the mod's configuration file. - /// The config class type. - /// The config settings to save. - public void WriteConfig(TConfig config) - where TConfig : class, new() - { - this.WriteJsonFile("config.json", config); - } - - /**** - ** Generic JSON files - ****/ - /// Read a JSON file. - /// The model type. - /// The file path relative to the mod directory. - /// Returns the deserialised model, or null if the file doesn't exist or is empty. - public TModel ReadJsonFile(string path) - where TModel : class - { - // read file - string fullPath = Path.Combine(this.DirectoryPath, path); - string json; - try - { - json = File.ReadAllText(fullPath); - } - catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException) - { - return null; - } - - // deserialise model - TModel model = JsonConvert.DeserializeObject(json, this.JsonSettings); - if (model is IConfigFile) - { - var wrapper = (IConfigFile)model; - wrapper.ModHelper = this; - wrapper.FilePath = path; - } - - return model; - } - - /// Save to a JSON file. - /// The model type. - /// The file path relative to the mod directory. - /// The model to save. - public void WriteJsonFile(string path, TModel model) - where TModel : class - { - path = Path.Combine(this.DirectoryPath, path); - - // create directory if needed - string dir = Path.GetDirectoryName(path); - if (!Directory.Exists(dir)) - Directory.CreateDirectory(dir); - - // write file - string json = JsonConvert.SerializeObject(model, this.JsonSettings); - File.WriteAllText(path, json); - } - } -} diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index cecfc831..d148e0c8 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -183,10 +183,9 @@ - - + -- cgit From 366769e8a27e8de39842c82907cf40451c488a2c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Feb 2017 00:11:46 -0500 Subject: add SaveEvents.AfterReturnToTitle event (#231) --- release-notes.md | 5 +++-- src/StardewModdingAPI/Events/SaveEvents.cs | 10 ++++++++++ src/StardewModdingAPI/Framework/SGame.cs | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index b67030e8..61b1d689 100644 --- a/release-notes.md +++ b/release-notes.md @@ -7,9 +7,10 @@ For players: * Updated for Stardew Valley 1.2. For mod developers: +* Added `SaveEvents.AfterReturnToTitle` event. * Added `GetPrivateProperty` to reflection helper. -* Many deprecated APIs have been removed; see [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod) - for more information. +* Many deprecated APIs have been removed; see the + [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod) for more information. ## 1.8 See [log](https://github.com/Pathoschild/SMAPI/compare/1.7...1.8). diff --git a/src/StardewModdingAPI/Events/SaveEvents.cs b/src/StardewModdingAPI/Events/SaveEvents.cs index 2921003a..50e6d729 100644 --- a/src/StardewModdingAPI/Events/SaveEvents.cs +++ b/src/StardewModdingAPI/Events/SaveEvents.cs @@ -18,6 +18,9 @@ namespace StardewModdingAPI.Events /// Raised after the player loads a save slot. public static event EventHandler AfterLoad; + /// Raised after the game returns to the title screen. + public static event EventHandler AfterReturnToTitle; + /********* ** Internal methods @@ -42,5 +45,12 @@ namespace StardewModdingAPI.Events { monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.AfterLoad)}", SaveEvents.AfterLoad?.GetInvocationList(), null, EventArgs.Empty); } + + /// Raise a event. + /// Encapsulates monitoring and logging. + internal static void InvokeAfterReturnToTitle(IMonitor monitor) + { + monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.AfterReturnToTitle)}", SaveEvents.AfterReturnToTitle?.GetInvocationList(), null, EventArgs.Empty); + } } } diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 24abd4fd..6a751b85 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -37,6 +37,9 @@ namespace StardewModdingAPI.Framework /// Whether the player has loaded a save and the world has finished initialising. private bool IsWorldReady => this.AfterLoadTimer < 0; + /// Whether the game is returning to the menu. + private bool IsExiting = false; + /// The debug messages to add to the next debug output. internal static Queue DebugMessageQueue { get; private set; } @@ -1160,6 +1163,19 @@ namespace StardewModdingAPI.Framework this.AfterLoadTimer--; } + // before exit to title + if (Game1.exitToTitle) + this.IsExiting = true; + + // after exit to title + if (this.IsWorldReady && this.IsExiting && Game1.activeClickableMenu is TitleMenu) + { + Console.WriteLine($"{Game1.currentGameTime.TotalGameTime}: after return to title"); + SaveEvents.InvokeAfterReturnToTitle(this.Monitor); + this.AfterLoadTimer = 5; + this.IsExiting = false; + } + // input events { // get latest state -- cgit From d52b3572f3d80c232c80de44adc93cc6cb015812 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Feb 2017 00:17:00 -0500 Subject: simplify log filename --- release-notes.md | 1 + src/StardewModdingAPI/Constants.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 61b1d689..9b6bd4fb 100644 --- a/release-notes.md +++ b/release-notes.md @@ -5,6 +5,7 @@ See [log](https://github.com/Pathoschild/SMAPI/compare/1.8...1.9). For players: * Updated for Stardew Valley 1.2. +* Simplified log filename. For mod developers: * Added `SaveEvents.AfterReturnToTitle` event. diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index d3c2ddcc..7b4d8f40 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -61,8 +61,8 @@ namespace StardewModdingAPI /// The directory path in which error logs should be stored. public static string LogDir => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs"); - /// The file path to the error log where the latest output should be saved. - public static string LogPath => Path.Combine(Constants.LogDir, "MODDED_ProgramLog.Log_LATEST.txt"); + /// The file path to the log where the latest output should be saved. + public static string LogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt"); /// The file path for the SMAPI configuration file. internal static string ApiConfigPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.config.json"); -- cgit From 6092f9ea00f63980231a646696671ae9db3fa958 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Feb 2017 00:22:27 -0500 Subject: always use \r\n line endings in log file for crossplatform compatibility (#230) --- release-notes.md | 1 + src/StardewModdingAPI/Framework/LogFileManager.cs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 9b6bd4fb..47902e22 100644 --- a/release-notes.md +++ b/release-notes.md @@ -12,6 +12,7 @@ For mod developers: * Added `GetPrivateProperty` to reflection helper. * Many deprecated APIs have been removed; see the [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod) for more information. +* Log files now always use `\r\n` to simplify crossplatform viewing. ## 1.8 See [log](https://github.com/Pathoschild/SMAPI/compare/1.7...1.8). diff --git a/src/StardewModdingAPI/Framework/LogFileManager.cs b/src/StardewModdingAPI/Framework/LogFileManager.cs index c2a2105b..80437e9c 100644 --- a/src/StardewModdingAPI/Framework/LogFileManager.cs +++ b/src/StardewModdingAPI/Framework/LogFileManager.cs @@ -34,7 +34,9 @@ namespace StardewModdingAPI.Framework /// The message to log. public void WriteLine(string message) { - this.Stream.WriteLine(message); + // always use Windows-style line endings for convenience + // (Linux/Mac editors are fine with them, Windows editors often require them) + this.Stream.Write(message + "\r\n"); } /// Release all resources. -- cgit From e440d9042e88b6bb10ceece6ed5eabed4d004331 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Feb 2017 00:26:32 -0500 Subject: bump minimum game version to 1.2 (#231) --- src/StardewModdingAPI/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 7b4d8f40..0965ffcd 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -29,7 +29,7 @@ namespace StardewModdingAPI public static ISemanticVersion ApiVersion => new SemanticVersion(1, 8, 0, null); /// The minimum supported version of Stardew Valley. - public const string MinimumGameVersion = "1.1"; + public const string MinimumGameVersion = "1.2"; /// The GitHub repository to check for updates. public const string GitHubRepository = "Pathoschild/SMAPI"; -- cgit From 036595cc712d20e7c0fb9a9a9444d5206a25ad7e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Feb 2017 00:53:53 -0500 Subject: remove old log file to avoid confusion (#231) --- src/StardewModdingAPI.Installer/InteractiveInstaller.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 551c648c..d6d395b6 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -85,6 +85,7 @@ namespace StardewModdingApi.Installer foreach (DirectoryInfo modDir in modsDir.EnumerateDirectories()) yield return Path.Combine(modDir.FullName, ".cache"); // 1.4–1.7 } + yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs", "MODDED_ProgramLog.Log_LATEST.txt"); // *–1.8 } /// Whether the current console supports color formatting. -- cgit From 40a90147420614d7d593b478fcf93b9be542c5b0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 9 Feb 2017 13:45:34 -0500 Subject: generalise CIL rewriters for reuse (#231) --- .../IInstructionRewriter.cs | 21 ++++++ .../IMethodRewriter.cs | 21 ------ .../Rewriters/BaseMethodRewriter.cs | 43 +++++++++--- .../Rewriters/SpriteBatchRewriter.cs | 80 +++++++++++++++++++--- .../StardewModdingAPI.AssemblyRewriters.csproj | 3 +- .../Wrappers/CompatibleSpriteBatch.cs | 52 -------------- src/StardewModdingAPI.sln.DotSettings | 1 + src/StardewModdingAPI/Constants.cs | 4 +- src/StardewModdingAPI/Framework/AssemblyLoader.cs | 71 ++++++++++--------- 9 files changed, 168 insertions(+), 128 deletions(-) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/IMethodRewriter.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Wrappers/CompatibleSpriteBatch.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs new file mode 100644 index 00000000..5c24acb6 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs @@ -0,0 +1,21 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace StardewModdingAPI.AssemblyRewriters +{ + /// Rewrites a CIL instruction for compatibility. + public interface IInstructionRewriter + { + /// Get whether a CIL instruction should be rewritten. + /// The IL instruction. + /// Whether the mod was compiled on a different platform. + bool ShouldRewrite(Instruction instruction, bool platformChanged); + + /// Rewrite a CIL instruction for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction to rewrite. + /// Metadata for mapping assemblies to the current platform. + void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap); + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/IMethodRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/IMethodRewriter.cs deleted file mode 100644 index 5cbb7e0d..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/IMethodRewriter.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace StardewModdingAPI.AssemblyRewriters -{ - /// Rewrites a method for compatibility. - public interface IMethodRewriter - { - /// Get whether the given method reference can be rewritten. - /// The method reference. - bool ShouldRewrite(MethodReference methodRef); - - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which calls the method. - /// The method reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction callOp, MethodReference methodRef, PlatformAssemblyMap assemblyMap); - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/BaseMethodRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/BaseMethodRewriter.cs index 1af6e6c4..e44acaf9 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/BaseMethodRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/BaseMethodRewriter.cs @@ -7,27 +7,52 @@ using Mono.Cecil.Cil; namespace StardewModdingAPI.AssemblyRewriters.Rewriters { /// Base class for a method rewriter. - public abstract class BaseMethodRewriter : IMethodRewriter + public abstract class BaseMethodRewriter : IInstructionRewriter { /********* ** Public methods *********/ - /// Get whether the given method reference can be rewritten. - /// The method reference. - public abstract bool ShouldRewrite(MethodReference methodRef); + /// Get whether a CIL instruction should be rewritten. + /// The IL instruction. + /// Whether the mod was compiled on a different platform. + public bool ShouldRewrite(Instruction instruction, bool platformChanged) + { + // ignore non-method-call instructions + if (instruction.OpCode != OpCodes.Call && instruction.OpCode != OpCodes.Callvirt) + return false; - /// Rewrite a method for compatibility. + // check reference + MethodReference methodRef = (MethodReference)instruction.Operand; + return this.ShouldRewrite(methodRef, platformChanged); + } + + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. - /// The instruction which calls the method. - /// The method reference invoked by the . + /// The instruction to rewrite. /// Metadata for mapping assemblies to the current platform. - public abstract void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction callOp, MethodReference methodRef, PlatformAssemblyMap assemblyMap); - + public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) + { + MethodReference methodRef = (MethodReference)instruction.Operand; + this.Rewrite(module, cil, instruction, methodRef, assemblyMap); + } /********* ** Protected methods *********/ + /// Get whether the given method reference can be rewritten. + /// The method reference. + /// Whether the mod was compiled on a different platform. + protected abstract bool ShouldRewrite(MethodReference methodRef, bool platformChanged); + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which calls the method. + /// The method reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected abstract void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap); + /// Get whether a method definition matches the signature expected by a method reference. /// The method definition. /// The method reference. diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs index 1c0a5cf3..f64bf768 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs @@ -1,30 +1,94 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Mono.Cecil; using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Wrappers; namespace StardewModdingAPI.AssemblyRewriters.Rewriters { /// Rewrites references to to fix inconsistent method signatures between MonoGame and XNA. - /// MonoGame has one SpriteBatch.Begin method with optional arguments, but XNA has multiple method overloads. Incompatible method references are rewritten to use , which redirects all method signatures to the proper compiled MonoGame/XNA method. + /// MonoGame has one SpriteBatch.Begin method with optional arguments, but XNA has multiple method overloads. Incompatible method references are rewritten to use , which redirects all method signatures to the proper compiled MonoGame/XNA method. public class SpriteBatchRewriter : BaseMethodRewriter { + /********* + ** Protected methods + *********/ /// Get whether the given method reference can be rewritten. /// The method reference. - public override bool ShouldRewrite(MethodReference methodRef) + /// Whether the mod was compiled on a different platform. + protected override bool ShouldRewrite(MethodReference methodRef, bool platformChanged) { - return methodRef.DeclaringType.FullName == typeof(SpriteBatch).FullName && this.HasMatchingSignature(typeof(CompatibleSpriteBatch), methodRef); + return platformChanged + && methodRef.DeclaringType.FullName == typeof(SpriteBatch).FullName + && this.HasMatchingSignature(typeof(SpriteBatchRewriter.WrapperMethods), methodRef); } /// Rewrite a method for compatibility. /// The module being rewritten. /// The CIL rewriter. - /// The instruction which calls the method. - /// The method reference invoked by the . + /// The instruction which calls the method. + /// The method reference invoked by the . /// Metadata for mapping assemblies to the current platform. - public override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction callOp, MethodReference methodRef, PlatformAssemblyMap assemblyMap) + protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap) { - methodRef.DeclaringType = module.Import(typeof(CompatibleSpriteBatch)); + methodRef.DeclaringType = module.Import(typeof(SpriteBatchRewriter.WrapperMethods)); + } + + + /********* + ** Wrapper methods + *********/ + /// Wraps methods that are incompatible when converting compiled code between MonoGame and XNA. + public class WrapperMethods : SpriteBatch + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public WrapperMethods(GraphicsDevice graphicsDevice) : base(graphicsDevice) { } + + + /**** + ** MonoGame signatures + ****/ + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/Mac.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity); + } + + /**** + ** XNA signatures + ****/ + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin() + { + base.Begin(); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState) + { + base.Begin(sortMode, blendState); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix transformMatrix) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix); + } } } } \ No newline at end of file diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 1e6caacc..01ca1d66 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -70,13 +70,12 @@ Properties\GlobalAssemblyInfo.cs - + - diff --git a/src/StardewModdingAPI.AssemblyRewriters/Wrappers/CompatibleSpriteBatch.cs b/src/StardewModdingAPI.AssemblyRewriters/Wrappers/CompatibleSpriteBatch.cs deleted file mode 100644 index e28d1a68..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Wrappers/CompatibleSpriteBatch.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; - -#pragma warning disable CS0109 // Member does not hide an inherited member; new keyword is not required -namespace StardewModdingAPI.AssemblyRewriters.Wrappers -{ - /// Wraps methods that are incompatible when converting compiled code between MonoGame and XNA. - public class CompatibleSpriteBatch : SpriteBatch - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public CompatibleSpriteBatch(GraphicsDevice graphicsDevice) : base(graphicsDevice) { } - - /**** - ** MonoGame signatures - ****/ - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity); - } - - /**** - ** XNA signatures - ****/ - public new void Begin() - { - base.Begin(); - } - - public new void Begin(SpriteSortMode sortMode, BlendState blendState) - { - base.Begin(sortMode, blendState); - } - - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState); - } - - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect); - } - - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix transformMatrix) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix); - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI.sln.DotSettings b/src/StardewModdingAPI.sln.DotSettings index 7ee9b76e..81b52fd4 100644 --- a/src/StardewModdingAPI.sln.DotSettings +++ b/src/StardewModdingAPI.sln.DotSettings @@ -1,4 +1,5 @@  + HINT Field, Property, Event, Method Field, Property, Event, Method True diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 0965ffcd..f6afa95f 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -120,8 +120,8 @@ namespace StardewModdingAPI return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences, targetAssemblies); } - /// Get method rewriters which fix incompatible method calls in mod assemblies. - internal static IEnumerable GetMethodRewriters() + /// Get rewriters which fix incompatible CIL instructions in mod assemblies. + internal static IEnumerable GetRewriters() { return new[] { diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index 123211b9..d5e8f5ee 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -72,10 +72,10 @@ namespace StardewModdingAPI.Framework Assembly lastAssembly = null; foreach (AssemblyParseResult assembly in assemblies) { - this.Monitor.Log($"Loading {assembly.File.Name}...", LogLevel.Trace); bool changed = this.RewriteAssembly(assembly.Definition); if (changed) { + this.Monitor.Log($"Loading {assembly.File.Name} (rewritten in memory)...", LogLevel.Trace); using (MemoryStream outStream = new MemoryStream()) { assembly.Definition.Write(outStream); @@ -84,7 +84,10 @@ namespace StardewModdingAPI.Framework } } else + { + this.Monitor.Log($"Loading {assembly.File.Name}...", LogLevel.Trace); lastAssembly = Assembly.UnsafeLoadFrom(assembly.File.FullName); + } } // last assembly loaded is the root @@ -155,59 +158,59 @@ namespace StardewModdingAPI.Framework /// Returns whether the assembly was modified. private bool RewriteAssembly(AssemblyDefinition assembly) { - ModuleDefinition module = assembly.Modules.Single(); // technically an assembly can have multiple modules, but none of the build tools (including MSBuild) support it; simplify by assuming one module + ModuleDefinition module = assembly.MainModule; - // remove old assembly references - bool shouldRewrite = false; + // swap assembly references if needed (e.g. XNA => MonoGame) + bool platformChanged = false; for (int i = 0; i < module.AssemblyReferences.Count; i++) { + // remove old assembly reference if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name)) { - shouldRewrite = true; + platformChanged = true; module.AssemblyReferences.RemoveAt(i); i--; } } - if (!shouldRewrite) - return false; - - // add target assembly references - foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values) - module.AssemblyReferences.Add(target); - - // rewrite type scopes to use target assemblies - IEnumerable typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName); - foreach (TypeReference type in typeReferences) - this.ChangeTypeScope(type); + if (platformChanged) + { + // add target assembly references + foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values) + module.AssemblyReferences.Add(target); + + // rewrite type scopes to use target assemblies + IEnumerable typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName); + foreach (TypeReference type in typeReferences) + this.ChangeTypeScope(type); + } - // rewrite incompatible methods - IMethodRewriter[] methodRewriters = Constants.GetMethodRewriters().ToArray(); + // rewrite incompatible instructions + bool anyRewritten = false; + IInstructionRewriter[] rewriters = Constants.GetRewriters().ToArray(); foreach (MethodDefinition method in this.GetMethods(module)) { - // skip methods with no rewritable method - bool hasMethodToRewrite = method.Body.Instructions.Any(op => (op.OpCode == OpCodes.Call || op.OpCode == OpCodes.Callvirt) && methodRewriters.Any(rewriter => rewriter.ShouldRewrite((MethodReference)op.Operand))); - if (!hasMethodToRewrite) + // skip methods with no rewritable instructions + bool canRewrite = method.Body.Instructions.Any(op => rewriters.Any(rewriter => rewriter.ShouldRewrite(op, platformChanged))); + if (!canRewrite) continue; - // rewrite method references + // prepare method method.Body.SimplifyMacros(); ILProcessor cil = method.Body.GetILProcessor(); - Instruction[] instructions = cil.Body.Instructions.ToArray(); - foreach (Instruction op in instructions) + + // rewrite instructions + foreach (Instruction op in cil.Body.Instructions.ToArray()) { - if (op.OpCode == OpCodes.Call || op.OpCode == OpCodes.Callvirt) - { - IMethodRewriter rewriter = methodRewriters.FirstOrDefault(p => p.ShouldRewrite((MethodReference)op.Operand)); - if (rewriter != null) - { - MethodReference methodRef = (MethodReference)op.Operand; - rewriter.Rewrite(module, cil, op, methodRef, this.AssemblyMap); - } - } + IInstructionRewriter rewriter = rewriters.FirstOrDefault(p => p.ShouldRewrite(op, platformChanged)); + rewriter?.Rewrite(module, cil, op, this.AssemblyMap); } + + // finalise method method.Body.OptimizeMacros(); + anyRewritten = true; } - return true; + + return platformChanged || anyRewritten; } /// Get the correct reference to use for compatibility with the current platform. -- cgit From 74a56a7b3b3bde30fbcb711eaef977ad69601e03 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 9 Feb 2017 14:02:43 -0500 Subject: further generalise CIL rewriters for reuse (#231) --- .../Framework/BaseMethodRewriter.cs | 89 ++++++++++++++++ .../Framework/RewriteHelper.cs | 41 ++++++++ .../Rewriters/BaseMethodRewriter.cs | 117 --------------------- .../Rewriters/SpriteBatchRewriter.cs | 3 +- .../StardewModdingAPI.AssemblyRewriters.csproj | 3 +- 5 files changed, 134 insertions(+), 119 deletions(-) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/RewriteHelper.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/BaseMethodRewriter.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs new file mode 100644 index 00000000..c9729ee8 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs @@ -0,0 +1,89 @@ +using System; +using System.Linq; +using System.Reflection; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace StardewModdingAPI.AssemblyRewriters.Framework +{ + /// Base class for a method rewriter. + public abstract class BaseMethodRewriter : IInstructionRewriter + { + /********* + ** Public methods + *********/ + /// Get whether a CIL instruction should be rewritten. + /// The IL instruction. + /// Whether the mod was compiled on a different platform. + public bool ShouldRewrite(Instruction instruction, bool platformChanged) + { + // ignore non-method-call instructions + if (instruction.OpCode != OpCodes.Call && instruction.OpCode != OpCodes.Callvirt) + return false; + + // check reference + MethodReference methodRef = (MethodReference)instruction.Operand; + return this.ShouldRewrite(methodRef, platformChanged); + } + + /// Rewrite a CIL instruction for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction to rewrite. + /// Metadata for mapping assemblies to the current platform. + public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) + { + MethodReference methodRef = (MethodReference)instruction.Operand; + this.Rewrite(module, cil, instruction, methodRef, assemblyMap); + } + + + /********* + ** Protected methods + *********/ + /// Get whether a method reference should be rewritten. + /// The method reference. + /// Whether the mod was compiled on a different platform. + protected abstract bool ShouldRewrite(MethodReference methodRef, bool platformChanged); + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which calls the method. + /// The method reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected abstract void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap); + + /// Get whether a method definition matches the signature expected by a method reference. + /// The method definition. + /// The method reference. + protected bool HasMatchingSignature(MethodInfo definition, MethodReference reference) + { + // same name + if (definition.Name != reference.Name) + return false; + + // same arguments + ParameterInfo[] definitionParameters = definition.GetParameters(); + ParameterDefinition[] referenceParameters = reference.Parameters.ToArray(); + if (referenceParameters.Length != definitionParameters.Length) + return false; + for (int i = 0; i < referenceParameters.Length; i++) + { + if (!RewriteHelper.IsMatchingType(definitionParameters[i].ParameterType, referenceParameters[i].ParameterType)) + return false; + } + return true; + } + + /// Get whether a type has a method whose signature matches the one expected by a method reference. + /// The type to check. + /// The method reference. + protected bool HasMatchingSignature(Type type, MethodReference reference) + { + return type + .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public) + .Any(method => this.HasMatchingSignature(method, reference)); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/RewriteHelper.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/RewriteHelper.cs new file mode 100644 index 00000000..0307053f --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/RewriteHelper.cs @@ -0,0 +1,41 @@ +using System; +using Mono.Cecil; + +namespace StardewModdingAPI.AssemblyRewriters.Framework +{ + /// Provides helper methods for field rewriters. + internal static class RewriteHelper + { + /********* + ** Public methods + *********/ + /// Get whether a type matches a type reference. + /// The defined type. + /// The type reference. + public static bool IsMatchingType(Type type, TypeReference reference) + { + // same namespace & name + if (type.Namespace != reference.Namespace || type.Name != reference.Name) + return false; + + // same generic parameters + if (type.IsGenericType) + { + if (!reference.IsGenericInstance) + return false; + + Type[] defGenerics = type.GetGenericArguments(); + TypeReference[] refGenerics = ((GenericInstanceType)reference).GenericArguments.ToArray(); + if (defGenerics.Length != refGenerics.Length) + return false; + for (int i = 0; i < defGenerics.Length; i++) + { + if (!RewriteHelper.IsMatchingType(defGenerics[i], refGenerics[i])) + return false; + } + } + + return true; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/BaseMethodRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/BaseMethodRewriter.cs deleted file mode 100644 index e44acaf9..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/BaseMethodRewriter.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace StardewModdingAPI.AssemblyRewriters.Rewriters -{ - /// Base class for a method rewriter. - public abstract class BaseMethodRewriter : IInstructionRewriter - { - /********* - ** Public methods - *********/ - /// Get whether a CIL instruction should be rewritten. - /// The IL instruction. - /// Whether the mod was compiled on a different platform. - public bool ShouldRewrite(Instruction instruction, bool platformChanged) - { - // ignore non-method-call instructions - if (instruction.OpCode != OpCodes.Call && instruction.OpCode != OpCodes.Callvirt) - return false; - - // check reference - MethodReference methodRef = (MethodReference)instruction.Operand; - return this.ShouldRewrite(methodRef, platformChanged); - } - - /// Rewrite a CIL instruction for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction to rewrite. - /// Metadata for mapping assemblies to the current platform. - public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) - { - MethodReference methodRef = (MethodReference)instruction.Operand; - this.Rewrite(module, cil, instruction, methodRef, assemblyMap); - } - - /********* - ** Protected methods - *********/ - /// Get whether the given method reference can be rewritten. - /// The method reference. - /// Whether the mod was compiled on a different platform. - protected abstract bool ShouldRewrite(MethodReference methodRef, bool platformChanged); - - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which calls the method. - /// The method reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - protected abstract void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap); - - /// Get whether a method definition matches the signature expected by a method reference. - /// The method definition. - /// The method reference. - protected bool HasMatchingSignature(MethodInfo definition, MethodReference reference) - { - // same name - if (definition.Name != reference.Name) - return false; - - // same arguments - ParameterInfo[] definitionParameters = definition.GetParameters(); - ParameterDefinition[] referenceParameters = reference.Parameters.ToArray(); - if (referenceParameters.Length != definitionParameters.Length) - return false; - for (int i = 0; i < referenceParameters.Length; i++) - { - if (!this.IsMatchingType(definitionParameters[i].ParameterType, referenceParameters[i].ParameterType)) - return false; - } - return true; - } - - /// Get whether a type has a method whose signature matches the one expected by a method reference. - /// The type to check. - /// The method reference. - protected bool HasMatchingSignature(Type type, MethodReference reference) - { - return type - .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public) - .Any(method => this.HasMatchingSignature(method, reference)); - } - - /// Get whether a type matches a type reference. - /// The defined type. - /// The type reference. - private bool IsMatchingType(Type type, TypeReference reference) - { - // same namespace & name - if (type.Namespace != reference.Namespace || type.Name != reference.Name) - return false; - - // same generic parameters - if (type.IsGenericType) - { - if (!reference.IsGenericInstance) - return false; - - Type[] defGenerics = type.GetGenericArguments(); - TypeReference[] refGenerics = ((GenericInstanceType)reference).GenericArguments.ToArray(); - if (defGenerics.Length != refGenerics.Length) - return false; - for (int i = 0; i < defGenerics.Length; i++) - { - if (!this.IsMatchingType(defGenerics[i], refGenerics[i])) - return false; - } - } - - return true; - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs index f64bf768..819578e0 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs @@ -3,6 +3,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Mono.Cecil; using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; namespace StardewModdingAPI.AssemblyRewriters.Rewriters { @@ -13,7 +14,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /********* ** Protected methods *********/ - /// Get whether the given method reference can be rewritten. + /// Get whether a method reference should be rewritten. /// The method reference. /// Whether the mod was compiled on a different platform. protected override bool ShouldRewrite(MethodReference methodRef, bool platformChanged) diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 01ca1d66..0b549a86 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -74,7 +74,8 @@ - + + -- cgit From 5f9c03a8a93d51202e737d1d32c21e4a2e55ff08 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 9 Feb 2017 14:28:44 -0500 Subject: add field rewriter for the `Game1.activeClickableMenu` change in SDV 1.2 (#231) --- .../Framework/BaseFieldRewriter.cs | 51 ++++++++++++++++++++++ .../Framework/BaseMethodRewriter.cs | 11 ++--- .../Rewriters/ActiveClickableMenuRewriter.cs | 40 +++++++++++++++++ .../Rewriters/SpriteBatchRewriter.cs | 3 +- .../StardewModdingAPI.AssemblyRewriters.csproj | 2 + 5 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/ActiveClickableMenuRewriter.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs new file mode 100644 index 00000000..7e01ca73 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs @@ -0,0 +1,51 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace StardewModdingAPI.AssemblyRewriters.Framework +{ + /// Base class for a field rewriter. + public abstract class BaseFieldRewriter : IInstructionRewriter + { + /********* + ** Public methods + *********/ + /// Get whether a CIL instruction should be rewritten. + /// The IL instruction. + /// Whether the mod was compiled on a different platform. + public bool ShouldRewrite(Instruction instruction, bool platformChanged) + { + if (instruction.OpCode != OpCodes.Ldfld && instruction.OpCode != OpCodes.Ldsfld && instruction.OpCode != OpCodes.Stfld && instruction.OpCode != OpCodes.Stsfld) + return false; // not a field reference + return this.ShouldRewrite(instruction, (FieldReference)instruction.Operand, platformChanged); + } + + /// Rewrite a CIL instruction for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction to rewrite. + /// Metadata for mapping assemblies to the current platform. + public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) + { + FieldReference fieldRef = (FieldReference)instruction.Operand; + this.Rewrite(module, cil, instruction, fieldRef, assemblyMap); + } + + + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected abstract bool ShouldRewrite(Instruction instruction, FieldReference fieldRef, bool platformChanged); + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which references the field. + /// The field reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected abstract void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap); + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs index c9729ee8..e53e5c56 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs @@ -17,13 +17,9 @@ namespace StardewModdingAPI.AssemblyRewriters.Framework /// Whether the mod was compiled on a different platform. public bool ShouldRewrite(Instruction instruction, bool platformChanged) { - // ignore non-method-call instructions if (instruction.OpCode != OpCodes.Call && instruction.OpCode != OpCodes.Callvirt) - return false; - - // check reference - MethodReference methodRef = (MethodReference)instruction.Operand; - return this.ShouldRewrite(methodRef, platformChanged); + return false; // not a method reference + return this.ShouldRewrite(instruction, (MethodReference)instruction.Operand, platformChanged); } /// Rewrite a CIL instruction for compatibility. @@ -42,9 +38,10 @@ namespace StardewModdingAPI.AssemblyRewriters.Framework ** Protected methods *********/ /// Get whether a method reference should be rewritten. + /// The IL instruction. /// The method reference. /// Whether the mod was compiled on a different platform. - protected abstract bool ShouldRewrite(MethodReference methodRef, bool platformChanged); + protected abstract bool ShouldRewrite(Instruction instruction, MethodReference methodRef, bool platformChanged); /// Rewrite a method for compatibility. /// The module being rewritten. diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/ActiveClickableMenuRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/ActiveClickableMenuRewriter.cs new file mode 100644 index 00000000..b8aef019 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/ActiveClickableMenuRewriter.cs @@ -0,0 +1,40 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; +using StardewValley; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters +{ + /// Rewrites field references to . + /// Stardew Valley changed the field to a property, which broke many mods that reference it. + public class ActiveClickableMenuRewriter : BaseFieldRewriter + { + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected override bool ShouldRewrite(Instruction instruction, FieldReference fieldRef, bool platformChanged) + { + return + (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) // static field + && fieldRef.DeclaringType.FullName == typeof(Game1).FullName + && fieldRef.Name == nameof(Game1.activeClickableMenu); + } + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which references the field. + /// The field reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) + { + string methodPrefix = instruction.OpCode == OpCodes.Ldsfld ? "get" : "set"; + MethodReference propertyRef = module.Import(typeof(Game1).GetMethod($"{methodPrefix}_{nameof(Game1.activeClickableMenu)}")); + cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs index 819578e0..109a6a6e 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs @@ -15,9 +15,10 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters ** Protected methods *********/ /// Get whether a method reference should be rewritten. + /// The IL instruction. /// The method reference. /// Whether the mod was compiled on a different platform. - protected override bool ShouldRewrite(MethodReference methodRef, bool platformChanged) + protected override bool ShouldRewrite(Instruction instruction, MethodReference methodRef, bool platformChanged) { return platformChanged && methodRef.DeclaringType.FullName == typeof(SpriteBatch).FullName diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 0b549a86..4433131b 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -74,8 +74,10 @@ + + -- cgit From 3668b0902d077166c78be882bcea1d38f759fe81 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 9 Feb 2017 20:34:34 -0500 Subject: fix field rewriter not added to config (#231) --- src/StardewModdingAPI/Constants.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index f6afa95f..57704def 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -123,9 +123,10 @@ namespace StardewModdingAPI /// Get rewriters which fix incompatible CIL instructions in mod assemblies. internal static IEnumerable GetRewriters() { - return new[] + return new IInstructionRewriter[] { - new SpriteBatchRewriter() + new SpriteBatchRewriter(), + new ActiveClickableMenuRewriter() }; } -- cgit From a13003de8b8601ac693d7af960fab67d285dbd0e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 9 Feb 2017 22:38:10 -0500 Subject: remove Mono.Cecil.Rocks (#231) It's not needed since we're not injecting new instructions, and causes the field rewriters to fail unexpectedly. --- README.md | 2 -- src/StardewModdingAPI.Installer/InteractiveInstaller.cs | 6 +++--- src/StardewModdingAPI/Framework/AssemblyLoader.cs | 3 --- src/StardewModdingAPI/StardewModdingAPI.csproj | 4 ---- src/prepare-install-package.targets | 2 -- 5 files changed, 3 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/README.md b/README.md index b1062077..63adcf78 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,6 @@ folder containing `src`). Mono/ Mods/* Mono.Cecil.dll - Mono.Cecil.Rocks.dll Newtonsoft.Json.dll StardewModdingAPI StardewModdingAPI.AssemblyRewriters.dll @@ -95,7 +94,6 @@ folder containing `src`). Windows/ Mods/* Mono.Cecil.dll - Mono.Cecil.Rocks.dll Newtonsoft.Json.dll StardewModdingAPI.AssemblyRewriters.dll StardewModdingAPI.config.json diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index d6d395b6..868889fa 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -59,6 +59,8 @@ namespace StardewModdingApi.Installer Func installPath = 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"); @@ -66,9 +68,6 @@ namespace StardewModdingApi.Installer yield return installPath("steam_appid.txt"); // Linux/Mac only - yield return installPath("Mono.Cecil.dll"); - yield return installPath("Mono.Cecil.Rocks.dll"); - yield return installPath("Newtonsoft.Json.dll"); yield return installPath("StardewModdingAPI"); yield return installPath("StardewModdingAPI.exe.mdb"); yield return installPath("System.Numerics.dll"); @@ -79,6 +78,7 @@ namespace StardewModdingApi.Installer // 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 if (modsDir.Exists) { diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index d5e8f5ee..0cf6e569 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; -using Mono.Cecil.Rocks; using StardewModdingAPI.AssemblyRewriters; namespace StardewModdingAPI.Framework @@ -195,7 +194,6 @@ namespace StardewModdingAPI.Framework continue; // prepare method - method.Body.SimplifyMacros(); ILProcessor cil = method.Body.GetILProcessor(); // rewrite instructions @@ -206,7 +204,6 @@ namespace StardewModdingAPI.Framework } // finalise method - method.Body.OptimizeMacros(); anyRewritten = true; } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index d148e0c8..eca2713f 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -90,10 +90,6 @@ ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll True - - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll - True - ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True diff --git a/src/prepare-install-package.targets b/src/prepare-install-package.targets index f411b909..bd9287f1 100644 --- a/src/prepare-install-package.targets +++ b/src/prepare-install-package.targets @@ -23,7 +23,6 @@ - @@ -38,7 +37,6 @@ - -- cgit From 388ef0a01205387c5ae56f0cdbe8a86a1febc3de Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 9 Feb 2017 22:56:42 -0500 Subject: reorganise rewriters (#231) --- .../Rewriters/ActiveClickableMenuRewriter.cs | 40 --------- .../Crossplatform/SpriteBatch_MethodRewriter.cs | 97 ++++++++++++++++++++++ .../Game1_ActiveClickableMenu_FieldRewriter.cs | 42 ++++++++++ .../Rewriters/SpriteBatchRewriter.cs | 96 --------------------- .../StardewModdingAPI.AssemblyRewriters.csproj | 4 +- src/StardewModdingAPI/Constants.cs | 10 ++- 6 files changed, 148 insertions(+), 141 deletions(-) delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/ActiveClickableMenuRewriter.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/ActiveClickableMenuRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/ActiveClickableMenuRewriter.cs deleted file mode 100644 index b8aef019..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/ActiveClickableMenuRewriter.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; -using StardewValley; - -namespace StardewModdingAPI.AssemblyRewriters.Rewriters -{ - /// Rewrites field references to . - /// Stardew Valley changed the field to a property, which broke many mods that reference it. - public class ActiveClickableMenuRewriter : BaseFieldRewriter - { - /********* - ** Protected methods - *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected override bool ShouldRewrite(Instruction instruction, FieldReference fieldRef, bool platformChanged) - { - return - (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) // static field - && fieldRef.DeclaringType.FullName == typeof(Game1).FullName - && fieldRef.Name == nameof(Game1.activeClickableMenu); - } - - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which references the field. - /// The field reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) - { - string methodPrefix = instruction.OpCode == OpCodes.Ldsfld ? "get" : "set"; - MethodReference propertyRef = module.Import(typeof(Game1).GetMethod($"{methodPrefix}_{nameof(Game1.activeClickableMenu)}")); - cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs new file mode 100644 index 00000000..252fe6d0 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs @@ -0,0 +1,97 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters.Crossplatform +{ + /// Rewrites references to to fix inconsistent method signatures between MonoGame and XNA. + /// MonoGame has one SpriteBatch.Begin method with optional arguments, but XNA has multiple method overloads. Incompatible method references are rewritten to use , which redirects all method signatures to the proper compiled MonoGame/XNA method. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] + public class SpriteBatch_MethodRewriter : BaseMethodRewriter + { + /********* + ** Protected methods + *********/ + /// Get whether a method reference should be rewritten. + /// The IL instruction. + /// The method reference. + /// Whether the mod was compiled on a different platform. + protected override bool ShouldRewrite(Instruction instruction, MethodReference methodRef, bool platformChanged) + { + return platformChanged + && methodRef.DeclaringType.FullName == typeof(SpriteBatch).FullName + && this.HasMatchingSignature(typeof(SpriteBatch_MethodRewriter.WrapperMethods), methodRef); + } + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which calls the method. + /// The method reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap) + { + methodRef.DeclaringType = module.Import(typeof(SpriteBatch_MethodRewriter.WrapperMethods)); + } + + + /********* + ** Wrapper methods + *********/ + /// Wraps methods that are incompatible when converting compiled code between MonoGame and XNA. + public class WrapperMethods : SpriteBatch + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public WrapperMethods(GraphicsDevice graphicsDevice) : base(graphicsDevice) { } + + + /**** + ** MonoGame signatures + ****/ + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/Mac.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity); + } + + /**** + ** XNA signatures + ****/ + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin() + { + base.Begin(); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState) + { + base.Begin(sortMode, blendState); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix transformMatrix) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix); + } + } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs new file mode 100644 index 00000000..36634b28 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.CodeAnalysis; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; +using StardewValley; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 +{ + /// Rewrites field references to . + /// Stardew Valley changed the field to a property, which broke many mods that reference it. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] + public class Game1_ActiveClickableMenu_FieldRewriter : BaseFieldRewriter + { + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected override bool ShouldRewrite(Instruction instruction, FieldReference fieldRef, bool platformChanged) + { + return + (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) // static field + && fieldRef.DeclaringType.FullName == typeof(Game1).FullName + && fieldRef.Name == nameof(Game1.activeClickableMenu); + } + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which references the field. + /// The field reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) + { + string methodPrefix = instruction.OpCode == OpCodes.Ldsfld ? "get" : "set"; + MethodReference propertyRef = module.Import(typeof(Game1).GetMethod($"{methodPrefix}_{nameof(Game1.activeClickableMenu)}")); + cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs deleted file mode 100644 index 109a6a6e..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SpriteBatchRewriter.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; - -namespace StardewModdingAPI.AssemblyRewriters.Rewriters -{ - /// Rewrites references to to fix inconsistent method signatures between MonoGame and XNA. - /// MonoGame has one SpriteBatch.Begin method with optional arguments, but XNA has multiple method overloads. Incompatible method references are rewritten to use , which redirects all method signatures to the proper compiled MonoGame/XNA method. - public class SpriteBatchRewriter : BaseMethodRewriter - { - /********* - ** Protected methods - *********/ - /// Get whether a method reference should be rewritten. - /// The IL instruction. - /// The method reference. - /// Whether the mod was compiled on a different platform. - protected override bool ShouldRewrite(Instruction instruction, MethodReference methodRef, bool platformChanged) - { - return platformChanged - && methodRef.DeclaringType.FullName == typeof(SpriteBatch).FullName - && this.HasMatchingSignature(typeof(SpriteBatchRewriter.WrapperMethods), methodRef); - } - - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which calls the method. - /// The method reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap) - { - methodRef.DeclaringType = module.Import(typeof(SpriteBatchRewriter.WrapperMethods)); - } - - - /********* - ** Wrapper methods - *********/ - /// Wraps methods that are incompatible when converting compiled code between MonoGame and XNA. - public class WrapperMethods : SpriteBatch - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public WrapperMethods(GraphicsDevice graphicsDevice) : base(graphicsDevice) { } - - - /**** - ** MonoGame signatures - ****/ - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/Mac.")] - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity); - } - - /**** - ** XNA signatures - ****/ - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] - public new void Begin() - { - base.Begin(); - } - - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] - public new void Begin(SpriteSortMode sortMode, BlendState blendState) - { - base.Begin(sortMode, blendState); - } - - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState); - } - - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect); - } - - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix transformMatrix) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix); - } - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 4433131b..a58dc176 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -77,8 +77,8 @@ - - + + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 57704def..cee33560 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -4,7 +4,8 @@ using System.IO; using System.Linq; using System.Reflection; using StardewModdingAPI.AssemblyRewriters; -using StardewModdingAPI.AssemblyRewriters.Rewriters; +using StardewModdingAPI.AssemblyRewriters.Rewriters.Crossplatform; +using StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2; using StardewValley; namespace StardewModdingAPI @@ -125,8 +126,11 @@ namespace StardewModdingAPI { return new IInstructionRewriter[] { - new SpriteBatchRewriter(), - new ActiveClickableMenuRewriter() + // crossplatform + new SpriteBatch_MethodRewriter(), + + // Stardew Valley 1.2 + new Game1_ActiveClickableMenu_FieldRewriter() }; } -- cgit From 2b336faa1bcdf7c2abc1cffb72c1def70fa6e8a7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 9 Feb 2017 23:03:36 -0500 Subject: add field rewriter for the `Game1.player` change in SDV 1.2 (#231) --- .../Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs | 42 ++++++++++++++++++++++ .../StardewModdingAPI.AssemblyRewriters.csproj | 1 + src/StardewModdingAPI/Constants.cs | 3 +- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs new file mode 100644 index 00000000..fb87c19a --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.CodeAnalysis; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; +using StardewValley; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 +{ + /// Rewrites field references to . + /// Stardew Valley changed the field to a property, which broke many mods that reference it. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] + public class Game1_Player_FieldRewriter : BaseFieldRewriter + { + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected override bool ShouldRewrite(Instruction instruction, FieldReference fieldRef, bool platformChanged) + { + return + (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) // static field + && fieldRef.DeclaringType.FullName == typeof(Game1).FullName + && fieldRef.Name == nameof(Game1.player); + } + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which references the field. + /// The field reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) + { + string methodPrefix = instruction.OpCode == OpCodes.Ldsfld ? "get" : "set"; + MethodReference propertyRef = module.Import(typeof(Game1).GetMethod($"{methodPrefix}_{nameof(Game1.player)}")); + cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index a58dc176..2383fb0b 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -77,6 +77,7 @@ + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index cee33560..74387ee3 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -130,7 +130,8 @@ namespace StardewModdingAPI new SpriteBatch_MethodRewriter(), // Stardew Valley 1.2 - new Game1_ActiveClickableMenu_FieldRewriter() + new Game1_ActiveClickableMenu_FieldRewriter(), + new Game1_Player_FieldRewriter() }; } -- cgit From 25a3d9773c7e91903ced41e4bafd164ae42a9644 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 9 Feb 2017 23:24:54 -0500 Subject: add field rewriter for the `Game1.gameMode` change in SDV 1.2 (#231) --- .../SDV1_2/Game1_GameMode_FieldRewriter.cs | 42 ++++++++++++++++++++++ .../StardewModdingAPI.AssemblyRewriters.csproj | 1 + src/StardewModdingAPI/Constants.cs | 1 + 3 files changed, 44 insertions(+) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs new file mode 100644 index 00000000..e8cf3d13 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.CodeAnalysis; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; +using StardewValley; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 +{ + /// Rewrites field references to . + /// Stardew Valley changed the field to a property, which broke many mods that reference it. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] + public class Game1_GameMode_FieldRewriter : BaseFieldRewriter + { + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected override bool ShouldRewrite(Instruction instruction, FieldReference fieldRef, bool platformChanged) + { + return + (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) // static field + && fieldRef.DeclaringType.FullName == typeof(Game1).FullName + && fieldRef.Name == nameof(Game1.gameMode); + } + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which references the field. + /// The field reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) + { + string methodPrefix = instruction.OpCode == OpCodes.Ldsfld ? "get" : "set"; + MethodReference propertyRef = module.Import(typeof(Game1).GetMethod($"{methodPrefix}_{nameof(Game1.gameMode)}")); + cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 2383fb0b..94501220 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -77,6 +77,7 @@ + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 74387ee3..c71401b1 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -131,6 +131,7 @@ namespace StardewModdingAPI // Stardew Valley 1.2 new Game1_ActiveClickableMenu_FieldRewriter(), + new Game1_GameMode_FieldRewriter(), new Game1_Player_FieldRewriter() }; } -- cgit From 56d28ef0d4072d6b3b3dce0b2c1f6cb24408bf95 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 9 Feb 2017 23:27:08 -0500 Subject: make some constants internal (#231) --- src/StardewModdingAPI/Constants.cs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index c71401b1..438c869c 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -26,24 +26,21 @@ namespace StardewModdingAPI /********* ** Accessors *********/ + /**** + ** Public + ****/ /// SMAPI's current semantic version. public static ISemanticVersion ApiVersion => new SemanticVersion(1, 8, 0, null); /// The minimum supported version of Stardew Valley. public const string MinimumGameVersion = "1.2"; - /// The GitHub repository to check for updates. - public const string GitHubRepository = "Pathoschild/SMAPI"; - /// The directory path containing Stardew Valley's app data. public static string DataPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley"); /// The directory path where all saves are stored. public static string SavesPath => Path.Combine(Constants.DataPath, "Saves"); - /// Whether the directory containing the current save's data exists on disk. - public static bool CurrentSavePathExists => Directory.Exists(Constants.RawSavePath); - /// The directory name containing the current save's data (if a save is loaded and the directory exists). public static string SaveFolderName => Constants.CurrentSavePathExists ? Constants.RawSaveFolderName : ""; @@ -56,14 +53,17 @@ namespace StardewModdingAPI /// The path to the current assembly being executing. public static string ExecutionPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - /// The title of the SMAPI console window. - public static string ConsoleTitle => $"Stardew Modding API Console - Version {Constants.ApiVersion} - Mods Loaded: {Program.ModsLoaded}"; - /// The directory path in which error logs should be stored. public static string LogDir => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs"); - /// The file path to the log where the latest output should be saved. - public static string LogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt"); + /**** + ** Internal + ****/ + /// The GitHub repository to check for updates. + internal const string GitHubRepository = "Pathoschild/SMAPI"; + + /// The title of the SMAPI console window. + internal static string ConsoleTitle => $"Stardew Modding API Console - Version {Constants.ApiVersion} - Mods Loaded: {Program.ModsLoaded}"; /// The file path for the SMAPI configuration file. internal static string ApiConfigPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.config.json"); @@ -71,6 +71,12 @@ namespace StardewModdingAPI /// The file path for the SMAPI data file containing metadata about known mods. internal static string ApiModMetadataPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.data.json"); + /// The file path to the log where the latest output should be saved. + internal static string LogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt"); + + /// Whether the directory containing the current save's data exists on disk. + internal static bool CurrentSavePathExists => Directory.Exists(Constants.RawSavePath); + /********* ** Protected methods -- cgit From a6977878d59d12668dbccae635f9dfbb7d759547 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Feb 2017 02:55:27 -0500 Subject: remove leftover references to Mono.Cecil.Rocks (#231) --- .../StardewModdingAPI.AssemblyRewriters.csproj | 4 ---- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 - 2 files changed, 5 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 94501220..d5394d67 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -60,10 +60,6 @@ ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll True - - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll - True - diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index eca2713f..54cf0565 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -257,6 +257,5 @@ - \ No newline at end of file -- cgit From 8f678d13c1bc26d3b90af1a4d17589f43439a669 Mon Sep 17 00:00:00 2001 From: James Stine Date: Fri, 10 Feb 2017 03:25:47 -0500 Subject: Mac and Linux debug run works! 🙃 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/StardewModdingAPI/StardewModdingAPI.csproj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 54cf0565..5d119b73 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -241,12 +241,14 @@ - - + + Program $(GamePath)\StardewModdingAPI.exe $(GamePath) + + -- cgit From c357013156b0e50d05427ccada855042bdd546d7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Feb 2017 19:22:22 -0500 Subject: tweak debug build config, update release notes --- release-notes.md | 4 ++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 47902e22..e6becdbd 100644 --- a/release-notes.md +++ b/release-notes.md @@ -6,6 +6,7 @@ See [log](https://github.com/Pathoschild/SMAPI/compare/1.8...1.9). For players: * Updated for Stardew Valley 1.2. * Simplified log filename. +* SMAPI now rewrites most mods for Stardew Valley 1.2 compatibility. For mod developers: * Added `SaveEvents.AfterReturnToTitle` event. @@ -14,6 +15,9 @@ For mod developers: [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod) for more information. * Log files now always use `\r\n` to simplify crossplatform viewing. +For SMAPI developers: +* Added support for debugging with Visual Studio for Mac. + ## 1.8 See [log](https://github.com/Pathoschild/SMAPI/compare/1.7...1.8). diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 5d119b73..896721ea 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -241,14 +241,8 @@ + - - Program - $(GamePath)\StardewModdingAPI.exe - $(GamePath) - - - @@ -260,4 +254,15 @@ + + + + Program + $(GamePath)\StardewModdingAPI.exe + $(GamePath) + + + + + \ No newline at end of file -- cgit From 3e91af6b06deec6aa2dca80945c82af528094c52 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Feb 2017 22:52:16 -0500 Subject: mark several mods incompatible with Stardew Valley 1.2+ (#231) --- src/StardewModdingAPI/Program.cs | 2 +- src/StardewModdingAPI/StardewModdingAPI.data.json | 60 ++++++++++++++++------- 2 files changed, 44 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index c0a05e2d..75be23f2 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -385,7 +385,7 @@ namespace StardewModdingAPI // validate known incompatible mods IncompatibleMod compatibility; - if (incompatibleMods.TryGetValue(manifest.UniqueID ?? $"{manifest.Name}|{manifest.Author}|{manifest.EntryDll}", out compatibility)) + if (incompatibleMods.TryGetValue(!string.IsNullOrWhiteSpace(manifest.UniqueID) ? manifest.UniqueID : manifest.EntryDll, out compatibility)) { if (!compatibility.IsCompatible(manifest.Version)) { diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json index 3295336f..3c9be222 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -6,45 +6,71 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. */ [ - /* versions not compatible with Stardew Valley 1.1+ */ + /* versions which crash the game */ { - "ID": "SPDSprinklersMod", - "Name": "Better Sprinklers", - "UpperVersion": "2.1", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/41", - "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", - "ForceCompatibleVersion": "^2.1-EntoPatch" + "Name": "NPC Map Locations", + "ID": "NPCMapLocationsMod", + "LowerVersion": "1.42", + "UpperVersion": "1.43", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/239", + "ReasonPhrase": "this version has an update check error which crashes the game" }, + + /* versions not compatible with Stardew Valley 1.1+ */ { - "ID": "SPDChestLabel", "Name": "Chest Label System", + "ID": "SPDChestLabel", "UpperVersion": "1.5", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/242", "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", "ForceCompatibleVersion": "^1.5-EntoPatch" }, { - "ID": "CJBCheatsMenu", "Name": "CJB Cheats Menu", + "ID": "CJBCheatsMenu", "UpperVersion": "1.12", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4", "ForceCompatibleVersion": "^1.12-EntoPatch" }, { - "ID": "CJBItemSpawner", "Name": "CJB Item Spawner", + "ID": "CJBItemSpawner", "UpperVersion": "1.5", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93", "ForceCompatibleVersion": "^1.5-EntoPatch" }, - /* versions which crash the game */ + /* versions not compatible with Stardew Valley 1.2+ */ { - "ID": "NPCMapLocationsMod", - "Name": "NPC Map Locations", - "LowerVersion": "1.42", - "UpperVersion": "1.43", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/239", - "ReasonPhrase": "this version has an update check error which crashes the game" + "Name": "Better Sprinklers", + "ID": "SPDSprinklersMod", + "UpperVersion": "2.1", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/41", + "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", + "ForceCompatibleVersion": "^2.1-EntoPatch.7" + }, + { + "Name": "Casks Anywhere", + "ID": "CasksAnywhere", + "UpperVersion": "1.1", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/878" + }, + { + "Name": "Entoarox Framework", + "ID": "eacdb74b-4080-4452-b16b-93773cda5cf9", + "UpperVersion": "1.6.1", + "UpdateUrl": "http://community.playstarbound.com/resources/4228" + }, + { + "Name": "Get Dressed", + "ID": "GetDressed.dll", + "UpperVersion": "3.2", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/331" + }, + { + "Name": "NoSoilDecay", + "ID": "289dee03-5f38-4d8e-8ffc-e440198e8610", + "UpperVersion": "0.5", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/237" } ] -- cgit From 46b7d7a4001e209d7201ed5cad38cad2f1ad2a7f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Feb 2017 01:15:56 -0500 Subject: redirect the game's debug messages into trace logs (#233) The game writes debug messages directly to the console, which shows up for SMAPI users. This commit redirects direct console messages to a monitor. --- release-notes.md | 6 +- src/StardewModdingAPI/Framework/LogFileManager.cs | 48 ------------ .../Logging/ConsoleInterceptionManager.cs | 86 ++++++++++++++++++++++ .../Framework/Logging/InterceptingTextWriter.cs | 79 ++++++++++++++++++++ .../Framework/Logging/LogFileManager.cs | 48 ++++++++++++ src/StardewModdingAPI/Framework/Monitor.cs | 56 ++++++-------- src/StardewModdingAPI/Framework/SGame.cs | 1 - src/StardewModdingAPI/Program.cs | 20 +++-- src/StardewModdingAPI/StardewModdingAPI.csproj | 4 +- 9 files changed, 257 insertions(+), 91 deletions(-) delete mode 100644 src/StardewModdingAPI/Framework/LogFileManager.cs create mode 100644 src/StardewModdingAPI/Framework/Logging/ConsoleInterceptionManager.cs create mode 100644 src/StardewModdingAPI/Framework/Logging/InterceptingTextWriter.cs create mode 100644 src/StardewModdingAPI/Framework/Logging/LogFileManager.cs (limited to 'src') diff --git a/release-notes.md b/release-notes.md index e6becdbd..e3e8752b 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,13 +4,13 @@ See [log](https://github.com/Pathoschild/SMAPI/compare/1.8...1.9). For players: -* Updated for Stardew Valley 1.2. +* Updated for Stardew Valley 1.2. Most mods are now rewritten for compatibility; some mods require an update from their authors. * Simplified log filename. -* SMAPI now rewrites most mods for Stardew Valley 1.2 compatibility. +* Fixed game's debug output being shown in the console for all users. For mod developers: * Added `SaveEvents.AfterReturnToTitle` event. -* Added `GetPrivateProperty` to reflection helper. +* Added `GetPrivateProperty` reflection helper. * Many deprecated APIs have been removed; see the [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod) for more information. * Log files now always use `\r\n` to simplify crossplatform viewing. diff --git a/src/StardewModdingAPI/Framework/LogFileManager.cs b/src/StardewModdingAPI/Framework/LogFileManager.cs deleted file mode 100644 index 80437e9c..00000000 --- a/src/StardewModdingAPI/Framework/LogFileManager.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.IO; - -namespace StardewModdingAPI.Framework -{ - /// Manages reading and writing to log file. - internal class LogFileManager : IDisposable - { - /********* - ** Properties - *********/ - /// The underlying stream writer. - private readonly StreamWriter Stream; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The log file to write. - public LogFileManager(string path) - { - // create log directory if needed - string logDir = Path.GetDirectoryName(path); - if (logDir == null) - throw new ArgumentException($"The log path '{path}' is not valid."); - Directory.CreateDirectory(logDir); - - // open log file stream - this.Stream = new StreamWriter(path, append: false) { AutoFlush = true }; - } - - /// Write a message to the log. - /// The message to log. - public void WriteLine(string message) - { - // always use Windows-style line endings for convenience - // (Linux/Mac editors are fine with them, Windows editors often require them) - this.Stream.Write(message + "\r\n"); - } - - /// Release all resources. - public void Dispose() - { - this.Stream.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/Logging/ConsoleInterceptionManager.cs b/src/StardewModdingAPI/Framework/Logging/ConsoleInterceptionManager.cs new file mode 100644 index 00000000..d84671ee --- /dev/null +++ b/src/StardewModdingAPI/Framework/Logging/ConsoleInterceptionManager.cs @@ -0,0 +1,86 @@ +using System; + +namespace StardewModdingAPI.Framework.Logging +{ + /// Manages console output interception. + internal class ConsoleInterceptionManager : IDisposable + { + /********* + ** Properties + *********/ + /// The intercepting console writer. + private readonly InterceptingTextWriter Output; + + + /********* + ** Accessors + *********/ + /// Whether the current console supports color formatting. + public bool SupportsColor { get; } + + /// The event raised when something writes a line to the console directly. + public event Action OnLineIntercepted; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public ConsoleInterceptionManager() + { + // redirect output through interceptor + this.Output = new InterceptingTextWriter(Console.Out); + this.Output.OnLineIntercepted += line => this.OnLineIntercepted?.Invoke(line); + Console.SetOut(this.Output); + + // test color support + this.SupportsColor = this.TestColorSupport(); + } + + /// Get an exclusive lock and write to the console output without interception. + /// The action to perform within the exclusive write block. + public void ExclusiveWriteWithoutInterception(Action action) + { + lock (Console.Out) + { + try + { + this.Output.ShouldIntercept = false; + action(); + } + finally + { + this.Output.ShouldIntercept = true; + } + } + } + + /// Release all resources. + public void Dispose() + { + Console.SetOut(this.Output.Out); + this.Output.Dispose(); + } + + + /********* + ** private methods + *********/ + /// Test whether the current console supports color formatting. + private bool TestColorSupport() + { + try + { + this.ExclusiveWriteWithoutInterception(() => + { + Console.ForegroundColor = Console.ForegroundColor; + }); + return true; + } + catch (Exception) + { + return false; // Mono bug + } + } + } +} diff --git a/src/StardewModdingAPI/Framework/Logging/InterceptingTextWriter.cs b/src/StardewModdingAPI/Framework/Logging/InterceptingTextWriter.cs new file mode 100644 index 00000000..14789109 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Logging/InterceptingTextWriter.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace StardewModdingAPI.Framework.Logging +{ + /// A text writer which allows intercepting output. + internal class InterceptingTextWriter : TextWriter + { + /********* + ** Properties + *********/ + /// The current line being intercepted. + private readonly List Line = new List(); + + + /********* + ** Accessors + *********/ + /// The underlying console output. + public TextWriter Out { get; } + + /// The character encoding in which the output is written. + public override Encoding Encoding => this.Out.Encoding; + + /// Whether to intercept console output. + public bool ShouldIntercept { get; set; } + + /// The event raised when a line of text is intercepted. + public event Action OnLineIntercepted; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The underlying output writer. + public InterceptingTextWriter(TextWriter output) + { + this.Out = output; + } + + /// Writes a character to the text string or stream. + /// The character to write to the text stream. + public override void Write(char ch) + { + // intercept + if (this.ShouldIntercept) + { + switch (ch) + { + case '\r': + return; + + case '\n': + this.OnLineIntercepted?.Invoke(new string(this.Line.ToArray())); + this.Line.Clear(); + break; + + default: + this.Line.Add(ch); + break; + } + } + + // pass through + else + this.Out.Write(ch); + } + + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + this.OnLineIntercepted = null; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Logging/LogFileManager.cs b/src/StardewModdingAPI/Framework/Logging/LogFileManager.cs new file mode 100644 index 00000000..1f6ade1d --- /dev/null +++ b/src/StardewModdingAPI/Framework/Logging/LogFileManager.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; + +namespace StardewModdingAPI.Framework.Logging +{ + /// Manages reading and writing to log file. + internal class LogFileManager : IDisposable + { + /********* + ** Properties + *********/ + /// The underlying stream writer. + private readonly StreamWriter Stream; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The log file to write. + public LogFileManager(string path) + { + // create log directory if needed + string logDir = Path.GetDirectoryName(path); + if (logDir == null) + throw new ArgumentException($"The log path '{path}' is not valid."); + Directory.CreateDirectory(logDir); + + // open log file stream + this.Stream = new StreamWriter(path, append: false) { AutoFlush = true }; + } + + /// Write a message to the log. + /// The message to log. + public void WriteLine(string message) + { + // always use Windows-style line endings for convenience + // (Linux/Mac editors are fine with them, Windows editors often require them) + this.Stream.Write(message + "\r\n"); + } + + /// Release all resources. + public void Dispose() + { + this.Stream.Dispose(); + } + } +} diff --git a/src/StardewModdingAPI/Framework/Monitor.cs b/src/StardewModdingAPI/Framework/Monitor.cs index 39b567d8..33c1bbf4 100644 --- a/src/StardewModdingAPI/Framework/Monitor.cs +++ b/src/StardewModdingAPI/Framework/Monitor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using StardewModdingAPI.Framework.Logging; namespace StardewModdingAPI.Framework { @@ -13,6 +14,9 @@ namespace StardewModdingAPI.Framework /// The name of the module which logs messages using this instance. private readonly string Source; + /// Manages access to the console output. + private readonly ConsoleInterceptionManager ConsoleManager; + /// The log file to which to write messages. private readonly LogFileManager LogFile; @@ -34,9 +38,6 @@ namespace StardewModdingAPI.Framework /********* ** Accessors *********/ - /// Whether the current console supports color codes. - internal static readonly bool ConsoleSupportsColor = Monitor.GetConsoleSupportsColor(); - /// Whether to show trace messages in the console. internal bool ShowTraceInConsole { get; set; } @@ -49,8 +50,9 @@ namespace StardewModdingAPI.Framework *********/ /// Construct an instance. /// The name of the module which logs messages using this instance. + /// Manages access to the console output. /// The log file to which to write messages. - public Monitor(string source, LogFileManager logFile) + public Monitor(string source, ConsoleInterceptionManager consoleManager, LogFileManager logFile) { // validate if (string.IsNullOrWhiteSpace(source)) @@ -61,6 +63,7 @@ namespace StardewModdingAPI.Framework // initialise this.Source = source; this.LogFile = logFile; + this.ConsoleManager = consoleManager; } /// Log a message for the player or developer. @@ -68,7 +71,7 @@ namespace StardewModdingAPI.Framework /// The log severity level. public void Log(string message, LogLevel level = LogLevel.Debug) { - this.LogImpl(this.Source, message, Monitor.Colors[level], level); + this.LogImpl(this.Source, message, level, Monitor.Colors[level]); } /// 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. @@ -83,8 +86,7 @@ namespace StardewModdingAPI.Framework /// The message to log. internal void LogFatal(string message) { - Console.BackgroundColor = ConsoleColor.Red; - this.LogImpl(this.Source, message, ConsoleColor.White, LogLevel.Error); + this.LogImpl(this.Source, message, LogLevel.Error, ConsoleColor.White, background: ConsoleColor.Red); } /// Log a message for the player or developer, using the specified console color. @@ -95,7 +97,7 @@ namespace StardewModdingAPI.Framework [Obsolete("This method is provided for backwards compatibility and otherwise should not be used. Use " + nameof(Monitor) + "." + nameof(Monitor.Log) + " instead.")] internal void LegacyLog(string source, string message, ConsoleColor color, LogLevel level = LogLevel.Debug) { - this.LogImpl(source, message, color, level); + this.LogImpl(source, message, level, color); } @@ -105,9 +107,10 @@ namespace StardewModdingAPI.Framework /// Write a message line to the log. /// The name of the mod logging the message. /// The message to log. - /// The console color. /// The log level. - private void LogImpl(string source, string message, ConsoleColor color, LogLevel level) + /// The console foreground color. + /// The console background color (or null to leave it as-is). + private void LogImpl(string source, string message, LogLevel level, ConsoleColor color, ConsoleColor? background = null) { // generate message string levelStr = level.ToString().ToUpper().PadRight(Monitor.MaxLevelLength); @@ -116,30 +119,19 @@ namespace StardewModdingAPI.Framework // log if (this.WriteToConsole && (this.ShowTraceInConsole || level != LogLevel.Trace)) { - if (Monitor.ConsoleSupportsColor) + this.ConsoleManager.ExclusiveWriteWithoutInterception(() => { - Console.ForegroundColor = color; - Console.WriteLine(message); - Console.ResetColor(); - } - else - Console.WriteLine(message); + if (this.ConsoleManager.SupportsColor) + { + Console.ForegroundColor = color; + Console.WriteLine(message); + Console.ResetColor(); + } + else + Console.WriteLine(message); + }); } this.LogFile.WriteLine(message); } - - /// Test whether the current console supports color formatting. - private static bool GetConsoleSupportsColor() - { - try - { - Console.ForegroundColor = Console.ForegroundColor; - return true; - } - catch (Exception) - { - return false; // Mono bug - } - } } -} \ No newline at end of file +} diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 6a751b85..9e269df3 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -1170,7 +1170,6 @@ namespace StardewModdingAPI.Framework // after exit to title if (this.IsWorldReady && this.IsExiting && Game1.activeClickableMenu is TitleMenu) { - Console.WriteLine($"{Game1.currentGameTime.TotalGameTime}: after return to title"); SaveEvents.InvokeAfterReturnToTitle(this.Monitor); this.AfterLoadTimer = 5; this.IsExiting = false; diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 75be23f2..a6302540 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -13,6 +13,7 @@ using Newtonsoft.Json; using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.Events; using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Logging; using StardewModdingAPI.Framework.Models; using StardewValley; using Monitor = StardewModdingAPI.Framework.Monitor; @@ -42,8 +43,11 @@ namespace StardewModdingAPI /// The log file to which to write messages. private static readonly LogFileManager LogFile = new LogFileManager(Constants.LogPath); + /// Manages console output interception. + private static readonly ConsoleInterceptionManager ConsoleManager = new ConsoleInterceptionManager(); + /// The core logger for SMAPI. - private static readonly Monitor Monitor = new Monitor("SMAPI", Program.LogFile); + private static readonly Monitor Monitor = new Monitor("SMAPI", Program.ConsoleManager, Program.LogFile); /// The user settings for SMAPI. private static UserSettings Settings; @@ -87,14 +91,12 @@ namespace StardewModdingAPI /// The command-line arguments. private static void Main(string[] args) { - // set log options + // initialise logging Program.Monitor.WriteToConsole = !args.Contains("--no-terminal"); Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); // for consistent log formatting - - // add info header Program.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info); - // initialise user settings + // read config { string settingsPath = Constants.ApiConfigPath; if (File.Exists(settingsPath)) @@ -108,6 +110,12 @@ namespace StardewModdingAPI File.WriteAllText(settingsPath, JsonConvert.SerializeObject(Program.Settings, Formatting.Indented)); } + // redirect direct console output + { + IMonitor monitor = Program.GetSecondaryMonitor("Console.Out"); + Program.ConsoleManager.OnLineIntercepted += line => monitor.Log(line, LogLevel.Trace); + } + // add warning headers if (Program.Settings.DeveloperMode) { @@ -574,7 +582,7 @@ namespace StardewModdingAPI /// The name of the module which will log messages with this instance. private static Monitor GetSecondaryMonitor(string name) { - return new Monitor(name, Program.LogFile) { WriteToConsole = Program.Monitor.WriteToConsole, ShowTraceInConsole = Program.Settings.DeveloperMode }; + return new Monitor(name, Program.ConsoleManager, Program.LogFile) { WriteToConsole = Program.Monitor.WriteToConsole, ShowTraceInConsole = Program.Settings.DeveloperMode }; } } } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 896721ea..ae08012d 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -145,6 +145,8 @@ + + @@ -166,7 +168,7 @@ - + -- cgit From 824ca7174a2b6a46d8a4ea50cac41c78e56dc48f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Feb 2017 02:04:01 -0500 Subject: delve into mod folders that only contain another folder (#208) This fixes a common issue when users unpack mods into a nested folder (e.g. "SomeMod-1.0.0\SomeMod\manifest.json"), which previously wouldn't be recognised as a mod. SMAPI will not do this if the folder contains files or more than one folder, to prevent backup folders and the like from being loaded. --- release-notes.md | 1 + src/StardewModdingAPI/Program.cs | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index e3e8752b..72e3b3c5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -6,6 +6,7 @@ See [log](https://github.com/Pathoschild/SMAPI/compare/1.8...1.9). For players: * Updated for Stardew Valley 1.2. Most mods are now rewritten for compatibility; some mods require an update from their authors. * Simplified log filename. +* Simple nested mod folders are now recognised by SMAPI (e.g. `ModName-1.0\ModName\manifest.json`). * Fixed game's debug output being shown in the console for all users. For mod developers: diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index a6302540..91a39042 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -337,9 +337,12 @@ namespace StardewModdingAPI // load mod assemblies List deprecationWarnings = new List(); // queue up deprecation warnings to show after mod list - foreach (string directory in Directory.GetDirectories(Program.ModPath)) + foreach (string directoryPath in Directory.GetDirectories(Program.ModPath)) { - string directoryName = new DirectoryInfo(directory).Name; + // passthrough empty directories + DirectoryInfo directory = new DirectoryInfo(directoryPath); + while (!directory.GetFiles().Any() && directory.GetDirectories().Length == 1) + directory = directory.GetDirectories().First(); // check for cancellation if (Program.CancellationTokenSource.IsCancellationRequested) @@ -349,13 +352,13 @@ namespace StardewModdingAPI } // get helper - IModHelper helper = new ModHelper(directory, Program.ModRegistry); + IModHelper helper = new ModHelper(directory.FullName, Program.ModRegistry); // get manifest path - string manifestPath = Path.Combine(directory, "manifest.json"); + string manifestPath = Path.Combine(directory.FullName, "manifest.json"); if (!File.Exists(manifestPath)) { - Program.Monitor.Log($"Ignored folder \"{directoryName}\" which doesn't have a manifest.json.", LogLevel.Warn); + Program.Monitor.Log($"Ignored folder \"{directory.Name}\" which doesn't have a manifest.json.", LogLevel.Warn); continue; } string errorPrefix = $"Couldn't load mod for manifest '{manifestPath}'"; @@ -434,7 +437,7 @@ namespace StardewModdingAPI deprecationWarnings.Add(() => Program.DeprecationManager.Warn(manifest.Name, $"{nameof(Manifest)}.{nameof(Manifest.PerSaveConfigs)}", "1.0", DeprecationLevel.Info)); try { - string psDir = Path.Combine(directory, "psconfigs"); + string psDir = Path.Combine(directory.FullName, "psconfigs"); Directory.CreateDirectory(psDir); if (!Directory.Exists(psDir)) { @@ -450,7 +453,7 @@ namespace StardewModdingAPI } // validate mod path to simplify errors - string assemblyPath = Path.Combine(directory, manifest.EntryDll); + string assemblyPath = Path.Combine(directory.FullName, manifest.EntryDll); if (!File.Exists(assemblyPath)) { Program.Monitor.Log($"{errorPrefix}: the entry DLL '{manifest.EntryDll}' does not exist.", LogLevel.Error); @@ -501,7 +504,7 @@ namespace StardewModdingAPI mod.ModManifest = manifest; mod.Helper = helper; mod.Monitor = Program.GetSecondaryMonitor(manifest.Name); - mod.PathOnDisk = directory; + mod.PathOnDisk = directory.FullName; // track mod Program.ModRegistry.Add(mod); -- cgit From 693f16f99ec3492e3bfcd0071af1d9d5e519dcfa Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Feb 2017 02:08:21 -0500 Subject: don't write direct console output to log file (#233) Per discussion with mod developers. --- src/StardewModdingAPI/Framework/Monitor.cs | 10 ++++++++-- src/StardewModdingAPI/Program.cs | 6 ++++-- 2 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/Monitor.cs b/src/StardewModdingAPI/Framework/Monitor.cs index 33c1bbf4..3a9276a0 100644 --- a/src/StardewModdingAPI/Framework/Monitor.cs +++ b/src/StardewModdingAPI/Framework/Monitor.cs @@ -44,6 +44,9 @@ namespace StardewModdingAPI.Framework /// Whether to write anything to the console. This should be disabled if no console is available. internal bool WriteToConsole { get; set; } = true; + /// Whether to write anything to the log file. This should almost always be enabled. + internal bool WriteToFile { get; set; } = true; + /********* ** Public methods @@ -116,7 +119,7 @@ namespace StardewModdingAPI.Framework string levelStr = level.ToString().ToUpper().PadRight(Monitor.MaxLevelLength); message = $"[{DateTime.Now:HH:mm:ss} {levelStr} {source}] {message}"; - // log + // write to console if (this.WriteToConsole && (this.ShowTraceInConsole || level != LogLevel.Trace)) { this.ConsoleManager.ExclusiveWriteWithoutInterception(() => @@ -131,7 +134,10 @@ namespace StardewModdingAPI.Framework Console.WriteLine(message); }); } - this.LogFile.WriteLine(message); + + // write to log file + if (this.WriteToFile) + this.LogFile.WriteLine(message); } } } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 91a39042..f4518e21 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -112,8 +112,10 @@ namespace StardewModdingAPI // redirect direct console output { - IMonitor monitor = Program.GetSecondaryMonitor("Console.Out"); - Program.ConsoleManager.OnLineIntercepted += line => monitor.Log(line, LogLevel.Trace); + Monitor monitor = Program.GetSecondaryMonitor("Console.Out"); + monitor.WriteToFile = false; // not useful for troubleshooting mods per discussion + if (monitor.WriteToConsole) + Program.ConsoleManager.OnLineIntercepted += line => monitor.Log(line, LogLevel.Trace); } // add warning headers -- cgit From 1f332c5f4d522908ee89e07ea081359b17ad324c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Feb 2017 12:11:45 -0500 Subject: remove esoteric & undocumented debug message queue & DrawDebug event --- src/StardewModdingAPI/Events/GraphicsEvents.cs | 10 ------ src/StardewModdingAPI/Framework/SGame.cs | 43 ++------------------------ 2 files changed, 2 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Events/GraphicsEvents.cs b/src/StardewModdingAPI/Events/GraphicsEvents.cs index 03dabb85..d9d57a8d 100644 --- a/src/StardewModdingAPI/Events/GraphicsEvents.cs +++ b/src/StardewModdingAPI/Events/GraphicsEvents.cs @@ -15,9 +15,6 @@ namespace StardewModdingAPI.Events /// Raised after the game window is resized. public static event EventHandler Resize; - /// Raised when drawing debug information to the screen (when is true). This is called after the sprite batch is begun. If you just want to add debug info, use in your update loop. - public static event EventHandler DrawDebug; - /**** ** Main render events ****/ @@ -61,13 +58,6 @@ namespace StardewModdingAPI.Events monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.Resize)}", GraphicsEvents.Resize?.GetInvocationList(), sender, e); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeDrawDebug(IMonitor monitor) - { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.DrawDebug)}", GraphicsEvents.DrawDebug?.GetInvocationList()); - } - /**** ** Main render events ****/ diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 9e269df3..55e7ff1a 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -40,9 +40,6 @@ namespace StardewModdingAPI.Framework /// Whether the game is returning to the menu. private bool IsExiting = false; - /// The debug messages to add to the next debug output. - internal static Queue DebugMessageQueue { get; private set; } - /// Whether the game's zoom level is at 100% (i.e. nothing should be scaled). public bool ZoomLevelIsOne => Game1.options.zoomLevel.Equals(1.0f); @@ -267,20 +264,6 @@ namespace StardewModdingAPI.Framework return buttons.ToArray(); } - /// Queue a message to be added to the debug output. - /// The message to add. - /// Returns whether the message was successfully queued. - public static bool QueueDebugMessage(string message) - { - if (!SGame.Debug) - return false; - if (SGame.DebugMessageQueue.Count > 32) - return false; - - SGame.DebugMessageQueue.Enqueue(message); - return true; - } - /********* ** Protected methods @@ -297,7 +280,6 @@ namespace StardewModdingAPI.Framework /// The method called during game launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. protected override void Initialize() { - SGame.DebugMessageQueue = new Queue(); this.PreviouslyPressedButtons = new Buttons[4][]; for (var i = 0; i < 4; ++i) this.PreviouslyPressedButtons[i] = new Buttons[0]; @@ -317,9 +299,6 @@ namespace StardewModdingAPI.Framework /// A snapshot of the game timing state. protected override void Update(GameTime gameTime) { - // add FPS to debug output - SGame.QueueDebugMessage($"FPS: {SGame.FramesPerSecond}"); - // raise game loaded if (this.FirstUpdate) GameEvents.InvokeGameLoaded(this.Monitor); @@ -928,14 +907,14 @@ namespace StardewModdingAPI.Framework } if (Game1.currentBillboard != 0) this.drawBillboard(); - if ((Game1.displayHUD || Game1.eventUp) && (Game1.currentBillboard == 0 && (int) Game1.gameMode == 3) && (!Game1.freezeControls && !Game1.panMode)) + 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); + 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) @@ -1093,24 +1072,6 @@ namespace StardewModdingAPI.Framework { this.Monitor.Log($"An error occured in the overridden draw loop: {ex.GetLogSummary()}", LogLevel.Error); } - - if (SGame.Debug) - { - Game1.spriteBatch.Begin(); - - int i = 0; - while (SGame.DebugMessageQueue.Any()) - { - string message = SGame.DebugMessageQueue.Dequeue(); - Game1.spriteBatch.DrawString(Game1.smoothFont, message, new Vector2(0, i * 14), Color.CornflowerBlue); - i++; - } - GraphicsEvents.InvokeDrawDebug(this.Monitor); - - Game1.spriteBatch.End(); - } - else - SGame.DebugMessageQueue.Clear(); } /// Get whether a controller button was pressed since the last check. -- cgit From e393a2aad958c636ffaf5431e0e9dfdf6faf4461 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Feb 2017 12:38:32 -0500 Subject: add TimeEvents.AfterDayStarted event (#236) --- release-notes.md | 2 +- src/StardewModdingAPI/Events/TimeEvents.cs | 14 ++++++++++++-- src/StardewModdingAPI/Framework/SGame.cs | 4 ++++ 3 files changed, 17 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 72e3b3c5..29d66a80 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,7 +10,7 @@ For players: * Fixed game's debug output being shown in the console for all users. For mod developers: -* Added `SaveEvents.AfterReturnToTitle` event. +* Added `SaveEvents.AfterReturnToTitle` and `TimeEvents.AfterDayStarted` events. * Added `GetPrivateProperty` reflection helper. * Many deprecated APIs have been removed; see the [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod) for more information. diff --git a/src/StardewModdingAPI/Events/TimeEvents.cs b/src/StardewModdingAPI/Events/TimeEvents.cs index dedd7e77..a140a223 100644 --- a/src/StardewModdingAPI/Events/TimeEvents.cs +++ b/src/StardewModdingAPI/Events/TimeEvents.cs @@ -9,10 +9,13 @@ namespace StardewModdingAPI.Events /********* ** Events *********/ + /// Raised after the game begins a new day, including when loading a save. + public static event EventHandler AfterDayStarted; + /// Raised after the in-game clock changes. public static event EventHandler TimeOfDayChanged; - /// Raised after the day-of-month value changes, including when loading a save (unlike ). + /// Raised after the day-of-month value changes, including when loading a save. This may happen before save; in most cases you should use instead. public static event EventHandler DayOfMonthChanged; /// Raised after the year value changes. @@ -22,13 +25,20 @@ namespace StardewModdingAPI.Events public static event EventHandler SeasonOfYearChanged; /// Raised when the player is transitioning to a new day and the game is performing its day update logic. This event is triggered twice: once after the game starts transitioning, and again after it finishes. - [Obsolete("Use " + nameof(TimeEvents) + "." + nameof(DayOfMonthChanged) + " or " + nameof(SaveEvents) + " instead")] + [Obsolete("Use " + nameof(TimeEvents) + "." + nameof(TimeEvents.AfterDayStarted) + " or " + nameof(SaveEvents) + " instead")] public static event EventHandler OnNewDay; /********* ** Internal methods *********/ + /// Raise an event. + /// Encapsulates monitoring and logging. + internal static void InvokeAfterDayStarted(IMonitor monitor) + { + monitor.SafelyRaisePlainEvent($"{nameof(TimeEvents)}.{nameof(TimeEvents.AfterDayStarted)}", TimeEvents.AfterDayStarted?.GetInvocationList(), null, EventArgs.Empty); + } + /// Raise a event. /// Encapsulates monitoring and logging. /// The previous time in military time format (e.g. 6:00pm is 1800). diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 55e7ff1a..83af62e0 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -1120,6 +1120,7 @@ namespace StardewModdingAPI.Framework { SaveEvents.InvokeAfterLoad(this.Monitor); PlayerEvents.InvokeLoadedGame(this.Monitor, new EventArgsLoadedGameChanged(Game1.hasLoadedGame)); + TimeEvents.InvokeAfterDayStarted(this.Monitor); } this.AfterLoadTimer--; } @@ -1200,7 +1201,10 @@ namespace StardewModdingAPI.Framework if (newMenu is SaveGameMenu || newMenu is ShippingMenu) SaveEvents.InvokeBeforeSave(this.Monitor); else if (previousMenu is SaveGameMenu || previousMenu is ShippingMenu) + { SaveEvents.InvokeAfterSave(this.Monitor); + TimeEvents.InvokeAfterDayStarted(this.Monitor); + } // raise menu events if (newMenu != null) -- cgit From 36c831f4c95c97344745783924cbdd8c7791d0ba Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Feb 2017 14:35:25 -0500 Subject: fix world-ready events being raised before the game finishes loading the save in SDV 1.2 (#231) --- src/StardewModdingAPI/Framework/SGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 83af62e0..d1f500fb 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -30,7 +30,7 @@ namespace StardewModdingAPI.Framework /**** ** SMAPI state ****/ - /// The number of ticks until SMAPI should notify mods when is set. + /// The number of ticks until SMAPI should notify mods that the game has loaded. /// Skipping a few frames ensures the game finishes initialising the world before mods try to change it. private int AfterLoadTimer = 5; @@ -1114,7 +1114,7 @@ namespace StardewModdingAPI.Framework private void UpdateEventCalls() { // save loaded event - if (Game1.hasLoadedGame && this.AfterLoadTimer >= 0) + if (Game1.hasLoadedGame && !SaveGame.IsProcessing/*still loading save*/ && this.AfterLoadTimer >= 0) { if (this.AfterLoadTimer == 0) { -- cgit From e71233686101920dd8e23747998840a5985f7129 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Feb 2017 14:38:12 -0500 Subject: mark more incompatible mods (#231) --- src/StardewModdingAPI/StardewModdingAPI.data.json | 61 +++++++++++++++++++++++ 1 file changed, 61 insertions(+) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json index 3c9be222..79fa2cc8 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -41,6 +41,13 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. }, /* versions not compatible with Stardew Valley 1.2+ */ + { + "Name": "AccessChestAnywhere", + "ID": "AccessChestAnywhere", + "UpperVersion": "1.1", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/257", + "UnofficialUpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/518" + }, { "Name": "Better Sprinklers", "ID": "SPDSprinklersMod", @@ -55,22 +62,76 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "UpperVersion": "1.1", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/878" }, + { + "Name": "Chests Anywhere", + "ID": "ChestsAnywhere", + "UpperVersion": "1.8.2", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/518" + }, + { + "Name": "CJB Automation", + "ID": "CJBAutomation", + "UpperVersion": "1.4", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/211" + }, + { + "Name": "Enemy Health Bars", + "ID": "SPDHealthBar", + "UpperVersion": "1.7", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/193" + }, { "Name": "Entoarox Framework", "ID": "eacdb74b-4080-4452-b16b-93773cda5cf9", "UpperVersion": "1.6.1", "UpdateUrl": "http://community.playstarbound.com/resources/4228" }, + { + "Name": "Extended Fridge", + "ID": "Mystra007ExtendedFridge", + "UpperVersion": "1.0", // real latest version is 0.94, but mod incorrectly sets version to 1.0 in manifest + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/485" + }, { "Name": "Get Dressed", "ID": "GetDressed.dll", "UpperVersion": "3.2", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/331" }, + { + "Name": "Lookup Anything", + "ID": "Pathoschild.LookupAnything", + "UpperVersion": "1.10.1", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/541" + }, + { + "Name": "Lookup Anything", + "ID": "LookupAnything", // ID changed in latest version + "UpperVersion": "1.10.1", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/541" + }, { "Name": "NoSoilDecay", "ID": "289dee03-5f38-4d8e-8ffc-e440198e8610", "UpperVersion": "0.5", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/237" + }, + { + "Name": "Reusable Wallpapers", + "ID": "dae1b553-2e39-43e7-8400-c7c5c836134b", + "UpperVersion": "1.5", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/356" + }, + { + "Name": "Save Anywhere", // depends on StarDustCore + "ID": "SaveAnywhere", + "UpperVersion": "2.0", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/444" + }, + { + "Name": "StarDustCore", + "ID": "StarDustCore", + "UpperVersion": "1.0", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/683" } ] -- cgit From 59ff0a3266b069e808cfa10d62c37914d2446ae9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Feb 2017 18:26:51 -0500 Subject: mark another incompatible mod (#231) --- src/StardewModdingAPI/StardewModdingAPI.data.json | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json index 79fa2cc8..2a470a39 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -128,6 +128,12 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "UpperVersion": "2.0", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/444" }, + { + "Name": "StackSplitX", + "ID": "StackSplitX.dll", + "UpperVersion": "1.0", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/798" + }, { "Name": "StarDustCore", "ID": "StarDustCore", -- cgit From 95786e9e44ed2ddfc0d38e8279b8c0166db7fc85 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Feb 2017 18:43:21 -0500 Subject: encapsulate file & folder deletion (#237) --- .../InteractiveInstaller.cs | 30 ++++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 868889fa..36301829 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -104,7 +104,7 @@ namespace StardewModdingApi.Installer /// /// Uninstall logic: /// 1. On Linux/Mac: if a backup of the launcher exists, delete the launcher and restore the backup. - /// 2. Delete all files and folders in the game directory matching one of the . + /// 2. Delete all files and folders in the game directory matching one of the values returned by . /// /// Install flow: /// 1. Run the uninstall flow. @@ -179,8 +179,7 @@ namespace StardewModdingApi.Installer if (platform == Platform.Mono && File.Exists(paths.unixLauncherBackup)) { this.PrintDebug("Removing SMAPI launcher..."); - if (File.Exists(paths.unixLauncher)) - File.Delete(paths.unixLauncher); + this.InteractivelyDelete(paths.unixLauncher); File.Move(paths.unixLauncherBackup, paths.unixLauncher); } @@ -192,12 +191,7 @@ namespace StardewModdingApi.Installer { this.PrintDebug(action == ScriptAction.Install ? "Removing previous SMAPI files..." : "Removing SMAPI files..."); foreach (string path in removePaths) - { - if (Directory.Exists(path)) - Directory.Delete(path, recursive: true); - else - File.Delete(path); - } + this.InteractivelyDelete(path); } /**** @@ -210,8 +204,7 @@ namespace StardewModdingApi.Installer foreach (FileInfo sourceFile in packageDir.EnumerateFiles()) { string targetPath = Path.Combine(installDir.FullName, sourceFile.Name); - if (File.Exists(targetPath)) - File.Delete(targetPath); + this.InteractivelyDelete(targetPath); sourceFile.CopyTo(targetPath); } @@ -222,7 +215,7 @@ namespace StardewModdingApi.Installer if (!File.Exists(paths.unixLauncherBackup)) File.Move(paths.unixLauncher, paths.unixLauncherBackup); else if (File.Exists(paths.unixLauncher)) - File.Delete(paths.unixLauncher); + this.InteractivelyDelete(paths.unixLauncher); File.Move(paths.unixSmapiLauncher, paths.unixLauncher); } @@ -246,8 +239,7 @@ namespace StardewModdingApi.Installer // initialise target dir DirectoryInfo targetDir = new DirectoryInfo(Path.Combine(modsDir.FullName, sourceDir.Name)); - if (targetDir.Exists) - targetDir.Delete(recursive: true); + this.InteractivelyDelete(targetDir.FullName); targetDir.Create(); // copy files @@ -364,6 +356,16 @@ namespace StardewModdingApi.Installer Console.WriteLine(text); } + /// Interactively delete a file or folder path. + /// The file or folder path. + private void InteractivelyDelete(string path) + { + if (Directory.Exists(path)) + Directory.Delete(path, recursive: true); + else if (File.Exists(path)) + File.Delete(path); + } + /// Interactively ask the user to choose a value. /// The message to print. /// The allowed options (not case sensitive). -- cgit From 96c21b1acc2140fb58b5ba214f42b709b2893e74 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Feb 2017 19:11:00 -0500 Subject: avoid special case in error printing (#237) --- src/StardewModdingAPI.Installer/InteractiveInstaller.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 36301829..c751cef9 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -137,12 +137,14 @@ namespace StardewModdingApi.Installer ****/ if (!packageDir.Exists) { - this.ExitError($"The 'internal/{platform}' package folder is missing (should be at {packageDir})."); + this.PrintError($"The 'internal/{platform}' package folder is missing (should be at {packageDir})."); + Console.ReadLine(); return; } if (!File.Exists(paths.executable)) { - this.ExitError("The detected game install path doesn't contain a Stardew Valley executable."); + this.PrintError("The detected game install path doesn't contain a Stardew Valley executable."); + Console.ReadLine(); return; } Console.WriteLine(); @@ -333,12 +335,11 @@ namespace StardewModdingApi.Installer this.PrintColor(text, ConsoleColor.DarkYellow); } - /// Print an error and pause the console if needed. - /// The error text. - private void ExitError(string error) + /// Print a warning message. + /// The text to print. + private void PrintError(string text) { - this.PrintColor(error, ConsoleColor.Red); - Console.ReadLine(); + this.PrintColor(text, ConsoleColor.Red); } /// Print a message to the console. -- cgit From e5324e170c27adddb4292c97c34a2e8f90dfe231 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Feb 2017 19:39:38 -0500 Subject: handle permissions & delays in installer's file/folder deletion code (#237) --- .../InteractiveInstaller.cs | 56 ++++++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index c751cef9..86e11d71 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Threading; #if SMAPI_FOR_WINDOWS using Microsoft.Win32; #endif @@ -357,14 +358,59 @@ namespace StardewModdingApi.Installer Console.WriteLine(text); } - /// Interactively delete a file or folder path. + /// Interactively delete a file or folder path, and block until deletion completes. /// The file or folder path. private void InteractivelyDelete(string path) { - if (Directory.Exists(path)) - Directory.Delete(path, recursive: true); - else if (File.Exists(path)) - File.Delete(path); + while (true) + { + try + { + this.ForceDelete(Directory.Exists(path) ? new DirectoryInfo(path) : (FileSystemInfo)new FileInfo(path)); + break; + } + 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."); + Console.ReadKey(); + } + } + } + + /// Delete a file or folder regardless of file permissions, and block until deletion completes. + /// The file or folder to reset. + private void ForceDelete(FileSystemInfo entry) + { + // ignore if already deleted + entry.Refresh(); + if (!entry.Exists) + return; + + // delete children + var folder = entry as DirectoryInfo; + if (folder != null) + { + foreach (FileSystemInfo child in folder.GetFileSystemInfos()) + this.ForceDelete(child); + } + + // reset permissions & delete + entry.Attributes = FileAttributes.Normal; + entry.Delete(); + + // wait for deletion to finish + for (int i = 0; i < 10; i++) + { + entry.Refresh(); + if (entry.Exists) + Thread.Sleep(500); + } + + // throw exception if deletion didn't happen before timeout + entry.Refresh(); + if (entry.Exists) + throw new IOException($"Timed out trying to delete {entry.FullName}"); } /// Interactively ask the user to choose a value. -- cgit From d1080a8b2b54c777a446f08d9ecd5b76b4b2561a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 13 Feb 2017 00:13:29 -0500 Subject: move core JSON logic out of mod helper (#199) This lets SMAPI parse manifest.json files without a mod helper, so we can pass the mod name into the helper. --- src/StardewModdingAPI/Framework/ModHelper.cs | 59 +++++------------ .../Framework/Serialisation/JsonHelper.cs | 73 ++++++++++++++++++++++ src/StardewModdingAPI/Program.cs | 12 ++-- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 4 files changed, 96 insertions(+), 49 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/ModHelper.cs b/src/StardewModdingAPI/Framework/ModHelper.cs index 2b562b4f..4a3d5ed5 100644 --- a/src/StardewModdingAPI/Framework/ModHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelper.cs @@ -1,8 +1,7 @@ using System; using System.IO; -using Newtonsoft.Json; -using StardewModdingAPI.Advanced; using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Framework.Serialisation; namespace StardewModdingAPI.Framework { @@ -12,12 +11,8 @@ namespace StardewModdingAPI.Framework /********* ** Properties *********/ - /// The JSON settings to use when serialising and deserialising files. - private readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings - { - Formatting = Formatting.Indented, - ObjectCreationHandling = ObjectCreationHandling.Replace // avoid issue where default ICollection values are duplicated each time the config is loaded - }; + /// Encapsulates SMAPI's JSON file parsing. + private readonly JsonHelper JsonHelper; /********* @@ -38,20 +33,24 @@ namespace StardewModdingAPI.Framework *********/ /// Construct an instance. /// The mod directory path. + /// Encapsulate SMAPI's JSON parsing. /// Metadata about loaded mods. - /// An argument is null or invalid. + /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(string modDirectory, IModRegistry modRegistry) + public ModHelper(string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry) { // validate - if (modRegistry == null) - throw new ArgumentException("The mod registry cannot be null."); if (string.IsNullOrWhiteSpace(modDirectory)) - throw new ArgumentException("The mod directory cannot be empty."); + throw new ArgumentNullException(nameof(modDirectory)); + if (jsonHelper == null) + throw new ArgumentNullException(nameof(jsonHelper)); + if (modRegistry == null) + throw new ArgumentNullException(nameof(modRegistry)); if (!Directory.Exists(modDirectory)) throw new InvalidOperationException("The specified mod directory does not exist."); // initialise + this.JsonHelper = jsonHelper; this.DirectoryPath = modDirectory; this.ModRegistry = modRegistry; } @@ -88,28 +87,8 @@ namespace StardewModdingAPI.Framework public TModel ReadJsonFile(string path) where TModel : class { - // read file - string fullPath = Path.Combine(this.DirectoryPath, path); - string json; - try - { - json = File.ReadAllText(fullPath); - } - catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException) - { - return null; - } - - // deserialise model - TModel model = JsonConvert.DeserializeObject(json, this.JsonSettings); - if (model is IConfigFile) - { - var wrapper = (IConfigFile)model; - wrapper.ModHelper = this; - wrapper.FilePath = path; - } - - return model; + path = Path.Combine(this.DirectoryPath, path); + return this.JsonHelper.ReadJsonFile(path, this); } /// Save to a JSON file. @@ -120,15 +99,7 @@ namespace StardewModdingAPI.Framework where TModel : class { path = Path.Combine(this.DirectoryPath, path); - - // create directory if needed - string dir = Path.GetDirectoryName(path); - if (!Directory.Exists(dir)) - Directory.CreateDirectory(dir); - - // write file - string json = JsonConvert.SerializeObject(model, this.JsonSettings); - File.WriteAllText(path, json); + this.JsonHelper.WriteJsonFile(path, model); } } } diff --git a/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs b/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs new file mode 100644 index 00000000..3809666f --- /dev/null +++ b/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; +using Newtonsoft.Json; +using StardewModdingAPI.Advanced; + +namespace StardewModdingAPI.Framework.Serialisation +{ + /// Encapsulates SMAPI's JSON file parsing. + public class JsonHelper + { + /********* + ** Accessors + *********/ + /// The JSON settings to use when serialising and deserialising files. + private readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + ObjectCreationHandling = ObjectCreationHandling.Replace // avoid issue where default ICollection values are duplicated each time the config is loaded + }; + + + /********* + ** Public methods + *********/ + /// Read a JSON file. + /// The model type. + /// The absolete file path. + /// The mod helper to inject for instances. + /// Returns the deserialised model, or null if the file doesn't exist or is empty. + public TModel ReadJsonFile(string fullPath, IModHelper modHelper) + where TModel : class + { + // read file + string json; + try + { + json = File.ReadAllText(fullPath); + } + catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException) + { + return null; + } + + // deserialise model + TModel model = JsonConvert.DeserializeObject(json, this.JsonSettings); + if (model is IConfigFile) + { + var wrapper = (IConfigFile)model; + wrapper.ModHelper = modHelper; + wrapper.FilePath = fullPath; + } + + return model; + } + + /// Save to a JSON file. + /// The model type. + /// The absolete file path. + /// The model to save. + public void WriteJsonFile(string fullPath, TModel model) + where TModel : class + { + // create directory if needed + string dir = Path.GetDirectoryName(fullPath); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + // write file + string json = JsonConvert.SerializeObject(model, this.JsonSettings); + File.WriteAllText(fullPath, json); + } + } +} diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index f4518e21..b86e186f 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -15,6 +15,7 @@ using StardewModdingAPI.Events; using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.Logging; using StardewModdingAPI.Framework.Models; +using StardewModdingAPI.Framework.Serialisation; using StardewValley; using Monitor = StardewModdingAPI.Framework.Monitor; @@ -319,6 +320,9 @@ namespace StardewModdingAPI { Program.Monitor.Log("Loading mods..."); + // get JSON helper + JsonHelper jsonHelper = new JsonHelper(); + // get assembly loader AssemblyLoader modAssemblyLoader = new AssemblyLoader(Program.TargetPlatform, Program.Monitor); AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name); @@ -353,9 +357,6 @@ namespace StardewModdingAPI return; } - // get helper - IModHelper helper = new ModHelper(directory.FullName, Program.ModRegistry); - // get manifest path string manifestPath = Path.Combine(directory.FullName, "manifest.json"); if (!File.Exists(manifestPath)) @@ -378,7 +379,7 @@ namespace StardewModdingAPI } // deserialise manifest - manifest = helper.ReadJsonFile("manifest.json"); + manifest = jsonHelper.ReadJsonFile(Path.Combine(directory.FullName, "manifest.json"), null); if (manifest == null) { Program.Monitor.Log($"{errorPrefix}: the manifest file does not exist.", LogLevel.Error); @@ -503,8 +504,9 @@ namespace StardewModdingAPI } // inject data + // get helper mod.ModManifest = manifest; - mod.Helper = helper; + mod.Helper = new ModHelper(directory.FullName, jsonHelper, Program.ModRegistry); mod.Monitor = Program.GetSecondaryMonitor(manifest.Name); mod.PathOnDisk = directory.FullName; diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index ae08012d..add6ec40 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -148,6 +148,7 @@ + -- cgit From 0441d0843c65775bc72377e32ed4b3b5ee0b8f75 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 13 Feb 2017 00:40:33 -0500 Subject: add new console command API with backward compatibility (#199) --- src/StardewModdingAPI/Command.cs | 77 +++++++++------ src/StardewModdingAPI/Events/EventArgsCommand.cs | 1 + src/StardewModdingAPI/Framework/Command.cs | 40 ++++++++ src/StardewModdingAPI/Framework/CommandHelper.cs | 53 ++++++++++ src/StardewModdingAPI/Framework/CommandManager.cs | 114 ++++++++++++++++++++++ src/StardewModdingAPI/Framework/ModHelper.cs | 8 +- src/StardewModdingAPI/ICommandHelper.cs | 26 +++++ src/StardewModdingAPI/IModHelper.cs | 3 + src/StardewModdingAPI/Program.cs | 32 +++--- src/StardewModdingAPI/StardewModdingAPI.csproj | 4 + 10 files changed, 317 insertions(+), 41 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Command.cs create mode 100644 src/StardewModdingAPI/Framework/CommandHelper.cs create mode 100644 src/StardewModdingAPI/Framework/CommandManager.cs create mode 100644 src/StardewModdingAPI/ICommandHelper.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Command.cs b/src/StardewModdingAPI/Command.cs index 6195bd8b..3b1e5632 100644 --- a/src/StardewModdingAPI/Command.cs +++ b/src/StardewModdingAPI/Command.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; -using System.Linq; using StardewModdingAPI.Events; using StardewModdingAPI.Framework; namespace StardewModdingAPI { /// A command that can be submitted through the SMAPI console to interact with SMAPI. + [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.ConsoleCommands))] public class Command { /********* @@ -16,7 +16,7 @@ namespace StardewModdingAPI ** SMAPI ****/ /// The commands registered with SMAPI. - internal static List RegisteredCommands = new List(); + private static readonly IDictionary LegacyCommands = new Dictionary(StringComparer.InvariantCultureIgnoreCase); /// The event raised when this command is submitted through the console. public event EventHandler CommandFired; @@ -64,6 +64,7 @@ namespace StardewModdingAPI this.CommandFired.Invoke(this, new EventArgsCommand(this)); } + /**** ** SMAPI ****/ @@ -72,27 +73,7 @@ namespace StardewModdingAPI /// Encapsulates monitoring and logging. public static void CallCommand(string input, IMonitor monitor) { - // normalise input - input = input?.Trim(); - if (string.IsNullOrWhiteSpace(input)) - return; - - // tokenise input - string[] args = input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - string commandName = args[0]; - args = args.Skip(1).ToArray(); - - // get command - Command command = Command.FindCommand(commandName); - if (command == null) - { - monitor.Log("Unknown command", LogLevel.Error); - return; - } - - // fire command - command.CalledArgs = args; - command.Fire(); + Program.CommandManager.Trigger(input); } /// Register a command with SMAPI. @@ -101,11 +82,25 @@ namespace StardewModdingAPI /// A human-readable list of accepted arguments. public static Command RegisterCommand(string name, string description, string[] args = null) { - var command = new Command(name, description, args); - if (Command.RegisteredCommands.Contains(command)) - throw new InvalidOperationException($"The '{command.CommandName}' command is already registered!"); + name = name?.Trim().ToLower(); + + // raise deprecation warning + Program.DeprecationManager.Warn("Command.RegisterCommand", "1.9", DeprecationLevel.Notice); - Command.RegisteredCommands.Add(command); + // validate + if (Command.LegacyCommands.ContainsKey(name)) + throw new InvalidOperationException($"The '{name}' command is already registered!"); + + // add command + string modName = Program.ModRegistry.GetModFromStack() ?? ""; + string documentation = args?.Length > 0 + ? $"{description} - {string.Join(", ", args)}" + : description; + Program.CommandManager.Add(modName, name, documentation, Command.Fire); + + // add legacy command + Command command = new Command(name, description, args); + Command.LegacyCommands.Add(name, command); return command; } @@ -113,7 +108,33 @@ namespace StardewModdingAPI /// The command name to find. public static Command FindCommand(string name) { - return Command.RegisteredCommands.Find(x => x.CommandName.Equals(name)); + if (name == null) + return null; + + Command command; + Command.LegacyCommands.TryGetValue(name.Trim(), out command); + return command; + } + + + /********* + ** Private methods + *********/ + /// Trigger this command. + /// The command name. + /// The command arguments. + private static void Fire(string name, string[] args) + { + // get legacy command + Command command; + if (!Command.LegacyCommands.TryGetValue(name, out command)) + { + throw new InvalidOperationException($"Can't run command '{name}' because there's no such legacy command."); + return; + } + + // raise event + command.Fire(); } } } diff --git a/src/StardewModdingAPI/Events/EventArgsCommand.cs b/src/StardewModdingAPI/Events/EventArgsCommand.cs index ddf644fb..bae13694 100644 --- a/src/StardewModdingAPI/Events/EventArgsCommand.cs +++ b/src/StardewModdingAPI/Events/EventArgsCommand.cs @@ -3,6 +3,7 @@ namespace StardewModdingAPI.Events { /// Event arguments for a event. + [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.ConsoleCommands))] public class EventArgsCommand : EventArgs { /********* diff --git a/src/StardewModdingAPI/Framework/Command.cs b/src/StardewModdingAPI/Framework/Command.cs new file mode 100644 index 00000000..943e018d --- /dev/null +++ b/src/StardewModdingAPI/Framework/Command.cs @@ -0,0 +1,40 @@ +using System; + +namespace StardewModdingAPI.Framework +{ + /// A command that can be submitted through the SMAPI console to interact with SMAPI. + internal class Command + { + /********* + ** Accessor + *********/ + /// The friendly name for the mod that registered the command. + public string ModName { get; } + + /// The command name, which the user must type to trigger it. + public string Name { get; } + + /// The human-readable documentation shown when the player runs the built-in 'help' command. + public string Documentation { get; } + + /// The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user. + public Action Callback { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The friendly name for the mod that registered the command. + /// The command name, which the user must type to trigger it. + /// The human-readable documentation shown when the player runs the built-in 'help' command. + /// The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user. + public Command(string modName, string name, string documentation, Action callback) + { + this.ModName = modName; + this.Name = name; + this.Documentation = documentation; + this.Callback = callback; + } + } +} diff --git a/src/StardewModdingAPI/Framework/CommandHelper.cs b/src/StardewModdingAPI/Framework/CommandHelper.cs new file mode 100644 index 00000000..2e9dea8e --- /dev/null +++ b/src/StardewModdingAPI/Framework/CommandHelper.cs @@ -0,0 +1,53 @@ +using System; + +namespace StardewModdingAPI.Framework +{ + /// Provides an API for managing console commands. + internal class CommandHelper : ICommandHelper + { + /********* + ** Accessors + *********/ + /// The friendly mod name for this instance. + private readonly string ModName; + + /// Manages console commands. + private readonly CommandManager CommandManager; + + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The friendly mod name for this instance. + /// Manages console commands. + public CommandHelper(string modName, CommandManager commandManager) + { + this.ModName = modName; + this.CommandManager = commandManager; + } + + /// Add a console command. + /// The command name, which the user must type to trigger it. + /// The human-readable documentation shown when the player runs the built-in 'help' command. + /// The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user. + /// The or is null or empty. + /// The is not a valid format. + /// There's already a command with that name. + public ICommandHelper Add(string name, string documentation, Action callback) + { + this.CommandManager.Add(this.ModName, name, documentation, callback); + return this; + } + + /// Trigger a command. + /// The command name. + /// The command arguments. + /// Returns whether a matching command was triggered. + public bool Trigger(string name, string[] arguments) + { + return this.CommandManager.Trigger(name, arguments); + } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/CommandManager.cs b/src/StardewModdingAPI/Framework/CommandManager.cs new file mode 100644 index 00000000..3aa4bf97 --- /dev/null +++ b/src/StardewModdingAPI/Framework/CommandManager.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace StardewModdingAPI.Framework +{ + /// Manages console commands. + internal class CommandManager + { + /********* + ** Properties + *********/ + /// The commands registered with SMAPI. + private readonly IDictionary Commands = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + + /********* + ** Public methods + *********/ + /// Add a console command. + /// The friendly mod name for this instance. + /// The command name, which the user must type to trigger it. + /// The human-readable documentation shown when the player runs the built-in 'help' command. + /// The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user. + /// Whether to allow a null argument; this should only used for backwards compatibility. + /// The or is null or empty. + /// The is not a valid format. + /// There's already a command with that name. + public void Add(string modName, string name, string documentation, Action callback, bool allowNullCallback = false) + { + name = this.GetNormalisedName(name); + + // validate format + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name), "Can't register a command with no name."); + if (name.Any(char.IsWhiteSpace)) + throw new FormatException($"Can't register the '{name}' command because the name can't contain whitespace."); + if (callback == null && !allowNullCallback) + throw new ArgumentNullException(nameof(callback), $"Can't register the '{name}' command because without a callback."); + + // ensure uniqueness + if (this.Commands.ContainsKey(name)) + throw new ArgumentException(nameof(callback), $"Can't register the '{name}' command because there's already a command with that name."); + + // add command + this.Commands.Add(name, new Command(modName, name, documentation, callback)); + } + + /// Get a command by its unique name. + /// The command name. + /// Returns the matching command, or null if not found. + public Command Get(string name) + { + name = this.GetNormalisedName(name); + Command command; + this.Commands.TryGetValue(name, out command); + return command; + } + + /// Get all registered commands. + public IEnumerable GetAll() + { + return this.Commands + .Values + .OrderBy(p => p.Name); + } + + /// Trigger a command. + /// The raw command input. + /// Returns whether a matching command was triggered. + public bool Trigger(string input) + { + string[] args = input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + string name = args[0]; + args = args.Skip(1).ToArray(); + + return this.Trigger(name, args); + } + + /// Trigger a command. + /// The command name. + /// The command arguments. + /// Returns whether a matching command was triggered. + public bool Trigger(string name, string[] arguments) + { + // get normalised name + name = this.GetNormalisedName(name); + if (name == null) + return false; + + // get command + Command command; + if (this.Commands.TryGetValue(name, out command)) + { + command.Callback.Invoke(name, arguments); + return true; + } + return false; + } + + /********* + ** Private methods + *********/ + /// Get a normalised command name. + /// The command name. + private string GetNormalisedName(string name) + { + name = name?.Trim().ToLower(); + return !string.IsNullOrWhiteSpace(name) + ? name + : null; + } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/ModHelper.cs b/src/StardewModdingAPI/Framework/ModHelper.cs index 4a3d5ed5..04767de5 100644 --- a/src/StardewModdingAPI/Framework/ModHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelper.cs @@ -27,17 +27,22 @@ namespace StardewModdingAPI.Framework /// Metadata about loaded mods. public IModRegistry ModRegistry { get; } + /// An API for managing console commands. + public ICommandHelper ConsoleCommands { get; } + /********* ** Public methods *********/ /// Construct an instance. + /// The friendly mod name. /// The mod directory path. /// Encapsulate SMAPI's JSON parsing. /// Metadata about loaded mods. + /// Manages console commands. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry) + public ModHelper(string modName, string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry, CommandManager commandManager) { // validate if (string.IsNullOrWhiteSpace(modDirectory)) @@ -53,6 +58,7 @@ namespace StardewModdingAPI.Framework this.JsonHelper = jsonHelper; this.DirectoryPath = modDirectory; this.ModRegistry = modRegistry; + this.ConsoleCommands = new CommandHelper(modName, commandManager); } /**** diff --git a/src/StardewModdingAPI/ICommandHelper.cs b/src/StardewModdingAPI/ICommandHelper.cs new file mode 100644 index 00000000..3a51ffb4 --- /dev/null +++ b/src/StardewModdingAPI/ICommandHelper.cs @@ -0,0 +1,26 @@ +using System; + +namespace StardewModdingAPI +{ + /// Provides an API for managing console commands. + public interface ICommandHelper + { + /********* + ** Public methods + *********/ + /// Add a console command. + /// The command name, which the user must type to trigger it. + /// The human-readable documentation shown when the player runs the built-in 'help' command. + /// The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user. + /// The or is null or empty. + /// The is not a valid format. + /// There's already a command with that name. + ICommandHelper Add(string name, string documentation, Action callback); + + /// Trigger a command. + /// The command name. + /// The command arguments. + /// Returns whether a matching command was triggered. + bool Trigger(string name, string[] arguments); + } +} diff --git a/src/StardewModdingAPI/IModHelper.cs b/src/StardewModdingAPI/IModHelper.cs index 02f9c038..ef67cd1c 100644 --- a/src/StardewModdingAPI/IModHelper.cs +++ b/src/StardewModdingAPI/IModHelper.cs @@ -15,6 +15,9 @@ /// Metadata about loaded mods. IModRegistry ModRegistry { get; } + /// An API for managing console commands. + ICommandHelper ConsoleCommands { get; } + /********* ** Public methods diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index b86e186f..ff0dff29 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -84,6 +84,9 @@ namespace StardewModdingAPI /// Manages deprecation warnings. internal static readonly DeprecationManager DeprecationManager = new DeprecationManager(Program.Monitor, Program.ModRegistry); + /// Manages console commands. + internal static readonly CommandManager CommandManager = new CommandManager(); + /********* ** Public methods @@ -262,7 +265,7 @@ namespace StardewModdingAPI while (!Program.ready) Thread.Sleep(1000); // register help command - Command.RegisterCommand("help", "Lists all commands | 'help ' returns command description").CommandFired += Program.help_CommandFired; + Program.CommandManager.Add("SMAPI", "help", "Lists all commands | 'help ' returns command description", Program.HandleHelpCommand); // listen for command line input Program.Monitor.Log("Starting console..."); @@ -506,7 +509,7 @@ namespace StardewModdingAPI // inject data // get helper mod.ModManifest = manifest; - mod.Helper = new ModHelper(directory.FullName, jsonHelper, Program.ModRegistry); + mod.Helper = new ModHelper(manifest.Name, directory.FullName, jsonHelper, Program.ModRegistry, Program.CommandManager); mod.Monitor = Program.GetSecondaryMonitor(manifest.Name); mod.PathOnDisk = directory.FullName; @@ -556,24 +559,29 @@ namespace StardewModdingAPI private static void ConsoleInputLoop() { while (true) - Command.CallCommand(Console.ReadLine(), Program.Monitor); + { + string input = Console.ReadLine(); + if (!Program.CommandManager.Trigger(input)) + Program.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error); + } } /// The method called when the user submits the help command in the console. - /// The event sender. - /// The event data. - private static void help_CommandFired(object sender, EventArgsCommand e) + /// The command name. + /// The command arguments. + private static void HandleHelpCommand(string name, string[] arguments) { - if (e.Command.CalledArgs.Length > 0) + if (arguments.Any()) { - var command = Command.FindCommand(e.Command.CalledArgs[0]); - if (command == null) - Program.Monitor.Log("The specified command could't be found", LogLevel.Error); + + Framework.Command result = Program.CommandManager.Get(arguments[0]); + if (result == null) + Program.Monitor.Log("There's no command with that name.", LogLevel.Error); else - Program.Monitor.Log(command.CommandArgs.Length > 0 ? $"{command.CommandName}: {command.CommandDesc} - {string.Join(", ", command.CommandArgs)}" : $"{command.CommandName}: {command.CommandDesc}", LogLevel.Info); + Program.Monitor.Log($"{result.Name}: {result.Documentation} [added by {result.ModName}]", LogLevel.Info); } else - Program.Monitor.Log("Commands: " + string.Join(", ", Command.RegisteredCommands.Select(x => x.CommandName)), LogLevel.Info); + Program.Monitor.Log("Commands: " + string.Join(", ", Program.CommandManager.GetAll().Select(p => p.Name)), LogLevel.Info); } /// Show a 'press any key to exit' message, and exit when they press a key. diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index add6ec40..ffeb8e2e 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -117,6 +117,7 @@ + @@ -145,11 +146,14 @@ + + + -- cgit From 845fbaab12a040464e403aa693a0eaaa9c55c9ae Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 13 Feb 2017 01:19:02 -0500 Subject: migrate TrainerMod to new API (#199) --- src/StardewModdingAPI/Program.cs | 2 +- src/TrainerMod/TrainerMod.cs | 1027 +++++++++++++++++--------------------- 2 files changed, 468 insertions(+), 561 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index ff0dff29..58b86e8f 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -578,7 +578,7 @@ namespace StardewModdingAPI if (result == null) Program.Monitor.Log("There's no command with that name.", LogLevel.Error); else - Program.Monitor.Log($"{result.Name}: {result.Documentation} [added by {result.ModName}]", LogLevel.Info); + Program.Monitor.Log($"{result.Name}: {result.Documentation}\n(Added by {result.ModName}.)", LogLevel.Info); } else Program.Monitor.Log("Commands: " + string.Join(", ", Program.CommandManager.GetAll().Select(p => p.Name)), LogLevel.Info); diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index a7cedb6a..1690d5b5 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -44,7 +44,7 @@ namespace TrainerMod /// Provides simplified APIs for writing mods. public override void Entry(IModHelper helper) { - this.RegisterCommands(); + this.RegisterCommands(helper); GameEvents.UpdateTick += this.ReceiveUpdateTick; } @@ -73,634 +73,541 @@ namespace TrainerMod Game1.timeOfDay = this.FrozenTime; } - /// Register all trainer commands. - private void RegisterCommands() - { - Command.RegisterCommand("types", "Lists all value types | types").CommandFired += this.HandleTypes; - - Command.RegisterCommand("save", "Saves the game? Doesn't seem to work. | save").CommandFired += this.HandleSave; - Command.RegisterCommand("load", "Shows the load screen | load").CommandFired += this.HandleLoad; - - Command.RegisterCommand("player_setname", "Sets the player's name | player_setname ", new[] { "(player, pet, farm) (String) The target name" }).CommandFired += this.HandlePlayerSetName; - Command.RegisterCommand("player_setmoney", "Sets the player's money | player_setmoney |inf", new[] { "(Int32) The target money" }).CommandFired += this.HandlePlayerSetMoney; - Command.RegisterCommand("player_setstamina", "Sets the player's stamina | player_setstamina |inf", new[] { "(Int32) The target stamina" }).CommandFired += this.HandlePlayerSetStamina; - Command.RegisterCommand("player_setmaxstamina", "Sets the player's max stamina | player_setmaxstamina ", new[] { "(Int32) The target max stamina" }).CommandFired += this.HandlePlayerSetMaxStamina; - Command.RegisterCommand("player_sethealth", "Sets the player's health | player_sethealth |inf", new[] { "(Int32) The target health" }).CommandFired += this.HandlePlayerSetHealth; - Command.RegisterCommand("player_setmaxhealth", "Sets the player's max health | player_setmaxhealth ", new[] { "(Int32) The target max health" }).CommandFired += this.HandlePlayerSetMaxHealth; - Command.RegisterCommand("player_setimmunity", "Sets the player's immunity | player_setimmunity ", new[] { "(Int32) The target immunity" }).CommandFired += this.HandlePlayerSetImmunity; - - Command.RegisterCommand("player_setlevel", "Sets the player's specified skill to the specified value | player_setlevel ", new[] { "(luck, mining, combat, farming, fishing, foraging) (1-10) The target level" }).CommandFired += this.HandlePlayerSetLevel; - Command.RegisterCommand("player_setspeed", "Sets the player's speed to the specified value?", new[] { "(Int32) The target speed [0 is normal]" }).CommandFired += this.HandlePlayerSetSpeed; - Command.RegisterCommand("player_changecolour", "Sets the player's colour of the specified object | player_changecolor ", new[] { "(hair, eyes, pants) (r,g,b)" }).CommandFired += this.HandlePlayerChangeColor; - Command.RegisterCommand("player_changestyle", "Sets the player's style of the specified object | player_changecolor ", new[] { "(hair, shirt, skin, acc, shoe, swim, gender) (Int32)" }).CommandFired += this.HandlePlayerChangeStyle; - - Command.RegisterCommand("player_additem", "Gives the player an item | player_additem [count] [quality]", new[] { "(Int32) (Int32)[count] (Int32)[quality]" }).CommandFired += this.HandlePlayerAddItem; - Command.RegisterCommand("player_addmelee", "Gives the player a melee item | player_addmelee ", new[] { "?" }).CommandFired += this.HandlePlayerAddMelee; - Command.RegisterCommand("player_addring", "Gives the player a ring | player_addring ", new[] { "?" }).CommandFired += this.HandlePlayerAddRing; - - Command.RegisterCommand("list_items", "Lists items in the game data | list_items [search]", new[] { "(String)" }).CommandFired += this.HandleListItems; - - Command.RegisterCommand("world_settime", "Sets the time to the specified value | world_settime ", new[] { "(Int32) The target time [06:00 AM is 600]" }).CommandFired += this.HandleWorldSetTime; - Command.RegisterCommand("world_freezetime", "Freezes or thaws time | world_freezetime ", new[] { "(0 - 1) Whether or not to freeze time. 0 is thawed, 1 is frozen" }).CommandFired += this.HandleWorldFreezeTime; - Command.RegisterCommand("world_setday", "Sets the day to the specified value | world_setday ", new[] { "(Int32) The target day [1-28]" }).CommandFired += this.world_setDay; - Command.RegisterCommand("world_setseason", "Sets the season to the specified value | world_setseason ", new[] { "(winter, spring, summer, fall) The target season" }).CommandFired += this.HandleWorldSetSeason; - Command.RegisterCommand("world_downminelevel", "Goes down one mine level? | world_downminelevel", new[] { "" }).CommandFired += this.HandleWorldDownMineLevel; - Command.RegisterCommand("world_setminelevel", "Sets mine level? | world_setminelevel", new[] { "(Int32) The target level" }).CommandFired += this.HandleWorldSetMineLevel; - - Command.RegisterCommand("show_game_files", "Opens the game folder. | show_game_files").CommandFired += this.HandleShowGameFiles; - Command.RegisterCommand("show_data_files", "Opens the folder containing the save and log files. | show_data_files").CommandFired += this.HandleShowDataFiles; - } - /**** - ** Command handlers + ** Command definitions ****/ - /// The event raised when the 'types' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleTypes(object sender, EventArgsCommand e) - { - this.Monitor.Log($"[Int32: {int.MinValue} - {int.MaxValue}], [Int64: {long.MinValue} - {long.MaxValue}], [String: \"raw text\"], [Colour: r,g,b (EG: 128, 32, 255)]", LogLevel.Info); - } + /// Register all trainer commands. + /// Provides simplified APIs for writing mods. + private void RegisterCommands(IModHelper helper) + { + helper.ConsoleCommands + .Add("types", "Lists all value types.", this.HandleCommand) + .Add("save", "Saves the game? Doesn't seem to work.", this.HandleCommand) + .Add("load", "Shows the load screen.", this.HandleCommand) + .Add("player_setname", "Sets the player's name.\n\nUsage: player_setname \n- target: what to rename (one of 'player' or 'farm').\n- name: the new name to set.", this.HandleCommand) + .Add("player_setmoney", "Sets the player's money.\n\nUsage: player_setmoney \n- value: an integer amount, or 'inf' for infinite money.", this.HandleCommand) + .Add("player_setstamina", "Sets the player's stamina.\n\nUsage: player_setstamina \n- value: an integer amount, or 'inf' for infinite stamina.", this.HandleCommand) + .Add("player_setmaxstamina", "Sets the player's max stamina.\n\nUsage: player_setmaxstamina \n- value: an integer amount.", this.HandleCommand) + .Add("player_sethealth", "Sets the player's health.\n\nUsage: player_sethealth \n- value: an integer amount, or 'inf' for infinite health.", this.HandleCommand) + .Add("player_setmaxhealth", "Sets the player's max health.\n\nUsage: player_setmaxhealth \n- value: an integer amount.", this.HandleCommand) + .Add("player_setimmunity", "Sets the player's immunity.\n\nUsage: player_setimmunity \n- value: an integer amount.", this.HandleCommand) + + .Add("player_setlevel", "Sets the player's specified skill to the specified value.\n\nUsage: player_setlevel \n- skill: the skill to set (one of 'luck', 'mining', 'combat', 'farming', 'fishing', or 'foraging').\n- value: the target level (a number from 1 to 10).", this.HandleCommand) + .Add("player_setspeed", "Sets the player's speed to the specified value?\n\nUsage: player_setspeed \n- value: an integer amount (0 is normal).", this.HandleCommand) + .Add("player_changecolour", "Sets the colour of a player feature.\n\nUsage: player_changecolor \n- target: what to change (one of 'hair', 'eyes', or 'pants').\n- colour: a colour value in RGB format, like (255,255,255).", this.HandleCommand) + .Add("player_changestyle", "Sets the style of a player feature.\n\nUsage: player_changecolor .\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 [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 \n- item: the melee 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 \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 \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 \n- value: one of 0 (resume) or 1 (freeze).", this.HandleCommand) + .Add("world_setday", "Sets the day to the specified value.\n\nUsage: world_setday .\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 \n- value: the target season (one of 'spring', 'summer', 'fall', 'winter').", this.HandleCommand) + .Add("world_downminelevel", "Goes down one mine level?", this.HandleCommand) + .Add("world_setminelevel", "Sets the mine level?\n\nUsage: world_setminelevel \n- value: The target level (a number between 1 and 120).", this.HandleCommand) + + .Add("show_game_files", "Opens the game folder.", this.HandleCommand) + .Add("show_data_files", "Opens the folder containing the save and log files.", this.HandleCommand); + } + + /// Handle a TrainerMod command. + /// The command name. + /// The command arguments. + private void HandleCommand(string name, string[] args) + { + switch (name) + { + case "type": + this.Monitor.Log($"[Int32: {int.MinValue} - {int.MaxValue}], [Int64: {long.MinValue} - {long.MaxValue}], [String: \"raw text\"], [Colour: r,g,b (EG: 128, 32, 255)]", LogLevel.Info); + break; - /// The event raised when the 'save' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleSave(object sender, EventArgsCommand e) - { - SaveGame.Save(); - } + case "save": + SaveGame.Save(); + break; - /// The event raised when the 'load' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleLoad(object sender, EventArgsCommand e) - { - Game1.hasLoadedGame = false; - Game1.activeClickableMenu = new LoadGameMenu(); - } + case "load": + Game1.hasLoadedGame = false; + Game1.activeClickableMenu = new LoadGameMenu(); + break; - /// The event raised when the 'player_setName' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerSetName(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 1) - { - string target = e.Command.CalledArgs[0]; - string[] validTargets = { "player", "pet", "farm" }; - if (validTargets.Contains(target)) - { - switch (target) + case "player_setname": + if (args.Length > 1) { - case "player": - Game1.player.Name = e.Command.CalledArgs[1]; - break; - case "pet": - this.Monitor.Log("Pets cannot currently be renamed.", LogLevel.Info); - break; - case "farm": - Game1.player.farmName = e.Command.CalledArgs[1]; - break; + string target = args[0]; + string[] validTargets = { "player", "farm" }; + if (validTargets.Contains(target)) + { + switch (target) + { + case "player": + Game1.player.Name = args[1]; + break; + case "farm": + Game1.player.farmName = args[1]; + break; + } + } + else + this.LogObjectInvalid(); } - } - else - this.LogObjectInvalid(); - } - else - this.LogObjectValueNotSpecified(); - } + else + this.LogObjectValueNotSpecified(); + break; - /// The event raised when the 'player_setMoney' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerSetMoney(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - string amountStr = e.Command.CalledArgs[0]; - if (amountStr == "inf") - this.InfiniteMoney = true; - else - { - this.InfiniteMoney = false; - int amount; - if (int.TryParse(amountStr, out amount)) + case "player_setmoney": + if (args.Any()) { - Game1.player.Money = amount; - this.Monitor.Log($"Set {Game1.player.Name}'s money to {Game1.player.Money}", LogLevel.Info); + string amountStr = args[0]; + if (amountStr == "inf") + this.InfiniteMoney = true; + else + { + this.InfiniteMoney = false; + int amount; + if (int.TryParse(amountStr, out amount)) + { + Game1.player.Money = amount; + this.Monitor.Log($"Set {Game1.player.Name}'s money to {Game1.player.Money}", LogLevel.Info); + } + else + this.LogValueNotInt32(); + } } else - this.LogValueNotInt32(); - } - } - else - this.LogValueNotSpecified(); - } + this.LogValueNotSpecified(); + break; - /// The event raised when the 'player_setStamina' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerSetStamina(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - string amountStr = e.Command.CalledArgs[0]; - if (amountStr == "inf") - this.InfiniteStamina = true; - else - { - this.InfiniteStamina = false; - int amount; - if (int.TryParse(amountStr, out amount)) + case "playet_setstamina": + if (args.Any()) { - Game1.player.Stamina = amount; - this.Monitor.Log($"Set {Game1.player.Name}'s stamina to {Game1.player.Stamina}", LogLevel.Info); + string amountStr = args[0]; + if (amountStr == "inf") + this.InfiniteStamina = true; + else + { + this.InfiniteStamina = false; + int amount; + if (int.TryParse(amountStr, out amount)) + { + Game1.player.Stamina = amount; + this.Monitor.Log($"Set {Game1.player.Name}'s stamina to {Game1.player.Stamina}", LogLevel.Info); + } + else + this.LogValueNotInt32(); + } } else - this.LogValueNotInt32(); - } - } - else - this.LogValueNotSpecified(); - } + this.LogValueNotSpecified(); + break; - /// The event raised when the 'player_setMaxStamina' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerSetMaxStamina(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - int amount; - if (int.TryParse(e.Command.CalledArgs[0], out amount)) - { - Game1.player.MaxStamina = amount; - this.Monitor.Log($"Set {Game1.player.Name}'s max stamina to {Game1.player.MaxStamina}", LogLevel.Info); - } - else - this.LogValueNotInt32(); - } - else - this.LogValueNotSpecified(); - } - - /// The event raised when the 'player_setLevel' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerSetLevel(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 1) - { - string skill = e.Command.CalledArgs[0]; - string[] skills = { "luck", "mining", "combat", "farming", "fishing", "foraging" }; - if (skills.Contains(skill)) - { - int level; - if (int.TryParse(e.Command.CalledArgs[1], out level)) + case "player_setmaxstamina": + if (args.Any()) { - switch (skill) + int amount; + if (int.TryParse(args[0], out amount)) { - case "luck": - Game1.player.LuckLevel = level; - break; - case "mining": - Game1.player.MiningLevel = level; - break; - case "combat": - Game1.player.CombatLevel = level; - break; - case "farming": - Game1.player.FarmingLevel = level; - break; - case "fishing": - Game1.player.FishingLevel = level; - break; - case "foraging": - Game1.player.ForagingLevel = level; - break; + Game1.player.MaxStamina = amount; + this.Monitor.Log($"Set {Game1.player.Name}'s max stamina to {Game1.player.MaxStamina}", LogLevel.Info); } + else + this.LogValueNotInt32(); } else - this.LogValueNotInt32(); - } - else - this.Monitor.Log(" is invalid", LogLevel.Error); - } - else - this.Monitor.Log(" and must be specified", LogLevel.Error); - } + this.LogValueNotSpecified(); + break; - /// The event raised when the 'player_setSpeed' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerSetSpeed(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - string amountStr = e.Command.CalledArgs[0]; - if (amountStr.IsInt()) - { - Game1.player.addedSpeed = amountStr.ToInt(); - this.Monitor.Log($"Set {Game1.player.Name}'s added speed to {Game1.player.addedSpeed}", LogLevel.Info); - } - else - this.LogValueNotInt32(); - } - else - this.LogValueNotSpecified(); - } - - /// The event raised when the 'player_changeColour' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerChangeColor(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 1) - { - string target = e.Command.CalledArgs[0]; - string[] validTargets = { "hair", "eyes", "pants" }; - if (validTargets.Contains(target)) - { - string[] colorHexes = e.Command.CalledArgs[1].Split(new[] { ',' }, 3); - if (colorHexes[0].IsInt() && colorHexes[1].IsInt() && colorHexes[2].IsInt()) + case "player_setlevel": + if (args.Length > 1) { - var color = new Color(colorHexes[0].ToInt(), colorHexes[1].ToInt(), colorHexes[2].ToInt()); - switch (target) + string skill = args[0]; + string[] skills = { "luck", "mining", "combat", "farming", "fishing", "foraging" }; + if (skills.Contains(skill)) { - case "hair": - Game1.player.hairstyleColor = color; - break; - case "eyes": - Game1.player.changeEyeColor(color); - break; - case "pants": - Game1.player.pantsColor = color; - break; + int level; + if (int.TryParse(args[1], out level)) + { + switch (skill) + { + case "luck": + Game1.player.LuckLevel = level; + break; + case "mining": + Game1.player.MiningLevel = level; + break; + case "combat": + Game1.player.CombatLevel = level; + break; + case "farming": + Game1.player.FarmingLevel = level; + break; + case "fishing": + Game1.player.FishingLevel = level; + break; + case "foraging": + Game1.player.ForagingLevel = level; + break; + } + } + else + this.LogValueNotInt32(); } + else + this.Monitor.Log(" is invalid", LogLevel.Error); } else - this.Monitor.Log(" is invalid", LogLevel.Error); - } - else - this.LogObjectInvalid(); - } - else - this.Monitor.Log(" and must be specified", LogLevel.Error); - } + this.Monitor.Log(" and must be specified", LogLevel.Error); + break; - /// The event raised when the 'player_changeStyle' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerChangeStyle(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 1) - { - string target = e.Command.CalledArgs[0]; - string[] validTargets = { "hair", "shirt", "skin", "acc", "shoe", "swim", "gender" }; - if (validTargets.Contains(target)) - { - if (e.Command.CalledArgs[1].IsInt()) + case "player_setspeed": + if (args.Any()) { - var styleID = e.Command.CalledArgs[1].ToInt(); - switch (target) + string amountStr = args[0]; + if (amountStr.IsInt()) { - case "hair": - Game1.player.changeHairStyle(styleID); - break; - case "shirt": - Game1.player.changeShirt(styleID); - break; - case "acc": - Game1.player.changeAccessory(styleID); - break; - case "skin": - Game1.player.changeSkinColor(styleID); - break; - case "shoe": - Game1.player.changeShoeColor(styleID); - break; - case "swim": - if (styleID == 0) - Game1.player.changeOutOfSwimSuit(); - else if (styleID == 1) - Game1.player.changeIntoSwimsuit(); - else - this.Monitor.Log(" must be 0 or 1 for this ", LogLevel.Error); - break; - case "gender": - if (styleID == 0) - Game1.player.changeGender(true); - else if (styleID == 1) - Game1.player.changeGender(false); - else - this.Monitor.Log(" must be 0 or 1 for this ", LogLevel.Error); - break; + Game1.player.addedSpeed = amountStr.ToInt(); + this.Monitor.Log($"Set {Game1.player.Name}'s added speed to {Game1.player.addedSpeed}", LogLevel.Info); } + else + this.LogValueNotInt32(); } else - this.LogValueInvalid(); - } - else - this.LogObjectInvalid(); - } - else - this.LogObjectValueNotSpecified(); - } + this.LogValueNotSpecified(); + break; - /// The event raised when the 'world_freezeTime' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleWorldFreezeTime(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - string valueStr = e.Command.CalledArgs[0]; - if (valueStr.IsInt()) - { - int value = valueStr.ToInt(); - if (value == 0 || value == 1) + case "player_changecolour": + if (args.Length > 1) { - this.FreezeTime = value == 1; - this.FrozenTime = this.FreezeTime ? Game1.timeOfDay : 0; - this.Monitor.Log("Time is now " + (this.FreezeTime ? "frozen" : "thawed"), LogLevel.Info); + string target = args[0]; + string[] validTargets = { "hair", "eyes", "pants" }; + if (validTargets.Contains(target)) + { + string[] colorHexes = args[1].Split(new[] { ',' }, 3); + if (colorHexes[0].IsInt() && colorHexes[1].IsInt() && colorHexes[2].IsInt()) + { + var color = new Color(colorHexes[0].ToInt(), colorHexes[1].ToInt(), colorHexes[2].ToInt()); + switch (target) + { + case "hair": + Game1.player.hairstyleColor = color; + break; + case "eyes": + Game1.player.changeEyeColor(color); + break; + case "pants": + Game1.player.pantsColor = color; + break; + } + } + else + this.Monitor.Log(" is invalid", LogLevel.Error); + } + else + this.LogObjectInvalid(); } else - this.Monitor.Log(" should be 0 or 1", LogLevel.Error); - } - else - this.LogValueNotInt32(); - } - else - this.LogValueNotSpecified(); - } + this.Monitor.Log(" and must be specified", LogLevel.Error); + break; - /// The event raised when the 'world_setTime' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleWorldSetTime(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - string timeStr = e.Command.CalledArgs[0]; - if (timeStr.IsInt()) - { - int time = timeStr.ToInt(); + case "player_changestyle": + if (args.Length > 1) + { + string target = args[0]; + string[] validTargets = { "hair", "shirt", "skin", "acc", "shoe", "swim", "gender" }; + if (validTargets.Contains(target)) + { + if (args[1].IsInt()) + { + var styleID = args[1].ToInt(); + switch (target) + { + case "hair": + Game1.player.changeHairStyle(styleID); + break; + case "shirt": + Game1.player.changeShirt(styleID); + break; + case "acc": + Game1.player.changeAccessory(styleID); + break; + case "skin": + Game1.player.changeSkinColor(styleID); + break; + case "shoe": + Game1.player.changeShoeColor(styleID); + break; + case "swim": + if (styleID == 0) + Game1.player.changeOutOfSwimSuit(); + else if (styleID == 1) + Game1.player.changeIntoSwimsuit(); + else + this.Monitor.Log(" must be 0 or 1 for this ", LogLevel.Error); + break; + case "gender": + if (styleID == 0) + Game1.player.changeGender(true); + else if (styleID == 1) + Game1.player.changeGender(false); + else + this.Monitor.Log(" must be 0 or 1 for this ", LogLevel.Error); + break; + } + } + else + this.LogValueInvalid(); + } + else + this.LogObjectInvalid(); + } + else + this.LogObjectValueNotSpecified(); + break; - if (time <= 2600 && time >= 600) + case "world_freezetime": + if (args.Any()) { - Game1.timeOfDay = e.Command.CalledArgs[0].ToInt(); - this.FrozenTime = this.FreezeTime ? Game1.timeOfDay : 0; - this.Monitor.Log($"Time set to: {Game1.timeOfDay}", LogLevel.Info); + string valueStr = args[0]; + if (valueStr.IsInt()) + { + int value = valueStr.ToInt(); + if (value == 0 || value == 1) + { + this.FreezeTime = value == 1; + this.FrozenTime = this.FreezeTime ? Game1.timeOfDay : 0; + this.Monitor.Log("Time is now " + (this.FreezeTime ? "frozen" : "thawed"), LogLevel.Info); + } + else + this.Monitor.Log(" should be 0 or 1", LogLevel.Error); + } + else + this.LogValueNotInt32(); } else - this.Monitor.Log(" should be between 600 and 2600 (06:00 AM - 02:00 AM [NEXT DAY])", LogLevel.Error); - } - else - this.LogValueNotInt32(); - } - else - this.LogValueNotSpecified(); - } + this.LogValueNotSpecified(); + break; - /// The event raised when the 'world_setDay' command is triggered. - /// The event sender. - /// The event arguments. - private void world_setDay(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - string dayStr = e.Command.CalledArgs[0]; + case "world_settime": + if (args.Any()) + { + string timeStr = args[0]; + if (timeStr.IsInt()) + { + int time = timeStr.ToInt(); - if (dayStr.IsInt()) - { - int day = dayStr.ToInt(); - if (day <= 28 && day > 0) - Game1.dayOfMonth = day; + if (time <= 2600 && time >= 600) + { + Game1.timeOfDay = args[0].ToInt(); + this.FrozenTime = this.FreezeTime ? Game1.timeOfDay : 0; + this.Monitor.Log($"Time set to: {Game1.timeOfDay}", LogLevel.Info); + } + else + this.Monitor.Log(" should be between 600 and 2600 (06:00 AM - 02:00 AM [NEXT DAY])", LogLevel.Error); + } + else + this.LogValueNotInt32(); + } else - this.Monitor.Log(" must be between 1 and 28", LogLevel.Error); - } - else - this.LogValueNotInt32(); - } - else - this.LogValueNotSpecified(); - } - - /// The event raised when the 'world_setSeason' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleWorldSetSeason(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - string season = e.Command.CalledArgs[0]; - string[] validSeasons = { "winter", "spring", "summer", "fall" }; - if (validSeasons.Contains(season)) - Game1.currentSeason = season; - else - this.LogValueInvalid(); - } - else - this.LogValueNotSpecified(); - } + this.LogValueNotSpecified(); + break; - /// The event raised when the 'player_setHealth' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerSetHealth(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - string amountStr = e.Command.CalledArgs[0]; + case "world_setday": + if (args.Any()) + { + string dayStr = args[0]; - if (amountStr == "inf") - this.InfiniteHealth = true; - else - { - this.InfiniteHealth = false; - if (amountStr.IsInt()) - Game1.player.health = amountStr.ToInt(); + if (dayStr.IsInt()) + { + int day = dayStr.ToInt(); + if (day <= 28 && day > 0) + Game1.dayOfMonth = day; + else + this.Monitor.Log(" must be between 1 and 28", LogLevel.Error); + } + else + this.LogValueNotInt32(); + } else - this.LogValueNotInt32(); - } - } - else - this.LogValueNotSpecified(); - } - - /// The event raised when the 'player_setMaxHealth' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerSetMaxHealth(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - string amountStr = e.Command.CalledArgs[0]; - if (amountStr.IsInt()) - Game1.player.maxHealth = amountStr.ToInt(); - else - this.LogValueNotInt32(); - } - else - this.LogValueNotSpecified(); - } + this.LogValueNotSpecified(); + break; - /// The event raised when the 'player_setImmunity' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerSetImmunity(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - string amountStr = e.Command.CalledArgs[0]; - if (amountStr.IsInt()) - Game1.player.immunity = amountStr.ToInt(); - else - this.LogValueNotInt32(); - } - else - this.LogValueNotSpecified(); - } + case "world_setseason": + if (args.Any()) + { + string season = args[0]; + string[] validSeasons = { "winter", "spring", "summer", "fall" }; + if (validSeasons.Contains(season)) + Game1.currentSeason = season; + else + this.LogValueInvalid(); + } + else + this.LogValueNotSpecified(); + break; - /// The event raised when the 'player_addItem' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerAddItem(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - string itemIdStr = e.Command.CalledArgs[0]; - if (itemIdStr.IsInt()) - { - int itemID = itemIdStr.ToInt(); - int count = 1; - int quality = 0; - if (e.Command.CalledArgs.Length > 1) + case "player_sethealth": + if (args.Any()) { - if (e.Command.CalledArgs[1].IsInt()) - count = e.Command.CalledArgs[1].ToInt(); + string amountStr = args[0]; + + if (amountStr == "inf") + this.InfiniteHealth = true; else { - this.Monitor.Log("[count] is invalid", LogLevel.Error); - return; + this.InfiniteHealth = false; + if (amountStr.IsInt()) + Game1.player.health = amountStr.ToInt(); + else + this.LogValueNotInt32(); } + } + else + this.LogValueNotSpecified(); + break; + + case "player_setmaxhealth": + if (args.Any()) + { + string amountStr = args[0]; + if (amountStr.IsInt()) + Game1.player.maxHealth = amountStr.ToInt(); + else + this.LogValueNotInt32(); + } + else + this.LogValueNotSpecified(); + break; + + case "player_setimmunity": + if (args.Any()) + { + string amountStr = args[0]; + if (amountStr.IsInt()) + Game1.player.immunity = amountStr.ToInt(); + else + this.LogValueNotInt32(); + } + else + this.LogValueNotSpecified(); + break; - if (e.Command.CalledArgs.Length > 2) + case "player_additem": + if (args.Any()) + { + string itemIdStr = args[0]; + if (itemIdStr.IsInt()) { - if (e.Command.CalledArgs[2].IsInt()) - quality = e.Command.CalledArgs[2].ToInt(); - else + int itemID = itemIdStr.ToInt(); + int count = 1; + int quality = 0; + if (args.Length > 1) { - this.Monitor.Log("[quality] is invalid", LogLevel.Error); - return; + if (args[1].IsInt()) + count = args[1].ToInt(); + else + { + this.Monitor.Log("[count] is invalid", LogLevel.Error); + return; + } + + if (args.Length > 2) + { + if (args[2].IsInt()) + quality = args[2].ToInt(); + else + { + this.Monitor.Log("[quality] is invalid", LogLevel.Error); + return; + } + } } + + var item = new Object(itemID, count) { quality = quality }; + + Game1.player.addItemByMenuIfNecessary(item); } + else + this.Monitor.Log(" is invalid", LogLevel.Error); } + else + this.LogObjectValueNotSpecified(); + break; - var item = new Object(itemID, count) { quality = quality }; + case "player_addmelee": + if (args.Any()) + { + if (args[0].IsInt()) + { + MeleeWeapon weapon = new MeleeWeapon(args[0].ToInt()); + Game1.player.addItemByMenuIfNecessary(weapon); + this.Monitor.Log($"Gave {weapon.Name} to {Game1.player.Name}", LogLevel.Info); + } + else + this.Monitor.Log(" is invalid", LogLevel.Error); + } + else + this.LogObjectValueNotSpecified(); + break; - Game1.player.addItemByMenuIfNecessary(item); - } - else - this.Monitor.Log(" is invalid", LogLevel.Error); - } - else - this.LogObjectValueNotSpecified(); - } + case "player_addring": + if (args.Any()) + { + if (args[0].IsInt()) + { + Ring ring = new Ring(args[0].ToInt()); + Game1.player.addItemByMenuIfNecessary(ring); + this.Monitor.Log($"Gave {ring.Name} to {Game1.player.Name}", LogLevel.Info); + } + else + this.Monitor.Log(" is invalid", LogLevel.Error); + } + else + this.LogObjectValueNotSpecified(); + break; - /// The event raised when the 'player_addMelee' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerAddMelee(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - if (e.Command.CalledArgs[0].IsInt()) - { - MeleeWeapon weapon = new MeleeWeapon(e.Command.CalledArgs[0].ToInt()); - Game1.player.addItemByMenuIfNecessary(weapon); - this.Monitor.Log($"Gave {weapon.Name} to {Game1.player.Name}", LogLevel.Info); - } - else - this.Monitor.Log(" is invalid", LogLevel.Error); - } - else - this.LogObjectValueNotSpecified(); - } + case "list_items": + { + var matches = this.GetItems(args).ToArray(); - /// The event raised when the 'player_addRing' command is triggered. - /// The event sender. - /// The event arguments. - private void HandlePlayerAddRing(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - if (e.Command.CalledArgs[0].IsInt()) - { - Ring ring = new Ring(e.Command.CalledArgs[0].ToInt()); - Game1.player.addItemByMenuIfNecessary(ring); - this.Monitor.Log($"Gave {ring.Name} to {Game1.player.Name}", LogLevel.Info); - } - else - this.Monitor.Log(" is invalid", LogLevel.Error); - } - else - this.LogObjectValueNotSpecified(); - } + // show matches + string summary = "Searching...\n"; + if (matches.Any()) + this.Monitor.Log(summary + this.GetTableString(matches, new[] { "type", "id", "name" }, val => new[] { val.Type.ToString(), val.ID.ToString(), val.Name }), LogLevel.Info); + else + this.Monitor.Log(summary + "No items found", LogLevel.Info); + } + break; - /// The event raised when the 'list_items' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleListItems(object sender, EventArgsCommand e) - { - var matches = this.GetItems(e.Command.CalledArgs).ToArray(); - - // show matches - string summary = "Searching...\n"; - if (matches.Any()) - this.Monitor.Log(summary + this.GetTableString(matches, new[] { "type", "id", "name" }, val => new[] { val.Type.ToString(), val.ID.ToString(), val.Name }), LogLevel.Info); - else - this.Monitor.Log(summary + "No items found", LogLevel.Info); - } + case "world_downminelevel": + Game1.nextMineLevel(); + break; - /// The event raised when the 'world_downMineLevel' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleWorldDownMineLevel(object sender, EventArgsCommand e) - { - Game1.nextMineLevel(); - } + case "world_setminelevel": + if (args.Any()) + { + if (args[0].IsInt()) + Game1.enterMine(true, args[0].ToInt(), ""); + else + this.LogValueNotInt32(); + } + else + this.LogValueNotSpecified(); + break; - /// The event raised when the 'world_setMineLevel' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleWorldSetMineLevel(object sender, EventArgsCommand e) - { - if (e.Command.CalledArgs.Length > 0) - { - if (e.Command.CalledArgs[0].IsInt()) - Game1.enterMine(true, e.Command.CalledArgs[0].ToInt(), ""); - else - this.LogValueNotInt32(); - } - else - this.LogValueNotSpecified(); - } + case "show_game_files": + Process.Start(Constants.ExecutionPath); + break; - /// The event raised when the 'show_game_files' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleShowGameFiles(object sender, EventArgsCommand e) - { - Process.Start(Constants.ExecutionPath); - } + case "show_data_files": + Process.Start(Constants.DataPath); + break; - /// The event raised when the 'show_data_files' command is triggered. - /// The event sender. - /// The event arguments. - private void HandleShowDataFiles(object sender, EventArgsCommand e) - { - Process.Start(Constants.DataPath); + default: + throw new NotImplementedException($"TrainerMod received unknown command '{name}'."); + } } /**** -- cgit From 1038f4e7041c18e1af28a627880e760fd2b221fe Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 13 Feb 2017 02:10:55 -0500 Subject: mark all Command methods deprecated (#199) --- src/StardewModdingAPI/Command.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Command.cs b/src/StardewModdingAPI/Command.cs index 3b1e5632..1cbb01ff 100644 --- a/src/StardewModdingAPI/Command.cs +++ b/src/StardewModdingAPI/Command.cs @@ -73,6 +73,7 @@ namespace StardewModdingAPI /// Encapsulates monitoring and logging. public static void CallCommand(string input, IMonitor monitor) { + Program.DeprecationManager.Warn("Command.CallCommand", "1.9", DeprecationLevel.Notice); Program.CommandManager.Trigger(input); } @@ -108,6 +109,7 @@ namespace StardewModdingAPI /// The command name to find. public static Command FindCommand(string name) { + Program.DeprecationManager.Warn("Command.FindCommand", "1.9", DeprecationLevel.Notice); if (name == null) return null; @@ -125,15 +127,9 @@ namespace StardewModdingAPI /// The command arguments. private static void Fire(string name, string[] args) { - // get legacy command Command command; if (!Command.LegacyCommands.TryGetValue(name, out command)) - { throw new InvalidOperationException($"Can't run command '{name}' because there's no such legacy command."); - return; - } - - // raise event command.Fire(); } } -- cgit From 253a4306cf468dd47ac67d328af565ee7798ea67 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 13 Feb 2017 16:50:54 -0500 Subject: mark Entoarox Framework incompatible up to 1.6.5 (#231) --- src/StardewModdingAPI/StardewModdingAPI.data.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json index 2a470a39..7744a5e3 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -83,7 +83,7 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. { "Name": "Entoarox Framework", "ID": "eacdb74b-4080-4452-b16b-93773cda5cf9", - "UpperVersion": "1.6.1", + "UpperVersion": "1.6.5", "UpdateUrl": "http://community.playstarbound.com/resources/4228" }, { -- cgit From 32d919a1c308c179e336cda26a8b0f6616185def Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 13 Feb 2017 17:40:59 -0500 Subject: update draw logic for Stardew Valley 1.2.6 (#231) --- src/StardewModdingAPI/Framework/SGame.cs | 152 +++++++++---------------------- 1 file changed, 41 insertions(+), 111 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index d1f500fb..baf74fd4 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -38,7 +38,7 @@ namespace StardewModdingAPI.Framework private bool IsWorldReady => this.AfterLoadTimer < 0; /// Whether the game is returning to the menu. - private bool IsExiting = false; + private bool IsExiting; /// Whether the game's zoom level is at 100% (i.e. nothing should be scaled). public bool ZoomLevelIsOne => Game1.options.zoomLevel.Equals(1.0f); @@ -167,9 +167,6 @@ namespace StardewModdingAPI.Framework /// The current game instance. public static SGame Instance { get; private set; } - /// The game's current frame rate, recalculated on each draw update. - public static float FramesPerSecond { get; private set; } - /// Whether we're in pseudo-debug mode, which shows information like FPS. public static bool Debug { get; private set; } @@ -365,9 +362,6 @@ namespace StardewModdingAPI.Framework [SuppressMessage("ReSharper", "RedundantTypeArgumentsOfMethod", Justification = "copied from game code as-is")] protected override void Draw(GameTime gameTime) { - // track frame rate - SGame.FramesPerSecond = 1 / (float)gameTime.ElapsedGameTime.TotalSeconds; - try { if (Game1.debugMode) @@ -457,9 +451,9 @@ namespace StardewModdingAPI.Framework else if ((int)Game1.gameMode == 11) { Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - Game1.spriteBatch.DrawString(Game1.smoothFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3685"), new Vector2(16f, 16f), Color.HotPink); - Game1.spriteBatch.DrawString(Game1.smoothFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3686"), new Vector2(16f, 32f), new Color(0, (int)byte.MaxValue, 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.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(); } else if (Game1.currentMinigame != null) @@ -523,13 +517,16 @@ namespace StardewModdingAPI.Framework string str1 = ""; for (int index = 0; (double)index < gameTime.TotalGameTime.TotalMilliseconds % 999.0 / 333.0; ++index) str1 += "."; - string s = Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3688") + str1; - string str2 = Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3689"); - int widthOfString = SpriteText.getWidthOfString(str2); + 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, str2, -1); + 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) { @@ -548,7 +545,6 @@ namespace StardewModdingAPI.Framework else { Microsoft.Xna.Framework.Rectangle rectangle; - Viewport viewport; if ((int)Game1.gameMode == 0) { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); @@ -595,7 +591,7 @@ namespace StardewModdingAPI.Framework 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 bounds1; + Microsoft.Xna.Framework.Rectangle bounds; if (Game1.displayFarmer && !Game1.player.swimming && (!Game1.player.isRidingHorse() && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(Game1.player.getTileLocation()))) { SpriteBatch spriteBatch = Game1.spriteBatch; @@ -605,8 +601,8 @@ namespace StardewModdingAPI.Framework Color white = Color.White; double num1 = 0.0; double x = (double)Game1.shadowTexture.Bounds.Center.X; - bounds1 = Game1.shadowTexture.Bounds; - double y = (double)bounds1.Center.Y; + 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; @@ -629,10 +625,10 @@ namespace StardewModdingAPI.Framework Microsoft.Xna.Framework.Rectangle? sourceRectangle = new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds); Color white = Color.White; double num1 = 0.0; - bounds1 = Game1.shadowTexture.Bounds; - double x = (double)bounds1.Center.X; - bounds1 = Game1.shadowTexture.Bounds; - double y = (double)bounds1.Center.Y; + 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; @@ -653,10 +649,10 @@ namespace StardewModdingAPI.Framework Microsoft.Xna.Framework.Rectangle? sourceRectangle = new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds); Color white = Color.White; double num1 = 0.0; - bounds1 = Game1.shadowTexture.Bounds; - double x = (double)bounds1.Center.X; - bounds1 = Game1.shadowTexture.Bounds; - double y = (double)bounds1.Center.Y; + 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; @@ -689,19 +685,9 @@ namespace StardewModdingAPI.Framework 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 && Game1.currentLocation.currentEvent.messageToScreen != null) + if (Game1.eventUp && Game1.currentLocation.currentEvent != null) { string messageToScreen = Game1.currentLocation.currentEvent.messageToScreen; - Color black = Color.Black; - Color white = Color.White; - double num1 = (double)(Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Width / 2) - (double)Game1.borderFont.MeasureString(Game1.currentLocation.currentEvent.messageToScreen).X / 2.0; - viewport = Game1.graphics.GraphicsDevice.Viewport; - double num2 = (double)(viewport.TitleSafeArea.Height - Game1.tileSize); - Vector2 position = new Vector2((float)num1, (float)num2); - double num3 = 0.0; - double num4 = 1.0; - double num5 = 0.999000012874603; - Game1.drawWithBorder(messageToScreen, black, white, position, (float)num3, (float)num4, (float)num5); } 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); @@ -793,23 +779,9 @@ namespace StardewModdingAPI.Framework if (Game1.farmEvent != null) Game1.farmEvent.draw(Game1.spriteBatch); if ((double)Game1.currentLocation.LightLevel > 0.0 && Game1.timeOfDay < 2000) - { - SpriteBatch spriteBatch = Game1.spriteBatch; - Texture2D fadeToBlackRect = Game1.fadeToBlackRect; - viewport = Game1.graphics.GraphicsDevice.Viewport; - Microsoft.Xna.Framework.Rectangle bounds2 = viewport.Bounds; - Color color = Color.Black * Game1.currentLocation.LightLevel; - spriteBatch.Draw(fadeToBlackRect, bounds2, color); - } + Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * Game1.currentLocation.LightLevel); if (Game1.screenGlow) - { - SpriteBatch spriteBatch = Game1.spriteBatch; - Texture2D fadeToBlackRect = Game1.fadeToBlackRect; - viewport = Game1.graphics.GraphicsDevice.Viewport; - Microsoft.Xna.Framework.Rectangle bounds2 = viewport.Bounds; - Color color = Game1.screenGlowColor * Game1.screenGlowAlpha; - spriteBatch.Draw(fadeToBlackRect, bounds2, color); - } + 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); @@ -843,66 +815,25 @@ namespace StardewModdingAPI.Framework 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)) - { - SpriteBatch spriteBatch = Game1.spriteBatch; - Texture2D staminaRect = Game1.staminaRect; - viewport = Game1.graphics.GraphicsDevice.Viewport; - Microsoft.Xna.Framework.Rectangle bounds2 = viewport.Bounds; - Color color = Color.OrangeRed * 0.45f; - spriteBatch.Draw(staminaRect, bounds2, color); - } + 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 num1 = -Game1.viewport.X % Game1.tileSize; - float num2 = (float)(-Game1.viewport.Y % Game1.tileSize); - int num3 = num1; - while (true) + 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) { - int num4 = num3; - viewport = Game1.graphics.GraphicsDevice.Viewport; - int width1 = viewport.Width; - if (num4 < width1) - { - SpriteBatch spriteBatch = Game1.spriteBatch; - Texture2D staminaRect = Game1.staminaRect; - int x = num3; - int y = (int)num2; - int width2 = 1; - viewport = Game1.graphics.GraphicsDevice.Viewport; - int height = viewport.Height; - Microsoft.Xna.Framework.Rectangle destinationRectangle = new Microsoft.Xna.Framework.Rectangle(x, y, width2, height); - Color color = Color.Red * 0.5f; - spriteBatch.Draw(staminaRect, destinationRectangle, color); - num3 += Game1.tileSize; - } - else - break; + 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 num5 = num2; - while (true) + float num2 = num1; + while ((double)num2 < (double)Game1.graphics.GraphicsDevice.Viewport.Height) { - double num4 = (double)num5; - viewport = Game1.graphics.GraphicsDevice.Viewport; - double height1 = (double)viewport.Height; - if (num4 < height1) - { - SpriteBatch spriteBatch = Game1.spriteBatch; - Texture2D staminaRect = Game1.staminaRect; - int x = num1; - int y = (int)num5; - viewport = Game1.graphics.GraphicsDevice.Viewport; - int width = viewport.Width; - int height2 = 1; - Microsoft.Xna.Framework.Rectangle destinationRectangle = new Microsoft.Xna.Framework.Rectangle(x, y, width, height2); - Color color = Color.Red * 0.5f; - spriteBatch.Draw(staminaRect, destinationRectangle, color); - num5 += (float)Game1.tileSize; - } - else - break; + 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) @@ -925,14 +856,13 @@ namespace StardewModdingAPI.Framework 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; - viewport = Game1.graphics.GraphicsDevice.Viewport; - int x1 = (viewport.TitleSafeArea.Width - Game1.dialogueWidth) / 2; - viewport = Game1.graphics.GraphicsDevice.Viewport; - rectangle = viewport.TitleSafeArea; + 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; @@ -952,9 +882,9 @@ namespace StardewModdingAPI.Framework Color dimGray = Color.DimGray; spriteBatch2.Draw(staminaRect, destinationRectangle2, dimGray); } - if (Game1.eventUp && (object)Game1.currentLocation != null && Game1.currentLocation.currentEvent != null) + if (Game1.eventUp && Game1.currentLocation != null && Game1.currentLocation.currentEvent != null) Game1.currentLocation.currentEvent.drawAfterMap(Game1.spriteBatch); - if (Game1.isRaining && (object)Game1.currentLocation != null && (Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert))) + if (Game1.isRaining && Game1.currentLocation != null && (Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert))) { SpriteBatch spriteBatch = Game1.spriteBatch; Texture2D staminaRect = Game1.staminaRect; -- cgit From 8d1b7cbfb89892fff180c68c81a69bc41dbb9495 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Feb 2017 11:46:41 -0500 Subject: mark more incompatible mods (#231) --- src/StardewModdingAPI/StardewModdingAPI.data.json | 56 +++++++++++++++++------ 1 file changed, 42 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json index 7744a5e3..054ebb9c 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -25,20 +25,6 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", "ForceCompatibleVersion": "^1.5-EntoPatch" }, - { - "Name": "CJB Cheats Menu", - "ID": "CJBCheatsMenu", - "UpperVersion": "1.12", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4", - "ForceCompatibleVersion": "^1.12-EntoPatch" - }, - { - "Name": "CJB Item Spawner", - "ID": "CJBItemSpawner", - "UpperVersion": "1.5", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93", - "ForceCompatibleVersion": "^1.5-EntoPatch" - }, /* versions not compatible with Stardew Valley 1.2+ */ { @@ -74,6 +60,18 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "UpperVersion": "1.4", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/211" }, + { + "Name": "CJB Cheats Menu", + "ID": "CJBCheatsMenu", + "UpperVersion": "1.13", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4" + }, + { + "Name": "CJB Item Spawner", + "ID": "CJBItemSpawner", + "UpperVersion": "1.6", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93" + }, { "Name": "Enemy Health Bars", "ID": "SPDHealthBar", @@ -139,5 +137,35 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "ID": "StarDustCore", "UpperVersion": "1.0", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/683" + }, + { + "Name": "Zoryn's Better RNG", + "ID": "76b6d1e1-f7ba-4d72-8c32-5a1e6d2716f6", + "UpperVersion": "1.5", + "UpdateUrl": "http://community.playstarbound.com/threads/108756" + }, + { + "Name": "Zoryn's Calendar Anywhere", + "ID": "a41c01cd-0437-43eb-944f-78cb5a53002a", + "UpperVersion": "1.5", + "UpdateUrl": "http://community.playstarbound.com/threads/108756" + }, + { + "Name": "Zoryn's Health Bars", + "ID": "HealthBars.dll", + "UpperVersion": "1.5", + "UpdateUrl": "http://community.playstarbound.com/threads/108756" + }, + { + "Name": "Zoryn's Movement Mod", + "ID": "8a632929-8335-484f-87dd-c29d2ba3215d", + "UpperVersion": "1.5", + "UpdateUrl": "http://community.playstarbound.com/threads/108756" + }, + { + "Name": "Zoryn's Regen Mode", + "ID": "dfac4383-1b6b-4f33-ae4e-37fc23e5252e", + "UpperVersion": "1.5", + "UpdateUrl": "http://community.playstarbound.com/threads/108756" } ] -- cgit From 548cbcecc41e88413885b1fcce7504a627640826 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Feb 2017 13:07:30 -0500 Subject: mark two internal classes internal --- src/StardewModdingAPI/Framework/Manifest.cs | 39 ++++++++++++++++++++++ .../Framework/Serialisation/JsonHelper.cs | 2 +- src/StardewModdingAPI/Manifest.cs | 39 ---------------------- src/StardewModdingAPI/Mod.cs | 6 ++-- src/StardewModdingAPI/StardewModdingAPI.csproj | 2 +- 5 files changed, 44 insertions(+), 44 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Manifest.cs delete mode 100644 src/StardewModdingAPI/Manifest.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/Manifest.cs b/src/StardewModdingAPI/Framework/Manifest.cs new file mode 100644 index 00000000..189da9a8 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Manifest.cs @@ -0,0 +1,39 @@ +using System; +using Newtonsoft.Json; +using StardewModdingAPI.Framework.Serialisation; + +namespace StardewModdingAPI.Framework +{ + /// A manifest which describes a mod for SMAPI. + internal class Manifest : IManifest + { + /********* + ** Accessors + *********/ + /// The mod name. + public string Name { get; set; } + + /// A brief description of the mod. + public string Description { get; set; } + + /// The mod author's name. + public string Author { get; set; } + + /// The mod version. + [JsonConverter(typeof(SemanticVersionConverter))] + public ISemanticVersion Version { get; set; } + + /// The minimum SMAPI version required by this mod, if any. + public string MinimumApiVersion { get; set; } + + /// The name of the DLL in the directory that has the method. + public string EntryDll { get; set; } + + /// The unique mod ID. + public string UniqueID { get; set; } + + /// Whether the mod uses per-save config files. + [Obsolete("Use " + nameof(Mod) + "." + nameof(Mod.Helper) + "." + nameof(IModHelper.ReadConfig) + " instead")] + public bool PerSaveConfigs { get; set; } + } +} diff --git a/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs b/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs index 3809666f..26d937a5 100644 --- a/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs +++ b/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs @@ -6,7 +6,7 @@ using StardewModdingAPI.Advanced; namespace StardewModdingAPI.Framework.Serialisation { /// Encapsulates SMAPI's JSON file parsing. - public class JsonHelper + internal class JsonHelper { /********* ** Accessors diff --git a/src/StardewModdingAPI/Manifest.cs b/src/StardewModdingAPI/Manifest.cs deleted file mode 100644 index baacf954..00000000 --- a/src/StardewModdingAPI/Manifest.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using Newtonsoft.Json; -using StardewModdingAPI.Framework.Serialisation; - -namespace StardewModdingAPI -{ - /// A manifest which describes a mod for SMAPI. - public class Manifest : IManifest - { - /********* - ** Accessors - *********/ - /// The mod name. - public string Name { get; set; } - - /// A brief description of the mod. - public string Description { get; set; } - - /// The mod author's name. - public string Author { get; set; } - - /// The mod version. - [JsonConverter(typeof(SemanticVersionConverter))] - public ISemanticVersion Version { get; set; } - - /// The minimum SMAPI version required by this mod, if any. - public string MinimumApiVersion { get; set; } - - /// The name of the DLL in the directory that has the method. - public string EntryDll { get; set; } - - /// The unique mod ID. - public string UniqueID { get; set; } - - /// Whether the mod uses per-save config files. - [Obsolete("Use " + nameof(Mod) + "." + nameof(Mod.Helper) + "." + nameof(IModHelper.ReadConfig) + " instead")] - public bool PerSaveConfigs { get; set; } - } -} diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs index 45534e16..c8456a29 100644 --- a/src/StardewModdingAPI/Mod.cs +++ b/src/StardewModdingAPI/Mod.cs @@ -50,11 +50,11 @@ namespace StardewModdingAPI } } - /// The full path to the per-save configs folder (if is true). + /// The full path to the per-save configs folder (if is true). [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.ReadJsonFile) + " instead")] public string PerSaveConfigFolder => this.GetPerSaveConfigFolder(); - /// The full path to the per-save configuration file for the current save (if is true). + /// The full path to the per-save configuration file for the current save (if is true). [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.ReadJsonFile) + " instead")] public string PerSaveConfigPath { @@ -82,7 +82,7 @@ namespace StardewModdingAPI /********* ** Private methods *********/ - /// Get the full path to the per-save configuration file for the current save (if is true). + /// Get the full path to the per-save configuration file for the current save (if is true). [Obsolete] private string GetPerSaveConfigFolder() { diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index ffeb8e2e..1e896d4b 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -186,7 +186,7 @@ - + -- cgit From e804ed5479b3fffbe060e11d31e55499cab842f9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Feb 2017 13:11:43 -0500 Subject: mark one constant internal --- src/StardewModdingAPI/Constants.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 438c869c..db0c2622 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -47,9 +47,6 @@ namespace StardewModdingAPI /// The directory path containing the current save's data (if a save is loaded and the directory exists). public static string CurrentSavePath => Constants.CurrentSavePathExists ? Constants.RawSavePath : ""; - /// Whether a player save has been loaded. - public static bool PlayerNull => !Game1.hasLoadedGame || Game1.player == null || string.IsNullOrEmpty(Game1.player.name); - /// The path to the current assembly being executing. public static string ExecutionPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); @@ -77,6 +74,9 @@ namespace StardewModdingAPI /// Whether the directory containing the current save's data exists on disk. internal static bool CurrentSavePathExists => Directory.Exists(Constants.RawSavePath); + /// Whether a player save has been loaded. + internal static bool PlayerNull => !Game1.hasLoadedGame || Game1.player == null || string.IsNullOrEmpty(Game1.player.name); + /********* ** Protected methods -- cgit From f140e844ed1e52d1f5955e07a397f44ef9ab3233 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Feb 2017 22:06:06 -0500 Subject: streamline startup a bit --- src/StardewModdingAPI/Framework/Monitor.cs | 2 +- src/StardewModdingAPI/Program.cs | 56 +++++++++++++----------------- 2 files changed, 26 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/Monitor.cs b/src/StardewModdingAPI/Framework/Monitor.cs index 3a9276a0..c1735917 100644 --- a/src/StardewModdingAPI/Framework/Monitor.cs +++ b/src/StardewModdingAPI/Framework/Monitor.cs @@ -82,7 +82,7 @@ namespace StardewModdingAPI.Framework public void ExitGameImmediately(string reason) { Program.ExitGameImmediately(this.Source, reason); - Program.gamePtr.Exit(); + Program.GameInstance.Exit(); } /// Log a fatal error message. diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 58b86e8f..c3f8f8d8 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -59,21 +59,12 @@ namespace StardewModdingAPI /// Whether the game is currently running. private static bool ready; - /// The underlying game assembly. - private static Assembly StardewAssembly; - - /// The underlying type. - private static Type StardewProgramType; - - /// The field containing game's main instance. - private static FieldInfo StardewGameInfo; - /********* ** Accessors *********/ /// The underlying game instance. - internal static SGame gamePtr; + internal static SGame GameInstance; /// The number of mods currently loaded by SMAPI. internal static int ModsLoaded; @@ -185,8 +176,8 @@ namespace StardewModdingAPI Program.CancellationTokenSource.Cancel(); if (Program.ready) { - Program.gamePtr.Exiting += (sender, e) => Program.PressAnyKeyToExit(); - Program.gamePtr.Exit(); + Program.GameInstance.Exiting += (sender, e) => Program.PressAnyKeyToExit(); + Program.GameInstance.Exit(); } } @@ -226,29 +217,31 @@ namespace StardewModdingAPI { try { - // load the game assembly - Program.Monitor.Log("Loading game..."); - Program.StardewAssembly = Assembly.UnsafeLoadFrom(Program.GameExecutablePath); - Program.StardewProgramType = Program.StardewAssembly.GetType("StardewValley.Program", true); - Program.StardewGameInfo = Program.StardewProgramType.GetField("gamePtr"); - Game1.version += $" | SMAPI {Constants.ApiVersion}"; - - // add error interceptors + // add error handlers #if SMAPI_FOR_WINDOWS Application.ThreadException += (sender, e) => Program.Monitor.Log($"Critical thread exception: {e.Exception.GetLogSummary()}", LogLevel.Error); Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); #endif AppDomain.CurrentDomain.UnhandledException += (sender, e) => Program.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error); - // initialise game instance - Program.gamePtr = new SGame(Program.Monitor) { IsMouseVisible = false }; - Program.gamePtr.Exiting += (sender, e) => Program.ready = false; - Program.gamePtr.Window.ClientSizeChanged += (sender, e) => GraphicsEvents.InvokeResize(Program.Monitor, sender, e); - Program.gamePtr.Window.Title = $"Stardew Valley - Version {Game1.version}"; - Program.StardewGameInfo.SetValue(Program.StardewProgramType, Program.gamePtr); - - // patch graphics - Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; + // initialise game + { + // load assembly + Program.Monitor.Log("Loading game..."); + Assembly gameAssembly = Assembly.UnsafeLoadFrom(Program.GameExecutablePath); + Type gameProgramType = gameAssembly.GetType("StardewValley.Program", true); + + // set Game1 instance + Program.GameInstance = new SGame(Program.Monitor); + Program.GameInstance.Exiting += (sender, e) => Program.ready = false; + Program.GameInstance.Window.ClientSizeChanged += (sender, e) => GraphicsEvents.InvokeResize(Program.Monitor, sender, e); + Program.GameInstance.Window.Title = $"Stardew Valley - Version {Game1.version}"; + gameProgramType.GetField("gamePtr").SetValue(gameProgramType, Program.GameInstance); + + // configure + Game1.version += $" | SMAPI {Constants.ApiVersion}"; + Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; + } // load mods Program.LoadMods(); @@ -262,7 +255,8 @@ namespace StardewModdingAPI new Thread(() => { // wait for the game to load up - while (!Program.ready) Thread.Sleep(1000); + while (!Program.ready) + Thread.Sleep(1000); // register help command Program.CommandManager.Add("SMAPI", "help", "Lists all commands | 'help ' returns command description", Program.HandleHelpCommand); @@ -290,7 +284,7 @@ namespace StardewModdingAPI try { Program.ready = true; - Program.gamePtr.Run(); + Program.GameInstance.Run(); } finally { -- cgit From 153c04535fddfb3966d402fcd018760d981a1a34 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Feb 2017 23:37:26 -0500 Subject: fix typo --- src/StardewModdingAPI/StardewModdingAPI.data.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json index 054ebb9c..86cb3be0 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -163,7 +163,7 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "UpdateUrl": "http://community.playstarbound.com/threads/108756" }, { - "Name": "Zoryn's Regen Mode", + "Name": "Zoryn's Regen Mod", "ID": "dfac4383-1b6b-4f33-ae4e-37fc23e5252e", "UpperVersion": "1.5", "UpdateUrl": "http://community.playstarbound.com/threads/108756" -- cgit From 2c6ab6805de0e0a21d5191838237db04aa0176ec Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 15 Feb 2017 16:27:40 -0500 Subject: mark more incompatible mods (#231) --- src/StardewModdingAPI/StardewModdingAPI.data.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json index 86cb3be0..9ee2cb98 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -34,6 +34,12 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/257", "UnofficialUpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/518" }, + { + "Name": "Almighty Tool", + "ID": "AlmightyTool.dll", + "UpperVersion": "1.1.1", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/439" + }, { "Name": "Better Sprinklers", "ID": "SPDSprinklersMod", @@ -72,6 +78,12 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "UpperVersion": "1.6", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93" }, + { + "Name": "Cooking Skill", + "ID": "CookingSkill", + "UpperVersion": "1.0.3", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/522" + }, { "Name": "Enemy Health Bars", "ID": "SPDHealthBar", @@ -114,6 +126,12 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "UpperVersion": "0.5", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/237" }, + { + "Name": "Point-and-Plant", + "ID": "PointAndPlant.dll", + "UpperVersion": "1.0.2", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/572" + }, { "Name": "Reusable Wallpapers", "ID": "dae1b553-2e39-43e7-8400-c7c5c836134b", -- cgit From 176eddbf7b70934c2665aa3a0ac8b46bef04012a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 16 Feb 2017 00:54:41 -0500 Subject: make SMAPI core non-static, eliminate direct access between core components --- src/StardewModdingAPI/Command.cs | 39 ++- src/StardewModdingAPI/Config.cs | 18 +- src/StardewModdingAPI/Constants.cs | 3 - src/StardewModdingAPI/Events/PlayerEvents.cs | 18 +- src/StardewModdingAPI/Events/TimeEvents.cs | 16 +- .../Framework/InternalExtensions.cs | 16 +- src/StardewModdingAPI/Framework/Monitor.cs | 9 +- .../Framework/RequestExitDelegate.cs | 7 + src/StardewModdingAPI/Log.cs | 16 +- src/StardewModdingAPI/Mod.cs | 24 +- src/StardewModdingAPI/Program.cs | 287 +++++++++++---------- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 12 files changed, 294 insertions(+), 160 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/RequestExitDelegate.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Command.cs b/src/StardewModdingAPI/Command.cs index 1cbb01ff..6b056ce7 100644 --- a/src/StardewModdingAPI/Command.cs +++ b/src/StardewModdingAPI/Command.cs @@ -12,12 +12,22 @@ namespace StardewModdingAPI /********* ** Properties *********/ - /**** - ** SMAPI - ****/ /// The commands registered with SMAPI. private static readonly IDictionary LegacyCommands = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + /// Manages console commands. + private static CommandManager CommandManager; + + /// Manages deprecation warnings. + private static DeprecationManager DeprecationManager; + + /// Tracks the installed mods. + private static ModRegistry ModRegistry; + + + /********* + ** Accessors + *********/ /// The event raised when this command is submitted through the console. public event EventHandler CommandFired; @@ -43,6 +53,17 @@ namespace StardewModdingAPI /**** ** Command ****/ + /// Injects types required for backwards compatibility. + /// Manages console commands. + /// Manages deprecation warnings. + /// Tracks the installed mods. + internal static void Shim(CommandManager commandManager, DeprecationManager deprecationManager, ModRegistry modRegistry) + { + Command.CommandManager = commandManager; + Command.DeprecationManager = deprecationManager; + Command.ModRegistry = modRegistry; + } + /// Construct an instance. /// The name of the command. /// A human-readable description of what the command does. @@ -73,8 +94,8 @@ namespace StardewModdingAPI /// Encapsulates monitoring and logging. public static void CallCommand(string input, IMonitor monitor) { - Program.DeprecationManager.Warn("Command.CallCommand", "1.9", DeprecationLevel.Notice); - Program.CommandManager.Trigger(input); + Command.DeprecationManager.Warn("Command.CallCommand", "1.9", DeprecationLevel.Notice); + Command.CommandManager.Trigger(input); } /// Register a command with SMAPI. @@ -86,18 +107,18 @@ namespace StardewModdingAPI name = name?.Trim().ToLower(); // raise deprecation warning - Program.DeprecationManager.Warn("Command.RegisterCommand", "1.9", DeprecationLevel.Notice); + Command.DeprecationManager.Warn("Command.RegisterCommand", "1.9", DeprecationLevel.Notice); // validate if (Command.LegacyCommands.ContainsKey(name)) throw new InvalidOperationException($"The '{name}' command is already registered!"); // add command - string modName = Program.ModRegistry.GetModFromStack() ?? ""; + string modName = Command.ModRegistry.GetModFromStack() ?? ""; string documentation = args?.Length > 0 ? $"{description} - {string.Join(", ", args)}" : description; - Program.CommandManager.Add(modName, name, documentation, Command.Fire); + Command.CommandManager.Add(modName, name, documentation, Command.Fire); // add legacy command Command command = new Command(name, description, args); @@ -109,7 +130,7 @@ namespace StardewModdingAPI /// The command name to find. public static Command FindCommand(string name) { - Program.DeprecationManager.Warn("Command.FindCommand", "1.9", DeprecationLevel.Notice); + Command.DeprecationManager.Warn("Command.FindCommand", "1.9", DeprecationLevel.Notice); if (name == null) return null; diff --git a/src/StardewModdingAPI/Config.cs b/src/StardewModdingAPI/Config.cs index 037c0fdf..f253930d 100644 --- a/src/StardewModdingAPI/Config.cs +++ b/src/StardewModdingAPI/Config.cs @@ -11,6 +11,13 @@ namespace StardewModdingAPI [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public abstract class Config { + /********* + ** Properties + *********/ + /// Manages deprecation warnings. + private static DeprecationManager DeprecationManager; + + /********* ** Accessors *********/ @@ -26,6 +33,13 @@ namespace StardewModdingAPI /********* ** Public methods *********/ + /// Injects types required for backwards compatibility. + /// Manages deprecation warnings. + internal static void Shim(DeprecationManager deprecationManager) + { + Config.DeprecationManager = deprecationManager; + } + /// Construct an instance of the config class. /// The config class type. [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] @@ -111,8 +125,8 @@ namespace StardewModdingAPI /// Construct an instance. protected Config() { - Program.DeprecationManager.Warn("the Config class", "1.0", DeprecationLevel.Notice); - Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.BaseConfigPath)}", "1.0"); // typically used to construct config, avoid redundant warnings + Config.DeprecationManager.Warn("the Config class", "1.0", DeprecationLevel.Notice); + Config.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.BaseConfigPath)}", "1.0"); // typically used to construct config, avoid redundant warnings } } diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index db0c2622..262ba61d 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -59,9 +59,6 @@ namespace StardewModdingAPI /// The GitHub repository to check for updates. internal const string GitHubRepository = "Pathoschild/SMAPI"; - /// The title of the SMAPI console window. - internal static string ConsoleTitle => $"Stardew Modding API Console - Version {Constants.ApiVersion} - Mods Loaded: {Program.ModsLoaded}"; - /// The file path for the SMAPI configuration file. internal static string ApiConfigPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.config.json"); diff --git a/src/StardewModdingAPI/Events/PlayerEvents.cs b/src/StardewModdingAPI/Events/PlayerEvents.cs index 99bdac16..996077ab 100644 --- a/src/StardewModdingAPI/Events/PlayerEvents.cs +++ b/src/StardewModdingAPI/Events/PlayerEvents.cs @@ -10,6 +10,13 @@ namespace StardewModdingAPI.Events /// Events raised when the player data changes. public static class PlayerEvents { + /********* + ** Properties + *********/ + /// Manages deprecation warnings. + private static DeprecationManager DeprecationManager; + + /********* ** Events *********/ @@ -31,6 +38,13 @@ namespace StardewModdingAPI.Events /********* ** Internal methods *********/ + /// Injects types required for backwards compatibility. + /// Manages deprecation warnings. + internal static void Shim(DeprecationManager deprecationManager) + { + PlayerEvents.DeprecationManager = deprecationManager; + } + /// Raise a event. /// Encapsulates monitoring and logging. /// Whether the save has been loaded. This is always true. @@ -42,7 +56,7 @@ namespace StardewModdingAPI.Events string name = $"{nameof(PlayerEvents)}.{nameof(PlayerEvents.LoadedGame)}"; Delegate[] handlers = PlayerEvents.LoadedGame.GetInvocationList(); - Program.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Notice); + PlayerEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Notice); monitor.SafelyRaiseGenericEvent(name, handlers, null, loaded); } @@ -58,7 +72,7 @@ namespace StardewModdingAPI.Events string name = $"{nameof(PlayerEvents)}.{nameof(PlayerEvents.FarmerChanged)}"; Delegate[] handlers = PlayerEvents.FarmerChanged.GetInvocationList(); - Program.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Notice); + PlayerEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Notice); monitor.SafelyRaiseGenericEvent(name, handlers, null, new EventArgsFarmerChanged(priorFarmer, newFarmer)); } diff --git a/src/StardewModdingAPI/Events/TimeEvents.cs b/src/StardewModdingAPI/Events/TimeEvents.cs index a140a223..0f9257c1 100644 --- a/src/StardewModdingAPI/Events/TimeEvents.cs +++ b/src/StardewModdingAPI/Events/TimeEvents.cs @@ -6,6 +6,13 @@ namespace StardewModdingAPI.Events /// Events raised when the in-game date or time changes. public static class TimeEvents { + /********* + ** Properties + *********/ + /// Manages deprecation warnings. + private static DeprecationManager DeprecationManager; + + /********* ** Events *********/ @@ -32,6 +39,13 @@ namespace StardewModdingAPI.Events /********* ** Internal methods *********/ + /// Injects types required for backwards compatibility. + /// Manages deprecation warnings. + internal static void Shim(DeprecationManager deprecationManager) + { + TimeEvents.DeprecationManager = deprecationManager; + } + /// Raise an event. /// Encapsulates monitoring and logging. internal static void InvokeAfterDayStarted(IMonitor monitor) @@ -88,7 +102,7 @@ namespace StardewModdingAPI.Events string name = $"{nameof(TimeEvents)}.{nameof(TimeEvents.OnNewDay)}"; Delegate[] handlers = TimeEvents.OnNewDay.GetInvocationList(); - Program.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Notice); + TimeEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Notice); monitor.SafelyRaiseGenericEvent(name, handlers, null, new EventArgsNewDay(priorDay, newDay, isTransitioning)); } } diff --git a/src/StardewModdingAPI/Framework/InternalExtensions.cs b/src/StardewModdingAPI/Framework/InternalExtensions.cs index c4bd2d35..4ca79518 100644 --- a/src/StardewModdingAPI/Framework/InternalExtensions.cs +++ b/src/StardewModdingAPI/Framework/InternalExtensions.cs @@ -8,9 +8,23 @@ namespace StardewModdingAPI.Framework /// Provides extension methods for SMAPI's internal use. internal static class InternalExtensions { + /********* + ** Properties + *********/ + /// Tracks the installed mods. + private static ModRegistry ModRegistry; + + /********* ** Public methods *********/ + /// Injects types required for backwards compatibility. + /// Tracks the installed mods. + internal static void Shim(ModRegistry modRegistry) + { + InternalExtensions.ModRegistry = modRegistry; + } + /**** ** IMonitor ****/ @@ -103,7 +117,7 @@ namespace StardewModdingAPI.Framework foreach (Delegate handler in handlers) { - string modName = Program.ModRegistry.GetModFrom(handler) ?? "an unknown mod"; // suppress stack trace for unknown mods, not helpful here + string modName = InternalExtensions.ModRegistry.GetModFrom(handler) ?? "an unknown mod"; // suppress stack trace for unknown mods, not helpful here deprecationManager.Warn(modName, nounPhrase, version, severity); } } diff --git a/src/StardewModdingAPI/Framework/Monitor.cs b/src/StardewModdingAPI/Framework/Monitor.cs index c1735917..64075f2f 100644 --- a/src/StardewModdingAPI/Framework/Monitor.cs +++ b/src/StardewModdingAPI/Framework/Monitor.cs @@ -34,6 +34,9 @@ namespace StardewModdingAPI.Framework [LogLevel.Alert] = ConsoleColor.Magenta }; + /// 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. + private RequestExitDelegate RequestExit; + /********* ** Accessors @@ -55,7 +58,8 @@ namespace StardewModdingAPI.Framework /// The name of the module which logs messages using this instance. /// Manages access to the console output. /// The log file to which to write messages. - public Monitor(string source, ConsoleInterceptionManager consoleManager, LogFileManager logFile) + /// A delegate which requests that SMAPI immediately exit the game. + public Monitor(string source, ConsoleInterceptionManager consoleManager, LogFileManager logFile, RequestExitDelegate requestExitDelegate) { // validate if (string.IsNullOrWhiteSpace(source)) @@ -81,8 +85,7 @@ namespace StardewModdingAPI.Framework /// The reason for the shutdown. public void ExitGameImmediately(string reason) { - Program.ExitGameImmediately(this.Source, reason); - Program.GameInstance.Exit(); + this.RequestExit(this.Source, reason); } /// Log a fatal error message. diff --git a/src/StardewModdingAPI/Framework/RequestExitDelegate.cs b/src/StardewModdingAPI/Framework/RequestExitDelegate.cs new file mode 100644 index 00000000..12d0ea0c --- /dev/null +++ b/src/StardewModdingAPI/Framework/RequestExitDelegate.cs @@ -0,0 +1,7 @@ +namespace StardewModdingAPI.Framework +{ + /// 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. + /// The module which requested an immediate exit. + /// The reason provided for the shutdown. + internal delegate void RequestExitDelegate(string module, string reason); +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Log.cs b/src/StardewModdingAPI/Log.cs index 5cb794f9..da98baba 100644 --- a/src/StardewModdingAPI/Log.cs +++ b/src/StardewModdingAPI/Log.cs @@ -9,6 +9,13 @@ namespace StardewModdingAPI [Obsolete("Use " + nameof(Mod) + "." + nameof(Mod.Monitor))] public static class Log { + /********* + ** Properties + *********/ + /// Manages deprecation warnings. + private static DeprecationManager DeprecationManager; + + /********* ** Accessors *********/ @@ -22,6 +29,13 @@ namespace StardewModdingAPI /********* ** Public methods *********/ + /// Injects types required for backwards compatibility. + /// Manages deprecation warnings. + internal static void Shim(DeprecationManager deprecationManager) + { + Log.DeprecationManager = deprecationManager; + } + /**** ** Exceptions ****/ @@ -292,7 +306,7 @@ namespace StardewModdingAPI /// Raise a deprecation warning. private static void WarnDeprecated() { - Program.DeprecationManager.Warn($"the {nameof(Log)} class", "1.1", DeprecationLevel.Notice); + Log.DeprecationManager.Warn($"the {nameof(Log)} class", "1.1", DeprecationLevel.Notice); } /// Get the name of the mod logging a message from the stack. diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs index c8456a29..d3fe882f 100644 --- a/src/StardewModdingAPI/Mod.cs +++ b/src/StardewModdingAPI/Mod.cs @@ -10,6 +10,9 @@ namespace StardewModdingAPI /********* ** Properties *********/ + /// Manages deprecation warnings. + private static DeprecationManager DeprecationManager; + /// The backing field for . private string _pathOnDisk; @@ -32,7 +35,7 @@ namespace StardewModdingAPI { get { - Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0", DeprecationLevel.Notice); + Mod.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0", DeprecationLevel.Notice); return this._pathOnDisk; } internal set { this._pathOnDisk = value; } @@ -44,8 +47,8 @@ namespace StardewModdingAPI { get { - Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.BaseConfigPath)}", "1.0", DeprecationLevel.Notice); - Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0"); // avoid redundant warnings + Mod.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.BaseConfigPath)}", "1.0", DeprecationLevel.Notice); + Mod.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0"); // avoid redundant warnings return Path.Combine(this.PathOnDisk, "config.json"); } } @@ -60,8 +63,8 @@ namespace StardewModdingAPI { get { - Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigPath)}", "1.0", DeprecationLevel.Info); - Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigFolder)}", "1.0"); // avoid redundant warnings + Mod.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigPath)}", "1.0", DeprecationLevel.Info); + Mod.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigFolder)}", "1.0"); // avoid redundant warnings return Constants.CurrentSavePathExists ? Path.Combine(this.PerSaveConfigFolder, Constants.SaveFolderName + ".json") : ""; } } @@ -70,6 +73,13 @@ namespace StardewModdingAPI /********* ** Public methods *********/ + /// Injects types required for backwards compatibility. + /// Manages deprecation warnings. + internal static void Shim(DeprecationManager deprecationManager) + { + Mod.DeprecationManager = deprecationManager; + } + /// The mod entry point, called after the mod is first loaded. [Obsolete("This overload is obsolete since SMAPI 1.0.")] public virtual void Entry(params object[] objects) { } @@ -86,8 +96,8 @@ namespace StardewModdingAPI [Obsolete] private string GetPerSaveConfigFolder() { - Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigFolder)}", "1.0", DeprecationLevel.Notice); - Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0"); // avoid redundant warnings + Mod.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigFolder)}", "1.0", DeprecationLevel.Notice); + Mod.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0"); // avoid redundant warnings if (!((Manifest)this.ModManifest).PerSaveConfigs) { diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index c3f8f8d8..0857d41b 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI ** Properties *********/ /// The target game platform. - private static readonly Platform TargetPlatform = + private readonly Platform TargetPlatform = #if SMAPI_FOR_WINDOWS Platform.Windows; #else @@ -36,47 +36,47 @@ namespace StardewModdingAPI #endif /// The full path to the Stardew Valley executable. - private static readonly string GameExecutablePath = Path.Combine(Constants.ExecutionPath, Program.TargetPlatform == Platform.Windows ? "Stardew Valley.exe" : "StardewValley.exe"); + private readonly string GameExecutablePath; /// The full path to the folder containing mods. - private static readonly string ModPath = Path.Combine(Constants.ExecutionPath, "Mods"); + private readonly string ModPath = Path.Combine(Constants.ExecutionPath, "Mods"); /// The log file to which to write messages. - private static readonly LogFileManager LogFile = new LogFileManager(Constants.LogPath); + private readonly LogFileManager LogFile = new LogFileManager(Constants.LogPath); /// Manages console output interception. - private static readonly ConsoleInterceptionManager ConsoleManager = new ConsoleInterceptionManager(); + private readonly ConsoleInterceptionManager ConsoleManager = new ConsoleInterceptionManager(); /// The core logger for SMAPI. - private static readonly Monitor Monitor = new Monitor("SMAPI", Program.ConsoleManager, Program.LogFile); + private readonly Monitor Monitor; /// The user settings for SMAPI. - private static UserSettings Settings; + private UserSettings Settings; /// Tracks whether the game should exit immediately and any pending initialisation should be cancelled. - private static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); + private readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); /// Whether the game is currently running. - private static bool ready; + private bool IsGameRunning; /********* ** Accessors *********/ /// The underlying game instance. - internal static SGame GameInstance; + internal SGame GameInstance; /// The number of mods currently loaded by SMAPI. - internal static int ModsLoaded; + internal int ModsLoaded; /// Tracks the installed mods. - internal static readonly ModRegistry ModRegistry = new ModRegistry(); + internal readonly ModRegistry ModRegistry = new ModRegistry(); /// Manages deprecation warnings. - internal static readonly DeprecationManager DeprecationManager = new DeprecationManager(Program.Monitor, Program.ModRegistry); + internal readonly DeprecationManager DeprecationManager; /// Manages console commands. - internal static readonly CommandManager CommandManager = new CommandManager(); + internal readonly CommandManager CommandManager = new CommandManager(); /********* @@ -85,11 +85,36 @@ namespace StardewModdingAPI /// The main entry point which hooks into and launches the game. /// The command-line arguments. private static void Main(string[] args) + { + new Program(writeToConsole: !args.Contains("--no-terminal")) + .LaunchInteractively(); + } + + /// Construct an instance. + internal Program(bool writeToConsole) + { + this.GameExecutablePath = Path.Combine(Constants.ExecutionPath, this.TargetPlatform == Platform.Windows ? "Stardew Valley.exe" : "StardewValley.exe"); + this.Monitor = new Monitor("SMAPI", this.ConsoleManager, this.LogFile, this.ExitGameImmediately) { WriteToConsole = writeToConsole }; + this.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); + } + + /// Launch SMAPI. + internal void LaunchInteractively() { // initialise logging - Program.Monitor.WriteToConsole = !args.Contains("--no-terminal"); Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); // for consistent log formatting - Program.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info); + this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info); + + // 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); + Mod.Shim(this.DeprecationManager); + PlayerEvents.Shim(this.DeprecationManager); + TimeEvents.Shim(this.DeprecationManager); +#pragma warning restore 618 // read config { @@ -97,39 +122,39 @@ namespace StardewModdingAPI if (File.Exists(settingsPath)) { string json = File.ReadAllText(settingsPath); - Program.Settings = JsonConvert.DeserializeObject(json); + this.Settings = JsonConvert.DeserializeObject(json); } else - Program.Settings = new UserSettings(); + this.Settings = new UserSettings(); - File.WriteAllText(settingsPath, JsonConvert.SerializeObject(Program.Settings, Formatting.Indented)); + File.WriteAllText(settingsPath, JsonConvert.SerializeObject(this.Settings, Formatting.Indented)); } // redirect direct console output { - Monitor monitor = Program.GetSecondaryMonitor("Console.Out"); + Monitor monitor = this.GetSecondaryMonitor("Console.Out"); monitor.WriteToFile = false; // not useful for troubleshooting mods per discussion if (monitor.WriteToConsole) - Program.ConsoleManager.OnLineIntercepted += line => monitor.Log(line, LogLevel.Trace); + this.ConsoleManager.OnLineIntercepted += line => monitor.Log(line, LogLevel.Trace); } // add warning headers - if (Program.Settings.DeveloperMode) + if (this.Settings.DeveloperMode) { - Program.Monitor.ShowTraceInConsole = true; - Program.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 or deleting {Constants.ApiConfigPath}.", LogLevel.Warn); + 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 or deleting {Constants.ApiConfigPath}.", LogLevel.Warn); } - if (!Program.Settings.CheckForUpdates) - Program.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 editing or deleting {Constants.ApiConfigPath}.", LogLevel.Warn); - if (!Program.Monitor.WriteToConsole) - Program.Monitor.Log("Writing to the terminal is disabled because the --no-terminal argument was received. This usually means launching the terminal failed.", 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 editing or deleting {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); // print file paths - Program.Monitor.Log($"Mods go here: {Program.ModPath}"); + this.Monitor.Log($"Mods go here: {this.ModPath}"); // initialise legacy log - Log.Monitor = Program.GetSecondaryMonitor("legacy mod"); - Log.ModRegistry = Program.ModRegistry; + Log.Monitor = this.GetSecondaryMonitor("legacy mod"); + Log.ModRegistry = this.ModRegistry; // hook into & launch the game try @@ -137,56 +162,56 @@ namespace StardewModdingAPI // verify version if (String.Compare(Game1.version, Constants.MinimumGameVersion, StringComparison.InvariantCultureIgnoreCase) < 0) { - Program.Monitor.Log($"Oops! You're running Stardew Valley {Game1.version}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI. If you're on the Steam beta channel, note that the beta channel may not receive the latest updates.", LogLevel.Error); + this.Monitor.Log($"Oops! You're running Stardew Valley {Game1.version}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI. If you're on the Steam beta channel, note that the beta channel may not receive the latest updates.", LogLevel.Error); return; } // initialise - Program.Monitor.Log("Loading SMAPI..."); - Console.Title = Constants.ConsoleTitle; - Program.VerifyPath(Program.ModPath); - Program.VerifyPath(Constants.LogDir); - if (!File.Exists(Program.GameExecutablePath)) + this.Monitor.Log("Loading SMAPI..."); + Console.Title = $"Stardew Modding API Console - Version {Constants.ApiVersion}"; + this.VerifyPath(this.ModPath); + this.VerifyPath(Constants.LogDir); + if (!File.Exists(this.GameExecutablePath)) { - Program.Monitor.Log($"Couldn't find executable: {Program.GameExecutablePath}", LogLevel.Error); - Program.PressAnyKeyToExit(); + this.Monitor.Log($"Couldn't find executable: {this.GameExecutablePath}", LogLevel.Error); + this.PressAnyKeyToExit(); return; } // check for update when game loads - if (Program.Settings.CheckForUpdates) - GameEvents.GameLoaded += (sender, e) => Program.CheckForUpdateAsync(); + if (this.Settings.CheckForUpdates) + GameEvents.GameLoaded += (sender, e) => this.CheckForUpdateAsync(); // launch game - Program.StartGame(); + this.StartGame(); } catch (Exception ex) { - Program.Monitor.Log($"Critical error: {ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"Critical error: {ex.GetLogSummary()}", LogLevel.Error); } - Program.PressAnyKeyToExit(); + this.PressAnyKeyToExit(); } /// 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. /// The module which requested an immediate exit. /// The reason provided for the shutdown. - internal static void ExitGameImmediately(string module, string reason) + internal void ExitGameImmediately(string module, string reason) { - Program.Monitor.LogFatal($"{module} requested an immediate game shutdown: {reason}"); - Program.CancellationTokenSource.Cancel(); - if (Program.ready) + this.Monitor.LogFatal($"{module} requested an immediate game shutdown: {reason}"); + this.CancellationTokenSource.Cancel(); + if (this.IsGameRunning) { - Program.GameInstance.Exiting += (sender, e) => Program.PressAnyKeyToExit(); - Program.GameInstance.Exit(); + this.GameInstance.Exiting += (sender, e) => this.PressAnyKeyToExit(); + this.GameInstance.Exit(); } } /// Get a monitor for legacy code which doesn't have one passed in. [Obsolete("This method should only be used when needed for backwards compatibility.")] - internal static IMonitor GetLegacyMonitorForMod() + internal IMonitor GetLegacyMonitorForMod() { - string modName = Program.ModRegistry.GetModFromStack() ?? "unknown"; - return Program.GetSecondaryMonitor(modName); + string modName = this.ModRegistry.GetModFromStack() ?? "unknown"; + return this.GetSecondaryMonitor(modName); } @@ -194,7 +219,7 @@ namespace StardewModdingAPI ** Private methods *********/ /// Asynchronously check for a new version of SMAPI, and print a message to the console if an update is available. - private static void CheckForUpdateAsync() + private void CheckForUpdateAsync() { new Thread(() => { @@ -203,40 +228,40 @@ namespace StardewModdingAPI GitRelease release = UpdateHelper.GetLatestVersionAsync(Constants.GitHubRepository).Result; ISemanticVersion latestVersion = new SemanticVersion(release.Tag); if (latestVersion.IsNewerThan(Constants.ApiVersion)) - Program.Monitor.Log($"You can update SMAPI from version {Constants.ApiVersion} to {latestVersion}", LogLevel.Alert); + this.Monitor.Log($"You can update SMAPI from version {Constants.ApiVersion} to {latestVersion}", LogLevel.Alert); } catch (Exception ex) { - Program.Monitor.Log($"Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.\n{ex.GetLogSummary()}"); + this.Monitor.Log($"Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.\n{ex.GetLogSummary()}"); } }).Start(); } /// Hook into Stardew Valley and launch the game. - private static void StartGame() + private void StartGame() { try { // add error handlers #if SMAPI_FOR_WINDOWS - Application.ThreadException += (sender, e) => Program.Monitor.Log($"Critical thread exception: {e.Exception.GetLogSummary()}", LogLevel.Error); + 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) => Program.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error); + AppDomain.CurrentDomain.UnhandledException += (sender, e) => this.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error); // initialise game { // load assembly - Program.Monitor.Log("Loading game..."); - Assembly gameAssembly = Assembly.UnsafeLoadFrom(Program.GameExecutablePath); + this.Monitor.Log("Loading game..."); + Assembly gameAssembly = Assembly.UnsafeLoadFrom(this.GameExecutablePath); Type gameProgramType = gameAssembly.GetType("StardewValley.Program", true); // set Game1 instance - Program.GameInstance = new SGame(Program.Monitor); - Program.GameInstance.Exiting += (sender, e) => Program.ready = false; - Program.GameInstance.Window.ClientSizeChanged += (sender, e) => GraphicsEvents.InvokeResize(Program.Monitor, sender, e); - Program.GameInstance.Window.Title = $"Stardew Valley - Version {Game1.version}"; - gameProgramType.GetField("gamePtr").SetValue(gameProgramType, Program.GameInstance); + 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 - Version {Game1.version}"; + gameProgramType.GetField("gamePtr").SetValue(gameProgramType, this.GameInstance); // configure Game1.version += $" | SMAPI {Constants.ApiVersion}"; @@ -244,10 +269,10 @@ namespace StardewModdingAPI } // load mods - Program.LoadMods(); - if (Program.CancellationTokenSource.IsCancellationRequested) + this.LoadMods(); + if (this.CancellationTokenSource.IsCancellationRequested) { - Program.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error); + this.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error); return; } @@ -255,18 +280,18 @@ namespace StardewModdingAPI new Thread(() => { // wait for the game to load up - while (!Program.ready) + while (!this.IsGameRunning) Thread.Sleep(1000); // register help command - Program.CommandManager.Add("SMAPI", "help", "Lists all commands | 'help ' returns command description", Program.HandleHelpCommand); + this.CommandManager.Add("SMAPI", "help", "Lists all commands | 'help ' returns command description", this.HandleHelpCommand); // listen for command line input - Program.Monitor.Log("Starting console..."); - Program.Monitor.Log("Type 'help' for help, or 'help ' for a command's usage", LogLevel.Info); - Thread consoleInputThread = new Thread(Program.ConsoleInputLoop); + this.Monitor.Log("Starting console..."); + this.Monitor.Log("Type 'help' for help, or 'help ' for a command's usage", LogLevel.Info); + Thread consoleInputThread = new Thread(this.ConsoleInputLoop); consoleInputThread.Start(); - while (Program.ready) + 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 @@ -275,31 +300,31 @@ namespace StardewModdingAPI }).Start(); // start game loop - Program.Monitor.Log("Starting game..."); - if (Program.CancellationTokenSource.IsCancellationRequested) + this.Monitor.Log("Starting game..."); + if (this.CancellationTokenSource.IsCancellationRequested) { - Program.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error); + this.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error); return; } try { - Program.ready = true; - Program.GameInstance.Run(); + this.IsGameRunning = true; + this.GameInstance.Run(); } finally { - Program.ready = false; + this.IsGameRunning = false; } } catch (Exception ex) { - Program.Monitor.Log($"The game encountered a fatal error:\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"The game encountered a fatal error:\n{ex.GetLogSummary()}", LogLevel.Error); } } /// Create a directory path if it doesn't exist. /// The directory path. - private static void VerifyPath(string path) + private void VerifyPath(string path) { try { @@ -308,20 +333,20 @@ namespace StardewModdingAPI } catch (Exception ex) { - Program.Monitor.Log($"Couldn't create a path: {path}\n\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"Couldn't create a path: {path}\n\n{ex.GetLogSummary()}", LogLevel.Error); } } /// Load and hook up all mods in the mod directory. - private static void LoadMods() + private void LoadMods() { - Program.Monitor.Log("Loading mods..."); + this.Monitor.Log("Loading mods..."); // get JSON helper JsonHelper jsonHelper = new JsonHelper(); // get assembly loader - AssemblyLoader modAssemblyLoader = new AssemblyLoader(Program.TargetPlatform, Program.Monitor); + AssemblyLoader modAssemblyLoader = new AssemblyLoader(this.TargetPlatform, this.Monitor); AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name); // get known incompatible mods @@ -335,12 +360,12 @@ namespace StardewModdingAPI catch (Exception ex) { incompatibleMods = new Dictionary(); - Program.Monitor.Log($"Couldn't read metadata file at {Constants.ApiModMetadataPath}. SMAPI will still run, but some features may be disabled.\n{ex}", LogLevel.Warn); + this.Monitor.Log($"Couldn't read metadata file at {Constants.ApiModMetadataPath}. SMAPI will still run, but some features may be disabled.\n{ex}", LogLevel.Warn); } // load mod assemblies List deprecationWarnings = new List(); // queue up deprecation warnings to show after mod list - foreach (string directoryPath in Directory.GetDirectories(Program.ModPath)) + foreach (string directoryPath in Directory.GetDirectories(this.ModPath)) { // passthrough empty directories DirectoryInfo directory = new DirectoryInfo(directoryPath); @@ -348,9 +373,9 @@ namespace StardewModdingAPI directory = directory.GetDirectories().First(); // check for cancellation - if (Program.CancellationTokenSource.IsCancellationRequested) + if (this.CancellationTokenSource.IsCancellationRequested) { - Program.Monitor.Log("Shutdown requested; interrupting mod loading.", LogLevel.Error); + this.Monitor.Log("Shutdown requested; interrupting mod loading.", LogLevel.Error); return; } @@ -358,7 +383,7 @@ namespace StardewModdingAPI string manifestPath = Path.Combine(directory.FullName, "manifest.json"); if (!File.Exists(manifestPath)) { - Program.Monitor.Log($"Ignored folder \"{directory.Name}\" which doesn't have a manifest.json.", LogLevel.Warn); + this.Monitor.Log($"Ignored folder \"{directory.Name}\" which doesn't have a manifest.json.", LogLevel.Warn); continue; } string errorPrefix = $"Couldn't load mod for manifest '{manifestPath}'"; @@ -371,7 +396,7 @@ namespace StardewModdingAPI string json = File.ReadAllText(manifestPath); if (string.IsNullOrEmpty(json)) { - Program.Monitor.Log($"{errorPrefix}: manifest is empty.", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: manifest is empty.", LogLevel.Error); continue; } @@ -379,18 +404,18 @@ namespace StardewModdingAPI manifest = jsonHelper.ReadJsonFile(Path.Combine(directory.FullName, "manifest.json"), null); if (manifest == null) { - Program.Monitor.Log($"{errorPrefix}: the manifest file does not exist.", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: the manifest file does not exist.", LogLevel.Error); continue; } if (string.IsNullOrEmpty(manifest.EntryDll)) { - Program.Monitor.Log($"{errorPrefix}: manifest doesn't specify an entry DLL.", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: manifest doesn't specify an entry DLL.", LogLevel.Error); continue; } } catch (Exception ex) { - Program.Monitor.Log($"{errorPrefix}: manifest parsing failed.\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: manifest parsing failed.\n{ex.GetLogSummary()}", LogLevel.Error); continue; } @@ -407,7 +432,7 @@ namespace StardewModdingAPI if (!string.IsNullOrWhiteSpace(compatibility.UnofficialUpdateUrl)) warning += $"{Environment.NewLine}- unofficial update: {compatibility.UnofficialUpdateUrl}"; - Program.Monitor.Log(warning, LogLevel.Error); + this.Monitor.Log(warning, LogLevel.Error); continue; } } @@ -420,13 +445,13 @@ namespace StardewModdingAPI ISemanticVersion minVersion = new SemanticVersion(manifest.MinimumApiVersion); if (minVersion.IsNewerThan(Constants.ApiVersion)) { - Program.Monitor.Log($"{errorPrefix}: this mod requires SMAPI {minVersion} or later. Please update SMAPI to the latest version to use this mod.", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: this mod requires SMAPI {minVersion} or later. Please update SMAPI to the latest version to use this mod.", LogLevel.Error); continue; } } catch (FormatException ex) when (ex.Message.Contains("not a valid semantic version")) { - Program.Monitor.Log($"{errorPrefix}: the mod specified an invalid minimum SMAPI version '{manifest.MinimumApiVersion}'. This should be a semantic version number like {Constants.ApiVersion}.", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: the mod specified an invalid minimum SMAPI version '{manifest.MinimumApiVersion}'. This should be a semantic version number like {Constants.ApiVersion}.", LogLevel.Error); continue; } } @@ -434,20 +459,20 @@ namespace StardewModdingAPI // create per-save directory if (manifest.PerSaveConfigs) { - deprecationWarnings.Add(() => Program.DeprecationManager.Warn(manifest.Name, $"{nameof(Manifest)}.{nameof(Manifest.PerSaveConfigs)}", "1.0", DeprecationLevel.Info)); + deprecationWarnings.Add(() => this.DeprecationManager.Warn(manifest.Name, $"{nameof(Manifest)}.{nameof(Manifest.PerSaveConfigs)}", "1.0", DeprecationLevel.Info)); try { string psDir = Path.Combine(directory.FullName, "psconfigs"); Directory.CreateDirectory(psDir); if (!Directory.Exists(psDir)) { - Program.Monitor.Log($"{errorPrefix}: couldn't create the per-save configuration directory ('psconfigs') requested by this mod. The failure reason is unknown.", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: couldn't create the per-save configuration directory ('psconfigs') requested by this mod. The failure reason is unknown.", LogLevel.Error); continue; } } catch (Exception ex) { - Program.Monitor.Log($"{errorPrefix}: couldn't create the per-save configuration directory ('psconfigs') requested by this mod.\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: couldn't create the per-save configuration directory ('psconfigs') requested by this mod.\n{ex.GetLogSummary()}", LogLevel.Error); continue; } } @@ -456,7 +481,7 @@ namespace StardewModdingAPI string assemblyPath = Path.Combine(directory.FullName, manifest.EntryDll); if (!File.Exists(assemblyPath)) { - Program.Monitor.Log($"{errorPrefix}: the entry DLL '{manifest.EntryDll}' does not exist.", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: the entry DLL '{manifest.EntryDll}' does not exist.", LogLevel.Error); continue; } @@ -468,7 +493,7 @@ namespace StardewModdingAPI } catch (Exception ex) { - Program.Monitor.Log($"{errorPrefix}: an error occurred while preprocessing '{manifest.EntryDll}'.\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: an error occurred while preprocessing '{manifest.EntryDll}'.\n{ex.GetLogSummary()}", LogLevel.Error); continue; } @@ -477,13 +502,13 @@ namespace StardewModdingAPI { if (modAssembly.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) == 0) { - Program.Monitor.Log($"{errorPrefix}: the mod DLL does not contain an implementation of the 'Mod' class.", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: the mod DLL does not contain an implementation of the 'Mod' class.", LogLevel.Error); continue; } } catch (Exception ex) { - Program.Monitor.Log($"{errorPrefix}: an error occurred while reading the mod DLL.\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: an error occurred while reading the mod DLL.\n{ex.GetLogSummary()}", LogLevel.Error); continue; } @@ -496,25 +521,25 @@ namespace StardewModdingAPI mod = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); if (mod == null) { - Program.Monitor.Log($"{errorPrefix}: the mod's entry class could not be instantiated."); + this.Monitor.Log($"{errorPrefix}: the mod's entry class could not be instantiated."); continue; } // inject data // get helper mod.ModManifest = manifest; - mod.Helper = new ModHelper(manifest.Name, directory.FullName, jsonHelper, Program.ModRegistry, Program.CommandManager); - mod.Monitor = Program.GetSecondaryMonitor(manifest.Name); + mod.Helper = new ModHelper(manifest.Name, directory.FullName, jsonHelper, this.ModRegistry, this.CommandManager); + mod.Monitor = this.GetSecondaryMonitor(manifest.Name); mod.PathOnDisk = directory.FullName; // track mod - Program.ModRegistry.Add(mod); - Program.ModsLoaded += 1; - Program.Monitor.Log($"Loaded mod: {manifest.Name} by {manifest.Author}, v{manifest.Version} | {manifest.Description}", LogLevel.Info); + this.ModRegistry.Add(mod); + this.ModsLoaded += 1; + this.Monitor.Log($"Loaded mod: {manifest.Name} by {manifest.Author}, v{manifest.Version} | {manifest.Description}", LogLevel.Info); } catch (Exception ex) { - Program.Monitor.Log($"{errorPrefix}: an error occurred while loading the target DLL.\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"{errorPrefix}: an error occurred while loading the target DLL.\n{ex.GetLogSummary()}", LogLevel.Error); continue; } } @@ -525,7 +550,7 @@ namespace StardewModdingAPI deprecationWarnings = null; // initialise mods - foreach (Mod mod in Program.ModRegistry.GetMods()) + foreach (Mod mod in this.ModRegistry.GetMods()) { try { @@ -534,54 +559,54 @@ namespace StardewModdingAPI mod.Entry(mod.Helper); // raise deprecation warning for old Entry() methods - if (Program.DeprecationManager.IsVirtualMethodImplemented(mod.GetType(), typeof(Mod), nameof(Mod.Entry), new[] { typeof(object[]) })) - Program.DeprecationManager.Warn(mod.ModManifest.Name, $"{nameof(Mod)}.{nameof(Mod.Entry)}(object[]) instead of {nameof(Mod)}.{nameof(Mod.Entry)}({nameof(IModHelper)})", "1.0", DeprecationLevel.Notice); + if (this.DeprecationManager.IsVirtualMethodImplemented(mod.GetType(), typeof(Mod), nameof(Mod.Entry), new[] { typeof(object[]) })) + this.DeprecationManager.Warn(mod.ModManifest.Name, $"{nameof(Mod)}.{nameof(Mod.Entry)}(object[]) instead of {nameof(Mod)}.{nameof(Mod.Entry)}({nameof(IModHelper)})", "1.0", DeprecationLevel.Notice); } catch (Exception ex) { - Program.Monitor.Log($"The {mod.ModManifest.Name} mod failed on entry initialisation. It will still be loaded, but may not function correctly.\n{ex.GetLogSummary()}", LogLevel.Warn); + this.Monitor.Log($"The {mod.ModManifest.Name} mod failed on entry initialisation. It will still be loaded, but may not function correctly.\n{ex.GetLogSummary()}", LogLevel.Warn); } } // print result - Program.Monitor.Log($"Loaded {Program.ModsLoaded} mods."); - Console.Title = Constants.ConsoleTitle; + this.Monitor.Log($"Loaded {this.ModsLoaded} mods."); + Console.Title = $"Stardew Modding API Console - Version {Constants.ApiVersion} - Mods Loaded: {this.ModsLoaded}"; } // ReSharper disable once FunctionNeverReturns /// Run a loop handling console input. - private static void ConsoleInputLoop() + private void ConsoleInputLoop() { while (true) { string input = Console.ReadLine(); - if (!Program.CommandManager.Trigger(input)) - Program.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error); + if (!this.CommandManager.Trigger(input)) + this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error); } } /// The method called when the user submits the help command in the console. /// The command name. /// The command arguments. - private static void HandleHelpCommand(string name, string[] arguments) + private void HandleHelpCommand(string name, string[] arguments) { if (arguments.Any()) { - Framework.Command result = Program.CommandManager.Get(arguments[0]); + Framework.Command result = this.CommandManager.Get(arguments[0]); if (result == null) - Program.Monitor.Log("There's no command with that name.", LogLevel.Error); + this.Monitor.Log("There's no command with that name.", LogLevel.Error); else - Program.Monitor.Log($"{result.Name}: {result.Documentation}\n(Added by {result.ModName}.)", LogLevel.Info); + this.Monitor.Log($"{result.Name}: {result.Documentation}\n(Added by {result.ModName}.)", LogLevel.Info); } else - Program.Monitor.Log("Commands: " + string.Join(", ", Program.CommandManager.GetAll().Select(p => p.Name)), LogLevel.Info); + this.Monitor.Log("Commands: " + string.Join(", ", this.CommandManager.GetAll().Select(p => p.Name)), LogLevel.Info); } /// Show a 'press any key to exit' message, and exit when they press a key. - private static void PressAnyKeyToExit() + private void PressAnyKeyToExit() { - Program.Monitor.Log("Game has ended. Press any key to exit.", LogLevel.Info); + this.Monitor.Log("Game has ended. Press any key to exit.", LogLevel.Info); Thread.Sleep(100); Console.ReadKey(); Environment.Exit(0); @@ -589,9 +614,9 @@ namespace StardewModdingAPI /// Get a monitor instance derived from SMAPI's current settings. /// The name of the module which will log messages with this instance. - private static Monitor GetSecondaryMonitor(string name) + private Monitor GetSecondaryMonitor(string name) { - return new Monitor(name, Program.ConsoleManager, Program.LogFile) { WriteToConsole = Program.Monitor.WriteToConsole, ShowTraceInConsole = Program.Settings.DeveloperMode }; + return new Monitor(name, this.ConsoleManager, this.LogFile, this.ExitGameImmediately) { WriteToConsole = this.Monitor.WriteToConsole, ShowTraceInConsole = this.Settings.DeveloperMode }; } } } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 1e896d4b..35dd6513 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -151,6 +151,7 @@ + -- cgit From 960507879eacc0a760862c269b39f9d7448a7bd5 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 16 Feb 2017 01:03:50 -0500 Subject: remove unneeded property for game exe path --- src/StardewModdingAPI/Log.cs | 14 +++++++------- src/StardewModdingAPI/Program.cs | 28 ++++++++++++---------------- 2 files changed, 19 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Log.cs b/src/StardewModdingAPI/Log.cs index da98baba..a8d78e55 100644 --- a/src/StardewModdingAPI/Log.cs +++ b/src/StardewModdingAPI/Log.cs @@ -15,15 +15,11 @@ namespace StardewModdingAPI /// Manages deprecation warnings. private static DeprecationManager DeprecationManager; - - /********* - ** Accessors - *********/ /// The underlying logger. - internal static Monitor Monitor; + private static Monitor Monitor; /// Tracks the installed mods. - internal static ModRegistry ModRegistry; + private static ModRegistry ModRegistry; /********* @@ -31,9 +27,13 @@ namespace StardewModdingAPI *********/ /// Injects types required for backwards compatibility. /// Manages deprecation warnings. - internal static void Shim(DeprecationManager deprecationManager) + /// The underlying logger. + /// Tracks the installed mods. + internal static void Shim(DeprecationManager deprecationManager, Monitor monitor, ModRegistry modRegistry) { Log.DeprecationManager = deprecationManager; + Log.Monitor = monitor; + Log.ModRegistry = modRegistry; } /**** diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 0857d41b..41e12394 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -105,18 +105,7 @@ namespace StardewModdingAPI Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); // for consistent log formatting this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info); - // 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); - Mod.Shim(this.DeprecationManager); - PlayerEvents.Shim(this.DeprecationManager); - TimeEvents.Shim(this.DeprecationManager); -#pragma warning restore 618 - - // read config + // read settings { string settingsPath = Constants.ApiConfigPath; if (File.Exists(settingsPath)) @@ -130,6 +119,17 @@ namespace StardewModdingAPI File.WriteAllText(settingsPath, JsonConvert.SerializeObject(this.Settings, Formatting.Indented)); } + // 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); + PlayerEvents.Shim(this.DeprecationManager); + TimeEvents.Shim(this.DeprecationManager); +#pragma warning restore 618 + // redirect direct console output { Monitor monitor = this.GetSecondaryMonitor("Console.Out"); @@ -152,10 +152,6 @@ namespace StardewModdingAPI // print file paths this.Monitor.Log($"Mods go here: {this.ModPath}"); - // initialise legacy log - Log.Monitor = this.GetSecondaryMonitor("legacy mod"); - Log.ModRegistry = this.ModRegistry; - // hook into & launch the game try { -- cgit From a0c94752c02c185b9e75b61a6098aaefa4e24aac Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 16 Feb 2017 01:07:11 -0500 Subject: remove unneeded property for game exe path --- src/StardewModdingAPI/Program.cs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 41e12394..8fd9c8e1 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -35,9 +35,6 @@ namespace StardewModdingAPI Platform.Mono; #endif - /// The full path to the Stardew Valley executable. - private readonly string GameExecutablePath; - /// The full path to the folder containing mods. private readonly string ModPath = Path.Combine(Constants.ExecutionPath, "Mods"); @@ -93,7 +90,6 @@ namespace StardewModdingAPI /// Construct an instance. internal Program(bool writeToConsole) { - this.GameExecutablePath = Path.Combine(Constants.ExecutionPath, this.TargetPlatform == Platform.Windows ? "Stardew Valley.exe" : "StardewValley.exe"); this.Monitor = new Monitor("SMAPI", this.ConsoleManager, this.LogFile, this.ExitGameImmediately) { WriteToConsole = writeToConsole }; this.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); } @@ -156,20 +152,22 @@ namespace StardewModdingAPI try { // verify version - if (String.Compare(Game1.version, Constants.MinimumGameVersion, StringComparison.InvariantCultureIgnoreCase) < 0) + if (string.Compare(Game1.version, Constants.MinimumGameVersion, StringComparison.InvariantCultureIgnoreCase) < 0) { this.Monitor.Log($"Oops! You're running Stardew Valley {Game1.version}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI. If you're on the Steam beta channel, note that the beta channel may not receive the latest updates.", LogLevel.Error); return; } - // initialise + // initialise folders this.Monitor.Log("Loading SMAPI..."); - Console.Title = $"Stardew Modding API Console - Version {Constants.ApiVersion}"; this.VerifyPath(this.ModPath); this.VerifyPath(Constants.LogDir); - if (!File.Exists(this.GameExecutablePath)) + + // get executable path + string executablePath = Path.Combine(Constants.ExecutionPath, this.TargetPlatform == Platform.Windows ? "Stardew Valley.exe" : "StardewValley.exe"); + if (!File.Exists(executablePath)) { - this.Monitor.Log($"Couldn't find executable: {this.GameExecutablePath}", LogLevel.Error); + this.Monitor.Log($"Couldn't find executable: {executablePath}", LogLevel.Error); this.PressAnyKeyToExit(); return; } @@ -179,7 +177,7 @@ namespace StardewModdingAPI GameEvents.GameLoaded += (sender, e) => this.CheckForUpdateAsync(); // launch game - this.StartGame(); + this.StartGame(executablePath); } catch (Exception ex) { @@ -234,7 +232,8 @@ namespace StardewModdingAPI } /// Hook into Stardew Valley and launch the game. - private void StartGame() + /// The absolute path to the executable to launch. + private void StartGame(string executablePath) { try { @@ -249,7 +248,7 @@ namespace StardewModdingAPI { // load assembly this.Monitor.Log("Loading game..."); - Assembly gameAssembly = Assembly.UnsafeLoadFrom(this.GameExecutablePath); + Assembly gameAssembly = Assembly.UnsafeLoadFrom(executablePath); Type gameProgramType = gameAssembly.GetType("StardewValley.Program", true); // set Game1 instance @@ -536,7 +535,6 @@ namespace StardewModdingAPI catch (Exception ex) { this.Monitor.Log($"{errorPrefix}: an error occurred while loading the target DLL.\n{ex.GetLogSummary()}", LogLevel.Error); - continue; } } -- cgit From f8866ac4a81ad02f6f06391372ad1b32e6cfdb68 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 16 Feb 2017 01:10:07 -0500 Subject: remove unneeded property for number of mods loaded --- src/StardewModdingAPI/Program.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 8fd9c8e1..ecc0360e 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -63,9 +63,6 @@ namespace StardewModdingAPI /// The underlying game instance. internal SGame GameInstance; - /// The number of mods currently loaded by SMAPI. - internal int ModsLoaded; - /// Tracks the installed mods. internal readonly ModRegistry ModRegistry = new ModRegistry(); @@ -359,6 +356,7 @@ namespace StardewModdingAPI } // load mod assemblies + int modsLoaded = 0; List deprecationWarnings = new List(); // queue up deprecation warnings to show after mod list foreach (string directoryPath in Directory.GetDirectories(this.ModPath)) { @@ -529,7 +527,7 @@ namespace StardewModdingAPI // track mod this.ModRegistry.Add(mod); - this.ModsLoaded += 1; + modsLoaded += 1; this.Monitor.Log($"Loaded mod: {manifest.Name} by {manifest.Author}, v{manifest.Version} | {manifest.Description}", LogLevel.Info); } catch (Exception ex) @@ -563,8 +561,8 @@ namespace StardewModdingAPI } // print result - this.Monitor.Log($"Loaded {this.ModsLoaded} mods."); - Console.Title = $"Stardew Modding API Console - Version {Constants.ApiVersion} - Mods Loaded: {this.ModsLoaded}"; + this.Monitor.Log($"Loaded {modsLoaded} mods."); + Console.Title = $"Stardew Modding API Console - Version {Constants.ApiVersion} - Mods Loaded: {modsLoaded}"; } // ReSharper disable once FunctionNeverReturns -- cgit From 16c362f4c5949b463107e88b4b49e1e49cc395d7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 16 Feb 2017 13:59:13 -0500 Subject: increase all notice deprecations to info, tweak deprecation message format --- release-notes.md | 4 ++-- src/StardewModdingAPI/Command.cs | 6 +++--- src/StardewModdingAPI/Config.cs | 2 +- src/StardewModdingAPI/Events/PlayerEvents.cs | 4 ++-- src/StardewModdingAPI/Events/TimeEvents.cs | 2 +- src/StardewModdingAPI/Framework/DeprecationManager.cs | 2 +- src/StardewModdingAPI/Log.cs | 2 +- src/StardewModdingAPI/Mod.cs | 6 +++--- src/StardewModdingAPI/Program.cs | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 98f527d2..8d571514 100644 --- a/release-notes.md +++ b/release-notes.md @@ -14,9 +14,9 @@ For mod developers: * Added `SaveEvents.AfterReturnToTitle` and `TimeEvents.AfterDayStarted` events. * Added a simpler API for console commands (see `helper.ConsoleCommands`). * Added `GetPrivateProperty` reflection helper. -* Many deprecated APIs have been removed; see the - [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod) for more information. * Log files now always use `\r\n` to simplify crossplatform viewing. +* Several obsolete APIs have been removed (see [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod)), + and all _notice_-level deprecations have been increased to _info_. For SMAPI developers: * Added support for debugging with Visual Studio for Mac. diff --git a/src/StardewModdingAPI/Command.cs b/src/StardewModdingAPI/Command.cs index 6b056ce7..e2d08538 100644 --- a/src/StardewModdingAPI/Command.cs +++ b/src/StardewModdingAPI/Command.cs @@ -94,7 +94,7 @@ namespace StardewModdingAPI /// Encapsulates monitoring and logging. public static void CallCommand(string input, IMonitor monitor) { - Command.DeprecationManager.Warn("Command.CallCommand", "1.9", DeprecationLevel.Notice); + Command.DeprecationManager.Warn("Command.CallCommand", "1.9", DeprecationLevel.Info); Command.CommandManager.Trigger(input); } @@ -107,7 +107,7 @@ namespace StardewModdingAPI name = name?.Trim().ToLower(); // raise deprecation warning - Command.DeprecationManager.Warn("Command.RegisterCommand", "1.9", DeprecationLevel.Notice); + Command.DeprecationManager.Warn("Command.RegisterCommand", "1.9", DeprecationLevel.Info); // validate if (Command.LegacyCommands.ContainsKey(name)) @@ -130,7 +130,7 @@ namespace StardewModdingAPI /// The command name to find. public static Command FindCommand(string name) { - Command.DeprecationManager.Warn("Command.FindCommand", "1.9", DeprecationLevel.Notice); + Command.DeprecationManager.Warn("Command.FindCommand", "1.9", DeprecationLevel.Info); if (name == null) return null; diff --git a/src/StardewModdingAPI/Config.cs b/src/StardewModdingAPI/Config.cs index f253930d..9f4bfad2 100644 --- a/src/StardewModdingAPI/Config.cs +++ b/src/StardewModdingAPI/Config.cs @@ -125,7 +125,7 @@ namespace StardewModdingAPI /// Construct an instance. protected Config() { - Config.DeprecationManager.Warn("the Config class", "1.0", DeprecationLevel.Notice); + Config.DeprecationManager.Warn("the Config class", "1.0", DeprecationLevel.Info); Config.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.BaseConfigPath)}", "1.0"); // typically used to construct config, avoid redundant warnings } } diff --git a/src/StardewModdingAPI/Events/PlayerEvents.cs b/src/StardewModdingAPI/Events/PlayerEvents.cs index 996077ab..b02ebfec 100644 --- a/src/StardewModdingAPI/Events/PlayerEvents.cs +++ b/src/StardewModdingAPI/Events/PlayerEvents.cs @@ -56,7 +56,7 @@ namespace StardewModdingAPI.Events string name = $"{nameof(PlayerEvents)}.{nameof(PlayerEvents.LoadedGame)}"; Delegate[] handlers = PlayerEvents.LoadedGame.GetInvocationList(); - PlayerEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Notice); + PlayerEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Info); monitor.SafelyRaiseGenericEvent(name, handlers, null, loaded); } @@ -72,7 +72,7 @@ namespace StardewModdingAPI.Events string name = $"{nameof(PlayerEvents)}.{nameof(PlayerEvents.FarmerChanged)}"; Delegate[] handlers = PlayerEvents.FarmerChanged.GetInvocationList(); - PlayerEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Notice); + PlayerEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Info); monitor.SafelyRaiseGenericEvent(name, handlers, null, new EventArgsFarmerChanged(priorFarmer, newFarmer)); } diff --git a/src/StardewModdingAPI/Events/TimeEvents.cs b/src/StardewModdingAPI/Events/TimeEvents.cs index 0f9257c1..3f06a46b 100644 --- a/src/StardewModdingAPI/Events/TimeEvents.cs +++ b/src/StardewModdingAPI/Events/TimeEvents.cs @@ -102,7 +102,7 @@ namespace StardewModdingAPI.Events string name = $"{nameof(TimeEvents)}.{nameof(TimeEvents.OnNewDay)}"; Delegate[] handlers = TimeEvents.OnNewDay.GetInvocationList(); - TimeEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Notice); + TimeEvents.DeprecationManager.WarnForEvent(handlers, name, "1.6", DeprecationLevel.Info); monitor.SafelyRaiseGenericEvent(name, handlers, null, new EventArgsNewDay(priorDay, newDay, isTransitioning)); } } diff --git a/src/StardewModdingAPI/Framework/DeprecationManager.cs b/src/StardewModdingAPI/Framework/DeprecationManager.cs index 8c32ba6a..e44cd369 100644 --- a/src/StardewModdingAPI/Framework/DeprecationManager.cs +++ b/src/StardewModdingAPI/Framework/DeprecationManager.cs @@ -70,7 +70,7 @@ namespace StardewModdingAPI.Framework break; case DeprecationLevel.Info: - this.Monitor.Log(message, LogLevel.Info); + this.Monitor.Log(message, LogLevel.Warn); break; case DeprecationLevel.PendingRemoval: diff --git a/src/StardewModdingAPI/Log.cs b/src/StardewModdingAPI/Log.cs index a8d78e55..d58cebfe 100644 --- a/src/StardewModdingAPI/Log.cs +++ b/src/StardewModdingAPI/Log.cs @@ -306,7 +306,7 @@ namespace StardewModdingAPI /// Raise a deprecation warning. private static void WarnDeprecated() { - Log.DeprecationManager.Warn($"the {nameof(Log)} class", "1.1", DeprecationLevel.Notice); + Log.DeprecationManager.Warn($"the {nameof(Log)} class", "1.1", DeprecationLevel.Info); } /// Get the name of the mod logging a message from the stack. diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs index d3fe882f..44cfd4b3 100644 --- a/src/StardewModdingAPI/Mod.cs +++ b/src/StardewModdingAPI/Mod.cs @@ -35,7 +35,7 @@ namespace StardewModdingAPI { get { - Mod.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0", DeprecationLevel.Notice); + Mod.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0", DeprecationLevel.Info); return this._pathOnDisk; } internal set { this._pathOnDisk = value; } @@ -47,7 +47,7 @@ namespace StardewModdingAPI { get { - Mod.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.BaseConfigPath)}", "1.0", DeprecationLevel.Notice); + Mod.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.BaseConfigPath)}", "1.0", DeprecationLevel.Info); Mod.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0"); // avoid redundant warnings return Path.Combine(this.PathOnDisk, "config.json"); } @@ -96,7 +96,7 @@ namespace StardewModdingAPI [Obsolete] private string GetPerSaveConfigFolder() { - Mod.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigFolder)}", "1.0", DeprecationLevel.Notice); + Mod.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigFolder)}", "1.0", DeprecationLevel.Info); Mod.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0"); // avoid redundant warnings if (!((Manifest)this.ModManifest).PerSaveConfigs) diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index ecc0360e..de07e8ad 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -552,7 +552,7 @@ namespace StardewModdingAPI // raise deprecation warning for old Entry() methods if (this.DeprecationManager.IsVirtualMethodImplemented(mod.GetType(), typeof(Mod), nameof(Mod.Entry), new[] { typeof(object[]) })) - this.DeprecationManager.Warn(mod.ModManifest.Name, $"{nameof(Mod)}.{nameof(Mod.Entry)}(object[]) instead of {nameof(Mod)}.{nameof(Mod.Entry)}({nameof(IModHelper)})", "1.0", DeprecationLevel.Notice); + this.DeprecationManager.Warn(mod.ModManifest.Name, $"{nameof(Mod)}.{nameof(Mod.Entry)}(object[]) instead of {nameof(Mod)}.{nameof(Mod.Entry)}({nameof(IModHelper)})", "1.0", DeprecationLevel.Info); } catch (Exception ex) { -- cgit From 1dfedd2d1a032d357bf42e9edbd7a797beb2e124 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 17 Feb 2017 01:44:19 -0500 Subject: fix issue where mod dependencies overrode SMAPI dependencies --- release-notes.md | 1 + src/StardewModdingAPI/Framework/AssemblyLoader.cs | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 8d571514..6111776f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -9,6 +9,7 @@ For players: * Simple nested mod folders are now recognised by SMAPI (e.g. `ModName-1.0\ModName\manifest.json`). * Fixed game's debug output being shown in the console for all users. * Fixed installer errors for some players when deleting files. +* Fixed rare issue where mod dependencies would override SMAPI dependencies and cause unpredictable bugs. For mod developers: * Added `SaveEvents.AfterReturnToTitle` and `TimeEvents.AfterDayStarted` events. diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index 0cf6e569..a932a47c 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -61,7 +61,8 @@ namespace StardewModdingAPI.Framework AssemblyParseResult[] assemblies; { AssemblyDefinitionResolver resolver = new AssemblyDefinitionResolver(); - assemblies = this.GetReferencedLocalAssemblies(new FileInfo(assemblyPath), new HashSet(), resolver).ToArray(); + HashSet visitedAssemblyNames = new HashSet(AppDomain.CurrentDomain.GetAssemblies().Select(p => p.GetName().Name)); // don't try loading assemblies that are already loaded + assemblies = this.GetReferencedLocalAssemblies(new FileInfo(assemblyPath), visitedAssemblyNames, resolver).ToArray(); if (!assemblies.Any()) throw new InvalidOperationException($"Could not load '{assemblyPath}' because it doesn't exist."); resolver.Add(assemblies.Select(p => p.Definition).ToArray()); @@ -118,18 +119,16 @@ namespace StardewModdingAPI.Framework ****/ /// Get a list of referenced local assemblies starting from the mod assembly, ordered from leaf to root. /// The assembly file to load. - /// The assembly paths that should be skipped. + /// The assembly names that should be skipped. + /// A resolver which resolves references to known assemblies. /// Returns the rewrite metadata for the preprocessed assembly. - private IEnumerable GetReferencedLocalAssemblies(FileInfo file, HashSet visitedAssemblyPaths, IAssemblyResolver assemblyResolver) + private IEnumerable GetReferencedLocalAssemblies(FileInfo file, HashSet visitedAssemblyNames, IAssemblyResolver assemblyResolver) { // validate if (file.Directory == null) throw new InvalidOperationException($"Could not get directory from file path '{file.FullName}'."); - if (visitedAssemblyPaths.Contains(file.FullName)) - yield break; // already visited if (!file.Exists) yield break; // not a local assembly - visitedAssemblyPaths.Add(file.FullName); // read assembly byte[] assemblyBytes = File.ReadAllBytes(file.FullName); @@ -137,11 +136,16 @@ namespace StardewModdingAPI.Framework using (Stream readStream = new MemoryStream(assemblyBytes)) assembly = AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Deferred) { AssemblyResolver = assemblyResolver }); + // skip if already visited + if (visitedAssemblyNames.Contains(assembly.Name.Name)) + yield break; + visitedAssemblyNames.Add(assembly.Name.Name); + // yield referenced assemblies foreach (AssemblyNameReference dependency in assembly.MainModule.AssemblyReferences) { FileInfo dependencyFile = new FileInfo(Path.Combine(file.Directory.FullName, $"{dependency.Name}.dll")); - foreach (AssemblyParseResult result in this.GetReferencedLocalAssemblies(dependencyFile, visitedAssemblyPaths, assemblyResolver)) + foreach (AssemblyParseResult result in this.GetReferencedLocalAssemblies(dependencyFile, visitedAssemblyNames, assemblyResolver)) yield return result; } -- cgit From 41ee8990f81a63c686953b2c28e7af8627fd8098 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 17 Feb 2017 11:33:22 -0500 Subject: write XNA input enums to JSON as strings automatically Mods often reference Json.NET to do this, so this lets many mods remove Json.NET as a dependency. --- release-notes.md | 1 + .../Framework/Serialisation/JsonHelper.cs | 8 ++++- .../Serialisation/SelectiveStringEnumConverter.cs | 35 ++++++++++++++++++++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/StardewModdingAPI/Framework/Serialisation/SelectiveStringEnumConverter.cs (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 6111776f..a277ee4f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -15,6 +15,7 @@ For mod developers: * Added `SaveEvents.AfterReturnToTitle` and `TimeEvents.AfterDayStarted` events. * Added a simpler API for console commands (see `helper.ConsoleCommands`). * Added `GetPrivateProperty` reflection helper. +* SMAPI now writes XNA input enums (`Buttons` and `Keys`) to JSON as strings, so mods no longer need to add a `StringEnumConverter` themselves for those. * Log files now always use `\r\n` to simplify crossplatform viewing. * Several obsolete APIs have been removed (see [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod)), and all _notice_-level deprecations have been increased to _info_. diff --git a/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs b/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs index 26d937a5..d5f5bfd0 100644 --- a/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs +++ b/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.IO; +using Microsoft.Xna.Framework.Input; using Newtonsoft.Json; using StardewModdingAPI.Advanced; @@ -15,7 +17,11 @@ namespace StardewModdingAPI.Framework.Serialisation private readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings { Formatting = Formatting.Indented, - ObjectCreationHandling = ObjectCreationHandling.Replace // avoid issue where default ICollection values are duplicated each time the config is loaded + ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection values are duplicated each time the config is loaded + Converters = new List + { + new SelectiveStringEnumConverter(typeof(Buttons), typeof(Keys)) + } }; diff --git a/src/StardewModdingAPI/Framework/Serialisation/SelectiveStringEnumConverter.cs b/src/StardewModdingAPI/Framework/Serialisation/SelectiveStringEnumConverter.cs new file mode 100644 index 00000000..e9c5496d --- /dev/null +++ b/src/StardewModdingAPI/Framework/Serialisation/SelectiveStringEnumConverter.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Converters; + +namespace StardewModdingAPI.Framework.Serialisation +{ + /// A variant of which only converts certain enums. + internal class SelectiveStringEnumConverter : StringEnumConverter + { + /********* + ** Properties + *********/ + /// The enum type names to convert. + private readonly HashSet Types; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The enum types to convert. + public SelectiveStringEnumConverter(params Type[] types) + { + this.Types = new HashSet(types.Select(p => p.FullName)); + } + + /// Get whether this instance can convert the specified object type. + /// The object type. + public override bool CanConvert(Type type) + { + return base.CanConvert(type) && this.Types.Contains(type.FullName); + } + } +} diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 35dd6513..796980cb 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -153,6 +153,7 @@ + -- cgit From e321362378eaacd0081db44f0db3ef457ef97368 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 17 Feb 2017 13:59:31 -0500 Subject: fix nullable enums not being written to JSON as string --- .../Framework/Serialisation/SelectiveStringEnumConverter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/Serialisation/SelectiveStringEnumConverter.cs b/src/StardewModdingAPI/Framework/Serialisation/SelectiveStringEnumConverter.cs index e9c5496d..37108556 100644 --- a/src/StardewModdingAPI/Framework/Serialisation/SelectiveStringEnumConverter.cs +++ b/src/StardewModdingAPI/Framework/Serialisation/SelectiveStringEnumConverter.cs @@ -29,7 +29,9 @@ namespace StardewModdingAPI.Framework.Serialisation /// The object type. public override bool CanConvert(Type type) { - return base.CanConvert(type) && this.Types.Contains(type.FullName); + return + base.CanConvert(type) + && this.Types.Contains((Nullable.GetUnderlyingType(type) ?? type).FullName); } } } -- cgit From b2efd34fec00a952cd7d2ebdc02a2c2be506380e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 19 Feb 2017 01:02:07 -0500 Subject: fix on-post-render graphics event being raised after screen is rendered This causes mods to draw on top of the rendered screen instead of within it, which leads to strange bugs like cursor coordinates not lining up with the cursor and transparency issues. --- src/StardewModdingAPI/Events/GraphicsEvents.cs | 6 ++++++ src/StardewModdingAPI/Framework/SGame.cs | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Events/GraphicsEvents.cs b/src/StardewModdingAPI/Events/GraphicsEvents.cs index d9d57a8d..25b976f1 100644 --- a/src/StardewModdingAPI/Events/GraphicsEvents.cs +++ b/src/StardewModdingAPI/Events/GraphicsEvents.cs @@ -75,6 +75,12 @@ namespace StardewModdingAPI.Events monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPostRenderEvent)}", GraphicsEvents.OnPostRenderEvent?.GetInvocationList()); } + /// Get whether there are any post-render event listeners. + internal static bool HasPostRenderListeners() + { + return GraphicsEvents.OnPostRenderEvent != null; + } + /**** ** GUI events ****/ diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index baf74fd4..f64457f8 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -987,13 +987,20 @@ namespace StardewModdingAPI.Framework Game1.overlayMenu.draw(Game1.spriteBatch); Game1.spriteBatch.End(); } + + if (GraphicsEvents.HasPostRenderListeners()) + { + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); + GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor); + Game1.spriteBatch.End(); + } + if ((double)Game1.options.zoomLevel == 1.0) return; this.GraphicsDevice.SetRenderTarget((RenderTarget2D)null); this.GraphicsDevice.Clear(this.bgColor); Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, 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); - GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor); Game1.spriteBatch.End(); } } -- cgit From a893cd9eeabdd88bd57a4b7e422de5f1d442073c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 19 Feb 2017 01:07:04 -0500 Subject: fix installer not ignoring potential game folders that don't contain a Stardew Valley exe --- release-notes.md | 1 + src/StardewModdingAPI.Installer/InteractiveInstaller.cs | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index a277ee4f..7cce15c4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -9,6 +9,7 @@ For players: * Simple nested mod folders are now recognised by SMAPI (e.g. `ModName-1.0\ModName\manifest.json`). * Fixed game's debug output being shown in the console for all users. * Fixed installer errors for some players when deleting files. +* Fixed installer not ignoring potential game folders that don't contain a Stardew Valley exe. * Fixed rare issue where mod dependencies would override SMAPI dependencies and cause unpredictable bugs. For mod developers: diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 86e11d71..1496bedd 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -435,10 +435,16 @@ namespace StardewModdingApi.Installer /// The current platform. private DirectoryInfo InteractivelyGetInstallPath(Platform platform) { + // get executable name + string executableFilename = platform == Platform.Windows + ? "Stardew Valley.exe" + : "StardewValley.exe"; + // try default paths foreach (string defaultPath in this.DefaultInstallPaths) { - if (Directory.Exists(defaultPath)) + DirectoryInfo dir = new DirectoryInfo(defaultPath); + if (dir.Exists && dir.EnumerateFiles(executableFilename).Any()) return new DirectoryInfo(defaultPath); } @@ -447,7 +453,7 @@ namespace StardewModdingApi.Installer while (true) { // get path from user - Console.WriteLine($"Type the file path to the game directory (the one containing '{(platform == Platform.Mono ? "StardewValley.exe" : "Stardew Valley.exe")}'), then press enter."); + Console.WriteLine($"Type the file path to the game directory (the one containing '{executableFilename}'), then press enter."); string path = Console.ReadLine()?.Trim(); if (string.IsNullOrWhiteSpace(path)) { @@ -470,7 +476,7 @@ namespace StardewModdingApi.Installer Console.WriteLine(" That directory doesn't seem to exist."); continue; } - if (!directory.EnumerateFiles("*.exe").Any(p => p.Name == "StardewValley.exe" || p.Name == "Stardew Valley.exe")) + if (!directory.EnumerateFiles(executableFilename).Any()) { Console.WriteLine(" That directory doesn't contain a Stardew Valley executable."); continue; -- cgit From c72adcd119d9e96f47f93a2a7d5beb4974dffc0b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 19 Feb 2017 01:28:00 -0500 Subject: use more robust crossplatform path checks in installer --- .../InteractiveInstaller.cs | 62 +++++++++++----------- 1 file changed, 32 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 1496bedd..7f59ed2a 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -4,9 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading; -#if SMAPI_FOR_WINDOWS using Microsoft.Win32; -#endif using StardewModdingApi.Installer.Enums; namespace StardewModdingApi.Installer @@ -18,37 +16,43 @@ namespace StardewModdingApi.Installer ** Properties *********/ /// The default file paths where Stardew Valley can be installed. + /// The target platform. /// Derived from the crossplatform mod config: https://github.com/Pathoschild/Stardew.ModBuildConfig. - private IEnumerable DefaultInstallPaths + private IEnumerable GetDefaultInstallPaths(Platform platform) { - get + switch (platform) { - // Linux - yield return $"{Environment.GetEnvironmentVariable("HOME")}/GOG Games/Stardew Valley/game"; - yield return $"{Environment.GetEnvironmentVariable("HOME")}/.local/share/Steam/steamapps/common/Stardew Valley"; + case Platform.Mono: + // Linux + yield return $"{Environment.GetEnvironmentVariable("HOME")}/GOG Games/Stardew Valley/game"; + yield return $"{Environment.GetEnvironmentVariable("HOME")}/.local/share/Steam/steamapps/common/Stardew Valley"; + + // Mac + yield return "/Applications/Stardew Valley.app/Contents/MacOS"; + yield return $"{Environment.GetEnvironmentVariable("HOME")}/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS"; + break; - // Mac - yield return "/Applications/Stardew Valley.app/Contents/MacOS"; - yield return $"{Environment.GetEnvironmentVariable("HOME")}/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS"; + case Platform.Windows: + // Windows + yield return @"C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley"; + yield return @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley"; - // Windows - yield return @"C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley"; - yield return @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley"; + // Windows registry + IDictionary registryKeys = new Dictionary + { + [@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150"] = "InstallLocation", // Steam + [@"SOFTWARE\WOW6432Node\GOG.com\Games\1453375253"] = "PATH", // GOG on 64-bit Windows + }; + foreach (var pair in registryKeys) + { + string path = this.GetLocalMachineRegistryValue(pair.Key, pair.Value); + if (!string.IsNullOrWhiteSpace(path)) + yield return path; + } + break; - // Windows registry -#if SMAPI_FOR_WINDOWS - IDictionary registryKeys = new Dictionary - { - [@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150"] = "InstallLocation", // Steam - [@"SOFTWARE\WOW6432Node\GOG.com\Games\1453375253"] = "PATH", // GOG on 64-bit Windows - }; - foreach (var pair in registryKeys) - { - string path = this.GetLocalMachineRegistryValue(pair.Key, pair.Value); - if (!string.IsNullOrWhiteSpace(path)) - yield return path; - } -#endif + default: + throw new InvalidOperationException($"Unknown platform '{platform}'."); } } @@ -307,7 +311,6 @@ namespace StardewModdingApi.Installer } } -#if SMAPI_FOR_WINDOWS /// Get the value of a key in the Windows registry. /// The full path of the registry key relative to HKLM. /// The name of the value. @@ -320,7 +323,6 @@ namespace StardewModdingApi.Installer using (openKey) return (string)openKey.GetValue(name); } -#endif /// Print a debug message. /// The text to print. @@ -441,7 +443,7 @@ namespace StardewModdingApi.Installer : "StardewValley.exe"; // try default paths - foreach (string defaultPath in this.DefaultInstallPaths) + foreach (string defaultPath in this.GetDefaultInstallPaths(platform)) { DirectoryInfo dir = new DirectoryInfo(defaultPath); if (dir.Exists && dir.EnumerateFiles(executableFilename).Any()) -- cgit From 69ed617e568c9f2e4a15aee4a7bdecba03c13341 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 19 Feb 2017 01:29:30 -0500 Subject: fix installer not recognising Linux/Mac paths starting with ~ or containing an escaped space --- release-notes.md | 1 + src/StardewModdingAPI.Installer/InteractiveInstaller.cs | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 7cce15c4..b827f6bf 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ For players: * Fixed game's debug output being shown in the console for all users. * Fixed installer errors for some players when deleting files. * Fixed installer not ignoring potential game folders that don't contain a Stardew Valley exe. +* Fixed installer not recognising Linux/Mac paths starting with `~/` or containing an escaped space. * Fixed rare issue where mod dependencies would override SMAPI dependencies and cause unpredictable bugs. For mod developers: diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 7f59ed2a..7dcd88fd 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -463,9 +463,16 @@ namespace StardewModdingApi.Installer continue; } - // normalise on Windows + // normalise path if (platform == Platform.Windows) path = path.Replace("\"", ""); // in Windows, quotes are used to escape spaces and aren't part of the file path + if (platform == Platform.Mono) + path = path.Replace("\\ ", " "); // in Linux/Mac, spaces in paths may be escaped if copied from the command line + if (path.StartsWith("~/")) + { + string home = Environment.GetEnvironmentVariable("HOME") ?? Environment.GetEnvironmentVariable("USERPROFILE"); + path = Path.Combine(home, path.Substring(2)); + } // get directory if (File.Exists(path)) -- cgit From 5bbebcb46b38c562d0310eba9def31884d0362ce Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 19 Feb 2017 19:55:00 -0500 Subject: mark another incompatible mod (#231) --- src/StardewModdingAPI/StardewModdingAPI.data.json | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json index 9ee2cb98..592af665 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -120,6 +120,12 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "UpperVersion": "1.10.1", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/541" }, + { + "Name": "Makeshift Multiplayer", + "ID": "StardewValleyMP", + "UpperVersion": "0.2.10", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/501" + }, { "Name": "NoSoilDecay", "ID": "289dee03-5f38-4d8e-8ffc-e440198e8610", -- cgit From 7521570341b79ea78c590a202fbda55bd6fe06b7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 19 Feb 2017 20:12:15 -0500 Subject: make mod-not-compatible messages shorter --- src/StardewModdingAPI/Program.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index de07e8ad..b14240ca 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -418,11 +418,14 @@ namespace StardewModdingAPI { if (!compatibility.IsCompatible(manifest.Version)) { - string reasonPhrase = compatibility.ReasonPhrase ?? "this version is not compatible with the latest version of the game"; - string warning = $"Skipped {compatibility.Name} {manifest.Version} because {reasonPhrase}. Please check for a newer version of the mod here:"; - if (!string.IsNullOrWhiteSpace(compatibility.UpdateUrl)) - warning += $"{Environment.NewLine}- official mod: {compatibility.UpdateUrl}"; - if (!string.IsNullOrWhiteSpace(compatibility.UnofficialUpdateUrl)) + bool hasOfficialUrl = !string.IsNullOrWhiteSpace(compatibility.UpdateUrl); + bool hasUnofficialUrl = !string.IsNullOrWhiteSpace(compatibility.UnofficialUpdateUrl); + + string reasonPhrase = compatibility.ReasonPhrase ?? "it isn't compatible with the latest version of the game"; + string warning = $"Skipped {compatibility.Name} because {reasonPhrase}. Please check for a version newer than {compatibility.UpperVersion} here:"; + if (hasOfficialUrl) + warning += !hasUnofficialUrl ? $" {compatibility.UpdateUrl}" : $"{Environment.NewLine}- official mod: {compatibility.UpdateUrl}"; + if (hasUnofficialUrl) warning += $"{Environment.NewLine}- unofficial update: {compatibility.UnofficialUpdateUrl}"; this.Monitor.Log(warning, LogLevel.Error); -- cgit From 703f5f89a8d8eadede4d6fa4a9022283702608bd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 21 Feb 2017 15:54:48 -0500 Subject: fix new error when entering an empty command in SMAPI console --- src/StardewModdingAPI/Framework/CommandManager.cs | 3 +++ src/StardewModdingAPI/Program.cs | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/CommandManager.cs b/src/StardewModdingAPI/Framework/CommandManager.cs index 3aa4bf97..9af3d27a 100644 --- a/src/StardewModdingAPI/Framework/CommandManager.cs +++ b/src/StardewModdingAPI/Framework/CommandManager.cs @@ -70,6 +70,9 @@ namespace StardewModdingAPI.Framework /// Returns whether a matching command was triggered. public bool Trigger(string input) { + if (string.IsNullOrWhiteSpace(input)) + return false; + string[] args = input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string name = args[0]; args = args.Skip(1).ToArray(); diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index b14240ca..ea81d182 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -575,7 +575,7 @@ namespace StardewModdingAPI while (true) { string input = Console.ReadLine(); - if (!this.CommandManager.Trigger(input)) + if (!string.IsNullOrWhiteSpace(input) && !this.CommandManager.Trigger(input)) this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error); } } @@ -587,7 +587,6 @@ namespace StardewModdingAPI { if (arguments.Any()) { - Framework.Command result = this.CommandManager.Get(arguments[0]); if (result == null) this.Monitor.Log("There's no command with that name.", LogLevel.Error); -- cgit From b30d93d0ffd28c667e5ac51b26c6125fb57ad13a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 22 Feb 2017 19:36:10 -0500 Subject: add rationale notes to incompatible-mods list (#231) --- src/StardewModdingAPI/StardewModdingAPI.data.json | 91 +++++++++++++++-------- 1 file changed, 59 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json index 592af665..c4f708dc 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -32,13 +32,15 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "ID": "AccessChestAnywhere", "UpperVersion": "1.1", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/257", - "UnofficialUpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/518" + "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", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/439" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/439", + "Notes": "Uses obsolete StardewModdingAPI.Extensions." }, { "Name": "Better Sprinklers", @@ -46,150 +48,175 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "UpperVersion": "2.1", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/41", "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", - "ForceCompatibleVersion": "^2.1-EntoPatch.7" + "ForceCompatibleVersion": "^2.1-EntoPatch.7", + "Notes": "Uses obsolete StardewModdingAPI.Extensions." }, { "Name": "Casks Anywhere", "ID": "CasksAnywhere", "UpperVersion": "1.1", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/878" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/878", + "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." }, { "Name": "Chests Anywhere", "ID": "ChestsAnywhere", "UpperVersion": "1.8.2", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/518" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/518", + "Notes": "Crashes with 'Method not found: Void StardewValley.Menus.TextBox.set_Highlighted(Boolean)'." }, { "Name": "CJB Automation", "ID": "CJBAutomation", "UpperVersion": "1.4", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/211" + "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.13", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4", + "Notes": "Uses removed Game1.borderFont." }, { "Name": "CJB Item Spawner", "ID": "CJBItemSpawner", "UpperVersion": "1.6", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93", + "Notes": "Uses removed Game1.borderFont." }, { "Name": "Cooking Skill", "ID": "CookingSkill", "UpperVersion": "1.0.3", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/522" + "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", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/193" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/193", + "Notes": "Uses obsolete GraphicsEvents.DrawTick." }, { "Name": "Entoarox Framework", "ID": "eacdb74b-4080-4452-b16b-93773cda5cf9", "UpperVersion": "1.6.5", - "UpdateUrl": "http://community.playstarbound.com/resources/4228" + "UpdateUrl": "http://community.playstarbound.com/resources/4228", + "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", // real latest version is 0.94, but mod incorrectly sets version to 1.0 in manifest - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/485" + "UpperVersion": "1.0", + "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", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/331" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/331", + "Notes": "Crashes with NullReferenceException in GameEvents.UpdateTick." }, { "Name": "Lookup Anything", - "ID": "Pathoschild.LookupAnything", - "UpperVersion": "1.10.1", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/541" + "ID": "LookupAnything", + "UpperVersion": "1.10", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/541", + "Notes": "Crashes with FormatException when looking up NPCs." }, { "Name": "Lookup Anything", - "ID": "LookupAnything", // ID changed in latest version + "ID": "Pathoschild.LookupAnything", "UpperVersion": "1.10.1", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/541" + "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", "UpperVersion": "0.2.10", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/501" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/501", + "Notes": "Uses obsolete GraphicsEvents.OnPreRenderHudEventNoCheck." }, { "Name": "NoSoilDecay", "ID": "289dee03-5f38-4d8e-8ffc-e440198e8610", "UpperVersion": "0.5", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/237" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/237", + "Notes": "Uses Assembly.GetExecutingAssembly().Location." }, { "Name": "Point-and-Plant", "ID": "PointAndPlant.dll", "UpperVersion": "1.0.2", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/572" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/572", + "Notes": "Uses obsolete StardewModdingAPI.Extensions." }, { "Name": "Reusable Wallpapers", "ID": "dae1b553-2e39-43e7-8400-c7c5c836134b", "UpperVersion": "1.5", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/356" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/356", + "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." }, { - "Name": "Save Anywhere", // depends on StarDustCore + "Name": "Save Anywhere", "ID": "SaveAnywhere", "UpperVersion": "2.0", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/444" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/444", + "Notes": "Depends on StarDustCore." }, { "Name": "StackSplitX", "ID": "StackSplitX.dll", "UpperVersion": "1.0", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/798" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/798", + "Notes": "Uses SMAPI's internal SGame class." }, { "Name": "StarDustCore", "ID": "StarDustCore", "UpperVersion": "1.0", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/683" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/683", + "Notes": "Crashes with 'Method not found: Void StardewModdingAPI.Command.CallCommand(System.String)'." }, { "Name": "Zoryn's Better RNG", "ID": "76b6d1e1-f7ba-4d72-8c32-5a1e6d2716f6", "UpperVersion": "1.5", - "UpdateUrl": "http://community.playstarbound.com/threads/108756" + "UpdateUrl": "http://community.playstarbound.com/threads/108756", + "Notes": "Uses SMAPI's internal SGame class." }, { "Name": "Zoryn's Calendar Anywhere", "ID": "a41c01cd-0437-43eb-944f-78cb5a53002a", "UpperVersion": "1.5", - "UpdateUrl": "http://community.playstarbound.com/threads/108756" + "UpdateUrl": "http://community.playstarbound.com/threads/108756", + "Notes": "Uses SMAPI's internal SGame class." }, { "Name": "Zoryn's Health Bars", "ID": "HealthBars.dll", "UpperVersion": "1.5", - "UpdateUrl": "http://community.playstarbound.com/threads/108756" + "UpdateUrl": "http://community.playstarbound.com/threads/108756", + "Notes": "Uses SMAPI's internal SGame class." }, { "Name": "Zoryn's Movement Mod", "ID": "8a632929-8335-484f-87dd-c29d2ba3215d", "UpperVersion": "1.5", - "UpdateUrl": "http://community.playstarbound.com/threads/108756" + "UpdateUrl": "http://community.playstarbound.com/threads/108756", + "Notes": "Uses SMAPI's internal SGame class." }, { "Name": "Zoryn's Regen Mod", "ID": "dfac4383-1b6b-4f33-ae4e-37fc23e5252e", "UpperVersion": "1.5", - "UpdateUrl": "http://community.playstarbound.com/threads/108756" + "UpdateUrl": "http://community.playstarbound.com/threads/108756", + "Notes": "Uses SMAPI's internal SGame class." } ] -- cgit From 96c7010c1b1757217a14b49a563707a972fd6107 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 22 Feb 2017 19:48:25 -0500 Subject: update for Stardew Valley 1.9 (#231) --- src/StardewModdingAPI/Constants.cs | 2 +- src/StardewModdingAPI/Framework/SGame.cs | 24 ++++++++++++++--------- src/StardewModdingAPI/Program.cs | 6 +++--- src/StardewModdingAPI/StardewModdingAPI.data.json | 14 +++++++++++++ 4 files changed, 33 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 262ba61d..76003b69 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -33,7 +33,7 @@ namespace StardewModdingAPI public static ISemanticVersion ApiVersion => new SemanticVersion(1, 8, 0, null); /// The minimum supported version of Stardew Valley. - public const string MinimumGameVersion = "1.2"; + public const string MinimumGameVersion = "1.2.9"; /// The directory path containing Stardew Valley's app data. public static string DataPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley"); diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index f64457f8..56631260 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -923,7 +923,7 @@ namespace StardewModdingAPI.Framework { SpriteBatch spriteBatch = Game1.spriteBatch; SpriteFont smallFont = Game1.smallFont; - object[] objArray = new object[8]; + object[] objArray = new object[10]; int index1 = 0; string str1; if (!Game1.panMode) @@ -938,20 +938,26 @@ namespace StardewModdingAPI.Framework float cursorTransparency = Game1.mouseCursorTransparency; objArray[index3] = (object)cursorTransparency; int index4 = 3; - string str3 = " wasMouseVisibleThisFrame: "; + string str3 = " mousePosition: "; objArray[index4] = (object)str3; int index5 = 4; - string str4 = Game1.wasMouseVisibleThisFrame.ToString(); - objArray[index5] = (object)str4; + int mouseX = Game1.getMouseX(); + objArray[index5] = (object)mouseX; int index6 = 5; - string newLine = Environment.NewLine; - objArray[index6] = (object)newLine; + string str4 = ","; + objArray[index6] = (object)str4; int index7 = 6; - string str5 = "debugOutput: "; - objArray[index7] = (object)str5; + 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[index8] = (object)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; diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index ea81d182..b8a16e79 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -97,6 +97,7 @@ namespace StardewModdingAPI // initialise logging Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); // for consistent log formatting this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info); + Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Game1.version}"; // read settings { @@ -252,11 +253,10 @@ namespace StardewModdingAPI 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 - Version {Game1.version}"; + this.GameInstance.Window.Title = $"Stardew Valley {Game1.version}"; gameProgramType.GetField("gamePtr").SetValue(gameProgramType, this.GameInstance); // configure - Game1.version += $" | SMAPI {Constants.ApiVersion}"; Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; } @@ -565,7 +565,7 @@ namespace StardewModdingAPI // print result this.Monitor.Log($"Loaded {modsLoaded} mods."); - Console.Title = $"Stardew Modding API Console - Version {Constants.ApiVersion} - Mods Loaded: {modsLoaded}"; + Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Game1.version} with {modsLoaded} mods"; } // ReSharper disable once FunctionNeverReturns diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json index c4f708dc..a28dc3e8 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -65,6 +65,13 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "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", + "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", @@ -184,6 +191,13 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/683", "Notes": "Crashes with 'Method not found: Void StardewModdingAPI.Command.CallCommand(System.String)'." }, + { + "Name": "Teleporter", + "ID": "Teleporter", + "UpperVersion": "1.0.2", + "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", -- cgit From 6a18dd6fadaea7d72ce9ef4e2752c669c4f6420b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 23 Feb 2017 22:58:23 -0500 Subject: merge config files --- README.md | 2 - src/StardewModdingAPI/Constants.cs | 3 - src/StardewModdingAPI/Framework/Models/SConfig.cs | 18 ++ .../Framework/Models/UserSettings.cs | 15 -- src/StardewModdingAPI/Program.cs | 65 ++---- .../StardewModdingAPI.config.json | 241 ++++++++++++++++++++- src/StardewModdingAPI/StardewModdingAPI.csproj | 6 +- src/StardewModdingAPI/StardewModdingAPI.data.json | 236 -------------------- src/prepare-install-package.targets | 2 - 9 files changed, 280 insertions(+), 308 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Models/SConfig.cs delete mode 100644 src/StardewModdingAPI/Framework/Models/UserSettings.cs delete mode 100644 src/StardewModdingAPI/StardewModdingAPI.data.json (limited to 'src') diff --git a/README.md b/README.md index 63adcf78..349916cc 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,6 @@ folder containing `src`). StardewModdingAPI StardewModdingAPI.AssemblyRewriters.dll StardewModdingAPI.config.json - StardewModdingAPI.data.json StardewModdingAPI.exe StardewModdingAPI.exe.mdb steam_appid.txt @@ -97,7 +96,6 @@ folder containing `src`). Newtonsoft.Json.dll StardewModdingAPI.AssemblyRewriters.dll StardewModdingAPI.config.json - StardewModdingAPI.data.json StardewModdingAPI.exe StardewModdingAPI.pdb StardewModdingAPI.xml diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 76003b69..8b085eac 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -62,9 +62,6 @@ namespace StardewModdingAPI /// The file path for the SMAPI configuration file. internal static string ApiConfigPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.config.json"); - /// The file path for the SMAPI data file containing metadata about known mods. - internal static string ApiModMetadataPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.data.json"); - /// The file path to the log where the latest output should be saved. internal static string LogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt"); diff --git a/src/StardewModdingAPI/Framework/Models/SConfig.cs b/src/StardewModdingAPI/Framework/Models/SConfig.cs new file mode 100644 index 00000000..558da82a --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/SConfig.cs @@ -0,0 +1,18 @@ +namespace StardewModdingAPI.Framework.Models +{ + /// The SMAPI configuration settings. + internal class SConfig + { + /******** + ** Accessors + ********/ + /// Whether to enable development features. + public bool DeveloperMode { get; set; } + + /// Whether to check if a newer version of SMAPI is available on startup. + public bool CheckForUpdates { get; set; } = true; + + /// A list of mod versions which should be considered incompatible. + public IncompatibleMod[] IncompatibleMods { get; set; } + } +} diff --git a/src/StardewModdingAPI/Framework/Models/UserSettings.cs b/src/StardewModdingAPI/Framework/Models/UserSettings.cs deleted file mode 100644 index a0074f77..00000000 --- a/src/StardewModdingAPI/Framework/Models/UserSettings.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace StardewModdingAPI.Framework.Models -{ - /// Contains user settings from SMAPI's JSON configuration file. - internal class UserSettings - { - /********* - ** Accessors - *********/ - /// Whether to enable development features. - public bool DeveloperMode { get; set; } - - /// Whether to check if a newer version of SMAPI is available on startup. - public bool CheckForUpdates { get; set; } = true; - } -} diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index b8a16e79..eadbaaeb 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -47,8 +47,8 @@ namespace StardewModdingAPI /// The core logger for SMAPI. private readonly Monitor Monitor; - /// The user settings for SMAPI. - private UserSettings Settings; + /// The SMAPI configuration settings. + private readonly SConfig Settings; /// Tracks whether the game should exit immediately and any pending initialisation should be cancelled. private readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); @@ -87,6 +87,10 @@ namespace StardewModdingAPI /// Construct an instance. internal Program(bool writeToConsole) { + // load settings + this.Settings = JsonConvert.DeserializeObject(File.ReadAllText(Constants.ApiConfigPath)); + + // initialise this.Monitor = new Monitor("SMAPI", this.ConsoleManager, this.LogFile, this.ExitGameImmediately) { WriteToConsole = writeToConsole }; this.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); } @@ -99,20 +103,6 @@ namespace StardewModdingAPI this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info); Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Game1.version}"; - // read settings - { - string settingsPath = Constants.ApiConfigPath; - if (File.Exists(settingsPath)) - { - string json = File.ReadAllText(settingsPath); - this.Settings = JsonConvert.DeserializeObject(json); - } - else - this.Settings = new UserSettings(); - - File.WriteAllText(settingsPath, JsonConvert.SerializeObject(this.Settings, Formatting.Indented)); - } - // inject compatibility shims #pragma warning disable 618 Command.Shim(this.CommandManager, this.DeprecationManager, this.ModRegistry); @@ -341,20 +331,6 @@ namespace StardewModdingAPI AssemblyLoader modAssemblyLoader = new AssemblyLoader(this.TargetPlatform, this.Monitor); AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name); - // get known incompatible mods - IDictionary incompatibleMods; - try - { - incompatibleMods = File.Exists(Constants.ApiModMetadataPath) - ? JsonConvert.DeserializeObject(File.ReadAllText(Constants.ApiModMetadataPath)).ToDictionary(p => p.ID, p => p) - : new Dictionary(0); - } - catch (Exception ex) - { - incompatibleMods = new Dictionary(); - this.Monitor.Log($"Couldn't read metadata file at {Constants.ApiModMetadataPath}. SMAPI will still run, but some features may be disabled.\n{ex}", LogLevel.Warn); - } - // load mod assemblies int modsLoaded = 0; List deprecationWarnings = new List(); // queue up deprecation warnings to show after mod list @@ -413,23 +389,26 @@ namespace StardewModdingAPI } // validate known incompatible mods - IncompatibleMod compatibility; - if (incompatibleMods.TryGetValue(!string.IsNullOrWhiteSpace(manifest.UniqueID) ? manifest.UniqueID : manifest.EntryDll, out compatibility)) { - if (!compatibility.IsCompatible(manifest.Version)) + string modKey = !string.IsNullOrWhiteSpace(manifest.UniqueID) ? manifest.UniqueID : manifest.EntryDll; + IncompatibleMod compatibility = this.Settings.IncompatibleMods.FirstOrDefault(p => p.ID == modKey); + if(compatibility != null) { - bool hasOfficialUrl = !string.IsNullOrWhiteSpace(compatibility.UpdateUrl); - bool hasUnofficialUrl = !string.IsNullOrWhiteSpace(compatibility.UnofficialUpdateUrl); + if (!compatibility.IsCompatible(manifest.Version)) + { + bool hasOfficialUrl = !string.IsNullOrWhiteSpace(compatibility.UpdateUrl); + bool hasUnofficialUrl = !string.IsNullOrWhiteSpace(compatibility.UnofficialUpdateUrl); - string reasonPhrase = compatibility.ReasonPhrase ?? "it isn't compatible with the latest version of the game"; - string warning = $"Skipped {compatibility.Name} because {reasonPhrase}. Please check for a version newer than {compatibility.UpperVersion} here:"; - if (hasOfficialUrl) - warning += !hasUnofficialUrl ? $" {compatibility.UpdateUrl}" : $"{Environment.NewLine}- official mod: {compatibility.UpdateUrl}"; - if (hasUnofficialUrl) - warning += $"{Environment.NewLine}- unofficial update: {compatibility.UnofficialUpdateUrl}"; + string reasonPhrase = compatibility.ReasonPhrase ?? "it isn't compatible with the latest version of the game"; + string warning = $"Skipped {compatibility.Name} because {reasonPhrase}. Please check for a version newer than {compatibility.UpperVersion} here:"; + if (hasOfficialUrl) + warning += !hasUnofficialUrl ? $" {compatibility.UpdateUrl}" : $"{Environment.NewLine}- official mod: {compatibility.UpdateUrl}"; + if (hasUnofficialUrl) + warning += $"{Environment.NewLine}- unofficial update: {compatibility.UnofficialUpdateUrl}"; - this.Monitor.Log(warning, LogLevel.Error); - continue; + this.Monitor.Log(warning, LogLevel.Error); + continue; + } } } diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index 2abaf73a..e971c324 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -1,4 +1,241 @@ -{ +/* + + +This file contains advanced configuration for SMAPI. You +generally shouldn't change this file unless necessary. + + +*/ +{ "DeveloperMode": true, - "CheckForUpdates": true + "CheckForUpdates": true, + "IncompatibleMods": [ + /* versions which crash the game */ + { + "Name": "NPC Map Locations", + "ID": "NPCMapLocationsMod", + "LowerVersion": "1.42", + "UpperVersion": "1.43", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/239", + "ReasonPhrase": "this version has an update check error which crashes the game" + }, + + /* versions not compatible with Stardew Valley 1.1+ */ + { + "Name": "Chest Label System", + "ID": "SPDChestLabel", + "UpperVersion": "1.5", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/242", + "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", + "ForceCompatibleVersion": "^1.5-EntoPatch" + }, + + /* versions not compatible with Stardew Valley 1.2+ */ + { + "Name": "AccessChestAnywhere", + "ID": "AccessChestAnywhere", + "UpperVersion": "1.1", + "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", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/439", + "Notes": "Uses obsolete StardewModdingAPI.Extensions." + }, + { + "Name": "Better Sprinklers", + "ID": "SPDSprinklersMod", + "UpperVersion": "2.1", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/41", + "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", + "ForceCompatibleVersion": "^2.1-EntoPatch.7", + "Notes": "Uses obsolete StardewModdingAPI.Extensions." + }, + { + "Name": "Casks Anywhere", + "ID": "CasksAnywhere", + "UpperVersion": "1.1", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/878", + "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." + }, + { + "Name": "Chests Anywhere", + "ID": "ChestsAnywhere", + "UpperVersion": "1.8.2", + "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", + "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", + "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.13", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4", + "Notes": "Uses removed Game1.borderFont." + }, + { + "Name": "CJB Item Spawner", + "ID": "CJBItemSpawner", + "UpperVersion": "1.6", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93", + "Notes": "Uses removed Game1.borderFont." + }, + { + "Name": "Cooking Skill", + "ID": "CookingSkill", + "UpperVersion": "1.0.3", + "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", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/193", + "Notes": "Uses obsolete GraphicsEvents.DrawTick." + }, + { + "Name": "Entoarox Framework", + "ID": "eacdb74b-4080-4452-b16b-93773cda5cf9", + "UpperVersion": "1.6.5", + "UpdateUrl": "http://community.playstarbound.com/resources/4228", + "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", + "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", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/331", + "Notes": "Crashes with NullReferenceException in GameEvents.UpdateTick." + }, + { + "Name": "Lookup Anything", + "ID": "LookupAnything", + "UpperVersion": "1.10", + "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", + "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", + "UpperVersion": "0.2.10", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/501", + "Notes": "Uses obsolete GraphicsEvents.OnPreRenderHudEventNoCheck." + }, + { + "Name": "NoSoilDecay", + "ID": "289dee03-5f38-4d8e-8ffc-e440198e8610", + "UpperVersion": "0.5", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/237", + "Notes": "Uses Assembly.GetExecutingAssembly().Location." + }, + { + "Name": "Point-and-Plant", + "ID": "PointAndPlant.dll", + "UpperVersion": "1.0.2", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/572", + "Notes": "Uses obsolete StardewModdingAPI.Extensions." + }, + { + "Name": "Reusable Wallpapers", + "ID": "dae1b553-2e39-43e7-8400-c7c5c836134b", + "UpperVersion": "1.5", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/356", + "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." + }, + { + "Name": "Save Anywhere", + "ID": "SaveAnywhere", + "UpperVersion": "2.0", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/444", + "Notes": "Depends on StarDustCore." + }, + { + "Name": "StackSplitX", + "ID": "StackSplitX.dll", + "UpperVersion": "1.0", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/798", + "Notes": "Uses SMAPI's internal SGame class." + }, + { + "Name": "StarDustCore", + "ID": "StarDustCore", + "UpperVersion": "1.0", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/683", + "Notes": "Crashes with 'Method not found: Void StardewModdingAPI.Command.CallCommand(System.String)'." + }, + { + "Name": "Teleporter", + "ID": "Teleporter", + "UpperVersion": "1.0.2", + "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", + "UpdateUrl": "http://community.playstarbound.com/threads/108756", + "Notes": "Uses SMAPI's internal SGame class." + }, + { + "Name": "Zoryn's Calendar Anywhere", + "ID": "a41c01cd-0437-43eb-944f-78cb5a53002a", + "UpperVersion": "1.5", + "UpdateUrl": "http://community.playstarbound.com/threads/108756", + "Notes": "Uses SMAPI's internal SGame class." + }, + { + "Name": "Zoryn's Health Bars", + "ID": "HealthBars.dll", + "UpperVersion": "1.5", + "UpdateUrl": "http://community.playstarbound.com/threads/108756", + "Notes": "Uses SMAPI's internal SGame class." + }, + { + "Name": "Zoryn's Movement Mod", + "ID": "8a632929-8335-484f-87dd-c29d2ba3215d", + "UpperVersion": "1.5", + "UpdateUrl": "http://community.playstarbound.com/threads/108756", + "Notes": "Uses SMAPI's internal SGame class." + }, + { + "Name": "Zoryn's Regen Mod", + "ID": "dfac4383-1b6b-4f33-ae4e-37fc23e5252e", + "UpperVersion": "1.5", + "UpdateUrl": "http://community.playstarbound.com/threads/108756", + "Notes": "Uses SMAPI's internal SGame class." + } + ] } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 796980cb..72cc1ed2 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -150,6 +150,7 @@ + @@ -182,7 +183,6 @@ - @@ -209,9 +209,6 @@ Always - - Always - Always @@ -255,7 +252,6 @@ - diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json deleted file mode 100644 index a28dc3e8..00000000 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ /dev/null @@ -1,236 +0,0 @@ -/* - - -This file contains advanced metadata for SMAPI. You shouldn't change this file. - - -*/ -[ - /* versions which crash the game */ - { - "Name": "NPC Map Locations", - "ID": "NPCMapLocationsMod", - "LowerVersion": "1.42", - "UpperVersion": "1.43", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/239", - "ReasonPhrase": "this version has an update check error which crashes the game" - }, - - /* versions not compatible with Stardew Valley 1.1+ */ - { - "Name": "Chest Label System", - "ID": "SPDChestLabel", - "UpperVersion": "1.5", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/242", - "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", - "ForceCompatibleVersion": "^1.5-EntoPatch" - }, - - /* versions not compatible with Stardew Valley 1.2+ */ - { - "Name": "AccessChestAnywhere", - "ID": "AccessChestAnywhere", - "UpperVersion": "1.1", - "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", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/439", - "Notes": "Uses obsolete StardewModdingAPI.Extensions." - }, - { - "Name": "Better Sprinklers", - "ID": "SPDSprinklersMod", - "UpperVersion": "2.1", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/41", - "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", - "ForceCompatibleVersion": "^2.1-EntoPatch.7", - "Notes": "Uses obsolete StardewModdingAPI.Extensions." - }, - { - "Name": "Casks Anywhere", - "ID": "CasksAnywhere", - "UpperVersion": "1.1", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/878", - "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." - }, - { - "Name": "Chests Anywhere", - "ID": "ChestsAnywhere", - "UpperVersion": "1.8.2", - "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", - "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", - "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.13", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4", - "Notes": "Uses removed Game1.borderFont." - }, - { - "Name": "CJB Item Spawner", - "ID": "CJBItemSpawner", - "UpperVersion": "1.6", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93", - "Notes": "Uses removed Game1.borderFont." - }, - { - "Name": "Cooking Skill", - "ID": "CookingSkill", - "UpperVersion": "1.0.3", - "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", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/193", - "Notes": "Uses obsolete GraphicsEvents.DrawTick." - }, - { - "Name": "Entoarox Framework", - "ID": "eacdb74b-4080-4452-b16b-93773cda5cf9", - "UpperVersion": "1.6.5", - "UpdateUrl": "http://community.playstarbound.com/resources/4228", - "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", - "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", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/331", - "Notes": "Crashes with NullReferenceException in GameEvents.UpdateTick." - }, - { - "Name": "Lookup Anything", - "ID": "LookupAnything", - "UpperVersion": "1.10", - "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", - "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", - "UpperVersion": "0.2.10", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/501", - "Notes": "Uses obsolete GraphicsEvents.OnPreRenderHudEventNoCheck." - }, - { - "Name": "NoSoilDecay", - "ID": "289dee03-5f38-4d8e-8ffc-e440198e8610", - "UpperVersion": "0.5", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/237", - "Notes": "Uses Assembly.GetExecutingAssembly().Location." - }, - { - "Name": "Point-and-Plant", - "ID": "PointAndPlant.dll", - "UpperVersion": "1.0.2", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/572", - "Notes": "Uses obsolete StardewModdingAPI.Extensions." - }, - { - "Name": "Reusable Wallpapers", - "ID": "dae1b553-2e39-43e7-8400-c7c5c836134b", - "UpperVersion": "1.5", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/356", - "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." - }, - { - "Name": "Save Anywhere", - "ID": "SaveAnywhere", - "UpperVersion": "2.0", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/444", - "Notes": "Depends on StarDustCore." - }, - { - "Name": "StackSplitX", - "ID": "StackSplitX.dll", - "UpperVersion": "1.0", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/798", - "Notes": "Uses SMAPI's internal SGame class." - }, - { - "Name": "StarDustCore", - "ID": "StarDustCore", - "UpperVersion": "1.0", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/683", - "Notes": "Crashes with 'Method not found: Void StardewModdingAPI.Command.CallCommand(System.String)'." - }, - { - "Name": "Teleporter", - "ID": "Teleporter", - "UpperVersion": "1.0.2", - "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", - "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class." - }, - { - "Name": "Zoryn's Calendar Anywhere", - "ID": "a41c01cd-0437-43eb-944f-78cb5a53002a", - "UpperVersion": "1.5", - "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class." - }, - { - "Name": "Zoryn's Health Bars", - "ID": "HealthBars.dll", - "UpperVersion": "1.5", - "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class." - }, - { - "Name": "Zoryn's Movement Mod", - "ID": "8a632929-8335-484f-87dd-c29d2ba3215d", - "UpperVersion": "1.5", - "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class." - }, - { - "Name": "Zoryn's Regen Mod", - "ID": "dfac4383-1b6b-4f33-ae4e-37fc23e5252e", - "UpperVersion": "1.5", - "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class." - } -] diff --git a/src/prepare-install-package.targets b/src/prepare-install-package.targets index bd9287f1..709bd8d4 100644 --- a/src/prepare-install-package.targets +++ b/src/prepare-install-package.targets @@ -28,7 +28,6 @@ - @@ -43,7 +42,6 @@ - -- cgit From 3005270437f609bb14be9da67e3ffadabd8685a3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 23 Feb 2017 23:05:46 -0500 Subject: shorten mod path in error messages --- src/StardewModdingAPI/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index eadbaaeb..9a6f07ef 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -355,7 +355,7 @@ namespace StardewModdingAPI this.Monitor.Log($"Ignored folder \"{directory.Name}\" which doesn't have a manifest.json.", LogLevel.Warn); continue; } - string errorPrefix = $"Couldn't load mod for manifest '{manifestPath}'"; + string errorPrefix = $"Couldn't load mod for '{manifestPath.Replace(this.ModPath, "").Trim('/', '\\')}'"; // read manifest Manifest manifest; -- cgit From 6b26eceb57b8c1bdf245ec02ff979504701ede92 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 23 Feb 2017 23:36:14 -0500 Subject: move incompatible mod logic into mod registry --- src/StardewModdingAPI/Framework/ModRegistry.cs | 30 ++++++++++++++++ .../Framework/Models/IncompatibleMod.cs | 40 +++++++++++++--------- src/StardewModdingAPI/Program.cs | 35 ++++++++----------- src/StardewModdingAPI/SemanticVersion.cs | 4 +++ 4 files changed, 73 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/ModRegistry.cs b/src/StardewModdingAPI/Framework/ModRegistry.cs index 209f1928..233deb3c 100644 --- a/src/StardewModdingAPI/Framework/ModRegistry.cs +++ b/src/StardewModdingAPI/Framework/ModRegistry.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; +using StardewModdingAPI.Framework.Models; namespace StardewModdingAPI.Framework { @@ -18,10 +20,21 @@ namespace StardewModdingAPI.Framework /// The friendly mod names treated as deprecation warning sources (assembly full name => mod name). private readonly IDictionary ModNamesByAssembly = new Dictionary(); + /// The mod versions which should be disabled due to incompatibility. + private readonly IncompatibleMod[] IncompatibleMods; + /********* ** Public methods *********/ + /// Construct an instance. + /// The mod versions which should be disabled due to incompatibility. + public ModRegistry(IEnumerable incompatibleMods) + { + this.IncompatibleMods = incompatibleMods.ToArray(); + } + + /**** ** IModRegistry ****/ @@ -113,5 +126,22 @@ namespace StardewModdingAPI.Framework // no known assembly found return null; } + + /// Get a record indicating why a mod is incompatible (if applicable). + /// The mod manifest. + /// Returns the incompatibility record if applicable, else null. + internal IncompatibleMod GetIncompatibilityRecord(IManifest manifest) + { + string key = !string.IsNullOrWhiteSpace(manifest.UniqueID) ? manifest.UniqueID : manifest.EntryDll; + return ( + from mod in this.IncompatibleMods + where + mod.ID == key + && (mod.LowerSemanticVersion == null || !manifest.Version.IsOlderThan(mod.LowerSemanticVersion)) + && !manifest.Version.IsNewerThan(mod.UpperSemanticVersion) + && (string.IsNullOrWhiteSpace(mod.ForceCompatibleVersion) || !Regex.IsMatch(manifest.Version.ToString(), mod.ForceCompatibleVersion, RegexOptions.IgnoreCase)) + select mod + ).FirstOrDefault(); + } } } \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs b/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs index bcf5639c..29e18ddb 100644 --- a/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs +++ b/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using System.Runtime.Serialization; +using Newtonsoft.Json; namespace StardewModdingAPI.Framework.Models { @@ -8,6 +9,9 @@ namespace StardewModdingAPI.Framework.Models /********* ** Accessors *********/ + /**** + ** From config + ****/ /// The unique mod ID. public string ID { get; set; } @@ -34,24 +38,28 @@ namespace StardewModdingAPI.Framework.Models public string ReasonPhrase { get; set; } + /**** + ** Injected + ****/ + /// The semantic version corresponding to . + [JsonIgnore] + public ISemanticVersion LowerSemanticVersion { get; set; } + + /// The semantic version corresponding to . + [JsonIgnore] + public ISemanticVersion UpperSemanticVersion { get; set; } + + /********* - ** Public methods + ** Private methods *********/ - /// Get whether the specified version is compatible according to this metadata. - /// The current version of the matching mod. - public bool IsCompatible(ISemanticVersion version) + /// The method called when the model finishes deserialising. + /// The deserialisation context. + [OnDeserialized] + private void OnDeserialized(StreamingContext context) { - ISemanticVersion lowerVersion = this.LowerVersion != null ? new SemanticVersion(this.LowerVersion) : null; - ISemanticVersion upperVersion = new SemanticVersion(this.UpperVersion); - - // ignore versions not in range - if (lowerVersion != null && version.IsOlderThan(lowerVersion)) - return true; - if (version.IsNewerThan(upperVersion)) - return true; - - // allow versions matching override - return !string.IsNullOrWhiteSpace(this.ForceCompatibleVersion) && Regex.IsMatch(version.ToString(), this.ForceCompatibleVersion, RegexOptions.IgnoreCase); + this.LowerSemanticVersion = this.LowerVersion != null ? new SemanticVersion(this.LowerVersion) : null; + this.UpperSemanticVersion = this.UpperVersion != null ? new SemanticVersion(this.UpperVersion) : null; } } } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 9a6f07ef..3b6d1702 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -64,7 +64,7 @@ namespace StardewModdingAPI internal SGame GameInstance; /// Tracks the installed mods. - internal readonly ModRegistry ModRegistry = new ModRegistry(); + internal readonly ModRegistry ModRegistry; /// Manages deprecation warnings. internal readonly DeprecationManager DeprecationManager; @@ -92,6 +92,7 @@ namespace StardewModdingAPI // initialise this.Monitor = new Monitor("SMAPI", this.ConsoleManager, this.LogFile, this.ExitGameImmediately) { WriteToConsole = writeToConsole }; + this.ModRegistry = new ModRegistry(this.Settings.IncompatibleMods); this.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); } @@ -388,28 +389,22 @@ namespace StardewModdingAPI continue; } - // validate known incompatible mods + // validate compatibility + IncompatibleMod compatibility = this.ModRegistry.GetIncompatibilityRecord(manifest); + if (compatibility != null) { - string modKey = !string.IsNullOrWhiteSpace(manifest.UniqueID) ? manifest.UniqueID : manifest.EntryDll; - IncompatibleMod compatibility = this.Settings.IncompatibleMods.FirstOrDefault(p => p.ID == modKey); - if(compatibility != null) - { - if (!compatibility.IsCompatible(manifest.Version)) - { - bool hasOfficialUrl = !string.IsNullOrWhiteSpace(compatibility.UpdateUrl); - bool hasUnofficialUrl = !string.IsNullOrWhiteSpace(compatibility.UnofficialUpdateUrl); + bool hasOfficialUrl = !string.IsNullOrWhiteSpace(compatibility.UpdateUrl); + bool hasUnofficialUrl = !string.IsNullOrWhiteSpace(compatibility.UnofficialUpdateUrl); - string reasonPhrase = compatibility.ReasonPhrase ?? "it isn't compatible with the latest version of the game"; - string warning = $"Skipped {compatibility.Name} because {reasonPhrase}. Please check for a version newer than {compatibility.UpperVersion} here:"; - if (hasOfficialUrl) - warning += !hasUnofficialUrl ? $" {compatibility.UpdateUrl}" : $"{Environment.NewLine}- official mod: {compatibility.UpdateUrl}"; - if (hasUnofficialUrl) - warning += $"{Environment.NewLine}- unofficial update: {compatibility.UnofficialUpdateUrl}"; + string reasonPhrase = compatibility.ReasonPhrase ?? "it isn't compatible with the latest version of the game"; + string warning = $"Skipped {compatibility.Name} because {reasonPhrase}. Please check for a version newer than {compatibility.UpperVersion} here:"; + if (hasOfficialUrl) + warning += !hasUnofficialUrl ? $" {compatibility.UpdateUrl}" : $"{Environment.NewLine}- official mod: {compatibility.UpdateUrl}"; + if (hasUnofficialUrl) + warning += $"{Environment.NewLine}- unofficial update: {compatibility.UnofficialUpdateUrl}"; - this.Monitor.Log(warning, LogLevel.Error); - continue; - } - } + this.Monitor.Log(warning, LogLevel.Error); + continue; } // validate SMAPI version diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs index 3cb592e2..9610562f 100644 --- a/src/StardewModdingAPI/SemanticVersion.cs +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -63,9 +63,13 @@ namespace StardewModdingAPI /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. /// The version to compare with this instance. + /// The value is null. /// The implementation is defined by Semantic Version 2.0 (http://semver.org/). public int CompareTo(ISemanticVersion other) { + if(other == null) + throw new ArgumentNullException(nameof(other)); + const int same = 0; const int curNewer = 1; const int curOlder = -1; -- cgit From 9bbd0a44593e65f9e05b9c1161347173d43ff395 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 23 Feb 2017 23:36:48 -0500 Subject: make skipped-mod messages more user-friendly --- release-notes.md | 1 + src/StardewModdingAPI/Program.cs | 38 ++++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index b827f6bf..d6fb4b5b 100644 --- a/release-notes.md +++ b/release-notes.md @@ -6,6 +6,7 @@ See [log](https://github.com/Pathoschild/SMAPI/compare/1.8...1.9). For players: * Updated for Stardew Valley 1.2. Most mods are now rewritten for compatibility; some mods require an update from their authors. * Simplified log filename. +* Simplified error messages when a mod can't be loaded. * Simple nested mod folders are now recognised by SMAPI (e.g. `ModName-1.0\ModName\manifest.json`). * Fixed game's debug output being shown in the console for all users. * Fixed installer errors for some players when deleting files. diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 3b6d1702..8a6392ad 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -356,7 +356,7 @@ namespace StardewModdingAPI this.Monitor.Log($"Ignored folder \"{directory.Name}\" which doesn't have a manifest.json.", LogLevel.Warn); continue; } - string errorPrefix = $"Couldn't load mod for '{manifestPath.Replace(this.ModPath, "").Trim('/', '\\')}'"; + string skippedPrefix = $"Skipped {manifestPath.Replace(this.ModPath, "").Trim('/', '\\')}"; // read manifest Manifest manifest; @@ -366,7 +366,7 @@ namespace StardewModdingAPI string json = File.ReadAllText(manifestPath); if (string.IsNullOrEmpty(json)) { - this.Monitor.Log($"{errorPrefix}: manifest is empty.", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because the manifest is empty.", LogLevel.Error); continue; } @@ -374,20 +374,22 @@ namespace StardewModdingAPI manifest = jsonHelper.ReadJsonFile(Path.Combine(directory.FullName, "manifest.json"), null); if (manifest == null) { - this.Monitor.Log($"{errorPrefix}: the manifest file does not exist.", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because its manifest is invalid.", LogLevel.Error); continue; } if (string.IsNullOrEmpty(manifest.EntryDll)) { - this.Monitor.Log($"{errorPrefix}: manifest doesn't specify an entry DLL.", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because its manifest doesn't specify an entry DLL.", LogLevel.Error); continue; } } catch (Exception ex) { - this.Monitor.Log($"{errorPrefix}: manifest parsing failed.\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because manifest parsing failed.\n{ex.GetLogSummary()}", LogLevel.Error); continue; } + if(!string.IsNullOrWhiteSpace(manifest.Name)) + skippedPrefix = $"Skipped {manifest.Name}"; // validate compatibility IncompatibleMod compatibility = this.ModRegistry.GetIncompatibilityRecord(manifest); @@ -396,8 +398,8 @@ namespace StardewModdingAPI bool hasOfficialUrl = !string.IsNullOrWhiteSpace(compatibility.UpdateUrl); bool hasUnofficialUrl = !string.IsNullOrWhiteSpace(compatibility.UnofficialUpdateUrl); - string reasonPhrase = compatibility.ReasonPhrase ?? "it isn't compatible with the latest version of the game"; - string warning = $"Skipped {compatibility.Name} because {reasonPhrase}. Please check for a version newer than {compatibility.UpperVersion} here:"; + string reasonPhrase = compatibility.ReasonPhrase ?? "it's not compatible with the latest version of the game"; + string warning = $"{skippedPrefix} because {reasonPhrase}. Please check for a version newer than {compatibility.UpperVersion} here:"; if (hasOfficialUrl) warning += !hasUnofficialUrl ? $" {compatibility.UpdateUrl}" : $"{Environment.NewLine}- official mod: {compatibility.UpdateUrl}"; if (hasUnofficialUrl) @@ -415,13 +417,13 @@ namespace StardewModdingAPI ISemanticVersion minVersion = new SemanticVersion(manifest.MinimumApiVersion); if (minVersion.IsNewerThan(Constants.ApiVersion)) { - this.Monitor.Log($"{errorPrefix}: this mod requires SMAPI {minVersion} or later. Please update SMAPI to the latest version to use this mod.", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because it needs SMAPI {minVersion} or later. Please update SMAPI to the latest version to use this mod.", LogLevel.Error); continue; } } catch (FormatException ex) when (ex.Message.Contains("not a valid semantic version")) { - this.Monitor.Log($"{errorPrefix}: the mod specified an invalid minimum SMAPI version '{manifest.MinimumApiVersion}'. This should be a semantic version number like {Constants.ApiVersion}.", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because it has an invalid minimum SMAPI version '{manifest.MinimumApiVersion}'. This should be a semantic version number like {Constants.ApiVersion}.", LogLevel.Error); continue; } } @@ -436,13 +438,13 @@ namespace StardewModdingAPI Directory.CreateDirectory(psDir); if (!Directory.Exists(psDir)) { - this.Monitor.Log($"{errorPrefix}: couldn't create the per-save configuration directory ('psconfigs') requested by this mod. The failure reason is unknown.", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because it requires per-save configuration files ('psconfigs') which couldn't be created for some reason.", LogLevel.Error); continue; } } catch (Exception ex) { - this.Monitor.Log($"{errorPrefix}: couldn't create the per-save configuration directory ('psconfigs') requested by this mod.\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because it requires per-save configuration files ('psconfigs') which couldn't be created:\n{ex.GetLogSummary()}", LogLevel.Error); continue; } } @@ -451,7 +453,7 @@ namespace StardewModdingAPI string assemblyPath = Path.Combine(directory.FullName, manifest.EntryDll); if (!File.Exists(assemblyPath)) { - this.Monitor.Log($"{errorPrefix}: the entry DLL '{manifest.EntryDll}' does not exist.", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because its DLL '{manifest.EntryDll}' doesn't exist.", LogLevel.Error); continue; } @@ -463,7 +465,7 @@ namespace StardewModdingAPI } catch (Exception ex) { - this.Monitor.Log($"{errorPrefix}: an error occurred while preprocessing '{manifest.EntryDll}'.\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because its DLL '{manifest.EntryDll}' couldn't be loaded.\n{ex.GetLogSummary()}", LogLevel.Error); continue; } @@ -472,13 +474,13 @@ namespace StardewModdingAPI { if (modAssembly.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) == 0) { - this.Monitor.Log($"{errorPrefix}: the mod DLL does not contain an implementation of the 'Mod' class.", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because its DLL has no 'Mod' subclass.", LogLevel.Error); continue; } } catch (Exception ex) { - this.Monitor.Log($"{errorPrefix}: an error occurred while reading the mod DLL.\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because its DLL couldn't be loaded.\n{ex.GetLogSummary()}", LogLevel.Error); continue; } @@ -491,7 +493,7 @@ namespace StardewModdingAPI mod = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); if (mod == null) { - this.Monitor.Log($"{errorPrefix}: the mod's entry class could not be instantiated."); + this.Monitor.Log($"{skippedPrefix} because its entry class couldn't be instantiated."); continue; } @@ -505,11 +507,11 @@ namespace StardewModdingAPI // track mod this.ModRegistry.Add(mod); modsLoaded += 1; - this.Monitor.Log($"Loaded mod: {manifest.Name} by {manifest.Author}, v{manifest.Version} | {manifest.Description}", LogLevel.Info); + this.Monitor.Log($"Loaded {manifest.Name} by {manifest.Author}, v{manifest.Version} | {manifest.Description}", LogLevel.Info); } catch (Exception ex) { - this.Monitor.Log($"{errorPrefix}: an error occurred while loading the target DLL.\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because initialisation failed:\n{ex.GetLogSummary()}", LogLevel.Error); } } -- cgit From 12cb2d272d54a850d3912e5fb39089c70cae5a9e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 23 Feb 2017 23:51:52 -0500 Subject: minor cleanup --- src/StardewModdingAPI/Program.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 8a6392ad..0394362e 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -388,7 +389,7 @@ namespace StardewModdingAPI this.Monitor.Log($"{skippedPrefix} because manifest parsing failed.\n{ex.GetLogSummary()}", LogLevel.Error); continue; } - if(!string.IsNullOrWhiteSpace(manifest.Name)) + if (!string.IsNullOrWhiteSpace(manifest.Name)) skippedPrefix = $"Skipped {manifest.Name}"; // validate compatibility @@ -485,12 +486,11 @@ namespace StardewModdingAPI } // initialise mod - Mod mod; try { // get implementation TypeInfo modEntryType = modAssembly.DefinedTypes.First(x => x.BaseType == typeof(Mod)); - mod = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); + Mod mod = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); if (mod == null) { this.Monitor.Log($"{skippedPrefix} because its entry class couldn't be instantiated."); @@ -544,8 +544,8 @@ namespace StardewModdingAPI Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Game1.version} with {modsLoaded} mods"; } - // ReSharper disable once FunctionNeverReturns /// Run a loop handling console input. + [SuppressMessage("ReSharper", "FunctionNeverReturns", Justification = "The thread is aborted when the game exits.")] private void ConsoleInputLoop() { while (true) -- cgit From 2ed3b25b6b886092e16980288491b47d7b54a309 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 23 Feb 2017 23:54:50 -0500 Subject: further group deprecation warnings during mod loading --- src/StardewModdingAPI/Program.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 0394362e..df64de3b 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -515,11 +515,6 @@ namespace StardewModdingAPI } } - // log deprecation warnings - foreach (Action warning in deprecationWarnings) - warning(); - deprecationWarnings = null; - // initialise mods foreach (Mod mod in this.ModRegistry.GetMods()) { @@ -531,7 +526,7 @@ namespace StardewModdingAPI // raise deprecation warning for old Entry() methods if (this.DeprecationManager.IsVirtualMethodImplemented(mod.GetType(), typeof(Mod), nameof(Mod.Entry), new[] { typeof(object[]) })) - this.DeprecationManager.Warn(mod.ModManifest.Name, $"{nameof(Mod)}.{nameof(Mod.Entry)}(object[]) instead of {nameof(Mod)}.{nameof(Mod.Entry)}({nameof(IModHelper)})", "1.0", DeprecationLevel.Info); + deprecationWarnings.Add(() => this.DeprecationManager.Warn(mod.ModManifest.Name, $"{nameof(Mod)}.{nameof(Mod.Entry)}(object[]) instead of {nameof(Mod)}.{nameof(Mod.Entry)}({nameof(IModHelper)})", "1.0", DeprecationLevel.Info)); } catch (Exception ex) { @@ -541,6 +536,8 @@ namespace StardewModdingAPI // print result this.Monitor.Log($"Loaded {modsLoaded} mods."); + foreach (Action warning in deprecationWarnings) + warning(); Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Game1.version} with {modsLoaded} mods"; } -- cgit From be0aa19f30ac1168214f0dcf39a37587e8f4abb3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 24 Feb 2017 00:15:45 -0500 Subject: fix game version always being detected as 1.2.9 because Game1.version is a const now --- src/StardewModdingAPI/Constants.cs | 12 ++++++++++++ src/StardewModdingAPI/Program.cs | 12 ++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 8b085eac..69dd1fa8 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -71,6 +71,18 @@ namespace StardewModdingAPI /// Whether a player save has been loaded. internal static bool PlayerNull => !Game1.hasLoadedGame || Game1.player == null || string.IsNullOrEmpty(Game1.player.name); + /// The actual game version. + /// This is necessary because is a constant, so SMAPI's references to it are inlined at compile-time. + internal static string GameVersion + { + get + { + FieldInfo field = typeof(Game1).GetField(nameof(Game1.version), BindingFlags.Public | BindingFlags.Static); + if (field == null) + throw new InvalidOperationException($"The {nameof(Game1)}.{nameof(Game1.version)} field could not be found."); + return (string)field.GetValue(null); + } + } /********* ** Protected methods diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index df64de3b..3431e02f 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -102,8 +102,8 @@ namespace StardewModdingAPI { // initialise logging Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); // for consistent log formatting - this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info); - Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Game1.version}"; + this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {Environment.OSVersion}", LogLevel.Info); + Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion}"; // inject compatibility shims #pragma warning disable 618 @@ -142,9 +142,9 @@ namespace StardewModdingAPI try { // verify version - if (string.Compare(Game1.version, Constants.MinimumGameVersion, StringComparison.InvariantCultureIgnoreCase) < 0) + if (string.Compare(Constants.GameVersion, Constants.MinimumGameVersion, StringComparison.InvariantCultureIgnoreCase) < 0) { - this.Monitor.Log($"Oops! You're running Stardew Valley {Game1.version}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI. If you're on the Steam beta channel, note that the beta channel may not receive the latest updates.", LogLevel.Error); + this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GameVersion}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI. If you're on the Steam beta channel, note that the beta channel may not receive the latest updates.", LogLevel.Error); return; } @@ -245,7 +245,7 @@ namespace StardewModdingAPI 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 {Game1.version}"; + this.GameInstance.Window.Title = $"Stardew Valley {Constants.GameVersion}"; gameProgramType.GetField("gamePtr").SetValue(gameProgramType, this.GameInstance); // configure @@ -538,7 +538,7 @@ namespace StardewModdingAPI this.Monitor.Log($"Loaded {modsLoaded} mods."); foreach (Action warning in deprecationWarnings) warning(); - Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Game1.version} with {modsLoaded} mods"; + Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion} with {modsLoaded} mods"; } /// Run a loop handling console input. -- cgit From 6a07a1cbaf62f7bf7ccdf89c341e6b761bd2d6af Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 24 Feb 2017 14:46:24 -0500 Subject: clean up constants, avoid regenerating values unnecessarily --- src/StardewModdingAPI/Constants.cs | 61 ++++++++++++++++++-------------------- src/StardewModdingAPI/Mod.cs | 2 +- 2 files changed, 30 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 69dd1fa8..3326e43f 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -16,11 +16,11 @@ namespace StardewModdingAPI /********* ** Properties *********/ - /// The directory name containing the current save's data (if a save is loaded). - private static string RawSaveFolderName => Constants.PlayerNull ? string.Empty : Constants.GetSaveFolderName(); - /// The directory path containing the current save's data (if a save is loaded). - private static string RawSavePath => Constants.PlayerNull ? string.Empty : Path.Combine(Constants.SavesPath, Constants.RawSaveFolderName); + private static string RawSavePath => Constants.IsSaveLoaded ? Path.Combine(Constants.SavesPath, Constants.GetSaveFolderName()) : null; + + /// Whether the directory containing the current save's data exists on disk. + private static bool SavePathReady => Constants.IsSaveLoaded && Directory.Exists(Constants.RawSavePath); /********* @@ -30,28 +30,28 @@ namespace StardewModdingAPI ** Public ****/ /// SMAPI's current semantic version. - public static ISemanticVersion ApiVersion => new SemanticVersion(1, 8, 0, null); + public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 8, 0); /// The minimum supported version of Stardew Valley. - public const string MinimumGameVersion = "1.2.9"; + public static string MinimumGameVersion { get; } = "1.2.9"; + + /// The path to the game folder. + public static string ExecutionPath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); /// The directory path containing Stardew Valley's app data. - public static string DataPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley"); + public static string DataPath { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley"); + + /// The directory path in which error logs should be stored. + public static string LogDir { get; } = Path.Combine(Constants.DataPath, "ErrorLogs"); /// The directory path where all saves are stored. - public static string SavesPath => Path.Combine(Constants.DataPath, "Saves"); + public static string SavesPath { get; } = Path.Combine(Constants.DataPath, "Saves"); /// The directory name containing the current save's data (if a save is loaded and the directory exists). - public static string SaveFolderName => Constants.CurrentSavePathExists ? Constants.RawSaveFolderName : ""; + public static string SaveFolderName => Constants.SavePathReady ? Constants.GetSaveFolderName() : ""; /// The directory path containing the current save's data (if a save is loaded and the directory exists). - public static string CurrentSavePath => Constants.CurrentSavePathExists ? Constants.RawSavePath : ""; - - /// The path to the current assembly being executing. - public static string ExecutionPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - - /// The directory path in which error logs should be stored. - public static string LogDir => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs"); + public static string CurrentSavePath => Constants.SavePathReady ? Path.Combine(Constants.SavesPath, Constants.GetSaveFolderName()) : ""; /**** ** Internal @@ -65,24 +65,11 @@ namespace StardewModdingAPI /// The file path to the log where the latest output should be saved. internal static string LogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt"); - /// Whether the directory containing the current save's data exists on disk. - internal static bool CurrentSavePathExists => Directory.Exists(Constants.RawSavePath); - /// Whether a player save has been loaded. - internal static bool PlayerNull => !Game1.hasLoadedGame || Game1.player == null || string.IsNullOrEmpty(Game1.player.name); + internal static bool IsSaveLoaded => Game1.hasLoadedGame && !string.IsNullOrEmpty(Game1.player.name); - /// The actual game version. - /// This is necessary because is a constant, so SMAPI's references to it are inlined at compile-time. - internal static string GameVersion - { - get - { - FieldInfo field = typeof(Game1).GetField(nameof(Game1.version), BindingFlags.Public | BindingFlags.Static); - if (field == null) - throw new InvalidOperationException($"The {nameof(Game1)}.{nameof(Game1.version)} field could not be found."); - return (string)field.GetValue(null); - } - } + /// The current game version. + internal static string GameVersion { get; } = Constants.GetGameVersion(); /********* ** Protected methods @@ -154,5 +141,15 @@ namespace StardewModdingAPI string prefix = new string(Game1.player.name.Where(char.IsLetterOrDigit).ToArray()); return $"{prefix}_{Game1.uniqueIDForThisGame}"; } + + /// Get the actual game version. + /// This uses reflection because is a constant, so SMAPI's references to it are inlined at compile-time. + private static string GetGameVersion() + { + FieldInfo field = typeof(Game1).GetField(nameof(Game1.version), BindingFlags.Public | BindingFlags.Static); + if (field == null) + throw new InvalidOperationException($"The {nameof(Game1)}.{nameof(Game1.version)} field could not be found."); + return (string)field.GetValue(null); + } } } diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs index 44cfd4b3..caa20774 100644 --- a/src/StardewModdingAPI/Mod.cs +++ b/src/StardewModdingAPI/Mod.cs @@ -65,7 +65,7 @@ namespace StardewModdingAPI { Mod.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigPath)}", "1.0", DeprecationLevel.Info); Mod.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigFolder)}", "1.0"); // avoid redundant warnings - return Constants.CurrentSavePathExists ? Path.Combine(this.PerSaveConfigFolder, Constants.SaveFolderName + ".json") : ""; + return Constants.IsSaveLoaded ? Path.Combine(this.PerSaveConfigFolder, $"{Constants.SaveFolderName}.json") : ""; } } -- cgit From fd2d7d714d025f7f787e14e47645078f988ae8ce Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 24 Feb 2017 15:04:28 -0500 Subject: fix game version checks not using semantic versioning This caused an issue where SMAPI didn't consider SDV 1.2.10 to pass the minimum game version of 1.2.9. This requires some workarounds for SDV 1.11's non-semantic version. --- src/StardewModdingAPI/Constants.cs | 37 ++++++++++++++++++++++++++++++------- src/StardewModdingAPI/Program.cs | 4 ++-- 2 files changed, 32 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 3326e43f..99bf834c 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -33,7 +33,7 @@ namespace StardewModdingAPI public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 8, 0); /// The minimum supported version of Stardew Valley. - public static string MinimumGameVersion { get; } = "1.2.9"; + public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.9"); /// The path to the game folder. public static string ExecutionPath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); @@ -68,8 +68,12 @@ namespace StardewModdingAPI /// Whether a player save has been loaded. internal static bool IsSaveLoaded => Game1.hasLoadedGame && !string.IsNullOrEmpty(Game1.player.name); - /// The current game version. - internal static string GameVersion { get; } = Constants.GetGameVersion(); + /// The game's current semantic version. + internal static ISemanticVersion GameVersion { get; } = Constants.GetGameVersion(); + + /// The game's current version as it should be displayed to players. + internal static ISemanticVersion GameDisplayVersion { get; } = Constants.GetGameDisplayVersion(Constants.GameVersion); + /********* ** Protected methods @@ -142,14 +146,33 @@ namespace StardewModdingAPI return $"{prefix}_{Game1.uniqueIDForThisGame}"; } - /// Get the actual game version. - /// This uses reflection because is a constant, so SMAPI's references to it are inlined at compile-time. - private static string GetGameVersion() + /// Get the game's current semantic version. + private static ISemanticVersion GetGameVersion() { + // get raw version + // we need reflection because it's a constant, so SMAPI's references to it are inlined at compile-time FieldInfo field = typeof(Game1).GetField(nameof(Game1.version), BindingFlags.Public | BindingFlags.Static); if (field == null) throw new InvalidOperationException($"The {nameof(Game1)}.{nameof(Game1.version)} field could not be found."); - return (string)field.GetValue(null); + string version = (string)field.GetValue(null); + + // get semantic version + if (version == "1.11") + version = "1.1.1"; // The 1.1 patch was released as 1.11, which means it's out of order for semantic version checks + return new SemanticVersion(version); + } + + /// Get game current version as it should be displayed to players. + /// The semantic game version. + private static ISemanticVersion GetGameDisplayVersion(ISemanticVersion version) + { + switch (version.ToString()) + { + case "1.1.1": + return new SemanticVersion(1, 11, 0); // The 1.1 patch was released as 1.11 + default: + return version; + } } } } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 3431e02f..8264d0ee 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -142,9 +142,9 @@ namespace StardewModdingAPI try { // verify version - if (string.Compare(Constants.GameVersion, Constants.MinimumGameVersion, StringComparison.InvariantCultureIgnoreCase) < 0) + if (Constants.GameVersion.IsOlderThan(Constants.MinimumGameVersion)) { - this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GameVersion}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI. If you're on the Steam beta channel, note that the beta channel may not receive the latest updates.", LogLevel.Error); + this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GameDisplayVersion}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI. If you're on the Steam beta channel, note that the beta channel may not receive the latest updates.", LogLevel.Error); return; } -- cgit From ba55ed34ca855de13e738e6bb97dc404419f5350 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 24 Feb 2017 15:06:38 -0500 Subject: fix 'please update your game' error not pausing before exit --- src/StardewModdingAPI/Program.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 8264d0ee..05d3e5e4 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -145,6 +145,7 @@ namespace StardewModdingAPI if (Constants.GameVersion.IsOlderThan(Constants.MinimumGameVersion)) { this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GameDisplayVersion}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI. If you're on the Steam beta channel, note that the beta channel may not receive the latest updates.", LogLevel.Error); + this.PressAnyKeyToExit(); return; } -- cgit From 60f31b0fc66f2215ccb23552a2c9124f09fd8769 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 24 Feb 2017 15:10:02 -0500 Subject: clean up program properties --- src/StardewModdingAPI/Constants.cs | 11 +++++++++++ src/StardewModdingAPI/Program.cs | 35 ++++++++++------------------------- 2 files changed, 21 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 99bf834c..be44c664 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -65,6 +65,9 @@ namespace StardewModdingAPI /// The file path to the log where the latest output should be saved. internal static string LogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt"); + /// The full path to the folder containing mods. + internal static string ModPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods"); + /// Whether a player save has been loaded. internal static bool IsSaveLoaded => Game1.hasLoadedGame && !string.IsNullOrEmpty(Game1.player.name); @@ -74,6 +77,14 @@ namespace StardewModdingAPI /// The game's current version as it should be displayed to players. internal static ISemanticVersion GameDisplayVersion { get; } = Constants.GetGameDisplayVersion(Constants.GameVersion); + /// The target game platform. + internal static Platform TargetPlatform { get; } = +#if SMAPI_FOR_WINDOWS + Platform.Windows; +#else + Platform.Mono; +#endif + /********* ** Protected methods diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 05d3e5e4..b7947df1 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -28,17 +28,6 @@ namespace StardewModdingAPI /********* ** Properties *********/ - /// The target game platform. - private readonly Platform TargetPlatform = -#if SMAPI_FOR_WINDOWS - Platform.Windows; -#else - Platform.Mono; -#endif - - /// The full path to the folder containing mods. - private readonly string ModPath = Path.Combine(Constants.ExecutionPath, "Mods"); - /// The log file to which to write messages. private readonly LogFileManager LogFile = new LogFileManager(Constants.LogPath); @@ -57,21 +46,17 @@ namespace StardewModdingAPI /// Whether the game is currently running. private bool IsGameRunning; - - /********* - ** Accessors - *********/ /// The underlying game instance. - internal SGame GameInstance; + private SGame GameInstance; /// Tracks the installed mods. - internal readonly ModRegistry ModRegistry; + private readonly ModRegistry ModRegistry; /// Manages deprecation warnings. - internal readonly DeprecationManager DeprecationManager; + private readonly DeprecationManager DeprecationManager; /// Manages console commands. - internal readonly CommandManager CommandManager = new CommandManager(); + private readonly CommandManager CommandManager = new CommandManager(); /********* @@ -136,7 +121,7 @@ namespace StardewModdingAPI 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); // print file paths - this.Monitor.Log($"Mods go here: {this.ModPath}"); + this.Monitor.Log($"Mods go here: {Constants.ModPath}"); // hook into & launch the game try @@ -151,11 +136,11 @@ namespace StardewModdingAPI // initialise folders this.Monitor.Log("Loading SMAPI..."); - this.VerifyPath(this.ModPath); + this.VerifyPath(Constants.ModPath); this.VerifyPath(Constants.LogDir); // get executable path - string executablePath = Path.Combine(Constants.ExecutionPath, this.TargetPlatform == Platform.Windows ? "Stardew Valley.exe" : "StardewValley.exe"); + string executablePath = Path.Combine(Constants.ExecutionPath, Constants.TargetPlatform == Platform.Windows ? "Stardew Valley.exe" : "StardewValley.exe"); if (!File.Exists(executablePath)) { this.Monitor.Log($"Couldn't find executable: {executablePath}", LogLevel.Error); @@ -331,13 +316,13 @@ namespace StardewModdingAPI JsonHelper jsonHelper = new JsonHelper(); // get assembly loader - AssemblyLoader modAssemblyLoader = new AssemblyLoader(this.TargetPlatform, this.Monitor); + AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.TargetPlatform, this.Monitor); AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name); // load mod assemblies int modsLoaded = 0; List deprecationWarnings = new List(); // queue up deprecation warnings to show after mod list - foreach (string directoryPath in Directory.GetDirectories(this.ModPath)) + foreach (string directoryPath in Directory.GetDirectories(Constants.ModPath)) { // passthrough empty directories DirectoryInfo directory = new DirectoryInfo(directoryPath); @@ -358,7 +343,7 @@ namespace StardewModdingAPI this.Monitor.Log($"Ignored folder \"{directory.Name}\" which doesn't have a manifest.json.", LogLevel.Warn); continue; } - string skippedPrefix = $"Skipped {manifestPath.Replace(this.ModPath, "").Trim('/', '\\')}"; + string skippedPrefix = $"Skipped {manifestPath.Replace(Constants.ModPath, "").Trim('/', '\\')}"; // read manifest Manifest manifest; -- cgit From 615c89bc0ba19568a147120cfd49c97142f63969 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 24 Feb 2017 18:52:53 -0500 Subject: override content manager (#173) --- src/StardewModdingAPI/Framework/SContentManager.cs | 63 ++++++++++++++++++++++ src/StardewModdingAPI/Framework/SGame.cs | 7 +++ src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 3 files changed, 71 insertions(+) create mode 100644 src/StardewModdingAPI/Framework/SContentManager.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs new file mode 100644 index 00000000..27001a06 --- /dev/null +++ b/src/StardewModdingAPI/Framework/SContentManager.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +using Microsoft.Xna.Framework; +using StardewModdingAPI.Framework.Reflection; +using StardewValley; + +namespace StardewModdingAPI.Framework +{ + /// SMAPI's implementation of the game's content manager which lets it raise content events. + internal class SContentManager : LocalizedContentManager + { + /********* + ** Accessors + *********/ + /// Encapsulates monitoring and logging. + private readonly IMonitor Monitor; + + /// The underlying content manager's asset cache. + private readonly IDictionary Cache; + + /// Normalises an asset key to match the cache key. + private readonly IPrivateMethod NormaliseAssetKey; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The service provider to use to locate services. + /// The root directory to search for content. + /// Encapsulates monitoring and logging. + public SContentManager(IServiceProvider serviceProvider, string rootDirectory, IMonitor monitor) + : this(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, null, monitor) { } + + /// Construct an instance. + /// The service provider to use to locate services. + /// The root directory to search for content. + /// The current culture for which to localise content. + /// The current language code for which to localise content. + /// Encapsulates monitoring and logging. + public SContentManager(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, string languageCodeOverride, IMonitor monitor) + : base(serviceProvider, rootDirectory, currentCulture, languageCodeOverride) + { + this.Monitor = monitor; + + IReflectionHelper reflection = new ReflectionHelper(); + this.Cache = reflection + .GetPrivateField>(this, "loadedAssets") + .GetValue(); + this.NormaliseAssetKey = reflection.GetPrivateMethod(typeof(TitleContainer), "GetCleanPath"); + } + + /// Load an asset that has been processed by the Content Pipeline. + /// The type of asset to load. + /// The asset path relative to the loader root directory, not including the .xnb extension. + public override T Load(string assetName) + { + return base.Load(assetName); + } + } +} diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 56631260..aab44b59 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -288,7 +288,14 @@ namespace StardewModdingAPI.Framework /// The method called before XNA or MonoGame loads or reloads graphics resources. protected override void LoadContent() { + // override content manager + LocalizedContentManager contentManager = Game1.content; + Game1.content = new SContentManager(this.Content.ServiceProvider, this.Content.RootDirectory, this.Monitor); + + // defer to game logic base.LoadContent(); + + // raise load content event GameEvents.InvokeLoadContent(this.Monitor); } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 72cc1ed2..ee37379d 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -153,6 +153,7 @@ + -- cgit From 89cb791cae0db9637be051db1a1543a8b1ee4745 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 24 Feb 2017 19:44:19 -0500 Subject: fix content manager compatibility with MonoGame (#173) --- src/StardewModdingAPI/Framework/SContentManager.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs index 27001a06..24237c63 100644 --- a/src/StardewModdingAPI/Framework/SContentManager.cs +++ b/src/StardewModdingAPI/Framework/SContentManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Threading; using Microsoft.Xna.Framework; +using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.Framework.Reflection; using StardewValley; @@ -49,7 +50,9 @@ namespace StardewModdingAPI.Framework this.Cache = reflection .GetPrivateField>(this, "loadedAssets") .GetValue(); - this.NormaliseAssetKey = reflection.GetPrivateMethod(typeof(TitleContainer), "GetCleanPath"); + this.NormaliseAssetKey = Constants.TargetPlatform == Platform.Windows + ? reflection.GetPrivateMethod(typeof(TitleContainer), "GetCleanPath") + : reflection.GetPrivateMethod(this, nameof(this.NormaliseKeyForMono)); } /// Load an asset that has been processed by the Content Pipeline. @@ -57,7 +60,19 @@ namespace StardewModdingAPI.Framework /// The asset path relative to the loader root directory, not including the .xnb extension. public override T Load(string assetName) { + assetName = this.NormaliseAssetKey.Invoke(assetName); return base.Load(assetName); } + + + /********* + ** Private methods + *********/ + /// Normalise an asset key for Mono. + /// The asset key. + private string NormaliseKeyForMono(string key) + { + return key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load logic + } } } -- cgit From 2151625898fcad388a29c17f92de4bf26c2c091d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 24 Feb 2017 20:55:19 -0500 Subject: add release note, fix docblock --- release-notes.md | 1 + src/StardewModdingAPI/Framework/SContentManager.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index d6fb4b5b..d157c06a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -9,6 +9,7 @@ For players: * Simplified error messages when a mod can't be loaded. * Simple nested mod folders are now recognised by SMAPI (e.g. `ModName-1.0\ModName\manifest.json`). * Fixed game's debug output being shown in the console for all users. +* Fixed the game-outdated error not pausing before exit. * Fixed installer errors for some players when deleting files. * Fixed installer not ignoring potential game folders that don't contain a Stardew Valley exe. * Fixed installer not recognising Linux/Mac paths starting with `~/` or containing an escaped space. diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs index 24237c63..2a876d72 100644 --- a/src/StardewModdingAPI/Framework/SContentManager.cs +++ b/src/StardewModdingAPI/Framework/SContentManager.cs @@ -55,7 +55,7 @@ namespace StardewModdingAPI.Framework : reflection.GetPrivateMethod(this, nameof(this.NormaliseKeyForMono)); } - /// Load an asset that has been processed by the Content Pipeline. + /// Load an asset that has been processed by the content pipeline. /// The type of asset to load. /// The asset path relative to the loader root directory, not including the .xnb extension. public override T Load(string assetName) -- cgit From 9c53a254d50718fee3b8043bb0b8bb840557e82f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 25 Feb 2017 15:22:45 -0500 Subject: add prototype content event + helper to manipulate XNB data (#173) --- src/StardewModdingAPI/Events/ContentEvents.cs | 69 ++++++++++ .../Framework/ContentEventHelper.cs | 142 +++++++++++++++++++++ src/StardewModdingAPI/Framework/SContentManager.cs | 36 +++++- src/StardewModdingAPI/IContentEventHelper.cs | 53 ++++++++ src/StardewModdingAPI/Program.cs | 1 + src/StardewModdingAPI/StardewModdingAPI.csproj | 3 + 6 files changed, 297 insertions(+), 7 deletions(-) create mode 100644 src/StardewModdingAPI/Events/ContentEvents.cs create mode 100644 src/StardewModdingAPI/Framework/ContentEventHelper.cs create mode 100644 src/StardewModdingAPI/IContentEventHelper.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Events/ContentEvents.cs b/src/StardewModdingAPI/Events/ContentEvents.cs new file mode 100644 index 00000000..cc07f242 --- /dev/null +++ b/src/StardewModdingAPI/Events/ContentEvents.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using StardewModdingAPI.Framework; + +namespace StardewModdingAPI.Events +{ + /// Events raised when the game loads content. + [Obsolete("This is an undocumented experimental API and may change without warning.")] + public static class ContentEvents + { + /********* + ** Properties + *********/ + /// Tracks the installed mods. + private static ModRegistry ModRegistry; + + /// Encapsulates monitoring and logging. + private static IMonitor Monitor; + + /// The mods using the experimental API for which a warning has been raised. + private static readonly HashSet WarnedMods = new HashSet(); + + + /********* + ** Events + *********/ + /// Raised when an XNB file is being read into the cache. Mods can change the data here before it's cached. + public static event EventHandler AssetLoading; + + + /********* + ** Internal methods + *********/ + /// Injects types required for backwards compatibility. + /// Tracks the installed mods. + /// Encapsulates monitoring and logging. + internal static void Shim(ModRegistry modRegistry, IMonitor monitor) + { + ContentEvents.ModRegistry = modRegistry; + ContentEvents.Monitor = monitor; + } + + /// Raise an event. + /// Encapsulates monitoring and logging. + /// Encapsulates access and changes to content being read from a data file. + internal static void InvokeAssetLoading(IMonitor monitor, IContentEventHelper contentHelper) + { + // raise warning about experimental API + foreach (Delegate handler in ContentEvents.AssetLoading.GetInvocationList()) + { + string modName = ContentEvents.ModRegistry.GetModFrom(handler) ?? "An unknown mod"; + if (!ContentEvents.WarnedMods.Contains(modName)) + { + ContentEvents.WarnedMods.Add(modName); + ContentEvents.Monitor.Log($"{modName} used the undocumented and experimental content API, which may change or be removed without warning.", LogLevel.Warn); + } + } + + // raise event + monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AssetLoading)}", ContentEvents.AssetLoading?.GetInvocationList(), null, contentHelper); + } + + /// Get whether there are any listeners. + internal static bool HasAssetLoadingListeners() + { + return ContentEvents.AssetLoading != null; + } + } +} diff --git a/src/StardewModdingAPI/Framework/ContentEventHelper.cs b/src/StardewModdingAPI/Framework/ContentEventHelper.cs new file mode 100644 index 00000000..d4a9bbb8 --- /dev/null +++ b/src/StardewModdingAPI/Framework/ContentEventHelper.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Microsoft.Xna.Framework.Graphics; + +namespace StardewModdingAPI.Framework +{ + /// Encapsulates access and changes to content being read from a data file. + internal class ContentEventHelper : EventArgs, IContentEventHelper + { + /********* + ** Properties + *********/ + /// Normalises an asset key to match the cache key. + private readonly Func GetNormalisedPath; + + + /********* + ** Accessors + *********/ + /// The normalised asset path being read. The format may change between platforms; see to compare with a known path. + public string Path { get; } + + /// The content data being read. + public object Data { get; private set; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The file path being read. + /// The content data being read. + /// Normalises an asset key to match the cache key. + public ContentEventHelper(string path, object data, Func getNormalisedPath) + { + this.Path = path; + this.Data = data; + this.GetNormalisedPath = getNormalisedPath; + } + + /// Get whether the asset path being loaded matches a given path after normalisation. + /// The expected asset path, relative to the game folder and without the .xnb extension (like 'Data\ObjectInformation'). + /// Whether to match a localised version of the asset file (like 'Data\ObjectInformation.ja-JP'). + public bool IsPath(string path, bool matchLocalisedVersion = true) + { + path = this.GetNormalisedPath(path); + + // equivalent + if (this.Path.Equals(path, StringComparison.InvariantCultureIgnoreCase)) + return true; + + // localised version + if (matchLocalisedVersion) + { + return + this.Path.StartsWith($"{path}.", StringComparison.InvariantCultureIgnoreCase) // starts with given path + && Regex.IsMatch(this.Path.Substring(path.Length + 1), "^[a-z]+-[A-Z]+$"); // ends with locale (e.g. pt-BR) + } + + // no match + return false; + } + + /// Get the data as a given type. + /// The expected data type. + /// The data can't be converted to . + public TData GetData() + { + if (!(this.Data is TData)) + throw new InvalidCastException($"The content data of type {this.Data.GetType().FullName} can't be converted to the requested {typeof(TData).FullName}."); + return (TData)this.Data; + } + + /// Add or replace an entry in the dictionary data. + /// The entry key type. + /// The entry value type. + /// The entry key. + /// The entry value. + /// The content being read isn't a dictionary. + public void SetDictionaryEntry(TKey key, TValue value) + { + IDictionary data = this.GetData>(); + data[key] = value; + } + + /// Add or replace an entry in the dictionary data. + /// The entry key type. + /// The entry value type. + /// The entry key. + /// A callback which accepts the current value and returns the new value. + /// The content being read isn't a dictionary. + public void SetDictionaryEntry(TKey key, Func value) + { + IDictionary data = this.GetData>(); + data[key] = value(data[key]); + } + + /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. + /// The new content value. + /// The is null. + /// The 's type is not compatible with the loaded asset's type. + public void ReplaceWith(object value) + { + if (value == null) + throw new ArgumentNullException(nameof(value), "Can't set a loaded asset to a null value."); + if (!this.Data.GetType().IsInstanceOfType(value)) + throw new InvalidCastException($"Can't replace loaded asset of type {this.GetFriendlyTypeName(this.Data.GetType())} with value of type {this.GetFriendlyTypeName(value.GetType())}. The new type must be compatible to prevent game errors."); + + this.Data = value; + } + + + /********* + ** Private methods + *********/ + /// Get a human-readable type name. + /// The type to name. + private string GetFriendlyTypeName(Type type) + { + // dictionary + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + Type[] genericArgs = type.GetGenericArguments(); + return $"Dictionary<{this.GetFriendlyTypeName(genericArgs[0])}, {this.GetFriendlyTypeName(genericArgs[1])}>"; + } + + // texture + if (type == typeof(Texture2D)) + return type.Name; + + // native type + if (type == typeof(int)) + return "int"; + if (type == typeof(string)) + return "string"; + + // default + return type.FullName; + } + } +} diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs index 2a876d72..344d3ed9 100644 --- a/src/StardewModdingAPI/Framework/SContentManager.cs +++ b/src/StardewModdingAPI/Framework/SContentManager.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Threading; using Microsoft.Xna.Framework; using StardewModdingAPI.AssemblyRewriters; +using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Reflection; using StardewValley; @@ -22,7 +23,7 @@ namespace StardewModdingAPI.Framework private readonly IDictionary Cache; /// Normalises an asset key to match the cache key. - private readonly IPrivateMethod NormaliseAssetKey; + private readonly Func NormaliseAssetKey; /********* @@ -44,15 +45,23 @@ namespace StardewModdingAPI.Framework public SContentManager(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, string languageCodeOverride, IMonitor monitor) : base(serviceProvider, rootDirectory, currentCulture, languageCodeOverride) { + // initialise this.Monitor = monitor; - IReflectionHelper reflection = new ReflectionHelper(); + + // get underlying asset cache this.Cache = reflection .GetPrivateField>(this, "loadedAssets") .GetValue(); - this.NormaliseAssetKey = Constants.TargetPlatform == Platform.Windows - ? reflection.GetPrivateMethod(typeof(TitleContainer), "GetCleanPath") - : reflection.GetPrivateMethod(this, nameof(this.NormaliseKeyForMono)); + + // get asset key normalisation logic + if (Constants.TargetPlatform == Platform.Windows) + { + IPrivateMethod method = reflection.GetPrivateMethod(typeof(TitleContainer), "GetCleanPath"); + this.NormaliseAssetKey = path => method.Invoke(path); + } + else + this.NormaliseAssetKey = this.NormaliseKeyForMono; } /// Load an asset that has been processed by the content pipeline. @@ -60,8 +69,21 @@ namespace StardewModdingAPI.Framework /// The asset path relative to the loader root directory, not including the .xnb extension. public override T Load(string assetName) { - assetName = this.NormaliseAssetKey.Invoke(assetName); - return base.Load(assetName); + // pass through if no event handlers + if (!ContentEvents.HasAssetLoadingListeners()) + return base.Load(assetName); + + // skip if already loaded + string key = this.NormaliseAssetKey(assetName); + if (this.Cache.ContainsKey(key)) + return base.Load(assetName); + + // intercept load + T data = base.Load(assetName); + IContentEventHelper helper = new ContentEventHelper(assetName, data, this.NormaliseAssetKey); + ContentEvents.InvokeAssetLoading(this.Monitor, helper); + this.Cache[key] = helper.Data; + return (T)helper.Data; } diff --git a/src/StardewModdingAPI/IContentEventHelper.cs b/src/StardewModdingAPI/IContentEventHelper.cs new file mode 100644 index 00000000..98d074d9 --- /dev/null +++ b/src/StardewModdingAPI/IContentEventHelper.cs @@ -0,0 +1,53 @@ +using System; + +namespace StardewModdingAPI +{ + /// Encapsulates access and changes to content being read from a data file. + public interface IContentEventHelper + { + /********* + ** Accessors + *********/ + /// The normalised asset path being read. The format may change between platforms; see to compare with a known path. + string Path { get; } + + /// The content data being read. + object Data { get; } + + + /********* + ** Public methods + *********/ + /// Get whether the asset path being loaded matches a given path after normalisation. + /// The expected asset path, relative to the game folder and without the .xnb extension (like 'Data\ObjectInformation'). + /// Whether to match a localised version of the asset file (like 'Data\ObjectInformation.ja-JP'). + bool IsPath(string path, bool matchLocalisedVersion = true); + + /// Get the data as a given type. + /// The expected data type. + /// The data can't be converted to . + TData GetData(); + + /// Add or replace an entry in the dictionary data. + /// The entry key type. + /// The entry value type. + /// The entry key. + /// The entry value. + /// The content being read isn't a dictionary. + void SetDictionaryEntry(TKey key, TValue value); + + /// Add or replace an entry in the dictionary data. + /// The entry key type. + /// The entry value type. + /// The entry key. + /// A callback which accepts the current value and returns the new value. + /// The content being read isn't a dictionary. + void SetDictionaryEntry(TKey key, Func value); + + /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. + /// The new content value. + /// The is null. + /// The 's type is not compatible with the loaded asset's type. + void ReplaceWith(object value); + } +} diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index b7947df1..18a83e32 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -97,6 +97,7 @@ namespace StardewModdingAPI 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 diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index ee37379d..9b86adac 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -117,6 +117,7 @@ + @@ -147,6 +148,7 @@ + @@ -158,6 +160,7 @@ + -- cgit From ac5bcce02df70ccc8d62d4803fac9320bd65be96 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 25 Feb 2017 17:52:56 -0500 Subject: fix current path not being normalised in path checks (#173) --- src/StardewModdingAPI/Framework/SContentManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs index 344d3ed9..2781bec4 100644 --- a/src/StardewModdingAPI/Framework/SContentManager.cs +++ b/src/StardewModdingAPI/Framework/SContentManager.cs @@ -80,7 +80,7 @@ namespace StardewModdingAPI.Framework // intercept load T data = base.Load(assetName); - IContentEventHelper helper = new ContentEventHelper(assetName, data, this.NormaliseAssetKey); + IContentEventHelper helper = new ContentEventHelper(key, data, this.NormaliseAssetKey); ContentEvents.InvokeAssetLoading(this.Monitor, helper); this.Cache[key] = helper.Data; return (T)helper.Data; -- cgit From 614cdc6fddc61c2b8f27d6d15c998a4cbeaa90b7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 25 Feb 2017 18:26:43 -0500 Subject: improve asset key normalisation (#173) --- src/StardewModdingAPI/Framework/SContentManager.cs | 41 ++++++++++++++-------- 1 file changed, 26 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs index 2781bec4..45f42bf6 100644 --- a/src/StardewModdingAPI/Framework/SContentManager.cs +++ b/src/StardewModdingAPI/Framework/SContentManager.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; +using System.Linq; using System.Threading; using Microsoft.Xna.Framework; using StardewModdingAPI.AssemblyRewriters; @@ -16,14 +18,20 @@ namespace StardewModdingAPI.Framework /********* ** Accessors *********/ + /// The possible directory separator characters in an asset key. + private static readonly char[] PossiblePathSeparators = new[] { '/', '\\', Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }.Distinct().ToArray(); + + /// The preferred directory separator chaeacter in an asset key. + private static readonly string PreferredPathSeparator = Path.DirectorySeparatorChar.ToString(); + /// Encapsulates monitoring and logging. private readonly IMonitor Monitor; /// The underlying content manager's asset cache. private readonly IDictionary Cache; - /// Normalises an asset key to match the cache key. - private readonly Func NormaliseAssetKey; + /// Applies platform-specific asset key normalisation so it's consistent with the underlying cache. + private readonly Func NormaliseKeyForPlatform; /********* @@ -58,10 +66,10 @@ namespace StardewModdingAPI.Framework if (Constants.TargetPlatform == Platform.Windows) { IPrivateMethod method = reflection.GetPrivateMethod(typeof(TitleContainer), "GetCleanPath"); - this.NormaliseAssetKey = path => method.Invoke(path); + this.NormaliseKeyForPlatform = path => method.Invoke(path); } else - this.NormaliseAssetKey = this.NormaliseKeyForMono; + this.NormaliseKeyForPlatform = key => key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load logic } /// Load an asset that has been processed by the content pipeline. @@ -69,20 +77,18 @@ namespace StardewModdingAPI.Framework /// The asset path relative to the loader root directory, not including the .xnb extension. public override T Load(string assetName) { - // pass through if no event handlers - if (!ContentEvents.HasAssetLoadingListeners()) - return base.Load(assetName); + // normalise key so can override the cache value later + assetName = this.NormaliseKey(assetName); - // skip if already loaded - string key = this.NormaliseAssetKey(assetName); - if (this.Cache.ContainsKey(key)) + // skip if no event handlers or already loaded + if (!ContentEvents.HasAssetLoadingListeners() || this.Cache.ContainsKey(assetName)) return base.Load(assetName); // intercept load T data = base.Load(assetName); - IContentEventHelper helper = new ContentEventHelper(key, data, this.NormaliseAssetKey); + IContentEventHelper helper = new ContentEventHelper(assetName, data, this.NormaliseKeyForPlatform); ContentEvents.InvokeAssetLoading(this.Monitor, helper); - this.Cache[key] = helper.Data; + this.Cache[assetName] = helper.Data; return (T)helper.Data; } @@ -90,11 +96,16 @@ namespace StardewModdingAPI.Framework /********* ** Private methods *********/ - /// Normalise an asset key for Mono. + /// Normalise an asset key so it's consistent with the underlying cache. /// The asset key. - private string NormaliseKeyForMono(string key) + private string NormaliseKey(string key) { - return key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load logic + // ensure key format is consistent + string[] parts = key.Split(SContentManager.PossiblePathSeparators, StringSplitOptions.RemoveEmptyEntries); + key = string.Join(SContentManager.PreferredPathSeparator, parts); + + // apply platform normalisation logic + return this.NormaliseKeyForPlatform(key); } } } -- cgit From 529e0dbb84548ac47d6b3ca9ac892b743171886e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 25 Feb 2017 19:08:21 -0500 Subject: fix handling of localised XNB files (#173) --- .../Framework/ContentEventHelper.cs | 39 +++++++----------- src/StardewModdingAPI/Framework/SContentManager.cs | 48 ++++++++++++++-------- src/StardewModdingAPI/IContentEventHelper.cs | 14 ++++--- 3 files changed, 53 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/ContentEventHelper.cs b/src/StardewModdingAPI/Framework/ContentEventHelper.cs index d4a9bbb8..a58efe32 100644 --- a/src/StardewModdingAPI/Framework/ContentEventHelper.cs +++ b/src/StardewModdingAPI/Framework/ContentEventHelper.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text.RegularExpressions; using Microsoft.Xna.Framework.Graphics; namespace StardewModdingAPI.Framework @@ -18,8 +17,11 @@ namespace StardewModdingAPI.Framework /********* ** Accessors *********/ - /// The normalised asset path being read. The format may change between platforms; see to compare with a known path. - public string Path { get; } + /// The content's locale code, if the content is localised. + public string Locale { get; } + + /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. + public string AssetName { get; } /// The content data being read. public object Data { get; private set; } @@ -29,37 +31,24 @@ namespace StardewModdingAPI.Framework ** Public methods *********/ /// Construct an instance. - /// The file path being read. + /// The content's locale code, if the content is localised. + /// The normalised asset name being read. /// The content data being read. /// Normalises an asset key to match the cache key. - public ContentEventHelper(string path, object data, Func getNormalisedPath) + public ContentEventHelper(string locale, string assetName, object data, Func getNormalisedPath) { - this.Path = path; + this.Locale = locale; + this.AssetName = assetName; this.Data = data; this.GetNormalisedPath = getNormalisedPath; } - /// Get whether the asset path being loaded matches a given path after normalisation. - /// The expected asset path, relative to the game folder and without the .xnb extension (like 'Data\ObjectInformation'). - /// Whether to match a localised version of the asset file (like 'Data\ObjectInformation.ja-JP'). - public bool IsPath(string path, bool matchLocalisedVersion = true) + /// Get whether the asset name being loaded matches a given name after normalisation. + /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). + public bool IsAssetName(string path) { path = this.GetNormalisedPath(path); - - // equivalent - if (this.Path.Equals(path, StringComparison.InvariantCultureIgnoreCase)) - return true; - - // localised version - if (matchLocalisedVersion) - { - return - this.Path.StartsWith($"{path}.", StringComparison.InvariantCultureIgnoreCase) // starts with given path - && Regex.IsMatch(this.Path.Substring(path.Length + 1), "^[a-z]+-[A-Z]+$"); // ends with locale (e.g. pt-BR) - } - - // no match - return false; + return this.AssetName.Equals(path, StringComparison.InvariantCultureIgnoreCase); } /// Get the data as a given type. diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs index 45f42bf6..65c330db 100644 --- a/src/StardewModdingAPI/Framework/SContentManager.cs +++ b/src/StardewModdingAPI/Framework/SContentManager.cs @@ -31,7 +31,10 @@ namespace StardewModdingAPI.Framework private readonly IDictionary Cache; /// Applies platform-specific asset key normalisation so it's consistent with the underlying cache. - private readonly Func NormaliseKeyForPlatform; + private readonly Func NormaliseAssetNameForPlatform; + + /// The private method which generates the locale portion of an asset name. + private readonly IPrivateMethod GetKeyLocale; /********* @@ -57,19 +60,18 @@ namespace StardewModdingAPI.Framework this.Monitor = monitor; IReflectionHelper reflection = new ReflectionHelper(); - // get underlying asset cache - this.Cache = reflection - .GetPrivateField>(this, "loadedAssets") - .GetValue(); + // get underlying fields for interception + this.Cache = reflection.GetPrivateField>(this, "loadedAssets").GetValue(); + this.GetKeyLocale = reflection.GetPrivateMethod(this, "languageCode"); // get asset key normalisation logic if (Constants.TargetPlatform == Platform.Windows) { IPrivateMethod method = reflection.GetPrivateMethod(typeof(TitleContainer), "GetCleanPath"); - this.NormaliseKeyForPlatform = path => method.Invoke(path); + this.NormaliseAssetNameForPlatform = path => method.Invoke(path); } else - this.NormaliseKeyForPlatform = key => key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load logic + this.NormaliseAssetNameForPlatform = key => key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load logic } /// Load an asset that has been processed by the content pipeline. @@ -77,8 +79,8 @@ namespace StardewModdingAPI.Framework /// The asset path relative to the loader root directory, not including the .xnb extension. public override T Load(string assetName) { - // normalise key so can override the cache value later - assetName = this.NormaliseKey(assetName); + // normalise asset name so can override the cache value later + assetName = this.NormaliseAssetName(assetName); // skip if no event handlers or already loaded if (!ContentEvents.HasAssetLoadingListeners() || this.Cache.ContainsKey(assetName)) @@ -86,7 +88,8 @@ namespace StardewModdingAPI.Framework // intercept load T data = base.Load(assetName); - IContentEventHelper helper = new ContentEventHelper(assetName, data, this.NormaliseKeyForPlatform); + string cacheLocale = this.GetCacheLocale(assetName, this.Cache); + IContentEventHelper helper = new ContentEventHelper(cacheLocale, assetName, data, this.NormaliseAssetName); ContentEvents.InvokeAssetLoading(this.Monitor, helper); this.Cache[assetName] = helper.Data; return (T)helper.Data; @@ -96,16 +99,27 @@ namespace StardewModdingAPI.Framework /********* ** Private methods *********/ - /// Normalise an asset key so it's consistent with the underlying cache. - /// The asset key. - private string NormaliseKey(string key) + /// Normalise an asset name so it's consistent with the underlying cache. + /// The asset key. + private string NormaliseAssetName(string assetName) { - // ensure key format is consistent - string[] parts = key.Split(SContentManager.PossiblePathSeparators, StringSplitOptions.RemoveEmptyEntries); - key = string.Join(SContentManager.PreferredPathSeparator, parts); + // ensure name format is consistent + string[] parts = assetName.Split(SContentManager.PossiblePathSeparators, StringSplitOptions.RemoveEmptyEntries); + assetName = string.Join(SContentManager.PreferredPathSeparator, parts); // apply platform normalisation logic - return this.NormaliseKeyForPlatform(key); + return this.NormaliseAssetNameForPlatform(assetName); + } + + /// Get the locale for which the asset name was saved, if any. + /// The normalised asset name. + /// The cache to search. + private string GetCacheLocale(string normalisedAssetName, IDictionary cache) + { + string locale = this.GetKeyLocale.Invoke(); + return this.Cache.ContainsKey($"{normalisedAssetName}.{this.GetKeyLocale.Invoke()}") + ? locale + : null; } } } diff --git a/src/StardewModdingAPI/IContentEventHelper.cs b/src/StardewModdingAPI/IContentEventHelper.cs index 98d074d9..be8290eb 100644 --- a/src/StardewModdingAPI/IContentEventHelper.cs +++ b/src/StardewModdingAPI/IContentEventHelper.cs @@ -8,8 +8,11 @@ namespace StardewModdingAPI /********* ** Accessors *********/ - /// The normalised asset path being read. The format may change between platforms; see to compare with a known path. - string Path { get; } + /// The content's locale code, if the content is localised. + string Locale { get; } + + /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. + string AssetName { get; } /// The content data being read. object Data { get; } @@ -18,10 +21,9 @@ namespace StardewModdingAPI /********* ** Public methods *********/ - /// Get whether the asset path being loaded matches a given path after normalisation. - /// The expected asset path, relative to the game folder and without the .xnb extension (like 'Data\ObjectInformation'). - /// Whether to match a localised version of the asset file (like 'Data\ObjectInformation.ja-JP'). - bool IsPath(string path, bool matchLocalisedVersion = true); + /// Get whether the asset name being loaded matches a given name after normalisation. + /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). + bool IsAssetName(string path); /// Get the data as a given type. /// The expected data type. -- cgit From d956a7b223fdeeea0ed12a4e2eb1ed1ba2a4fb25 Mon Sep 17 00:00:00 2001 From: Efreak Date: Mon, 27 Feb 2017 13:33:10 -0800 Subject: typo fix a typo preventing usage of player_setstamina --- src/TrainerMod/TrainerMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index 1690d5b5..a1eef561 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -181,7 +181,7 @@ namespace TrainerMod this.LogValueNotSpecified(); break; - case "playet_setstamina": + case "player_setstamina": if (args.Any()) { string amountStr = args[0]; -- cgit From 49a801b2b0dce4bd3b4abeee3e45ba0d7f2bee4d Mon Sep 17 00:00:00 2001 From: Efreak Date: Tue, 28 Feb 2017 14:59:10 -0800 Subject: toggle timefreeze when no value given --- src/TrainerMod/TrainerMod.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index a1eef561..22ec2e66 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -104,7 +104,7 @@ namespace TrainerMod .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 \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 \n- value: one of 0 (resume) or 1 (freeze).", 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_setday", "Sets the day to the specified value.\n\nUsage: world_setday .\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 \n- value: the target season (one of 'spring', 'summer', 'fall', 'winter').", this.HandleCommand) .Add("world_downminelevel", "Goes down one mine level?", this.HandleCommand) @@ -381,13 +381,24 @@ namespace TrainerMod this.Monitor.Log("Time is now " + (this.FreezeTime ? "frozen" : "thawed"), LogLevel.Info); } else - this.Monitor.Log(" should be 0 or 1", LogLevel.Error); + this.Monitor.Log(" should be 0, 1, or empty", LogLevel.Error); } else this.LogValueNotInt32(); } else - this.LogValueNotSpecified(); + int valu = 1; + if (this.FreezeTime == false) + { + valu = 1; + } + else + { + valu = 0; + } + this.FreezeTime = valu == 1; + this.FrozenTime = this.FreezeTime ? Game1.timeOfDay : 0; + this.Monitor.Log("Time is now " + (this.FreezeTime ? "frozen" : "thawed"), LogLevel.Info); break; case "world_settime": -- cgit From 3863c159ae10632b7632ca347555dd17494ca7d0 Mon Sep 17 00:00:00 2001 From: Efreak Date: Tue, 28 Feb 2017 15:04:11 -0800 Subject: Return current values for max/current health/stamina/immunity instead of errors when no value given --- src/TrainerMod/TrainerMod.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index 22ec2e66..1a85b9b9 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -201,7 +201,7 @@ namespace TrainerMod } } else - this.LogValueNotSpecified(); + this.Monitor.Log($"{Game1.player.Name}'s stamina is {Game1.player.Stamina}", LogLevel.Info); break; case "player_setmaxstamina": @@ -217,7 +217,7 @@ namespace TrainerMod this.LogValueNotInt32(); } else - this.LogValueNotSpecified(); + this.Monitor.Log($"{Game1.player.Name}'s maxstamina is {Game1.player.MaxStamina}", LogLevel.Info); break; case "player_setlevel": @@ -476,7 +476,7 @@ namespace TrainerMod } } else - this.LogValueNotSpecified(); + this.Monitor.Log($"Health is: {Game1.player.health}", LogLevel.Info); break; case "player_setmaxhealth": @@ -489,7 +489,7 @@ namespace TrainerMod this.LogValueNotInt32(); } else - this.LogValueNotSpecified(); + this.Monitor.Log($"MaxHealth is: {Game1.player.maxHealth}", LogLevel.Info); break; case "player_setimmunity": @@ -502,7 +502,7 @@ namespace TrainerMod this.LogValueNotInt32(); } else - this.LogValueNotSpecified(); + this.Monitor.Log($"Immunity is: {Game1.player.immunity}", LogLevel.Info); break; case "player_additem": -- cgit From aba50aba45c81c8ed18cf7ee47baa0071455ff97 Mon Sep 17 00:00:00 2001 From: Efreak Date: Tue, 28 Feb 2017 15:27:15 -0800 Subject: Update <> to [] for changed commands --- src/TrainerMod/TrainerMod.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index 1a85b9b9..1298c5cd 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -86,11 +86,11 @@ namespace TrainerMod .Add("load", "Shows the load screen.", this.HandleCommand) .Add("player_setname", "Sets the player's name.\n\nUsage: player_setname \n- target: what to rename (one of 'player' or 'farm').\n- name: the new name to set.", this.HandleCommand) .Add("player_setmoney", "Sets the player's money.\n\nUsage: player_setmoney \n- value: an integer amount, or 'inf' for infinite money.", this.HandleCommand) - .Add("player_setstamina", "Sets the player's stamina.\n\nUsage: player_setstamina \n- value: an integer amount, or 'inf' for infinite stamina.", this.HandleCommand) - .Add("player_setmaxstamina", "Sets the player's max stamina.\n\nUsage: player_setmaxstamina \n- value: an integer amount.", this.HandleCommand) - .Add("player_sethealth", "Sets the player's health.\n\nUsage: player_sethealth \n- value: an integer amount, or 'inf' for infinite health.", this.HandleCommand) - .Add("player_setmaxhealth", "Sets the player's max health.\n\nUsage: player_setmaxhealth \n- value: an integer amount.", this.HandleCommand) - .Add("player_setimmunity", "Sets the player's immunity.\n\nUsage: player_setimmunity \n- value: an integer amount.", this.HandleCommand) + .Add("player_setstamina", "Sets the player's stamina.\n\nUsage: player_setstamina [value]\n- value: an integer amount, or 'inf' for infinite stamina.", this.HandleCommand) + .Add("player_setmaxstamina", "Sets the player's max stamina.\n\nUsage: player_setmaxstamina [value]\n- value: an integer amount.", this.HandleCommand) + .Add("player_sethealth", "Sets the player's health.\n\nUsage: player_sethealth [value]\n- value: an integer amount, or 'inf' for infinite health.", this.HandleCommand) + .Add("player_setmaxhealth", "Sets the player's max health.\n\nUsage: player_setmaxhealth [value]\n- value: an integer amount.", this.HandleCommand) + .Add("player_setimmunity", "Sets the player's immunity.\n\nUsage: player_setimmunity [value]\n- value: an integer amount.", this.HandleCommand) .Add("player_setlevel", "Sets the player's specified skill to the specified value.\n\nUsage: player_setlevel \n- skill: the skill to set (one of 'luck', 'mining', 'combat', 'farming', 'fishing', or 'foraging').\n- value: the target level (a number from 1 to 10).", this.HandleCommand) .Add("player_setspeed", "Sets the player's speed to the specified value?\n\nUsage: player_setspeed \n- value: an integer amount (0 is normal).", this.HandleCommand) -- cgit From 6de4888a1b3e62f3b3976ba013521fbd79405288 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Mar 2017 19:17:34 -0500 Subject: improve TrainerMod feedback to user, standardise color/colour spelling --- release-notes.md | 1 + src/StardewModdingAPI/Program.cs | 5 +- src/TrainerMod/TrainerMod.cs | 260 +++++++++++++++++++++++---------------- 3 files changed, 160 insertions(+), 106 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index d157c06a..81d770f0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,6 +8,7 @@ For players: * Simplified log filename. * Simplified error messages when a mod can't be loaded. * Simple nested mod folders are now recognised by SMAPI (e.g. `ModName-1.0\ModName\manifest.json`). +* Improved TrainerMod command handling & feedback. * Fixed game's debug output being shown in the console for all users. * Fixed the game-outdated error not pausing before exit. * Fixed installer errors for some players when deleting files. diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 18a83e32..07857512 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -554,7 +554,10 @@ namespace StardewModdingAPI this.Monitor.Log($"{result.Name}: {result.Documentation}\n(Added by {result.ModName}.)", LogLevel.Info); } else - this.Monitor.Log("Commands: " + string.Join(", ", this.CommandManager.GetAll().Select(p => p.Name)), LogLevel.Info); + { + this.Monitor.Log("The following commands are registered: " + string.Join(", ", this.CommandManager.GetAll().Select(p => p.Name)) + ".", LogLevel.Info); + this.Monitor.Log("For more information about a command, type 'help command_name'.", LogLevel.Info); + } } /// Show a 'press any key to exit' message, and exit when they press a key. diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index 1298c5cd..b89cbf5c 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -94,7 +94,7 @@ namespace TrainerMod .Add("player_setlevel", "Sets the player's specified skill to the specified value.\n\nUsage: player_setlevel \n- skill: the skill to set (one of 'luck', 'mining', 'combat', 'farming', 'fishing', or 'foraging').\n- value: the target level (a number from 1 to 10).", this.HandleCommand) .Add("player_setspeed", "Sets the player's speed to the specified value?\n\nUsage: player_setspeed \n- value: an integer amount (0 is normal).", this.HandleCommand) - .Add("player_changecolour", "Sets the colour of a player feature.\n\nUsage: player_changecolor \n- target: what to change (one of 'hair', 'eyes', or 'pants').\n- colour: a colour value in RGB format, like (255,255,255).", this.HandleCommand) + .Add("player_changecolor", "Sets the color of a player feature.\n\nUsage: player_changecolor \n- target: what to change (one of 'hair', 'eyes', or 'pants').\n- color: a color value in RGB format, like (255,255,255).", this.HandleCommand) .Add("player_changestyle", "Sets the style of a player feature.\n\nUsage: player_changecolor .\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 [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) @@ -104,7 +104,7 @@ namespace TrainerMod .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 \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_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_setday", "Sets the day to the specified value.\n\nUsage: world_setday .\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 \n- value: the target season (one of 'spring', 'summer', 'fall', 'winter').", this.HandleCommand) .Add("world_downminelevel", "Goes down one mine level?", this.HandleCommand) @@ -115,21 +115,23 @@ namespace TrainerMod } /// Handle a TrainerMod command. - /// The command name. + /// The command name. /// The command arguments. - private void HandleCommand(string name, string[] args) + private void HandleCommand(string command, string[] args) { - switch (name) + switch (command) { case "type": - this.Monitor.Log($"[Int32: {int.MinValue} - {int.MaxValue}], [Int64: {long.MinValue} - {long.MaxValue}], [String: \"raw text\"], [Colour: r,g,b (EG: 128, 32, 255)]", LogLevel.Info); + this.Monitor.Log($"[Int32: {int.MinValue} - {int.MaxValue}], [Int64: {long.MinValue} - {long.MaxValue}], [String: \"raw text\"], [Color: r,g,b (EG: 128, 32, 255)]", LogLevel.Info); break; case "save": + this.Monitor.Log("Saving the game...", LogLevel.Info); SaveGame.Save(); break; case "load": + this.Monitor.Log("Triggering load menu...", LogLevel.Info); Game1.hasLoadedGame = false; Game1.activeClickableMenu = new LoadGameMenu(); break; @@ -145,17 +147,19 @@ namespace TrainerMod { case "player": Game1.player.Name = args[1]; + this.Monitor.Log($"OK, your player's name is now {Game1.player.Name}.", LogLevel.Info); break; case "farm": Game1.player.farmName = args[1]; + this.Monitor.Log($"OK, your farm's name is now {Game1.player.Name}.", LogLevel.Info); break; } } else - this.LogObjectInvalid(); + this.LogArgumentsInvalid(command); } else - this.LogObjectValueNotSpecified(); + this.Monitor.Log($"Your name is currently '{Game1.player.Name}'. Type 'help player_setname' for usage.", LogLevel.Info); break; case "player_setmoney": @@ -163,7 +167,10 @@ namespace TrainerMod { string amountStr = args[0]; if (amountStr == "inf") + { this.InfiniteMoney = true; + this.Monitor.Log("OK, you now have infinite money.", LogLevel.Info); + } else { this.InfiniteMoney = false; @@ -171,14 +178,14 @@ namespace TrainerMod if (int.TryParse(amountStr, out amount)) { Game1.player.Money = amount; - this.Monitor.Log($"Set {Game1.player.Name}'s money to {Game1.player.Money}", LogLevel.Info); + this.Monitor.Log($"OK, you now have {Game1.player.Money} gold.", LogLevel.Info); } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } } else - this.LogValueNotSpecified(); + this.Monitor.Log($"You currently have {(this.InfiniteMoney ? "infinite" : Game1.player.Money.ToString())} gold. Specify a value to change it.", LogLevel.Info); break; case "player_setstamina": @@ -186,7 +193,10 @@ namespace TrainerMod { string amountStr = args[0]; if (amountStr == "inf") + { this.InfiniteStamina = true; + this.Monitor.Log("OK, you now have infinite stamina.", LogLevel.Info); + } else { this.InfiniteStamina = false; @@ -194,14 +204,14 @@ namespace TrainerMod if (int.TryParse(amountStr, out amount)) { Game1.player.Stamina = amount; - this.Monitor.Log($"Set {Game1.player.Name}'s stamina to {Game1.player.Stamina}", LogLevel.Info); + this.Monitor.Log($"OK, you now have {Game1.player.Stamina} stamina.", LogLevel.Info); } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } } else - this.Monitor.Log($"{Game1.player.Name}'s stamina is {Game1.player.Stamina}", LogLevel.Info); + this.Monitor.Log($"You currently have {(this.InfiniteStamina ? "infinite" : Game1.player.Stamina.ToString())} stamina. Specify a value to change it.", LogLevel.Info); break; case "player_setmaxstamina": @@ -211,13 +221,13 @@ namespace TrainerMod if (int.TryParse(args[0], out amount)) { Game1.player.MaxStamina = amount; - this.Monitor.Log($"Set {Game1.player.Name}'s max stamina to {Game1.player.MaxStamina}", LogLevel.Info); + this.Monitor.Log($"OK, you now have {Game1.player.MaxStamina} max stamina.", LogLevel.Info); } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } else - this.Monitor.Log($"{Game1.player.Name}'s maxstamina is {Game1.player.MaxStamina}", LogLevel.Info); + this.Monitor.Log($"You currently have {Game1.player.MaxStamina} max stamina. Specify a value to change it.", LogLevel.Info); break; case "player_setlevel": @@ -234,32 +244,38 @@ namespace TrainerMod { case "luck": Game1.player.LuckLevel = level; + this.Monitor.Log($"OK, your luck skill is now {Game1.player.LuckLevel}.", LogLevel.Info); break; case "mining": Game1.player.MiningLevel = level; + this.Monitor.Log($"OK, your mining skill is now {Game1.player.MiningLevel}.", LogLevel.Info); break; case "combat": Game1.player.CombatLevel = level; + this.Monitor.Log($"OK, your combat skill is now {Game1.player.CombatLevel}.", LogLevel.Info); break; case "farming": Game1.player.FarmingLevel = level; + this.Monitor.Log($"OK, your farming skill is now {Game1.player.FarmingLevel}.", LogLevel.Info); break; case "fishing": Game1.player.FishingLevel = level; + this.Monitor.Log($"OK, your fishing skill is now {Game1.player.FishingLevel}.", LogLevel.Info); break; case "foraging": Game1.player.ForagingLevel = level; + this.Monitor.Log($"OK, your foraging skill is now {Game1.player.ForagingLevel}.", LogLevel.Info); break; } } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } else - this.Monitor.Log(" is invalid", LogLevel.Error); + this.LogUsageError("That isn't a valid skill.", command); } else - this.Monitor.Log(" and must be specified", LogLevel.Error); + this.LogArgumentsInvalid(command); break; case "player_setspeed": @@ -269,16 +285,16 @@ namespace TrainerMod if (amountStr.IsInt()) { Game1.player.addedSpeed = amountStr.ToInt(); - this.Monitor.Log($"Set {Game1.player.Name}'s added speed to {Game1.player.addedSpeed}", LogLevel.Info); + this.Monitor.Log($"OK, your added speed is now {Game1.player.addedSpeed}.", LogLevel.Info); } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } else - this.LogValueNotSpecified(); + this.Monitor.Log($"You currently have {Game1.player.addedSpeed} added speed. Specify a value to change it.", LogLevel.Info); break; - case "player_changecolour": + case "player_changecolor": if (args.Length > 1) { string target = args[0]; @@ -293,23 +309,26 @@ namespace TrainerMod { case "hair": Game1.player.hairstyleColor = color; + this.Monitor.Log("OK, your hair color is updated.", LogLevel.Info); break; case "eyes": Game1.player.changeEyeColor(color); + this.Monitor.Log("OK, your eye color is updated.", LogLevel.Info); break; case "pants": Game1.player.pantsColor = color; + this.Monitor.Log("OK, your pants color is updated.", LogLevel.Info); break; } } else - this.Monitor.Log(" is invalid", LogLevel.Error); + this.LogUsageError("The color should be an RBG value like '255,150,0'.", command); } else - this.LogObjectInvalid(); + this.LogArgumentsInvalid(command); } else - this.Monitor.Log(" and must be specified", LogLevel.Error); + this.LogArgumentsInvalid(command); break; case "player_changestyle": @@ -326,45 +345,66 @@ namespace TrainerMod { case "hair": Game1.player.changeHairStyle(styleID); + this.Monitor.Log("OK, your hair style is updated.", LogLevel.Info); break; case "shirt": Game1.player.changeShirt(styleID); + this.Monitor.Log("OK, your shirt style is updated.", LogLevel.Info); break; case "acc": Game1.player.changeAccessory(styleID); + this.Monitor.Log("OK, your accessory style is updated.", LogLevel.Info); break; case "skin": Game1.player.changeSkinColor(styleID); + this.Monitor.Log("OK, your skin color is updated.", LogLevel.Info); break; case "shoe": Game1.player.changeShoeColor(styleID); + this.Monitor.Log("OK, your shoe style is updated.", LogLevel.Info); break; case "swim": - if (styleID == 0) - Game1.player.changeOutOfSwimSuit(); - else if (styleID == 1) - Game1.player.changeIntoSwimsuit(); - else - this.Monitor.Log(" must be 0 or 1 for this ", LogLevel.Error); + switch (styleID) + { + case 0: + Game1.player.changeOutOfSwimSuit(); + this.Monitor.Log("OK, you're no longer in your swimming suit.", LogLevel.Info); + break; + case 1: + Game1.player.changeIntoSwimsuit(); + this.Monitor.Log("OK, you're now in your swimming suit.", LogLevel.Info); + break; + default: + this.LogUsageError("The swim value should be 0 (no swimming suit) or 1 (swimming suit).", command); + break; + } break; case "gender": - if (styleID == 0) - Game1.player.changeGender(true); - else if (styleID == 1) - Game1.player.changeGender(false); - else - this.Monitor.Log(" must be 0 or 1 for this ", LogLevel.Error); + switch (styleID) + { + case 0: + Game1.player.changeGender(true); + this.Monitor.Log("OK, you're now male.", LogLevel.Info); + break; + case 1: + Game1.player.changeGender(false); + this.Monitor.Log("OK, you're now female.", LogLevel.Info); + break; + default: + this.LogUsageError("The gender value should be 0 (male) or 1 (female).", command); + break; + } break; } } else - this.LogValueInvalid(); + this.LogArgumentsInvalid(command); } else - this.LogObjectInvalid(); + this.LogArgumentsInvalid(command); } else - this.LogObjectValueNotSpecified(); + this.LogArgumentsInvalid(command); break; case "world_freezetime": @@ -378,27 +418,20 @@ namespace TrainerMod { this.FreezeTime = value == 1; this.FrozenTime = this.FreezeTime ? Game1.timeOfDay : 0; - this.Monitor.Log("Time is now " + (this.FreezeTime ? "frozen" : "thawed"), LogLevel.Info); + this.Monitor.Log($"OK, time is now {(this.FreezeTime ? "frozen" : "resumed")}.", LogLevel.Info); } else - this.Monitor.Log(" should be 0, 1, or empty", LogLevel.Error); + this.LogUsageError("The value should be 0 (not frozen), 1 (frozen), or empty (toggle).", command); } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } else - int valu = 1; - if (this.FreezeTime == false) - { - valu = 1; - } - else - { - valu = 0; - } - this.FreezeTime = valu == 1; + { + this.FreezeTime = !this.FreezeTime; this.FrozenTime = this.FreezeTime ? Game1.timeOfDay : 0; - this.Monitor.Log("Time is now " + (this.FreezeTime ? "frozen" : "thawed"), LogLevel.Info); + this.Monitor.Log($"OK, time is now {(this.FreezeTime ? "frozen" : "resumed")}.", LogLevel.Info); + } break; case "world_settime": @@ -413,16 +446,16 @@ namespace TrainerMod { Game1.timeOfDay = args[0].ToInt(); this.FrozenTime = this.FreezeTime ? Game1.timeOfDay : 0; - this.Monitor.Log($"Time set to: {Game1.timeOfDay}", LogLevel.Info); + this.Monitor.Log($"OK, the time is now {Game1.timeOfDay.ToString().PadLeft(4, '0')}.", LogLevel.Info); } else - this.Monitor.Log(" should be between 600 and 2600 (06:00 AM - 02:00 AM [NEXT DAY])", LogLevel.Error); + this.LogUsageError("That isn't a valid time.", command); } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } else - this.LogValueNotSpecified(); + this.Monitor.Log($"The current time is {Game1.timeOfDay}. Specify a value to change it.", LogLevel.Info); break; case "world_setday": @@ -434,15 +467,18 @@ namespace TrainerMod { int day = dayStr.ToInt(); if (day <= 28 && day > 0) + { Game1.dayOfMonth = day; + this.Monitor.Log($"OK, the date is now {Game1.currentSeason} {Game1.dayOfMonth}.", LogLevel.Info); + } else - this.Monitor.Log(" must be between 1 and 28", LogLevel.Error); + this.LogUsageError("That isn't a valid day.", command); } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } else - this.LogValueNotSpecified(); + this.Monitor.Log($"The current date is {Game1.currentSeason} {Game1.dayOfMonth}. Specify a value to change the day.", LogLevel.Info); break; case "world_setseason": @@ -451,12 +487,15 @@ namespace TrainerMod string season = args[0]; string[] validSeasons = { "winter", "spring", "summer", "fall" }; if (validSeasons.Contains(season)) + { Game1.currentSeason = season; + this.Monitor.Log($"OK, the date is now {Game1.currentSeason} {Game1.dayOfMonth}.", LogLevel.Info); + } else - this.LogValueInvalid(); + this.LogUsageError("That isn't a valid season name.", command); } else - this.LogValueNotSpecified(); + this.Monitor.Log($"The current season is {Game1.currentSeason}. Specify a value to change it.", LogLevel.Info); break; case "player_sethealth": @@ -465,18 +504,24 @@ namespace TrainerMod string amountStr = args[0]; if (amountStr == "inf") + { this.InfiniteHealth = true; + this.Monitor.Log("OK, you now have infinite health.", LogLevel.Info); + } else { this.InfiniteHealth = false; if (amountStr.IsInt()) + { Game1.player.health = amountStr.ToInt(); + this.Monitor.Log($"OK, you now have {Game1.player.health} health.", LogLevel.Info); + } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } } else - this.Monitor.Log($"Health is: {Game1.player.health}", LogLevel.Info); + this.Monitor.Log($"You currently have {(this.InfiniteHealth ? "infinite" : Game1.player.health.ToString())} health. Specify a value to change it.", LogLevel.Info); break; case "player_setmaxhealth": @@ -484,12 +529,15 @@ namespace TrainerMod { string amountStr = args[0]; if (amountStr.IsInt()) + { Game1.player.maxHealth = amountStr.ToInt(); + this.Monitor.Log($"OK, you now have {Game1.player.maxHealth} max health.", LogLevel.Info); + } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } else - this.Monitor.Log($"MaxHealth is: {Game1.player.maxHealth}", LogLevel.Info); + this.Monitor.Log($"You currently have {Game1.player.maxHealth} max health. Specify a value to change it.", LogLevel.Info); break; case "player_setimmunity": @@ -497,12 +545,15 @@ namespace TrainerMod { string amountStr = args[0]; if (amountStr.IsInt()) + { Game1.player.immunity = amountStr.ToInt(); + this.Monitor.Log($"OK, you now have {Game1.player.immunity} immunity.", LogLevel.Info); + } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } else - this.Monitor.Log($"Immunity is: {Game1.player.immunity}", LogLevel.Info); + this.Monitor.Log($"You currently have {Game1.player.immunity} immunity. Specify a value to change it.", LogLevel.Info); break; case "player_additem": @@ -520,7 +571,7 @@ namespace TrainerMod count = args[1].ToInt(); else { - this.Monitor.Log("[count] is invalid", LogLevel.Error); + this.LogUsageError("The optional count is invalid.", command); return; } @@ -530,21 +581,21 @@ namespace TrainerMod quality = args[2].ToInt(); else { - this.Monitor.Log("[quality] is invalid", LogLevel.Error); + this.LogUsageError("The optional quality is invalid.", command); return; } } } var item = new Object(itemID, count) { quality = quality }; - Game1.player.addItemByMenuIfNecessary(item); + this.Monitor.Log($"OK, added {item.Name} to your inventory.", LogLevel.Info); } else - this.Monitor.Log(" is invalid", LogLevel.Error); + this.LogUsageError("The item ID must be an integer.", command); } else - this.LogObjectValueNotSpecified(); + this.LogArgumentsInvalid(command); break; case "player_addmelee": @@ -554,13 +605,13 @@ namespace TrainerMod { MeleeWeapon weapon = new MeleeWeapon(args[0].ToInt()); Game1.player.addItemByMenuIfNecessary(weapon); - this.Monitor.Log($"Gave {weapon.Name} to {Game1.player.Name}", LogLevel.Info); + this.Monitor.Log($"OK, added {weapon.Name} to your inventory.", LogLevel.Info); } else - this.Monitor.Log(" is invalid", LogLevel.Error); + this.LogUsageError("The weapon ID must be an integer.", command); } else - this.LogObjectValueNotSpecified(); + this.LogArgumentsInvalid(command); break; case "player_addring": @@ -570,13 +621,13 @@ namespace TrainerMod { Ring ring = new Ring(args[0].ToInt()); Game1.player.addItemByMenuIfNecessary(ring); - this.Monitor.Log($"Gave {ring.Name} to {Game1.player.Name}", LogLevel.Info); + this.Monitor.Log($"OK, added {ring.Name} to your inventory.", LogLevel.Info); } else this.Monitor.Log(" is invalid", LogLevel.Error); } else - this.LogObjectValueNotSpecified(); + this.LogArgumentsInvalid(command); break; case "list_items": @@ -594,30 +645,37 @@ namespace TrainerMod case "world_downminelevel": Game1.nextMineLevel(); + this.Monitor.Log("OK, warping you to the next mine level.", LogLevel.Info); break; case "world_setminelevel": if (args.Any()) { if (args[0].IsInt()) - Game1.enterMine(true, args[0].ToInt(), ""); + { + int level = args[0].ToInt(); + Game1.enterMine(true, level, ""); + this.Monitor.Log($"OK, warping you to mine level {level}.", LogLevel.Info); + } else - this.LogValueNotInt32(); + this.LogArgumentNotInt(command); } else - this.LogValueNotSpecified(); + this.LogArgumentsInvalid(command); break; case "show_game_files": Process.Start(Constants.ExecutionPath); + this.Monitor.Log($"OK, opening {Constants.ExecutionPath}.", LogLevel.Info); break; case "show_data_files": Process.Start(Constants.DataPath); + this.Monitor.Log($"OK, opening {Constants.DataPath}.", LogLevel.Info); break; default: - throw new NotImplementedException($"TrainerMod received unknown command '{name}'."); + throw new NotImplementedException($"TrainerMod received unknown command '{command}'."); } } @@ -706,34 +764,26 @@ namespace TrainerMod /**** ** Logging ****/ - /// Log an error indicating a value must be specified. - public void LogValueNotSpecified() + /// Log an error indicating incorrect usage. + /// A sentence explaining the problem. + /// The name of the command. + private void LogUsageError(string error, string command) { - this.Monitor.Log(" must be specified", LogLevel.Error); + this.Monitor.Log($"{error} Type 'help {command}' for usage.", LogLevel.Error); } - /// Log an error indicating a target and value must be specified. - public void LogObjectValueNotSpecified() + /// Log an error indicating a value must be an integer. + /// The name of the command. + private void LogArgumentNotInt(string command) { - this.Monitor.Log(" and must be specified", LogLevel.Error); + this.LogUsageError("The value must be a whole number.", command); } /// Log an error indicating a value is invalid. - public void LogValueInvalid() - { - this.Monitor.Log(" is invalid", LogLevel.Error); - } - - /// Log an error indicating a target is invalid. - public void LogObjectInvalid() - { - this.Monitor.Log(" is invalid", LogLevel.Error); - } - - /// Log an error indicating a value must be an integer. - public void LogValueNotInt32() + /// The name of the command. + private void LogArgumentsInvalid(string command) { - this.Monitor.Log(" must be a whole number (Int32)", LogLevel.Error); + this.LogUsageError("The arguments are invalid.", command); } } } -- cgit From 696bdab3cd3352b439207076d604f21bbcf1d922 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Mar 2017 19:18:21 -0500 Subject: fix errors in console command handlers crashing the game --- release-notes.md | 1 + src/StardewModdingAPI/Program.cs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 81d770f0..797da19d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -15,6 +15,7 @@ For players: * Fixed installer not ignoring potential game folders that don't contain a Stardew Valley exe. * Fixed installer not recognising Linux/Mac paths starting with `~/` or containing an escaped space. * Fixed rare issue where mod dependencies would override SMAPI dependencies and cause unpredictable bugs. +* Fixed errors in console command handlers causing the game to crash. For mod developers: * Added `SaveEvents.AfterReturnToTitle` and `TimeEvents.AfterDayStarted` events. diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 07857512..ebeefe96 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -535,8 +535,15 @@ namespace StardewModdingAPI while (true) { string input = Console.ReadLine(); - if (!string.IsNullOrWhiteSpace(input) && !this.CommandManager.Trigger(input)) - this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error); + 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); + } } } -- cgit From 035f94f7cbe693a107e76ae97d29c8e7da2ccb07 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Mar 2017 19:19:09 -0500 Subject: remove unneeded 'types' TrainerMod command --- src/TrainerMod/TrainerMod.cs | 5 ----- 1 file changed, 5 deletions(-) (limited to 'src') diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index b89cbf5c..1e668b42 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -81,7 +81,6 @@ namespace TrainerMod private void RegisterCommands(IModHelper helper) { helper.ConsoleCommands - .Add("types", "Lists all value types.", this.HandleCommand) .Add("save", "Saves the game? Doesn't seem to work.", this.HandleCommand) .Add("load", "Shows the load screen.", this.HandleCommand) .Add("player_setname", "Sets the player's name.\n\nUsage: player_setname \n- target: what to rename (one of 'player' or 'farm').\n- name: the new name to set.", this.HandleCommand) @@ -121,10 +120,6 @@ namespace TrainerMod { switch (command) { - case "type": - this.Monitor.Log($"[Int32: {int.MinValue} - {int.MaxValue}], [Int64: {long.MinValue} - {long.MaxValue}], [String: \"raw text\"], [Color: r,g,b (EG: 128, 32, 255)]", LogLevel.Info); - break; - case "save": this.Monitor.Log("Saving the game...", LogLevel.Info); SaveGame.Save(); -- cgit From 51ac0127e8fcee3d89eb4e3193250ed58fec8de9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Mar 2017 19:20:11 -0500 Subject: fix TrainerMod letting player add invalid items --- release-notes.md | 1 + src/TrainerMod/TrainerMod.cs | 30 +++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 797da19d..3dfa42ad 100644 --- a/release-notes.md +++ b/release-notes.md @@ -14,6 +14,7 @@ For players: * Fixed installer errors for some players when deleting files. * Fixed installer not ignoring potential game folders that don't contain a Stardew Valley exe. * Fixed installer not recognising Linux/Mac paths starting with `~/` or containing an escaped space. +* Fixed TrainerMod letting you add invalid items which may crash the game. * Fixed rare issue where mod dependencies would override SMAPI dependencies and cause unpredictable bugs. * Fixed errors in console command handlers causing the game to crash. diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index 1e668b42..5cbd1ae6 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -583,8 +583,13 @@ namespace TrainerMod } var item = new Object(itemID, count) { quality = quality }; - Game1.player.addItemByMenuIfNecessary(item); - this.Monitor.Log($"OK, added {item.Name} to your inventory.", LogLevel.Info); + if (item.Name == "Error Item") + this.Monitor.Log("There is no such item ID.", LogLevel.Error); + else + { + Game1.player.addItemByMenuIfNecessary(item); + this.Monitor.Log($"OK, added {item.Name} to your inventory.", LogLevel.Info); + } } else this.LogUsageError("The item ID must be an integer.", command); @@ -599,8 +604,13 @@ namespace TrainerMod if (args[0].IsInt()) { MeleeWeapon weapon = new MeleeWeapon(args[0].ToInt()); - Game1.player.addItemByMenuIfNecessary(weapon); - this.Monitor.Log($"OK, added {weapon.Name} to your inventory.", LogLevel.Info); + if (weapon.Name == null) + this.Monitor.Log("There is no such weapon ID.", LogLevel.Error); + else + { + 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); @@ -614,9 +624,15 @@ namespace TrainerMod { if (args[0].IsInt()) { - Ring ring = new Ring(args[0].ToInt()); - Game1.player.addItemByMenuIfNecessary(ring); - this.Monitor.Log($"OK, added {ring.Name} to your inventory.", LogLevel.Info); + int ringID = args[0].ToInt(); + if (ringID < Ring.ringLowerIndexRange || ringID > Ring.ringUpperIndexRange) + this.Monitor.Log($"There is no such ring ID (must be between {Ring.ringLowerIndexRange} and {Ring.ringUpperIndexRange}).", LogLevel.Error); + else + { + Ring ring = new Ring(ringID); + Game1.player.addItemByMenuIfNecessary(ring); + this.Monitor.Log($"OK, added {ring.Name} to your inventory.", LogLevel.Info); + } } else this.Monitor.Log(" is invalid", LogLevel.Error); -- cgit From 5270240c01a32b19d949a24566b65731dbc3d865 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Mar 2017 19:21:58 -0500 Subject: update TrainerMod manifest --- src/TrainerMod/manifest.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/TrainerMod/manifest.json b/src/TrainerMod/manifest.json index 72a3f40a..8bddf02d 100644 --- a/src/TrainerMod/manifest.json +++ b/src/TrainerMod/manifest.json @@ -1,13 +1,13 @@ { "Name": "Trainer Mod", - "Author": "Zoryn", + "Author": "SMAPI", "Version": { "MajorVersion": 1, - "MinorVersion": 0, + "MinorVersion": 9, "PatchVersion": 0, - "Build": "" + "Build": null }, - "Description": "Registers several commands to use. Most commands are trainer-like in that they offer forms of cheating.", - "UniqueID": "Zoryn.TrainerMod", + "Description": "Adds SMAPI console commands that let you manipulate the game.", + "UniqueID": "SMAPI.TrainerMod", "EntryDll": "TrainerMod.dll" -} \ No newline at end of file +} -- cgit From 5cdf75b463cdae632ee441da312b763c899e9e72 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Mar 2017 19:32:14 -0500 Subject: show OS caption (like "Windows 10") instead of internal version when available --- release-notes.md | 3 ++- src/StardewModdingAPI/Program.cs | 19 ++++++++++++++++++- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 3dfa42ad..029246b0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -23,7 +23,8 @@ For mod developers: * Added a simpler API for console commands (see `helper.ConsoleCommands`). * Added `GetPrivateProperty` reflection helper. * SMAPI now writes XNA input enums (`Buttons` and `Keys`) to JSON as strings, so mods no longer need to add a `StringEnumConverter` themselves for those. -* Log files now always use `\r\n` to simplify crossplatform viewing. +* Logs now show the OS caption (like "Windows 10") instead of its internal version when available. +* Logs now always use `\r\n` to simplify crossplatform viewing. * Several obsolete APIs have been removed (see [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod)), and all _notice_-level deprecations have been increased to _info_. diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index ebeefe96..360f75f0 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; +using System.Management; using System.Reflection; using System.Threading; #if SMAPI_FOR_WINDOWS @@ -87,7 +88,7 @@ namespace StardewModdingAPI { // initialise logging Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); // for consistent log formatting - this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {Environment.OSVersion}", LogLevel.Info); + this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {this.GetFriendlyPlatformName()}", LogLevel.Info); Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion}"; // inject compatibility shims @@ -582,5 +583,21 @@ namespace StardewModdingAPI { return new Monitor(name, this.ConsoleManager, this.LogFile, this.ExitGameImmediately) { WriteToConsole = this.Monitor.WriteToConsole, ShowTraceInConsole = this.Settings.DeveloperMode }; } + + /// Get a human-readable name for the current platform. + private string GetFriendlyPlatformName() + { + try + { + return ( + from entry in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().Cast() + select entry.GetPropertyValue("Caption").ToString() + ).FirstOrDefault(); + } + catch + { + return Environment.OSVersion.ToString(); + } + } } } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 9b86adac..2340a431 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -97,6 +97,7 @@ + True -- cgit From 5ed6eb09860050f1a669539a75df21200d59f554 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Mar 2017 20:06:03 -0500 Subject: fix `world_downminelevel` command not working, prevent invalid `world_setminelevel` value --- release-notes.md | 1 + src/TrainerMod/TrainerMod.cs | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 029246b0..7a936694 100644 --- a/release-notes.md +++ b/release-notes.md @@ -15,6 +15,7 @@ For players: * Fixed installer not ignoring potential game folders that don't contain a Stardew Valley exe. * Fixed installer not recognising Linux/Mac paths starting with `~/` or containing an escaped space. * Fixed TrainerMod letting you add invalid items which may crash the game. +* Fixed TrainerMod's `world_downminelevel` command not working. * Fixed rare issue where mod dependencies would override SMAPI dependencies and cause unpredictable bugs. * Fixed errors in console command handlers causing the game to crash. diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index 5cbd1ae6..1d4e0b17 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -6,6 +6,7 @@ using Microsoft.Xna.Framework; using StardewModdingAPI; using StardewModdingAPI.Events; using StardewValley; +using StardewValley.Locations; using StardewValley.Menus; using StardewValley.Objects; using StardewValley.Tools; @@ -655,18 +656,21 @@ namespace TrainerMod break; case "world_downminelevel": - Game1.nextMineLevel(); - this.Monitor.Log("OK, warping you to the next mine level.", LogLevel.Info); - break; + { + int level = (Game1.currentLocation as MineShaft)?.mineLevel ?? 0; + this.Monitor.Log($"OK, warping you to mine level {level + 1}.", LogLevel.Info); + Game1.enterMine(false, level + 1, ""); + break; + } case "world_setminelevel": if (args.Any()) { if (args[0].IsInt()) { - int level = args[0].ToInt(); - Game1.enterMine(true, level, ""); + int level = Math.Max(1, args[0].ToInt()); this.Monitor.Log($"OK, warping you to mine level {level}.", LogLevel.Info); + Game1.enterMine(true, level, ""); } else this.LogArgumentNotInt(command); -- cgit From c23f70d602eacbed405a39797e40ee9e89537cf4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Mar 2017 20:09:51 -0500 Subject: update content manager code for SDV 1.2.11 (#173) --- src/StardewModdingAPI/Framework/SGame.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index aab44b59..675dc0c2 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -285,17 +285,18 @@ namespace StardewModdingAPI.Framework GameEvents.InvokeInitialize(this.Monitor); } + /// Constructor a content manager to read XNB files. + /// The service provider to use to locate services. + /// The root directory to search for content. + protected override LocalizedContentManager CreateContentManager(IServiceProvider serviceProvider, string rootDirectory) + { + return new SContentManager(this.Content.ServiceProvider, this.Content.RootDirectory, this.Monitor); + } + /// The method called before XNA or MonoGame loads or reloads graphics resources. protected override void LoadContent() { - // override content manager - LocalizedContentManager contentManager = Game1.content; - Game1.content = new SContentManager(this.Content.ServiceProvider, this.Content.RootDirectory, this.Monitor); - - // defer to game logic base.LoadContent(); - - // raise load content event GameEvents.InvokeLoadContent(this.Monitor); } -- cgit From ade1a692a38efe12cdb1889883ec6b1179a9df6b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Mar 2017 20:20:58 -0500 Subject: deprecate `IConfigFile` (#238) --- release-notes.md | 3 ++- src/StardewModdingAPI/Advanced/ConfigFile.cs | 4 +++- src/StardewModdingAPI/Advanced/IConfigFile.cs | 5 ++++- src/StardewModdingAPI/Framework/ModHelper.cs | 15 ++++++++++++++- src/StardewModdingAPI/Program.cs | 1 + 5 files changed, 24 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 7a936694..4f62182e 100644 --- a/release-notes.md +++ b/release-notes.md @@ -23,11 +23,12 @@ For mod developers: * Added `SaveEvents.AfterReturnToTitle` and `TimeEvents.AfterDayStarted` events. * Added a simpler API for console commands (see `helper.ConsoleCommands`). * Added `GetPrivateProperty` reflection helper. -* SMAPI now writes XNA input enums (`Buttons` and `Keys`) to JSON as strings, so mods no longer need to add a `StringEnumConverter` themselves for those. +* SMAPI now writes XNA input enums (`Buttons` and `Keys`) to JSON as strings automatically, so mods no longer need to add a `StringEnumConverter` themselves for those. * Logs now show the OS caption (like "Windows 10") instead of its internal version when available. * Logs now always use `\r\n` to simplify crossplatform viewing. * Several obsolete APIs have been removed (see [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod)), and all _notice_-level deprecations have been increased to _info_. +* Deprecated the experimental `IConfigFile`. For SMAPI developers: * Added support for debugging with Visual Studio for Mac. diff --git a/src/StardewModdingAPI/Advanced/ConfigFile.cs b/src/StardewModdingAPI/Advanced/ConfigFile.cs index 1a2e6618..78cad26a 100644 --- a/src/StardewModdingAPI/Advanced/ConfigFile.cs +++ b/src/StardewModdingAPI/Advanced/ConfigFile.cs @@ -1,9 +1,11 @@ -using System.IO; +using System; +using System.IO; using Newtonsoft.Json; namespace StardewModdingAPI.Advanced { /// Wraps a configuration file with IO methods for convenience. + [Obsolete] public abstract class ConfigFile : IConfigFile { /********* diff --git a/src/StardewModdingAPI/Advanced/IConfigFile.cs b/src/StardewModdingAPI/Advanced/IConfigFile.cs index 5bc31a88..1b424ace 100644 --- a/src/StardewModdingAPI/Advanced/IConfigFile.cs +++ b/src/StardewModdingAPI/Advanced/IConfigFile.cs @@ -1,6 +1,9 @@ -namespace StardewModdingAPI.Advanced +using System; + +namespace StardewModdingAPI.Advanced { /// Wraps a configuration file with IO methods for convenience. + [Obsolete] public interface IConfigFile { /********* diff --git a/src/StardewModdingAPI/Framework/ModHelper.cs b/src/StardewModdingAPI/Framework/ModHelper.cs index 04767de5..0d50201b 100644 --- a/src/StardewModdingAPI/Framework/ModHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelper.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using StardewModdingAPI.Advanced; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Serialisation; @@ -14,6 +15,9 @@ namespace StardewModdingAPI.Framework /// Encapsulates SMAPI's JSON file parsing. private readonly JsonHelper JsonHelper; + /// Manages deprecation warnings. + private static DeprecationManager DeprecationManager; + /********* ** Accessors @@ -61,6 +65,13 @@ namespace StardewModdingAPI.Framework this.ConsoleCommands = new CommandHelper(modName, commandManager); } + /// Injects types required for backwards compatibility. + /// Manages deprecation warnings. + internal static void Shim(DeprecationManager deprecationManager) + { + ModHelper.DeprecationManager = deprecationManager; + } + /**** ** Mod config file ****/ @@ -69,7 +80,9 @@ namespace StardewModdingAPI.Framework public TConfig ReadConfig() where TConfig : class, new() { - var config = this.ReadJsonFile("config.json") ?? new TConfig(); + TConfig config = this.ReadJsonFile("config.json") ?? new TConfig(); + if (config is IConfigFile) + ModHelper.DeprecationManager.Warn($"{nameof(IConfigFile)}", "1.9", DeprecationLevel.Info); this.WriteConfig(config); // create file or fill in missing fields return config; } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 360f75f0..6ebeccd2 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -98,6 +98,7 @@ namespace StardewModdingAPI InternalExtensions.Shim(this.ModRegistry); Log.Shim(this.DeprecationManager, this.GetSecondaryMonitor("legacy mod"), this.ModRegistry); Mod.Shim(this.DeprecationManager); + ModHelper.Shim(this.DeprecationManager); ContentEvents.Shim(this.ModRegistry, this.Monitor); PlayerEvents.Shim(this.DeprecationManager); TimeEvents.Shim(this.DeprecationManager); -- cgit From b7180c47d404568a95bbc3de989f6109857fd54d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Mar 2017 21:06:59 -0500 Subject: bump minimum game version --- src/StardewModdingAPI/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index be44c664..66bf5842 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -33,7 +33,7 @@ namespace StardewModdingAPI public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 8, 0); /// The minimum supported version of Stardew Valley. - public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.9"); + public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.13"); /// The path to the game folder. public static string ExecutionPath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); -- cgit From 1485d98b24c7518b102102ef9790d88382c9863b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Mar 2017 21:30:24 -0500 Subject: update draw code for SDV 1.2.13 (#231) --- src/StardewModdingAPI/Framework/SGame.cs | 1059 +++++++++++++++--------------- 1 file changed, 529 insertions(+), 530 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 675dc0c2..2e54544b 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -65,6 +65,7 @@ namespace StardewModdingAPI.Framework 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 @@ -399,623 +400,621 @@ namespace StardewModdingAPI.Framework this.GraphicsDevice.Clear(this.bgColor); //base.Draw(gameTime); } - else if (this.IsSaving) - { - this.GraphicsDevice.Clear(this.bgColor); - IClickableMenu activeClickableMenu = Game1.activeClickableMenu; - if (activeClickableMenu != null) - { - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - try - { - GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor); - activeClickableMenu.draw(Game1.spriteBatch); - GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); - } - catch (Exception ex) - { - this.Monitor.Log($"The {activeClickableMenu.GetType().FullName} menu crashed while drawing itself during save. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); - activeClickableMenu.exitThisMenu(); - } - Game1.spriteBatch.End(); - } - //base.Draw(gameTime); - } else { if ((double)Game1.options.zoomLevel != 1.0) this.GraphicsDevice.SetRenderTarget(this.screenWrapper); - this.GraphicsDevice.Clear(this.bgColor); - if (Game1.activeClickableMenu != null && Game1.options.showMenuBackground && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet()) + if (this.IsSaving) { - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - try - { - Game1.activeClickableMenu.drawBackground(Game1.spriteBatch); - GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor); - Game1.activeClickableMenu.draw(Game1.spriteBatch); - GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); - } - catch (Exception ex) - { - this.Monitor.Log($"The {Game1.activeClickableMenu.GetType().FullName} menu crashed while drawing itself. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); - Game1.activeClickableMenu.exitThisMenu(); - } - Game1.spriteBatch.End(); - if ((double)Game1.options.zoomLevel != 1.0) - { - this.GraphicsDevice.SetRenderTarget((RenderTarget2D)null); - this.GraphicsDevice.Clear(this.bgColor); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw((Texture2D)this.screenWrapper, Vector2.Zero, new Microsoft.Xna.Framework.Rectangle?(this.screenWrapper.Bounds), Color.White, 0.0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); - Game1.spriteBatch.End(); - } - if (Game1.overlayMenu == null) - return; - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - Game1.overlayMenu.draw(Game1.spriteBatch); - Game1.spriteBatch.End(); - } - else if ((int)Game1.gameMode == 11) - { - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3685"), new Vector2(16f, 16f), Color.HotPink); - Game1.spriteBatch.DrawString(Game1.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(); - } - else if (Game1.currentMinigame != null) - { - Game1.currentMinigame.draw(Game1.spriteBatch); - if (Game1.globalFade && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause)) + this.GraphicsDevice.Clear(this.bgColor); + IClickableMenu activeClickableMenu = Game1.activeClickableMenu; + if (activeClickableMenu != null) { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - 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); + try + { + GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor); + activeClickableMenu.draw(Game1.spriteBatch); + GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); + } + catch (Exception ex) + { + this.Monitor.Log($"The {activeClickableMenu.GetType().FullName} menu crashed while drawing itself during save. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); + activeClickableMenu.exitThisMenu(); + } Game1.spriteBatch.End(); } - 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(); + //base.Draw(gameTime); + this.renderScreenBuffer(); } - else if (Game1.showingEndOfNightStuff) + else { - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - if (Game1.activeClickableMenu != null) + this.GraphicsDevice.Clear(this.bgColor); + if (Game1.activeClickableMenu != null && Game1.options.showMenuBackground && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet()) { + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); try { + Game1.activeClickableMenu.drawBackground(Game1.spriteBatch); GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor); Game1.activeClickableMenu.draw(Game1.spriteBatch); GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); } catch (Exception ex) { - this.Monitor.Log($"The {Game1.activeClickableMenu.GetType().FullName} menu crashed while drawing itself during end-of-night-stuff. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"The {Game1.activeClickableMenu.GetType().FullName} menu crashed while drawing itself. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); Game1.activeClickableMenu.exitThisMenu(); } - } - Game1.spriteBatch.End(); - if ((double)Game1.options.zoomLevel != 1.0) - { - this.GraphicsDevice.SetRenderTarget((RenderTarget2D)null); - this.GraphicsDevice.Clear(this.bgColor); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone); - Game1.spriteBatch.Draw((Texture2D)this.screenWrapper, Vector2.Zero, new Microsoft.Xna.Framework.Rectangle?(this.screenWrapper.Bounds), Color.White, 0.0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f); + Game1.spriteBatch.End(); + if ((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(); } - 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.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) + else if ((int)Game1.gameMode == 11) { - 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.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(); } - 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 - { - Microsoft.Xna.Framework.Rectangle rectangle; - if ((int)Game1.gameMode == 0) + else if (Game1.currentMinigame != null) { + Game1.currentMinigame.draw(Game1.spriteBatch); + if (Game1.globalFade && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause)) + { + 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(); } - else + else if (Game1.showingEndOfNightStuff) { - if (Game1.drawLighting) + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); + if (Game1.activeClickableMenu != null) { - 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) + try { - if (Utility.isOnScreen(Game1.currentLightSources.ElementAt(index).position, (int)((double)Game1.currentLightSources.ElementAt(index).radius * (double)Game1.tileSize * 4.0))) - Game1.spriteBatch.Draw(Game1.currentLightSources.ElementAt(index).lightTexture, Game1.GlobalToLocal(Game1.viewport, Game1.currentLightSources.ElementAt(index).position) / (float)(Game1.options.lightingQuality / 2), new Microsoft.Xna.Framework.Rectangle?(Game1.currentLightSources.ElementAt(index).lightTexture.Bounds), Game1.currentLightSources.ElementAt(index).color, 0.0f, new Vector2((float)Game1.currentLightSources.ElementAt(index).lightTexture.Bounds.Center.X, (float)Game1.currentLightSources.ElementAt(index).lightTexture.Bounds.Center.Y), Game1.currentLightSources.ElementAt(index).radius / (float)(Game1.options.lightingQuality / 2), SpriteEffects.None, 0.9f); + GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor); + Game1.activeClickableMenu.draw(Game1.spriteBatch); + GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); } - 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) + catch (Exception ex) { - 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); + 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(); } } - else + Game1.spriteBatch.End(); + if ((double)Game1.options.zoomLevel != 1.0) { - 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); - } + 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(); } - Microsoft.Xna.Framework.Rectangle bounds; - if (Game1.displayFarmer && !Game1.player.swimming && (!Game1.player.isRidingHorse() && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(Game1.player.getTileLocation()))) + 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.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) { - 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); + 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(); } - Game1.currentLocation.Map.GetLayer("Buildings").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, Game1.pixelZoom); - Game1.mapDisplayDevice.EndScene(); + 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.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - if (Game1.CurrentEvent == null) + } + 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 { - foreach (NPC character in Game1.currentLocation.characters) + if (Game1.drawLighting) { - if (!character.swimming && !character.hideShadow && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(character.getTileLocation())) + 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) { - 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); + if (Utility.isOnScreen(Game1.currentLightSources.ElementAt(index).position, (int)((double)Game1.currentLightSources.ElementAt(index).radius * (double)Game1.tileSize * 4.0))) + Game1.spriteBatch.Draw(Game1.currentLightSources.ElementAt(index).lightTexture, Game1.GlobalToLocal(Game1.viewport, Game1.currentLightSources.ElementAt(index).position) / (float)(Game1.options.lightingQuality / 2), new Microsoft.Xna.Framework.Rectangle?(Game1.currentLightSources.ElementAt(index).lightTexture.Bounds), Game1.currentLightSources.ElementAt(index).color, 0.0f, new Vector2((float)Game1.currentLightSources.ElementAt(index).lightTexture.Bounds.Center.X, (float)Game1.currentLightSources.ElementAt(index).lightTexture.Bounds.Center.Y), Game1.currentLightSources.ElementAt(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); } - } - else - { - foreach (NPC actor in Game1.CurrentEvent.actors) + 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) { - if (!actor.swimming && !actor.hideShadow && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(actor.getTileLocation())) + foreach (NPC character in Game1.currentLocation.characters) { - 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 (!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); } } - } - 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")) + else { - 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) + foreach (NPC actor in Game1.CurrentEvent.actors) { - 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_128; + 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); } - else - goto label_128; } - Game1.drawPlayerHeldObject(Game1.player); - } - label_128: - 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) - { + 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("AlwaysFront").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, Game1.pixelZoom); + Game1.currentLocation.Map.GetLayer("Front").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) + 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) { - 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.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); } - 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 (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 (actor.isEmoting) + 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")) { - 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); + 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(); - 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) + //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.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(x2, (int)num1, 1, Game1.graphics.GraphicsDevice.Viewport.Height), Color.Red * 0.5f); - x2 += Game1.tileSize; + 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(); } - float num2 = num1; - while ((double)num2 < (double)Game1.graphics.GraphicsDevice.Viewport.Height) + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); + if (Game1.drawGrid) { - 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; + 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.currentBillboard != 0) - this.drawBillboard(); - if ((Game1.displayHUD || Game1.eventUp) && (Game1.currentBillboard == 0 && (int)Game1.gameMode == 3) && (!Game1.freezeControls && !Game1.panMode)) + 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) { - GraphicsEvents.InvokeOnPreRenderHudEvent(this.Monitor); - this.drawHUD(); - GraphicsEvents.InvokeOnPostRenderHudEvent(this.Monitor); + 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); } - 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())) + 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))) { - for (int i = Game1.hudMessages.Count - 1; i >= 0; --i) - Game1.hudMessages[i].draw(Game1.spriteBatch, i); + 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.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) + 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.White * Math.Min(1f, Game1.flashAlpha); + Color color = Color.Black * ((int)Game1.gameMode == 0 ? 1f - Game1.fadeToBlackAlpha : Game1.fadeToBlackAlpha); 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 + else if ((double)Game1.flashAlpha > 0.0) { - GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor); - Game1.activeClickableMenu.draw(Game1.spriteBatch); - GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); + 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; } - catch (Exception ex) + 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) { - 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(); + 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); } - } - 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); + 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 (GraphicsEvents.HasPostRenderListeners()) - { - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); - GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor); - Game1.spriteBatch.End(); - } + if (GraphicsEvents.HasPostRenderListeners()) + { + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); + GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor); + Game1.spriteBatch.End(); + } - if ((double)Game1.options.zoomLevel == 1.0) - return; - this.GraphicsDevice.SetRenderTarget((RenderTarget2D)null); - this.GraphicsDevice.Clear(this.bgColor); - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, 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(); + this.renderScreenBuffer(); + } } } } -- cgit From 6f07801b047a4574445266de582b23cef815fe0e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 2 Mar 2017 22:03:23 -0500 Subject: only use WMI on Windows --- src/StardewModdingAPI/Program.cs | 20 +++++++++++--------- src/StardewModdingAPI/StardewModdingAPI.csproj | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 6ebeccd2..3e428eb0 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -4,10 +4,10 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; -using System.Management; using System.Reflection; using System.Threading; #if SMAPI_FOR_WINDOWS +using System.Management; using System.Windows.Forms; #endif using Microsoft.Xna.Framework.Graphics; @@ -586,19 +586,21 @@ namespace StardewModdingAPI } /// Get a human-readable name for the current platform. + [SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")] private string GetFriendlyPlatformName() { +#if SMAPI_FOR_WINDOWS try { - return ( - from entry in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().Cast() - select entry.GetPropertyValue("Caption").ToString() - ).FirstOrDefault(); - } - catch - { - return Environment.OSVersion.ToString(); + return new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem") + .Get() + .Cast() + .Select(entry => entry.GetPropertyValue("Caption").ToString()) + .FirstOrDefault(); } + catch { } +#endif + return Environment.OSVersion.ToString(); } } } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 2340a431..4d782f56 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -97,7 +97,7 @@ - + True -- cgit From 5d32b9802942e70c2d51d0edc376d92ac35b1032 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 3 Mar 2017 17:13:33 -0500 Subject: fix mods running code concurrently with a SDV 1.2+ background task --- src/StardewModdingAPI/Framework/SGame.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 2e54544b..aa22d572 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -305,6 +305,22 @@ namespace StardewModdingAPI.Framework /// A snapshot of the game timing state. 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.InvokeGameLoaded(this.Monitor); @@ -361,7 +377,7 @@ namespace StardewModdingAPI.Framework /// The method called to draw everything to the screen. /// A snapshot of the game timing state. - /// This implementation is identical to , except for try..catch around menu draw code, minor formatting, and added events. + /// This implementation is identical to , except for try..catch around menu draw code, private field references replaced by wrappers, and added events. [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "LocalVariableHidesMember", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "PossibleLossOfFraction", Justification = "copied from game code as-is")] -- cgit From 043508ed4264a3024b281665bfbf6e073c029fdf Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 3 Mar 2017 20:22:30 -0500 Subject: add texture patching to content events (#173) --- .../Framework/ContentEventHelper.cs | 51 ++++++++++++++++++++++ src/StardewModdingAPI/IContentEventHelper.cs | 12 +++++ src/StardewModdingAPI/PatchMode.cs | 12 +++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 4 files changed, 76 insertions(+) create mode 100644 src/StardewModdingAPI/PatchMode.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/ContentEventHelper.cs b/src/StardewModdingAPI/Framework/ContentEventHelper.cs index a58efe32..ebd04692 100644 --- a/src/StardewModdingAPI/Framework/ContentEventHelper.cs +++ b/src/StardewModdingAPI/Framework/ContentEventHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace StardewModdingAPI.Framework @@ -85,6 +86,56 @@ namespace StardewModdingAPI.Framework data[key] = value(data[key]); } + /// Overwrite part of the image. + /// The image to patch into the content. + /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. + /// The part of the content to patch (or null to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet. + /// Indicates how an image should be patched. + /// One of the arguments is null. + /// The is outside the bounds of the spritesheet. + /// The content being read isn't an image. + public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace) + { + // get texture + Texture2D target = this.GetData(); + + // get areas + sourceArea = sourceArea ?? new Rectangle(0, 0, source.Width, source.Height); + targetArea = targetArea ?? new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height)); + + // validate + if (source == null) + throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); + if (sourceArea.Value.X < 0 || sourceArea.Value.Y < 0 || sourceArea.Value.Right > source.Width || sourceArea.Value.Bottom > source.Height) + throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); + if (targetArea.Value.X < 0 || targetArea.Value.Y < 0 || targetArea.Value.Right > target.Width || targetArea.Value.Bottom > target.Height) + throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture."); + if (sourceArea.Value.Width != targetArea.Value.Width || sourceArea.Value.Height != targetArea.Value.Height) + throw new InvalidOperationException("The source and target areas must be the same size."); + + // get source data + int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height; + Color[] sourceData = new Color[pixelCount]; + source.GetData(0, sourceArea, sourceData, 0, pixelCount); + + // merge data in overlay mode + if (patchMode == PatchMode.Overlay) + { + Color[] newData = new Color[targetArea.Value.Width * targetArea.Value.Height]; + target.GetData(0, targetArea, newData, 0, newData.Length); + for (int i = 0; i < sourceData.Length; i++) + { + Color pixel = sourceData[i]; + if (pixel.A != 0) // not transparent + newData[i] = pixel; + } + sourceData = newData; + } + + // patch target texture + target.SetData(0, targetArea, sourceData, 0, pixelCount); + } + /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. /// The new content value. /// The is null. diff --git a/src/StardewModdingAPI/IContentEventHelper.cs b/src/StardewModdingAPI/IContentEventHelper.cs index be8290eb..7b8fd3bc 100644 --- a/src/StardewModdingAPI/IContentEventHelper.cs +++ b/src/StardewModdingAPI/IContentEventHelper.cs @@ -1,4 +1,6 @@ using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; namespace StardewModdingAPI { @@ -46,6 +48,16 @@ namespace StardewModdingAPI /// The content being read isn't a dictionary. void SetDictionaryEntry(TKey key, Func value); + /// Overwrite part of the image. + /// The image to patch into the content. + /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. + /// The part of the content to patch (or null to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet. + /// Indicates how an image should be patched. + /// One of the arguments is null. + /// The is outside the bounds of the spritesheet. + /// The content being read isn't an image. + void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace); + /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. /// The new content value. /// The is null. diff --git a/src/StardewModdingAPI/PatchMode.cs b/src/StardewModdingAPI/PatchMode.cs new file mode 100644 index 00000000..b4286a89 --- /dev/null +++ b/src/StardewModdingAPI/PatchMode.cs @@ -0,0 +1,12 @@ +namespace StardewModdingAPI +{ + /// Indicates how an image should be patched. + public enum PatchMode + { + /// Erase the original content within the area before drawing the new content. + Replace, + + /// Draw the new content over the original content, so the original content shows through any transparent pixels. + Overlay + } +} diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 4d782f56..40f964b9 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -196,6 +196,7 @@ + -- cgit From 4991a25d4633e146ead43872277d67685cb2e23b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 3 Mar 2017 20:57:52 -0500 Subject: add content language changed event (#243) --- release-notes.md | 7 ++--- src/StardewModdingAPI/Events/ContentEvents.cs | 12 +++++++++ .../Events/EventArgsIntChanged.cs | 5 ++-- .../Events/EventArgsValueChanged.cs | 31 ++++++++++++++++++++++ src/StardewModdingAPI/Framework/SGame.cs | 14 ++++++++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 6 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 src/StardewModdingAPI/Events/EventArgsValueChanged.cs (limited to 'src') diff --git a/release-notes.md b/release-notes.md index fde9316f..22df8f70 100644 --- a/release-notes.md +++ b/release-notes.md @@ -5,8 +5,9 @@ See [log](https://github.com/Pathoschild/SMAPI/compare/1.9...2.0). For mod developers: -* Added content events and an API which let you intercept XNB content as it's loaded, then - dynamically replace data entries or patch images. +* 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.9 @@ -29,7 +30,7 @@ For players: * Fixed errors in console command handlers causing the game to crash. For mod developers: -* Added `SaveEvents.AfterReturnToTitle` and `TimeEvents.AfterDayStarted` events. +* Added `SaveEvents.AfterReturnToTitle`, `TimeEvents.AfterDayStarted`, and `ContentEvents.AfterLocaleChanged` events. * Added a simpler API for console commands (see `helper.ConsoleCommands`). * Added `GetPrivateProperty` reflection helper. * SMAPI now writes XNA input enums (`Buttons` and `Keys`) to JSON as strings automatically, so mods no longer need to add a `StringEnumConverter` themselves for those. diff --git a/src/StardewModdingAPI/Events/ContentEvents.cs b/src/StardewModdingAPI/Events/ContentEvents.cs index cc07f242..558fc070 100644 --- a/src/StardewModdingAPI/Events/ContentEvents.cs +++ b/src/StardewModdingAPI/Events/ContentEvents.cs @@ -24,6 +24,9 @@ namespace StardewModdingAPI.Events /********* ** Events *********/ + /// Raised after the content language changes. + public static event EventHandler> AfterLocaleChanged; + /// Raised when an XNB file is being read into the cache. Mods can change the data here before it's cached. public static event EventHandler AssetLoading; @@ -40,6 +43,15 @@ namespace StardewModdingAPI.Events ContentEvents.Monitor = monitor; } + /// Raise an event. + /// Encapsulates monitoring and logging. + /// The previous locale. + /// The current locale. + internal static void InvokeAfterLocaleChanged(IMonitor monitor, string oldLocale, string newLocale) + { + monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AfterLocaleChanged)}", ContentEvents.AfterLocaleChanged?.GetInvocationList(), null, new EventArgsValueChanged(oldLocale, newLocale)); + } + /// Raise an event. /// Encapsulates monitoring and logging. /// Encapsulates access and changes to content being read from a data file. diff --git a/src/StardewModdingAPI/Events/EventArgsIntChanged.cs b/src/StardewModdingAPI/Events/EventArgsIntChanged.cs index 31079730..0c742d12 100644 --- a/src/StardewModdingAPI/Events/EventArgsIntChanged.cs +++ b/src/StardewModdingAPI/Events/EventArgsIntChanged.cs @@ -9,11 +9,10 @@ namespace StardewModdingAPI.Events ** Accessors *********/ /// The previous value. - public int NewInt { get; } - - /// The current value. public int PriorInt { get; } + /// The current value. + public int NewInt { get; } /********* ** Public methods diff --git a/src/StardewModdingAPI/Events/EventArgsValueChanged.cs b/src/StardewModdingAPI/Events/EventArgsValueChanged.cs new file mode 100644 index 00000000..1d25af49 --- /dev/null +++ b/src/StardewModdingAPI/Events/EventArgsValueChanged.cs @@ -0,0 +1,31 @@ +using System; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a field that changed value. + /// The value type. + public class EventArgsValueChanged : EventArgs + { + /********* + ** Accessors + *********/ + /// The previous value. + public T PriorValue { get; } + + /// The current value. + public T NewValue { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The previous value. + /// The current value. + public EventArgsValueChanged(T priorValue, T newValue) + { + this.PriorValue = priorValue; + this.NewValue = newValue; + } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index aa22d572..5edb103e 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -159,6 +159,9 @@ namespace StardewModdingAPI.Framework /// The player character at last check. public SFarmer PreviousFarmer { get; private set; } + /// The previous content locale. + public LocalizedContentManager.LanguageCode? PreviousLocale { get; private set; } + /// An index incremented on every tick and reset every 60th tick (0–59). public int CurrentUpdateTick { get; private set; } @@ -1079,6 +1082,17 @@ namespace StardewModdingAPI.Framework /// Detect changes since the last update ticket and trigger mod events. 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 (Game1.hasLoadedGame && !SaveGame.IsProcessing/*still loading save*/ && this.AfterLoadTimer >= 0) { diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 40f964b9..727da30f 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -119,6 +119,7 @@ + -- cgit From dfaed472b0e7f1e35ad07eea427fe5034b40ddc7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 4 Mar 2017 14:43:23 -0500 Subject: fix game window no longer showing SMAPI version --- src/StardewModdingAPI/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 3e428eb0..95161538 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -234,7 +234,7 @@ namespace StardewModdingAPI 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}"; + this.GameInstance.Window.Title = $"Stardew Valley {Constants.GameVersion} with SMAPI {Constants.ApiVersion}"; gameProgramType.GetField("gamePtr").SetValue(gameProgramType, this.GameInstance); // configure -- cgit From b2e88bccf63545d950f4cbf47a0466321c614245 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Mar 2017 15:25:30 -0500 Subject: add dictionary/image content helpers for more intuitive usage (#173) --- .../Framework/Content/ContentEventBaseHelper.cs | 98 +++++++++++ .../Framework/Content/ContentEventHelper.cs | 47 ++++++ .../Content/ContentEventHelperForDictionary.cs | 36 ++++ .../Content/ContentEventHelperForImage.cs | 70 ++++++++ .../Framework/ContentEventHelper.cs | 182 --------------------- src/StardewModdingAPI/Framework/SContentManager.cs | 1 + src/StardewModdingAPI/IContentEventHelper.cs | 38 ++--- .../IContentEventHelperForDictionary.cs | 44 +++++ .../IContentEventHelperForImage.cs | 45 +++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 7 +- 10 files changed, 357 insertions(+), 211 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs create mode 100644 src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs create mode 100644 src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs create mode 100644 src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs delete mode 100644 src/StardewModdingAPI/Framework/ContentEventHelper.cs create mode 100644 src/StardewModdingAPI/IContentEventHelperForDictionary.cs create mode 100644 src/StardewModdingAPI/IContentEventHelperForImage.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs b/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs new file mode 100644 index 00000000..736cdc70 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework.Graphics; + +namespace StardewModdingAPI.Framework.Content +{ + /// Base implementation for a content helper which encapsulates access and changes to content being read from a data file. + /// The interface value type. + internal class ContentEventBaseHelper : EventArgs + { + /********* + ** Properties + *********/ + /// Normalises an asset key to match the cache key. + protected readonly Func GetNormalisedPath; + + + /********* + ** Accessors + *********/ + /// The content's locale code, if the content is localised. + public string Locale { get; } + + /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. + public string AssetName { get; } + + /// The content data being read. + public TValue Data { get; protected set; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The content's locale code, if the content is localised. + /// The normalised asset name being read. + /// The content data being read. + /// Normalises an asset key to match the cache key. + public ContentEventBaseHelper(string locale, string assetName, TValue data, Func getNormalisedPath) + { + this.Locale = locale; + this.AssetName = assetName; + this.Data = data; + this.GetNormalisedPath = getNormalisedPath; + } + + /// Get whether the asset name being loaded matches a given name after normalisation. + /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). + public bool IsAssetName(string path) + { + path = this.GetNormalisedPath(path); + return this.AssetName.Equals(path, StringComparison.InvariantCultureIgnoreCase); + } + + /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. + /// The new content value. + /// The is null. + /// The 's type is not compatible with the loaded asset's type. + public void ReplaceWith(TValue value) + { + if (value == null) + throw new ArgumentNullException(nameof(value), "Can't set a loaded asset to a null value."); + if (!this.Data.GetType().IsInstanceOfType(value)) + throw new InvalidCastException($"Can't replace loaded asset of type {this.GetFriendlyTypeName(this.Data.GetType())} with value of type {this.GetFriendlyTypeName(value.GetType())}. The new type must be compatible to prevent game errors."); + + this.Data = value; + } + + + /********* + ** Protected methods + *********/ + /// Get a human-readable type name. + /// The type to name. + protected string GetFriendlyTypeName(Type type) + { + // dictionary + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + Type[] genericArgs = type.GetGenericArguments(); + return $"Dictionary<{this.GetFriendlyTypeName(genericArgs[0])}, {this.GetFriendlyTypeName(genericArgs[1])}>"; + } + + // texture + if (type == typeof(Texture2D)) + return type.Name; + + // native type + if (type == typeof(int)) + return "int"; + if (type == typeof(string)) + return "string"; + + // default + return type.FullName; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs new file mode 100644 index 00000000..26292cab --- /dev/null +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework.Graphics; + +namespace StardewModdingAPI.Framework.Content +{ + /// Encapsulates access and changes to content being read from a data file. + internal class ContentEventHelper : ContentEventBaseHelper, IContentEventHelper + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The content's locale code, if the content is localised. + /// The normalised asset name being read. + /// The content data being read. + /// Normalises an asset key to match the cache key. + public ContentEventHelper(string locale, string assetName, object data, Func getNormalisedPath) + : base(locale, assetName, data, getNormalisedPath) { } + + /// Get a helper to manipulate the data as a dictionary. + /// The expected dictionary key. + /// The expected dictionary balue. + /// The content being read isn't a dictionary. + public IContentEventHelperForDictionary AsDictionary() + { + return new ContentEventHelperForDictionary(this.Locale, this.AssetName, this.GetData>(), this.GetNormalisedPath); + } + + /// Get a helper to manipulate the data as an image. + /// The content being read isn't an image. + public IContentEventHelperForImage AsImage() + { + return new ContentEventHelperForImage(this.Locale, this.AssetName, this.GetData(), this.GetNormalisedPath); + } + + /// Get the data as a given type. + /// The expected data type. + /// The data can't be converted to . + public TData GetData() + { + if (!(this.Data is TData)) + throw new InvalidCastException($"The content data of type {this.Data.GetType().FullName} can't be converted to the requested {typeof(TData).FullName}."); + return (TData)this.Data; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs new file mode 100644 index 00000000..8fcaae3c --- /dev/null +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace StardewModdingAPI.Framework.Content +{ + /// Encapsulates access and changes to dictionary content being read from a data file. + internal class ContentEventHelperForDictionary : ContentEventBaseHelper>, IContentEventHelperForDictionary + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The content's locale code, if the content is localised. + /// The normalised asset name being read. + /// The content data being read. + /// Normalises an asset key to match the cache key. + public ContentEventHelperForDictionary(string locale, string assetName, IDictionary data, Func getNormalisedPath) + : base(locale, assetName, data, getNormalisedPath) { } + + /// Add or replace an entry in the dictionary data. + /// The entry key. + /// The entry value. + public void SetEntry(TKey key, TValue value) + { + this.Data[key] = value; + } + + /// Add or replace an entry in the dictionary data. + /// The entry key. + /// A callback which accepts the current value and returns the new value. + public void SetEntry(TKey key, Func value) + { + this.Data[key] = value(this.Data[key]); + } + } +} diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs new file mode 100644 index 00000000..23760f0f --- /dev/null +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs @@ -0,0 +1,70 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace StardewModdingAPI.Framework.Content +{ + /// Encapsulates access and changes to dictionary content being read from a data file. + internal class ContentEventHelperForImage : ContentEventBaseHelper, IContentEventHelperForImage + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The content's locale code, if the content is localised. + /// The normalised asset name being read. + /// The content data being read. + /// Normalises an asset key to match the cache key. + public ContentEventHelperForImage(string locale, string assetName, Texture2D data, Func getNormalisedPath) + : base(locale, assetName, data, getNormalisedPath) { } + + /// Overwrite part of the image. + /// The image to patch into the content. + /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. + /// The part of the content to patch (or null to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet. + /// Indicates how an image should be patched. + /// One of the arguments is null. + /// The is outside the bounds of the spritesheet. + public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace) + { + // get texture + Texture2D target = this.Data; + + // get areas + sourceArea = sourceArea ?? new Rectangle(0, 0, source.Width, source.Height); + targetArea = targetArea ?? new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height)); + + // validate + if (source == null) + throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); + if (sourceArea.Value.X < 0 || sourceArea.Value.Y < 0 || sourceArea.Value.Right > source.Width || sourceArea.Value.Bottom > source.Height) + throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); + if (targetArea.Value.X < 0 || targetArea.Value.Y < 0 || targetArea.Value.Right > target.Width || targetArea.Value.Bottom > target.Height) + throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture."); + if (sourceArea.Value.Width != targetArea.Value.Width || sourceArea.Value.Height != targetArea.Value.Height) + throw new InvalidOperationException("The source and target areas must be the same size."); + + // get source data + int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height; + Color[] sourceData = new Color[pixelCount]; + source.GetData(0, sourceArea, sourceData, 0, pixelCount); + + // merge data in overlay mode + if (patchMode == PatchMode.Overlay) + { + Color[] newData = new Color[targetArea.Value.Width * targetArea.Value.Height]; + target.GetData(0, targetArea, newData, 0, newData.Length); + for (int i = 0; i < sourceData.Length; i++) + { + Color pixel = sourceData[i]; + if (pixel.A != 0) // not transparent + newData[i] = pixel; + } + sourceData = newData; + } + + // patch target texture + target.SetData(0, targetArea, sourceData, 0, pixelCount); + } + } +} diff --git a/src/StardewModdingAPI/Framework/ContentEventHelper.cs b/src/StardewModdingAPI/Framework/ContentEventHelper.cs deleted file mode 100644 index ebd04692..00000000 --- a/src/StardewModdingAPI/Framework/ContentEventHelper.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; - -namespace StardewModdingAPI.Framework -{ - /// Encapsulates access and changes to content being read from a data file. - internal class ContentEventHelper : EventArgs, IContentEventHelper - { - /********* - ** Properties - *********/ - /// Normalises an asset key to match the cache key. - private readonly Func GetNormalisedPath; - - - /********* - ** Accessors - *********/ - /// The content's locale code, if the content is localised. - public string Locale { get; } - - /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. - public string AssetName { get; } - - /// The content data being read. - public object Data { get; private set; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The content's locale code, if the content is localised. - /// The normalised asset name being read. - /// The content data being read. - /// Normalises an asset key to match the cache key. - public ContentEventHelper(string locale, string assetName, object data, Func getNormalisedPath) - { - this.Locale = locale; - this.AssetName = assetName; - this.Data = data; - this.GetNormalisedPath = getNormalisedPath; - } - - /// Get whether the asset name being loaded matches a given name after normalisation. - /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). - public bool IsAssetName(string path) - { - path = this.GetNormalisedPath(path); - return this.AssetName.Equals(path, StringComparison.InvariantCultureIgnoreCase); - } - - /// Get the data as a given type. - /// The expected data type. - /// The data can't be converted to . - public TData GetData() - { - if (!(this.Data is TData)) - throw new InvalidCastException($"The content data of type {this.Data.GetType().FullName} can't be converted to the requested {typeof(TData).FullName}."); - return (TData)this.Data; - } - - /// Add or replace an entry in the dictionary data. - /// The entry key type. - /// The entry value type. - /// The entry key. - /// The entry value. - /// The content being read isn't a dictionary. - public void SetDictionaryEntry(TKey key, TValue value) - { - IDictionary data = this.GetData>(); - data[key] = value; - } - - /// Add or replace an entry in the dictionary data. - /// The entry key type. - /// The entry value type. - /// The entry key. - /// A callback which accepts the current value and returns the new value. - /// The content being read isn't a dictionary. - public void SetDictionaryEntry(TKey key, Func value) - { - IDictionary data = this.GetData>(); - data[key] = value(data[key]); - } - - /// Overwrite part of the image. - /// The image to patch into the content. - /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. - /// The part of the content to patch (or null to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet. - /// Indicates how an image should be patched. - /// One of the arguments is null. - /// The is outside the bounds of the spritesheet. - /// The content being read isn't an image. - public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace) - { - // get texture - Texture2D target = this.GetData(); - - // get areas - sourceArea = sourceArea ?? new Rectangle(0, 0, source.Width, source.Height); - targetArea = targetArea ?? new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height)); - - // validate - if (source == null) - throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); - if (sourceArea.Value.X < 0 || sourceArea.Value.Y < 0 || sourceArea.Value.Right > source.Width || sourceArea.Value.Bottom > source.Height) - throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); - if (targetArea.Value.X < 0 || targetArea.Value.Y < 0 || targetArea.Value.Right > target.Width || targetArea.Value.Bottom > target.Height) - throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture."); - if (sourceArea.Value.Width != targetArea.Value.Width || sourceArea.Value.Height != targetArea.Value.Height) - throw new InvalidOperationException("The source and target areas must be the same size."); - - // get source data - int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height; - Color[] sourceData = new Color[pixelCount]; - source.GetData(0, sourceArea, sourceData, 0, pixelCount); - - // merge data in overlay mode - if (patchMode == PatchMode.Overlay) - { - Color[] newData = new Color[targetArea.Value.Width * targetArea.Value.Height]; - target.GetData(0, targetArea, newData, 0, newData.Length); - for (int i = 0; i < sourceData.Length; i++) - { - Color pixel = sourceData[i]; - if (pixel.A != 0) // not transparent - newData[i] = pixel; - } - sourceData = newData; - } - - // patch target texture - target.SetData(0, targetArea, sourceData, 0, pixelCount); - } - - /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. - /// The new content value. - /// The is null. - /// The 's type is not compatible with the loaded asset's type. - public void ReplaceWith(object value) - { - if (value == null) - throw new ArgumentNullException(nameof(value), "Can't set a loaded asset to a null value."); - if (!this.Data.GetType().IsInstanceOfType(value)) - throw new InvalidCastException($"Can't replace loaded asset of type {this.GetFriendlyTypeName(this.Data.GetType())} with value of type {this.GetFriendlyTypeName(value.GetType())}. The new type must be compatible to prevent game errors."); - - this.Data = value; - } - - - /********* - ** Private methods - *********/ - /// Get a human-readable type name. - /// The type to name. - private string GetFriendlyTypeName(Type type) - { - // dictionary - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) - { - Type[] genericArgs = type.GetGenericArguments(); - return $"Dictionary<{this.GetFriendlyTypeName(genericArgs[0])}, {this.GetFriendlyTypeName(genericArgs[1])}>"; - } - - // texture - if (type == typeof(Texture2D)) - return type.Name; - - // native type - if (type == typeof(int)) - return "int"; - if (type == typeof(string)) - return "string"; - - // default - return type.FullName; - } - } -} diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs index 65c330db..97472d53 100644 --- a/src/StardewModdingAPI/Framework/SContentManager.cs +++ b/src/StardewModdingAPI/Framework/SContentManager.cs @@ -7,6 +7,7 @@ using System.Threading; using Microsoft.Xna.Framework; using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.Events; +using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Reflection; using StardewValley; diff --git a/src/StardewModdingAPI/IContentEventHelper.cs b/src/StardewModdingAPI/IContentEventHelper.cs index 7b8fd3bc..341778b4 100644 --- a/src/StardewModdingAPI/IContentEventHelper.cs +++ b/src/StardewModdingAPI/IContentEventHelper.cs @@ -1,6 +1,4 @@ using System; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; namespace StardewModdingAPI { @@ -27,37 +25,21 @@ namespace StardewModdingAPI /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). bool IsAssetName(string path); + /// Get a helper to manipulate the data as a dictionary. + /// The expected dictionary key. + /// The expected dictionary balue. + /// The content being read isn't a dictionary. + IContentEventHelperForDictionary AsDictionary(); + + /// Get a helper to manipulate the data as an image. + /// The content being read isn't an image. + IContentEventHelperForImage AsImage(); + /// Get the data as a given type. /// The expected data type. /// The data can't be converted to . TData GetData(); - /// Add or replace an entry in the dictionary data. - /// The entry key type. - /// The entry value type. - /// The entry key. - /// The entry value. - /// The content being read isn't a dictionary. - void SetDictionaryEntry(TKey key, TValue value); - - /// Add or replace an entry in the dictionary data. - /// The entry key type. - /// The entry value type. - /// The entry key. - /// A callback which accepts the current value and returns the new value. - /// The content being read isn't a dictionary. - void SetDictionaryEntry(TKey key, Func value); - - /// Overwrite part of the image. - /// The image to patch into the content. - /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. - /// The part of the content to patch (or null to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet. - /// Indicates how an image should be patched. - /// One of the arguments is null. - /// The is outside the bounds of the spritesheet. - /// The content being read isn't an image. - void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace); - /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. /// The new content value. /// The is null. diff --git a/src/StardewModdingAPI/IContentEventHelperForDictionary.cs b/src/StardewModdingAPI/IContentEventHelperForDictionary.cs new file mode 100644 index 00000000..1f259920 --- /dev/null +++ b/src/StardewModdingAPI/IContentEventHelperForDictionary.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace StardewModdingAPI +{ + /// Encapsulates access and changes to dictionary content being read from a data file. + public interface IContentEventHelperForDictionary + { + /********* + ** Accessors + *********/ + /// The content's locale code, if the content is localised. + string Locale { get; } + + /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. + string AssetName { get; } + + /// The content data being read. + IDictionary Data { get; } + + + /********* + ** Public methods + *********/ + /// Get whether the asset name being loaded matches a given name after normalisation. + /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). + bool IsAssetName(string path); + + /// Add or replace an entry in the dictionary data. + /// The entry key. + /// The entry value. + void SetEntry(TKey key, TValue value); + + /// Add or replace an entry in the dictionary data. + /// The entry key. + /// A callback which accepts the current value and returns the new value. + void SetEntry(TKey key, Func value); + + /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. + /// The new content value. + /// The is null. + void ReplaceWith(IDictionary value); + } +} diff --git a/src/StardewModdingAPI/IContentEventHelperForImage.cs b/src/StardewModdingAPI/IContentEventHelperForImage.cs new file mode 100644 index 00000000..14bc23a4 --- /dev/null +++ b/src/StardewModdingAPI/IContentEventHelperForImage.cs @@ -0,0 +1,45 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace StardewModdingAPI +{ + /// Encapsulates access and changes to dictionary content being read from a data file. + public interface IContentEventHelperForImage + { + /********* + ** Accessors + *********/ + /// The content's locale code, if the content is localised. + string Locale { get; } + + /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. + string AssetName { get; } + + /// The content data being read. + Texture2D Data { get; } + + + /********* + ** Public methods + *********/ + /// Get whether the asset name being loaded matches a given name after normalisation. + /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). + bool IsAssetName(string path); + + /// Overwrite part of the image. + /// The image to patch into the content. + /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. + /// The part of the content to patch (or null to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet. + /// Indicates how an image should be patched. + /// One of the arguments is null. + /// The is outside the bounds of the spritesheet. + /// The content being read isn't an image. + void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace); + + /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. + /// The new content value. + /// The is null. + void ReplaceWith(Texture2D value); + } +} diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 727da30f..478d1af0 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -150,7 +150,10 @@ - + + + + @@ -163,6 +166,8 @@ + + -- cgit From 28c78e8f25a0d062d0dda49bc089ebebef8bfa20 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Mar 2017 15:30:27 -0500 Subject: add dict content helper method to replace values based on a lambda (#173) --- .../Framework/Content/ContentEventHelperForDictionary.cs | 9 +++++++++ src/StardewModdingAPI/IContentEventHelperForDictionary.cs | 4 ++++ 2 files changed, 13 insertions(+) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs index 8fcaae3c..9bfc16d8 100644 --- a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace StardewModdingAPI.Framework.Content { @@ -32,5 +33,13 @@ namespace StardewModdingAPI.Framework.Content { this.Data[key] = value(this.Data[key]); } + + /// Dynamically replace values in the dictionary. + /// A lambda which takes the current key and value for an entry, and returns the new value. + public void Replace(Func replacer) + { + foreach (var pair in this.Data.ToArray()) + this.Data[pair.Key] = replacer(pair.Key, pair.Value); + } } } diff --git a/src/StardewModdingAPI/IContentEventHelperForDictionary.cs b/src/StardewModdingAPI/IContentEventHelperForDictionary.cs index 1f259920..34796d5c 100644 --- a/src/StardewModdingAPI/IContentEventHelperForDictionary.cs +++ b/src/StardewModdingAPI/IContentEventHelperForDictionary.cs @@ -36,6 +36,10 @@ namespace StardewModdingAPI /// A callback which accepts the current value and returns the new value. void SetEntry(TKey key, Func value); + /// Dynamically replace values in the dictionary. + /// A lambda which takes the current key and value for an entry, and returns the new value. + void Replace(Func replacer); + /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. /// The new content value. /// The is null. -- cgit From d47cf433f39ddfa77c7903bca676f725052a2592 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Mar 2017 15:34:38 -0500 Subject: use consistent dict helper method naming (#173) --- .../Framework/Content/ContentEventHelperForDictionary.cs | 10 +++++----- src/StardewModdingAPI/IContentEventHelperForDictionary.cs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs index 9bfc16d8..8a8a4845 100644 --- a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs @@ -18,25 +18,25 @@ namespace StardewModdingAPI.Framework.Content public ContentEventHelperForDictionary(string locale, string assetName, IDictionary data, Func getNormalisedPath) : base(locale, assetName, data, getNormalisedPath) { } - /// Add or replace an entry in the dictionary data. + /// Add or replace an entry in the dictionary. /// The entry key. /// The entry value. - public void SetEntry(TKey key, TValue value) + public void Set(TKey key, TValue value) { this.Data[key] = value; } - /// Add or replace an entry in the dictionary data. + /// Add or replace an entry in the dictionary. /// The entry key. /// A callback which accepts the current value and returns the new value. - public void SetEntry(TKey key, Func value) + public void Set(TKey key, Func value) { this.Data[key] = value(this.Data[key]); } /// Dynamically replace values in the dictionary. /// A lambda which takes the current key and value for an entry, and returns the new value. - public void Replace(Func replacer) + public void Set(Func replacer) { foreach (var pair in this.Data.ToArray()) this.Data[pair.Key] = replacer(pair.Key, pair.Value); diff --git a/src/StardewModdingAPI/IContentEventHelperForDictionary.cs b/src/StardewModdingAPI/IContentEventHelperForDictionary.cs index 34796d5c..34e69d42 100644 --- a/src/StardewModdingAPI/IContentEventHelperForDictionary.cs +++ b/src/StardewModdingAPI/IContentEventHelperForDictionary.cs @@ -26,19 +26,19 @@ namespace StardewModdingAPI /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). bool IsAssetName(string path); - /// Add or replace an entry in the dictionary data. + /// Add or replace an entry in the dictionary. /// The entry key. /// The entry value. - void SetEntry(TKey key, TValue value); + void Set(TKey key, TValue value); - /// Add or replace an entry in the dictionary data. + /// Add or replace an entry in the dictionary. /// The entry key. /// A callback which accepts the current value and returns the new value. - void SetEntry(TKey key, Func value); + void Set(TKey key, Func value); /// Dynamically replace values in the dictionary. /// A lambda which takes the current key and value for an entry, and returns the new value. - void Replace(Func replacer); + void Set(Func replacer); /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. /// The new content value. -- cgit From edbbb7cff41ba0656bd17774a3d5d99dd321dd9b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Mar 2017 15:51:00 -0500 Subject: update old instructions about resetting config file --- src/StardewModdingAPI/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 95161538..bf3c86fb 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -116,10 +116,10 @@ namespace StardewModdingAPI 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 or deleting {Constants.ApiConfigPath}.", LogLevel.Warn); + 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 editing or deleting {Constants.ApiConfigPath}.", LogLevel.Warn); + 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); -- cgit From ff39e9b1716104e02c92dfd56bb919887873bd3d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Mar 2017 11:05:17 -0500 Subject: move generic content properties & methods into separate interface (#173) --- .../Framework/Content/ContentEventBaseHelper.cs | 2 +- src/StardewModdingAPI/IContentEventData.cs | 35 ++++++++++++++++++++++ src/StardewModdingAPI/IContentEventHelper.cs | 25 +--------------- .../IContentEventHelperForDictionary.cs | 24 +-------------- .../IContentEventHelperForImage.cs | 24 +-------------- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 6 files changed, 40 insertions(+), 71 deletions(-) create mode 100644 src/StardewModdingAPI/IContentEventData.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs b/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs index 736cdc70..a89fe337 100644 --- a/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs +++ b/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs @@ -6,7 +6,7 @@ namespace StardewModdingAPI.Framework.Content { /// Base implementation for a content helper which encapsulates access and changes to content being read from a data file. /// The interface value type. - internal class ContentEventBaseHelper : EventArgs + internal class ContentEventBaseHelper : EventArgs, IContentEventData { /********* ** Properties diff --git a/src/StardewModdingAPI/IContentEventData.cs b/src/StardewModdingAPI/IContentEventData.cs new file mode 100644 index 00000000..a21861d2 --- /dev/null +++ b/src/StardewModdingAPI/IContentEventData.cs @@ -0,0 +1,35 @@ +using System; + +namespace StardewModdingAPI +{ + /// Generic metadata and methods for a content asset being loaded. + /// The expected data type. + public interface IContentEventData + { + /********* + ** Accessors + *********/ + /// The content's locale code, if the content is localised. + string Locale { get; } + + /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. + string AssetName { get; } + + /// The content data being read. + TValue Data { get; } + + + /********* + ** Public methods + *********/ + /// Get whether the asset name being loaded matches a given name after normalisation. + /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). + bool IsAssetName(string path); + + /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. + /// The new content value. + /// The is null. + /// The 's type is not compatible with the loaded asset's type. + void ReplaceWith(TValue value); + } +} diff --git a/src/StardewModdingAPI/IContentEventHelper.cs b/src/StardewModdingAPI/IContentEventHelper.cs index 341778b4..421a1e06 100644 --- a/src/StardewModdingAPI/IContentEventHelper.cs +++ b/src/StardewModdingAPI/IContentEventHelper.cs @@ -3,28 +3,11 @@ namespace StardewModdingAPI { /// Encapsulates access and changes to content being read from a data file. - public interface IContentEventHelper + public interface IContentEventHelper : IContentEventData { - /********* - ** Accessors - *********/ - /// The content's locale code, if the content is localised. - string Locale { get; } - - /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. - string AssetName { get; } - - /// The content data being read. - object Data { get; } - - /********* ** Public methods *********/ - /// Get whether the asset name being loaded matches a given name after normalisation. - /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). - bool IsAssetName(string path); - /// Get a helper to manipulate the data as a dictionary. /// The expected dictionary key. /// The expected dictionary balue. @@ -39,11 +22,5 @@ namespace StardewModdingAPI /// The expected data type. /// The data can't be converted to . TData GetData(); - - /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. - /// The new content value. - /// The is null. - /// The 's type is not compatible with the loaded asset's type. - void ReplaceWith(object value); } } diff --git a/src/StardewModdingAPI/IContentEventHelperForDictionary.cs b/src/StardewModdingAPI/IContentEventHelperForDictionary.cs index 34e69d42..2f9d5a65 100644 --- a/src/StardewModdingAPI/IContentEventHelperForDictionary.cs +++ b/src/StardewModdingAPI/IContentEventHelperForDictionary.cs @@ -4,28 +4,11 @@ using System.Collections.Generic; namespace StardewModdingAPI { /// Encapsulates access and changes to dictionary content being read from a data file. - public interface IContentEventHelperForDictionary + public interface IContentEventHelperForDictionary : IContentEventData> { - /********* - ** Accessors - *********/ - /// The content's locale code, if the content is localised. - string Locale { get; } - - /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. - string AssetName { get; } - - /// The content data being read. - IDictionary Data { get; } - - /********* ** Public methods *********/ - /// Get whether the asset name being loaded matches a given name after normalisation. - /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). - bool IsAssetName(string path); - /// Add or replace an entry in the dictionary. /// The entry key. /// The entry value. @@ -39,10 +22,5 @@ namespace StardewModdingAPI /// Dynamically replace values in the dictionary. /// A lambda which takes the current key and value for an entry, and returns the new value. void Set(Func replacer); - - /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. - /// The new content value. - /// The is null. - void ReplaceWith(IDictionary value); } } diff --git a/src/StardewModdingAPI/IContentEventHelperForImage.cs b/src/StardewModdingAPI/IContentEventHelperForImage.cs index 14bc23a4..1158c868 100644 --- a/src/StardewModdingAPI/IContentEventHelperForImage.cs +++ b/src/StardewModdingAPI/IContentEventHelperForImage.cs @@ -5,28 +5,11 @@ using Microsoft.Xna.Framework.Graphics; namespace StardewModdingAPI { /// Encapsulates access and changes to dictionary content being read from a data file. - public interface IContentEventHelperForImage + public interface IContentEventHelperForImage : IContentEventData { - /********* - ** Accessors - *********/ - /// The content's locale code, if the content is localised. - string Locale { get; } - - /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. - string AssetName { get; } - - /// The content data being read. - Texture2D Data { get; } - - /********* ** Public methods *********/ - /// Get whether the asset name being loaded matches a given name after normalisation. - /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). - bool IsAssetName(string path); - /// Overwrite part of the image. /// The image to patch into the content. /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. @@ -36,10 +19,5 @@ namespace StardewModdingAPI /// The is outside the bounds of the spritesheet. /// The content being read isn't an image. void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace); - - /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. - /// The new content value. - /// The is null. - void ReplaceWith(Texture2D value); } } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 478d1af0..445f6f37 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -168,6 +168,7 @@ + -- cgit From e3522edddd0636116bd4772b4e4dbfce9c7c0f8d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Mar 2017 12:00:11 -0500 Subject: extend base content helper to support null content (#173) --- .../Framework/Content/ContentEventBaseHelper.cs | 98 ------------------ .../Framework/Content/ContentEventData.cs | 111 +++++++++++++++++++++ .../Framework/Content/ContentEventHelper.cs | 2 +- .../Content/ContentEventHelperForDictionary.cs | 2 +- .../Content/ContentEventHelperForImage.cs | 2 +- src/StardewModdingAPI/IContentEventData.cs | 3 + src/StardewModdingAPI/StardewModdingAPI.csproj | 4 +- 7 files changed, 119 insertions(+), 103 deletions(-) delete mode 100644 src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs create mode 100644 src/StardewModdingAPI/Framework/Content/ContentEventData.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs b/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs deleted file mode 100644 index a89fe337..00000000 --- a/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; - -namespace StardewModdingAPI.Framework.Content -{ - /// Base implementation for a content helper which encapsulates access and changes to content being read from a data file. - /// The interface value type. - internal class ContentEventBaseHelper : EventArgs, IContentEventData - { - /********* - ** Properties - *********/ - /// Normalises an asset key to match the cache key. - protected readonly Func GetNormalisedPath; - - - /********* - ** Accessors - *********/ - /// The content's locale code, if the content is localised. - public string Locale { get; } - - /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. - public string AssetName { get; } - - /// The content data being read. - public TValue Data { get; protected set; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The content's locale code, if the content is localised. - /// The normalised asset name being read. - /// The content data being read. - /// Normalises an asset key to match the cache key. - public ContentEventBaseHelper(string locale, string assetName, TValue data, Func getNormalisedPath) - { - this.Locale = locale; - this.AssetName = assetName; - this.Data = data; - this.GetNormalisedPath = getNormalisedPath; - } - - /// Get whether the asset name being loaded matches a given name after normalisation. - /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). - public bool IsAssetName(string path) - { - path = this.GetNormalisedPath(path); - return this.AssetName.Equals(path, StringComparison.InvariantCultureIgnoreCase); - } - - /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. - /// The new content value. - /// The is null. - /// The 's type is not compatible with the loaded asset's type. - public void ReplaceWith(TValue value) - { - if (value == null) - throw new ArgumentNullException(nameof(value), "Can't set a loaded asset to a null value."); - if (!this.Data.GetType().IsInstanceOfType(value)) - throw new InvalidCastException($"Can't replace loaded asset of type {this.GetFriendlyTypeName(this.Data.GetType())} with value of type {this.GetFriendlyTypeName(value.GetType())}. The new type must be compatible to prevent game errors."); - - this.Data = value; - } - - - /********* - ** Protected methods - *********/ - /// Get a human-readable type name. - /// The type to name. - protected string GetFriendlyTypeName(Type type) - { - // dictionary - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) - { - Type[] genericArgs = type.GetGenericArguments(); - return $"Dictionary<{this.GetFriendlyTypeName(genericArgs[0])}, {this.GetFriendlyTypeName(genericArgs[1])}>"; - } - - // texture - if (type == typeof(Texture2D)) - return type.Name; - - // native type - if (type == typeof(int)) - return "int"; - if (type == typeof(string)) - return "string"; - - // default - return type.FullName; - } - } -} diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventData.cs b/src/StardewModdingAPI/Framework/Content/ContentEventData.cs new file mode 100644 index 00000000..1a1779d4 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Content/ContentEventData.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework.Graphics; + +namespace StardewModdingAPI.Framework.Content +{ + /// Base implementation for a content helper which encapsulates access and changes to content being read from a data file. + /// The interface value type. + internal class ContentEventData : EventArgs, IContentEventData + { + /********* + ** Properties + *********/ + /// Normalises an asset key to match the cache key. + protected readonly Func GetNormalisedPath; + + + /********* + ** Accessors + *********/ + /// The content's locale code, if the content is localised. + public string Locale { get; } + + /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. + public string AssetName { get; } + + /// The content data being read. + public TValue Data { get; protected set; } + + /// The content data type. + public Type DataType { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The content's locale code, if the content is localised. + /// The normalised asset name being read. + /// The content data being read. + /// Normalises an asset key to match the cache key. + public ContentEventData(string locale, string assetName, TValue data, Func getNormalisedPath) + : this(locale, assetName, data, data.GetType(), getNormalisedPath) { } + + /// Construct an instance. + /// The content's locale code, if the content is localised. + /// The normalised asset name being read. + /// The content data being read. + /// The content data type being read. + /// Normalises an asset key to match the cache key. + public ContentEventData(string locale, string assetName, TValue data, Type dataType, Func getNormalisedPath) + { + this.Locale = locale; + this.AssetName = assetName; + this.Data = data; + this.DataType = dataType; + this.GetNormalisedPath = getNormalisedPath; + } + + /// Get whether the asset name being loaded matches a given name after normalisation. + /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). + public bool IsAssetName(string path) + { + path = this.GetNormalisedPath(path); + return this.AssetName.Equals(path, StringComparison.InvariantCultureIgnoreCase); + } + + /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. + /// The new content value. + /// The is null. + /// The 's type is not compatible with the loaded asset's type. + public void ReplaceWith(TValue value) + { + if (value == null) + throw new ArgumentNullException(nameof(value), "Can't set a loaded asset to a null value."); + if (!this.DataType.IsInstanceOfType(value)) + throw new InvalidCastException($"Can't replace loaded asset of type {this.GetFriendlyTypeName(this.DataType)} with value of type {this.GetFriendlyTypeName(value.GetType())}. The new type must be compatible to prevent game errors."); + + this.Data = value; + } + + + /********* + ** Protected methods + *********/ + /// Get a human-readable type name. + /// The type to name. + protected string GetFriendlyTypeName(Type type) + { + // dictionary + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + Type[] genericArgs = type.GetGenericArguments(); + return $"Dictionary<{this.GetFriendlyTypeName(genericArgs[0])}, {this.GetFriendlyTypeName(genericArgs[1])}>"; + } + + // texture + if (type == typeof(Texture2D)) + return type.Name; + + // native type + if (type == typeof(int)) + return "int"; + if (type == typeof(string)) + return "string"; + + // default + return type.FullName; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs index 26292cab..9bf1ea17 100644 --- a/src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs @@ -5,7 +5,7 @@ using Microsoft.Xna.Framework.Graphics; namespace StardewModdingAPI.Framework.Content { /// Encapsulates access and changes to content being read from a data file. - internal class ContentEventHelper : ContentEventBaseHelper, IContentEventHelper + internal class ContentEventHelper : ContentEventData, IContentEventHelper { /********* ** Public methods diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs index 8a8a4845..26f059e4 100644 --- a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs @@ -5,7 +5,7 @@ using System.Linq; namespace StardewModdingAPI.Framework.Content { /// Encapsulates access and changes to dictionary content being read from a data file. - internal class ContentEventHelperForDictionary : ContentEventBaseHelper>, IContentEventHelperForDictionary + internal class ContentEventHelperForDictionary : ContentEventData>, IContentEventHelperForDictionary { /********* ** Public methods diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs index 23760f0f..da30590b 100644 --- a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs @@ -5,7 +5,7 @@ using Microsoft.Xna.Framework.Graphics; namespace StardewModdingAPI.Framework.Content { /// Encapsulates access and changes to dictionary content being read from a data file. - internal class ContentEventHelperForImage : ContentEventBaseHelper, IContentEventHelperForImage + internal class ContentEventHelperForImage : ContentEventData, IContentEventHelperForImage { /********* ** Public methods diff --git a/src/StardewModdingAPI/IContentEventData.cs b/src/StardewModdingAPI/IContentEventData.cs index a21861d2..7e2d4df1 100644 --- a/src/StardewModdingAPI/IContentEventData.cs +++ b/src/StardewModdingAPI/IContentEventData.cs @@ -18,6 +18,9 @@ namespace StardewModdingAPI /// The content data being read. TValue Data { get; } + /// The content data type. + Type DataType { get; } + /********* ** Public methods diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 445f6f37..ab808948 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -150,7 +150,7 @@ - + @@ -165,10 +165,10 @@ + - -- cgit From 363f5aeef2dac07299d8d37229fd205a8dff7a61 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Mar 2017 20:48:54 -0500 Subject: rename content event for consistency, simplify usage (#173) --- src/StardewModdingAPI/Events/ContentEvents.cs | 34 +++++++++++++--------- src/StardewModdingAPI/Framework/SContentManager.cs | 26 +++++++++++------ 2 files changed, 37 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Events/ContentEvents.cs b/src/StardewModdingAPI/Events/ContentEvents.cs index 558fc070..f19a33e2 100644 --- a/src/StardewModdingAPI/Events/ContentEvents.cs +++ b/src/StardewModdingAPI/Events/ContentEvents.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Events public static event EventHandler> AfterLocaleChanged; /// Raised when an XNB file is being read into the cache. Mods can change the data here before it's cached. - public static event EventHandler AssetLoading; + public static event EventHandler AfterAssetLoaded; /********* @@ -52,13 +52,28 @@ namespace StardewModdingAPI.Events monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AfterLocaleChanged)}", ContentEvents.AfterLocaleChanged?.GetInvocationList(), null, new EventArgsValueChanged(oldLocale, newLocale)); } - /// Raise an event. + /// Raise an event. /// Encapsulates monitoring and logging. /// Encapsulates access and changes to content being read from a data file. - internal static void InvokeAssetLoading(IMonitor monitor, IContentEventHelper contentHelper) + internal static void InvokeAfterAssetLoaded(IMonitor monitor, IContentEventHelper contentHelper) { - // raise warning about experimental API - foreach (Delegate handler in ContentEvents.AssetLoading.GetInvocationList()) + if (ContentEvents.AfterAssetLoaded != null) + { + Delegate[] handlers = ContentEvents.AfterAssetLoaded.GetInvocationList(); + ContentEvents.RaiseDeprecationWarning(handlers); + monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AfterAssetLoaded)}", handlers, null, contentHelper); + } + } + + + /********* + ** Private methods + *********/ + /// Raise a 'experimental API' warning for each mod using the content API. + /// The event handlers. + private static void RaiseDeprecationWarning(Delegate[] handlers) + { + foreach (Delegate handler in handlers) { string modName = ContentEvents.ModRegistry.GetModFrom(handler) ?? "An unknown mod"; if (!ContentEvents.WarnedMods.Contains(modName)) @@ -67,15 +82,6 @@ namespace StardewModdingAPI.Events ContentEvents.Monitor.Log($"{modName} used the undocumented and experimental content API, which may change or be removed without warning.", LogLevel.Warn); } } - - // raise event - monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AssetLoading)}", ContentEvents.AssetLoading?.GetInvocationList(), null, contentHelper); - } - - /// Get whether there are any listeners. - internal static bool HasAssetLoadingListeners() - { - return ContentEvents.AssetLoading != null; } } } diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs index 97472d53..e4b9d74e 100644 --- a/src/StardewModdingAPI/Framework/SContentManager.cs +++ b/src/StardewModdingAPI/Framework/SContentManager.cs @@ -80,18 +80,20 @@ namespace StardewModdingAPI.Framework /// The asset path relative to the loader root directory, not including the .xnb extension. public override T Load(string assetName) { - // normalise asset name so can override the cache value later + // get normalised metadata assetName = this.NormaliseAssetName(assetName); + string cacheLocale = this.GetCacheLocale(assetName); - // skip if no event handlers or already loaded - if (!ContentEvents.HasAssetLoadingListeners() || this.Cache.ContainsKey(assetName)) + // skip if already loaded + if (this.IsLoaded(assetName)) return base.Load(assetName); - // intercept load + // load data T data = base.Load(assetName); - string cacheLocale = this.GetCacheLocale(assetName, this.Cache); + + // let mods intercept content IContentEventHelper helper = new ContentEventHelper(cacheLocale, assetName, data, this.NormaliseAssetName); - ContentEvents.InvokeAssetLoading(this.Monitor, helper); + ContentEvents.InvokeAfterAssetLoaded(this.Monitor, helper); this.Cache[assetName] = helper.Data; return (T)helper.Data; } @@ -112,13 +114,19 @@ namespace StardewModdingAPI.Framework return this.NormaliseAssetNameForPlatform(assetName); } + /// Get whether an asset has already been loaded. + /// The normalised asset name. + private bool IsLoaded(string normalisedAssetName) + { + return this.Cache.ContainsKey(normalisedAssetName); + } + /// Get the locale for which the asset name was saved, if any. /// The normalised asset name. - /// The cache to search. - private string GetCacheLocale(string normalisedAssetName, IDictionary cache) + private string GetCacheLocale(string normalisedAssetName) { string locale = this.GetKeyLocale.Invoke(); - return this.Cache.ContainsKey($"{normalisedAssetName}.{this.GetKeyLocale.Invoke()}") + return this.Cache.ContainsKey($"{normalisedAssetName}.{locale}") ? locale : null; } -- cgit From d881f568565f8a12025e013cf92eaa025aac8c7c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Mar 2017 20:49:04 -0500 Subject: fix SMAPI not correctly detecting cached assets if they were translated (#173) --- src/StardewModdingAPI/Framework/SContentManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs index e4b9d74e..ef5855b2 100644 --- a/src/StardewModdingAPI/Framework/SContentManager.cs +++ b/src/StardewModdingAPI/Framework/SContentManager.cs @@ -118,7 +118,8 @@ namespace StardewModdingAPI.Framework /// The normalised asset name. private bool IsLoaded(string normalisedAssetName) { - return this.Cache.ContainsKey(normalisedAssetName); + return this.Cache.ContainsKey(normalisedAssetName) + || this.Cache.ContainsKey($"{normalisedAssetName}.{this.GetKeyLocale.Invoke()}"); // translated asset } /// Get the locale for which the asset name was saved, if any. -- cgit From 95e519796773a2ef17ec482e27cd4403282e2cd1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Mar 2017 17:28:39 -0500 Subject: fix SaveEvents.AfterLoad being raised during the new-game intro before the player is initialised --- release-notes.md | 1 + src/StardewModdingAPI/Framework/SGame.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 22df8f70..96e6439a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -36,6 +36,7 @@ For mod developers: * SMAPI now writes XNA input enums (`Buttons` and `Keys`) to JSON as strings automatically, so mods no longer need to add a `StringEnumConverter` themselves for those. * Logs now show the OS caption (like "Windows 10") instead of its internal version when available. * Logs now always use `\r\n` to simplify crossplatform viewing. +* Fixed `SaveEvents.AfterLoad` being raised during the new-game intro before the player is initialised. * Several obsolete APIs have been removed (see [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod)), and all _notice_-level deprecations have been increased to _info_. * Deprecated the experimental `IConfigFile`. diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 5edb103e..d551ff5b 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -1094,7 +1094,7 @@ namespace StardewModdingAPI.Framework } // save loaded event - if (Game1.hasLoadedGame && !SaveGame.IsProcessing/*still loading save*/ && this.AfterLoadTimer >= 0) + if (Constants.IsSaveLoaded && !SaveGame.IsProcessing/*still loading save*/ && this.AfterLoadTimer >= 0) { if (this.AfterLoadTimer == 0) { -- cgit From 3663f70603fae8ed34e1e0c7500adc2c899312a5 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 01:01:52 -0500 Subject: split IInstructionFinder from IInstructionRewriter (#247) --- .../Framework/BaseFieldFinder.cs | 32 +++++++++++ .../Framework/BaseFieldRewriter.cs | 18 +----- .../Framework/BaseMethodFinder.cs | 67 ++++++++++++++++++++++ .../Framework/BaseMethodRewriter.cs | 53 +---------------- .../IInstructionFinder.cs | 13 +++++ .../IInstructionRewriter.cs | 7 +-- .../Crossplatform/SpriteBatch_MethodRewriter.cs | 2 +- .../Game1_ActiveClickableMenu_FieldRewriter.cs | 2 +- .../SDV1_2/Game1_GameMode_FieldRewriter.cs | 2 +- .../Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs | 2 +- .../StardewModdingAPI.AssemblyRewriters.csproj | 3 + src/StardewModdingAPI/Framework/AssemblyLoader.cs | 4 +- 12 files changed, 124 insertions(+), 81 deletions(-) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs new file mode 100644 index 00000000..96e8b1c0 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs @@ -0,0 +1,32 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace StardewModdingAPI.AssemblyRewriters.Framework +{ + /// Base class for a field finder. + public abstract class BaseFieldFinder : IInstructionFinder + { + /********* + ** Public methods + *********/ + /// Get whether a CIL instruction matches. + /// The IL instruction. + /// Whether the mod was compiled on a different platform. + public bool IsMatch(Instruction instruction, bool platformChanged) + { + if (instruction.OpCode != OpCodes.Ldfld && instruction.OpCode != OpCodes.Ldsfld && instruction.OpCode != OpCodes.Stfld && instruction.OpCode != OpCodes.Stsfld) + return false; // not a field reference + return this.IsMatch(instruction, (FieldReference)instruction.Operand, platformChanged); + } + + + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected abstract bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged); + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs index 7e01ca73..b2c25587 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs @@ -4,21 +4,11 @@ using Mono.Cecil.Cil; namespace StardewModdingAPI.AssemblyRewriters.Framework { /// Base class for a field rewriter. - public abstract class BaseFieldRewriter : IInstructionRewriter + public abstract class BaseFieldRewriter : BaseFieldFinder, IInstructionRewriter { /********* ** Public methods *********/ - /// Get whether a CIL instruction should be rewritten. - /// The IL instruction. - /// Whether the mod was compiled on a different platform. - public bool ShouldRewrite(Instruction instruction, bool platformChanged) - { - if (instruction.OpCode != OpCodes.Ldfld && instruction.OpCode != OpCodes.Ldsfld && instruction.OpCode != OpCodes.Stfld && instruction.OpCode != OpCodes.Stsfld) - return false; // not a field reference - return this.ShouldRewrite(instruction, (FieldReference)instruction.Operand, platformChanged); - } - /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. @@ -34,12 +24,6 @@ namespace StardewModdingAPI.AssemblyRewriters.Framework /********* ** Protected methods *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected abstract bool ShouldRewrite(Instruction instruction, FieldReference fieldRef, bool platformChanged); - /// Rewrite a method for compatibility. /// The module being rewritten. /// The CIL rewriter. diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs new file mode 100644 index 00000000..7526286a --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using System.Reflection; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace StardewModdingAPI.AssemblyRewriters.Framework +{ + /// Base class for a method finder. + public abstract class BaseMethodFinder : IInstructionFinder + { + /********* + ** Public methods + *********/ + /// Get whether a CIL instruction matches. + /// The IL instruction. + /// Whether the mod was compiled on a different platform. + public bool IsMatch(Instruction instruction, bool platformChanged) + { + if (instruction.OpCode != OpCodes.Call && instruction.OpCode != OpCodes.Callvirt) + return false; // not a method reference + return this.IsMatch(instruction, (MethodReference)instruction.Operand, platformChanged); + } + + + /********* + ** Protected methods + *********/ + /// Get whether a method reference should be rewritten. + /// The IL instruction. + /// The method reference. + /// Whether the mod was compiled on a different platform. + protected abstract bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged); + + /// Get whether a method definition matches the signature expected by a method reference. + /// The method definition. + /// The method reference. + protected bool HasMatchingSignature(MethodInfo definition, MethodReference reference) + { + // same name + if (definition.Name != reference.Name) + return false; + + // same arguments + ParameterInfo[] definitionParameters = definition.GetParameters(); + ParameterDefinition[] referenceParameters = reference.Parameters.ToArray(); + if (referenceParameters.Length != definitionParameters.Length) + return false; + for (int i = 0; i < referenceParameters.Length; i++) + { + if (!RewriteHelper.IsMatchingType(definitionParameters[i].ParameterType, referenceParameters[i].ParameterType)) + return false; + } + return true; + } + + /// Get whether a type has a method whose signature matches the one expected by a method reference. + /// The type to check. + /// The method reference. + protected bool HasMatchingSignature(Type type, MethodReference reference) + { + return type + .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public) + .Any(method => this.HasMatchingSignature(method, reference)); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs index e53e5c56..6af1a0e1 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs @@ -1,27 +1,14 @@ -using System; -using System.Linq; -using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; namespace StardewModdingAPI.AssemblyRewriters.Framework { /// Base class for a method rewriter. - public abstract class BaseMethodRewriter : IInstructionRewriter + public abstract class BaseMethodRewriter : BaseMethodFinder, IInstructionRewriter { /********* ** Public methods *********/ - /// Get whether a CIL instruction should be rewritten. - /// The IL instruction. - /// Whether the mod was compiled on a different platform. - public bool ShouldRewrite(Instruction instruction, bool platformChanged) - { - if (instruction.OpCode != OpCodes.Call && instruction.OpCode != OpCodes.Callvirt) - return false; // not a method reference - return this.ShouldRewrite(instruction, (MethodReference)instruction.Operand, platformChanged); - } - /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. @@ -37,12 +24,6 @@ namespace StardewModdingAPI.AssemblyRewriters.Framework /********* ** Protected methods *********/ - /// Get whether a method reference should be rewritten. - /// The IL instruction. - /// The method reference. - /// Whether the mod was compiled on a different platform. - protected abstract bool ShouldRewrite(Instruction instruction, MethodReference methodRef, bool platformChanged); - /// Rewrite a method for compatibility. /// The module being rewritten. /// The CIL rewriter. @@ -50,37 +31,5 @@ namespace StardewModdingAPI.AssemblyRewriters.Framework /// The method reference invoked by the . /// Metadata for mapping assemblies to the current platform. protected abstract void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap); - - /// Get whether a method definition matches the signature expected by a method reference. - /// The method definition. - /// The method reference. - protected bool HasMatchingSignature(MethodInfo definition, MethodReference reference) - { - // same name - if (definition.Name != reference.Name) - return false; - - // same arguments - ParameterInfo[] definitionParameters = definition.GetParameters(); - ParameterDefinition[] referenceParameters = reference.Parameters.ToArray(); - if (referenceParameters.Length != definitionParameters.Length) - return false; - for (int i = 0; i < referenceParameters.Length; i++) - { - if (!RewriteHelper.IsMatchingType(definitionParameters[i].ParameterType, referenceParameters[i].ParameterType)) - return false; - } - return true; - } - - /// Get whether a type has a method whose signature matches the one expected by a method reference. - /// The type to check. - /// The method reference. - protected bool HasMatchingSignature(Type type, MethodReference reference) - { - return type - .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public) - .Any(method => this.HasMatchingSignature(method, reference)); - } } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs new file mode 100644 index 00000000..47a4247b --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs @@ -0,0 +1,13 @@ +using Mono.Cecil.Cil; + +namespace StardewModdingAPI.AssemblyRewriters +{ + /// Finds CIL instructions considered incompatible. + public interface IInstructionFinder + { + /// Get whether a CIL instruction matches. + /// The IL instruction. + /// Whether the mod was compiled on a different platform. + bool IsMatch(Instruction instruction, bool platformChanged); + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs index 5c24acb6..f99a0a5a 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs @@ -4,13 +4,8 @@ using Mono.Cecil.Cil; namespace StardewModdingAPI.AssemblyRewriters { /// Rewrites a CIL instruction for compatibility. - public interface IInstructionRewriter + public interface IInstructionRewriter : IInstructionFinder { - /// Get whether a CIL instruction should be rewritten. - /// The IL instruction. - /// Whether the mod was compiled on a different platform. - bool ShouldRewrite(Instruction instruction, bool platformChanged); - /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs index 252fe6d0..a47b8410 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs @@ -19,7 +19,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters.Crossplatform /// The IL instruction. /// The method reference. /// Whether the mod was compiled on a different platform. - protected override bool ShouldRewrite(Instruction instruction, MethodReference methodRef, bool platformChanged) + protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) { return platformChanged && methodRef.DeclaringType.FullName == typeof(SpriteBatch).FullName diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs index 36634b28..66fe0369 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs @@ -18,7 +18,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 /// The IL instruction. /// The field reference. /// Whether the mod was compiled on a different platform. - protected override bool ShouldRewrite(Instruction instruction, FieldReference fieldRef, bool platformChanged) + protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) { return (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) // static field diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs index e8cf3d13..9cb6e13d 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs @@ -18,7 +18,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 /// The IL instruction. /// The field reference. /// Whether the mod was compiled on a different platform. - protected override bool ShouldRewrite(Instruction instruction, FieldReference fieldRef, bool platformChanged) + protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) { return (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) // static field diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs index fb87c19a..115e3d7d 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs @@ -18,7 +18,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 /// The IL instruction. /// The field reference. /// Whether the mod was compiled on a different platform. - protected override bool ShouldRewrite(Instruction instruction, FieldReference fieldRef, bool platformChanged) + protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) { return (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) // static field diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index d5394d67..03ed6f3a 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -66,7 +66,10 @@ Properties\GlobalAssemblyInfo.cs + + + diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index a932a47c..eb5d1cf4 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -193,7 +193,7 @@ namespace StardewModdingAPI.Framework foreach (MethodDefinition method in this.GetMethods(module)) { // skip methods with no rewritable instructions - bool canRewrite = method.Body.Instructions.Any(op => rewriters.Any(rewriter => rewriter.ShouldRewrite(op, platformChanged))); + bool canRewrite = method.Body.Instructions.Any(op => rewriters.Any(rewriter => rewriter.IsMatch(op, platformChanged))); if (!canRewrite) continue; @@ -203,7 +203,7 @@ namespace StardewModdingAPI.Framework // rewrite instructions foreach (Instruction op in cil.Body.Instructions.ToArray()) { - IInstructionRewriter rewriter = rewriters.FirstOrDefault(p => p.ShouldRewrite(op, platformChanged)); + IInstructionRewriter rewriter = rewriters.FirstOrDefault(p => p.IsMatch(op, platformChanged)); rewriter?.Rewrite(module, cil, op, this.AssemblyMap); } -- cgit From b0fab4a0764d4cd1eb807db88e704aa401e4f716 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 01:15:01 -0500 Subject: log rewritten instructions (#247) --- .../Framework/BaseFieldFinder.cs | 7 ++++++ .../Framework/BaseMethodFinder.cs | 7 ++++++ .../IInstructionFinder.cs | 10 +++++++++ .../IInstructionRewriter.cs | 3 +++ .../Crossplatform/SpriteBatch_MethodRewriter.cs | 7 ++++++ .../Game1_ActiveClickableMenu_FieldRewriter.cs | 7 ++++++ .../SDV1_2/Game1_GameMode_FieldRewriter.cs | 7 ++++++ .../Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs | 7 ++++++ src/StardewModdingAPI/Framework/AssemblyLoader.cs | 26 ++++++++++++++++++---- 9 files changed, 77 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs index 96e8b1c0..f2074f22 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs @@ -6,6 +6,13 @@ namespace StardewModdingAPI.AssemblyRewriters.Framework /// Base class for a field finder. public abstract class BaseFieldFinder : IInstructionFinder { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public abstract string NounPhrase { get; } + + /********* ** Public methods *********/ diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs index 7526286a..bb71a9d7 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs @@ -9,6 +9,13 @@ namespace StardewModdingAPI.AssemblyRewriters.Framework /// Base class for a method finder. public abstract class BaseMethodFinder : IInstructionFinder { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public abstract string NounPhrase { get; } + + /********* ** Public methods *********/ diff --git a/src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs index 47a4247b..cc3006b9 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs @@ -5,6 +5,16 @@ namespace StardewModdingAPI.AssemblyRewriters /// Finds CIL instructions considered incompatible. public interface IInstructionFinder { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + string NounPhrase { get; } + + + /********* + ** Methods + *********/ /// Get whether a CIL instruction matches. /// The IL instruction. /// Whether the mod was compiled on a different platform. diff --git a/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs index f99a0a5a..b230f227 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs @@ -6,6 +6,9 @@ namespace StardewModdingAPI.AssemblyRewriters /// Rewrites a CIL instruction for compatibility. public interface IInstructionRewriter : IInstructionFinder { + /********* + ** Methods + *********/ /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs index a47b8410..1459ff17 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs @@ -12,6 +12,13 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters.Crossplatform [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] public class SpriteBatch_MethodRewriter : BaseMethodRewriter { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } = $"{nameof(SpriteBatch)} methods"; + + /********* ** Protected methods *********/ diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs index 66fe0369..bb49f16c 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs @@ -11,6 +11,13 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] public class Game1_ActiveClickableMenu_FieldRewriter : BaseFieldRewriter { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } = $"{nameof(Game1)}.{nameof(Game1.activeClickableMenu)} field"; + + /********* ** Protected methods *********/ diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs index 9cb6e13d..4d84d9ac 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs @@ -11,6 +11,13 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] public class Game1_GameMode_FieldRewriter : BaseFieldRewriter { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } = $"{nameof(Game1)}.{nameof(Game1.gameMode)} field"; + + /********* ** Protected methods *********/ diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs index 115e3d7d..f43f5d57 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs @@ -11,6 +11,13 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] public class Game1_Player_FieldRewriter : BaseFieldRewriter { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } = $"{nameof(Game1)}.{nameof(Game1.player)} field"; + + /********* ** Protected methods *********/ diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index eb5d1cf4..8af67772 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -162,6 +162,7 @@ namespace StardewModdingAPI.Framework private bool RewriteAssembly(AssemblyDefinition assembly) { ModuleDefinition module = assembly.MainModule; + HashSet loggedRewrites = new HashSet(); // swap assembly references if needed (e.g. XNA => MonoGame) bool platformChanged = false; @@ -170,6 +171,7 @@ namespace StardewModdingAPI.Framework // remove old assembly reference if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name)) { + this.LogOnce(this.Monitor, loggedRewrites, $"Rewriting {assembly.Name.Name} for OS..."); platformChanged = true; module.AssemblyReferences.RemoveAt(i); i--; @@ -197,14 +199,16 @@ namespace StardewModdingAPI.Framework if (!canRewrite) continue; - // prepare method - ILProcessor cil = method.Body.GetILProcessor(); - // rewrite instructions + ILProcessor cil = method.Body.GetILProcessor(); foreach (Instruction op in cil.Body.Instructions.ToArray()) { IInstructionRewriter rewriter = rewriters.FirstOrDefault(p => p.IsMatch(op, platformChanged)); - rewriter?.Rewrite(module, cil, op, this.AssemblyMap); + if (rewriter != null) + { + this.LogOnce(this.Monitor, loggedRewrites, $"Rewriting {assembly.Name.Name} to fix {rewriter.NounPhrase}..."); + rewriter.Rewrite(module, cil, op, this.AssemblyMap); + } } // finalise method @@ -244,5 +248,19 @@ namespace StardewModdingAPI.Framework select method ); } + + /// Log a message for the player or developer the first time it occurs. + /// The monitor through which to log the message. + /// The hash of logged messages. + /// The message to log. + /// The log severity level. + private void LogOnce(IMonitor monitor, HashSet hash, string message, LogLevel level = LogLevel.Trace) + { + if (!hash.Contains(message)) + { + this.Monitor.Log(message, level); + hash.Add(message); + } + } } } -- cgit From 6d2d90b7681e4e274e92742e98905ec4000486ca Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 01:31:15 -0500 Subject: add logic to detect incompatible mod instructions & reject mod load (#247) --- src/StardewModdingAPI/Constants.cs | 8 +++++++ src/StardewModdingAPI/Framework/AssemblyLoader.cs | 18 +++++++++++++++ .../Framework/IncompatibleInstructionException.cs | 27 ++++++++++++++++++++++ src/StardewModdingAPI/Program.cs | 5 ++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 5 files changed, 59 insertions(+) create mode 100644 src/StardewModdingAPI/Framework/IncompatibleInstructionException.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 66bf5842..d8149766 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -135,6 +135,14 @@ namespace StardewModdingAPI return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences, targetAssemblies); } + /// Get finders which match incompatible CIL instructions in mod assemblies. + internal static IEnumerable GetIncompatibilityFinders() + { + return new IInstructionFinder[] + { + }; + } + /// Get rewriters which fix incompatible CIL instructions in mod assemblies. internal static IEnumerable GetRewriters() { diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index 8af67772..dfe0d03f 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -55,6 +55,7 @@ namespace StardewModdingAPI.Framework /// Preprocess and load an assembly. /// The assembly file path. /// Returns the rewrite metadata for the preprocessed assembly. + /// An incompatible CIL instruction was found while rewriting the assembly. public Assembly Load(string assemblyPath) { // get referenced local assemblies @@ -159,6 +160,7 @@ namespace StardewModdingAPI.Framework /// Rewrite the types referenced by an assembly. /// The assembly to rewrite. /// Returns whether the assembly was modified. + /// An incompatible CIL instruction was found while rewriting the assembly. private bool RewriteAssembly(AssemblyDefinition assembly) { ModuleDefinition module = assembly.MainModule; @@ -189,6 +191,22 @@ namespace StardewModdingAPI.Framework this.ChangeTypeScope(type); } + // throw exception if assembly contains incompatible instructions can't be rewritten + { + IInstructionFinder[] finders = Constants.GetIncompatibilityFinders().ToArray(); + foreach (MethodDefinition method in this.GetMethods(module)) + { + foreach (Instruction instruction in method.Body.Instructions) + { + foreach (IInstructionFinder finder in finders) + { + if (finder.IsMatch(instruction, platformChanged)) + throw new IncompatibleInstructionException(finder.NounPhrase, $"Found an incompatible CIL instruction ({finder.NounPhrase}) while loading assembly {assembly.Name.Name}."); + } + } + } + } + // rewrite incompatible instructions bool anyRewritten = false; IInstructionRewriter[] rewriters = Constants.GetRewriters().ToArray(); diff --git a/src/StardewModdingAPI/Framework/IncompatibleInstructionException.cs b/src/StardewModdingAPI/Framework/IncompatibleInstructionException.cs new file mode 100644 index 00000000..affe2cb3 --- /dev/null +++ b/src/StardewModdingAPI/Framework/IncompatibleInstructionException.cs @@ -0,0 +1,27 @@ +using System; + +namespace StardewModdingAPI.Framework +{ + /// An exception raised when an incompatible instruction is found while loading a mod assembly. + internal class IncompatibleInstructionException : Exception + { + /********* + ** Accessors + *********/ + /// A brief noun phrase which describes the incompatible instruction that was found. + public string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// A brief noun phrase which describes the incompatible instruction that was found. + /// A message which describes the error. + public IncompatibleInstructionException(string nounPhrase, string message) + : base(message) + { + this.NounPhrase = nounPhrase; + } + } +} diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index bf3c86fb..cb8cc2e5 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -453,6 +453,11 @@ namespace StardewModdingAPI { modAssembly = modAssemblyLoader.Load(assemblyPath); } + catch (IncompatibleInstructionException ex) + { + this.Monitor.Log($"{skippedPrefix} because it's not compatible with the latest version of the game (detected {ex.NounPhrase}). Please check for a newer version of the mod (you have v{manifest.Version}).", LogLevel.Error); + continue; + } catch (Exception ex) { this.Monitor.Log($"{skippedPrefix} because its DLL '{manifest.EntryDll}' couldn't be loaded.\n{ex.GetLogSummary()}", LogLevel.Error); diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index ab808948..92726ca0 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -154,6 +154,7 @@ + -- cgit From 9fab0bf58f7c8b7d38f026bde4230e39049c056b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 01:32:30 -0500 Subject: reject mods which reference obsolete StardewModdingAPI.Extensions class (#247) --- .../Finders/SMAPI_Extensions_MethodFinder.cs | 31 ++++++++++++++++++++++ .../StardewModdingAPI.AssemblyRewriters.csproj | 1 + src/StardewModdingAPI/Constants.cs | 3 +++ 3 files changed, 35 insertions(+) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs new file mode 100644 index 00000000..f359b595 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Finders +{ + /// Matches CIL instructions that reference the former StardewModdingAPI.Extensions class, which was removed in SMAPI 1.9. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] + public class SMAPI_Extensions_MethodFinder : BaseMethodFinder + { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } = "obsolete StardewModdingAPI.Extensions API"; + + + /********* + ** Protected methods + *********/ + /// Get whether a method reference should be rewritten. + /// The IL instruction. + /// The method reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) + { + return methodRef.DeclaringType.FullName == "StardewModdingAPI.Extensions"; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 03ed6f3a..4d96d5d3 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -66,6 +66,7 @@ Properties\GlobalAssemblyInfo.cs + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index d8149766..f18e0792 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; using StardewModdingAPI.AssemblyRewriters; +using StardewModdingAPI.AssemblyRewriters.Finders; using StardewModdingAPI.AssemblyRewriters.Rewriters.Crossplatform; using StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2; using StardewValley; @@ -140,6 +141,8 @@ namespace StardewModdingAPI { return new IInstructionFinder[] { + // APIs removed in SMAPI 1.9 + new SMAPI_Extensions_MethodFinder() }; } -- cgit From fae362723f1390cb7758bd151d50889cf6c999a9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 03:23:20 -0400 Subject: reject mods which reference obsolete Game1.borderFont and Game1.smoothFont fields (#247) --- .../Finders/Game1_borderFont_FieldFinder.cs | 35 ++++++++++++++++++++++ .../Finders/Game1_smoothFont_FieldFinder.cs | 35 ++++++++++++++++++++++ .../Finders/SMAPI_Extensions_MethodFinder.cs | 2 +- .../Framework/BaseFieldFinder.cs | 7 +++++ .../Game1_ActiveClickableMenu_FieldRewriter.cs | 2 +- .../SDV1_2/Game1_GameMode_FieldRewriter.cs | 2 +- .../Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs | 2 +- .../StardewModdingAPI.AssemblyRewriters.csproj | 2 ++ src/StardewModdingAPI/Constants.cs | 4 +++ 9 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_borderFont_FieldFinder.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_smoothFont_FieldFinder.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_borderFont_FieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_borderFont_FieldFinder.cs new file mode 100644 index 00000000..e25cc544 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_borderFont_FieldFinder.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.CodeAnalysis; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; +using StardewValley; + +namespace StardewModdingAPI.AssemblyRewriters.Finders +{ + /// Finds CIL instructions that reference the former Game1.borderFont field, which was removed in Stardew Valley 1.2.3–1.2.6. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] + public class Game1_borderFont_FieldFinder : BaseFieldFinder + { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } = $"obsolete {nameof(Game1)}.borderFont field"; + + + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) + { + return + this.IsStaticField(instruction) + && fieldRef.DeclaringType.FullName == typeof(Game1).FullName + && fieldRef.Name == "borderFont"; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_smoothFont_FieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_smoothFont_FieldFinder.cs new file mode 100644 index 00000000..b852f10d --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_smoothFont_FieldFinder.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.CodeAnalysis; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; +using StardewValley; + +namespace StardewModdingAPI.AssemblyRewriters.Finders +{ + /// Finds CIL instructions that reference the former Game1.smoothFont field, which was removed in Stardew Valley 1.2.3–1.2.6. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] + public class Game1_smoothFont_FieldFinder : BaseFieldFinder + { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } = $"obsolete {nameof(Game1)}.smoothFont field"; + + + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) + { + return + this.IsStaticField(instruction) + && fieldRef.DeclaringType.FullName == typeof(Game1).FullName + && fieldRef.Name == "smoothFont"; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs index f359b595..4abcbc13 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs @@ -5,7 +5,7 @@ using StardewModdingAPI.AssemblyRewriters.Framework; namespace StardewModdingAPI.AssemblyRewriters.Finders { - /// Matches CIL instructions that reference the former StardewModdingAPI.Extensions class, which was removed in SMAPI 1.9. + /// Finds CIL instructions that reference the former StardewModdingAPI.Extensions class, which was removed in SMAPI 1.9. [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] public class SMAPI_Extensions_MethodFinder : BaseMethodFinder { diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs index f2074f22..ac2facec 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs @@ -35,5 +35,12 @@ namespace StardewModdingAPI.AssemblyRewriters.Framework /// The field reference. /// Whether the mod was compiled on a different platform. protected abstract bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged); + + /// Whether an instruction is a static field reference. + /// The IL instruction. + protected bool IsStaticField(Instruction instruction) + { + return instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld; + } } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs index bb49f16c..59a7c798 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) { return - (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) // static field + this.IsStaticField(instruction) && fieldRef.DeclaringType.FullName == typeof(Game1).FullName && fieldRef.Name == nameof(Game1.activeClickableMenu); } diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs index 4d84d9ac..c3da6863 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) { return - (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) // static field + this.IsStaticField(instruction) && fieldRef.DeclaringType.FullName == typeof(Game1).FullName && fieldRef.Name == nameof(Game1.gameMode); } diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs index f43f5d57..91eae416 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) { return - (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) // static field + this.IsStaticField(instruction) && fieldRef.DeclaringType.FullName == typeof(Game1).FullName && fieldRef.Name == nameof(Game1.player); } diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 4d96d5d3..3f437ca0 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -66,6 +66,8 @@ Properties\GlobalAssemblyInfo.cs + + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index f18e0792..563cdd52 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -141,6 +141,10 @@ namespace StardewModdingAPI { return new IInstructionFinder[] { + // changes in Stardew Valley 1.2 + new Game1_borderFont_FieldFinder(), + new Game1_smoothFont_FieldFinder(), + // APIs removed in SMAPI 1.9 new SMAPI_Extensions_MethodFinder() }; -- cgit From 003a9586b287f3bd831f9dfe7ca52b9e4e03c028 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 16:56:16 -0400 Subject: simplify access to game's Program class Stardew Valley 1.2.15 made the class public, so we no longer need reflection to access it. --- src/StardewModdingAPI/Program.cs | 43 ++++++++++++---------------------------- 1 file changed, 13 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index cb8cc2e5..f0cc00c7 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -12,7 +12,6 @@ using System.Windows.Forms; #endif using Microsoft.Xna.Framework.Graphics; using Newtonsoft.Json; -using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.Events; using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.Logging; @@ -142,21 +141,12 @@ namespace StardewModdingAPI this.VerifyPath(Constants.ModPath); this.VerifyPath(Constants.LogDir); - // get executable path - string executablePath = Path.Combine(Constants.ExecutionPath, Constants.TargetPlatform == Platform.Windows ? "Stardew Valley.exe" : "StardewValley.exe"); - if (!File.Exists(executablePath)) - { - this.Monitor.Log($"Couldn't find executable: {executablePath}", LogLevel.Error); - this.PressAnyKeyToExit(); - return; - } - // check for update when game loads if (this.Settings.CheckForUpdates) GameEvents.GameLoaded += (sender, e) => this.CheckForUpdateAsync(); // launch game - this.StartGame(executablePath); + this.StartGame(); } catch (Exception ex) { @@ -211,11 +201,12 @@ namespace StardewModdingAPI } /// Hook into Stardew Valley and launch the game. - /// The absolute path to the executable to launch. - private void StartGame(string executablePath) + 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); @@ -223,23 +214,15 @@ namespace StardewModdingAPI #endif AppDomain.CurrentDomain.UnhandledException += (sender, e) => this.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error); - // initialise game - { - // load assembly - this.Monitor.Log("Loading game..."); - Assembly gameAssembly = Assembly.UnsafeLoadFrom(executablePath); - Type gameProgramType = gameAssembly.GetType("StardewValley.Program", true); - - // set 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}"; - gameProgramType.GetField("gamePtr").SetValue(gameProgramType, this.GameInstance); - - // configure - Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; - } + // 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}"; + StardewValley.Program.gamePtr = this.GameInstance; + + // configure + Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; // load mods this.LoadMods(); -- cgit From a12bcf3b7845b7c4541ca7539a4810a81b87e3ce Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 17:48:53 -0400 Subject: reject mods which reference obsolete SGame class (#247) --- .../Finders/GenericTypeFinder.cs | 30 +++++++++++++ .../Framework/BaseTypeFinder.cs | 52 ++++++++++++++++++++++ .../StardewModdingAPI.AssemblyRewriters.csproj | 2 + src/StardewModdingAPI/Constants.cs | 3 +- 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/BaseTypeFinder.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs new file mode 100644 index 00000000..11ffa734 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs @@ -0,0 +1,30 @@ +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Finders +{ + /// Base class for a type reference finder. + public class GenericTypeFinder : BaseTypeFinder + { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + /// The full type name to match. + public override string FullTypeName { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name to match. + /// A brief noun phrase indicating what the instruction finder matches. + public GenericTypeFinder(string fullTypeName, string nounPhrase) + { + this.FullTypeName = fullTypeName; + this.NounPhrase = nounPhrase; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseTypeFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseTypeFinder.cs new file mode 100644 index 00000000..5a768794 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseTypeFinder.cs @@ -0,0 +1,52 @@ +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace StardewModdingAPI.AssemblyRewriters.Framework +{ + /// Base class for a type reference finder. + public abstract class BaseTypeFinder : IInstructionFinder + { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public abstract string NounPhrase { get; } + + /// The full type name to match. + public abstract string FullTypeName { get; } + + + /********* + ** Public methods + *********/ + /// Get whether a CIL instruction matches. + /// The IL instruction. + /// Whether the mod was compiled on a different platform. + public virtual bool IsMatch(Instruction instruction, bool platformChanged) + { + string fullName = this.FullTypeName; + + // field reference + if (instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld) + { + FieldReference field = (FieldReference)instruction.Operand; + return + field.DeclaringType.FullName == fullName // field on target class + || field.FieldType.FullName == fullName; // field value is target class + } + + // method reference + if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt) + { + MethodReference method = (MethodReference)instruction.Operand; + return + method.DeclaringType.FullName == fullName // method on target class + || method.ReturnType.FullName == fullName // method returns target class + || method.Parameters.Any(p => p.ParameterType.FullName == fullName); // method parameters + } + + return false; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 3f437ca0..d489eb34 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -69,8 +69,10 @@ + + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 563cdd52..1e1ca325 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -146,7 +146,8 @@ namespace StardewModdingAPI new Game1_smoothFont_FieldFinder(), // APIs removed in SMAPI 1.9 - new SMAPI_Extensions_MethodFinder() + new SMAPI_Extensions_MethodFinder(), + new GenericTypeFinder("StardewModdingAPI.Inheritance.SGame", "obsolete StardewModdingAPI.SGame class") }; } -- cgit From a6ed67a9f763b5efab58589776e8eaa31a4f2dbc Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 17:51:59 -0400 Subject: simplify & optimise instruction searching a bit (#247) --- src/StardewModdingAPI/Framework/AssemblyLoader.cs | 41 +++++++---------------- 1 file changed, 12 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index dfe0d03f..bc56de01 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -191,46 +191,29 @@ namespace StardewModdingAPI.Framework this.ChangeTypeScope(type); } - // throw exception if assembly contains incompatible instructions can't be rewritten - { - IInstructionFinder[] finders = Constants.GetIncompatibilityFinders().ToArray(); - foreach (MethodDefinition method in this.GetMethods(module)) - { - foreach (Instruction instruction in method.Body.Instructions) - { - foreach (IInstructionFinder finder in finders) - { - if (finder.IsMatch(instruction, platformChanged)) - throw new IncompatibleInstructionException(finder.NounPhrase, $"Found an incompatible CIL instruction ({finder.NounPhrase}) while loading assembly {assembly.Name.Name}."); - } - } - } - } - - // rewrite incompatible instructions + // find incompatible instructions bool anyRewritten = false; + IInstructionFinder[] finders = Constants.GetIncompatibilityFinders().ToArray(); IInstructionRewriter[] rewriters = Constants.GetRewriters().ToArray(); foreach (MethodDefinition method in this.GetMethods(module)) { - // skip methods with no rewritable instructions - bool canRewrite = method.Body.Instructions.Any(op => rewriters.Any(rewriter => rewriter.IsMatch(op, platformChanged))); - if (!canRewrite) - continue; - - // rewrite instructions ILProcessor cil = method.Body.GetILProcessor(); - foreach (Instruction op in cil.Body.Instructions.ToArray()) + foreach (Instruction instruction in cil.Body.Instructions.ToArray()) { - IInstructionRewriter rewriter = rewriters.FirstOrDefault(p => p.IsMatch(op, platformChanged)); + // throw exception if instruction is incompatible but can't be rewritten + IInstructionFinder finder = finders.FirstOrDefault(p => p.IsMatch(instruction, platformChanged)); + if (finder != null) + throw new IncompatibleInstructionException(finder.NounPhrase, $"Found an incompatible CIL instruction ({finder.NounPhrase}) while loading assembly {assembly.Name.Name}."); + + // rewrite instruction if needed + IInstructionRewriter rewriter = rewriters.FirstOrDefault(p => p.IsMatch(instruction, platformChanged)); if (rewriter != null) { this.LogOnce(this.Monitor, loggedRewrites, $"Rewriting {assembly.Name.Name} to fix {rewriter.NounPhrase}..."); - rewriter.Rewrite(module, cil, op, this.AssemblyMap); + rewriter.Rewrite(module, cil, instruction, this.AssemblyMap); + anyRewritten = true; } } - - // finalise method - anyRewritten = true; } return platformChanged || anyRewritten; -- cgit From ccc57935de9b75e17b9f531df87e2ac4dac43dfc Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 18:25:29 -0400 Subject: replace individual instruction finders with generic implementations (#247) --- .../Finders/Game1_borderFont_FieldFinder.cs | 35 ------------- .../Finders/Game1_smoothFont_FieldFinder.cs | 35 ------------- .../Finders/GenericFieldFinder.cs | 61 ++++++++++++++++++++++ .../Finders/GenericMethodFinder.cs | 54 +++++++++++++++++++ .../Finders/GenericTypeFinder.cs | 54 +++++++++++++++---- .../Finders/SMAPI_Extensions_MethodFinder.cs | 31 ----------- .../Framework/BaseTypeFinder.cs | 52 ------------------ .../StardewModdingAPI.AssemblyRewriters.csproj | 6 +-- src/StardewModdingAPI/Constants.cs | 8 +-- 9 files changed, 165 insertions(+), 171 deletions(-) delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_borderFont_FieldFinder.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_smoothFont_FieldFinder.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/GenericFieldFinder.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/GenericMethodFinder.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/BaseTypeFinder.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_borderFont_FieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_borderFont_FieldFinder.cs deleted file mode 100644 index e25cc544..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_borderFont_FieldFinder.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; -using StardewValley; - -namespace StardewModdingAPI.AssemblyRewriters.Finders -{ - /// Finds CIL instructions that reference the former Game1.borderFont field, which was removed in Stardew Valley 1.2.3–1.2.6. - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] - public class Game1_borderFont_FieldFinder : BaseFieldFinder - { - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } = $"obsolete {nameof(Game1)}.borderFont field"; - - - /********* - ** Protected methods - *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) - { - return - this.IsStaticField(instruction) - && fieldRef.DeclaringType.FullName == typeof(Game1).FullName - && fieldRef.Name == "borderFont"; - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_smoothFont_FieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_smoothFont_FieldFinder.cs deleted file mode 100644 index b852f10d..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/Game1_smoothFont_FieldFinder.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; -using StardewValley; - -namespace StardewModdingAPI.AssemblyRewriters.Finders -{ - /// Finds CIL instructions that reference the former Game1.smoothFont field, which was removed in Stardew Valley 1.2.3–1.2.6. - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] - public class Game1_smoothFont_FieldFinder : BaseFieldFinder - { - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } = $"obsolete {nameof(Game1)}.smoothFont field"; - - - /********* - ** Protected methods - *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) - { - return - this.IsStaticField(instruction) - && fieldRef.DeclaringType.FullName == typeof(Game1).FullName - && fieldRef.Name == "smoothFont"; - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericFieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericFieldFinder.cs new file mode 100644 index 00000000..056422a4 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericFieldFinder.cs @@ -0,0 +1,61 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Finders +{ + /// Finds CIL instructions that reference a given field. + public sealed class GenericFieldFinder : BaseFieldFinder + { + /********* + ** Properties + *********/ + /// The full type name for which to find references. + private readonly string FullTypeName; + + /// The field name for which to find references. + private readonly string FieldName; + + /// Whether the field to match is static. + private readonly bool IsStatic; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name for which to find references. + /// The field name for which to find references. + /// Whether the field to match is static. + public GenericFieldFinder(string fullTypeName, string fieldName, bool isStatic) + { + this.FullTypeName = fullTypeName; + this.FieldName = fieldName; + this.IsStatic = isStatic; + this.NounPhrase = $"obsolete {fullTypeName}.{fieldName} field"; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) + { + return + this.IsStaticField(instruction) == this.IsStatic + && fieldRef.DeclaringType.FullName == this.FullTypeName + && fieldRef.Name == this.FieldName; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericMethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericMethodFinder.cs new file mode 100644 index 00000000..f5443558 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericMethodFinder.cs @@ -0,0 +1,54 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Finders +{ + /// Finds CIL instructions that reference a given method. + public sealed class GenericMethodFinder : BaseMethodFinder + { + /********* + ** Properties + *********/ + /// The full type name for which to find references. + private readonly string FullTypeName; + + /// The method name for which to find references. + private readonly string MethodName; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name for which to find references. + /// The method name for which to find references. + public GenericMethodFinder(string fullTypeName, string methodName) + { + this.FullTypeName = fullTypeName; + this.MethodName = methodName; + this.NounPhrase = $"obsolete {fullTypeName}.{methodName} method"; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a method reference should be rewritten. + /// The IL instruction. + /// The method reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) + { + return methodRef.DeclaringType.FullName == this.FullTypeName + && methodRef.Name == this.MethodName; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs index 11ffa734..1556cc3c 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs @@ -1,18 +1,24 @@ -using StardewModdingAPI.AssemblyRewriters.Framework; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; namespace StardewModdingAPI.AssemblyRewriters.Finders { - /// Base class for a type reference finder. - public class GenericTypeFinder : BaseTypeFinder + /// Finds CIL instructions that reference a given type. + public sealed class GenericTypeFinder : IInstructionFinder { /********* ** Accessors *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } + /// The full type name for which to find references. + private readonly string FullTypeName; + - /// The full type name to match. - public override string FullTypeName { get; } + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public string NounPhrase { get; } /********* @@ -20,11 +26,39 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders *********/ /// Construct an instance. /// The full type name to match. - /// A brief noun phrase indicating what the instruction finder matches. - public GenericTypeFinder(string fullTypeName, string nounPhrase) + public GenericTypeFinder(string fullTypeName) { this.FullTypeName = fullTypeName; - this.NounPhrase = nounPhrase; + this.NounPhrase = $"obsolete {fullTypeName} type"; + } + + /// Get whether a CIL instruction matches. + /// The IL instruction. + /// Whether the mod was compiled on a different platform. + public bool IsMatch(Instruction instruction, bool platformChanged) + { + string fullName = this.FullTypeName; + + // field reference + if (instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld) + { + FieldReference field = (FieldReference)instruction.Operand; + return + field.DeclaringType.FullName == fullName // field on target class + || field.FieldType.FullName == fullName; // field value is target class + } + + // method reference + if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt) + { + MethodReference method = (MethodReference)instruction.Operand; + return + method.DeclaringType.FullName == fullName // method on target class + || method.ReturnType.FullName == fullName // method returns target class + || method.Parameters.Any(p => p.ParameterType.FullName == fullName); // method parameters + } + + return false; } } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs deleted file mode 100644 index 4abcbc13..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/SMAPI_Extensions_MethodFinder.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; - -namespace StardewModdingAPI.AssemblyRewriters.Finders -{ - /// Finds CIL instructions that reference the former StardewModdingAPI.Extensions class, which was removed in SMAPI 1.9. - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] - public class SMAPI_Extensions_MethodFinder : BaseMethodFinder - { - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } = "obsolete StardewModdingAPI.Extensions API"; - - - /********* - ** Protected methods - *********/ - /// Get whether a method reference should be rewritten. - /// The IL instruction. - /// The method reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) - { - return methodRef.DeclaringType.FullName == "StardewModdingAPI.Extensions"; - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseTypeFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseTypeFinder.cs deleted file mode 100644 index 5a768794..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseTypeFinder.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace StardewModdingAPI.AssemblyRewriters.Framework -{ - /// Base class for a type reference finder. - public abstract class BaseTypeFinder : IInstructionFinder - { - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public abstract string NounPhrase { get; } - - /// The full type name to match. - public abstract string FullTypeName { get; } - - - /********* - ** Public methods - *********/ - /// Get whether a CIL instruction matches. - /// The IL instruction. - /// Whether the mod was compiled on a different platform. - public virtual bool IsMatch(Instruction instruction, bool platformChanged) - { - string fullName = this.FullTypeName; - - // field reference - if (instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld) - { - FieldReference field = (FieldReference)instruction.Operand; - return - field.DeclaringType.FullName == fullName // field on target class - || field.FieldType.FullName == fullName; // field value is target class - } - - // method reference - if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt) - { - MethodReference method = (MethodReference)instruction.Operand; - return - method.DeclaringType.FullName == fullName // method on target class - || method.ReturnType.FullName == fullName // method returns target class - || method.Parameters.Any(p => p.ParameterType.FullName == fullName); // method parameters - } - - return false; - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index d489eb34..5089318c 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -66,10 +66,8 @@ Properties\GlobalAssemblyInfo.cs - - - - + + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 1e1ca325..4c1c7a8e 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -142,12 +142,12 @@ namespace StardewModdingAPI return new IInstructionFinder[] { // changes in Stardew Valley 1.2 - new Game1_borderFont_FieldFinder(), - new Game1_smoothFont_FieldFinder(), + new GenericFieldFinder("Game1", "borderFont", isStatic: true), + new GenericFieldFinder("Game1", "smoothFont", isStatic: true), // APIs removed in SMAPI 1.9 - new SMAPI_Extensions_MethodFinder(), - new GenericTypeFinder("StardewModdingAPI.Inheritance.SGame", "obsolete StardewModdingAPI.SGame class") + new GenericTypeFinder("StardewModdingAPI.Extensions"), + new GenericTypeFinder("StardewModdingAPI.Inheritance.SGame") }; } -- cgit From a93f1e20423cce790ece3fab769546815dfc2f3a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 18:38:01 -0400 Subject: add several incompatibility finders for SMAPI 1.9 and SDV 1.2 (#247) --- src/StardewModdingAPI/Constants.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 4c1c7a8e..d86a065b 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -141,13 +141,20 @@ namespace StardewModdingAPI { return new IInstructionFinder[] { - // changes in Stardew Valley 1.2 - new GenericFieldFinder("Game1", "borderFont", isStatic: true), - new GenericFieldFinder("Game1", "smoothFont", isStatic: true), + // changes in Stardew Valley 1.2 (that don't have rewriters) + new GenericFieldFinder("StardewValley.Game1", "borderFont", isStatic: true), + new GenericFieldFinder("StardewValley.Game1", "smoothFont", isStatic: true), + new GenericFieldFinder("StardewValley.Item", "set_Name", isStatic: false), // APIs removed in SMAPI 1.9 + new GenericTypeFinder("StardewModdingAPI.Entities.SPlayer"), new GenericTypeFinder("StardewModdingAPI.Extensions"), - new GenericTypeFinder("StardewModdingAPI.Inheritance.SGame") + new GenericTypeFinder("StardewModdingAPI.Inheritance.ItemStackChange"), + new GenericTypeFinder("StardewModdingAPI.Inheritance.SGame"), + new GenericTypeFinder("StardewModdingAPI.Inheritance.SObject"), + new GenericTypeFinder("StardewModdingAPI.LogWriter"), + new GenericTypeFinder("StardewModdingAPI.Manifest"), + new GenericTypeFinder("StardewModdingAPI.Version") }; } -- cgit From ac19a1a85aac382271b01bfc801f7a293f8b0804 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 19:05:31 -0400 Subject: add incompatibility finders for events removed in SMAPI 1.9 (#247) --- .../Finders/GenericEventFinder.cs | 54 ++++++++++++++++++++++ .../StardewModdingAPI.AssemblyRewriters.csproj | 1 + src/StardewModdingAPI/Constants.cs | 8 +++- 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/GenericEventFinder.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericEventFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericEventFinder.cs new file mode 100644 index 00000000..c2a981e5 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericEventFinder.cs @@ -0,0 +1,54 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Finders +{ + /// Finds CIL instructions that reference a given event. + public sealed class GenericEventFinder : BaseMethodFinder + { + /********* + ** Properties + *********/ + /// The full type name for which to find references. + private readonly string FullTypeName; + + /// The event name for which to find references. + private readonly string EventName; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name for which to find references. + /// The event name for which to find references. + public GenericEventFinder(string fullTypeName, string eventName) + { + this.FullTypeName = fullTypeName; + this.EventName = eventName; + this.NounPhrase = $"obsolete {fullTypeName}.{eventName} event"; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a method reference should be rewritten. + /// The IL instruction. + /// The method reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) + { + return methodRef.DeclaringType.FullName == this.FullTypeName + && (methodRef.Name == "add_" + this.EventName || methodRef.Name == "remove_" + this.EventName); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 5089318c..a3322e67 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -67,6 +67,7 @@ Properties\GlobalAssemblyInfo.cs + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index d86a065b..c0d3e3ae 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -154,7 +154,13 @@ namespace StardewModdingAPI new GenericTypeFinder("StardewModdingAPI.Inheritance.SObject"), new GenericTypeFinder("StardewModdingAPI.LogWriter"), new GenericTypeFinder("StardewModdingAPI.Manifest"), - new GenericTypeFinder("StardewModdingAPI.Version") + new GenericTypeFinder("StardewModdingAPI.Version"), + new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "DrawDebug"), + new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "DrawTick"), + new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderHudEventNoCheck"), + new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderGuiEventNoCheck"), + new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderHudEventNoCheck"), + new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderGuiEventNoCheck"), }; } -- cgit From 6a87f3566fcdde312483a5e9a0ec0698aa95d3b6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 19:15:55 -0400 Subject: update incompatibility record for Better Sprinklers --- src/StardewModdingAPI/Constants.cs | 2 +- src/StardewModdingAPI/StardewModdingAPI.config.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index c0d3e3ae..8949bc55 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -160,7 +160,7 @@ namespace StardewModdingAPI new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderHudEventNoCheck"), new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderGuiEventNoCheck"), new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderHudEventNoCheck"), - new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderGuiEventNoCheck"), + new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderGuiEventNoCheck") }; } diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index e971c324..020633e6 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -49,10 +49,9 @@ generally shouldn't change this file unless necessary. { "Name": "Better Sprinklers", "ID": "SPDSprinklersMod", - "UpperVersion": "2.1", + "UpperVersion": "2.3", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/41", "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", - "ForceCompatibleVersion": "^2.1-EntoPatch.7", "Notes": "Uses obsolete StardewModdingAPI.Extensions." }, { -- cgit From 183fb9ff6e66e519ee9c0e3a3357504e8caf662a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 20:12:47 -0400 Subject: remove unused IConfigFile (#238) --- src/StardewModdingAPI/Advanced/ConfigFile.cs | 37 ---------------------- src/StardewModdingAPI/Advanced/IConfigFile.cs | 28 ---------------- src/StardewModdingAPI/Constants.cs | 2 ++ src/StardewModdingAPI/Framework/ModHelper.cs | 15 +-------- .../Framework/Serialisation/JsonHelper.cs | 14 ++------ src/StardewModdingAPI/Program.cs | 3 +- src/StardewModdingAPI/StardewModdingAPI.csproj | 2 -- 7 files changed, 6 insertions(+), 95 deletions(-) delete mode 100644 src/StardewModdingAPI/Advanced/ConfigFile.cs delete mode 100644 src/StardewModdingAPI/Advanced/IConfigFile.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Advanced/ConfigFile.cs b/src/StardewModdingAPI/Advanced/ConfigFile.cs deleted file mode 100644 index 78cad26a..00000000 --- a/src/StardewModdingAPI/Advanced/ConfigFile.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.IO; -using Newtonsoft.Json; - -namespace StardewModdingAPI.Advanced -{ - /// Wraps a configuration file with IO methods for convenience. - [Obsolete] - public abstract class ConfigFile : IConfigFile - { - /********* - ** Accessors - *********/ - /// Provides simplified APIs for writing mods. - public IModHelper ModHelper { get; set; } - - /// The file path from which the model was loaded, relative to the mod directory. - public string FilePath { get; set; } - - - /********* - ** Public methods - *********/ - /// Reparse the underlying file and update this model. - public void Reload() - { - string json = File.ReadAllText(Path.Combine(this.ModHelper.DirectoryPath, this.FilePath)); - JsonConvert.PopulateObject(json, this); - } - - /// Save this model to the underlying file. - public void Save() - { - this.ModHelper.WriteJsonFile(this.FilePath, this); - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/Advanced/IConfigFile.cs b/src/StardewModdingAPI/Advanced/IConfigFile.cs deleted file mode 100644 index 1b424ace..00000000 --- a/src/StardewModdingAPI/Advanced/IConfigFile.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace StardewModdingAPI.Advanced -{ - /// Wraps a configuration file with IO methods for convenience. - [Obsolete] - public interface IConfigFile - { - /********* - ** Accessors - *********/ - /// Provides simplified APIs for writing mods. - IModHelper ModHelper { get; set; } - - /// The file path from which the model was loaded, relative to the mod directory. - string FilePath { get; set; } - - - /********* - ** Methods - *********/ - /// Reparse the underlying file and update this model. - void Reload(); - - /// Save this model to the underlying file. - void Save(); - } -} diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 8949bc55..ae2fbe87 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -147,6 +147,8 @@ namespace StardewModdingAPI new GenericFieldFinder("StardewValley.Item", "set_Name", isStatic: false), // APIs removed in SMAPI 1.9 + new GenericTypeFinder("StardewModdingAPI.Advanced.ConfigFile"), + new GenericTypeFinder("StardewModdingAPI.Advanced.IConfigFile"), new GenericTypeFinder("StardewModdingAPI.Entities.SPlayer"), new GenericTypeFinder("StardewModdingAPI.Extensions"), new GenericTypeFinder("StardewModdingAPI.Inheritance.ItemStackChange"), diff --git a/src/StardewModdingAPI/Framework/ModHelper.cs b/src/StardewModdingAPI/Framework/ModHelper.cs index 0d50201b..c8c44dba 100644 --- a/src/StardewModdingAPI/Framework/ModHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelper.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using StardewModdingAPI.Advanced; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Serialisation; @@ -15,9 +14,6 @@ namespace StardewModdingAPI.Framework /// Encapsulates SMAPI's JSON file parsing. private readonly JsonHelper JsonHelper; - /// Manages deprecation warnings. - private static DeprecationManager DeprecationManager; - /********* ** Accessors @@ -65,13 +61,6 @@ namespace StardewModdingAPI.Framework this.ConsoleCommands = new CommandHelper(modName, commandManager); } - /// Injects types required for backwards compatibility. - /// Manages deprecation warnings. - internal static void Shim(DeprecationManager deprecationManager) - { - ModHelper.DeprecationManager = deprecationManager; - } - /**** ** Mod config file ****/ @@ -81,8 +70,6 @@ namespace StardewModdingAPI.Framework where TConfig : class, new() { TConfig config = this.ReadJsonFile("config.json") ?? new TConfig(); - if (config is IConfigFile) - ModHelper.DeprecationManager.Warn($"{nameof(IConfigFile)}", "1.9", DeprecationLevel.Info); this.WriteConfig(config); // create file or fill in missing fields return config; } @@ -107,7 +94,7 @@ namespace StardewModdingAPI.Framework where TModel : class { path = Path.Combine(this.DirectoryPath, path); - return this.JsonHelper.ReadJsonFile(path, this); + return this.JsonHelper.ReadJsonFile(path); } /// Save to a JSON file. diff --git a/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs b/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs index d5f5bfd0..bd15c7bb 100644 --- a/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs +++ b/src/StardewModdingAPI/Framework/Serialisation/JsonHelper.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using Microsoft.Xna.Framework.Input; using Newtonsoft.Json; -using StardewModdingAPI.Advanced; namespace StardewModdingAPI.Framework.Serialisation { @@ -31,9 +30,8 @@ namespace StardewModdingAPI.Framework.Serialisation /// Read a JSON file. /// The model type. /// The absolete file path. - /// The mod helper to inject for instances. /// Returns the deserialised model, or null if the file doesn't exist or is empty. - public TModel ReadJsonFile(string fullPath, IModHelper modHelper) + public TModel ReadJsonFile(string fullPath) where TModel : class { // read file @@ -48,15 +46,7 @@ namespace StardewModdingAPI.Framework.Serialisation } // deserialise model - TModel model = JsonConvert.DeserializeObject(json, this.JsonSettings); - if (model is IConfigFile) - { - var wrapper = (IConfigFile)model; - wrapper.ModHelper = modHelper; - wrapper.FilePath = fullPath; - } - - return model; + return JsonConvert.DeserializeObject(json, this.JsonSettings); } /// Save to a JSON file. diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index f0cc00c7..db7a3df6 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -97,7 +97,6 @@ namespace StardewModdingAPI InternalExtensions.Shim(this.ModRegistry); Log.Shim(this.DeprecationManager, this.GetSecondaryMonitor("legacy mod"), this.ModRegistry); Mod.Shim(this.DeprecationManager); - ModHelper.Shim(this.DeprecationManager); ContentEvents.Shim(this.ModRegistry, this.Monitor); PlayerEvents.Shim(this.DeprecationManager); TimeEvents.Shim(this.DeprecationManager); @@ -344,7 +343,7 @@ namespace StardewModdingAPI } // deserialise manifest - manifest = jsonHelper.ReadJsonFile(Path.Combine(directory.FullName, "manifest.json"), null); + manifest = jsonHelper.ReadJsonFile(Path.Combine(directory.FullName, "manifest.json")); if (manifest == null) { this.Monitor.Log($"{skippedPrefix} because its manifest is invalid.", LogLevel.Error); diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 92726ca0..dceae74e 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -115,8 +115,6 @@ Properties\GlobalAssemblyInfo.cs - - -- cgit From 7bab161834652e7c205be893cec26f2e8b495692 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 20:15:36 -0400 Subject: disable experimental content event for upcoming 1.9 release (#173) --- src/StardewModdingAPI/Events/ContentEvents.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Events/ContentEvents.cs b/src/StardewModdingAPI/Events/ContentEvents.cs index f19a33e2..9418673a 100644 --- a/src/StardewModdingAPI/Events/ContentEvents.cs +++ b/src/StardewModdingAPI/Events/ContentEvents.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Events public static event EventHandler> AfterLocaleChanged; /// Raised when an XNB file is being read into the cache. Mods can change the data here before it's cached. - public static event EventHandler AfterAssetLoaded; + internal static event EventHandler AfterAssetLoaded; /********* -- cgit From ab89e422012e8ef1bca3bcaa383b28c36b4431d9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Mar 2017 20:31:46 -0400 Subject: bump minimum game version --- src/StardewModdingAPI/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index ae2fbe87..263d3352 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -34,7 +34,7 @@ namespace StardewModdingAPI public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 8, 0); /// The minimum supported version of Stardew Valley. - public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.13"); + public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.15"); /// The path to the game folder. public static string ExecutionPath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); -- cgit From 8bd265fed60d32a6fe64f01b8528738274aa0083 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Mar 2017 13:30:29 -0400 Subject: refactor SGame a bit now that it's internal --- src/StardewModdingAPI/Framework/SGame.cs | 324 +++++++++++++++---------------- 1 file changed, 159 insertions(+), 165 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index d551ff5b..33d823ef 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -47,223 +47,128 @@ namespace StardewModdingAPI.Framework private readonly IMonitor Monitor; /**** - ** Private wrappers + ** Game state ****/ - // ReSharper disable ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming - /// Used to access private fields and methods. - private static readonly IReflectionHelper Reflection = new ReflectionHelper(); - private static List _fpsList => SGame.Reflection.GetPrivateField>(typeof(Game1), nameof(_fpsList)).GetValue(); - private static Stopwatch _fpsStopwatch => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(SGame._fpsStopwatch)).GetValue(); - private static float _fps - { - set { SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_fps)).SetValue(value); } - } - private static Task _newDayTask => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_newDayTask)).GetValue(); - private Color bgColor => SGame.Reflection.GetPrivateField(this, nameof(bgColor)).GetValue(); - public RenderTarget2D screenWrapper => SGame.Reflection.GetPrivateProperty(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop - public BlendState lightingBlend => SGame.Reflection.GetPrivateField(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 - - - /********* - ** Accessors - *********/ /// Arrays of pressed controller buttons indexed by . - public Buttons[][] PreviouslyPressedButtons; + private Buttons[][] PreviouslyPressedButtons; /// A record of the keyboard state (i.e. the up/down state for each button) as of the latest tick. - public KeyboardState KStateNow { get; private set; } + private KeyboardState KStateNow; /// A record of the keyboard state (i.e. the up/down state for each button) as of the previous tick. - public KeyboardState KStatePrior { get; private set; } + private KeyboardState KStatePrior; /// A record of the mouse state (i.e. the cursor position, scroll amount, and the up/down state for each button) as of the latest tick. - public MouseState MStateNow { get; private set; } + private MouseState MStateNow; /// A record of the mouse state (i.e. the cursor position, scroll amount, and the up/down state for each button) as of the previous tick. - public MouseState MStatePrior { get; private set; } + private MouseState MStatePrior; /// The current mouse position on the screen adjusted for the zoom level. - public Point MPositionNow { get; private set; } + private Point MPositionNow; /// The previous mouse position on the screen adjusted for the zoom level. - public Point MPositionPrior { get; private set; } + private Point MPositionPrior; /// The keys that were pressed as of the latest tick. - public Keys[] CurrentlyPressedKeys => this.KStateNow.GetPressedKeys(); + private Keys[] CurrentlyPressedKeys => this.KStateNow.GetPressedKeys(); /// The keys that were pressed as of the previous tick. - public Keys[] PreviouslyPressedKeys => this.KStatePrior.GetPressedKeys(); + private Keys[] PreviouslyPressedKeys => this.KStatePrior.GetPressedKeys(); /// The keys that just entered the down state. - public Keys[] FramePressedKeys => this.CurrentlyPressedKeys.Except(this.PreviouslyPressedKeys).ToArray(); + private Keys[] FramePressedKeys => this.CurrentlyPressedKeys.Except(this.PreviouslyPressedKeys).ToArray(); /// The keys that just entered the up state. - public Keys[] FrameReleasedKeys => this.PreviouslyPressedKeys.Except(this.CurrentlyPressedKeys).ToArray(); + private Keys[] FrameReleasedKeys => this.PreviouslyPressedKeys.Except(this.CurrentlyPressedKeys).ToArray(); /// A hash of at last check. - public int PreviousGameLocations { get; private set; } + private int PreviousGameLocations; /// A hash of the current location's at last check. - public int PreviousLocationObjects { get; private set; } + private int PreviousLocationObjects; /// The player's inventory at last check. - public Dictionary PreviousItems { get; private set; } + private IDictionary PreviousItems; /// The player's combat skill level at last check. - public int PreviousCombatLevel { get; private set; } + private int PreviousCombatLevel; /// The player's farming skill level at last check. - public int PreviousFarmingLevel { get; private set; } + private int PreviousFarmingLevel; /// The player's fishing skill level at last check. - public int PreviousFishingLevel { get; private set; } + private int PreviousFishingLevel; /// The player's foraging skill level at last check. - public int PreviousForagingLevel { get; private set; } + private int PreviousForagingLevel; /// The player's mining skill level at last check. - public int PreviousMiningLevel { get; private set; } + private int PreviousMiningLevel; /// The player's luck skill level at last check. - public int PreviousLuckLevel { get; private set; } + private int PreviousLuckLevel; /// The player's location at last check. - public GameLocation PreviousGameLocation { get; private set; } + private GameLocation PreviousGameLocation; /// The active game menu at last check. - public IClickableMenu PreviousActiveMenu { get; private set; } + private IClickableMenu PreviousActiveMenu; /// The mine level at last check. - public int PreviousMineLevel { get; private set; } + private int PreviousMineLevel; /// The time of day (in 24-hour military format) at last check. - public int PreviousTimeOfDay { get; private set; } + private int PreviousTime; /// The day of month (1–28) at last check. - public int PreviousDayOfMonth { get; private set; } + private int PreviousDay; /// The season name (winter, spring, summer, or fall) at last check. - public string PreviousSeasonOfYear { get; private set; } + private string PreviousSeason; /// The year number at last check. - public int PreviousYearOfGame { get; private set; } + private int PreviousYear; /// Whether the game was transitioning to a new day at last check. - public bool PreviousIsNewDay { get; private set; } + private bool PreviousIsNewDay; /// The player character at last check. - public SFarmer PreviousFarmer { get; private set; } + private SFarmer PreviousFarmer; /// The previous content locale. - public LocalizedContentManager.LanguageCode? PreviousLocale { get; private set; } + private LocalizedContentManager.LanguageCode? PreviousLocale; /// An index incremented on every tick and reset every 60th tick (0–59). - public int CurrentUpdateTick { get; private set; } + private int CurrentUpdateTick; /// Whether this is the very first update tick since the game started. - public bool FirstUpdate { get; private set; } + private bool FirstUpdate; /// The current game instance. - public static SGame Instance { get; private set; } - - /// Whether we're in pseudo-debug mode, which shows information like FPS. - public static bool Debug { get; private set; } - - - /********* - ** Public methods - *********/ - /// Get the controller buttons which are currently pressed. - /// The controller to check. - public Buttons[] GetButtonsDown(PlayerIndex index) - { - var state = GamePad.GetState(index); - var buttons = new List(); - if (state.IsConnected) - { - if (state.Buttons.A == ButtonState.Pressed) buttons.Add(Buttons.A); - if (state.Buttons.B == ButtonState.Pressed) buttons.Add(Buttons.B); - if (state.Buttons.Back == ButtonState.Pressed) buttons.Add(Buttons.Back); - if (state.Buttons.BigButton == ButtonState.Pressed) buttons.Add(Buttons.BigButton); - if (state.Buttons.LeftShoulder == ButtonState.Pressed) buttons.Add(Buttons.LeftShoulder); - if (state.Buttons.LeftStick == ButtonState.Pressed) buttons.Add(Buttons.LeftStick); - if (state.Buttons.RightShoulder == ButtonState.Pressed) buttons.Add(Buttons.RightShoulder); - if (state.Buttons.RightStick == ButtonState.Pressed) buttons.Add(Buttons.RightStick); - if (state.Buttons.Start == ButtonState.Pressed) buttons.Add(Buttons.Start); - if (state.Buttons.X == ButtonState.Pressed) buttons.Add(Buttons.X); - if (state.Buttons.Y == ButtonState.Pressed) buttons.Add(Buttons.Y); - if (state.DPad.Up == ButtonState.Pressed) buttons.Add(Buttons.DPadUp); - if (state.DPad.Down == ButtonState.Pressed) buttons.Add(Buttons.DPadDown); - if (state.DPad.Left == ButtonState.Pressed) buttons.Add(Buttons.DPadLeft); - if (state.DPad.Right == ButtonState.Pressed) buttons.Add(Buttons.DPadRight); - if (state.Triggers.Left > 0.2f) buttons.Add(Buttons.LeftTrigger); - if (state.Triggers.Right > 0.2f) buttons.Add(Buttons.RightTrigger); - } - return buttons.ToArray(); - } - - /// Get the controller buttons which were pressed after the last update. - /// The controller to check. - public Buttons[] GetFramePressedButtons(PlayerIndex index) - { - var state = GamePad.GetState(index); - var buttons = new List(); - if (state.IsConnected) - { - if (this.WasButtonJustPressed(Buttons.A, state.Buttons.A, index)) buttons.Add(Buttons.A); - if (this.WasButtonJustPressed(Buttons.B, state.Buttons.B, index)) buttons.Add(Buttons.B); - if (this.WasButtonJustPressed(Buttons.Back, state.Buttons.Back, index)) buttons.Add(Buttons.Back); - if (this.WasButtonJustPressed(Buttons.BigButton, state.Buttons.BigButton, index)) buttons.Add(Buttons.BigButton); - if (this.WasButtonJustPressed(Buttons.LeftShoulder, state.Buttons.LeftShoulder, index)) buttons.Add(Buttons.LeftShoulder); - if (this.WasButtonJustPressed(Buttons.LeftStick, state.Buttons.LeftStick, index)) buttons.Add(Buttons.LeftStick); - if (this.WasButtonJustPressed(Buttons.RightShoulder, state.Buttons.RightShoulder, index)) buttons.Add(Buttons.RightShoulder); - if (this.WasButtonJustPressed(Buttons.RightStick, state.Buttons.RightStick, index)) buttons.Add(Buttons.RightStick); - if (this.WasButtonJustPressed(Buttons.Start, state.Buttons.Start, index)) buttons.Add(Buttons.Start); - if (this.WasButtonJustPressed(Buttons.X, state.Buttons.X, index)) buttons.Add(Buttons.X); - if (this.WasButtonJustPressed(Buttons.Y, state.Buttons.Y, index)) buttons.Add(Buttons.Y); - if (this.WasButtonJustPressed(Buttons.DPadUp, state.DPad.Up, index)) buttons.Add(Buttons.DPadUp); - if (this.WasButtonJustPressed(Buttons.DPadDown, state.DPad.Down, index)) buttons.Add(Buttons.DPadDown); - if (this.WasButtonJustPressed(Buttons.DPadLeft, state.DPad.Left, index)) buttons.Add(Buttons.DPadLeft); - if (this.WasButtonJustPressed(Buttons.DPadRight, state.DPad.Right, index)) buttons.Add(Buttons.DPadRight); - if (this.WasButtonJustPressed(Buttons.LeftTrigger, state.Triggers.Left, index)) buttons.Add(Buttons.LeftTrigger); - if (this.WasButtonJustPressed(Buttons.RightTrigger, state.Triggers.Right, index)) buttons.Add(Buttons.RightTrigger); - } - return buttons.ToArray(); - } + private static SGame Instance; - /// Get the controller buttons which were released after the last update. - /// The controller to check. - public Buttons[] GetFrameReleasedButtons(PlayerIndex index) + /**** + ** Private wrappers + ****/ + // ReSharper disable ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming + /// Used to access private fields and methods. + private static readonly IReflectionHelper Reflection = new ReflectionHelper(); + private static List _fpsList => SGame.Reflection.GetPrivateField>(typeof(Game1), nameof(_fpsList)).GetValue(); + private static Stopwatch _fpsStopwatch => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(SGame._fpsStopwatch)).GetValue(); + private static float _fps { - var state = GamePad.GetState(index); - var buttons = new List(); - if (state.IsConnected) - { - if (this.WasButtonJustReleased(Buttons.A, state.Buttons.A, index)) buttons.Add(Buttons.A); - if (this.WasButtonJustReleased(Buttons.B, state.Buttons.B, index)) buttons.Add(Buttons.B); - if (this.WasButtonJustReleased(Buttons.Back, state.Buttons.Back, index)) buttons.Add(Buttons.Back); - if (this.WasButtonJustReleased(Buttons.BigButton, state.Buttons.BigButton, index)) buttons.Add(Buttons.BigButton); - if (this.WasButtonJustReleased(Buttons.LeftShoulder, state.Buttons.LeftShoulder, index)) buttons.Add(Buttons.LeftShoulder); - if (this.WasButtonJustReleased(Buttons.LeftStick, state.Buttons.LeftStick, index)) buttons.Add(Buttons.LeftStick); - if (this.WasButtonJustReleased(Buttons.RightShoulder, state.Buttons.RightShoulder, index)) buttons.Add(Buttons.RightShoulder); - if (this.WasButtonJustReleased(Buttons.RightStick, state.Buttons.RightStick, index)) buttons.Add(Buttons.RightStick); - if (this.WasButtonJustReleased(Buttons.Start, state.Buttons.Start, index)) buttons.Add(Buttons.Start); - if (this.WasButtonJustReleased(Buttons.X, state.Buttons.X, index)) buttons.Add(Buttons.X); - if (this.WasButtonJustReleased(Buttons.Y, state.Buttons.Y, index)) buttons.Add(Buttons.Y); - if (this.WasButtonJustReleased(Buttons.DPadUp, state.DPad.Up, index)) buttons.Add(Buttons.DPadUp); - if (this.WasButtonJustReleased(Buttons.DPadDown, state.DPad.Down, index)) buttons.Add(Buttons.DPadDown); - if (this.WasButtonJustReleased(Buttons.DPadLeft, state.DPad.Left, index)) buttons.Add(Buttons.DPadLeft); - if (this.WasButtonJustReleased(Buttons.DPadRight, state.DPad.Right, index)) buttons.Add(Buttons.DPadRight); - if (this.WasButtonJustReleased(Buttons.LeftTrigger, state.Triggers.Left, index)) buttons.Add(Buttons.LeftTrigger); - if (this.WasButtonJustReleased(Buttons.RightTrigger, state.Triggers.Right, index)) buttons.Add(Buttons.RightTrigger); - } - return buttons.ToArray(); + set { SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_fps)).SetValue(value); } } + private static Task _newDayTask => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_newDayTask)).GetValue(); + private Color bgColor => SGame.Reflection.GetPrivateField(this, nameof(bgColor)).GetValue(); + public RenderTarget2D screenWrapper => SGame.Reflection.GetPrivateProperty(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop + public BlendState lightingBlend => SGame.Reflection.GetPrivateField(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 /********* @@ -278,6 +183,9 @@ namespace StardewModdingAPI.Framework SGame.Instance = this; } + /**** + ** Intercepted methods & events + ****/ /// The method called during game launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. protected override void Initialize() { @@ -331,10 +239,6 @@ namespace StardewModdingAPI.Framework // update SMAPI events this.UpdateEventCalls(); - // toggle debug output - if (this.FramePressedKeys.Contains(Keys.F3)) - SGame.Debug = !SGame.Debug; - // let game update try { @@ -1043,6 +947,96 @@ namespace StardewModdingAPI.Framework } } + /**** + ** Methods + ****/ + /// Get the controller buttons which are currently pressed. + /// The controller to check. + private Buttons[] GetButtonsDown(PlayerIndex index) + { + var state = GamePad.GetState(index); + var buttons = new List(); + if (state.IsConnected) + { + if (state.Buttons.A == ButtonState.Pressed) buttons.Add(Buttons.A); + if (state.Buttons.B == ButtonState.Pressed) buttons.Add(Buttons.B); + if (state.Buttons.Back == ButtonState.Pressed) buttons.Add(Buttons.Back); + if (state.Buttons.BigButton == ButtonState.Pressed) buttons.Add(Buttons.BigButton); + if (state.Buttons.LeftShoulder == ButtonState.Pressed) buttons.Add(Buttons.LeftShoulder); + if (state.Buttons.LeftStick == ButtonState.Pressed) buttons.Add(Buttons.LeftStick); + if (state.Buttons.RightShoulder == ButtonState.Pressed) buttons.Add(Buttons.RightShoulder); + if (state.Buttons.RightStick == ButtonState.Pressed) buttons.Add(Buttons.RightStick); + if (state.Buttons.Start == ButtonState.Pressed) buttons.Add(Buttons.Start); + if (state.Buttons.X == ButtonState.Pressed) buttons.Add(Buttons.X); + if (state.Buttons.Y == ButtonState.Pressed) buttons.Add(Buttons.Y); + if (state.DPad.Up == ButtonState.Pressed) buttons.Add(Buttons.DPadUp); + if (state.DPad.Down == ButtonState.Pressed) buttons.Add(Buttons.DPadDown); + if (state.DPad.Left == ButtonState.Pressed) buttons.Add(Buttons.DPadLeft); + if (state.DPad.Right == ButtonState.Pressed) buttons.Add(Buttons.DPadRight); + if (state.Triggers.Left > 0.2f) buttons.Add(Buttons.LeftTrigger); + if (state.Triggers.Right > 0.2f) buttons.Add(Buttons.RightTrigger); + } + return buttons.ToArray(); + } + + /// Get the controller buttons which were pressed after the last update. + /// The controller to check. + private Buttons[] GetFramePressedButtons(PlayerIndex index) + { + var state = GamePad.GetState(index); + var buttons = new List(); + if (state.IsConnected) + { + if (this.WasButtonJustPressed(Buttons.A, state.Buttons.A, index)) buttons.Add(Buttons.A); + if (this.WasButtonJustPressed(Buttons.B, state.Buttons.B, index)) buttons.Add(Buttons.B); + if (this.WasButtonJustPressed(Buttons.Back, state.Buttons.Back, index)) buttons.Add(Buttons.Back); + if (this.WasButtonJustPressed(Buttons.BigButton, state.Buttons.BigButton, index)) buttons.Add(Buttons.BigButton); + if (this.WasButtonJustPressed(Buttons.LeftShoulder, state.Buttons.LeftShoulder, index)) buttons.Add(Buttons.LeftShoulder); + if (this.WasButtonJustPressed(Buttons.LeftStick, state.Buttons.LeftStick, index)) buttons.Add(Buttons.LeftStick); + if (this.WasButtonJustPressed(Buttons.RightShoulder, state.Buttons.RightShoulder, index)) buttons.Add(Buttons.RightShoulder); + if (this.WasButtonJustPressed(Buttons.RightStick, state.Buttons.RightStick, index)) buttons.Add(Buttons.RightStick); + if (this.WasButtonJustPressed(Buttons.Start, state.Buttons.Start, index)) buttons.Add(Buttons.Start); + if (this.WasButtonJustPressed(Buttons.X, state.Buttons.X, index)) buttons.Add(Buttons.X); + if (this.WasButtonJustPressed(Buttons.Y, state.Buttons.Y, index)) buttons.Add(Buttons.Y); + if (this.WasButtonJustPressed(Buttons.DPadUp, state.DPad.Up, index)) buttons.Add(Buttons.DPadUp); + if (this.WasButtonJustPressed(Buttons.DPadDown, state.DPad.Down, index)) buttons.Add(Buttons.DPadDown); + if (this.WasButtonJustPressed(Buttons.DPadLeft, state.DPad.Left, index)) buttons.Add(Buttons.DPadLeft); + if (this.WasButtonJustPressed(Buttons.DPadRight, state.DPad.Right, index)) buttons.Add(Buttons.DPadRight); + if (this.WasButtonJustPressed(Buttons.LeftTrigger, state.Triggers.Left, index)) buttons.Add(Buttons.LeftTrigger); + if (this.WasButtonJustPressed(Buttons.RightTrigger, state.Triggers.Right, index)) buttons.Add(Buttons.RightTrigger); + } + return buttons.ToArray(); + } + + /// Get the controller buttons which were released after the last update. + /// The controller to check. + private Buttons[] GetFrameReleasedButtons(PlayerIndex index) + { + var state = GamePad.GetState(index); + var buttons = new List(); + if (state.IsConnected) + { + if (this.WasButtonJustReleased(Buttons.A, state.Buttons.A, index)) buttons.Add(Buttons.A); + if (this.WasButtonJustReleased(Buttons.B, state.Buttons.B, index)) buttons.Add(Buttons.B); + if (this.WasButtonJustReleased(Buttons.Back, state.Buttons.Back, index)) buttons.Add(Buttons.Back); + if (this.WasButtonJustReleased(Buttons.BigButton, state.Buttons.BigButton, index)) buttons.Add(Buttons.BigButton); + if (this.WasButtonJustReleased(Buttons.LeftShoulder, state.Buttons.LeftShoulder, index)) buttons.Add(Buttons.LeftShoulder); + if (this.WasButtonJustReleased(Buttons.LeftStick, state.Buttons.LeftStick, index)) buttons.Add(Buttons.LeftStick); + if (this.WasButtonJustReleased(Buttons.RightShoulder, state.Buttons.RightShoulder, index)) buttons.Add(Buttons.RightShoulder); + if (this.WasButtonJustReleased(Buttons.RightStick, state.Buttons.RightStick, index)) buttons.Add(Buttons.RightStick); + if (this.WasButtonJustReleased(Buttons.Start, state.Buttons.Start, index)) buttons.Add(Buttons.Start); + if (this.WasButtonJustReleased(Buttons.X, state.Buttons.X, index)) buttons.Add(Buttons.X); + if (this.WasButtonJustReleased(Buttons.Y, state.Buttons.Y, index)) buttons.Add(Buttons.Y); + if (this.WasButtonJustReleased(Buttons.DPadUp, state.DPad.Up, index)) buttons.Add(Buttons.DPadUp); + if (this.WasButtonJustReleased(Buttons.DPadDown, state.DPad.Down, index)) buttons.Add(Buttons.DPadDown); + if (this.WasButtonJustReleased(Buttons.DPadLeft, state.DPad.Left, index)) buttons.Add(Buttons.DPadLeft); + if (this.WasButtonJustReleased(Buttons.DPadRight, state.DPad.Right, index)) buttons.Add(Buttons.DPadRight); + if (this.WasButtonJustReleased(Buttons.LeftTrigger, state.Triggers.Left, index)) buttons.Add(Buttons.LeftTrigger); + if (this.WasButtonJustReleased(Buttons.RightTrigger, state.Triggers.Right, index)) buttons.Add(Buttons.RightTrigger); + } + return buttons.ToArray(); + } + /// Get whether a controller button was pressed since the last check. /// The controller button to check. /// The last known state. @@ -1272,25 +1266,25 @@ namespace StardewModdingAPI.Framework } // raise time changed - if (Game1.timeOfDay != this.PreviousTimeOfDay) + if (Game1.timeOfDay != this.PreviousTime) { - TimeEvents.InvokeTimeOfDayChanged(this.Monitor, this.PreviousTimeOfDay, Game1.timeOfDay); - this.PreviousTimeOfDay = Game1.timeOfDay; + TimeEvents.InvokeTimeOfDayChanged(this.Monitor, this.PreviousTime, Game1.timeOfDay); + this.PreviousTime = Game1.timeOfDay; } - if (Game1.dayOfMonth != this.PreviousDayOfMonth) + if (Game1.dayOfMonth != this.PreviousDay) { - TimeEvents.InvokeDayOfMonthChanged(this.Monitor, this.PreviousDayOfMonth, Game1.dayOfMonth); - this.PreviousDayOfMonth = Game1.dayOfMonth; + TimeEvents.InvokeDayOfMonthChanged(this.Monitor, this.PreviousDay, Game1.dayOfMonth); + this.PreviousDay = Game1.dayOfMonth; } - if (Game1.currentSeason != this.PreviousSeasonOfYear) + if (Game1.currentSeason != this.PreviousSeason) { - TimeEvents.InvokeSeasonOfYearChanged(this.Monitor, this.PreviousSeasonOfYear, Game1.currentSeason); - this.PreviousSeasonOfYear = Game1.currentSeason; + TimeEvents.InvokeSeasonOfYearChanged(this.Monitor, this.PreviousSeason, Game1.currentSeason); + this.PreviousSeason = Game1.currentSeason; } - if (Game1.year != this.PreviousYearOfGame) + if (Game1.year != this.PreviousYear) { - TimeEvents.InvokeYearOfGameChanged(this.Monitor, this.PreviousYearOfGame, Game1.year); - this.PreviousYearOfGame = Game1.year; + TimeEvents.InvokeYearOfGameChanged(this.Monitor, this.PreviousYear, Game1.year); + this.PreviousYear = Game1.year; } // raise mine level changed @@ -1304,7 +1298,7 @@ namespace StardewModdingAPI.Framework // raise game day transition event (obsolete) if (Game1.newDay != this.PreviousIsNewDay) { - TimeEvents.InvokeOnNewDay(this.Monitor, this.PreviousDayOfMonth, Game1.dayOfMonth, Game1.newDay); + TimeEvents.InvokeOnNewDay(this.Monitor, this.PreviousDay, Game1.dayOfMonth, Game1.newDay); this.PreviousIsNewDay = Game1.newDay; } } -- cgit From 02a4c40814caf26f21fc9afd9c64afd639da462f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Mar 2017 13:44:41 -0400 Subject: simplify TrainerMod int parsing with C# 7 out variables --- src/TrainerMod/Framework/Extensions.cs | 25 ------------ src/TrainerMod/TrainerMod.cs | 73 ++++++++++++---------------------- src/TrainerMod/TrainerMod.csproj | 3 +- 3 files changed, 26 insertions(+), 75 deletions(-) delete mode 100644 src/TrainerMod/Framework/Extensions.cs (limited to 'src') diff --git a/src/TrainerMod/Framework/Extensions.cs b/src/TrainerMod/Framework/Extensions.cs deleted file mode 100644 index da3a2f03..00000000 --- a/src/TrainerMod/Framework/Extensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace TrainerMod.Framework -{ - /// Provides extension methods on primitive types. - internal static class Extensions - { - /********* - ** Public methods - *********/ - /// Get whether an object is a number. - /// The object value. - public static bool IsInt(this object value) - { - int i; - return int.TryParse(value.ToString(), out i); - } - - /// Parse an object into a number. - /// The object value. - /// The value is not a valid number. - public static int ToInt(this object value) - { - return int.Parse(value.ToString()); - } - } -} diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index 1d4e0b17..d6990a14 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -10,7 +10,6 @@ using StardewValley.Locations; using StardewValley.Menus; using StardewValley.Objects; using StardewValley.Tools; -using TrainerMod.Framework; using TrainerMod.ItemData; using Object = StardewValley.Object; @@ -277,10 +276,9 @@ namespace TrainerMod case "player_setspeed": if (args.Any()) { - string amountStr = args[0]; - if (amountStr.IsInt()) + if (int.TryParse(args[0], out int addedSpeed)) { - Game1.player.addedSpeed = amountStr.ToInt(); + Game1.player.addedSpeed = addedSpeed; this.Monitor.Log($"OK, your added speed is now {Game1.player.addedSpeed}.", LogLevel.Info); } else @@ -298,9 +296,9 @@ namespace TrainerMod if (validTargets.Contains(target)) { string[] colorHexes = args[1].Split(new[] { ',' }, 3); - if (colorHexes[0].IsInt() && colorHexes[1].IsInt() && colorHexes[2].IsInt()) + if (int.TryParse(colorHexes[0], out int r) && int.TryParse(colorHexes[1], out int g) && int.TryParse(colorHexes[2], out int b)) { - var color = new Color(colorHexes[0].ToInt(), colorHexes[1].ToInt(), colorHexes[2].ToInt()); + Color color = new Color(r, g, b); switch (target) { case "hair": @@ -334,9 +332,8 @@ namespace TrainerMod string[] validTargets = { "hair", "shirt", "skin", "acc", "shoe", "swim", "gender" }; if (validTargets.Contains(target)) { - if (args[1].IsInt()) + if (int.TryParse(args[1], out int styleID)) { - var styleID = args[1].ToInt(); switch (target) { case "hair": @@ -406,10 +403,8 @@ namespace TrainerMod case "world_freezetime": if (args.Any()) { - string valueStr = args[0]; - if (valueStr.IsInt()) + if (int.TryParse(args[0], out int value)) { - int value = valueStr.ToInt(); if (value == 0 || value == 1) { this.FreezeTime = value == 1; @@ -433,14 +428,11 @@ namespace TrainerMod case "world_settime": if (args.Any()) { - string timeStr = args[0]; - if (timeStr.IsInt()) + if (int.TryParse(args[0], out int time)) { - int time = timeStr.ToInt(); - if (time <= 2600 && time >= 600) { - Game1.timeOfDay = args[0].ToInt(); + Game1.timeOfDay = time; this.FrozenTime = this.FreezeTime ? Game1.timeOfDay : 0; this.Monitor.Log($"OK, the time is now {Game1.timeOfDay.ToString().PadLeft(4, '0')}.", LogLevel.Info); } @@ -457,11 +449,8 @@ namespace TrainerMod case "world_setday": if (args.Any()) { - string dayStr = args[0]; - - if (dayStr.IsInt()) + if (int.TryParse(args[0], out int day)) { - int day = dayStr.ToInt(); if (day <= 28 && day > 0) { Game1.dayOfMonth = day; @@ -507,9 +496,9 @@ namespace TrainerMod else { this.InfiniteHealth = false; - if (amountStr.IsInt()) + if (int.TryParse(amountStr, out int amount)) { - Game1.player.health = amountStr.ToInt(); + Game1.player.health = amount; this.Monitor.Log($"OK, you now have {Game1.player.health} health.", LogLevel.Info); } else @@ -523,10 +512,9 @@ namespace TrainerMod case "player_setmaxhealth": if (args.Any()) { - string amountStr = args[0]; - if (amountStr.IsInt()) + if (int.TryParse(args[0], out int maxHealth)) { - Game1.player.maxHealth = amountStr.ToInt(); + Game1.player.maxHealth = maxHealth; this.Monitor.Log($"OK, you now have {Game1.player.maxHealth} max health.", LogLevel.Info); } else @@ -539,10 +527,9 @@ namespace TrainerMod case "player_setimmunity": if (args.Any()) { - string amountStr = args[0]; - if (amountStr.IsInt()) + if (int.TryParse(args[0], out int amount)) { - Game1.player.immunity = amountStr.ToInt(); + Game1.player.immunity = amount; this.Monitor.Log($"OK, you now have {Game1.player.immunity} immunity.", LogLevel.Info); } else @@ -555,31 +542,22 @@ namespace TrainerMod case "player_additem": if (args.Any()) { - string itemIdStr = args[0]; - if (itemIdStr.IsInt()) + if (int.TryParse(args[0], out int itemID)) { - int itemID = itemIdStr.ToInt(); int count = 1; int quality = 0; if (args.Length > 1) { - if (args[1].IsInt()) - count = args[1].ToInt(); - else + if (!int.TryParse(args[1], out count)) { this.LogUsageError("The optional count is invalid.", command); return; } - if (args.Length > 2) + if (args.Length > 2 && !int.TryParse(args[2], out quality)) { - if (args[2].IsInt()) - quality = args[2].ToInt(); - else - { - this.LogUsageError("The optional quality is invalid.", command); - return; - } + this.LogUsageError("The optional quality is invalid.", command); + return; } } @@ -602,9 +580,9 @@ namespace TrainerMod case "player_addmelee": if (args.Any()) { - if (args[0].IsInt()) + if (int.TryParse(args[0], out int weaponID)) { - MeleeWeapon weapon = new MeleeWeapon(args[0].ToInt()); + MeleeWeapon weapon = new MeleeWeapon(weaponID); if (weapon.Name == null) this.Monitor.Log("There is no such weapon ID.", LogLevel.Error); else @@ -623,9 +601,8 @@ namespace TrainerMod case "player_addring": if (args.Any()) { - if (args[0].IsInt()) + if (int.TryParse(args[0], out int ringID)) { - int ringID = args[0].ToInt(); if (ringID < Ring.ringLowerIndexRange || ringID > Ring.ringUpperIndexRange) this.Monitor.Log($"There is no such ring ID (must be between {Ring.ringLowerIndexRange} and {Ring.ringUpperIndexRange}).", LogLevel.Error); else @@ -666,9 +643,9 @@ namespace TrainerMod case "world_setminelevel": if (args.Any()) { - if (args[0].IsInt()) + if (int.TryParse(args[0], out int level)) { - int level = Math.Max(1, args[0].ToInt()); + level = Math.Max(1, level); this.Monitor.Log($"OK, warping you to mine level {level}.", LogLevel.Info); Game1.enterMine(true, level, ""); } diff --git a/src/TrainerMod/TrainerMod.csproj b/src/TrainerMod/TrainerMod.csproj index 1457ac2b..a6303767 100644 --- a/src/TrainerMod/TrainerMod.csproj +++ b/src/TrainerMod/TrainerMod.csproj @@ -22,7 +22,7 @@ 4 x86 false - 6 + 7 true @@ -53,7 +53,6 @@ Properties\GlobalAssemblyInfo.cs - -- cgit From da630efc1d5c95816493c2969736ae883e723757 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Mar 2017 14:15:50 -0400 Subject: downgrade to .NET Framework 4.0 for better compatibility on Windows 7–8.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + .../PlatformAssemblyMap.cs | 2 +- .../StardewModdingAPI.AssemblyRewriters.csproj | 14 +++++------ .../packages.config | 2 +- .../StardewModdingAPI.Installer.csproj | 2 +- src/StardewModdingAPI/App.config | 2 +- .../Events/ContentEventHandler.cs | 8 ++++++ src/StardewModdingAPI/Events/ContentEvents.cs | 2 +- .../Framework/InternalExtensions.cs | 29 ++++++++++++++++++++-- .../Framework/Reflection/PrivateProperty.cs | 4 +-- src/StardewModdingAPI/Framework/UpdateHelper.cs | 7 +++--- src/StardewModdingAPI/Program.cs | 24 ++++++------------ src/StardewModdingAPI/StardewModdingAPI.csproj | 20 ++++++++------- src/StardewModdingAPI/packages.config | 4 +-- src/TrainerMod/TrainerMod.csproj | 4 +-- src/TrainerMod/packages.config | 2 +- 16 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 src/StardewModdingAPI/Events/ContentEventHandler.cs (limited to 'src') diff --git a/release-notes.md b/release-notes.md index f91ef733..01d49dd7 100644 --- a/release-notes.md +++ b/release-notes.md @@ -20,6 +20,7 @@ For players: * Simplified error messages when a mod can't be loaded. * Simple nested mod folders are now recognised by SMAPI (e.g. `ModName-1.0\ModName\manifest.json`). * Improved TrainerMod command handling & feedback. +* Reduced minimum .NET Framework version to 4.0 for improved compatibility on Windows 7–8.1. * Fixed game's debug output being shown in the console for all users. * Fixed the game-outdated error not pausing before exit. * Fixed installer errors for some players when deleting files. diff --git a/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs b/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs index fce2b187..3ca90149 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs @@ -49,7 +49,7 @@ namespace StardewModdingAPI.AssemblyRewriters // cache assembly metadata this.Targets = targetAssemblies; this.TargetReferences = this.Targets.ToDictionary(assembly => assembly, assembly => AssemblyNameReference.Parse(assembly.FullName)); - this.TargetModules = this.Targets.ToDictionary(assembly => assembly, assembly => ModuleDefinition.ReadModule(assembly.Modules.Single().FullyQualifiedName)); + this.TargetModules = this.Targets.ToDictionary(assembly => assembly, assembly => ModuleDefinition.ReadModule(assembly.GetModules().Single().FullyQualifiedName)); } } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index a3322e67..15bb15ac 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -9,7 +9,7 @@ Properties StardewModdingAPI.AssemblyRewriters StardewModdingAPI.AssemblyRewriters - v4.5 + v4.0 512 @@ -49,16 +49,16 @@ - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll - True + ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.dll - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Mdb.dll - True + ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Mdb.dll - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll - True + ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Pdb.dll + + + ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Rocks.dll diff --git a/src/StardewModdingAPI.AssemblyRewriters/packages.config b/src/StardewModdingAPI.AssemblyRewriters/packages.config index 88fbc79d..70ba1aed 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/packages.config +++ b/src/StardewModdingAPI.AssemblyRewriters/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj b/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj index e31a1452..366e1c6e 100644 --- a/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj +++ b/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj @@ -9,7 +9,7 @@ Properties StardewModdingAPI.Installer StardewModdingAPI.Installer - v4.5 + v4.0 512 true diff --git a/src/StardewModdingAPI/App.config b/src/StardewModdingAPI/App.config index 27cdf0f7..314845f7 100644 --- a/src/StardewModdingAPI/App.config +++ b/src/StardewModdingAPI/App.config @@ -1,7 +1,7 @@ - + diff --git a/src/StardewModdingAPI/Events/ContentEventHandler.cs b/src/StardewModdingAPI/Events/ContentEventHandler.cs new file mode 100644 index 00000000..2a7e75d1 --- /dev/null +++ b/src/StardewModdingAPI/Events/ContentEventHandler.cs @@ -0,0 +1,8 @@ +namespace StardewModdingAPI.Events +{ + /// Represents a method that will handle a content event. + /// The source of the event. + /// The event arguments. + /// This deviates from in allowing T to be an interface instead of a concrete class. While .NET Framework 4.5 allows that, the current .NET Framework 4.0 targeted by SMAPI to improve compatibility does not. + public delegate void ContentEventHandler(object sender, IContentEventHelper e); +} diff --git a/src/StardewModdingAPI/Events/ContentEvents.cs b/src/StardewModdingAPI/Events/ContentEvents.cs index 9418673a..339e90fd 100644 --- a/src/StardewModdingAPI/Events/ContentEvents.cs +++ b/src/StardewModdingAPI/Events/ContentEvents.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Events public static event EventHandler> AfterLocaleChanged; /// Raised when an XNB file is being read into the cache. Mods can change the data here before it's cached. - internal static event EventHandler AfterAssetLoaded; + internal static event ContentEventHandler AfterAssetLoaded; /********* diff --git a/src/StardewModdingAPI/Framework/InternalExtensions.cs b/src/StardewModdingAPI/Framework/InternalExtensions.cs index 4ca79518..46c76656 100644 --- a/src/StardewModdingAPI/Framework/InternalExtensions.cs +++ b/src/StardewModdingAPI/Framework/InternalExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using StardewModdingAPI.Events; namespace StardewModdingAPI.Framework { @@ -59,12 +60,36 @@ namespace StardewModdingAPI.Framework /// The event handlers. /// The event sender. /// The event arguments. - public static void SafelyRaiseGenericEvent(this IMonitor monitor, string name, IEnumerable handlers, object sender, TEventArgs args) + public static void SafelyRaiseGenericEvent(this IMonitor monitor, string name, IEnumerable handlers, object sender, TEventArgs args) where TEventArgs : EventArgs { if (handlers == null) return; - foreach (EventHandler handler in Enumerable.Cast>(handlers)) + foreach (EventHandler handler in handlers.Cast>()) + { + try + { + handler.Invoke(sender, args); + } + catch (Exception ex) + { + monitor.Log($"A mod failed handling the {name} event:\n{ex.GetLogSummary()}", LogLevel.Error); + } + } + } + + /// Safely raise an event, and intercept any exceptions thrown by its handlers. + /// Encapsulates monitoring and logging. + /// The event name for error messages. + /// The event handlers. + /// The event sender. + /// The event arguments. + public static void SafelyRaiseGenericEvent(this IMonitor monitor, string name, IEnumerable handlers, object sender, IContentEventHelper args) + { + if (handlers == null) + return; + + foreach (ContentEventHandler handler in handlers.Cast()) { try { diff --git a/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs b/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs index 08204b7e..d89e8e44 100644 --- a/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs +++ b/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs @@ -60,7 +60,7 @@ namespace StardewModdingAPI.Framework.Reflection { try { - return (TValue)this.PropertyInfo.GetValue(this.Parent); + return (TValue)this.PropertyInfo.GetValue(this.Parent, null); } catch (InvalidCastException) { @@ -78,7 +78,7 @@ namespace StardewModdingAPI.Framework.Reflection { try { - this.PropertyInfo.SetValue(this.Parent, value); + this.PropertyInfo.SetValue(this.Parent, value, null); } catch (InvalidCastException) { diff --git a/src/StardewModdingAPI/Framework/UpdateHelper.cs b/src/StardewModdingAPI/Framework/UpdateHelper.cs index e01e55c8..342a08cf 100644 --- a/src/StardewModdingAPI/Framework/UpdateHelper.cs +++ b/src/StardewModdingAPI/Framework/UpdateHelper.cs @@ -1,7 +1,6 @@ using System.IO; using System.Net; using System.Reflection; -using System.Threading.Tasks; using Newtonsoft.Json; using StardewModdingAPI.Framework.Models; @@ -15,17 +14,17 @@ namespace StardewModdingAPI.Framework *********/ /// Get the latest release from a GitHub repository. /// The name of the repository from which to fetch releases (like "cjsu/SMAPI"). - public static async Task GetLatestVersionAsync(string repository) + public static GitRelease GetLatestVersion(string repository) { // build request // (avoid HttpClient for Mac compatibility) - HttpWebRequest request = WebRequest.CreateHttp($"https://api.github.com/repos/{repository}/releases/latest"); + HttpWebRequest request = (HttpWebRequest)WebRequest.Create($"https://api.github.com/repos/{repository}/releases/latest"); AssemblyName assembly = typeof(UpdateHelper).Assembly.GetName(); request.UserAgent = $"{assembly.Name}/{assembly.Version}"; request.Accept = "application/vnd.github.v3+json"; // fetch data - using (WebResponse response = await request.GetResponseAsync()) + using (WebResponse response = request.GetResponse()) using (Stream responseStream = response.GetResponseStream()) using (StreamReader reader = new StreamReader(responseStream)) { diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index db7a3df6..81e6518e 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -187,7 +187,7 @@ namespace StardewModdingAPI { try { - GitRelease release = UpdateHelper.GetLatestVersionAsync(Constants.GitHubRepository).Result; + GitRelease release = UpdateHelper.GetLatestVersion(Constants.GitHubRepository); ISemanticVersion latestVersion = new SemanticVersion(release.Tag); if (latestVersion.IsNewerThan(Constants.ApiVersion)) this.Monitor.Log($"You can update SMAPI from version {Constants.ApiVersion} to {latestVersion}", LogLevel.Alert); @@ -446,26 +446,18 @@ namespace StardewModdingAPI continue; } - // validate assembly + // initialise mod try { - if (modAssembly.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) == 0) + // get mod entry type + Type modEntryType = modAssembly.GetExportedTypes().FirstOrDefault(x => x.BaseType == typeof(Mod)); + if(modEntryType == null) { - this.Monitor.Log($"{skippedPrefix} because its DLL has no 'Mod' subclass.", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because its DLL has no {typeof(Mod).FullName} entry class.", LogLevel.Error); continue; } - } - catch (Exception ex) - { - this.Monitor.Log($"{skippedPrefix} because its DLL couldn't be loaded.\n{ex.GetLogSummary()}", LogLevel.Error); - continue; - } - - // initialise mod - try - { - // get implementation - TypeInfo modEntryType = modAssembly.DefinedTypes.First(x => x.BaseType == typeof(Mod)); + + // get mod class Mod mod = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); if (mod == null) { diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index dceae74e..3a2bb756 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -9,7 +9,7 @@ Properties StardewModdingAPI StardewModdingAPI - v4.5 + v4.0 512 false @@ -60,7 +60,7 @@ $(SolutionDir)\..\bin\Debug\SMAPI $(SolutionDir)\..\bin\Debug\SMAPI\StardewModdingAPI.xml true - 6 + 7 x86 @@ -79,19 +79,22 @@ - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll + ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.dll True - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Mdb.dll + ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Mdb.dll True - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll + ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Pdb.dll True + + ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Rocks.dll + - ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.8.0.3\lib\net40\Newtonsoft.Json.dll True @@ -116,6 +119,7 @@ Properties\GlobalAssemblyInfo.cs + @@ -215,12 +219,10 @@ Designer - - Designer - Always + Always diff --git a/src/StardewModdingAPI/packages.config b/src/StardewModdingAPI/packages.config index e5fa3c3a..1dee2c2a 100644 --- a/src/StardewModdingAPI/packages.config +++ b/src/StardewModdingAPI/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/src/TrainerMod/TrainerMod.csproj b/src/TrainerMod/TrainerMod.csproj index a6303767..7845bd8c 100644 --- a/src/TrainerMod/TrainerMod.csproj +++ b/src/TrainerMod/TrainerMod.csproj @@ -9,7 +9,7 @@ Properties TrainerMod TrainerMod - v4.5 + v4.0 512 @@ -39,7 +39,7 @@ - ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.8.0.3\lib\net40\Newtonsoft.Json.dll True diff --git a/src/TrainerMod/packages.config b/src/TrainerMod/packages.config index 75e68e71..2c6c3f12 100644 --- a/src/TrainerMod/packages.config +++ b/src/TrainerMod/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file -- cgit From 6f37e43a9b331f7c36f1062994e14cedf6f854c9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Mar 2017 15:18:57 -0400 Subject: use default C# version instead of specifying version --- src/StardewModdingAPI/StardewModdingAPI.csproj | 2 -- src/TrainerMod/TrainerMod.csproj | 2 -- 2 files changed, 4 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 3a2bb756..dcb299a2 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -60,7 +60,6 @@ $(SolutionDir)\..\bin\Debug\SMAPI $(SolutionDir)\..\bin\Debug\SMAPI\StardewModdingAPI.xml true - 7 x86 @@ -70,7 +69,6 @@ TRACE true true - 6 pdbonly true diff --git a/src/TrainerMod/TrainerMod.csproj b/src/TrainerMod/TrainerMod.csproj index 7845bd8c..c66f2ab8 100644 --- a/src/TrainerMod/TrainerMod.csproj +++ b/src/TrainerMod/TrainerMod.csproj @@ -22,7 +22,6 @@ 4 x86 false - 7 true @@ -33,7 +32,6 @@ prompt 4 false - 6 true x86 -- cgit From 06871a0603fa59ca6734fd913e118a4fe4a4c64c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Mar 2017 16:17:02 -0400 Subject: avoid C# 7 until MonoDevelop supports it --- src/TrainerMod/TrainerMod.cs | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index d6990a14..7187a358 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -276,7 +276,8 @@ namespace TrainerMod case "player_setspeed": if (args.Any()) { - if (int.TryParse(args[0], out int addedSpeed)) + int addedSpeed; + if (int.TryParse(args[0], out addedSpeed)) { Game1.player.addedSpeed = addedSpeed; this.Monitor.Log($"OK, your added speed is now {Game1.player.addedSpeed}.", LogLevel.Info); @@ -296,7 +297,8 @@ namespace TrainerMod if (validTargets.Contains(target)) { string[] colorHexes = args[1].Split(new[] { ',' }, 3); - if (int.TryParse(colorHexes[0], out int r) && int.TryParse(colorHexes[1], out int g) && int.TryParse(colorHexes[2], out int b)) + int r, g, b; + if (int.TryParse(colorHexes[0], out r) && int.TryParse(colorHexes[1], out g) && int.TryParse(colorHexes[2], out b)) { Color color = new Color(r, g, b); switch (target) @@ -332,7 +334,8 @@ namespace TrainerMod string[] validTargets = { "hair", "shirt", "skin", "acc", "shoe", "swim", "gender" }; if (validTargets.Contains(target)) { - if (int.TryParse(args[1], out int styleID)) + int styleID; + if (int.TryParse(args[1], out styleID)) { switch (target) { @@ -403,7 +406,8 @@ namespace TrainerMod case "world_freezetime": if (args.Any()) { - if (int.TryParse(args[0], out int value)) + int value; + if (int.TryParse(args[0], out value)) { if (value == 0 || value == 1) { @@ -428,7 +432,8 @@ namespace TrainerMod case "world_settime": if (args.Any()) { - if (int.TryParse(args[0], out int time)) + int time; + if (int.TryParse(args[0], out time)) { if (time <= 2600 && time >= 600) { @@ -449,7 +454,8 @@ namespace TrainerMod case "world_setday": if (args.Any()) { - if (int.TryParse(args[0], out int day)) + int day; + if (int.TryParse(args[0], out day)) { if (day <= 28 && day > 0) { @@ -496,7 +502,8 @@ namespace TrainerMod else { this.InfiniteHealth = false; - if (int.TryParse(amountStr, out int amount)) + int amount; + if (int.TryParse(amountStr, out amount)) { Game1.player.health = amount; this.Monitor.Log($"OK, you now have {Game1.player.health} health.", LogLevel.Info); @@ -512,7 +519,8 @@ namespace TrainerMod case "player_setmaxhealth": if (args.Any()) { - if (int.TryParse(args[0], out int maxHealth)) + int maxHealth; + if (int.TryParse(args[0], out maxHealth)) { Game1.player.maxHealth = maxHealth; this.Monitor.Log($"OK, you now have {Game1.player.maxHealth} max health.", LogLevel.Info); @@ -527,7 +535,8 @@ namespace TrainerMod case "player_setimmunity": if (args.Any()) { - if (int.TryParse(args[0], out int amount)) + int amount; + if (int.TryParse(args[0], out amount)) { Game1.player.immunity = amount; this.Monitor.Log($"OK, you now have {Game1.player.immunity} immunity.", LogLevel.Info); @@ -542,7 +551,8 @@ namespace TrainerMod case "player_additem": if (args.Any()) { - if (int.TryParse(args[0], out int itemID)) + int itemID; + if (int.TryParse(args[0], out itemID)) { int count = 1; int quality = 0; @@ -580,7 +590,8 @@ namespace TrainerMod case "player_addmelee": if (args.Any()) { - if (int.TryParse(args[0], out int weaponID)) + int weaponID; + if (int.TryParse(args[0], out weaponID)) { MeleeWeapon weapon = new MeleeWeapon(weaponID); if (weapon.Name == null) @@ -601,7 +612,8 @@ namespace TrainerMod case "player_addring": if (args.Any()) { - if (int.TryParse(args[0], out int ringID)) + int ringID; + if (int.TryParse(args[0], out ringID)) { if (ringID < Ring.ringLowerIndexRange || ringID > Ring.ringUpperIndexRange) this.Monitor.Log($"There is no such ring ID (must be between {Ring.ringLowerIndexRange} and {Ring.ringUpperIndexRange}).", LogLevel.Error); @@ -643,7 +655,8 @@ namespace TrainerMod case "world_setminelevel": if (args.Any()) { - if (int.TryParse(args[0], out int level)) + int level; + if (int.TryParse(args[0], out level)) { level = Math.Max(1, level); this.Monitor.Log($"OK, warping you to mine level {level}.", LogLevel.Info); -- cgit From 307304a03edb82f3a1f5cfa6c47cb17687560ccb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Mar 2017 18:16:44 -0400 Subject: revert all projects except installer to .NET Framework 4.5 This caused obscure invalid-IL crashes when compiled through MonoDevelop on Linux. --- release-notes.md | 1 - .../PlatformAssemblyMap.cs | 2 +- .../StardewModdingAPI.AssemblyRewriters.csproj | 14 +++++------ .../packages.config | 2 +- src/StardewModdingAPI/App.config | 2 +- .../Events/ContentEventHandler.cs | 8 ------ src/StardewModdingAPI/Events/ContentEvents.cs | 2 +- .../Framework/InternalExtensions.cs | 29 ++-------------------- .../Framework/Reflection/PrivateProperty.cs | 4 +-- src/StardewModdingAPI/Framework/UpdateHelper.cs | 7 +++--- src/StardewModdingAPI/Program.cs | 24 ++++++++++++------ src/StardewModdingAPI/StardewModdingAPI.csproj | 20 +++++++-------- src/StardewModdingAPI/packages.config | 4 +-- src/TrainerMod/TrainerMod.csproj | 4 +-- src/TrainerMod/packages.config | 2 +- 15 files changed, 49 insertions(+), 76 deletions(-) delete mode 100644 src/StardewModdingAPI/Events/ContentEventHandler.cs (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 01d49dd7..f91ef733 100644 --- a/release-notes.md +++ b/release-notes.md @@ -20,7 +20,6 @@ For players: * Simplified error messages when a mod can't be loaded. * Simple nested mod folders are now recognised by SMAPI (e.g. `ModName-1.0\ModName\manifest.json`). * Improved TrainerMod command handling & feedback. -* Reduced minimum .NET Framework version to 4.0 for improved compatibility on Windows 7–8.1. * Fixed game's debug output being shown in the console for all users. * Fixed the game-outdated error not pausing before exit. * Fixed installer errors for some players when deleting files. diff --git a/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs b/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs index 3ca90149..fce2b187 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs @@ -49,7 +49,7 @@ namespace StardewModdingAPI.AssemblyRewriters // cache assembly metadata this.Targets = targetAssemblies; this.TargetReferences = this.Targets.ToDictionary(assembly => assembly, assembly => AssemblyNameReference.Parse(assembly.FullName)); - this.TargetModules = this.Targets.ToDictionary(assembly => assembly, assembly => ModuleDefinition.ReadModule(assembly.GetModules().Single().FullyQualifiedName)); + this.TargetModules = this.Targets.ToDictionary(assembly => assembly, assembly => ModuleDefinition.ReadModule(assembly.Modules.Single().FullyQualifiedName)); } } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 15bb15ac..a3322e67 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -9,7 +9,7 @@ Properties StardewModdingAPI.AssemblyRewriters StardewModdingAPI.AssemblyRewriters - v4.0 + v4.5 512 @@ -49,16 +49,16 @@ - ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.dll + ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll + True - ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Mdb.dll + ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Mdb.dll + True - ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Pdb.dll - - - ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Rocks.dll + ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll + True diff --git a/src/StardewModdingAPI.AssemblyRewriters/packages.config b/src/StardewModdingAPI.AssemblyRewriters/packages.config index 70ba1aed..88fbc79d 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/packages.config +++ b/src/StardewModdingAPI.AssemblyRewriters/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/StardewModdingAPI/App.config b/src/StardewModdingAPI/App.config index 314845f7..27cdf0f7 100644 --- a/src/StardewModdingAPI/App.config +++ b/src/StardewModdingAPI/App.config @@ -1,7 +1,7 @@ - + diff --git a/src/StardewModdingAPI/Events/ContentEventHandler.cs b/src/StardewModdingAPI/Events/ContentEventHandler.cs deleted file mode 100644 index 2a7e75d1..00000000 --- a/src/StardewModdingAPI/Events/ContentEventHandler.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace StardewModdingAPI.Events -{ - /// Represents a method that will handle a content event. - /// The source of the event. - /// The event arguments. - /// This deviates from in allowing T to be an interface instead of a concrete class. While .NET Framework 4.5 allows that, the current .NET Framework 4.0 targeted by SMAPI to improve compatibility does not. - public delegate void ContentEventHandler(object sender, IContentEventHelper e); -} diff --git a/src/StardewModdingAPI/Events/ContentEvents.cs b/src/StardewModdingAPI/Events/ContentEvents.cs index 339e90fd..9418673a 100644 --- a/src/StardewModdingAPI/Events/ContentEvents.cs +++ b/src/StardewModdingAPI/Events/ContentEvents.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Events public static event EventHandler> AfterLocaleChanged; /// Raised when an XNB file is being read into the cache. Mods can change the data here before it's cached. - internal static event ContentEventHandler AfterAssetLoaded; + internal static event EventHandler AfterAssetLoaded; /********* diff --git a/src/StardewModdingAPI/Framework/InternalExtensions.cs b/src/StardewModdingAPI/Framework/InternalExtensions.cs index 46c76656..4ca79518 100644 --- a/src/StardewModdingAPI/Framework/InternalExtensions.cs +++ b/src/StardewModdingAPI/Framework/InternalExtensions.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using StardewModdingAPI.Events; namespace StardewModdingAPI.Framework { @@ -60,36 +59,12 @@ namespace StardewModdingAPI.Framework /// The event handlers. /// The event sender. /// The event arguments. - public static void SafelyRaiseGenericEvent(this IMonitor monitor, string name, IEnumerable handlers, object sender, TEventArgs args) where TEventArgs : EventArgs + public static void SafelyRaiseGenericEvent(this IMonitor monitor, string name, IEnumerable handlers, object sender, TEventArgs args) { if (handlers == null) return; - foreach (EventHandler handler in handlers.Cast>()) - { - try - { - handler.Invoke(sender, args); - } - catch (Exception ex) - { - monitor.Log($"A mod failed handling the {name} event:\n{ex.GetLogSummary()}", LogLevel.Error); - } - } - } - - /// Safely raise an event, and intercept any exceptions thrown by its handlers. - /// Encapsulates monitoring and logging. - /// The event name for error messages. - /// The event handlers. - /// The event sender. - /// The event arguments. - public static void SafelyRaiseGenericEvent(this IMonitor monitor, string name, IEnumerable handlers, object sender, IContentEventHelper args) - { - if (handlers == null) - return; - - foreach (ContentEventHandler handler in handlers.Cast()) + foreach (EventHandler handler in Enumerable.Cast>(handlers)) { try { diff --git a/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs b/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs index d89e8e44..08204b7e 100644 --- a/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs +++ b/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs @@ -60,7 +60,7 @@ namespace StardewModdingAPI.Framework.Reflection { try { - return (TValue)this.PropertyInfo.GetValue(this.Parent, null); + return (TValue)this.PropertyInfo.GetValue(this.Parent); } catch (InvalidCastException) { @@ -78,7 +78,7 @@ namespace StardewModdingAPI.Framework.Reflection { try { - this.PropertyInfo.SetValue(this.Parent, value, null); + this.PropertyInfo.SetValue(this.Parent, value); } catch (InvalidCastException) { diff --git a/src/StardewModdingAPI/Framework/UpdateHelper.cs b/src/StardewModdingAPI/Framework/UpdateHelper.cs index 342a08cf..e01e55c8 100644 --- a/src/StardewModdingAPI/Framework/UpdateHelper.cs +++ b/src/StardewModdingAPI/Framework/UpdateHelper.cs @@ -1,6 +1,7 @@ using System.IO; using System.Net; using System.Reflection; +using System.Threading.Tasks; using Newtonsoft.Json; using StardewModdingAPI.Framework.Models; @@ -14,17 +15,17 @@ namespace StardewModdingAPI.Framework *********/ /// Get the latest release from a GitHub repository. /// The name of the repository from which to fetch releases (like "cjsu/SMAPI"). - public static GitRelease GetLatestVersion(string repository) + public static async Task GetLatestVersionAsync(string repository) { // build request // (avoid HttpClient for Mac compatibility) - HttpWebRequest request = (HttpWebRequest)WebRequest.Create($"https://api.github.com/repos/{repository}/releases/latest"); + HttpWebRequest request = WebRequest.CreateHttp($"https://api.github.com/repos/{repository}/releases/latest"); AssemblyName assembly = typeof(UpdateHelper).Assembly.GetName(); request.UserAgent = $"{assembly.Name}/{assembly.Version}"; request.Accept = "application/vnd.github.v3+json"; // fetch data - using (WebResponse response = request.GetResponse()) + using (WebResponse response = await request.GetResponseAsync()) using (Stream responseStream = response.GetResponseStream()) using (StreamReader reader = new StreamReader(responseStream)) { diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 81e6518e..db7a3df6 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -187,7 +187,7 @@ namespace StardewModdingAPI { try { - GitRelease release = UpdateHelper.GetLatestVersion(Constants.GitHubRepository); + GitRelease release = UpdateHelper.GetLatestVersionAsync(Constants.GitHubRepository).Result; ISemanticVersion latestVersion = new SemanticVersion(release.Tag); if (latestVersion.IsNewerThan(Constants.ApiVersion)) this.Monitor.Log($"You can update SMAPI from version {Constants.ApiVersion} to {latestVersion}", LogLevel.Alert); @@ -446,18 +446,26 @@ namespace StardewModdingAPI continue; } - // initialise mod + // validate assembly try { - // get mod entry type - Type modEntryType = modAssembly.GetExportedTypes().FirstOrDefault(x => x.BaseType == typeof(Mod)); - if(modEntryType == null) + if (modAssembly.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) == 0) { - this.Monitor.Log($"{skippedPrefix} because its DLL has no {typeof(Mod).FullName} entry class.", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because its DLL has no 'Mod' subclass.", LogLevel.Error); continue; } - - // get mod class + } + catch (Exception ex) + { + this.Monitor.Log($"{skippedPrefix} because its DLL couldn't be loaded.\n{ex.GetLogSummary()}", LogLevel.Error); + continue; + } + + // initialise mod + try + { + // get implementation + TypeInfo modEntryType = modAssembly.DefinedTypes.First(x => x.BaseType == typeof(Mod)); Mod mod = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); if (mod == null) { diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index dcb299a2..99666f08 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -9,7 +9,7 @@ Properties StardewModdingAPI StardewModdingAPI - v4.0 + v4.5 512 false @@ -77,22 +77,19 @@ - ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.dll + ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll True - ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Mdb.dll + ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Mdb.dll True - ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Pdb.dll + ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll True - - ..\packages\Mono.Cecil.0.9.6.4\lib\net40\Mono.Cecil.Rocks.dll - - ..\packages\Newtonsoft.Json.8.0.3\lib\net40\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True @@ -117,7 +114,6 @@ Properties\GlobalAssemblyInfo.cs - @@ -217,10 +213,12 @@ Designer + + Designer + Always - Always @@ -282,4 +280,4 @@ - \ No newline at end of file + diff --git a/src/StardewModdingAPI/packages.config b/src/StardewModdingAPI/packages.config index 1dee2c2a..e5fa3c3a 100644 --- a/src/StardewModdingAPI/packages.config +++ b/src/StardewModdingAPI/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/src/TrainerMod/TrainerMod.csproj b/src/TrainerMod/TrainerMod.csproj index c66f2ab8..0bd667d4 100644 --- a/src/TrainerMod/TrainerMod.csproj +++ b/src/TrainerMod/TrainerMod.csproj @@ -9,7 +9,7 @@ Properties TrainerMod TrainerMod - v4.0 + v4.5 512 @@ -37,7 +37,7 @@ - ..\packages\Newtonsoft.Json.8.0.3\lib\net40\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True diff --git a/src/TrainerMod/packages.config b/src/TrainerMod/packages.config index 2c6c3f12..75e68e71 100644 --- a/src/TrainerMod/packages.config +++ b/src/TrainerMod/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file -- cgit From 79e63fde9a54717378bb29ea97d7b90e6bf56091 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Mar 2017 19:36:18 -0400 Subject: validate .NET Framework 4.5+ is installed on Windows in SMAPI installer --- release-notes.md | 3 +- .../InteractiveInstaller.cs | 34 ++++++++++++++++++++++ src/StardewModdingAPI.sln | 4 +-- 3 files changed, 38 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index f91ef733..3f7b9f9e 100644 --- a/release-notes.md +++ b/release-notes.md @@ -15,7 +15,8 @@ See [log](https://github.com/Pathoschild/SMAPI/compare/1.8...1.9). For players: * Updated for Stardew Valley 1.2. Most mods are now rewritten for compatibility; some mods require an update from their authors. -* Added automatic detection of incompatible mods so they're disabled before they cause problems. +* SMAPI now automatically detects incompatible mods and disables them before they cause problems. +* The installer now automatically detects if you need to update .NET Framework before SMAPI will work. * Simplified log filename. * Simplified error messages when a mod can't be loaded. * Simple nested mod folders are now recognised by SMAPI (e.g. `ModName-1.0\ModName\manifest.json`). diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 7dcd88fd..52d0642a 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -15,6 +15,9 @@ namespace StardewModdingApi.Installer /********* ** Properties *********/ + /// The value that represents Windows 7. + private readonly Version Windows7Version = new Version(6, 1); + /// The default file paths where Stardew Valley can be installed. /// The target platform. /// Derived from the crossplatform mod config: https://github.com/Pathoschild/Stardew.ModBuildConfig. @@ -152,6 +155,21 @@ namespace StardewModdingApi.Installer Console.ReadLine(); return; } + + /**** + ** validate .NET Framework version + ****/ + if (platform == Platform.Windows && !this.HasNetFramework45(platform)) + { + this.PrintError(Environment.OSVersion.Version >= this.Windows7Version + ? "Please install the latest version of .NET Framework before installing SMAPI." // Windows 7+ + : "Please install .NET Framework 4.5 before installing SMAPI." // Windows Vista or earlier + ); + this.PrintError("See the download page at https://www.microsoft.com/net/download/framework for details."); + Console.ReadLine(); + return; + } + Console.WriteLine(); /**** @@ -360,6 +378,22 @@ namespace StardewModdingApi.Installer Console.WriteLine(text); } + /// Get whether the current system has .NET Framework 4.5 or later installed. + /// The current platform. + /// The current platform is not Windows. + private bool HasNetFramework45(Platform platform) + { + switch (platform) + { + case Platform.Windows: + using (RegistryKey versionKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full")) + return versionKey?.GetValue("Release") != null; // .NET Framework 4.5+ + + default: + throw new NotSupportedException("The installed .NET Framework version can only be checked on Windows."); + } + } + /// Interactively delete a file or folder path, and block until deletion completes. /// The file or folder path. private void InteractivelyDelete(string path) diff --git a/src/StardewModdingAPI.sln b/src/StardewModdingAPI.sln index 8ab297ed..441b51a9 100644 --- a/src/StardewModdingAPI.sln +++ b/src/StardewModdingAPI.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.4 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrainerMod", "TrainerMod\TrainerMod.csproj", "{28480467-1A48-46A7-99F8-236D95225359}" EndProject -- cgit From 33df1e8c94aef9cb5517524cde1edbef0cefaf9f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Mar 2017 19:50:08 -0400 Subject: remove unofficial patch for Chest Label System (no longer available) --- src/StardewModdingAPI/StardewModdingAPI.config.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index 020633e6..9ecf2912 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -25,9 +25,7 @@ generally shouldn't change this file unless necessary. "Name": "Chest Label System", "ID": "SPDChestLabel", "UpperVersion": "1.5", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/242", - "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", - "ForceCompatibleVersion": "^1.5-EntoPatch" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/242" }, /* versions not compatible with Stardew Valley 1.2+ */ -- cgit From 104aa314127bd816d5dbcac8c57ecb84b12f20d1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Mar 2017 20:48:02 -0400 Subject: let players override SMAPI incompatible-code detection if needed --- README.md | 2 +- src/StardewModdingAPI/Framework/AssemblyLoader.cs | 20 +++-- src/StardewModdingAPI/Framework/ModRegistry.cs | 20 ++--- .../Framework/Models/IncompatibleMod.cs | 65 --------------- .../Framework/Models/ModCompatibility.cs | 65 +++++++++++++++ .../Framework/Models/ModCompatibilityType.cs | 12 +++ src/StardewModdingAPI/Framework/Models/SConfig.cs | 4 +- src/StardewModdingAPI/Program.cs | 8 +- .../StardewModdingAPI.config.json | 95 ++++++++++++++-------- src/StardewModdingAPI/StardewModdingAPI.csproj | 3 +- 10 files changed, 171 insertions(+), 123 deletions(-) delete mode 100644 src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs create mode 100644 src/StardewModdingAPI/Framework/Models/ModCompatibility.cs create mode 100644 src/StardewModdingAPI/Framework/Models/ModCompatibilityType.cs (limited to 'src') diff --git a/README.md b/README.md index 87383ffb..c90e62cc 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ field | purpose ----- | ------- `DeveloperMode` | Default `false` (except in _SMAPI for developers_ releases). Whether to enable features intended for mod developers. Currently this only makes `TRACE`-level messages appear in the console. `CheckForUpdates` | Default `true`. Whether SMAPI should check for a newer version when you load the game. If a new version is available, a small message will appear in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background. -`IncompatibleMods` | A list of mod versions SMAPI considers incompatible and will refuse to load. Changing this field is not recommended. +`ModCompatibility` | A list of mod versions SMAPI should consider compatible or broken regardless of whether it detects incompatible code. Each record can be set to `AssumeCompatible` or `AssumeBroken`. Changing this field is not recommended and may destabilise your game. ### Command-line arguments SMAPI recognises the following command-line arguments. These are intended for internal use and may diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index bc56de01..c7ad3da4 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -54,9 +54,10 @@ namespace StardewModdingAPI.Framework /// Preprocess and load an assembly. /// The assembly file path. + /// Assume the mod is compatible, even if incompatible code is detected. /// Returns the rewrite metadata for the preprocessed assembly. /// An incompatible CIL instruction was found while rewriting the assembly. - public Assembly Load(string assemblyPath) + public Assembly Load(string assemblyPath, bool assumeCompatible) { // get referenced local assemblies AssemblyParseResult[] assemblies; @@ -73,7 +74,7 @@ namespace StardewModdingAPI.Framework Assembly lastAssembly = null; foreach (AssemblyParseResult assembly in assemblies) { - bool changed = this.RewriteAssembly(assembly.Definition); + bool changed = this.RewriteAssembly(assembly.Definition, assumeCompatible); if (changed) { this.Monitor.Log($"Loading {assembly.File.Name} (rewritten in memory)...", LogLevel.Trace); @@ -159,12 +160,13 @@ namespace StardewModdingAPI.Framework ****/ /// Rewrite the types referenced by an assembly. /// The assembly to rewrite. + /// Assume the mod is compatible, even if incompatible code is detected. /// Returns whether the assembly was modified. /// An incompatible CIL instruction was found while rewriting the assembly. - private bool RewriteAssembly(AssemblyDefinition assembly) + private bool RewriteAssembly(AssemblyDefinition assembly, bool assumeCompatible) { ModuleDefinition module = assembly.MainModule; - HashSet loggedRewrites = new HashSet(); + HashSet loggedMessages = new HashSet(); // swap assembly references if needed (e.g. XNA => MonoGame) bool platformChanged = false; @@ -173,7 +175,7 @@ namespace StardewModdingAPI.Framework // remove old assembly reference if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name)) { - this.LogOnce(this.Monitor, loggedRewrites, $"Rewriting {assembly.Name.Name} for OS..."); + this.LogOnce(this.Monitor, loggedMessages, $"Rewriting {assembly.Name.Name} for OS..."); platformChanged = true; module.AssemblyReferences.RemoveAt(i); i--; @@ -203,13 +205,17 @@ namespace StardewModdingAPI.Framework // throw exception if instruction is incompatible but can't be rewritten IInstructionFinder finder = finders.FirstOrDefault(p => p.IsMatch(instruction, platformChanged)); if (finder != null) - throw new IncompatibleInstructionException(finder.NounPhrase, $"Found an incompatible CIL instruction ({finder.NounPhrase}) while loading assembly {assembly.Name.Name}."); + { + if (!assumeCompatible) + throw new IncompatibleInstructionException(finder.NounPhrase, $"Found an incompatible CIL instruction ({finder.NounPhrase}) while loading assembly {assembly.Name.Name}."); + this.LogOnce(this.Monitor, loggedMessages, $"Found an incompatible CIL instruction ({finder.NounPhrase}) while loading assembly {assembly.Name.Name}, but SMAPI is configured to allow it anyway. The mod may crash or behave unexpectedly.", LogLevel.Warn); + } // rewrite instruction if needed IInstructionRewriter rewriter = rewriters.FirstOrDefault(p => p.IsMatch(instruction, platformChanged)); if (rewriter != null) { - this.LogOnce(this.Monitor, loggedRewrites, $"Rewriting {assembly.Name.Name} to fix {rewriter.NounPhrase}..."); + this.LogOnce(this.Monitor, loggedMessages, $"Rewriting {assembly.Name.Name} to fix {rewriter.NounPhrase}..."); rewriter.Rewrite(module, cil, instruction, this.AssemblyMap); anyRewritten = true; } diff --git a/src/StardewModdingAPI/Framework/ModRegistry.cs b/src/StardewModdingAPI/Framework/ModRegistry.cs index 233deb3c..f015b7ba 100644 --- a/src/StardewModdingAPI/Framework/ModRegistry.cs +++ b/src/StardewModdingAPI/Framework/ModRegistry.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Text.RegularExpressions; using StardewModdingAPI.Framework.Models; namespace StardewModdingAPI.Framework @@ -20,18 +19,18 @@ namespace StardewModdingAPI.Framework /// The friendly mod names treated as deprecation warning sources (assembly full name => mod name). private readonly IDictionary ModNamesByAssembly = new Dictionary(); - /// The mod versions which should be disabled due to incompatibility. - private readonly IncompatibleMod[] IncompatibleMods; + /// Metadata about mods that SMAPI should assume is compatible or broken, regardless of whether it detects incompatible code. + private readonly ModCompatibility[] CompatibilityRecords; /********* ** Public methods *********/ /// Construct an instance. - /// The mod versions which should be disabled due to incompatibility. - public ModRegistry(IEnumerable incompatibleMods) + /// Metadata about mods that SMAPI should assume is compatible or broken, regardless of whether it detects incompatible code. + public ModRegistry(IEnumerable compatibilityRecords) { - this.IncompatibleMods = incompatibleMods.ToArray(); + this.CompatibilityRecords = compatibilityRecords.ToArray(); } @@ -127,21 +126,20 @@ namespace StardewModdingAPI.Framework return null; } - /// Get a record indicating why a mod is incompatible (if applicable). + /// Get metadata that indicates whether SMAPI should assume the mod is compatible or broken, regardless of whether it detects incompatible code. /// The mod manifest. /// Returns the incompatibility record if applicable, else null. - internal IncompatibleMod GetIncompatibilityRecord(IManifest manifest) + internal ModCompatibility GetCompatibilityRecord(IManifest manifest) { string key = !string.IsNullOrWhiteSpace(manifest.UniqueID) ? manifest.UniqueID : manifest.EntryDll; return ( - from mod in this.IncompatibleMods + from mod in this.CompatibilityRecords where mod.ID == key && (mod.LowerSemanticVersion == null || !manifest.Version.IsOlderThan(mod.LowerSemanticVersion)) && !manifest.Version.IsNewerThan(mod.UpperSemanticVersion) - && (string.IsNullOrWhiteSpace(mod.ForceCompatibleVersion) || !Regex.IsMatch(manifest.Version.ToString(), mod.ForceCompatibleVersion, RegexOptions.IgnoreCase)) select mod ).FirstOrDefault(); } } -} \ No newline at end of file +} diff --git a/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs b/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs deleted file mode 100644 index 29e18ddb..00000000 --- a/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Runtime.Serialization; -using Newtonsoft.Json; - -namespace StardewModdingAPI.Framework.Models -{ - /// Contains abstract metadata about an incompatible mod. - internal class IncompatibleMod - { - /********* - ** Accessors - *********/ - /**** - ** From config - ****/ - /// The unique mod ID. - public string ID { get; set; } - - /// The mod name. - public string Name { get; set; } - - /// The oldest incompatible mod version, or null for all past versions. - public string LowerVersion { get; set; } - - /// The most recent incompatible mod version. - public string UpperVersion { get; set; } - - /// The URL the user can check for an official updated version. - public string UpdateUrl { get; set; } - - /// The URL the user can check for an unofficial updated version. - public string UnofficialUpdateUrl { get; set; } - - /// A regular expression matching version strings to consider compatible, even if they technically precede . - public string ForceCompatibleVersion { get; set; } - - /// The reason phrase to show in the warning, or null to use the default value. - /// "this version is incompatible with the latest version of the game" - public string ReasonPhrase { get; set; } - - - /**** - ** Injected - ****/ - /// The semantic version corresponding to . - [JsonIgnore] - public ISemanticVersion LowerSemanticVersion { get; set; } - - /// The semantic version corresponding to . - [JsonIgnore] - public ISemanticVersion UpperSemanticVersion { get; set; } - - - /********* - ** Private methods - *********/ - /// The method called when the model finishes deserialising. - /// The deserialisation context. - [OnDeserialized] - private void OnDeserialized(StreamingContext context) - { - this.LowerSemanticVersion = this.LowerVersion != null ? new SemanticVersion(this.LowerVersion) : null; - this.UpperSemanticVersion = this.UpperVersion != null ? new SemanticVersion(this.UpperVersion) : null; - } - } -} diff --git a/src/StardewModdingAPI/Framework/Models/ModCompatibility.cs b/src/StardewModdingAPI/Framework/Models/ModCompatibility.cs new file mode 100644 index 00000000..1e71dae0 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/ModCompatibility.cs @@ -0,0 +1,65 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json; + +namespace StardewModdingAPI.Framework.Models +{ + /// Metadata about a mod version that SMAPI should assume is compatible or broken, regardless of whether it detects incompatible code. + internal class ModCompatibility + { + /********* + ** Accessors + *********/ + /**** + ** From config + ****/ + /// The unique mod ID. + public string ID { get; set; } + + /// The mod name. + public string Name { get; set; } + + /// The oldest incompatible mod version, or null for all past versions. + public string LowerVersion { get; set; } + + /// The most recent incompatible mod version. + public string UpperVersion { get; set; } + + /// The URL the user can check for an official updated version. + public string UpdateUrl { get; set; } + + /// The URL the user can check for an unofficial updated version. + public string UnofficialUpdateUrl { get; set; } + + /// The reason phrase to show in the warning, or null to use the default value. + /// "this version is incompatible with the latest version of the game" + public string ReasonPhrase { get; set; } + + /// Indicates how SMAPI should consider the mod. + public ModCompatibilityType Compatibility { get; set; } + + + /**** + ** Injected + ****/ + /// The semantic version corresponding to . + [JsonIgnore] + public ISemanticVersion LowerSemanticVersion { get; set; } + + /// The semantic version corresponding to . + [JsonIgnore] + public ISemanticVersion UpperSemanticVersion { get; set; } + + + /********* + ** Private methods + *********/ + /// The method called when the model finishes deserialising. + /// The deserialisation context. + [OnDeserialized] + private void OnDeserialized(StreamingContext context) + { + this.LowerSemanticVersion = this.LowerVersion != null ? new SemanticVersion(this.LowerVersion) : null; + this.UpperSemanticVersion = this.UpperVersion != null ? new SemanticVersion(this.UpperVersion) : null; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Models/ModCompatibilityType.cs b/src/StardewModdingAPI/Framework/Models/ModCompatibilityType.cs new file mode 100644 index 00000000..35edec5e --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/ModCompatibilityType.cs @@ -0,0 +1,12 @@ +namespace StardewModdingAPI.Framework.Models +{ + /// Indicates how SMAPI should consider a mod. + internal enum ModCompatibilityType + { + /// Assume the mod is not compatible, even if SMAPI doesn't detect any incompatible code. + AssumeBroken = 0, + + /// Assume the mod is compatible, even if SMAPI detects incompatible code. + AssumeCompatible = 1 + } +} diff --git a/src/StardewModdingAPI/Framework/Models/SConfig.cs b/src/StardewModdingAPI/Framework/Models/SConfig.cs index 558da82a..0de96297 100644 --- a/src/StardewModdingAPI/Framework/Models/SConfig.cs +++ b/src/StardewModdingAPI/Framework/Models/SConfig.cs @@ -12,7 +12,7 @@ /// Whether to check if a newer version of SMAPI is available on startup. public bool CheckForUpdates { get; set; } = true; - /// A list of mod versions which should be considered incompatible. - public IncompatibleMod[] IncompatibleMods { get; set; } + /// A list of mod versions which should be considered compatible or incompatible regardless of whether SMAPI detects incompatible code. + public ModCompatibility[] ModCompatibility { get; set; } } } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index db7a3df6..ac646b1f 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -78,7 +78,7 @@ namespace StardewModdingAPI // initialise this.Monitor = new Monitor("SMAPI", this.ConsoleManager, this.LogFile, this.ExitGameImmediately) { WriteToConsole = writeToConsole }; - this.ModRegistry = new ModRegistry(this.Settings.IncompatibleMods); + this.ModRegistry = new ModRegistry(this.Settings.ModCompatibility); this.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); } @@ -364,8 +364,8 @@ namespace StardewModdingAPI skippedPrefix = $"Skipped {manifest.Name}"; // validate compatibility - IncompatibleMod compatibility = this.ModRegistry.GetIncompatibilityRecord(manifest); - if (compatibility != null) + ModCompatibility compatibility = this.ModRegistry.GetCompatibilityRecord(manifest); + if (compatibility?.Compatibility == ModCompatibilityType.AssumeBroken) { bool hasOfficialUrl = !string.IsNullOrWhiteSpace(compatibility.UpdateUrl); bool hasUnofficialUrl = !string.IsNullOrWhiteSpace(compatibility.UnofficialUpdateUrl); @@ -433,7 +433,7 @@ namespace StardewModdingAPI Assembly modAssembly; try { - modAssembly = modAssemblyLoader.Load(assemblyPath); + modAssembly = modAssemblyLoader.Load(assemblyPath, assumeCompatible: compatibility?.Compatibility == ModCompatibilityType.AssumeCompatible); } catch (IncompatibleInstructionException ex) { diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index 9ecf2912..31514a21 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -9,7 +9,7 @@ generally shouldn't change this file unless necessary. { "DeveloperMode": true, "CheckForUpdates": true, - "IncompatibleMods": [ + "ModCompatibility": [ /* versions which crash the game */ { "Name": "NPC Map Locations", @@ -17,7 +17,8 @@ generally shouldn't change this file unless necessary. "LowerVersion": "1.42", "UpperVersion": "1.43", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/239", - "ReasonPhrase": "this version has an update check error which crashes the game" + "ReasonPhrase": "this version has an update check error which crashes the game", + "Compatibility": "AssumeBroken" }, /* versions not compatible with Stardew Valley 1.1+ */ @@ -25,7 +26,8 @@ generally shouldn't change this file unless necessary. "Name": "Chest Label System", "ID": "SPDChestLabel", "UpperVersion": "1.5", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/242" + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/242", + "Compatibility": "AssumeBroken" }, /* versions not compatible with Stardew Valley 1.2+ */ @@ -35,14 +37,16 @@ generally shouldn't change this file unless necessary. "UpperVersion": "1.1", "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)'." + "Notes": "Crashes with 'Method not found: Void StardewValley.Item.set_Name(System.String)'.", + "Compatibility": "AssumeBroken" }, { "Name": "Almighty Tool", "ID": "AlmightyTool.dll", "UpperVersion": "1.1.1", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/439", - "Notes": "Uses obsolete StardewModdingAPI.Extensions." + "Notes": "Uses obsolete StardewModdingAPI.Extensions.", + "Compatibility": "AssumeBroken" }, { "Name": "Better Sprinklers", @@ -50,189 +54,216 @@ generally shouldn't change this file unless necessary. "UpperVersion": "2.3", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/41", "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", - "Notes": "Uses obsolete StardewModdingAPI.Extensions." + "Notes": "Uses obsolete StardewModdingAPI.Extensions.", + "Compatibility": "AssumeBroken" }, { "Name": "Casks Anywhere", "ID": "CasksAnywhere", "UpperVersion": "1.1", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/878", - "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." + "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange.", + "Compatibility": "AssumeBroken" }, { "Name": "Chests Anywhere", "ID": "ChestsAnywhere", "UpperVersion": "1.8.2", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/518", - "Notes": "Crashes with 'Method not found: Void StardewValley.Menus.TextBox.set_Highlighted(Boolean)'." + "Notes": "Crashes with 'Method not found: Void StardewValley.Menus.TextBox.set_Highlighted(Boolean)'.", + "Compatibility": "AssumeBroken" }, { "Name": "Chests Anywhere", "ID": "Pathoschild.ChestsAnywhere", "UpperVersion": "1.9-beta", "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'." + "Notes": "ID changed in 1.9. Crashes with InvalidOperationException: 'The menu doesn't seem to have a player inventory'.", + "Compatibility": "AssumeBroken" }, { "Name": "CJB Automation", "ID": "CJBAutomation", "UpperVersion": "1.4", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/211", - "Notes": "Crashes with 'Method not found: Void StardewValley.Item.set_Name(System.String)'." + "Notes": "Crashes with 'Method not found: Void StardewValley.Item.set_Name(System.String)'.", + "Compatibility": "AssumeBroken" }, { "Name": "CJB Cheats Menu", "ID": "CJBCheatsMenu", "UpperVersion": "1.13", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4", - "Notes": "Uses removed Game1.borderFont." + "Notes": "Uses removed Game1.borderFont.", + "Compatibility": "AssumeBroken" }, { "Name": "CJB Item Spawner", "ID": "CJBItemSpawner", "UpperVersion": "1.6", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93", - "Notes": "Uses removed Game1.borderFont." + "Notes": "Uses removed Game1.borderFont.", + "Compatibility": "AssumeBroken" }, { "Name": "Cooking Skill", "ID": "CookingSkill", "UpperVersion": "1.0.3", "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)'." + "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)'.", + "Compatibility": "AssumeBroken" }, { "Name": "Enemy Health Bars", "ID": "SPDHealthBar", "UpperVersion": "1.7", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/193", - "Notes": "Uses obsolete GraphicsEvents.DrawTick." + "Notes": "Uses obsolete GraphicsEvents.DrawTick.", + "Compatibility": "AssumeBroken" }, { "Name": "Entoarox Framework", "ID": "eacdb74b-4080-4452-b16b-93773cda5cf9", "UpperVersion": "1.6.5", "UpdateUrl": "http://community.playstarbound.com/resources/4228", - "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)')." + "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)').", + "Compatibility": "AssumeBroken" }, { "Name": "Extended Fridge", "ID": "Mystra007ExtendedFridge", "UpperVersion": "1.0", "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'." + "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'.", + "Compatibility": "AssumeBroken" }, { "Name": "Get Dressed", "ID": "GetDressed.dll", "UpperVersion": "3.2", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/331", - "Notes": "Crashes with NullReferenceException in GameEvents.UpdateTick." + "Notes": "Crashes with NullReferenceException in GameEvents.UpdateTick.", + "Compatibility": "AssumeBroken" }, { "Name": "Lookup Anything", "ID": "LookupAnything", "UpperVersion": "1.10", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/541", - "Notes": "Crashes with FormatException when looking up NPCs." + "Notes": "Crashes with FormatException when looking up NPCs.", + "Compatibility": "AssumeBroken" }, { "Name": "Lookup Anything", "ID": "Pathoschild.LookupAnything", "UpperVersion": "1.10.1", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/541", - "Notes": "ID changed in 1.10.1. Crashes with FormatException when looking up NPCs." + "Notes": "ID changed in 1.10.1. Crashes with FormatException when looking up NPCs.", + "Compatibility": "AssumeBroken" }, { "Name": "Makeshift Multiplayer", "ID": "StardewValleyMP", "UpperVersion": "0.2.10", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/501", - "Notes": "Uses obsolete GraphicsEvents.OnPreRenderHudEventNoCheck." + "Notes": "Uses obsolete GraphicsEvents.OnPreRenderHudEventNoCheck.", + "Compatibility": "AssumeBroken" }, { "Name": "NoSoilDecay", "ID": "289dee03-5f38-4d8e-8ffc-e440198e8610", "UpperVersion": "0.5", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/237", - "Notes": "Uses Assembly.GetExecutingAssembly().Location." + "Notes": "Uses Assembly.GetExecutingAssembly().Location.", + "Compatibility": "AssumeBroken" }, { "Name": "Point-and-Plant", "ID": "PointAndPlant.dll", "UpperVersion": "1.0.2", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/572", - "Notes": "Uses obsolete StardewModdingAPI.Extensions." + "Notes": "Uses obsolete StardewModdingAPI.Extensions.", + "Compatibility": "AssumeBroken" }, { "Name": "Reusable Wallpapers", "ID": "dae1b553-2e39-43e7-8400-c7c5c836134b", "UpperVersion": "1.5", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/356", - "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." + "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange.", + "Compatibility": "AssumeBroken" }, { "Name": "Save Anywhere", "ID": "SaveAnywhere", "UpperVersion": "2.0", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/444", - "Notes": "Depends on StarDustCore." + "Notes": "Depends on StarDustCore.", + "Compatibility": "AssumeBroken" }, { "Name": "StackSplitX", "ID": "StackSplitX.dll", "UpperVersion": "1.0", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/798", - "Notes": "Uses SMAPI's internal SGame class." + "Notes": "Uses SMAPI's internal SGame class.", + "Compatibility": "AssumeBroken" }, { "Name": "StarDustCore", "ID": "StarDustCore", "UpperVersion": "1.0", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/683", - "Notes": "Crashes with 'Method not found: Void StardewModdingAPI.Command.CallCommand(System.String)'." + "Notes": "Crashes with 'Method not found: Void StardewModdingAPI.Command.CallCommand(System.String)'.", + "Compatibility": "AssumeBroken" }, { "Name": "Teleporter", "ID": "Teleporter", "UpperVersion": "1.0.2", "UpdateUrl": "http://community.playstarbound.com/resources/4374", - "Notes": "Crashes with 'InvalidOperationException: The StardewValley.Menus.MapPage object doesn't have a private 'points' instance field'." + "Notes": "Crashes with 'InvalidOperationException: The StardewValley.Menus.MapPage object doesn't have a private 'points' instance field'.", + "Compatibility": "AssumeBroken" }, { "Name": "Zoryn's Better RNG", "ID": "76b6d1e1-f7ba-4d72-8c32-5a1e6d2716f6", "UpperVersion": "1.5", "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class." + "Notes": "Uses SMAPI's internal SGame class.", + "Compatibility": "AssumeBroken" }, { "Name": "Zoryn's Calendar Anywhere", "ID": "a41c01cd-0437-43eb-944f-78cb5a53002a", "UpperVersion": "1.5", "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class." + "Notes": "Uses SMAPI's internal SGame class.", + "Compatibility": "AssumeBroken" }, { "Name": "Zoryn's Health Bars", "ID": "HealthBars.dll", "UpperVersion": "1.5", "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class." + "Notes": "Uses SMAPI's internal SGame class.", + "Compatibility": "AssumeBroken" }, { "Name": "Zoryn's Movement Mod", "ID": "8a632929-8335-484f-87dd-c29d2ba3215d", "UpperVersion": "1.5", "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class." + "Notes": "Uses SMAPI's internal SGame class.", + "Compatibility": "AssumeBroken" }, { "Name": "Zoryn's Regen Mod", "ID": "dfac4383-1b6b-4f33-ae4e-37fc23e5252e", "UpperVersion": "1.5", "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class." + "Notes": "Uses SMAPI's internal SGame class.", + "Compatibility": "AssumeBroken" } ] } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 99666f08..091b3d90 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -154,6 +154,7 @@ + @@ -176,7 +177,7 @@ - + -- cgit From abfa2022aad8478c369158e89a38eaae9bd81c96 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 16 Mar 2017 17:06:53 -0400 Subject: validate XNA 4.0+ is installed on Windows in SMAPI installer --- release-notes.md | 1 + .../InteractiveInstaller.cs | 46 +++++++++++++++++----- 2 files changed, 37 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index e8b6ccfd..9d133f30 100644 --- a/release-notes.md +++ b/release-notes.md @@ -19,6 +19,7 @@ For players: * SMAPI now detects incompatible mods and disables them before they cause problems. * SMAPI now allows mods nested into an otherwise empty parent folder (like `Mods\ModName-1.0\ModName\manifest.json`), since that's a common default behaviour when unpacking mods. * The installer now detects if you need to update .NET Framework before installing SMAPI. +* The installer now detects if you need to run the game at least once (to let it perform first-time setup) before installing SMAPI. * The console now has simpler error messages. * The console now has improved command handling & feedback. * The console no longer shows the game's debug output (unless you use a _SMAPI for developers_ build). diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 52d0642a..0e920f1f 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -157,17 +157,27 @@ namespace StardewModdingApi.Installer } /**** - ** validate .NET Framework version + ** validate Windows dependencies ****/ - if (platform == Platform.Windows && !this.HasNetFramework45(platform)) + if (platform == Platform.Windows) { - this.PrintError(Environment.OSVersion.Version >= this.Windows7Version - ? "Please install the latest version of .NET Framework before installing SMAPI." // Windows 7+ - : "Please install .NET Framework 4.5 before installing SMAPI." // Windows Vista or earlier - ); - this.PrintError("See the download page at https://www.microsoft.com/net/download/framework for details."); - Console.ReadLine(); - return; + // .NET Framework 4.5+ + if (!this.HasNetFramework45(platform)) + { + this.PrintError(Environment.OSVersion.Version >= this.Windows7Version + ? "Please install the latest version of .NET Framework before installing SMAPI." // Windows 7+ + : "Please install .NET Framework 4.5 before installing SMAPI." // Windows Vista or earlier + ); + this.PrintError("See the download page at https://www.microsoft.com/net/download/framework for details."); + Console.ReadLine(); + return; + } + if (!this.HasXNA(platform)) + { + this.PrintError("You don't seem to have XNA Framework installed. Please run the game at least once before installing SMAPI, so it can perform its first-time setup."); + Console.ReadLine(); + return; + } } Console.WriteLine(); @@ -378,7 +388,7 @@ namespace StardewModdingApi.Installer Console.WriteLine(text); } - /// Get whether the current system has .NET Framework 4.5 or later installed. + /// Get whether the current system has .NET Framework 4.5 or later installed. This only applies on Windows. /// The current platform. /// The current platform is not Windows. private bool HasNetFramework45(Platform platform) @@ -394,6 +404,22 @@ namespace StardewModdingApi.Installer } } + /// Get whether the current system has XNA Framework installed. This only applies on Windows. + /// The current platform. + /// The current platform is not Windows. + private bool HasXNA(Platform platform) + { + switch (platform) + { + case Platform.Windows: + using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\XNA\Framework")) + return key != null; // XNA Framework 4.0+ + + default: + throw new NotSupportedException("The installed XNA Framework version can only be checked on Windows."); + } + } + /// Interactively delete a file or folder path, and block until deletion completes. /// The file or folder path. private void InteractivelyDelete(string path) -- cgit From 0e67440e6390e824c8045173f0fcaa6badab2c64 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 16 Mar 2017 17:07:50 -0400 Subject: add inline documentation to StardewModdingAPI.config.json file --- .../StardewModdingAPI.config.json | 175 +++++++++++---------- 1 file changed, 94 insertions(+), 81 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index 31514a21..64b4fd53 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -1,269 +1,282 @@ /* -This file contains advanced configuration for SMAPI. You -generally shouldn't change this file unless necessary. + +This file contains advanced configuration for SMAPI. You generally shouldn't change this file. + */ { + /** + * Whether to enable features intended for mod developers. Currently this only makes TRACE-level + * messages appear in the console. + */ "DeveloperMode": true, - "CheckForUpdates": true, - "ModCompatibility": [ - /* versions which crash the game */ - { - "Name": "NPC Map Locations", - "ID": "NPCMapLocationsMod", - "LowerVersion": "1.42", - "UpperVersion": "1.43", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/239", - "ReasonPhrase": "this version has an update check error which crashes the game", - "Compatibility": "AssumeBroken" - }, - /* versions not compatible with Stardew Valley 1.1+ */ - { - "Name": "Chest Label System", - "ID": "SPDChestLabel", - "UpperVersion": "1.5", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/242", - "Compatibility": "AssumeBroken" - }, + /** + * Whether SMAPI should check for a newer version when you load the game. If a new version is + * available, a small message will appear in the console. This doesn't affect the load time even + * if your connection is offline or slow, because it happens in the background. + */ + "CheckForUpdates": true, - /* versions not compatible with Stardew Valley 1.2+ */ + /** + * A list of mod versions SMAPI should consider compatible or broken regardless of whether it + * detects incompatible code. Each record can be set to `AssumeCompatible` or `AssumeBroken`. + * Changing this field is not recommended and may destabilise your game. + */ + "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)'.", - "Compatibility": "AssumeBroken" + "Notes": "Crashes with 'Method not found: Void StardewValley.Item.set_Name(System.String)'." }, { "Name": "Almighty Tool", "ID": "AlmightyTool.dll", "UpperVersion": "1.1.1", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/439", - "Notes": "Uses obsolete StardewModdingAPI.Extensions.", - "Compatibility": "AssumeBroken" + "Notes": "Uses obsolete StardewModdingAPI.Extensions." }, { "Name": "Better Sprinklers", "ID": "SPDSprinklersMod", "UpperVersion": "2.3", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/41", "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", - "Notes": "Uses obsolete StardewModdingAPI.Extensions.", - "Compatibility": "AssumeBroken" + "Notes": "Uses obsolete StardewModdingAPI.Extensions." }, { "Name": "Casks Anywhere", "ID": "CasksAnywhere", + "Compatibility": "AssumeBroken", "UpperVersion": "1.1", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/878", - "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange.", - "Compatibility": "AssumeBroken" + "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." + }, + { + "Name": "Chest Label System", + "ID": "SPDChestLabel", + "UpperVersion": "1.5", + "Compatibility": "AssumeBroken", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/242", + "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)'.", - "Compatibility": "AssumeBroken" + "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'.", - "Compatibility": "AssumeBroken" + "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)'.", - "Compatibility": "AssumeBroken" + "Notes": "Crashes with 'Method not found: Void StardewValley.Item.set_Name(System.String)'." }, { "Name": "CJB Cheats Menu", "ID": "CJBCheatsMenu", "UpperVersion": "1.13", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4", - "Notes": "Uses removed Game1.borderFont.", - "Compatibility": "AssumeBroken" + "Notes": "Uses removed Game1.borderFont." }, { "Name": "CJB Item Spawner", "ID": "CJBItemSpawner", "UpperVersion": "1.6", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93", - "Notes": "Uses removed Game1.borderFont.", - "Compatibility": "AssumeBroken" + "Notes": "Uses removed Game1.borderFont." }, { "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)'.", - "Compatibility": "AssumeBroken" + "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", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/193", - "Notes": "Uses obsolete GraphicsEvents.DrawTick.", - "Compatibility": "AssumeBroken" + "Notes": "Uses obsolete GraphicsEvents.DrawTick." }, { "Name": "Entoarox Framework", "ID": "eacdb74b-4080-4452-b16b-93773cda5cf9", "UpperVersion": "1.6.5", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://community.playstarbound.com/resources/4228", - "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)').", - "Compatibility": "AssumeBroken" + "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'.", - "Compatibility": "AssumeBroken" + "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.", - "Compatibility": "AssumeBroken" + "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.", - "Compatibility": "AssumeBroken" + "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.", - "Compatibility": "AssumeBroken" + "Notes": "ID changed in 1.10.1. Crashes with FormatException when looking up NPCs." }, { "Name": "Makeshift Multiplayer", "ID": "StardewValleyMP", + "Compatibility": "AssumeBroken", "UpperVersion": "0.2.10", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/501", - "Notes": "Uses obsolete GraphicsEvents.OnPreRenderHudEventNoCheck.", - "Compatibility": "AssumeBroken" + "Notes": "Uses obsolete GraphicsEvents.OnPreRenderHudEventNoCheck." }, { "Name": "NoSoilDecay", "ID": "289dee03-5f38-4d8e-8ffc-e440198e8610", "UpperVersion": "0.5", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/237", - "Notes": "Uses Assembly.GetExecutingAssembly().Location.", - "Compatibility": "AssumeBroken" + "Notes": "Uses Assembly.GetExecutingAssembly().Location." + }, + { + "Name": "NPC Map Locations", + "ID": "NPCMapLocationsMod", + "LowerVersion": "1.42", + "UpperVersion": "1.43", + "Compatibility": "AssumeBroken", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/239", + "ReasonPhrase": "this version has an update check error which crashes the game" }, { "Name": "Point-and-Plant", "ID": "PointAndPlant.dll", "UpperVersion": "1.0.2", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/572", - "Notes": "Uses obsolete StardewModdingAPI.Extensions.", - "Compatibility": "AssumeBroken" + "Notes": "Uses obsolete StardewModdingAPI.Extensions." }, { "Name": "Reusable Wallpapers", "ID": "dae1b553-2e39-43e7-8400-c7c5c836134b", "UpperVersion": "1.5", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/356", - "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange.", - "Compatibility": "AssumeBroken" + "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." }, { "Name": "Save Anywhere", "ID": "SaveAnywhere", "UpperVersion": "2.0", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/444", - "Notes": "Depends on StarDustCore.", - "Compatibility": "AssumeBroken" + "Notes": "Depends on StarDustCore." }, { "Name": "StackSplitX", "ID": "StackSplitX.dll", "UpperVersion": "1.0", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/798", - "Notes": "Uses SMAPI's internal SGame class.", - "Compatibility": "AssumeBroken" + "Notes": "Uses SMAPI's internal SGame class." }, { "Name": "StarDustCore", "ID": "StarDustCore", "UpperVersion": "1.0", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/683", - "Notes": "Crashes with 'Method not found: Void StardewModdingAPI.Command.CallCommand(System.String)'.", - "Compatibility": "AssumeBroken" + "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'.", - "Compatibility": "AssumeBroken" + "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", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class.", - "Compatibility": "AssumeBroken" + "Notes": "Uses SMAPI's internal SGame class." }, { "Name": "Zoryn's Calendar Anywhere", "ID": "a41c01cd-0437-43eb-944f-78cb5a53002a", "UpperVersion": "1.5", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class.", - "Compatibility": "AssumeBroken" + "Notes": "Uses SMAPI's internal SGame class." }, { "Name": "Zoryn's Health Bars", "ID": "HealthBars.dll", "UpperVersion": "1.5", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class.", - "Compatibility": "AssumeBroken" + "Notes": "Uses SMAPI's internal SGame class." }, { "Name": "Zoryn's Movement Mod", "ID": "8a632929-8335-484f-87dd-c29d2ba3215d", "UpperVersion": "1.5", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class.", - "Compatibility": "AssumeBroken" + "Notes": "Uses SMAPI's internal SGame class." }, { "Name": "Zoryn's Regen Mod", "ID": "dfac4383-1b6b-4f33-ae4e-37fc23e5252e", "UpperVersion": "1.5", + "Compatibility": "AssumeBroken", "UpdateUrl": "http://community.playstarbound.com/threads/108756", - "Notes": "Uses SMAPI's internal SGame class.", - "Compatibility": "AssumeBroken" + "Notes": "Uses SMAPI's internal SGame class." } ] } -- cgit From 45d2ed705473f51935af269c852a94432968a15c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 16 Mar 2017 19:01:22 -0400 Subject: add another default game path for Linux Thanks to ShneekeyTheLost on the Stardew Valley forums. --- release-notes.md | 1 + src/StardewModdingAPI.Installer/InteractiveInstaller.cs | 1 + 2 files changed, 2 insertions(+) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 9d133f30..67286308 100644 --- a/release-notes.md +++ b/release-notes.md @@ -20,6 +20,7 @@ For players: * SMAPI now allows mods nested into an otherwise empty parent folder (like `Mods\ModName-1.0\ModName\manifest.json`), since that's a common default behaviour when unpacking mods. * The installer now detects if you need to update .NET Framework before installing SMAPI. * The installer now detects if you need to run the game at least once (to let it perform first-time setup) before installing SMAPI. +* The installer on Linux now finds games installed to `~/.steam/steam/steamapps/common/Stardew Valley` too. * The console now has simpler error messages. * The console now has improved command handling & feedback. * The console no longer shows the game's debug output (unless you use a _SMAPI for developers_ build). diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 0e920f1f..33cbc2dc 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -29,6 +29,7 @@ namespace StardewModdingApi.Installer // Linux yield return $"{Environment.GetEnvironmentVariable("HOME")}/GOG Games/Stardew Valley/game"; yield return $"{Environment.GetEnvironmentVariable("HOME")}/.local/share/Steam/steamapps/common/Stardew Valley"; + yield return $"{Environment.GetEnvironmentVariable("HOME")}/.steam/steam/steamapps/common/Stardew Valley"; // Mac yield return "/Applications/Stardew Valley.app/Contents/MacOS"; -- cgit From d724f54f32c4de113700fd2173c070d6a992f66e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 25 Mar 2017 12:55:06 -0400 Subject: replace SpriteBatch rewriter with a generic method mapper --- .../Crossplatform/SpriteBatch_MethodRewriter.cs | 104 --------------------- .../Rewriters/GenericMethodMapper.cs | 74 +++++++++++++++ .../Rewriters/Wrappers/SpriteBatchWrapper.cs | 59 ++++++++++++ .../StardewModdingAPI.AssemblyRewriters.csproj | 3 +- src/StardewModdingAPI/Constants.cs | 6 +- 5 files changed, 139 insertions(+), 107 deletions(-) delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/Wrappers/SpriteBatchWrapper.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs deleted file mode 100644 index 1459ff17..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Crossplatform/SpriteBatch_MethodRewriter.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; - -namespace StardewModdingAPI.AssemblyRewriters.Rewriters.Crossplatform -{ - /// Rewrites references to to fix inconsistent method signatures between MonoGame and XNA. - /// MonoGame has one SpriteBatch.Begin method with optional arguments, but XNA has multiple method overloads. Incompatible method references are rewritten to use , which redirects all method signatures to the proper compiled MonoGame/XNA method. - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] - public class SpriteBatch_MethodRewriter : BaseMethodRewriter - { - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } = $"{nameof(SpriteBatch)} methods"; - - - /********* - ** Protected methods - *********/ - /// Get whether a method reference should be rewritten. - /// The IL instruction. - /// The method reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) - { - return platformChanged - && methodRef.DeclaringType.FullName == typeof(SpriteBatch).FullName - && this.HasMatchingSignature(typeof(SpriteBatch_MethodRewriter.WrapperMethods), methodRef); - } - - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which calls the method. - /// The method reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap) - { - methodRef.DeclaringType = module.Import(typeof(SpriteBatch_MethodRewriter.WrapperMethods)); - } - - - /********* - ** Wrapper methods - *********/ - /// Wraps methods that are incompatible when converting compiled code between MonoGame and XNA. - public class WrapperMethods : SpriteBatch - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public WrapperMethods(GraphicsDevice graphicsDevice) : base(graphicsDevice) { } - - - /**** - ** MonoGame signatures - ****/ - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/Mac.")] - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity); - } - - /**** - ** XNA signatures - ****/ - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] - public new void Begin() - { - base.Begin(); - } - - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] - public new void Begin(SpriteSortMode sortMode, BlendState blendState) - { - base.Begin(sortMode, blendState); - } - - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState); - } - - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect); - } - - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix transformMatrix) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix); - } - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs new file mode 100644 index 00000000..92ba3af1 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs @@ -0,0 +1,74 @@ +using System; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters +{ + /// Rewrites method references from one parent type to another if the signatures match. + public class GenericMethodMapper : BaseMethodRewriter + { + /********* + ** Properties + *********/ + /// The type whose methods to remap. + private readonly Type FromType; + + /// The type with methods to map to. + private readonly Type ToType; + + /// Whether to only rewrite references if loading the assembly on a different platform than it was compiled on. + private readonly bool OnlyIfPlatformChanged; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The type whose methods to remap. + /// The type with methods to map to. + /// A brief noun phrase indicating what the instruction finder matches. + /// Whether to only rewrite references if loading the assembly on a different platform than it was compiled on. + public GenericMethodMapper(Type fromType, Type toType, string nounPhrase, bool onlyIfPlatformChanged = false) + { + this.FromType = fromType; + this.ToType = toType; + this.NounPhrase = nounPhrase; + this.OnlyIfPlatformChanged = onlyIfPlatformChanged; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a method reference should be rewritten. + /// The IL instruction. + /// The method reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) + { + return + (!this.OnlyIfPlatformChanged || platformChanged) + && methodRef.DeclaringType.FullName == this.FromType.FullName + && this.HasMatchingSignature(this.ToType, methodRef); + } + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which calls the method. + /// The method reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap) + { + methodRef.DeclaringType = module.Import(this.ToType); + } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Wrappers/SpriteBatchWrapper.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Wrappers/SpriteBatchWrapper.cs new file mode 100644 index 00000000..ee68f1d5 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/Wrappers/SpriteBatchWrapper.cs @@ -0,0 +1,59 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters.Wrappers +{ + /// Wraps methods that are incompatible when converting compiled code between MonoGame and XNA. + public class SpriteBatchWrapper : SpriteBatch + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SpriteBatchWrapper(GraphicsDevice graphicsDevice) : base(graphicsDevice) { } + + + /**** + ** MonoGame signatures + ****/ + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/Mac.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity); + } + + /**** + ** XNA signatures + ****/ + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin() + { + base.Begin(); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState) + { + base.Begin(sortMode, blendState); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix transformMatrix) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix); + } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index a3322e67..8c10aea7 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -80,10 +80,11 @@ + + - diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 263d3352..a7e28213 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.AssemblyRewriters.Finders; -using StardewModdingAPI.AssemblyRewriters.Rewriters.Crossplatform; +using StardewModdingAPI.AssemblyRewriters.Rewriters; using StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2; +using StardewModdingAPI.AssemblyRewriters.Rewriters.Wrappers; using StardewValley; namespace StardewModdingAPI @@ -172,7 +174,7 @@ namespace StardewModdingAPI return new IInstructionRewriter[] { // crossplatform - new SpriteBatch_MethodRewriter(), + new GenericMethodMapper(typeof(SpriteBatch), typeof(SpriteBatchWrapper), $"{nameof(SpriteBatch)} methods", onlyIfPlatformChanged: true), // Stardew Valley 1.2 new Game1_ActiveClickableMenu_FieldRewriter(), -- cgit From 2e58f853d27cc200be5b34a1ebef5bbdb7703a49 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 25 Mar 2017 13:40:37 -0400 Subject: replace field-to-property rewriters with a generic rewriter --- .../Rewriters/GenericFieldToPropertyRewriter.cs | 70 ++++++++++++++++++++++ .../Rewriters/GenericMethodMapper.cs | 8 +-- .../Game1_ActiveClickableMenu_FieldRewriter.cs | 49 --------------- .../SDV1_2/Game1_GameMode_FieldRewriter.cs | 49 --------------- .../Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs | 49 --------------- .../StardewModdingAPI.AssemblyRewriters.csproj | 6 +- src/StardewModdingAPI/Constants.cs | 9 ++- 7 files changed, 80 insertions(+), 160 deletions(-) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericFieldToPropertyRewriter.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericFieldToPropertyRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericFieldToPropertyRewriter.cs new file mode 100644 index 00000000..f58bcfbb --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericFieldToPropertyRewriter.cs @@ -0,0 +1,70 @@ +using System; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters +{ + /// Rewrites field references into property references. + public class GenericFieldToPropertyRewriter : BaseFieldRewriter + { + /********* + ** Properties + *********/ + /// The type whose field to which references should be rewritten. + private readonly Type Type; + + /// The field name to rewrite. + private readonly string FieldName; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The type whose field to which references should be rewritten. + /// The field name to rewrite. + /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). + public GenericFieldToPropertyRewriter(Type type, string fieldName, string nounPhrase = null) + { + this.Type = type; + this.FieldName = fieldName; + this.NounPhrase = nounPhrase ?? $"{type.Name}.{fieldName} field"; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) + { + return + fieldRef.DeclaringType.FullName == this.Type.FullName + && fieldRef.Name == this.FieldName; + } + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which references the field. + /// The field reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) + { + string methodPrefix = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld ? "get" : "set"; + MethodReference propertyRef = module.Import(this.Type.GetMethod($"{methodPrefix}_{this.FieldName}")); + cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs index 92ba3af1..49e0aad7 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs @@ -34,13 +34,13 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /// Construct an instance. /// The type whose methods to remap. /// The type with methods to map to. - /// A brief noun phrase indicating what the instruction finder matches. /// Whether to only rewrite references if loading the assembly on a different platform than it was compiled on. - public GenericMethodMapper(Type fromType, Type toType, string nounPhrase, bool onlyIfPlatformChanged = false) + /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). + public GenericMethodMapper(Type fromType, Type toType, bool onlyIfPlatformChanged = false, string nounPhrase = null) { this.FromType = fromType; this.ToType = toType; - this.NounPhrase = nounPhrase; + this.NounPhrase = nounPhrase ?? $"{fromType.Name} methods"; this.OnlyIfPlatformChanged = onlyIfPlatformChanged; } @@ -71,4 +71,4 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters methodRef.DeclaringType = module.Import(this.ToType); } } -} \ No newline at end of file +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs deleted file mode 100644 index 59a7c798..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_ActiveClickableMenu_FieldRewriter.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; -using StardewValley; - -namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 -{ - /// Rewrites field references to . - /// Stardew Valley changed the field to a property, which broke many mods that reference it. - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] - public class Game1_ActiveClickableMenu_FieldRewriter : BaseFieldRewriter - { - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } = $"{nameof(Game1)}.{nameof(Game1.activeClickableMenu)} field"; - - - /********* - ** Protected methods - *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) - { - return - this.IsStaticField(instruction) - && fieldRef.DeclaringType.FullName == typeof(Game1).FullName - && fieldRef.Name == nameof(Game1.activeClickableMenu); - } - - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which references the field. - /// The field reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) - { - string methodPrefix = instruction.OpCode == OpCodes.Ldsfld ? "get" : "set"; - MethodReference propertyRef = module.Import(typeof(Game1).GetMethod($"{methodPrefix}_{nameof(Game1.activeClickableMenu)}")); - cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs deleted file mode 100644 index c3da6863..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_GameMode_FieldRewriter.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; -using StardewValley; - -namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 -{ - /// Rewrites field references to . - /// Stardew Valley changed the field to a property, which broke many mods that reference it. - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] - public class Game1_GameMode_FieldRewriter : BaseFieldRewriter - { - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } = $"{nameof(Game1)}.{nameof(Game1.gameMode)} field"; - - - /********* - ** Protected methods - *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) - { - return - this.IsStaticField(instruction) - && fieldRef.DeclaringType.FullName == typeof(Game1).FullName - && fieldRef.Name == nameof(Game1.gameMode); - } - - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which references the field. - /// The field reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) - { - string methodPrefix = instruction.OpCode == OpCodes.Ldsfld ? "get" : "set"; - MethodReference propertyRef = module.Import(typeof(Game1).GetMethod($"{methodPrefix}_{nameof(Game1.gameMode)}")); - cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs deleted file mode 100644 index 91eae416..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/SDV1_2/Game1_Player_FieldRewriter.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; -using StardewValley; - -namespace StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2 -{ - /// Rewrites field references to . - /// Stardew Valley changed the field to a property, which broke many mods that reference it. - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "This class is not meant to be used directly, and is deliberately named to make it easier to know what it changes at a glance.")] - public class Game1_Player_FieldRewriter : BaseFieldRewriter - { - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } = $"{nameof(Game1)}.{nameof(Game1.player)} field"; - - - /********* - ** Protected methods - *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) - { - return - this.IsStaticField(instruction) - && fieldRef.DeclaringType.FullName == typeof(Game1).FullName - && fieldRef.Name == nameof(Game1.player); - } - - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which references the field. - /// The field reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) - { - string methodPrefix = instruction.OpCode == OpCodes.Ldsfld ? "get" : "set"; - MethodReference propertyRef = module.Import(typeof(Game1).GetMethod($"{methodPrefix}_{nameof(Game1.player)}")); - cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 8c10aea7..8416aad7 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -80,11 +80,9 @@ - + - - - + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index a7e28213..1965cae3 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -7,7 +7,6 @@ using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.AssemblyRewriters.Finders; using StardewModdingAPI.AssemblyRewriters.Rewriters; -using StardewModdingAPI.AssemblyRewriters.Rewriters.SDV1_2; using StardewModdingAPI.AssemblyRewriters.Rewriters.Wrappers; using StardewValley; @@ -174,12 +173,12 @@ namespace StardewModdingAPI return new IInstructionRewriter[] { // crossplatform - new GenericMethodMapper(typeof(SpriteBatch), typeof(SpriteBatchWrapper), $"{nameof(SpriteBatch)} methods", onlyIfPlatformChanged: true), + new GenericMethodMapper(typeof(SpriteBatch), typeof(SpriteBatchWrapper), onlyIfPlatformChanged: true), // Stardew Valley 1.2 - new Game1_ActiveClickableMenu_FieldRewriter(), - new Game1_GameMode_FieldRewriter(), - new Game1_Player_FieldRewriter() + new GenericFieldToPropertyRewriter(typeof(Game1), nameof(Game1.activeClickableMenu)), + new GenericFieldToPropertyRewriter(typeof(Game1), nameof(Game1.gameMode)), + new GenericFieldToPropertyRewriter(typeof(Game1), nameof(Game1.player)) }; } -- cgit From 4d48bdfe7c3806ec1995cd499ca9382ace2d8a53 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 25 Mar 2017 13:50:01 -0400 Subject: drop 'generic' prefix for rewriters since they're all generic now --- .../Finders/EventFinder.cs | 54 ++++++++++++++++ .../Finders/FieldFinder.cs | 61 ++++++++++++++++++ .../Finders/GenericEventFinder.cs | 54 ---------------- .../Finders/GenericFieldFinder.cs | 61 ------------------ .../Finders/GenericMethodFinder.cs | 54 ---------------- .../Finders/GenericTypeFinder.cs | 64 ------------------- .../Finders/MethodFinder.cs | 54 ++++++++++++++++ .../Finders/TypeFinder.cs | 64 +++++++++++++++++++ .../Rewriters/FieldToPropertyRewriter.cs | 70 ++++++++++++++++++++ .../Rewriters/GenericFieldToPropertyRewriter.cs | 70 -------------------- .../Rewriters/GenericMethodMapper.cs | 74 ---------------------- .../Rewriters/MethodParentRewriter.cs | 74 ++++++++++++++++++++++ .../StardewModdingAPI.AssemblyRewriters.csproj | 20 +++--- src/StardewModdingAPI/Constants.cs | 46 +++++++------- 14 files changed, 410 insertions(+), 410 deletions(-) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/GenericEventFinder.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/GenericFieldFinder.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/GenericMethodFinder.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericFieldToPropertyRewriter.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs new file mode 100644 index 00000000..359ca63e --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs @@ -0,0 +1,54 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Finders +{ + /// Finds CIL instructions that reference a given event. + public sealed class EventFinder : BaseMethodFinder + { + /********* + ** Properties + *********/ + /// The full type name for which to find references. + private readonly string FullTypeName; + + /// The event name for which to find references. + private readonly string EventName; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name for which to find references. + /// The event name for which to find references. + public EventFinder(string fullTypeName, string eventName) + { + this.FullTypeName = fullTypeName; + this.EventName = eventName; + this.NounPhrase = $"obsolete {fullTypeName}.{eventName} event"; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a method reference should be rewritten. + /// The IL instruction. + /// The method reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) + { + return methodRef.DeclaringType.FullName == this.FullTypeName + && (methodRef.Name == "add_" + this.EventName || methodRef.Name == "remove_" + this.EventName); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs new file mode 100644 index 00000000..516641f2 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs @@ -0,0 +1,61 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Finders +{ + /// Finds CIL instructions that reference a given field. + public sealed class FieldFinder : BaseFieldFinder + { + /********* + ** Properties + *********/ + /// The full type name for which to find references. + private readonly string FullTypeName; + + /// The field name for which to find references. + private readonly string FieldName; + + /// Whether the field to match is static. + private readonly bool IsStatic; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name for which to find references. + /// The field name for which to find references. + /// Whether the field to match is static. + public FieldFinder(string fullTypeName, string fieldName, bool isStatic) + { + this.FullTypeName = fullTypeName; + this.FieldName = fieldName; + this.IsStatic = isStatic; + this.NounPhrase = $"obsolete {fullTypeName}.{fieldName} field"; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) + { + return + this.IsStaticField(instruction) == this.IsStatic + && fieldRef.DeclaringType.FullName == this.FullTypeName + && fieldRef.Name == this.FieldName; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericEventFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericEventFinder.cs deleted file mode 100644 index c2a981e5..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericEventFinder.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; - -namespace StardewModdingAPI.AssemblyRewriters.Finders -{ - /// Finds CIL instructions that reference a given event. - public sealed class GenericEventFinder : BaseMethodFinder - { - /********* - ** Properties - *********/ - /// The full type name for which to find references. - private readonly string FullTypeName; - - /// The event name for which to find references. - private readonly string EventName; - - - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The full type name for which to find references. - /// The event name for which to find references. - public GenericEventFinder(string fullTypeName, string eventName) - { - this.FullTypeName = fullTypeName; - this.EventName = eventName; - this.NounPhrase = $"obsolete {fullTypeName}.{eventName} event"; - } - - - /********* - ** Protected methods - *********/ - /// Get whether a method reference should be rewritten. - /// The IL instruction. - /// The method reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) - { - return methodRef.DeclaringType.FullName == this.FullTypeName - && (methodRef.Name == "add_" + this.EventName || methodRef.Name == "remove_" + this.EventName); - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericFieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericFieldFinder.cs deleted file mode 100644 index 056422a4..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericFieldFinder.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; - -namespace StardewModdingAPI.AssemblyRewriters.Finders -{ - /// Finds CIL instructions that reference a given field. - public sealed class GenericFieldFinder : BaseFieldFinder - { - /********* - ** Properties - *********/ - /// The full type name for which to find references. - private readonly string FullTypeName; - - /// The field name for which to find references. - private readonly string FieldName; - - /// Whether the field to match is static. - private readonly bool IsStatic; - - - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The full type name for which to find references. - /// The field name for which to find references. - /// Whether the field to match is static. - public GenericFieldFinder(string fullTypeName, string fieldName, bool isStatic) - { - this.FullTypeName = fullTypeName; - this.FieldName = fieldName; - this.IsStatic = isStatic; - this.NounPhrase = $"obsolete {fullTypeName}.{fieldName} field"; - } - - - /********* - ** Protected methods - *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) - { - return - this.IsStaticField(instruction) == this.IsStatic - && fieldRef.DeclaringType.FullName == this.FullTypeName - && fieldRef.Name == this.FieldName; - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericMethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericMethodFinder.cs deleted file mode 100644 index f5443558..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericMethodFinder.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; - -namespace StardewModdingAPI.AssemblyRewriters.Finders -{ - /// Finds CIL instructions that reference a given method. - public sealed class GenericMethodFinder : BaseMethodFinder - { - /********* - ** Properties - *********/ - /// The full type name for which to find references. - private readonly string FullTypeName; - - /// The method name for which to find references. - private readonly string MethodName; - - - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The full type name for which to find references. - /// The method name for which to find references. - public GenericMethodFinder(string fullTypeName, string methodName) - { - this.FullTypeName = fullTypeName; - this.MethodName = methodName; - this.NounPhrase = $"obsolete {fullTypeName}.{methodName} method"; - } - - - /********* - ** Protected methods - *********/ - /// Get whether a method reference should be rewritten. - /// The IL instruction. - /// The method reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) - { - return methodRef.DeclaringType.FullName == this.FullTypeName - && methodRef.Name == this.MethodName; - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs deleted file mode 100644 index 1556cc3c..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/GenericTypeFinder.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace StardewModdingAPI.AssemblyRewriters.Finders -{ - /// Finds CIL instructions that reference a given type. - public sealed class GenericTypeFinder : IInstructionFinder - { - /********* - ** Accessors - *********/ - /// The full type name for which to find references. - private readonly string FullTypeName; - - - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public string NounPhrase { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The full type name to match. - public GenericTypeFinder(string fullTypeName) - { - this.FullTypeName = fullTypeName; - this.NounPhrase = $"obsolete {fullTypeName} type"; - } - - /// Get whether a CIL instruction matches. - /// The IL instruction. - /// Whether the mod was compiled on a different platform. - public bool IsMatch(Instruction instruction, bool platformChanged) - { - string fullName = this.FullTypeName; - - // field reference - if (instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld) - { - FieldReference field = (FieldReference)instruction.Operand; - return - field.DeclaringType.FullName == fullName // field on target class - || field.FieldType.FullName == fullName; // field value is target class - } - - // method reference - if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt) - { - MethodReference method = (MethodReference)instruction.Operand; - return - method.DeclaringType.FullName == fullName // method on target class - || method.ReturnType.FullName == fullName // method returns target class - || method.Parameters.Any(p => p.ParameterType.FullName == fullName); // method parameters - } - - return false; - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs new file mode 100644 index 00000000..6c210d68 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs @@ -0,0 +1,54 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Finders +{ + /// Finds CIL instructions that reference a given method. + public sealed class MethodFinder : BaseMethodFinder + { + /********* + ** Properties + *********/ + /// The full type name for which to find references. + private readonly string FullTypeName; + + /// The method name for which to find references. + private readonly string MethodName; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name for which to find references. + /// The method name for which to find references. + public MethodFinder(string fullTypeName, string methodName) + { + this.FullTypeName = fullTypeName; + this.MethodName = methodName; + this.NounPhrase = $"obsolete {fullTypeName}.{methodName} method"; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a method reference should be rewritten. + /// The IL instruction. + /// The method reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) + { + return methodRef.DeclaringType.FullName == this.FullTypeName + && methodRef.Name == this.MethodName; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs new file mode 100644 index 00000000..ba8e7102 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs @@ -0,0 +1,64 @@ +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace StardewModdingAPI.AssemblyRewriters.Finders +{ + /// Finds CIL instructions that reference a given type. + public sealed class TypeFinder : IInstructionFinder + { + /********* + ** Accessors + *********/ + /// The full type name for which to find references. + private readonly string FullTypeName; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name to match. + public TypeFinder(string fullTypeName) + { + this.FullTypeName = fullTypeName; + this.NounPhrase = $"obsolete {fullTypeName} type"; + } + + /// Get whether a CIL instruction matches. + /// The IL instruction. + /// Whether the mod was compiled on a different platform. + public bool IsMatch(Instruction instruction, bool platformChanged) + { + string fullName = this.FullTypeName; + + // field reference + if (instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld) + { + FieldReference field = (FieldReference)instruction.Operand; + return + field.DeclaringType.FullName == fullName // field on target class + || field.FieldType.FullName == fullName; // field value is target class + } + + // method reference + if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt) + { + MethodReference method = (MethodReference)instruction.Operand; + return + method.DeclaringType.FullName == fullName // method on target class + || method.ReturnType.FullName == fullName // method returns target class + || method.Parameters.Any(p => p.ParameterType.FullName == fullName); // method parameters + } + + return false; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs new file mode 100644 index 00000000..caf0a16c --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs @@ -0,0 +1,70 @@ +using System; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters +{ + /// Rewrites field references into property references. + public class FieldToPropertyRewriter : BaseFieldRewriter + { + /********* + ** Properties + *********/ + /// The type whose field to which references should be rewritten. + private readonly Type Type; + + /// The field name to rewrite. + private readonly string FieldName; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The type whose field to which references should be rewritten. + /// The field name to rewrite. + /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). + public FieldToPropertyRewriter(Type type, string fieldName, string nounPhrase = null) + { + this.Type = type; + this.FieldName = fieldName; + this.NounPhrase = nounPhrase ?? $"{type.Name}.{fieldName} field"; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) + { + return + fieldRef.DeclaringType.FullName == this.Type.FullName + && fieldRef.Name == this.FieldName; + } + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which references the field. + /// The field reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) + { + string methodPrefix = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld ? "get" : "set"; + MethodReference propertyRef = module.Import(this.Type.GetMethod($"{methodPrefix}_{this.FieldName}")); + cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericFieldToPropertyRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericFieldToPropertyRewriter.cs deleted file mode 100644 index f58bcfbb..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericFieldToPropertyRewriter.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; - -namespace StardewModdingAPI.AssemblyRewriters.Rewriters -{ - /// Rewrites field references into property references. - public class GenericFieldToPropertyRewriter : BaseFieldRewriter - { - /********* - ** Properties - *********/ - /// The type whose field to which references should be rewritten. - private readonly Type Type; - - /// The field name to rewrite. - private readonly string FieldName; - - - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The type whose field to which references should be rewritten. - /// The field name to rewrite. - /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). - public GenericFieldToPropertyRewriter(Type type, string fieldName, string nounPhrase = null) - { - this.Type = type; - this.FieldName = fieldName; - this.NounPhrase = nounPhrase ?? $"{type.Name}.{fieldName} field"; - } - - - /********* - ** Protected methods - *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) - { - return - fieldRef.DeclaringType.FullName == this.Type.FullName - && fieldRef.Name == this.FieldName; - } - - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which references the field. - /// The field reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) - { - string methodPrefix = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld ? "get" : "set"; - MethodReference propertyRef = module.Import(this.Type.GetMethod($"{methodPrefix}_{this.FieldName}")); - cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs deleted file mode 100644 index 49e0aad7..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/GenericMethodMapper.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; - -namespace StardewModdingAPI.AssemblyRewriters.Rewriters -{ - /// Rewrites method references from one parent type to another if the signatures match. - public class GenericMethodMapper : BaseMethodRewriter - { - /********* - ** Properties - *********/ - /// The type whose methods to remap. - private readonly Type FromType; - - /// The type with methods to map to. - private readonly Type ToType; - - /// Whether to only rewrite references if loading the assembly on a different platform than it was compiled on. - private readonly bool OnlyIfPlatformChanged; - - - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The type whose methods to remap. - /// The type with methods to map to. - /// Whether to only rewrite references if loading the assembly on a different platform than it was compiled on. - /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). - public GenericMethodMapper(Type fromType, Type toType, bool onlyIfPlatformChanged = false, string nounPhrase = null) - { - this.FromType = fromType; - this.ToType = toType; - this.NounPhrase = nounPhrase ?? $"{fromType.Name} methods"; - this.OnlyIfPlatformChanged = onlyIfPlatformChanged; - } - - - /********* - ** Protected methods - *********/ - /// Get whether a method reference should be rewritten. - /// The IL instruction. - /// The method reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) - { - return - (!this.OnlyIfPlatformChanged || platformChanged) - && methodRef.DeclaringType.FullName == this.FromType.FullName - && this.HasMatchingSignature(this.ToType, methodRef); - } - - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which calls the method. - /// The method reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap) - { - methodRef.DeclaringType = module.Import(this.ToType); - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs new file mode 100644 index 00000000..9c19f473 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs @@ -0,0 +1,74 @@ +using System; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters +{ + /// Rewrites method references from one parent type to another if the signatures match. + public class MethodParentRewriter : BaseMethodRewriter + { + /********* + ** Properties + *********/ + /// The type whose methods to remap. + private readonly Type FromType; + + /// The type with methods to map to. + private readonly Type ToType; + + /// Whether to only rewrite references if loading the assembly on a different platform than it was compiled on. + private readonly bool OnlyIfPlatformChanged; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The type whose methods to remap. + /// The type with methods to map to. + /// Whether to only rewrite references if loading the assembly on a different platform than it was compiled on. + /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). + public MethodParentRewriter(Type fromType, Type toType, bool onlyIfPlatformChanged = false, string nounPhrase = null) + { + this.FromType = fromType; + this.ToType = toType; + this.NounPhrase = nounPhrase ?? $"{fromType.Name} methods"; + this.OnlyIfPlatformChanged = onlyIfPlatformChanged; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a method reference should be rewritten. + /// The IL instruction. + /// The method reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) + { + return + (!this.OnlyIfPlatformChanged || platformChanged) + && methodRef.DeclaringType.FullName == this.FromType.FullName + && this.HasMatchingSignature(this.ToType, methodRef); + } + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which calls the method. + /// The method reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap) + { + methodRef.DeclaringType = module.Import(this.ToType); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 8416aad7..90e800d0 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -66,22 +66,22 @@ Properties\GlobalAssemblyInfo.cs - - - + + + + + - - + + + - - - - - + + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 1965cae3..5fa25014 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -143,27 +143,27 @@ namespace StardewModdingAPI return new IInstructionFinder[] { // changes in Stardew Valley 1.2 (that don't have rewriters) - new GenericFieldFinder("StardewValley.Game1", "borderFont", isStatic: true), - new GenericFieldFinder("StardewValley.Game1", "smoothFont", isStatic: true), - new GenericFieldFinder("StardewValley.Item", "set_Name", isStatic: false), + new FieldFinder("StardewValley.Game1", "borderFont", isStatic: true), + new FieldFinder("StardewValley.Game1", "smoothFont", isStatic: true), + new FieldFinder("StardewValley.Item", "set_Name", isStatic: false), // APIs removed in SMAPI 1.9 - new GenericTypeFinder("StardewModdingAPI.Advanced.ConfigFile"), - new GenericTypeFinder("StardewModdingAPI.Advanced.IConfigFile"), - new GenericTypeFinder("StardewModdingAPI.Entities.SPlayer"), - new GenericTypeFinder("StardewModdingAPI.Extensions"), - new GenericTypeFinder("StardewModdingAPI.Inheritance.ItemStackChange"), - new GenericTypeFinder("StardewModdingAPI.Inheritance.SGame"), - new GenericTypeFinder("StardewModdingAPI.Inheritance.SObject"), - new GenericTypeFinder("StardewModdingAPI.LogWriter"), - new GenericTypeFinder("StardewModdingAPI.Manifest"), - new GenericTypeFinder("StardewModdingAPI.Version"), - new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "DrawDebug"), - new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "DrawTick"), - new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderHudEventNoCheck"), - new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderGuiEventNoCheck"), - new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderHudEventNoCheck"), - new GenericEventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderGuiEventNoCheck") + new TypeFinder("StardewModdingAPI.Advanced.ConfigFile"), + new TypeFinder("StardewModdingAPI.Advanced.IConfigFile"), + new TypeFinder("StardewModdingAPI.Entities.SPlayer"), + new TypeFinder("StardewModdingAPI.Extensions"), + new TypeFinder("StardewModdingAPI.Inheritance.ItemStackChange"), + new TypeFinder("StardewModdingAPI.Inheritance.SGame"), + new TypeFinder("StardewModdingAPI.Inheritance.SObject"), + new TypeFinder("StardewModdingAPI.LogWriter"), + new TypeFinder("StardewModdingAPI.Manifest"), + new TypeFinder("StardewModdingAPI.Version"), + new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "DrawDebug"), + new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "DrawTick"), + new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderHudEventNoCheck"), + new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderGuiEventNoCheck"), + new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderHudEventNoCheck"), + new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderGuiEventNoCheck") }; } @@ -173,12 +173,12 @@ namespace StardewModdingAPI return new IInstructionRewriter[] { // crossplatform - new GenericMethodMapper(typeof(SpriteBatch), typeof(SpriteBatchWrapper), onlyIfPlatformChanged: true), + new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchWrapper), onlyIfPlatformChanged: true), // Stardew Valley 1.2 - new GenericFieldToPropertyRewriter(typeof(Game1), nameof(Game1.activeClickableMenu)), - new GenericFieldToPropertyRewriter(typeof(Game1), nameof(Game1.gameMode)), - new GenericFieldToPropertyRewriter(typeof(Game1), nameof(Game1.player)) + new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.activeClickableMenu)), + new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.gameMode)), + new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.player)) }; } -- cgit From 267e2469da290cbdb93bd58cea0272de403bbdab Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 25 Mar 2017 14:06:36 -0400 Subject: rewrite removed font references for compatibility --- .../Rewriters/FieldReplaceRewriter.cs | 78 ++++++++++++++++++++++ .../StardewModdingAPI.AssemblyRewriters.csproj | 1 + src/StardewModdingAPI/Constants.cs | 6 +- .../StardewModdingAPI.config.json | 16 ----- 4 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs new file mode 100644 index 00000000..31f9a40f --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs @@ -0,0 +1,78 @@ +using System; +using System.Reflection; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Framework; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters +{ + /// Rewrites references to one field with another. + public class FieldReplaceRewriter : BaseFieldRewriter + { + /********* + ** Properties + *********/ + /// The type whose field to which references should be rewritten. + private readonly Type Type; + + /// The field name to rewrite. + private readonly string FromFieldName; + + /// The new field name to reference. + private readonly string ToFieldName; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public override string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The type whose field to which references should be rewritten. + /// The field name to rewrite. + /// The new field name to reference. + /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). + public FieldReplaceRewriter(Type type, string fromFieldName, string toFieldName, string nounPhrase = null) + { + this.Type = type; + this.FromFieldName = fromFieldName; + this.ToFieldName = toFieldName; + this.NounPhrase = nounPhrase ?? $"{type.Name}.{fromFieldName} field"; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a field reference should be rewritten. + /// The IL instruction. + /// The field reference. + /// Whether the mod was compiled on a different platform. + protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) + { + return + fieldRef.DeclaringType.FullName == this.Type.FullName + && fieldRef.Name == this.FromFieldName; + } + + /// Rewrite a method for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction which references the field. + /// The field reference invoked by the . + /// Metadata for mapping assemblies to the current platform. + protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) + { + FieldInfo field = this.Type.GetField(this.ToFieldName); + if(field == null) + throw new InvalidOperationException($"The {this.Type.FullName} class doesn't have a {this.ToFieldName} field."); + FieldReference newRef = module.Import(field); + cil.Replace(instruction, cil.Create(instruction.OpCode, newRef)); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 90e800d0..0136c39b 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -80,6 +80,7 @@ + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 5fa25014..9a7fba84 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -143,8 +143,6 @@ namespace StardewModdingAPI return new IInstructionFinder[] { // changes in Stardew Valley 1.2 (that don't have rewriters) - new FieldFinder("StardewValley.Game1", "borderFont", isStatic: true), - new FieldFinder("StardewValley.Game1", "smoothFont", isStatic: true), new FieldFinder("StardewValley.Item", "set_Name", isStatic: false), // APIs removed in SMAPI 1.9 @@ -178,7 +176,9 @@ namespace StardewModdingAPI // Stardew Valley 1.2 new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.activeClickableMenu)), new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.gameMode)), - new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.player)) + new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.player)), + new FieldReplaceRewriter(typeof(Game1), "borderFont", nameof(Game1.smallFont)), + new FieldReplaceRewriter(typeof(Game1), "smoothFont", nameof(Game1.smallFont)) }; } diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index 64b4fd53..da6d3730 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -93,22 +93,6 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha "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.13", - "Compatibility": "AssumeBroken", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4", - "Notes": "Uses removed Game1.borderFont." - }, - { - "Name": "CJB Item Spawner", - "ID": "CJBItemSpawner", - "UpperVersion": "1.6", - "Compatibility": "AssumeBroken", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93", - "Notes": "Uses removed Game1.borderFont." - }, { "Name": "Cooking Skill", "ID": "CookingSkill", -- cgit From 7b641d816466fe7d9229374c175f59ee32b8dc5c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 25 Mar 2017 15:17:26 -0400 Subject: simplify CIL rewriter hierarchy --- .../Finders/EventFinder.cs | 15 ++-- .../Finders/FieldFinder.cs | 23 +++--- .../Finders/MethodFinder.cs | 15 ++-- .../Framework/BaseFieldFinder.cs | 46 ----------- .../Framework/BaseFieldRewriter.cs | 35 -------- .../Framework/BaseMethodFinder.cs | 74 ----------------- .../Framework/BaseMethodRewriter.cs | 35 -------- .../Framework/RewriteHelper.cs | 41 ---------- .../RewriteHelper.cs | 94 ++++++++++++++++++++++ .../Rewriters/FieldReplaceRewriter.cs | 37 ++------- .../Rewriters/FieldToPropertyRewriter.cs | 31 ++----- .../Rewriters/MethodParentRewriter.cs | 24 +++--- .../StardewModdingAPI.AssemblyRewriters.csproj | 6 +- src/StardewModdingAPI/Constants.cs | 2 +- 14 files changed, 146 insertions(+), 332 deletions(-) delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/Framework/RewriteHelper.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/RewriteHelper.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs index 359ca63e..9d0184c6 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs @@ -1,11 +1,10 @@ using Mono.Cecil; using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; namespace StardewModdingAPI.AssemblyRewriters.Finders { /// Finds CIL instructions that reference a given event. - public sealed class EventFinder : BaseMethodFinder + public sealed class EventFinder : IInstructionFinder { /********* ** Properties @@ -21,7 +20,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders ** Accessors *********/ /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } + public string NounPhrase { get; } /********* @@ -41,13 +40,15 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /********* ** Protected methods *********/ - /// Get whether a method reference should be rewritten. + /// Get whether a CIL instruction matches. /// The IL instruction. - /// The method reference. /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) + public bool IsMatch(Instruction instruction, bool platformChanged) { - return methodRef.DeclaringType.FullName == this.FullTypeName + MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + return + methodRef != null + && methodRef.DeclaringType.FullName == this.FullTypeName && (methodRef.Name == "add_" + this.EventName || methodRef.Name == "remove_" + this.EventName); } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs index 516641f2..068119b8 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs @@ -1,11 +1,10 @@ using Mono.Cecil; using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; namespace StardewModdingAPI.AssemblyRewriters.Finders { /// Finds CIL instructions that reference a given field. - public sealed class FieldFinder : BaseFieldFinder + public class FieldFinder : IInstructionFinder { /********* ** Properties @@ -16,15 +15,12 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// The field name for which to find references. private readonly string FieldName; - /// Whether the field to match is static. - private readonly bool IsStatic; - /********* ** Accessors *********/ /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } + public string NounPhrase { get; } /********* @@ -33,27 +29,26 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// Construct an instance. /// The full type name for which to find references. /// The field name for which to find references. - /// Whether the field to match is static. - public FieldFinder(string fullTypeName, string fieldName, bool isStatic) + /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). + public FieldFinder(string fullTypeName, string fieldName, string nounPhrase = null) { this.FullTypeName = fullTypeName; this.FieldName = fieldName; - this.IsStatic = isStatic; - this.NounPhrase = $"obsolete {fullTypeName}.{fieldName} field"; + this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{fieldName} field"; } /********* ** Protected methods *********/ - /// Get whether a field reference should be rewritten. + /// Get whether a CIL instruction matches. /// The IL instruction. - /// The field reference. /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) + public bool IsMatch(Instruction instruction, bool platformChanged) { + FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); return - this.IsStaticField(instruction) == this.IsStatic + fieldRef != null && fieldRef.DeclaringType.FullName == this.FullTypeName && fieldRef.Name == this.FieldName; } diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs index 6c210d68..bea549ee 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs @@ -1,11 +1,10 @@ using Mono.Cecil; using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; namespace StardewModdingAPI.AssemblyRewriters.Finders { /// Finds CIL instructions that reference a given method. - public sealed class MethodFinder : BaseMethodFinder + public class MethodFinder : IInstructionFinder { /********* ** Properties @@ -21,7 +20,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders ** Accessors *********/ /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } + public string NounPhrase { get; } /********* @@ -41,13 +40,15 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /********* ** Protected methods *********/ - /// Get whether a method reference should be rewritten. + /// Get whether a CIL instruction matches. /// The IL instruction. - /// The method reference. /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) + public bool IsMatch(Instruction instruction, bool platformChanged) { - return methodRef.DeclaringType.FullName == this.FullTypeName + MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + return + methodRef != null + && methodRef.DeclaringType.FullName == this.FullTypeName && methodRef.Name == this.MethodName; } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs deleted file mode 100644 index ac2facec..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldFinder.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace StardewModdingAPI.AssemblyRewriters.Framework -{ - /// Base class for a field finder. - public abstract class BaseFieldFinder : IInstructionFinder - { - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public abstract string NounPhrase { get; } - - - /********* - ** Public methods - *********/ - /// Get whether a CIL instruction matches. - /// The IL instruction. - /// Whether the mod was compiled on a different platform. - public bool IsMatch(Instruction instruction, bool platformChanged) - { - if (instruction.OpCode != OpCodes.Ldfld && instruction.OpCode != OpCodes.Ldsfld && instruction.OpCode != OpCodes.Stfld && instruction.OpCode != OpCodes.Stsfld) - return false; // not a field reference - return this.IsMatch(instruction, (FieldReference)instruction.Operand, platformChanged); - } - - - /********* - ** Protected methods - *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected abstract bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged); - - /// Whether an instruction is a static field reference. - /// The IL instruction. - protected bool IsStaticField(Instruction instruction) - { - return instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld; - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs deleted file mode 100644 index b2c25587..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseFieldRewriter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace StardewModdingAPI.AssemblyRewriters.Framework -{ - /// Base class for a field rewriter. - public abstract class BaseFieldRewriter : BaseFieldFinder, IInstructionRewriter - { - /********* - ** Public methods - *********/ - /// Rewrite a CIL instruction for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction to rewrite. - /// Metadata for mapping assemblies to the current platform. - public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) - { - FieldReference fieldRef = (FieldReference)instruction.Operand; - this.Rewrite(module, cil, instruction, fieldRef, assemblyMap); - } - - - /********* - ** Protected methods - *********/ - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which references the field. - /// The field reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - protected abstract void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap); - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs deleted file mode 100644 index bb71a9d7..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodFinder.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace StardewModdingAPI.AssemblyRewriters.Framework -{ - /// Base class for a method finder. - public abstract class BaseMethodFinder : IInstructionFinder - { - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public abstract string NounPhrase { get; } - - - /********* - ** Public methods - *********/ - /// Get whether a CIL instruction matches. - /// The IL instruction. - /// Whether the mod was compiled on a different platform. - public bool IsMatch(Instruction instruction, bool platformChanged) - { - if (instruction.OpCode != OpCodes.Call && instruction.OpCode != OpCodes.Callvirt) - return false; // not a method reference - return this.IsMatch(instruction, (MethodReference)instruction.Operand, platformChanged); - } - - - /********* - ** Protected methods - *********/ - /// Get whether a method reference should be rewritten. - /// The IL instruction. - /// The method reference. - /// Whether the mod was compiled on a different platform. - protected abstract bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged); - - /// Get whether a method definition matches the signature expected by a method reference. - /// The method definition. - /// The method reference. - protected bool HasMatchingSignature(MethodInfo definition, MethodReference reference) - { - // same name - if (definition.Name != reference.Name) - return false; - - // same arguments - ParameterInfo[] definitionParameters = definition.GetParameters(); - ParameterDefinition[] referenceParameters = reference.Parameters.ToArray(); - if (referenceParameters.Length != definitionParameters.Length) - return false; - for (int i = 0; i < referenceParameters.Length; i++) - { - if (!RewriteHelper.IsMatchingType(definitionParameters[i].ParameterType, referenceParameters[i].ParameterType)) - return false; - } - return true; - } - - /// Get whether a type has a method whose signature matches the one expected by a method reference. - /// The type to check. - /// The method reference. - protected bool HasMatchingSignature(Type type, MethodReference reference) - { - return type - .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public) - .Any(method => this.HasMatchingSignature(method, reference)); - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs deleted file mode 100644 index 6af1a0e1..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/BaseMethodRewriter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace StardewModdingAPI.AssemblyRewriters.Framework -{ - /// Base class for a method rewriter. - public abstract class BaseMethodRewriter : BaseMethodFinder, IInstructionRewriter - { - /********* - ** Public methods - *********/ - /// Rewrite a CIL instruction for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction to rewrite. - /// Metadata for mapping assemblies to the current platform. - public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) - { - MethodReference methodRef = (MethodReference)instruction.Operand; - this.Rewrite(module, cil, instruction, methodRef, assemblyMap); - } - - - /********* - ** Protected methods - *********/ - /// Rewrite a method for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction which calls the method. - /// The method reference invoked by the . - /// Metadata for mapping assemblies to the current platform. - protected abstract void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap); - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Framework/RewriteHelper.cs b/src/StardewModdingAPI.AssemblyRewriters/Framework/RewriteHelper.cs deleted file mode 100644 index 0307053f..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/Framework/RewriteHelper.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using Mono.Cecil; - -namespace StardewModdingAPI.AssemblyRewriters.Framework -{ - /// Provides helper methods for field rewriters. - internal static class RewriteHelper - { - /********* - ** Public methods - *********/ - /// Get whether a type matches a type reference. - /// The defined type. - /// The type reference. - public static bool IsMatchingType(Type type, TypeReference reference) - { - // same namespace & name - if (type.Namespace != reference.Namespace || type.Name != reference.Name) - return false; - - // same generic parameters - if (type.IsGenericType) - { - if (!reference.IsGenericInstance) - return false; - - Type[] defGenerics = type.GetGenericArguments(); - TypeReference[] refGenerics = ((GenericInstanceType)reference).GenericArguments.ToArray(); - if (defGenerics.Length != refGenerics.Length) - return false; - for (int i = 0; i < defGenerics.Length; i++) - { - if (!RewriteHelper.IsMatchingType(defGenerics[i], refGenerics[i])) - return false; - } - } - - return true; - } - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/RewriteHelper.cs b/src/StardewModdingAPI.AssemblyRewriters/RewriteHelper.cs new file mode 100644 index 00000000..cfb330dd --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/RewriteHelper.cs @@ -0,0 +1,94 @@ +using System; +using System.Linq; +using System.Reflection; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace StardewModdingAPI.AssemblyRewriters +{ + /// Provides helper methods for field rewriters. + internal static class RewriteHelper + { + /********* + ** Public methods + *********/ + /// Get the field reference from an instruction if it matches. + /// The IL instruction. + public static FieldReference AsFieldReference(Instruction instruction) + { + return instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld + ? (FieldReference)instruction.Operand + : null; + } + + /// Get the method reference from an instruction if it matches. + /// The IL instruction. + public static MethodReference AsMethodReference(Instruction instruction) + { + return instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt + ? (MethodReference)instruction.Operand + : null; + } + + /// Get whether a type matches a type reference. + /// The defined type. + /// The type reference. + public static bool IsSameType(Type type, TypeReference reference) + { + // same namespace & name + if (type.Namespace != reference.Namespace || type.Name != reference.Name) + return false; + + // same generic parameters + if (type.IsGenericType) + { + if (!reference.IsGenericInstance) + return false; + + Type[] defGenerics = type.GetGenericArguments(); + TypeReference[] refGenerics = ((GenericInstanceType)reference).GenericArguments.ToArray(); + if (defGenerics.Length != refGenerics.Length) + return false; + for (int i = 0; i < defGenerics.Length; i++) + { + if (!RewriteHelper.IsSameType(defGenerics[i], refGenerics[i])) + return false; + } + } + + return true; + } + + /// Get whether a method definition matches the signature expected by a method reference. + /// The method definition. + /// The method reference. + public static bool HasMatchingSignature(MethodInfo definition, MethodReference reference) + { + // same name + if (definition.Name != reference.Name) + return false; + + // same arguments + ParameterInfo[] definitionParameters = definition.GetParameters(); + ParameterDefinition[] referenceParameters = reference.Parameters.ToArray(); + if (referenceParameters.Length != definitionParameters.Length) + return false; + for (int i = 0; i < referenceParameters.Length; i++) + { + if (!RewriteHelper.IsSameType(definitionParameters[i].ParameterType, referenceParameters[i].ParameterType)) + return false; + } + return true; + } + + /// Get whether a type has a method whose signature matches the one expected by a method reference. + /// The type to check. + /// The method reference. + public static bool HasMatchingSignature(Type type, MethodReference reference) + { + return type + .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public) + .Any(method => RewriteHelper.HasMatchingSignature(method, reference)); + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs index 31f9a40f..a715e07b 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs @@ -2,12 +2,12 @@ using System; using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; +using StardewModdingAPI.AssemblyRewriters.Finders; namespace StardewModdingAPI.AssemblyRewriters.Rewriters { /// Rewrites references to one field with another. - public class FieldReplaceRewriter : BaseFieldRewriter + public class FieldReplaceRewriter : FieldFinder, IInstructionRewriter { /********* ** Properties @@ -15,20 +15,10 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /// The type whose field to which references should be rewritten. private readonly Type Type; - /// The field name to rewrite. - private readonly string FromFieldName; - /// The new field name to reference. private readonly string ToFieldName; - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } - - /********* ** Public methods *********/ @@ -38,38 +28,25 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /// The new field name to reference. /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). public FieldReplaceRewriter(Type type, string fromFieldName, string toFieldName, string nounPhrase = null) + : base(type.FullName, fromFieldName, nounPhrase) { this.Type = type; - this.FromFieldName = fromFieldName; this.ToFieldName = toFieldName; - this.NounPhrase = nounPhrase ?? $"{type.Name}.{fromFieldName} field"; } /********* ** Protected methods *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) - { - return - fieldRef.DeclaringType.FullName == this.Type.FullName - && fieldRef.Name == this.FromFieldName; - } - - /// Rewrite a method for compatibility. + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. - /// The instruction which references the field. - /// The field reference invoked by the . + /// The instruction to rewrite. /// Metadata for mapping assemblies to the current platform. - protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) + public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) { FieldInfo field = this.Type.GetField(this.ToFieldName); - if(field == null) + if (field == null) throw new InvalidOperationException($"The {this.Type.FullName} class doesn't have a {this.ToFieldName} field."); FieldReference newRef = module.Import(field); cil.Replace(instruction, cil.Create(instruction.OpCode, newRef)); diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs index caf0a16c..62e24559 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs @@ -1,12 +1,12 @@ using System; using Mono.Cecil; using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; +using StardewModdingAPI.AssemblyRewriters.Finders; namespace StardewModdingAPI.AssemblyRewriters.Rewriters { /// Rewrites field references into property references. - public class FieldToPropertyRewriter : BaseFieldRewriter + public class FieldToPropertyRewriter : FieldFinder, IInstructionRewriter { /********* ** Properties @@ -18,13 +18,6 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters private readonly string FieldName; - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } - - /********* ** Public methods *********/ @@ -33,34 +26,22 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /// The field name to rewrite. /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). public FieldToPropertyRewriter(Type type, string fieldName, string nounPhrase = null) + : base(type.FullName, fieldName, nounPhrase) { this.Type = type; this.FieldName = fieldName; - this.NounPhrase = nounPhrase ?? $"{type.Name}.{fieldName} field"; } /********* ** Protected methods *********/ - /// Get whether a field reference should be rewritten. - /// The IL instruction. - /// The field reference. - /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, FieldReference fieldRef, bool platformChanged) - { - return - fieldRef.DeclaringType.FullName == this.Type.FullName - && fieldRef.Name == this.FieldName; - } - - /// Rewrite a method for compatibility. + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. - /// The instruction which references the field. - /// The field reference invoked by the . + /// The instruction to rewrite. /// Metadata for mapping assemblies to the current platform. - protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, FieldReference fieldRef, PlatformAssemblyMap assemblyMap) + public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) { string methodPrefix = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld ? "get" : "set"; MethodReference propertyRef = module.Import(this.Type.GetMethod($"{methodPrefix}_{this.FieldName}")); diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs index 9c19f473..9b895056 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs @@ -1,12 +1,11 @@ using System; using Mono.Cecil; using Mono.Cecil.Cil; -using StardewModdingAPI.AssemblyRewriters.Framework; namespace StardewModdingAPI.AssemblyRewriters.Rewriters { /// Rewrites method references from one parent type to another if the signatures match. - public class MethodParentRewriter : BaseMethodRewriter + public class MethodParentRewriter : IInstructionRewriter { /********* ** Properties @@ -25,7 +24,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters ** Accessors *********/ /// A brief noun phrase indicating what the instruction finder matches. - public override string NounPhrase { get; } + public string NounPhrase { get; } /********* @@ -48,26 +47,27 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /********* ** Protected methods *********/ - /// Get whether a method reference should be rewritten. + /// Get whether a CIL instruction matches. /// The IL instruction. - /// The method reference. /// Whether the mod was compiled on a different platform. - protected override bool IsMatch(Instruction instruction, MethodReference methodRef, bool platformChanged) + public bool IsMatch(Instruction instruction, bool platformChanged) { + MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); return - (!this.OnlyIfPlatformChanged || platformChanged) + methodRef != null + && (platformChanged || !this.OnlyIfPlatformChanged) && methodRef.DeclaringType.FullName == this.FromType.FullName - && this.HasMatchingSignature(this.ToType, methodRef); + && RewriteHelper.HasMatchingSignature(this.ToType, methodRef); } - /// Rewrite a method for compatibility. + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. - /// The instruction which calls the method. - /// The method reference invoked by the . + /// The instruction to rewrite. /// Metadata for mapping assemblies to the current platform. - protected override void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, MethodReference methodRef, PlatformAssemblyMap assemblyMap) + public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) { + MethodReference methodRef = (MethodReference)instruction.Operand; methodRef.DeclaringType = module.Import(this.ToType); } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 0136c39b..09fd79ed 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -70,11 +70,7 @@ - - - - - + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 9a7fba84..52be6c05 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -143,7 +143,7 @@ namespace StardewModdingAPI return new IInstructionFinder[] { // changes in Stardew Valley 1.2 (that don't have rewriters) - new FieldFinder("StardewValley.Item", "set_Name", isStatic: false), + new FieldFinder("StardewValley.Item", "set_Name"), // APIs removed in SMAPI 1.9 new TypeFinder("StardewModdingAPI.Advanced.ConfigFile"), -- cgit From 06f5e92b88fac190f94690f1580775449014e411 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 25 Mar 2017 22:08:48 -0400 Subject: minor cleanup --- .../Finders/EventFinder.cs | 5 +++-- .../Finders/MethodFinder.cs | 5 +++-- .../Finders/TypeFinder.cs | 25 +++++++++++----------- .../Rewriters/FieldReplaceRewriter.cs | 4 ---- .../Rewriters/FieldToPropertyRewriter.cs | 4 ---- .../Rewriters/MethodParentRewriter.cs | 4 ---- 6 files changed, 19 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs index 9d0184c6..848e54ff 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs @@ -29,11 +29,12 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// Construct an instance. /// The full type name for which to find references. /// The event name for which to find references. - public EventFinder(string fullTypeName, string eventName) + /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). + public EventFinder(string fullTypeName, string eventName, string nounPhrase = null) { this.FullTypeName = fullTypeName; this.EventName = eventName; - this.NounPhrase = $"obsolete {fullTypeName}.{eventName} event"; + this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{eventName} event"; } diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs index bea549ee..d174bacd 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs @@ -29,11 +29,12 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// Construct an instance. /// The full type name for which to find references. /// The method name for which to find references. - public MethodFinder(string fullTypeName, string methodName) + /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). + public MethodFinder(string fullTypeName, string methodName, string nounPhrase = null) { this.FullTypeName = fullTypeName; this.MethodName = methodName; - this.NounPhrase = $"obsolete {fullTypeName}.{methodName} method"; + this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{methodName} method"; } diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs index ba8e7102..8f492d5f 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs @@ -5,7 +5,7 @@ using Mono.Cecil.Cil; namespace StardewModdingAPI.AssemblyRewriters.Finders { /// Finds CIL instructions that reference a given type. - public sealed class TypeFinder : IInstructionFinder + public class TypeFinder : IInstructionFinder { /********* ** Accessors @@ -26,10 +26,11 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders *********/ /// Construct an instance. /// The full type name to match. - public TypeFinder(string fullTypeName) + /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). + public TypeFinder(string fullTypeName, string nounPhrase = null) { this.FullTypeName = fullTypeName; - this.NounPhrase = $"obsolete {fullTypeName} type"; + this.NounPhrase = nounPhrase ?? $"{fullTypeName} type"; } /// Get whether a CIL instruction matches. @@ -40,22 +41,22 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders string fullName = this.FullTypeName; // field reference - if (instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld) + FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + if (fieldRef != null) { - FieldReference field = (FieldReference)instruction.Operand; return - field.DeclaringType.FullName == fullName // field on target class - || field.FieldType.FullName == fullName; // field value is target class + fieldRef.DeclaringType.FullName == fullName // field on target class + || fieldRef.FieldType.FullName == fullName; // field value is target class } // method reference - if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt) + MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + if (methodRef != null) { - MethodReference method = (MethodReference)instruction.Operand; return - method.DeclaringType.FullName == fullName // method on target class - || method.ReturnType.FullName == fullName // method returns target class - || method.Parameters.Any(p => p.ParameterType.FullName == fullName); // method parameters + methodRef.DeclaringType.FullName == fullName // method on target class + || methodRef.ReturnType.FullName == fullName // method returns target class + || methodRef.Parameters.Any(p => p.ParameterType.FullName == fullName); // method parameters } return false; diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs index a715e07b..ffd22e7c 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs @@ -34,10 +34,6 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters this.ToFieldName = toFieldName; } - - /********* - ** Protected methods - *********/ /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs index 62e24559..f2f99cc1 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs @@ -32,10 +32,6 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters this.FieldName = fieldName; } - - /********* - ** Protected methods - *********/ /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs index 9b895056..24d4dff9 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs @@ -43,10 +43,6 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters this.OnlyIfPlatformChanged = onlyIfPlatformChanged; } - - /********* - ** Protected methods - *********/ /// Get whether a CIL instruction matches. /// The IL instruction. /// Whether the mod was compiled on a different platform. -- cgit From 23443721cd2cc5391510a6e65b4e6559037e5b5e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Mar 2017 19:01:13 -0400 Subject: allow multiple rewriters to change the same CIL instruction (#254) --- src/StardewModdingAPI/Framework/AssemblyLoader.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index c7ad3da4..aee0bbb3 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -212,9 +212,11 @@ namespace StardewModdingAPI.Framework } // rewrite instruction if needed - IInstructionRewriter rewriter = rewriters.FirstOrDefault(p => p.IsMatch(instruction, platformChanged)); - if (rewriter != null) + foreach (IInstructionRewriter rewriter in rewriters) { + if (!rewriter.IsMatch(instruction, platformChanged)) + continue; + this.LogOnce(this.Monitor, loggedMessages, $"Rewriting {assembly.Name.Name} to fix {rewriter.NounPhrase}..."); rewriter.Rewrite(module, cil, instruction, this.AssemblyMap); anyRewritten = true; -- cgit From 85ed48809032fdbb8461ce4c34acfbe06f68652b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Mar 2017 19:01:35 -0400 Subject: merge CIL finders & rewriters into one interface (#254) --- .../Finders/EventFinder.cs | 22 ++++++++++++-- .../Finders/FieldFinder.cs | 22 ++++++++++++-- .../Finders/MethodFinder.cs | 22 ++++++++++++-- .../Finders/TypeFinder.cs | 26 ++++++++++++++-- .../IInstructionFinder.cs | 23 -------------- .../IInstructionRewriter.cs | 16 ++++++++-- .../IncompatibleInstructionException.cs | 35 ++++++++++++++++++++++ .../Rewriters/FieldReplaceRewriter.cs | 28 +++++++++-------- .../Rewriters/FieldToPropertyRewriter.cs | 11 +++++-- .../Rewriters/MethodParentRewriter.cs | 35 ++++++++++++++-------- .../StardewModdingAPI.AssemblyRewriters.csproj | 2 +- src/StardewModdingAPI/Constants.cs | 23 +++++++------- src/StardewModdingAPI/Framework/AssemblyLoader.cs | 33 ++++++++++---------- .../Framework/IncompatibleInstructionException.cs | 27 ----------------- src/StardewModdingAPI/Program.cs | 1 + src/StardewModdingAPI/StardewModdingAPI.csproj | 1 - 16 files changed, 203 insertions(+), 124 deletions(-) delete mode 100644 src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs create mode 100644 src/StardewModdingAPI.AssemblyRewriters/IncompatibleInstructionException.cs delete mode 100644 src/StardewModdingAPI/Framework/IncompatibleInstructionException.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs index 848e54ff..bcceee32 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs @@ -3,8 +3,8 @@ using Mono.Cecil.Cil; namespace StardewModdingAPI.AssemblyRewriters.Finders { - /// Finds CIL instructions that reference a given event. - public sealed class EventFinder : IInstructionFinder + /// Finds incompatible CIL instructions that reference a given event and throws an . + public class EventFinder : IInstructionRewriter { /********* ** Properties @@ -37,6 +37,22 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{eventName} event"; } + /// Rewrite a CIL instruction for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + if (!this.IsMatch(instruction, platformChanged)) + return false; + + throw new IncompatibleInstructionException(this.NounPhrase); + } + /********* ** Protected methods @@ -44,7 +60,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// Get whether a CIL instruction matches. /// The IL instruction. /// Whether the mod was compiled on a different platform. - public bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction, bool platformChanged) { MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); return diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs index 068119b8..cdfc3bd5 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs @@ -3,8 +3,8 @@ using Mono.Cecil.Cil; namespace StardewModdingAPI.AssemblyRewriters.Finders { - /// Finds CIL instructions that reference a given field. - public class FieldFinder : IInstructionFinder + /// Finds incompatible CIL instructions that reference a given field and throws an . + public class FieldFinder : IInstructionRewriter { /********* ** Properties @@ -37,6 +37,22 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{fieldName} field"; } + /// Rewrite a CIL instruction for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public virtual bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + if (!this.IsMatch(instruction, platformChanged)) + return false; + + throw new IncompatibleInstructionException(this.NounPhrase); + } + /********* ** Protected methods @@ -44,7 +60,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// Get whether a CIL instruction matches. /// The IL instruction. /// Whether the mod was compiled on a different platform. - public bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction, bool platformChanged) { FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); return diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs index d174bacd..2efcbb0f 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs @@ -3,8 +3,8 @@ using Mono.Cecil.Cil; namespace StardewModdingAPI.AssemblyRewriters.Finders { - /// Finds CIL instructions that reference a given method. - public class MethodFinder : IInstructionFinder + /// Finds incompatible CIL instructions that reference a given method and throws an . + public class MethodFinder : IInstructionRewriter { /********* ** Properties @@ -37,6 +37,22 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{methodName} method"; } + /// Rewrite a CIL instruction for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + if (!this.IsMatch(instruction, platformChanged)) + return false; + + throw new IncompatibleInstructionException(this.NounPhrase); + } + /********* ** Protected methods @@ -44,7 +60,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// Get whether a CIL instruction matches. /// The IL instruction. /// Whether the mod was compiled on a different platform. - public bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction, bool platformChanged) { MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); return diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs index 8f492d5f..96cbb229 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs @@ -4,8 +4,8 @@ using Mono.Cecil.Cil; namespace StardewModdingAPI.AssemblyRewriters.Finders { - /// Finds CIL instructions that reference a given type. - public class TypeFinder : IInstructionFinder + /// Finds incompatible CIL instructions that reference a given type and throws an . + public class TypeFinder : IInstructionRewriter { /********* ** Accessors @@ -33,10 +33,30 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName} type"; } + /// Rewrite a CIL instruction for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public virtual bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + if (!this.IsMatch(instruction, platformChanged)) + return false; + + throw new IncompatibleInstructionException(this.NounPhrase); + } + + + /********* + ** Protected methods + *********/ /// Get whether a CIL instruction matches. /// The IL instruction. /// Whether the mod was compiled on a different platform. - public bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction, bool platformChanged) { string fullName = this.FullTypeName; diff --git a/src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs deleted file mode 100644 index cc3006b9..00000000 --- a/src/StardewModdingAPI.AssemblyRewriters/IInstructionFinder.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Mono.Cecil.Cil; - -namespace StardewModdingAPI.AssemblyRewriters -{ - /// Finds CIL instructions considered incompatible. - public interface IInstructionFinder - { - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - string NounPhrase { get; } - - - /********* - ** Methods - *********/ - /// Get whether a CIL instruction matches. - /// The IL instruction. - /// Whether the mod was compiled on a different platform. - bool IsMatch(Instruction instruction, bool platformChanged); - } -} diff --git a/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs index b230f227..3a7b1365 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs @@ -3,9 +3,16 @@ using Mono.Cecil.Cil; namespace StardewModdingAPI.AssemblyRewriters { - /// Rewrites a CIL instruction for compatibility. - public interface IInstructionRewriter : IInstructionFinder + /// Rewrites CIL instructions for compatibility. + public interface IInstructionRewriter { + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the rewriter matches. + string NounPhrase { get; } + + /********* ** Methods *********/ @@ -14,6 +21,9 @@ namespace StardewModdingAPI.AssemblyRewriters /// The CIL rewriter. /// The instruction to rewrite. /// Metadata for mapping assemblies to the current platform. - void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap); + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged); } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/IncompatibleInstructionException.cs b/src/StardewModdingAPI.AssemblyRewriters/IncompatibleInstructionException.cs new file mode 100644 index 00000000..f7e6bd8f --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/IncompatibleInstructionException.cs @@ -0,0 +1,35 @@ +using System; + +namespace StardewModdingAPI.AssemblyRewriters +{ + /// An exception raised when an incompatible instruction is found while loading a mod assembly. + public class IncompatibleInstructionException : Exception + { + /********* + ** Accessors + *********/ + /// A brief noun phrase which describes the incompatible instruction that was found. + public string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// A brief noun phrase which describes the incompatible instruction that was found. + public IncompatibleInstructionException(string nounPhrase) + : base($"Found an incompatible CIL instruction ({nounPhrase}).") + { + this.NounPhrase = nounPhrase; + } + + /// Construct an instance. + /// A brief noun phrase which describes the incompatible instruction that was found. + /// A message which describes the error. + public IncompatibleInstructionException(string nounPhrase, string message) + : base(message) + { + this.NounPhrase = nounPhrase; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs index ffd22e7c..95663c49 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs @@ -7,16 +7,13 @@ using StardewModdingAPI.AssemblyRewriters.Finders; namespace StardewModdingAPI.AssemblyRewriters.Rewriters { /// Rewrites references to one field with another. - public class FieldReplaceRewriter : FieldFinder, IInstructionRewriter + public class FieldReplaceRewriter : FieldFinder { /********* ** Properties *********/ - /// The type whose field to which references should be rewritten. - private readonly Type Type; - - /// The new field name to reference. - private readonly string ToFieldName; + /// The new field to reference. + private readonly FieldInfo ToField; /********* @@ -30,8 +27,9 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters public FieldReplaceRewriter(Type type, string fromFieldName, string toFieldName, string nounPhrase = null) : base(type.FullName, fromFieldName, nounPhrase) { - this.Type = type; - this.ToFieldName = toFieldName; + this.ToField = type.GetField(toFieldName); + if (this.ToField == null) + throw new InvalidOperationException($"The {type.FullName} class doesn't have a {toFieldName} field."); } /// Rewrite a CIL instruction for compatibility. @@ -39,13 +37,17 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /// The CIL rewriter. /// The instruction to rewrite. /// Metadata for mapping assemblies to the current platform. - public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public override bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - FieldInfo field = this.Type.GetField(this.ToFieldName); - if (field == null) - throw new InvalidOperationException($"The {this.Type.FullName} class doesn't have a {this.ToFieldName} field."); - FieldReference newRef = module.Import(field); + if (!this.IsMatch(instruction, platformChanged)) + return false; + + FieldReference newRef = module.Import(this.ToField); cil.Replace(instruction, cil.Create(instruction.OpCode, newRef)); + return true; } } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs index f2f99cc1..a25f3fef 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs @@ -6,7 +6,7 @@ using StardewModdingAPI.AssemblyRewriters.Finders; namespace StardewModdingAPI.AssemblyRewriters.Rewriters { /// Rewrites field references into property references. - public class FieldToPropertyRewriter : FieldFinder, IInstructionRewriter + public class FieldToPropertyRewriter : FieldFinder { /********* ** Properties @@ -37,11 +37,18 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /// The CIL rewriter. /// The instruction to rewrite. /// Metadata for mapping assemblies to the current platform. - public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public override bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { + if (!this.IsMatch(instruction, platformChanged)) + return false; + string methodPrefix = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld ? "get" : "set"; MethodReference propertyRef = module.Import(this.Type.GetMethod($"{methodPrefix}_{this.FieldName}")); cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef)); + return true; } } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs index 24d4dff9..3ec8c704 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs @@ -43,10 +43,32 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters this.OnlyIfPlatformChanged = onlyIfPlatformChanged; } + /// Rewrite a CIL instruction for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + if (!this.IsMatch(instruction, platformChanged)) + return false; + + MethodReference methodRef = (MethodReference)instruction.Operand; + methodRef.DeclaringType = module.Import(this.ToType); + return true; + } + + + /********* + ** Protected methods + *********/ /// Get whether a CIL instruction matches. /// The IL instruction. /// Whether the mod was compiled on a different platform. - public bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction, bool platformChanged) { MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); return @@ -55,16 +77,5 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters && methodRef.DeclaringType.FullName == this.FromType.FullName && RewriteHelper.HasMatchingSignature(this.ToType, methodRef); } - - /// Rewrite a CIL instruction for compatibility. - /// The module being rewritten. - /// The CIL rewriter. - /// The instruction to rewrite. - /// Metadata for mapping assemblies to the current platform. - public void Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap) - { - MethodReference methodRef = (MethodReference)instruction.Operand; - methodRef.DeclaringType = module.Import(this.ToType); - } } } diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 09fd79ed..3c3acde3 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -70,8 +70,8 @@ + - diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 52be6c05..de0eab57 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -137,12 +137,15 @@ namespace StardewModdingAPI return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences, targetAssemblies); } - /// Get finders which match incompatible CIL instructions in mod assemblies. - internal static IEnumerable GetIncompatibilityFinders() + /// Get rewriters which detect or fix incompatible CIL instructions in mod assemblies. + internal static IEnumerable GetRewriters() { - return new IInstructionFinder[] + return new IInstructionRewriter[] { - // changes in Stardew Valley 1.2 (that don't have rewriters) + /**** + ** 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 @@ -161,15 +164,11 @@ namespace StardewModdingAPI new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderHudEventNoCheck"), new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderGuiEventNoCheck"), new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderHudEventNoCheck"), - new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderGuiEventNoCheck") - }; - } + new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderGuiEventNoCheck"), - /// Get rewriters which fix incompatible CIL instructions in mod assemblies. - internal static IEnumerable GetRewriters() - { - return new IInstructionRewriter[] - { + /**** + ** Rewriters change CIL as needed to fix incompatible code + ****/ // crossplatform new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchWrapper), onlyIfPlatformChanged: true), diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index aee0bbb3..5d00c525 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -193,33 +193,30 @@ namespace StardewModdingAPI.Framework this.ChangeTypeScope(type); } - // find incompatible instructions + // find (and optionally rewrite) incompatible instructions bool anyRewritten = false; - IInstructionFinder[] finders = Constants.GetIncompatibilityFinders().ToArray(); IInstructionRewriter[] rewriters = Constants.GetRewriters().ToArray(); foreach (MethodDefinition method in this.GetMethods(module)) { ILProcessor cil = method.Body.GetILProcessor(); foreach (Instruction instruction in cil.Body.Instructions.ToArray()) { - // throw exception if instruction is incompatible but can't be rewritten - IInstructionFinder finder = finders.FirstOrDefault(p => p.IsMatch(instruction, platformChanged)); - if (finder != null) - { - if (!assumeCompatible) - throw new IncompatibleInstructionException(finder.NounPhrase, $"Found an incompatible CIL instruction ({finder.NounPhrase}) while loading assembly {assembly.Name.Name}."); - this.LogOnce(this.Monitor, loggedMessages, $"Found an incompatible CIL instruction ({finder.NounPhrase}) while loading assembly {assembly.Name.Name}, but SMAPI is configured to allow it anyway. The mod may crash or behave unexpectedly.", LogLevel.Warn); - } - - // rewrite instruction if needed foreach (IInstructionRewriter rewriter in rewriters) { - if (!rewriter.IsMatch(instruction, platformChanged)) - continue; - - this.LogOnce(this.Monitor, loggedMessages, $"Rewriting {assembly.Name.Name} to fix {rewriter.NounPhrase}..."); - rewriter.Rewrite(module, cil, instruction, this.AssemblyMap); - anyRewritten = true; + try + { + if (rewriter.Rewrite(module, cil, instruction, this.AssemblyMap, platformChanged)) + { + this.LogOnce(this.Monitor, loggedMessages, $"Rewrote {assembly.Name.Name} to fix {rewriter.NounPhrase}..."); + anyRewritten = true; + } + } + catch (IncompatibleInstructionException) + { + if (!assumeCompatible) + throw new IncompatibleInstructionException(rewriter.NounPhrase, $"Found an incompatible CIL instruction ({rewriter.NounPhrase}) while loading assembly {assembly.Name.Name}."); + this.LogOnce(this.Monitor, loggedMessages, $"Found an incompatible CIL instruction ({rewriter.NounPhrase}) while loading assembly {assembly.Name.Name}, but SMAPI is configured to allow it anyway. The mod may crash or behave unexpectedly.", LogLevel.Warn); + } } } } diff --git a/src/StardewModdingAPI/Framework/IncompatibleInstructionException.cs b/src/StardewModdingAPI/Framework/IncompatibleInstructionException.cs deleted file mode 100644 index affe2cb3..00000000 --- a/src/StardewModdingAPI/Framework/IncompatibleInstructionException.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace StardewModdingAPI.Framework -{ - /// An exception raised when an incompatible instruction is found while loading a mod assembly. - internal class IncompatibleInstructionException : Exception - { - /********* - ** Accessors - *********/ - /// A brief noun phrase which describes the incompatible instruction that was found. - public string NounPhrase { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// A brief noun phrase which describes the incompatible instruction that was found. - /// A message which describes the error. - public IncompatibleInstructionException(string nounPhrase, string message) - : base(message) - { - this.NounPhrase = nounPhrase; - } - } -} diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index ac646b1f..25605148 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -12,6 +12,7 @@ using System.Windows.Forms; #endif using Microsoft.Xna.Framework.Graphics; using Newtonsoft.Json; +using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.Events; using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.Logging; diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 091b3d90..bcd0c390 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -150,7 +150,6 @@ - -- cgit From 8bf3ef118a822afd1c7d7f80f6cf6eaeed346167 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Mar 2017 19:17:48 -0400 Subject: add support for rewriting method definitions (#254) --- .../Finders/EventFinder.cs | 19 +++++++++--- .../Finders/FieldFinder.cs | 17 ++++++++-- .../Finders/MethodFinder.cs | 17 ++++++++-- .../Finders/TypeFinder.cs | 36 ++++++++++++++++++++-- .../IInstructionRewriter.cs | 9 ++++++ .../Rewriters/FieldReplaceRewriter.cs | 2 +- .../Rewriters/FieldToPropertyRewriter.cs | 2 +- .../Rewriters/MethodParentRewriter.cs | 12 ++++++++ src/StardewModdingAPI/Framework/AssemblyLoader.cs | 20 ++++++++++++ 9 files changed, 119 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs index bcceee32..c0051469 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs @@ -37,6 +37,18 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{eventName} event"; } + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public virtual bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + return false; + } + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. @@ -45,9 +57,9 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// Whether the mod was compiled on a different platform. /// Returns whether the instruction was rewritten. /// The CIL instruction is not compatible, and can't be rewritten. - public bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + public virtual bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; throw new IncompatibleInstructionException(this.NounPhrase); @@ -59,8 +71,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders *********/ /// Get whether a CIL instruction matches. /// The IL instruction. - /// Whether the mod was compiled on a different platform. - protected bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction) { MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); return diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs index cdfc3bd5..b44883e9 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs @@ -37,6 +37,18 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{fieldName} field"; } + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public virtual bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + return false; + } + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. @@ -47,7 +59,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// The CIL instruction is not compatible, and can't be rewritten. public virtual bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; throw new IncompatibleInstructionException(this.NounPhrase); @@ -59,8 +71,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders *********/ /// Get whether a CIL instruction matches. /// The IL instruction. - /// Whether the mod was compiled on a different platform. - protected bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction) { FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); return diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs index 2efcbb0f..19dda58a 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs @@ -37,6 +37,18 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{methodName} method"; } + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public virtual bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + return false; + } + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. @@ -47,7 +59,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// The CIL instruction is not compatible, and can't be rewritten. public bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; throw new IncompatibleInstructionException(this.NounPhrase); @@ -59,8 +71,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders *********/ /// Get whether a CIL instruction matches. /// The IL instruction. - /// Whether the mod was compiled on a different platform. - protected bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction) { MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); return diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs index 96cbb229..0e4d6824 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs @@ -33,6 +33,21 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName} type"; } + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public virtual bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + if (!this.IsMatch(method)) + return false; + + throw new IncompatibleInstructionException(this.NounPhrase); + } + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. @@ -43,7 +58,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// The CIL instruction is not compatible, and can't be rewritten. public virtual bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; throw new IncompatibleInstructionException(this.NounPhrase); @@ -53,10 +68,25 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /********* ** Protected methods *********/ + /// Get whether a CIL instruction matches. + /// The method deifnition. + protected bool IsMatch(MethodDefinition method) + { + if (method.ReturnType.FullName == this.FullTypeName) + return true; + + foreach (VariableDefinition variable in method.Body.Variables) + { + if (variable.VariableType.FullName == this.FullTypeName) + return true; + } + + return false; + } + /// Get whether a CIL instruction matches. /// The IL instruction. - /// Whether the mod was compiled on a different platform. - protected bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction) { string fullName = this.FullTypeName; diff --git a/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs index 3a7b1365..2f16b23d 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs @@ -16,6 +16,15 @@ namespace StardewModdingAPI.AssemblyRewriters /********* ** Methods *********/ + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged); + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs index 95663c49..73844073 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs @@ -42,7 +42,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /// The CIL instruction is not compatible, and can't be rewritten. public override bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; FieldReference newRef = module.Import(this.ToField); diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs index a25f3fef..3f57042d 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs @@ -42,7 +42,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /// The CIL instruction is not compatible, and can't be rewritten. public override bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; string methodPrefix = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld ? "get" : "set"; diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs index 3ec8c704..035ef211 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs @@ -43,6 +43,18 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters this.OnlyIfPlatformChanged = onlyIfPlatformChanged; } + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + return false; + } + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index 5d00c525..f6fe89f5 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -198,6 +198,26 @@ namespace StardewModdingAPI.Framework IInstructionRewriter[] rewriters = Constants.GetRewriters().ToArray(); foreach (MethodDefinition method in this.GetMethods(module)) { + // check method definition + foreach (IInstructionRewriter rewriter in rewriters) + { + try + { + if (rewriter.Rewrite(module, method, this.AssemblyMap, platformChanged)) + { + this.LogOnce(this.Monitor, loggedMessages, $"Rewrote {assembly.Name.Name} to fix {rewriter.NounPhrase}..."); + anyRewritten = true; + } + } + catch (IncompatibleInstructionException) + { + if (!assumeCompatible) + throw new IncompatibleInstructionException(rewriter.NounPhrase, $"Found an incompatible CIL instruction ({rewriter.NounPhrase}) while loading assembly {assembly.Name.Name}."); + this.LogOnce(this.Monitor, loggedMessages, $"Found an incompatible CIL instruction ({rewriter.NounPhrase}) while loading assembly {assembly.Name.Name}, but SMAPI is configured to allow it anyway. The mod may crash or behave unexpectedly.", LogLevel.Warn); + } + } + + // check CIL instructions ILProcessor cil = method.Body.GetILProcessor(); foreach (Instruction instruction in cil.Body.Instructions.ToArray()) { -- cgit From 911957d582d471dcb9ab1d3762273620269c87f4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Mar 2017 19:21:24 -0400 Subject: fix type finder not matching generic type parameters (#254) --- .../Finders/TypeFinder.cs | 38 +++++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs index 0e4d6824..0560e38e 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs @@ -72,12 +72,12 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// The method deifnition. protected bool IsMatch(MethodDefinition method) { - if (method.ReturnType.FullName == this.FullTypeName) + if (this.IsMatch(method.ReturnType)) return true; foreach (VariableDefinition variable in method.Body.Variables) { - if (variable.VariableType.FullName == this.FullTypeName) + if (this.IsMatch(variable.VariableType)) return true; } @@ -88,15 +88,13 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// The IL instruction. protected bool IsMatch(Instruction instruction) { - string fullName = this.FullTypeName; - // field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null) { return - fieldRef.DeclaringType.FullName == fullName // field on target class - || fieldRef.FieldType.FullName == fullName; // field value is target class + this.IsMatch(fieldRef.DeclaringType) // field on target class + || this.IsMatch(fieldRef.FieldType); // field value is target class } // method reference @@ -104,12 +102,34 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders if (methodRef != null) { return - methodRef.DeclaringType.FullName == fullName // method on target class - || methodRef.ReturnType.FullName == fullName // method returns target class - || methodRef.Parameters.Any(p => p.ParameterType.FullName == fullName); // method parameters + this.IsMatch(methodRef.DeclaringType) // method on target class + || this.IsMatch(methodRef.ReturnType) // method returns target class + || methodRef.Parameters.Any(p => this.IsMatch(p.ParameterType)); // method parameters } return false; } + + /// Get whether a type reference matches the expected type. + /// The type to check. + protected bool IsMatch(TypeReference type) + { + // root type + if (type.FullName == this.FullTypeName) + return true; + + // generic arguments + if (type is GenericInstanceType genericType) + { + if (genericType.GenericArguments.Any(this.IsMatch)) + return true; + } + + // generic parameters (e.g. constraints) + if (type.GenericParameters.Any(this.IsMatch)) + return true; + + return false; + } } } -- cgit From 5c253b7bae274c8509c90f8828f3f9f81653f250 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Mar 2017 20:08:26 -0400 Subject: add type reference rewriter (#254) --- .../Rewriters/TypeReferenceRewriter.cs | 157 +++++++++++++++++++++ .../StardewModdingAPI.AssemblyRewriters.csproj | 1 + 2 files changed, 158 insertions(+) create mode 100644 src/StardewModdingAPI.AssemblyRewriters/Rewriters/TypeReferenceRewriter.cs (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/TypeReferenceRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/TypeReferenceRewriter.cs new file mode 100644 index 00000000..da6d9bc9 --- /dev/null +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/TypeReferenceRewriter.cs @@ -0,0 +1,157 @@ +using System; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.AssemblyRewriters.Finders; + +namespace StardewModdingAPI.AssemblyRewriters.Rewriters +{ + /// Rewrites all references to a type. + public class TypeReferenceRewriter : TypeFinder + { + /********* + ** Properties + *********/ + /// The full type name to which to find references. + private readonly string FromTypeName; + + /// The new type to reference. + private readonly Type ToType; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name to which to find references. + /// The new type to reference. + /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). + public TypeReferenceRewriter(string fromTypeFullName, Type toType, string nounPhrase = null) + : base(fromTypeFullName, nounPhrase) + { + this.FromTypeName = fromTypeFullName; + this.ToType = toType; + } + + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public override bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + bool rewritten = false; + + // return type + if (this.IsMatch(method.ReturnType)) + { + method.ReturnType = this.RewriteIfNeeded(module, method.ReturnType); + rewritten = true; + } + + // parameters + foreach (ParameterDefinition parameter in method.Parameters) + { + if (this.IsMatch(parameter.ParameterType)) + { + parameter.ParameterType = this.RewriteIfNeeded(module, parameter.ParameterType); + rewritten = true; + } + } + + // generic parameters + for (int i = 0; i < method.GenericParameters.Count; i++) + { + var parameter = method.GenericParameters[i]; + if (this.IsMatch(parameter)) + { + TypeReference newType = this.RewriteIfNeeded(module, parameter); + if (newType != parameter) + method.GenericParameters[i] = new GenericParameter(parameter.Name, newType); + rewritten = true; + } + } + + // local variables + foreach (VariableDefinition variable in method.Body.Variables) + { + if (this.IsMatch(variable.VariableType)) + { + variable.VariableType = this.RewriteIfNeeded(module, variable.VariableType); + rewritten = true; + } + } + + return rewritten; + } + + /// Rewrite a CIL instruction for compatibility. + /// The module being rewritten. + /// The CIL rewriter. + /// The instruction to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public override bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + if (!this.IsMatch(instruction) && !instruction.ToString().Contains(this.FromTypeName)) + return false; + + // field reference + FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + if (fieldRef != null) + { + fieldRef.DeclaringType = this.RewriteIfNeeded(module, fieldRef.DeclaringType); + fieldRef.FieldType = this.RewriteIfNeeded(module, fieldRef.FieldType); + } + + // method reference + MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + if (methodRef != null) + { + methodRef.DeclaringType = this.RewriteIfNeeded(module, methodRef.DeclaringType); + methodRef.ReturnType = this.RewriteIfNeeded(module, methodRef.ReturnType); + foreach (var parameter in methodRef.Parameters) + parameter.ParameterType = this.RewriteIfNeeded(module, parameter.ParameterType); + } + + // type reference + if (instruction.Operand is TypeReference typeRef) + { + TypeReference newRef = this.RewriteIfNeeded(module, typeRef); + if (typeRef != newRef) + cil.Replace(instruction, cil.Create(instruction.OpCode, newRef)); + } + + return true; + } + + /********* + ** Private methods + *********/ + /// Get the adjusted type reference if it matches, else the same value. + /// The module being rewritten. + /// The type to replace if it matches. + private TypeReference RewriteIfNeeded(ModuleDefinition module, TypeReference type) + { + // root type + if (type.FullName == this.FromTypeName) + return module.Import(this.ToType); + + // generic arguments + if (type is GenericInstanceType genericType) + { + for (int i = 0; i < genericType.GenericArguments.Count; i++) + genericType.GenericArguments[i] = this.RewriteIfNeeded(module, genericType.GenericArguments[i]); + } + + // generic parameters (e.g. constraints) + for (int i = 0; i < type.GenericParameters.Count; i++) + type.GenericParameters[i] = new GenericParameter(this.RewriteIfNeeded(module, type.GenericParameters[i])); + + return type; + } + } +} diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 3c3acde3..775de9f2 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -76,6 +76,7 @@ + -- cgit From 3530f6f67c33dde69ac11079a9f9a43518fbb2b4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Mar 2017 20:09:46 -0400 Subject: rewrite ItemStackChange references to correct namespace (#254) --- src/StardewModdingAPI/Constants.cs | 5 +++-- src/StardewModdingAPI/StardewModdingAPI.config.json | 16 ---------------- 2 files changed, 3 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index de0eab57..1f1b6a65 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -8,6 +8,7 @@ using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.AssemblyRewriters.Finders; using StardewModdingAPI.AssemblyRewriters.Rewriters; using StardewModdingAPI.AssemblyRewriters.Rewriters.Wrappers; +using StardewModdingAPI.Events; using StardewValley; namespace StardewModdingAPI @@ -153,7 +154,6 @@ namespace StardewModdingAPI new TypeFinder("StardewModdingAPI.Advanced.IConfigFile"), new TypeFinder("StardewModdingAPI.Entities.SPlayer"), new TypeFinder("StardewModdingAPI.Extensions"), - new TypeFinder("StardewModdingAPI.Inheritance.ItemStackChange"), new TypeFinder("StardewModdingAPI.Inheritance.SGame"), new TypeFinder("StardewModdingAPI.Inheritance.SObject"), new TypeFinder("StardewModdingAPI.LogWriter"), @@ -177,7 +177,8 @@ namespace StardewModdingAPI 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)) + new FieldReplaceRewriter(typeof(Game1), "smoothFont", nameof(Game1.smallFont)), + new TypeReferenceRewriter("StardewModdingAPI.Inheritance.ItemStackChange", typeof(ItemStackChange)) }; } diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index da6d3730..a254fdd0 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -53,14 +53,6 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", "Notes": "Uses obsolete StardewModdingAPI.Extensions." }, - { - "Name": "Casks Anywhere", - "ID": "CasksAnywhere", - "Compatibility": "AssumeBroken", - "UpperVersion": "1.1", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/878", - "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." - }, { "Name": "Chest Label System", "ID": "SPDChestLabel", @@ -182,14 +174,6 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/572", "Notes": "Uses obsolete StardewModdingAPI.Extensions." }, - { - "Name": "Reusable Wallpapers", - "ID": "dae1b553-2e39-43e7-8400-c7c5c836134b", - "UpperVersion": "1.5", - "Compatibility": "AssumeBroken", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/356", - "Notes": "Uses obsolete StardewModdingAPI.Inheritance.ItemStackChange." - }, { "Name": "Save Anywhere", "ID": "SaveAnywhere", -- cgit From 04cae4ef4608352329c746e2163b2a9356b063f6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Mar 2017 20:15:48 -0400 Subject: fix SMAPI not recognising Mod instances that don't subclass Mod directly (#252) --- release-notes.md | 1 + src/StardewModdingAPI/Program.cs | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 67286308..3b654ee3 100644 --- a/release-notes.md +++ b/release-notes.md @@ -44,6 +44,7 @@ For mod developers: * The SMAPI log now shows the OS caption (like "Windows 10") instead of its internal version when available. * The SMAPI log now always uses `\r\n` line endings to simplify crossplatform viewing. * Fixed `SaveEvents.AfterLoad` being raised during the new-game intro before the player is initialised. +* Fixed SMAPI not recognising `Mod` instances that don't subclass `Mod` directly. * Several obsolete APIs have been removed (see [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod)), and all _notice_-level deprecations have been increased to _info_. * Removed the experimental `IConfigFile`. diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 25605148..276b66ce 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -450,9 +450,15 @@ namespace StardewModdingAPI // validate assembly try { - if (modAssembly.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) == 0) + int modEntries = modAssembly.DefinedTypes.Count(type => typeof(Mod).IsAssignableFrom(type) && !type.IsAbstract); + if(modEntries == 0) { - this.Monitor.Log($"{skippedPrefix} because its DLL has no 'Mod' subclass.", LogLevel.Error); + this.Monitor.Log($"{skippedPrefix} because its DLL has no '{nameof(Mod)}' subclass.", LogLevel.Error); + continue; + } + if (modEntries > 1) + { + this.Monitor.Log($"{skippedPrefix} because its DLL contains multiple '{nameof(Mod)}' subclasses.", LogLevel.Error); continue; } } @@ -466,7 +472,7 @@ namespace StardewModdingAPI try { // get implementation - TypeInfo modEntryType = modAssembly.DefinedTypes.First(x => x.BaseType == typeof(Mod)); + TypeInfo modEntryType = modAssembly.DefinedTypes.First(type => typeof(Mod).IsAssignableFrom(type) && !type.IsAbstract); Mod mod = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); if (mod == null) { -- cgit From df1e748629f7ed5f150daba6fd83f5cf576a97b7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 30 Mar 2017 18:59:01 -0400 Subject: backport SMAPI 1.9 to Stardew Valley 1.11 (#258) --- release-notes.md | 15 +- src/StardewModdingAPI/Constants.cs | 14 +- src/StardewModdingAPI/Framework/SGame.cs | 1042 +++++++------------- src/StardewModdingAPI/Program.cs | 5 +- .../StardewModdingAPI.config.json | 100 +- 5 files changed, 418 insertions(+), 758 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 3b654ee3..55740ef1 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,20 +2,23 @@ ## 1.9 See [log](https://github.com/Pathoschild/SMAPI/compare/1.8...1.9). For players: -* Updated for Stardew Valley 1.2. -* SMAPI now rewrites many mods for compatibility with game updates, but some mods will need an update. * SMAPI now detects incompatible mods and disables them before they cause problems. * SMAPI now allows mods nested into an otherwise empty parent folder (like `Mods\ModName-1.0\ModName\manifest.json`), since that's a common default behaviour when unpacking mods. * The installer now detects if you need to update .NET Framework before installing SMAPI. @@ -35,10 +38,10 @@ For players: For mod developers: * Added a simpler API for console commands (see `helper.ConsoleCommands`). -* Added `SaveEvents.AfterReturnToTitle` event triggered when the player returns to the title screen. * Added `TimeEvents.AfterDayStarted` event triggered when a day starts. This happens no matter how the day started (including new game, loaded save, or player went to bed). -* Added `ContentEvents.AfterLocaleChanged` event triggered when the player changes the content language. -* Added `GetPrivateProperty` to the reflection helper. +* Added `ContentEvents.AfterLocaleChanged` event triggered when the player changes the content language (for the upcoming Stardew Valley 1.2). +* Added `SaveEvents.AfterReturnToTitle` event triggered when the player returns to the title screen (for the upcoming Stardew Valley 1.2). +* Added `helper.Reflection.GetPrivateProperty` method. * SMAPI now writes XNA input enums (`Buttons` and `Keys`) to JSON as strings automatically, so mods no longer need to add a `StringEnumConverter` themselves for those. * The SMAPI log now has a simpler filename. * The SMAPI log now shows the OS caption (like "Windows 10") instead of its internal version when available. diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 1f1b6a65..3762269d 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -33,10 +33,10 @@ namespace StardewModdingAPI ** Public ****/ /// SMAPI's current semantic version. - public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 8, 0); + public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 9, 0); /// The minimum supported version of Stardew Valley. - public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.15"); + public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.1.1"); /// The path to the game folder. public static string ExecutionPath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); @@ -146,9 +146,6 @@ 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"), @@ -172,12 +169,7 @@ 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.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/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 33d823ef..5f265139 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -1,10 +1,8 @@ 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; @@ -16,7 +14,7 @@ using StardewValley.Locations; using StardewValley.Menus; using StardewValley.Tools; using xTile.Dimensions; -using xTile.Layers; +using Rectangle = Microsoft.Xna.Framework.Rectangle; using SFarmer = StardewValley.Farmer; namespace StardewModdingAPI.Framework @@ -136,9 +134,6 @@ namespace StardewModdingAPI.Framework /// The player character at last check. private SFarmer PreviousFarmer; - /// The previous content locale. - private LocalizedContentManager.LanguageCode? PreviousLocale; - /// An index incremented on every tick and reset every 60th tick (0–59). private int CurrentUpdateTick; @@ -154,20 +149,12 @@ namespace StardewModdingAPI.Framework // ReSharper disable ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming /// Used to access private fields and methods. private static readonly IReflectionHelper Reflection = new ReflectionHelper(); - private static List _fpsList => SGame.Reflection.GetPrivateField>(typeof(Game1), nameof(_fpsList)).GetValue(); - private static Stopwatch _fpsStopwatch => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(SGame._fpsStopwatch)).GetValue(); - private static float _fps - { - set { SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_fps)).SetValue(value); } - } - private static Task _newDayTask => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_newDayTask)).GetValue(); private Color bgColor => SGame.Reflection.GetPrivateField(this, nameof(bgColor)).GetValue(); - public RenderTarget2D screenWrapper => SGame.Reflection.GetPrivateProperty(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop + public RenderTarget2D screenWrapper => SGame.Reflection.GetPrivateField(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop public BlendState lightingBlend => SGame.Reflection.GetPrivateField(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 @@ -197,14 +184,6 @@ namespace StardewModdingAPI.Framework GameEvents.InvokeInitialize(this.Monitor); } - /// Constructor a content manager to read XNB files. - /// The service provider to use to locate services. - /// The root directory to search for content. - protected override LocalizedContentManager CreateContentManager(IServiceProvider serviceProvider, string rootDirectory) - { - return new SContentManager(this.Content.ServiceProvider, this.Content.RootDirectory, this.Monitor); - } - /// The method called before XNA or MonoGame loads or reloads graphics resources. protected override void LoadContent() { @@ -216,22 +195,6 @@ namespace StardewModdingAPI.Framework /// A snapshot of the game timing state. 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.InvokeGameLoaded(this.Monitor); @@ -274,8 +237,7 @@ namespace StardewModdingAPI.Framework this.CurrentUpdateTick = 0; // track keyboard state - if (this.KStatePrior != this.KStateNow) - this.KStatePrior = this.KStateNow; + this.KStatePrior = this.KStateNow; // track controller button state for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) @@ -296,649 +258,412 @@ namespace StardewModdingAPI.Framework { try { - if (Game1.debugMode) + if (!this.ZoomLevelIsOne) + this.GraphicsDevice.SetRenderTarget(this.screenWrapper); + + this.GraphicsDevice.Clear(this.bgColor); + if (Game1.options.showMenuBackground && Game1.activeClickableMenu != null && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet()) { - if (SGame._fpsStopwatch.IsRunning) + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); + try { - 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)); + Game1.activeClickableMenu.drawBackground(Game1.spriteBatch); } - SGame._fpsStopwatch.Restart(); + 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) + { + 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; } - else + if (Game1.gameMode == 11) { - if (SGame._fpsStopwatch.IsRunning) - SGame._fpsStopwatch.Reset(); - SGame._fps = 0.0f; - SGame._fpsList.Clear(); + 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._newDayTask != null) + if (Game1.currentMinigame != null) { - this.GraphicsDevice.Clear(this.bgColor); - //base.Draw(gameTime); + 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; } - else + if (Game1.showingEndOfNightStuff) { - if ((double)Game1.options.zoomLevel != 1.0) - this.GraphicsDevice.SetRenderTarget(this.screenWrapper); - if (this.IsSaving) + 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); - IClickableMenu activeClickableMenu = Game1.activeClickableMenu; - if (activeClickableMenu != null) + 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; + } + if (Game1.gameMode == 0) + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); + else + { + if (Game1.drawLighting) + { + 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++) { - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - try - { - GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor); - activeClickableMenu.draw(Game1.spriteBatch); - GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); - } - catch (Exception ex) - { - this.Monitor.Log($"The {activeClickableMenu.GetType().FullName} menu crashed while drawing itself during save. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); - activeClickableMenu.exitThisMenu(); - } - Game1.spriteBatch.End(); + 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); } - //base.Draw(gameTime); - this.renderScreenBuffer(); + Game1.spriteBatch.End(); + this.GraphicsDevice.SetRenderTarget(this.ZoomLevelIsOne ? null : this.screenWrapper); } - else + 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) { - this.GraphicsDevice.Clear(this.bgColor); - if (Game1.activeClickableMenu != null && Game1.options.showMenuBackground && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet()) + using (List.Enumerator enumerator = Game1.currentLocation.characters.GetEnumerator()) { - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - try - { - Game1.activeClickableMenu.drawBackground(Game1.spriteBatch); - GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor); - Game1.activeClickableMenu.draw(Game1.spriteBatch); - GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor); - } - catch (Exception ex) - { - this.Monitor.Log($"The {Game1.activeClickableMenu.GetType().FullName} menu crashed while drawing itself. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); - Game1.activeClickableMenu.exitThisMenu(); - } - Game1.spriteBatch.End(); - if ((double)Game1.options.zoomLevel != 1.0) + while (enumerator.MoveNext()) { - 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(); + 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); } - if (Game1.overlayMenu == null) - return; - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - Game1.overlayMenu.draw(Game1.spriteBatch); - Game1.spriteBatch.End(); - } - else if ((int)Game1.gameMode == 11) - { - Game1.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3685"), new Vector2(16f, 16f), Color.HotPink); - Game1.spriteBatch.DrawString(Game1.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(); + goto IL_B30; } - else if (Game1.currentMinigame != null) + } + foreach (NPC current2 in Game1.CurrentEvent.actors) + { + 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.Enumerator enumerator3 = Game1.currentLocation.characters.GetEnumerator()) { - Game1.currentMinigame.draw(Game1.spriteBatch); - if (Game1.globalFade && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause)) - { - 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) + while (enumerator3.MoveNext()) { - 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(); + 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); } - 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(); + goto IL_F5F; } - else if (Game1.showingEndOfNightStuff) + } + 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) { - 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(); + case 1: + color = Tool.copperColor; + break; + case 2: + color = Tool.steelColor; + break; + case 3: + color = Tool.goldColor; + break; + case 4: + color = Tool.iridiumColor; + break; } - else if ((int)Game1.gameMode == 6) + 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) { - 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) + if (current7.isEmoting) { - 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(); + 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); } - 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 + } + Game1.spriteBatch.End(); + if (Game1.drawLighting) + { + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, new BlendState { - 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(index).position, (int)((double)Game1.currentLightSources.ElementAt(index).radius * (double)Game1.tileSize * 4.0))) - Game1.spriteBatch.Draw(Game1.currentLightSources.ElementAt(index).lightTexture, Game1.GlobalToLocal(Game1.viewport, Game1.currentLightSources.ElementAt(index).position) / (float)(Game1.options.lightingQuality / 2), new Microsoft.Xna.Framework.Rectangle?(Game1.currentLightSources.ElementAt(index).lightTexture.Bounds), Game1.currentLightSources.ElementAt(index).color, 0.0f, new Vector2((float)Game1.currentLightSources.ElementAt(index).lightTexture.Bounds.Center.X, (float)Game1.currentLightSources.ElementAt(index).lightTexture.Bounds.Center.Y), Game1.currentLightSources.ElementAt(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(); - } + 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.Draw(Game1.staminaRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.OrangeRed * 0.45f); + } + 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 (GraphicsEvents.HasPostRenderListeners()) - { - Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); - GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor); - Game1.spriteBatch.End(); - } + 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); - this.renderScreenBuffer(); - } + 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 (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(); } + 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) @@ -1076,19 +801,8 @@ namespace StardewModdingAPI.Framework /// Detect changes since the last update ticket and trigger mod events. 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 && !SaveGame.IsProcessing/*still loading save*/ && this.AfterLoadTimer >= 0) + if (Constants.IsSaveLoaded && this.AfterLoadTimer >= 0) { if (this.AfterLoadTimer == 0) { @@ -1346,4 +1060,4 @@ namespace StardewModdingAPI.Framework return hash; } } -} +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 276b66ce..e11f411e 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -219,7 +219,10 @@ namespace StardewModdingAPI 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}"; - StardewValley.Program.gamePtr = this.GameInstance; + { + Type type = typeof(Game1).Assembly.GetType("StardewValley.Program", true); + type.GetField("gamePtr").SetValue(null, this.GameInstance); + } // configure Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index a254fdd0..0b6f3a37 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -27,15 +27,6 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha * Changing this field is not recommended and may destabilise your game. */ "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", @@ -50,7 +41,14 @@ 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", - "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", + "Notes": "Uses obsolete StardewModdingAPI.Extensions." + }, + { + "Name": "Better Sprinklers", + "ID": "Speeder.BetterSprinklers", + "UpperVersion": "2.3", + "Compatibility": "AssumeBroken", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/41", "Notes": "Uses obsolete StardewModdingAPI.Extensions." }, { @@ -62,36 +60,26 @@ 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 Cheats Menu", + "ID": "CJBCheatsMenu", + "UpperVersion": "1.12", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4", + "Notes": "Not compatible with Stardew Valley 1.1+" }, { - "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 Item Spawner", + "ID": "CJBItemSpawner", + "UpperVersion": "1.5", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93", + "Notes": "Not compatible with Stardew Valley 1.1+" }, { - "Name": "Cooking Skill", - "ID": "CookingSkill", - "UpperVersion": "1.0.3", + "Name": "CJB Show Item Sell Price", + "ID": "CJBShowItemSellPrice", + "UpperVersion": "1.6", "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)'." + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93", + "Notes": "Uses SMAPI's internal SGame class." }, { "Name": "Enemy Health Bars", @@ -109,38 +97,6 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha "UpdateUrl": "http://community.playstarbound.com/resources/4228", "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", @@ -155,7 +111,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 Assembly.GetExecutingAssembly().Location." + "Notes": "Uses obsolete StardewModdingAPI.Extensions." }, { "Name": "NPC Map Locations", @@ -198,14 +154,6 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/683", "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", -- cgit From 7175e9f8ee24a88a41f453fad500a19b9bbebeba Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 30 Mar 2017 19:43:10 -0400 Subject: add upper version check (#258) --- src/StardewModdingAPI/Constants.cs | 5 ++++- src/StardewModdingAPI/Program.cs | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 3762269d..37fa4d31 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -36,7 +36,10 @@ namespace StardewModdingAPI public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 9, 0); /// The minimum supported version of Stardew Valley. - public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.1.1"); + public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.1"); + + /// The maximum supported version of Stardew Valley. + public static ISemanticVersion MaximumGameVersion { get; } = new SemanticVersion("1.1.1"); /// The path to the game folder. public static string ExecutionPath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index e11f411e..90839216 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -131,7 +131,13 @@ namespace StardewModdingAPI // verify version if (Constants.GameVersion.IsOlderThan(Constants.MinimumGameVersion)) { - this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GameDisplayVersion}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI. If you're on the Steam beta channel, note that the beta channel may not receive the latest updates.", LogLevel.Error); + this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GameDisplayVersion}, but the oldest supported version is {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); + this.PressAnyKeyToExit(); + return; + } + if (Constants.MaximumGameVersion != null && Constants.GameVersion.IsNewerThan(Constants.MaximumGameVersion)) + { + this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GameDisplayVersion}, but this version of SMAPI is only compatible up to Stardew Valley {Constants.MaximumGameVersion}. Please check for a newer version of SMAPI.", LogLevel.Error); this.PressAnyKeyToExit(); return; } -- cgit From b4b4689f00c74b50842e3f651be6bbe297b5f02d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 30 Mar 2017 19:48:01 -0400 Subject: remove old error logs when installing a new version to avoid confusion --- release-notes.md | 1 + src/StardewModdingAPI.Installer/InteractiveInstaller.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index 55740ef1..bde3e4b0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -24,6 +24,7 @@ For players: * The installer now detects if you need to update .NET Framework before installing SMAPI. * The installer now detects if you need to run the game at least once (to let it perform first-time setup) before installing SMAPI. * The installer on Linux now finds games installed to `~/.steam/steam/steamapps/common/Stardew Valley` too. +* The installer now removes old SMAPI logs to prevent confusion. * The console now has simpler error messages. * The console now has improved command handling & feedback. * The console no longer shows the game's debug output (unless you use a _SMAPI for developers_ build). diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 33cbc2dc..fffba30f 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -94,7 +94,7 @@ namespace StardewModdingApi.Installer foreach (DirectoryInfo modDir in modsDir.EnumerateDirectories()) yield return Path.Combine(modDir.FullName, ".cache"); // 1.4–1.7 } - yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs", "MODDED_ProgramLog.Log_LATEST.txt"); // *–1.8 + yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs"); // remove old log files } /// Whether the current console supports color formatting. -- cgit From c023118356d9d7877dbdc9e88c4512fcdf33c256 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 30 Mar 2017 20:21:15 -0400 Subject: always show friendly game version --- src/StardewModdingAPI/Constants.cs | 29 +++++++++++++---------------- src/StardewModdingAPI/Program.cs | 10 +++++----- 2 files changed, 18 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 37fa4d31..e28b33d7 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -80,9 +80,6 @@ namespace StardewModdingAPI /// The game's current semantic version. internal static ISemanticVersion GameVersion { get; } = Constants.GetGameVersion(); - /// The game's current version as it should be displayed to players. - internal static ISemanticVersion GameDisplayVersion { get; } = Constants.GetGameDisplayVersion(Constants.GameVersion); - /// The target game platform. internal static Platform TargetPlatform { get; } = #if SMAPI_FOR_WINDOWS @@ -177,6 +174,19 @@ namespace StardewModdingAPI }; } + /// Get game current version as it should be displayed to players. + /// The semantic game version. + internal static ISemanticVersion GetGameDisplayVersion(ISemanticVersion version) + { + switch (version.ToString()) + { + case "1.1.1": + return new SemanticVersion(1, 11, 0); // The 1.1 patch was released as 1.11 + default: + return version; + } + } + /// Get the name of a save directory for the current player. private static string GetSaveFolderName() { @@ -199,18 +209,5 @@ namespace StardewModdingAPI version = "1.1.1"; // The 1.1 patch was released as 1.11, which means it's out of order for semantic version checks return new SemanticVersion(version); } - - /// Get game current version as it should be displayed to players. - /// The semantic game version. - private static ISemanticVersion GetGameDisplayVersion(ISemanticVersion version) - { - switch (version.ToString()) - { - case "1.1.1": - return new SemanticVersion(1, 11, 0); // The 1.1 patch was released as 1.11 - default: - return version; - } - } } } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 90839216..ac98f2e4 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -88,8 +88,8 @@ namespace StardewModdingAPI { // initialise logging Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); // for consistent log formatting - this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {this.GetFriendlyPlatformName()}", LogLevel.Info); - Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion}"; + 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 @@ -131,13 +131,13 @@ namespace StardewModdingAPI // verify version if (Constants.GameVersion.IsOlderThan(Constants.MinimumGameVersion)) { - this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GameDisplayVersion}, but the oldest supported version is {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); + 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); this.PressAnyKeyToExit(); return; } if (Constants.MaximumGameVersion != null && Constants.GameVersion.IsNewerThan(Constants.MaximumGameVersion)) { - this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GameDisplayVersion}, but this version of SMAPI is only compatible up to Stardew Valley {Constants.MaximumGameVersion}. Please check for a newer version of SMAPI.", LogLevel.Error); + this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)}, but this version of SMAPI is only compatible up to Stardew Valley {Constants.GetGameDisplayVersion(Constants.MaximumGameVersion)}. Please check for a newer version of SMAPI.", LogLevel.Error); this.PressAnyKeyToExit(); return; } @@ -530,7 +530,7 @@ namespace StardewModdingAPI this.Monitor.Log($"Loaded {modsLoaded} mods."); foreach (Action warning in deprecationWarnings) warning(); - Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion} with {modsLoaded} mods"; + Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} with {modsLoaded} mods"; } /// Run a loop handling console input. -- cgit From 4675da0600edf6781cd740549ad0a175b606fc1e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 1 Apr 2017 16:08:31 -0400 Subject: add --log-path argument to specify SMAPI log path during testing --- README.md | 5 +++-- release-notes.md | 1 + src/StardewModdingAPI/Constants.cs | 2 +- src/StardewModdingAPI/Program.cs | 29 +++++++++++++++++++++++++---- 4 files changed, 30 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/README.md b/README.md index c90e62cc..74388144 100644 --- a/README.md +++ b/README.md @@ -145,9 +145,10 @@ field | purpose `ModCompatibility` | A list of mod versions SMAPI should consider compatible or broken regardless of whether it detects incompatible code. Each record can be set to `AssumeCompatible` or `AssumeBroken`. Changing this field is not recommended and may destabilise your game. ### Command-line arguments -SMAPI recognises the following command-line arguments. These are intended for internal use and may -change without warning. +SMAPI recognises the following command-line arguments. These are intended for internal use or +testing and may change without warning. argument | purpose -------- | ------- +`--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 bde3e4b0..a5ef4a0b 100644 --- a/release-notes.md +++ b/release-notes.md @@ -43,6 +43,7 @@ For mod developers: * Added `ContentEvents.AfterLocaleChanged` event triggered when the player changes the content language (for the upcoming Stardew Valley 1.2). * Added `SaveEvents.AfterReturnToTitle` event triggered when the player returns to the title screen (for the upcoming Stardew Valley 1.2). * Added `helper.Reflection.GetPrivateProperty` method. +* Added a `--log-path` argument to specify the SMAPI log path during testing. * SMAPI now writes XNA input enums (`Buttons` and `Keys`) to JSON as strings automatically, so mods no longer need to add a `StringEnumConverter` themselves for those. * The SMAPI log now has a simpler filename. * The SMAPI log now shows the OS caption (like "Windows 10") instead of its internal version when available. diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index e28b33d7..4a036cd0 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -69,7 +69,7 @@ namespace StardewModdingAPI internal static string ApiConfigPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.config.json"); /// The file path to the log where the latest output should be saved. - internal static string LogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt"); + internal static string DefaultLogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt"); /// The full path to the folder containing mods. internal static string ModPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods"); diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index ac98f2e4..58850dc3 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -30,7 +30,7 @@ namespace StardewModdingAPI ** Properties *********/ /// The log file to which to write messages. - private readonly LogFileManager LogFile = new LogFileManager(Constants.LogPath); + private readonly LogFileManager LogFile; /// Manages console output interception. private readonly ConsoleInterceptionManager ConsoleManager = new ConsoleInterceptionManager(); @@ -67,17 +67,38 @@ namespace StardewModdingAPI /// The command-line arguments. private static void Main(string[] args) { - new Program(writeToConsole: !args.Contains("--no-terminal")) + // get flags from arguments + bool writeToConsole = !args.Contains("--no-terminal"); + + // get log path from arguments + string logPath = null; + { + int pathIndex = Array.LastIndexOf(args, "--log-path") + 1; + if (pathIndex >= 1 && args.Length >= pathIndex) + { + logPath = args[pathIndex]; + if (!Path.IsPathRooted(logPath)) + logPath = Path.Combine(Constants.LogDir, logPath); + } + } + if (string.IsNullOrWhiteSpace(logPath)) + logPath = Constants.DefaultLogPath; + + // load SMAPI + new Program(writeToConsole, logPath) .LaunchInteractively(); } /// Construct an instance. - internal Program(bool writeToConsole) + /// Whether to output log messages to the console. + /// The full file path to which to write log messages. + internal Program(bool writeToConsole, string logPath) { // load settings this.Settings = JsonConvert.DeserializeObject(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); @@ -460,7 +481,7 @@ namespace StardewModdingAPI try { int modEntries = modAssembly.DefinedTypes.Count(type => typeof(Mod).IsAssignableFrom(type) && !type.IsAbstract); - if(modEntries == 0) + if (modEntries == 0) { this.Monitor.Log($"{skippedPrefix} because its DLL has no '{nameof(Mod)}' subclass.", LogLevel.Error); continue; -- cgit