summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2017-07-03 01:03:13 -0400
committerJesse Plamondon-Willard <github@jplamondonw.com>2017-07-03 01:03:13 -0400
commit771263299cae11d464c25c5291e59507c639e822 (patch)
tree73c60cb5f9eed250ec37596a5ab9477c36194998
parent8c1bf732ff6bbf2056b7416887354020d10a7ca9 (diff)
downloadSMAPI-771263299cae11d464c25c5291e59507c639e822.tar.gz
SMAPI-771263299cae11d464c25c5291e59507c639e822.tar.bz2
SMAPI-771263299cae11d464c25c5291e59507c639e822.zip
add SMAPI 2.0 compile mode
-rw-r--r--README.md2
-rw-r--r--release-notes.md1
-rw-r--r--src/StardewModdingAPI.AssemblyRewriters/Finders/PropertyFinder.cs83
-rw-r--r--src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj1
-rw-r--r--src/StardewModdingAPI/Command.cs4
-rw-r--r--src/StardewModdingAPI/Config.cs4
-rw-r--r--src/StardewModdingAPI/Constants.cs33
-rw-r--r--src/StardewModdingAPI/Events/EventArgsCommand.cs4
-rw-r--r--src/StardewModdingAPI/Events/EventArgsFarmerChanged.cs2
-rw-r--r--src/StardewModdingAPI/Events/EventArgsLoadedGameChanged.cs4
-rw-r--r--src/StardewModdingAPI/Events/EventArgsNewDay.cs4
-rw-r--r--src/StardewModdingAPI/Events/EventArgsStringChanged.cs4
-rw-r--r--src/StardewModdingAPI/Events/GameEvents.cs22
-rw-r--r--src/StardewModdingAPI/Events/PlayerEvents.cs6
-rw-r--r--src/StardewModdingAPI/Events/TimeEvents.cs11
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs22
-rw-r--r--src/StardewModdingAPI/Framework/Models/Manifest.cs2
-rw-r--r--src/StardewModdingAPI/Framework/Monitor.cs2
-rw-r--r--src/StardewModdingAPI/Framework/SContentManager.cs2
-rw-r--r--src/StardewModdingAPI/Framework/SGame.cs24
-rw-r--r--src/StardewModdingAPI/Log.cs4
-rw-r--r--src/StardewModdingAPI/Mod.cs9
-rw-r--r--src/StardewModdingAPI/Program.cs28
23 files changed, 254 insertions, 24 deletions
diff --git a/README.md b/README.md
index 395cd314..ca3128e4 100644
--- a/README.md
+++ b/README.md
@@ -167,4 +167,4 @@ SMAPI uses a small number of conditional compilation constants, which you can se
flag | purpose
---- | -------
`SMAPI_FOR_WINDOWS` | Indicates that SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`.
-
+`SMAPI_2_0` | Sets SMAPI 2.0 mode, which enables features planned for SMAPI 2.0 and removes all deprecated code. This helps test how mods will work when SMAPI 2.0 is released.
diff --git a/release-notes.md b/release-notes.md
index 7a7045fc..94106ba6 100644
--- a/release-notes.md
+++ b/release-notes.md
@@ -36,6 +36,7 @@ For modders:
* Fixed corrupted state exceptions not being logged by SMAPI.
For SMAPI developers:
+* Added SMAPI 2.0 compile mode, for testing how mods will work with SMAPI 2.0.
* Added prototype SMAPI 2.0 feature to override XNB files (not enabled for mods yet).
* Added prototype SMAPI 2.0 support for version strings in `manifest.json` (not recommended for mods yet).
diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/PropertyFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/PropertyFinder.cs
new file mode 100644
index 00000000..441f15f2
--- /dev/null
+++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/PropertyFinder.cs
@@ -0,0 +1,83 @@
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace StardewModdingAPI.AssemblyRewriters.Finders
+{
+ /// <summary>Finds incompatible CIL instructions that reference a given property and throws an <see cref="IncompatibleInstructionException"/>.</summary>
+ public class PropertyFinder : IInstructionRewriter
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The full type name for which to find references.</summary>
+ private readonly string FullTypeName;
+
+ /// <summary>The property name for which to find references.</summary>
+ private readonly string PropertyName;
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
+ public string NounPhrase { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="fullTypeName">The full type name for which to find references.</param>
+ /// <param name="propertyName">The property name for which to find references.</param>
+ /// <param name="nounPhrase">A brief noun phrase indicating what the instruction finder matches (or <c>null</c> to generate one).</param>
+ public PropertyFinder(string fullTypeName, string propertyName, string nounPhrase = null)
+ {
+ this.FullTypeName = fullTypeName;
+ this.PropertyName = propertyName;
+ this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{propertyName} property";
+ }
+
+ /// <summary>Rewrite a method definition for compatibility.</summary>
+ /// <param name="module">The module being rewritten.</param>
+ /// <param name="method">The method definition to rewrite.</param>
+ /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
+ /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
+ /// <returns>Returns whether the instruction was rewritten.</returns>
+ /// <exception cref="IncompatibleInstructionException">The CIL instruction is not compatible, and can't be rewritten.</exception>
+ public virtual bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
+ {
+ return false;
+ }
+
+ /// <summary>Rewrite a CIL instruction for compatibility.</summary>
+ /// <param name="module">The module being rewritten.</param>
+ /// <param name="cil">The CIL rewriter.</param>
+ /// <param name="instruction">The instruction to rewrite.</param>
+ /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
+ /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
+ /// <returns>Returns whether the instruction was rewritten.</returns>
+ /// <exception cref="IncompatibleInstructionException">The CIL instruction is not compatible, and can't be rewritten.</exception>
+ public virtual bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
+ {
+ if (!this.IsMatch(instruction))
+ return false;
+
+ throw new IncompatibleInstructionException(this.NounPhrase);
+ }
+
+
+ /*********
+ ** Protected methods
+ *********/
+ /// <summary>Get whether a CIL instruction matches.</summary>
+ /// <param name="instruction">The IL instruction.</param>
+ protected bool IsMatch(Instruction instruction)
+ {
+ MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
+ return
+ methodRef != null
+ && methodRef.DeclaringType.FullName == this.FullTypeName
+ && (methodRef.Name == "get_" + this.PropertyName || methodRef.Name == "set_" + this.PropertyName);
+ }
+ }
+}
diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj
index e25b201e..7a12a8e9 100644
--- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj
+++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj
@@ -49,6 +49,7 @@
<Link>Properties\GlobalAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Finders\EventFinder.cs" />
+ <Compile Include="Finders\PropertyFinder.cs" />
<Compile Include="Finders\FieldFinder.cs" />
<Compile Include="Finders\MethodFinder.cs" />
<Compile Include="Finders\TypeFinder.cs" />
diff --git a/src/StardewModdingAPI/Command.cs b/src/StardewModdingAPI/Command.cs
index e2d08538..7613b240 100644
--- a/src/StardewModdingAPI/Command.cs
+++ b/src/StardewModdingAPI/Command.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_2_0
+using System;
using System.Collections.Generic;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework;
@@ -155,3 +156,4 @@ namespace StardewModdingAPI
}
}
}
+#endif \ No newline at end of file
diff --git a/src/StardewModdingAPI/Config.cs b/src/StardewModdingAPI/Config.cs
index 9f4bfad2..f6fe37d9 100644
--- a/src/StardewModdingAPI/Config.cs
+++ b/src/StardewModdingAPI/Config.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_2_0
+using System;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
@@ -184,3 +185,4 @@ namespace StardewModdingAPI
}
}
}
+#endif \ No newline at end of file
diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs
index bd489b29..06a8c486 100644
--- a/src/StardewModdingAPI/Constants.cs
+++ b/src/StardewModdingAPI/Constants.cs
@@ -33,7 +33,12 @@ namespace StardewModdingAPI
** Public
****/
/// <summary>SMAPI's current semantic version.</summary>
- public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 14, 1); // alpha-{DateTime.UtcNow:yyyyMMddHHmm}
+ public static ISemanticVersion ApiVersion { get; } =
+#if SMAPI_2_0
+ new SemanticVersion(2, 0, 0, $"alpha-{DateTime.UtcNow:yyyyMMddHHmm}");
+#else
+ new SemanticVersion(1, 15, 0, "prerelease.1"); // alpha-{DateTime.UtcNow:yyyyMMddHHmm}
+#endif
/// <summary>The minimum supported version of Stardew Valley.</summary>
public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.30");
@@ -169,6 +174,32 @@ namespace StardewModdingAPI
new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderHudEventNoCheck"),
new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderGuiEventNoCheck"),
+ // APIs removed in SMAPI 2.0
+#if SMAPI_2_0
+ new TypeFinder("StardewModdingAPI.Command"),
+ new TypeFinder("StardewModdingAPI.Config"),
+ new TypeFinder("StardewModdingAPI.Log"),
+ new TypeFinder("StardewModdingAPI.Events.EventArgsCommand"),
+ new TypeFinder("StardewModdingAPI.Events.EventArgsFarmerChanged"),
+ new TypeFinder("StardewModdingAPI.Events.EventArgsLoadedGameChanged"),
+ new TypeFinder("StardewModdingAPI.Events.EventArgsNewDay"),
+ new TypeFinder("StardewModdingAPI.Events.EventArgsStringChanged"),
+ new PropertyFinder("StardewModdingAPI.Mod", "PathOnDisk"),
+ new PropertyFinder("StardewModdingAPI.Mod", "BaseConfigPath"),
+ new PropertyFinder("StardewModdingAPI.Mod", "PerSaveConfigFolder"),
+ new PropertyFinder("StardewModdingAPI.Mod", "PerSaveConfigPath"),
+ new EventFinder("StardewModdingAPI.Events.GameEvents", "Initialize"),
+ new EventFinder("StardewModdingAPI.Events.GameEvents", "LoadContent"),
+ new EventFinder("StardewModdingAPI.Events.GameEvents", "GameLoaded"),
+ new EventFinder("StardewModdingAPI.Events.GameEvents", "FirstUpdateTick"),
+ new EventFinder("StardewModdingAPI.Events.PlayerEvents", "LoadedGame"),
+ new EventFinder("StardewModdingAPI.Events.PlayerEvents", "FarmerChanged"),
+ new EventFinder("StardewModdingAPI.Events.TimeEvents", "DayOfMonthChanged"),
+ new EventFinder("StardewModdingAPI.Events.TimeEvents", "YearOfGameChanged"),
+ new EventFinder("StardewModdingAPI.Events.TimeEvents", "SeasonOfYearChanged"),
+ new EventFinder("StardewModdingAPI.Events.TimeEvents", "OnNewDay"),
+#endif
+
/****
** Rewriters change CIL as needed to fix incompatible code
****/
diff --git a/src/StardewModdingAPI/Events/EventArgsCommand.cs b/src/StardewModdingAPI/Events/EventArgsCommand.cs
index 88a9e5a3..f0435904 100644
--- a/src/StardewModdingAPI/Events/EventArgsCommand.cs
+++ b/src/StardewModdingAPI/Events/EventArgsCommand.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_2_0
+using System;
namespace StardewModdingAPI.Events
{
@@ -24,3 +25,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif \ No newline at end of file
diff --git a/src/StardewModdingAPI/Events/EventArgsFarmerChanged.cs b/src/StardewModdingAPI/Events/EventArgsFarmerChanged.cs
index 699d90be..c34fc4ab 100644
--- a/src/StardewModdingAPI/Events/EventArgsFarmerChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsFarmerChanged.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_2_0
using System;
using SFarmer = StardewValley.Farmer;
@@ -29,3 +30,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif \ No newline at end of file
diff --git a/src/StardewModdingAPI/Events/EventArgsLoadedGameChanged.cs b/src/StardewModdingAPI/Events/EventArgsLoadedGameChanged.cs
index 51d64016..d6fc4594 100644
--- a/src/StardewModdingAPI/Events/EventArgsLoadedGameChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsLoadedGameChanged.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_2_0
+using System;
namespace StardewModdingAPI.Events
{
@@ -23,3 +24,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif \ No newline at end of file
diff --git a/src/StardewModdingAPI/Events/EventArgsNewDay.cs b/src/StardewModdingAPI/Events/EventArgsNewDay.cs
index aba837e4..5bd2ba66 100644
--- a/src/StardewModdingAPI/Events/EventArgsNewDay.cs
+++ b/src/StardewModdingAPI/Events/EventArgsNewDay.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_2_0
+using System;
namespace StardewModdingAPI.Events
{
@@ -33,3 +34,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif \ No newline at end of file
diff --git a/src/StardewModdingAPI/Events/EventArgsStringChanged.cs b/src/StardewModdingAPI/Events/EventArgsStringChanged.cs
index 85b6fab5..1498cb71 100644
--- a/src/StardewModdingAPI/Events/EventArgsStringChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsStringChanged.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_2_0
+using System;
namespace StardewModdingAPI.Events
{
@@ -27,3 +28,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif \ No newline at end of file
diff --git a/src/StardewModdingAPI/Events/GameEvents.cs b/src/StardewModdingAPI/Events/GameEvents.cs
index 8e3cf662..c97b2c36 100644
--- a/src/StardewModdingAPI/Events/GameEvents.cs
+++ b/src/StardewModdingAPI/Events/GameEvents.cs
@@ -11,6 +11,7 @@ namespace StardewModdingAPI.Events
/*********
** Properties
*********/
+#if !SMAPI_2_0
/// <summary>Manages deprecation warnings.</summary>
private static DeprecationManager DeprecationManager;
@@ -29,6 +30,7 @@ namespace StardewModdingAPI.Events
/// <summary>The backing field for <see cref="FirstUpdateTick"/>.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static event EventHandler _FirstUpdateTick;
+#endif
/*********
@@ -40,6 +42,7 @@ namespace StardewModdingAPI.Events
/// <summary>Raised during launch after configuring Stardew Valley, loading it into memory, and opening the game window. The window is still blank by this point.</summary>
internal static event EventHandler GameLoadedInternal;
+#if !SMAPI_2_0
/// <summary>Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after <see cref="Microsoft.Xna.Framework.Game.Initialize"/>.</summary>
[Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the " + nameof(GameEvents.Initialize) + " event, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")]
public static event EventHandler Initialize
@@ -87,6 +90,7 @@ namespace StardewModdingAPI.Events
}
remove => GameEvents._FirstUpdateTick -= value;
}
+#endif
/// <summary>Raised when the game updates its state (≈60 times per second).</summary>
public static event EventHandler UpdateTick;
@@ -113,42 +117,52 @@ namespace StardewModdingAPI.Events
/*********
** Internal methods
*********/
+#if !SMAPI_2_0
/// <summary>Injects types required for backwards compatibility.</summary>
/// <param name="deprecationManager">Manages deprecation warnings.</param>
internal static void Shim(DeprecationManager deprecationManager)
{
GameEvents.DeprecationManager = deprecationManager;
}
+#endif
- /// <summary>Raise an <see cref="Initialize"/> event.</summary>
- /// <param name="monitor">Encapsulates logging and monitoring.</param>
- internal static void InvokeInitialize(IMonitor monitor)
+ /// <summary>Raise an <see cref="InitializeInternal"/> event.</summary>
+ /// <param name="monitor">Encapsulates logging and monitoring.</param>
+ internal static void InvokeInitialize(IMonitor monitor)
{
monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.InitializeInternal)}", GameEvents.InitializeInternal?.GetInvocationList());
+#if !SMAPI_2_0
monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.Initialize)}", GameEvents._Initialize?.GetInvocationList());
+#endif
}
+#if !SMAPI_2_0
/// <summary>Raise a <see cref="LoadContent"/> event.</summary>
/// <param name="monitor">Encapsulates logging and monitoring.</param>
internal static void InvokeLoadContent(IMonitor monitor)
{
monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.LoadContent)}", GameEvents._LoadContent?.GetInvocationList());
}
+#endif
- /// <summary>Raise a <see cref="GameLoaded"/> event.</summary>
+ /// <summary>Raise a <see cref="GameLoadedInternal"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeGameLoaded(IMonitor monitor)
{
monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoadedInternal)}", GameEvents.GameLoadedInternal?.GetInvocationList());
+#if !SMAPI_2_0
monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoaded)}", GameEvents._GameLoaded?.GetInvocationList());
+#endif
}
+#if !SMAPI_2_0
/// <summary>Raise a <see cref="FirstUpdateTick"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeFirstUpdateTick(IMonitor monitor)
{
monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.FirstUpdateTick)}", GameEvents._FirstUpdateTick?.GetInvocationList());
}
+#endif
/// <summary>Raise an <see cref="UpdateTick"/> event.</summary>
/// <param name="monitor">Encapsulates logging and monitoring.</param>
diff --git a/src/StardewModdingAPI/Events/PlayerEvents.cs b/src/StardewModdingAPI/Events/PlayerEvents.cs
index 37649fee..efada876 100644
--- a/src/StardewModdingAPI/Events/PlayerEvents.cs
+++ b/src/StardewModdingAPI/Events/PlayerEvents.cs
@@ -15,6 +15,7 @@ namespace StardewModdingAPI.Events
/*********
** Properties
*********/
+#if !SMAPI_2_0
/// <summary>Manages deprecation warnings.</summary>
private static DeprecationManager DeprecationManager;
@@ -25,11 +26,13 @@ namespace StardewModdingAPI.Events
/// <summary>The backing field for <see cref="FarmerChanged"/>.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static event EventHandler<EventArgsFarmerChanged> _FarmerChanged;
+#endif
/*********
** Events
*********/
+#if !SMAPI_2_0
/// <summary>Raised after the player loads a saved game.</summary>
[Obsolete("Use " + nameof(SaveEvents) + "." + nameof(SaveEvents.AfterLoad) + " instead")]
public static event EventHandler<EventArgsLoadedGameChanged> LoadedGame
@@ -53,6 +56,7 @@ namespace StardewModdingAPI.Events
}
remove => PlayerEvents._FarmerChanged -= value;
}
+#endif
/// <summary>Raised after the player's inventory changes in any way (added or removed item, sorted, etc).</summary>
public static event EventHandler<EventArgsInventoryChanged> InventoryChanged;
@@ -64,6 +68,7 @@ namespace StardewModdingAPI.Events
/*********
** Internal methods
*********/
+#if !SMAPI_2_0
/// <summary>Injects types required for backwards compatibility.</summary>
/// <param name="deprecationManager">Manages deprecation warnings.</param>
internal static void Shim(DeprecationManager deprecationManager)
@@ -87,6 +92,7 @@ namespace StardewModdingAPI.Events
{
monitor.SafelyRaiseGenericEvent($"{nameof(PlayerEvents)}.{nameof(PlayerEvents.FarmerChanged)}", PlayerEvents._FarmerChanged?.GetInvocationList(), null, new EventArgsFarmerChanged(priorFarmer, newFarmer));
}
+#endif
/// <summary>Raise an <see cref="InventoryChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
diff --git a/src/StardewModdingAPI/Events/TimeEvents.cs b/src/StardewModdingAPI/Events/TimeEvents.cs
index 5dadf567..520f8b24 100644
--- a/src/StardewModdingAPI/Events/TimeEvents.cs
+++ b/src/StardewModdingAPI/Events/TimeEvents.cs
@@ -11,6 +11,7 @@ namespace StardewModdingAPI.Events
/*********
** Properties
*********/
+#if !SMAPI_2_0
/// <summary>Manages deprecation warnings.</summary>
private static DeprecationManager DeprecationManager;
@@ -29,6 +30,8 @@ namespace StardewModdingAPI.Events
/// <summary>The backing field for <see cref="YearOfGameChanged"/>.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static event EventHandler<EventArgsIntChanged> _YearOfGameChanged;
+#endif
+
/*********
** Events
@@ -39,6 +42,7 @@ namespace StardewModdingAPI.Events
/// <summary>Raised after the in-game clock changes.</summary>
public static event EventHandler<EventArgsIntChanged> TimeOfDayChanged;
+#if !SMAPI_2_0
/// <summary>Raised after the day-of-month value changes, including when loading a save. This may happen before save; in most cases you should use <see cref="AfterDayStarted"/> instead.</summary>
[Obsolete("Use " + nameof(TimeEvents) + "." + nameof(TimeEvents.AfterDayStarted) + " or " + nameof(SaveEvents) + " instead")]
public static event EventHandler<EventArgsIntChanged> DayOfMonthChanged
@@ -86,17 +90,20 @@ namespace StardewModdingAPI.Events
}
remove => TimeEvents._OnNewDay -= value;
}
+#endif
/*********
** Internal methods
*********/
+#if !SMAPI_2_0
/// <summary>Injects types required for backwards compatibility.</summary>
/// <param name="deprecationManager">Manages deprecation warnings.</param>
internal static void Shim(DeprecationManager deprecationManager)
{
TimeEvents.DeprecationManager = deprecationManager;
}
+#endif
/// <summary>Raise an <see cref="AfterDayStarted"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
@@ -105,7 +112,7 @@ namespace StardewModdingAPI.Events
monitor.SafelyRaisePlainEvent($"{nameof(TimeEvents)}.{nameof(TimeEvents.AfterDayStarted)}", TimeEvents.AfterDayStarted?.GetInvocationList(), null, EventArgs.Empty);
}
- /// <summary>Raise a <see cref="InvokeDayOfMonthChanged"/> event.</summary>
+ /// <summary>Raise a <see cref="TimeOfDayChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="priorTime">The previous time in military time format (e.g. 6:00pm is 1800).</param>
/// <param name="newTime">The current time in military time format (e.g. 6:10pm is 1810).</param>
@@ -114,6 +121,7 @@ namespace StardewModdingAPI.Events
monitor.SafelyRaiseGenericEvent($"{nameof(TimeEvents)}.{nameof(TimeEvents.TimeOfDayChanged)}", TimeEvents.TimeOfDayChanged?.GetInvocationList(), null, new EventArgsIntChanged(priorTime, newTime));
}
+#if !SMAPI_2_0
/// <summary>Raise a <see cref="DayOfMonthChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="priorDay">The previous day value.</param>
@@ -150,5 +158,6 @@ namespace StardewModdingAPI.Events
{
monitor.SafelyRaiseGenericEvent($"{nameof(TimeEvents)}.{nameof(TimeEvents.OnNewDay)}", TimeEvents._OnNewDay?.GetInvocationList(), null, new EventArgsNewDay(priorDay, newDay, isTransitioning));
}
+#endif
}
}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs
index cefc860b..ceb51bbb 100644
--- a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs
+++ b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs
@@ -109,7 +109,7 @@ namespace StardewModdingAPI.Framework.ModLoading
bool hasOfficialUrl = !string.IsNullOrWhiteSpace(mod.Compatibility.UpdateUrl);
bool hasUnofficialUrl = !string.IsNullOrWhiteSpace(mod.Compatibility.UnofficialUpdateUrl);
- string reasonPhrase = compatibility.ReasonPhrase ?? "it's not compatible with the latest version of the game";
+ string reasonPhrase = compatibility.ReasonPhrase ?? "it's not compatible with the latest version of the game or SMAPI";
string error = $"{reasonPhrase}. Please check for a version newer than {compatibility.UpperVersionLabel ?? compatibility.UpperVersion} here:";
if (hasOfficialUrl)
error += !hasUnofficialUrl ? $" {compatibility.UpdateUrl}" : $"{Environment.NewLine}- official mod: {compatibility.UpdateUrl}";
@@ -131,7 +131,27 @@ namespace StardewModdingAPI.Framework.ModLoading
// validate DLL path
string assemblyPath = Path.Combine(mod.DirectoryPath, mod.Manifest.EntryDll);
if (!File.Exists(assemblyPath))
+ {
mod.SetStatus(ModMetadataStatus.Failed, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist.");
+ continue;
+ }
+
+ // validate required fields
+#if SMAPI_2_0
+ {
+ List<string> missingFields = new List<string>(3);
+
+ if (string.IsNullOrWhiteSpace(mod.Manifest.Name))
+ missingFields.Add(nameof(IManifest.Name));
+ if (mod.Manifest.Version == null || mod.Manifest.Version.ToString() == "0.0")
+ missingFields.Add(nameof(IManifest.Version));
+ if (string.IsNullOrWhiteSpace(mod.Manifest.UniqueID))
+ missingFields.Add(nameof(IManifest.UniqueID));
+
+ if (missingFields.Any())
+ mod.SetStatus(ModMetadataStatus.Failed, $"its manifest is missing required fields ({string.Join(", ", missingFields)}).");
+ }
+#endif
}
}
diff --git a/src/StardewModdingAPI/Framework/Models/Manifest.cs b/src/StardewModdingAPI/Framework/Models/Manifest.cs
index 8e5d13f8..08b88025 100644
--- a/src/StardewModdingAPI/Framework/Models/Manifest.cs
+++ b/src/StardewModdingAPI/Framework/Models/Manifest.cs
@@ -38,9 +38,11 @@ namespace StardewModdingAPI.Framework.Models
/// <summary>The unique mod ID.</summary>
public string UniqueID { get; set; }
+#if !SMAPI_2_0
/// <summary>Whether the mod uses per-save config files.</summary>
[Obsolete("Use " + nameof(Mod) + "." + nameof(Mod.Helper) + "." + nameof(IModHelper.ReadConfig) + " instead")]
public bool PerSaveConfigs { get; set; }
+#endif
/// <summary>Any manifest fields which didn't match a valid field.</summary>
[JsonExtensionData]
diff --git a/src/StardewModdingAPI/Framework/Monitor.cs b/src/StardewModdingAPI/Framework/Monitor.cs
index 7d40b72b..64cc0bdc 100644
--- a/src/StardewModdingAPI/Framework/Monitor.cs
+++ b/src/StardewModdingAPI/Framework/Monitor.cs
@@ -104,6 +104,7 @@ namespace StardewModdingAPI.Framework
this.LogFile.WriteLine("");
}
+#if !SMAPI_2_0
/// <summary>Log a message for the player or developer, using the specified console color.</summary>
/// <param name="source">The name of the mod logging the message.</param>
/// <param name="message">The message to log.</param>
@@ -114,6 +115,7 @@ namespace StardewModdingAPI.Framework
{
this.LogImpl(source, message, level, color);
}
+#endif
/*********
diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs
index 0a8a0873..5707aab1 100644
--- a/src/StardewModdingAPI/Framework/SContentManager.cs
+++ b/src/StardewModdingAPI/Framework/SContentManager.cs
@@ -206,7 +206,7 @@ namespace StardewModdingAPI.Framework
// from Farmer constructor
if (Game1.player != null)
- Game1.player.FarmerRenderer = new FarmerRenderer(this.Load<Texture2D>($"Characters\\Farmer\\farmer_" + (Game1.player.isMale ? "" : "girl_") + "base"));
+ Game1.player.FarmerRenderer = new FarmerRenderer(this.Load<Texture2D>("Characters\\Farmer\\farmer_" + (Game1.player.isMale ? "" : "girl_") + "base"));
}
diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs
index 39713d4a..678dcf3a 100644
--- a/src/StardewModdingAPI/Framework/SGame.cs
+++ b/src/StardewModdingAPI/Framework/SGame.cs
@@ -34,7 +34,7 @@ namespace StardewModdingAPI.Framework
private readonly IMonitor Monitor;
/// <summary>SMAPI's content manager.</summary>
- private SContentManager SContentManager;
+ private readonly SContentManager SContentManager;
/// <summary>The maximum number of consecutive attempts SMAPI should make to recover from a draw error.</summary>
private readonly Countdown DrawCrashTimer = new Countdown(60); // 60 ticks = roughly one second
@@ -113,6 +113,7 @@ namespace StardewModdingAPI.Framework
/// <summary>The time of day (in 24-hour military format) at last check.</summary>
private int PreviousTime;
+#if !SMAPI_2_0
/// <summary>The day of month (1–28) at last check.</summary>
private int PreviousDay;
@@ -127,6 +128,7 @@ namespace StardewModdingAPI.Framework
/// <summary>The player character at last check.</summary>
private SFarmer PreviousFarmer;
+#endif
/// <summary>The previous content locale.</summary>
private LocalizedContentManager.LanguageCode? PreviousLocale;
@@ -285,7 +287,9 @@ namespace StardewModdingAPI.Framework
if (this.FirstUpdate)
{
GameEvents.InvokeInitialize(this.Monitor);
+#if !SMAPI_2_0
GameEvents.InvokeLoadContent(this.Monitor);
+#endif
GameEvents.InvokeGameLoaded(this.Monitor);
}
@@ -315,7 +319,9 @@ namespace StardewModdingAPI.Framework
Context.IsWorldReady = true;
SaveEvents.InvokeAfterLoad(this.Monitor);
+#if !SMAPI_2_0
PlayerEvents.InvokeLoadedGame(this.Monitor, new EventArgsLoadedGameChanged(Game1.hasLoadedGame));
+#endif
TimeEvents.InvokeAfterDayStarted(this.Monitor);
}
this.AfterLoadTimer--;
@@ -460,9 +466,11 @@ namespace StardewModdingAPI.Framework
if (this.GetHash(Game1.locations) != this.PreviousGameLocations)
LocationEvents.InvokeLocationsChanged(this.Monitor, Game1.locations);
+#if !SMAPI_2_0
// raise player changed
if (Game1.player != this.PreviousFarmer)
PlayerEvents.InvokeFarmerChanged(this.Monitor, this.PreviousFarmer, Game1.player);
+#endif
// raise events that shouldn't be triggered on initial load
if (Game1.uniqueIDForThisGame == this.PreviousSaveID)
@@ -493,12 +501,14 @@ namespace StardewModdingAPI.Framework
// raise time changed
if (Game1.timeOfDay != this.PreviousTime)
TimeEvents.InvokeTimeOfDayChanged(this.Monitor, this.PreviousTime, Game1.timeOfDay);
+#if !SMAPI_2_0
if (Game1.dayOfMonth != this.PreviousDay)
TimeEvents.InvokeDayOfMonthChanged(this.Monitor, this.PreviousDay, Game1.dayOfMonth);
if (Game1.currentSeason != this.PreviousSeason)
TimeEvents.InvokeSeasonOfYearChanged(this.Monitor, this.PreviousSeason, Game1.currentSeason);
if (Game1.year != this.PreviousYear)
TimeEvents.InvokeYearOfGameChanged(this.Monitor, this.PreviousYear, Game1.year);
+#endif
// raise mine level changed
if (Game1.mine != null && Game1.mine.mineLevel != this.PreviousMineLevel)
@@ -508,7 +518,6 @@ namespace StardewModdingAPI.Framework
// update state
this.PreviousGameLocations = this.GetHash(Game1.locations);
this.PreviousGameLocation = Game1.currentLocation;
- this.PreviousFarmer = Game1.player;
this.PreviousCombatLevel = Game1.player.combatLevel;
this.PreviousFarmingLevel = Game1.player.farmingLevel;
this.PreviousFishingLevel = Game1.player.fishingLevel;
@@ -518,21 +527,26 @@ namespace StardewModdingAPI.Framework
this.PreviousItems = Game1.player.items.Where(n => n != null).ToDictionary(n => n, n => n.Stack);
this.PreviousLocationObjects = this.GetHash(Game1.currentLocation.objects);
this.PreviousTime = Game1.timeOfDay;
+ this.PreviousMineLevel = Game1.mine?.mineLevel ?? 0;
+ this.PreviousSaveID = Game1.uniqueIDForThisGame;
+#if !SMAPI_2_0
+ this.PreviousFarmer = Game1.player;
this.PreviousDay = Game1.dayOfMonth;
this.PreviousSeason = Game1.currentSeason;
this.PreviousYear = Game1.year;
- this.PreviousMineLevel = Game1.mine?.mineLevel ?? 0;
- this.PreviousSaveID = Game1.uniqueIDForThisGame;
+#endif
}
/*********
** Game day transition event (obsolete)
*********/
+#if !SMAPI_2_0
if (Game1.newDay != this.PreviousIsNewDay)
{
TimeEvents.InvokeOnNewDay(this.Monitor, this.PreviousDay, Game1.dayOfMonth, Game1.newDay);
this.PreviousIsNewDay = Game1.newDay;
}
+#endif
/*********
** Game update
@@ -552,7 +566,9 @@ namespace StardewModdingAPI.Framework
GameEvents.InvokeUpdateTick(this.Monitor);
if (this.FirstUpdate)
{
+#if !SMAPI_2_0
GameEvents.InvokeFirstUpdateTick(this.Monitor);
+#endif
this.FirstUpdate = false;
}
if (this.CurrentUpdateTick % 2 == 0)
diff --git a/src/StardewModdingAPI/Log.cs b/src/StardewModdingAPI/Log.cs
index d58cebfe..562fa1f8 100644
--- a/src/StardewModdingAPI/Log.cs
+++ b/src/StardewModdingAPI/Log.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_2_0
using System;
using System.Threading;
using StardewModdingAPI.Framework;
@@ -315,4 +316,5 @@ namespace StardewModdingAPI
return Log.ModRegistry.GetModFromStack() ?? "<unknown mod>";
}
}
-} \ No newline at end of file
+}
+#endif \ No newline at end of file
diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs
index 171088cf..302f16ec 100644
--- a/src/StardewModdingAPI/Mod.cs
+++ b/src/StardewModdingAPI/Mod.cs
@@ -11,11 +11,14 @@ namespace StardewModdingAPI
/*********
** Properties
*********/
+#if !SMAPI_2_0
/// <summary>Manages deprecation warnings.</summary>
private static DeprecationManager DeprecationManager;
+
/// <summary>The backing field for <see cref="Mod.PathOnDisk"/>.</summary>
private string _pathOnDisk;
+#endif
/*********
@@ -30,6 +33,7 @@ namespace StardewModdingAPI
/// <summary>The mod's manifest.</summary>
public IManifest ModManifest { get; internal set; }
+#if !SMAPI_2_0
/// <summary>The full path to the mod's directory on the disk.</summary>
[Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.DirectoryPath) + " instead")]
public string PathOnDisk
@@ -69,11 +73,13 @@ namespace StardewModdingAPI
return Context.IsSaveLoaded ? Path.Combine(this.PerSaveConfigFolder, $"{Constants.SaveFolderName}.json") : "";
}
}
+#endif
/*********
** Public methods
*********/
+#if !SMAPI_2_0
/// <summary>Injects types required for backwards compatibility.</summary>
/// <param name="deprecationManager">Manages deprecation warnings.</param>
internal static void Shim(DeprecationManager deprecationManager)
@@ -84,6 +90,7 @@ namespace StardewModdingAPI
/// <summary>The mod entry point, called after the mod is first loaded.</summary>
[Obsolete("This overload is obsolete since SMAPI 1.0.")]
public virtual void Entry(params object[] objects) { }
+#endif
/// <summary>The mod entry point, called after the mod is first loaded.</summary>
/// <param name="helper">Provides simplified APIs for writing mods.</param>
@@ -101,6 +108,7 @@ namespace StardewModdingAPI
/*********
** Private methods
*********/
+#if !SMAPI_2_0
/// <summary>Get the full path to the per-save configuration file for the current save (if <see cref="Manifest.PerSaveConfigs"/> is <c>true</c>).</summary>
[Obsolete]
private string GetPerSaveConfigFolder()
@@ -115,6 +123,7 @@ namespace StardewModdingAPI
}
return Path.Combine(this.PathOnDisk, "psconfigs");
}
+#endif
/// <summary>Release or reset unmanaged resources when the game exits. There's no guarantee this will be called on every exit.</summary>
/// <param name="disposing">Whether the instance is being disposed explicitly rather than finalised. If this is false, the instance shouldn't dispose other objects since they may already be finalised.</param>
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs
index e960a684..d722b43e 100644
--- a/src/StardewModdingAPI/Program.cs
+++ b/src/StardewModdingAPI/Program.cs
@@ -226,6 +226,7 @@ namespace StardewModdingAPI
}
}
+#if !SMAPI_2_0
/// <summary>Get a monitor for legacy code which doesn't have one passed in.</summary>
[Obsolete("This method should only be used when needed for backwards compatibility.")]
internal IMonitor GetLegacyMonitorForMod()
@@ -233,6 +234,7 @@ namespace StardewModdingAPI
string modName = this.ModRegistry.GetModFromStack() ?? "unknown";
return this.GetSecondaryMonitor(modName);
}
+#endif
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
@@ -323,6 +325,7 @@ namespace StardewModdingAPI
this.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry);
this.CommandManager = new CommandManager();
+#if !SMAPI_2_0
// inject compatibility shims
#pragma warning disable 618
Command.Shim(this.CommandManager, this.DeprecationManager, this.ModRegistry);
@@ -333,6 +336,7 @@ namespace StardewModdingAPI
PlayerEvents.Shim(this.DeprecationManager);
TimeEvents.Shim(this.DeprecationManager);
#pragma warning restore 618
+#endif
// redirect direct console output
{
@@ -369,6 +373,7 @@ namespace StardewModdingAPI
resolver.ValidateManifests(mods, Constants.ApiVersion);
// check for deprecated metadata
+#if !SMAPI_2_0
IList<Action> deprecationWarnings = new List<Action>();
foreach (IModMetadata mod in mods.Where(m => m.Status != ModMetadataStatus.Failed))
{
@@ -403,15 +408,20 @@ namespace StardewModdingAPI
mod.SetStatus(ModMetadataStatus.Failed, $"it requires per-save configuration files ('psconfigs') which couldn't be created: {ex.GetLogSummary()}");
}
}
- }
+ }
+#endif
// process dependencies
mods = resolver.ProcessDependencies(mods).ToArray();
// load mods
+#if SMAPI_2_0
+ this.LoadMods(mods, new JsonHelper(), this.ContentManager);
+#else
this.LoadMods(mods, new JsonHelper(), this.ContentManager, deprecationWarnings);
foreach (Action warning in deprecationWarnings)
warning();
+#endif
}
if (this.Monitor.IsExiting)
{
@@ -576,8 +586,12 @@ namespace StardewModdingAPI
/// <param name="mods">The mods to load.</param>
/// <param name="jsonHelper">The JSON helper with which to read mods' JSON files.</param>
/// <param name="contentManager">The content manager to use for mod content.</param>
- /// <param name="deprecationWarnings">A list to populate with any deprecation warnings.</param>
+#if !SMAPI_2_0
+/// <param name="deprecationWarnings">A list to populate with any deprecation warnings.</param>
private void LoadMods(IModMetadata[] mods, JsonHelper jsonHelper, SContentManager contentManager, IList<Action> deprecationWarnings)
+#else
+ private void LoadMods(IModMetadata[] mods, JsonHelper jsonHelper, SContentManager contentManager)
+#endif
{
this.Monitor.Log("Loading mods...", LogLevel.Trace);
@@ -615,7 +629,7 @@ namespace StardewModdingAPI
}
catch (IncompatibleInstructionException ex)
{
- TrackSkip(metadata, $"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}).");
+ TrackSkip(metadata, $"it's not compatible with the latest version of the game or SMAPI (detected {ex.NounPhrase}). Please check for a newer version of the mod.");
continue;
}
catch (Exception ex)
@@ -657,6 +671,7 @@ namespace StardewModdingAPI
continue;
}
+#if !SMAPI_2_0
// prevent mods from using SMAPI 2.0 content interception before release
// ReSharper disable SuspiciousTypeConversion.Global
if (mod is IAssetEditor || mod is IAssetLoader)
@@ -664,12 +679,15 @@ namespace StardewModdingAPI
TrackSkip(metadata, $"its entry class implements {nameof(IAssetEditor)} or {nameof(IAssetLoader)}. These are part of a prototype API that isn't available for mods to use yet.");
}
// ReSharper restore SuspiciousTypeConversion.Global
+#endif
// inject data
mod.ModManifest = manifest;
mod.Helper = new ModHelper(metadata.DisplayName, metadata.DirectoryPath, jsonHelper, this.ModRegistry, this.CommandManager, contentManager, this.Reflection);
mod.Monitor = this.GetSecondaryMonitor(metadata.DisplayName);
+#if !SMAPI_2_0
mod.PathOnDisk = metadata.DirectoryPath;
+#endif
// track mod
metadata.SetMod(mod);
@@ -729,12 +747,14 @@ namespace StardewModdingAPI
try
{
IMod mod = metadata.Mod;
- (mod as Mod)?.Entry(); // deprecated since 1.0
mod.Entry(mod.Helper);
+#if !SMAPI_2_0
+ (mod as Mod)?.Entry(); // deprecated since 1.0
// raise deprecation warning for old Entry() methods
if (this.DeprecationManager.IsVirtualMethodImplemented(mod.GetType(), typeof(Mod), nameof(Mod.Entry), new[] { typeof(object[]) }))
deprecationWarnings.Add(() => this.DeprecationManager.Warn(metadata.DisplayName, $"{nameof(Mod)}.{nameof(Mod.Entry)}(object[]) instead of {nameof(Mod)}.{nameof(Mod.Entry)}({nameof(IModHelper)})", "1.0", DeprecationLevel.Info));
+#endif
}
catch (Exception ex)
{