summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI
diff options
context:
space:
mode:
Diffstat (limited to 'src/StardewModdingAPI')
-rw-r--r--src/StardewModdingAPI/Config.cs11
-rw-r--r--src/StardewModdingAPI/Framework/DeprecationManager.cs124
-rw-r--r--src/StardewModdingAPI/Manifest.cs10
-rw-r--r--src/StardewModdingAPI/Mod.cs45
-rw-r--r--src/StardewModdingAPI/Program.cs17
-rw-r--r--src/StardewModdingAPI/StardewModdingAPI.csproj1
-rw-r--r--src/StardewModdingAPI/Version.cs13
7 files changed, 212 insertions, 9 deletions
diff --git a/src/StardewModdingAPI/Config.cs b/src/StardewModdingAPI/Config.cs
index d811d69d..91503a83 100644
--- a/src/StardewModdingAPI/Config.cs
+++ b/src/StardewModdingAPI/Config.cs
@@ -99,6 +99,17 @@ namespace StardewModdingAPI
return this as T;
}
}
+
+
+ /*********
+ ** Protected methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ protected Config()
+ {
+ Program.DeprecationManager.Warn("the Config class", "1.0");
+ Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.BaseConfigPath)}", "1.0"); // typically used to construct config, avoid redundant warnings
+ }
}
/// <summary>Provides extension methods for <see cref="Config"/> classes.</summary>
diff --git a/src/StardewModdingAPI/Framework/DeprecationManager.cs b/src/StardewModdingAPI/Framework/DeprecationManager.cs
new file mode 100644
index 00000000..2d4ff614
--- /dev/null
+++ b/src/StardewModdingAPI/Framework/DeprecationManager.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+
+namespace StardewModdingAPI.Framework
+{
+ /// <summary>Manages deprecation warnings.</summary>
+ internal class DeprecationManager
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The friendly mod names treated as deprecation warning sources (assembly full name => mod name).</summary>
+ private readonly IDictionary<string, string> ModNamesByAssembly = new Dictionary<string, string>();
+
+ /// <summary>The deprecations which have already been logged (as 'mod name::noun phrase::version').</summary>
+ private readonly HashSet<string> LoggedDeprecations = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Register a mod as a possible source of deprecation warnings.</summary>
+ /// <param name="assembly">The mod assembly.</param>
+ /// <param name="name">The mod's friendly name.</param>
+ public void AddMod(Assembly assembly, string name)
+ {
+ this.ModNamesByAssembly[assembly.FullName] = name;
+ }
+
+ /// <summary>Log a deprecation warning.</summary>
+ /// <param name="nounPhrase">A noun phrase describing what is deprecated.</param>
+ /// <param name="version">The SMAPI version which deprecated it.</param>
+ public void Warn(string nounPhrase, string version)
+ {
+ this.Warn(this.GetSourceNameFromStack(), nounPhrase, version);
+ }
+
+ /// <summary>Log a deprecation warning.</summary>
+ /// <param name="source">The friendly mod name which used the deprecated code.</param>
+ /// <param name="nounPhrase">A noun phrase describing what is deprecated.</param>
+ /// <param name="version">The SMAPI version which deprecated it.</param>
+ public void Warn(string source, string nounPhrase, string version)
+ {
+ if (source != null && !this.MarkWarned(source, nounPhrase, version))
+ return;
+
+ Log.Debug(source != null
+ ? $"NOTE: {source} used {nounPhrase}, which is deprecated since SMAPI {version}. It will work fine for now, but may be removed in a future version of SMAPI."
+ : $"NOTE: an unknown mod used {nounPhrase}, which is deprecated since SMAPI {version}. It will work fine for now, but may be removed in a future version of SMAPI.\n{Environment.StackTrace}"
+ );
+ }
+
+ /// <summary>Mark a deprecation warning as already logged.</summary>
+ /// <param name="nounPhrase">A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method").</param>
+ /// <param name="version">The SMAPI version which deprecated it.</param>
+ /// <returns>Returns whether the deprecation was successfully marked as warned. Returns <c>false</c> if it was already marked.</returns>
+ public bool MarkWarned(string nounPhrase, string version)
+ {
+ return this.MarkWarned(this.GetSourceNameFromStack(), nounPhrase, version);
+ }
+
+ /// <summary>Mark a deprecation warning as already logged.</summary>
+ /// <param name="source">The friendly name of the assembly which used the deprecated code.</param>
+ /// <param name="nounPhrase">A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method").</param>
+ /// <param name="version">The SMAPI version which deprecated it.</param>
+ /// <returns>Returns whether the deprecation was successfully marked as warned. Returns <c>false</c> if it was already marked.</returns>
+ public bool MarkWarned(string source, string nounPhrase, string version)
+ {
+ if (string.IsNullOrWhiteSpace(source))
+ throw new InvalidOperationException("The deprecation source cannot be empty.");
+
+ string key = $"{source}::{nounPhrase}::{version}";
+ if (this.LoggedDeprecations.Contains(key))
+ return false;
+ this.LoggedDeprecations.Add(key);
+ return true;
+ }
+
+ /// <summary>Get whether a type implements the given virtual method.</summary>
+ /// <param name="subtype">The type to check.</param>
+ /// <param name="baseType">The base type which declares the virtual method.</param>
+ /// <param name="name">The method name.</param>
+ public bool IsVirtualMethodImplemented(Type subtype, Type baseType, string name)
+ {
+ MethodInfo method = subtype.GetMethod(nameof(Mod.Entry), new[] { typeof(object[]) });
+ return method.DeclaringType != baseType;
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get the friendly name for the closest assembly registered as a source of deprecation warnings.</summary>
+ /// <returns>Returns the source name, or <c>null</c> if no registered assemblies were found.</returns>
+ private string GetSourceNameFromStack()
+ {
+ // get stack frames
+ StackTrace stack = new StackTrace();
+ StackFrame[] frames = stack.GetFrames();
+ if (frames == null)
+ return null;
+
+ // search stack for a source assembly
+ foreach (StackFrame frame in frames)
+ {
+ // get assembly name
+ MethodBase method = frame.GetMethod();
+ Type type = method.ReflectedType;
+ if (type == null)
+ continue;
+ string assemblyName = type.Assembly.FullName;
+
+ // get name if it's a registered source
+ if (this.ModNamesByAssembly.ContainsKey(assemblyName))
+ return this.ModNamesByAssembly[assemblyName];
+ }
+
+ // no known assembly found
+ return null;
+ }
+ }
+}
diff --git a/src/StardewModdingAPI/Manifest.cs b/src/StardewModdingAPI/Manifest.cs
index 89ce7904..6819cbd1 100644
--- a/src/StardewModdingAPI/Manifest.cs
+++ b/src/StardewModdingAPI/Manifest.cs
@@ -9,6 +9,10 @@ namespace StardewModdingAPI
/*********
** Accessors
*********/
+ /// <summary>Whether the manifest defined the deprecated <see cref="Authour"/> field.</summary>
+ [JsonIgnore]
+ internal bool UsedAuthourField { get; private set; }
+
/// <summary>The mod name.</summary>
public virtual string Name { get; set; } = "";
@@ -20,7 +24,11 @@ namespace StardewModdingAPI
public virtual string Authour
{
get { return this.Author; }
- set { this.Author = value; }
+ set
+ {
+ this.UsedAuthourField = true;
+ this.Author = value;
+ }
}
/// <summary>The mod version.</summary>
diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs
index c1cc99d4..fa70d291 100644
--- a/src/StardewModdingAPI/Mod.cs
+++ b/src/StardewModdingAPI/Mod.cs
@@ -7,6 +7,12 @@ namespace StardewModdingAPI
public class Mod
{
/*********
+ ** Properties
+ *********/
+ /// <summary>The backing field for <see cref="Mod.PathOnDisk"/>.</summary>
+ private string _pathOnDisk;
+
+ /*********
** Accessors
*********/
/// <summary>Provides methods for interacting with the mod directory, such as read/writing a config file or custom JSON files.</summary>
@@ -16,16 +22,44 @@ namespace StardewModdingAPI
public Manifest Manifest { get; internal set; }
/// <summary>The full path to the mod's directory on the disk.</summary>
- public string PathOnDisk { get; internal set; }
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(ModHelper.DirectoryPath) + " instead")]
+ public string PathOnDisk
+ {
+ get
+ {
+ Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(PathOnDisk)}", "1.0");
+ return this._pathOnDisk;
+ }
+ internal set { this._pathOnDisk = value; }
+ }
/// <summary>The full path to the mod's <c>config.json</c> file on the disk.</summary>
- public string BaseConfigPath => Path.Combine(this.PathOnDisk, "config.json");
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(ModHelper.ReadConfig) + " instead")]
+ public string BaseConfigPath
+ {
+ get
+ {
+ Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(BaseConfigPath)}", "1.0");
+ Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(PathOnDisk)}", "1.0"); // avoid redundant warnings
+ return Path.Combine(this.PathOnDisk, "config.json");
+ }
+ }
/// <summary>The full path to the per-save configs folder (if <see cref="StardewModdingAPI.Manifest.PerSaveConfigs"/> is <c>true</c>).</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(ModHelper.ReadJsonFile) + " instead")]
public string PerSaveConfigFolder => this.GetPerSaveConfigFolder();
/// <summary>The full path to the per-save configuration file for the current save (if <see cref="StardewModdingAPI.Manifest.PerSaveConfigs"/> is <c>true</c>).</summary>
- public string PerSaveConfigPath => Constants.CurrentSavePathExists ? Path.Combine(this.PerSaveConfigFolder, Constants.SaveFolderName + ".json") : "";
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(ModHelper.ReadJsonFile) + " instead")]
+ public string PerSaveConfigPath
+ {
+ get
+ {
+ Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(PerSaveConfigPath)}", "1.0");
+ Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(PerSaveConfigFolder)}", "1.0"); // avoid redundant warnings
+ return Constants.CurrentSavePathExists ? Path.Combine(this.PerSaveConfigFolder, Constants.SaveFolderName + ".json") : "";
+ }
+ }
/*********
@@ -46,9 +80,12 @@ namespace StardewModdingAPI
/// <summary>Get the full path to the per-save configuration file for the current save (if <see cref="StardewModdingAPI.Manifest.PerSaveConfigs"/> is <c>true</c>).</summary>
private string GetPerSaveConfigFolder()
{
+ Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(PerSaveConfigFolder)}", "1.0");
+ Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(PathOnDisk)}", "1.0"); // avoid redundant warnings
+
if (!this.Manifest.PerSaveConfigs)
{
- Log.AsyncR($"The mod [{this.Manifest.Name}] is not configured to use per-save configs.");
+ Log.Error($"The mod [{this.Manifest.Name}] is not configured to use per-save configs.");
return "";
}
return Path.Combine(this.PathOnDisk, "psconfigs");
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs
index c0129036..3831e3d4 100644
--- a/src/StardewModdingAPI/Program.cs
+++ b/src/StardewModdingAPI/Program.cs
@@ -54,6 +54,8 @@ namespace StardewModdingAPI
/// <summary>The game's build type (i.e. GOG vs Steam).</summary>
public static int BuildType => (int)Program.StardewProgramType.GetField("buildType", BindingFlags.Public | BindingFlags.Static).GetValue(null);
+ /// <summary>Manages deprecation warnings.</summary>
+ internal static readonly DeprecationManager DeprecationManager = new DeprecationManager();
/*********
** Public methods
@@ -267,6 +269,10 @@ namespace StardewModdingAPI
Log.Error($"{errorPrefix}: manifest doesn't specify an entry DLL.");
continue;
}
+
+ // log deprecated fields
+ if(manifest.UsedAuthourField)
+ Program.DeprecationManager.Warn(manifest.Name, $"{nameof(Manifest)}.{nameof(Manifest.Authour)}", "1.0");
}
catch (Exception ex)
{
@@ -277,6 +283,7 @@ namespace StardewModdingAPI
// create per-save directory
if (manifest.PerSaveConfigs)
{
+ Program.DeprecationManager.Warn($"{nameof(Manifest)}.{nameof(Manifest.PerSaveConfigs)}", "1.0");
try
{
string psDir = Path.Combine(directory, "psconfigs");
@@ -312,13 +319,21 @@ namespace StardewModdingAPI
Mod modEntry = (Mod)modAssembly.CreateInstance(modEntryType.ToString());
if (modEntry != null)
{
+ // add as possible source of deprecation warnings
+ Program.DeprecationManager.AddMod(modAssembly, manifest.Name);
+
+ // hook up mod
modEntry.Helper = helper;
modEntry.PathOnDisk = directory;
modEntry.Manifest = manifest;
Log.Info($"Loaded mod: {modEntry.Manifest.Name} by {modEntry.Manifest.Author}, v{modEntry.Manifest.Version} | {modEntry.Manifest.Description}\n@ {targDll}");
Program.ModsLoaded += 1;
- modEntry.Entry(); // obsolete
+ modEntry.Entry(); // deprecated
modEntry.Entry(modEntry.Helper);
+
+ // raise deprecation warning for old Entry() method
+ if (Program.DeprecationManager.IsVirtualMethodImplemented(modEntryType, typeof(Mod), nameof(Mod.Entry)))
+ Program.DeprecationManager.Warn(manifest.Name, $"an old version of {nameof(Mod)}.{nameof(Mod.Entry)}", "1.0");
}
}
else
diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj
index 451e5961..0b55a925 100644
--- a/src/StardewModdingAPI/StardewModdingAPI.csproj
+++ b/src/StardewModdingAPI/StardewModdingAPI.csproj
@@ -195,6 +195,7 @@
<Compile Include="Events\MineEvents.cs" />
<Compile Include="Events\PlayerEvents.cs" />
<Compile Include="Events\TimeEvents.cs" />
+ <Compile Include="Framework\DeprecationManager.cs" />
<Compile Include="Framework\UpdateHelper.cs" />
<Compile Include="Framework\GitRelease.cs" />
<Compile Include="Inheritance\ChangeType.cs" />
diff --git a/src/StardewModdingAPI/Version.cs b/src/StardewModdingAPI/Version.cs
index b1903d7b..75195820 100644
--- a/src/StardewModdingAPI/Version.cs
+++ b/src/StardewModdingAPI/Version.cs
@@ -32,8 +32,15 @@ namespace StardewModdingAPI
/// <summary>Obsolete.</summary>
[JsonIgnore]
- [Obsolete("Use `Version.ToString()` instead.")]
- public string VersionString => this.ToString();
+ [Obsolete("Use " + nameof(Version) + "." + nameof(Version.ToString) + " instead.")]
+ public string VersionString
+ {
+ get
+ {
+ Program.DeprecationManager.Warn($"{nameof(Version)}.{nameof(Version.VersionString)}", "1.0");
+ return this.ToString();
+ }
+ }
/*********
@@ -59,7 +66,7 @@ namespace StardewModdingAPI
var match = Version.Regex.Match(version);
if (!match.Success)
throw new FormatException($"The input '{version}' is not a semantic version.");
-
+
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;