enable nullable annotations in SMAPI where no logic changes are needed (#837)
This commit is contained in:
parent
6e9e8aef1e
commit
f39da383a1
|
@ -56,7 +56,7 @@ namespace SMAPI.Tests.Core
|
|||
public void Constructor_NullOrWhitespace(string? name)
|
||||
{
|
||||
// act
|
||||
ArgumentException exception = Assert.Throws<ArgumentException>(() => _ = AssetName.Parse(name!, null))!;
|
||||
ArgumentException exception = Assert.Throws<ArgumentException>(() => _ = AssetName.Parse(name!, _ => null))!;
|
||||
|
||||
// assert
|
||||
exception.ParamName.Should().Be("rawName");
|
||||
|
|
|
@ -134,9 +134,9 @@ namespace SMAPI.Tests.Core
|
|||
|
||||
// assert
|
||||
if (translation.HasValue())
|
||||
Assert.AreEqual(text, (string)translation, "The translation returned an unexpected value given a valid input.");
|
||||
Assert.AreEqual(text, (string?)translation, "The translation returned an unexpected value given a valid input.");
|
||||
else
|
||||
Assert.AreEqual(this.GetPlaceholderText("key"), (string)translation, "The translation returned an unexpected value given a null or empty input.");
|
||||
Assert.AreEqual(this.GetPlaceholderText("key"), (string?)translation, "The translation returned an unexpected value given a null or empty input.");
|
||||
}
|
||||
|
||||
[Test(Description = "Assert that the translation returns the expected text given a use-placeholder setting.")]
|
||||
|
|
|
@ -80,7 +80,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
|||
|
||||
/// <summary>Get a parsed representation of the <see cref="ModDataRecord.Fields"/> which match a given manifest.</summary>
|
||||
/// <param name="manifest">The manifest to match.</param>
|
||||
public ModDataRecordVersionedFields GetVersionedFields(IManifest manifest)
|
||||
public ModDataRecordVersionedFields GetVersionedFields(IManifest? manifest)
|
||||
{
|
||||
ModDataRecordVersionedFields parsed = new(this);
|
||||
foreach (ModDataField field in this.Fields.Where(field => field.IsMatch(manifest)))
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -33,7 +31,7 @@ namespace StardewModdingAPI
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The path to the game folder.</summary>
|
||||
public static string GamePath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
public static string GamePath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
|
||||
|
||||
/// <summary>The absolute path to the folder containing SMAPI's internal files.</summary>
|
||||
public static readonly string InternalFilesPath = Path.Combine(EarlyConstants.GamePath, "smapi-internal");
|
||||
|
@ -69,8 +67,8 @@ namespace StardewModdingAPI
|
|||
/// <summary>The minimum supported version of Stardew Valley.</summary>
|
||||
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.6");
|
||||
|
||||
/// <summary>The maximum supported version of Stardew Valley.</summary>
|
||||
public static ISemanticVersion MaximumGameVersion { get; } = null;
|
||||
/// <summary>The maximum supported version of Stardew Valley, if any.</summary>
|
||||
public static ISemanticVersion? MaximumGameVersion { get; } = null;
|
||||
|
||||
/// <summary>The target game platform.</summary>
|
||||
public static GamePlatform TargetPlatform { get; } = EarlyConstants.Platform;
|
||||
|
@ -111,10 +109,10 @@ namespace StardewModdingAPI
|
|||
public static string SavesPath { get; } = Path.Combine(Constants.DataPath, "Saves");
|
||||
|
||||
/// <summary>The name of the current save folder (if save info is available, regardless of whether the save file exists yet).</summary>
|
||||
public static string SaveFolderName => Constants.GetSaveFolderName();
|
||||
public static string? SaveFolderName => Constants.GetSaveFolderName();
|
||||
|
||||
/// <summary>The absolute path to the current save folder (if save info is available and the save file exists).</summary>
|
||||
public static string CurrentSavePath => Constants.GetSaveFolderPathIfExists();
|
||||
public static string? CurrentSavePath => Constants.GetSaveFolderPathIfExists();
|
||||
|
||||
/****
|
||||
** Internal
|
||||
|
@ -164,7 +162,7 @@ namespace StardewModdingAPI
|
|||
internal static string DefaultModsPath { get; } = Path.Combine(Constants.GamePath, "Mods");
|
||||
|
||||
/// <summary>The actual full path to search for mods.</summary>
|
||||
internal static string ModsPath { get; set; }
|
||||
internal static string ModsPath { get; set; } = null!; // initialized early during SMAPI startup
|
||||
|
||||
/// <summary>The game's current semantic version.</summary>
|
||||
internal static ISemanticVersion GameVersion { get; } = new GameVersion(Game1.version);
|
||||
|
@ -179,7 +177,7 @@ namespace StardewModdingAPI
|
|||
/// <summary>Get the SMAPI version to recommend for an older game version, if any.</summary>
|
||||
/// <param name="version">The game version to search.</param>
|
||||
/// <returns>Returns the compatible SMAPI version, or <c>null</c> if none was found.</returns>
|
||||
internal static ISemanticVersion GetCompatibleApiVersion(ISemanticVersion version)
|
||||
internal static ISemanticVersion? GetCompatibleApiVersion(ISemanticVersion version)
|
||||
{
|
||||
// This covers all officially supported public game updates. It might seem like version
|
||||
// ranges would be better, but the given SMAPI versions may not be compatible with
|
||||
|
@ -337,22 +335,22 @@ namespace StardewModdingAPI
|
|||
}
|
||||
|
||||
/// <summary>Get the name of the save folder, if any.</summary>
|
||||
private static string GetSaveFolderName()
|
||||
private static string? GetSaveFolderName()
|
||||
{
|
||||
return Constants.GetSaveFolder()?.Name;
|
||||
}
|
||||
|
||||
/// <summary>Get the absolute path to the current save folder, if any.</summary>
|
||||
private static string GetSaveFolderPathIfExists()
|
||||
private static string? GetSaveFolderPathIfExists()
|
||||
{
|
||||
DirectoryInfo saveFolder = Constants.GetSaveFolder();
|
||||
DirectoryInfo? saveFolder = Constants.GetSaveFolder();
|
||||
return saveFolder?.Exists == true
|
||||
? saveFolder.FullName
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>Get the current save folder, if any.</summary>
|
||||
private static DirectoryInfo GetSaveFolder()
|
||||
private static DirectoryInfo? GetSaveFolder()
|
||||
{
|
||||
// save not available
|
||||
if (Context.LoadStage == LoadStage.None)
|
||||
|
@ -365,7 +363,7 @@ namespace StardewModdingAPI
|
|||
: Game1.uniqueIDForThisGame;
|
||||
|
||||
// get best match (accounting for rare case where folder name isn't sanitized)
|
||||
DirectoryInfo folder = null;
|
||||
DirectoryInfo? folder = null;
|
||||
foreach (string saveName in new[] { rawSaveName, new string(rawSaveName.Where(char.IsLetterOrDigit).ToArray()) })
|
||||
{
|
||||
try
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
@ -19,7 +17,7 @@ namespace StardewModdingAPI.Events
|
|||
private readonly IModMetadata Mod;
|
||||
|
||||
/// <summary>Get the mod metadata for a content pack, if it's a valid content pack for the mod.</summary>
|
||||
private readonly Func<IModMetadata, string, string, IModMetadata> GetOnBehalfOf;
|
||||
private readonly Func<IModMetadata, string?, string, IModMetadata?> GetOnBehalfOf;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -51,7 +49,7 @@ namespace StardewModdingAPI.Events
|
|||
/// <param name="dataType">The requested data type.</param>
|
||||
/// <param name="nameWithoutLocale">The <paramref name="name"/> with any locale codes stripped.</param>
|
||||
/// <param name="getOnBehalfOf">Get the mod metadata for a content pack, if it's a valid content pack for the mod.</param>
|
||||
internal AssetRequestedEventArgs(IModMetadata mod, IAssetName name, IAssetName nameWithoutLocale, Type dataType, Func<IModMetadata, string, string, IModMetadata> getOnBehalfOf)
|
||||
internal AssetRequestedEventArgs(IModMetadata mod, IAssetName name, IAssetName nameWithoutLocale, Type dataType, Func<IModMetadata, string?, string, IModMetadata?> getOnBehalfOf)
|
||||
{
|
||||
this.Mod = mod;
|
||||
this.Name = name;
|
||||
|
@ -71,7 +69,7 @@ namespace StardewModdingAPI.Events
|
|||
/// <item>Each asset can logically only have one initial instance. If multiple loads apply at the same time, SMAPI will use the <paramref name="priority"/> parameter to decide what happens. If you're making changes to the existing asset instead of replacing it, you should use <see cref="Edit"/> instead to avoid those limitations and improve mod compatibility.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public void LoadFrom(Func<object> load, AssetLoadPriority priority, string onBehalfOf = null)
|
||||
public void LoadFrom(Func<object> load, AssetLoadPriority priority, string? onBehalfOf = null)
|
||||
{
|
||||
this.LoadOperations.Add(
|
||||
new AssetLoadOperation(
|
||||
|
@ -95,13 +93,15 @@ namespace StardewModdingAPI.Events
|
|||
/// </list>
|
||||
/// </remarks>
|
||||
public void LoadFromModFile<TAsset>(string relativePath, AssetLoadPriority priority)
|
||||
where TAsset : notnull
|
||||
{
|
||||
this.LoadOperations.Add(
|
||||
new AssetLoadOperation(
|
||||
mod: this.Mod,
|
||||
priority: priority,
|
||||
onBehalfOf: null,
|
||||
_ => this.Mod.Mod.Helper.ModContent.Load<TAsset>(relativePath))
|
||||
_ => this.Mod.Mod!.Helper.ModContent.Load<TAsset>(relativePath)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ namespace StardewModdingAPI.Events
|
|||
/// <item>You can apply any number of edits to the asset. Each edit will be applied on top of the previous one (i.e. it'll see the merged asset from all previous edits as its input).</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public void Edit(Action<IAssetData> apply, AssetEditPriority priority = AssetEditPriority.Default, string onBehalfOf = null)
|
||||
public void Edit(Action<IAssetData> apply, AssetEditPriority priority = AssetEditPriority.Default, string? onBehalfOf = null)
|
||||
{
|
||||
this.EditOperations.Add(
|
||||
new AssetEditOperation(
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using StardewValley.Menus;
|
||||
|
||||
|
@ -11,20 +9,20 @@ namespace StardewModdingAPI.Events
|
|||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The previous menu.</summary>
|
||||
public IClickableMenu OldMenu { get; }
|
||||
/// <summary>The previous menu, if any.</summary>
|
||||
public IClickableMenu? OldMenu { get; }
|
||||
|
||||
/// <summary>The current menu.</summary>
|
||||
public IClickableMenu NewMenu { get; }
|
||||
/// <summary>The current menu, if any.</summary>
|
||||
public IClickableMenu? NewMenu { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="oldMenu">The previous menu.</param>
|
||||
/// <param name="newMenu">The current menu.</param>
|
||||
internal MenuChangedEventArgs(IClickableMenu oldMenu, IClickableMenu newMenu)
|
||||
/// <param name="oldMenu">The previous menu, if any.</param>
|
||||
/// <param name="newMenu">The current menu, if any.</param>
|
||||
internal MenuChangedEventArgs(IClickableMenu? oldMenu, IClickableMenu? newMenu)
|
||||
{
|
||||
this.OldMenu = oldMenu;
|
||||
this.NewMenu = newMenu;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
|
||||
namespace StardewModdingAPI.Framework
|
||||
|
@ -11,7 +9,7 @@ namespace StardewModdingAPI.Framework
|
|||
** Accessor
|
||||
*********/
|
||||
/// <summary>The mod that registered the command (or <c>null</c> if registered by SMAPI).</summary>
|
||||
public IModMetadata Mod { get; }
|
||||
public IModMetadata? Mod { get; }
|
||||
|
||||
/// <summary>The command name, which the user must type to trigger it.</summary>
|
||||
public string Name { get; }
|
||||
|
@ -31,7 +29,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="name">The command name, which the user must type to trigger it.</param>
|
||||
/// <param name="documentation">The human-readable documentation shown when the player runs the built-in 'help' command.</param>
|
||||
/// <param name="callback">The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user.</param>
|
||||
public Command(IModMetadata mod, string name, string documentation, Action<string, string[]> callback)
|
||||
public Command(IModMetadata? mod, string name, string documentation, Action<string, string[]> callback)
|
||||
{
|
||||
this.Mod = mod;
|
||||
this.Name = name;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -73,9 +71,9 @@ namespace StardewModdingAPI.Framework.Commands
|
|||
private IEnumerable<SearchResult> FilterPatches(string[] searchTerms)
|
||||
{
|
||||
bool hasSearch = searchTerms.Any();
|
||||
bool IsMatch(string target) => !hasSearch || searchTerms.Any(search => target != null && target.IndexOf(search, StringComparison.OrdinalIgnoreCase) > -1);
|
||||
bool IsMatch(string? target) => !hasSearch || searchTerms.Any(search => target != null && target.IndexOf(search, StringComparison.OrdinalIgnoreCase) > -1);
|
||||
|
||||
foreach (var patch in this.GetAllPatches())
|
||||
foreach (SearchResult patch in this.GetAllPatches())
|
||||
{
|
||||
// matches entire patch
|
||||
if (IsMatch(patch.MethodDescription))
|
||||
|
@ -85,10 +83,10 @@ namespace StardewModdingAPI.Framework.Commands
|
|||
}
|
||||
|
||||
// matches individual patchers
|
||||
foreach (var pair in patch.PatchTypesByOwner.ToArray())
|
||||
foreach ((string patcherId, ISet<PatchType> patchTypes) in patch.PatchTypesByOwner.ToArray())
|
||||
{
|
||||
if (!IsMatch(pair.Key) && !pair.Value.Any(type => IsMatch(type.ToString())))
|
||||
patch.PatchTypesByOwner.Remove(pair.Key);
|
||||
if (!IsMatch(patcherId) && !patchTypes.Any(type => IsMatch(type.ToString())))
|
||||
patch.PatchTypesByOwner.Remove(patcherId);
|
||||
}
|
||||
|
||||
if (patch.PatchTypesByOwner.Any())
|
||||
|
@ -114,13 +112,13 @@ namespace StardewModdingAPI.Framework.Commands
|
|||
|
||||
// get patch types by owner
|
||||
var typesByOwner = new Dictionary<string, ISet<PatchType>>();
|
||||
foreach (var group in patchGroups)
|
||||
foreach ((PatchType type, IReadOnlyCollection<Patch> patches) in patchGroups)
|
||||
{
|
||||
foreach (var patch in group.Value)
|
||||
foreach (Patch patch in patches)
|
||||
{
|
||||
if (!typesByOwner.TryGetValue(patch.owner, out ISet<PatchType> patchTypes))
|
||||
if (!typesByOwner.TryGetValue(patch.owner, out ISet<PatchType>? patchTypes))
|
||||
typesByOwner[patch.owner] = patchTypes = new HashSet<PatchType>();
|
||||
patchTypes.Add(group.Key);
|
||||
patchTypes.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Content
|
||||
|
@ -7,12 +5,13 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <summary>Base implementation for a content helper which encapsulates access and changes to content being read from a data file.</summary>
|
||||
/// <typeparam name="TValue">The interface value type.</typeparam>
|
||||
internal class AssetData<TValue> : AssetInfo, IAssetData<TValue>
|
||||
where TValue : notnull
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>A callback to invoke when the data is replaced (if any).</summary>
|
||||
private readonly Action<TValue> OnDataReplaced;
|
||||
private readonly Action<TValue>? OnDataReplaced;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -31,7 +30,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <param name="data">The content data being read.</param>
|
||||
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
|
||||
/// <param name="onDataReplaced">A callback to invoke when the data is replaced (if any).</param>
|
||||
public AssetData(string locale, IAssetName assetName, TValue data, Func<string, string> getNormalizedPath, Action<TValue> onDataReplaced)
|
||||
public AssetData(string? locale, IAssetName assetName, TValue data, Func<string, string> getNormalizedPath, Action<TValue>? onDataReplaced)
|
||||
: base(locale, assetName, data.GetType(), getNormalizedPath)
|
||||
{
|
||||
this.Data = data;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
@ -17,7 +15,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <param name="data">The content data being read.</param>
|
||||
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
|
||||
/// <param name="onDataReplaced">A callback to invoke when the data is replaced (if any).</param>
|
||||
public AssetDataForDictionary(string locale, IAssetName assetName, IDictionary<TKey, TValue> data, Func<string, string> getNormalizedPath, Action<IDictionary<TKey, TValue>> onDataReplaced)
|
||||
public AssetDataForDictionary(string? locale, IAssetName assetName, IDictionary<TKey, TValue> data, Func<string, string> getNormalizedPath, Action<IDictionary<TKey, TValue>> onDataReplaced)
|
||||
: base(locale, assetName, data, getNormalizedPath, onDataReplaced) { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <param name="data">The content data being read.</param>
|
||||
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
|
||||
/// <param name="onDataReplaced">A callback to invoke when the data is replaced (if any).</param>
|
||||
public AssetDataForImage(string locale, IAssetName assetName, Texture2D data, Func<string, string> getNormalizedPath, Action<Texture2D> onDataReplaced)
|
||||
public AssetDataForImage(string? locale, IAssetName assetName, Texture2D data, Func<string, string> getNormalizedPath, Action<Texture2D> onDataReplaced)
|
||||
: base(locale, assetName, data, getNormalizedPath, onDataReplaced) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <param name="data">The content data being read.</param>
|
||||
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
|
||||
/// <param name="reflection">Simplifies access to private code.</param>
|
||||
public AssetDataForObject(string locale, IAssetName assetName, object data, Func<string, string> getNormalizedPath, Reflector reflection)
|
||||
public AssetDataForObject(string? locale, IAssetName assetName, object data, Func<string, string> getNormalizedPath, Reflector reflection)
|
||||
: base(locale, assetName, data, getNormalizedPath, onDataReplaced: null)
|
||||
{
|
||||
this.Reflection = reflection;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using StardewModdingAPI.Events;
|
||||
|
||||
|
@ -18,7 +16,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
public AssetEditPriority Priority { get; }
|
||||
|
||||
/// <summary>The content pack on whose behalf the edit is being applied, if any.</summary>
|
||||
public IModMetadata OnBehalfOf { get; }
|
||||
public IModMetadata? OnBehalfOf { get; }
|
||||
|
||||
/// <summary>Apply the edit to an asset.</summary>
|
||||
public Action<IAssetData> ApplyEdit { get; }
|
||||
|
@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <param name="priority">If there are multiple edits that apply to the same asset, the priority with which this one should be applied.</param>
|
||||
/// <param name="onBehalfOf">The content pack on whose behalf the edit is being applied, if any.</param>
|
||||
/// <param name="applyEdit">Apply the edit to an asset.</param>
|
||||
public AssetEditOperation(IModMetadata mod, AssetEditPriority priority, IModMetadata onBehalfOf, Action<IAssetData> applyEdit)
|
||||
public AssetEditOperation(IModMetadata mod, AssetEditPriority priority, IModMetadata? onBehalfOf, Action<IAssetData> applyEdit)
|
||||
{
|
||||
this.Mod = mod;
|
||||
this.Priority = priority;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
** Accessors
|
||||
*********/
|
||||
/// <inheritdoc />
|
||||
public string Locale { get; }
|
||||
public string? Locale { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAssetName Name { get; }
|
||||
|
@ -28,7 +26,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
public IAssetName NameWithoutLocale { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete($"Use {nameof(Name)} or {nameof(NameWithoutLocale)} instead. This property will be removed in SMAPI 4.0.0.")]
|
||||
[Obsolete($"Use {nameof(AssetInfo.Name)} or {nameof(AssetInfo.NameWithoutLocale)} instead. This property will be removed in SMAPI 4.0.0.")]
|
||||
public string AssetName
|
||||
{
|
||||
get
|
||||
|
@ -56,7 +54,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <param name="assetName">The asset name being read.</param>
|
||||
/// <param name="type">The content type being read.</param>
|
||||
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
|
||||
public AssetInfo(string locale, IAssetName assetName, Type type, Func<string, string> getNormalizedPath)
|
||||
public AssetInfo(string? locale, IAssetName assetName, Type type, Func<string, string> getNormalizedPath)
|
||||
{
|
||||
this.Locale = locale;
|
||||
this.Name = assetName;
|
||||
|
@ -66,7 +64,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} or {nameof(NameWithoutLocale)}.{nameof(IAssetName.IsEquivalentTo)} instead. This method will be removed in SMAPI 4.0.0.")]
|
||||
[Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} or {nameof(AssetInfo.NameWithoutLocale)}.{nameof(IAssetName.IsEquivalentTo)} instead. This method will be removed in SMAPI 4.0.0.")]
|
||||
public bool AssetNameEquals(string path)
|
||||
{
|
||||
SCore.DeprecationManager.Warn(
|
||||
|
@ -106,7 +104,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
return "string";
|
||||
|
||||
// default
|
||||
return type.FullName;
|
||||
return type.FullName!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using StardewModdingAPI.Internal;
|
||||
|
||||
#pragma warning disable CS0618 // obsolete asset interceptors deliberately supported here
|
||||
namespace StardewModdingAPI.Framework.Content
|
||||
{
|
||||
/// <summary>A wrapper for <see cref="IAssetEditor"/> and <see cref="IAssetLoader"/> for internal cache invalidation.</summary>
|
||||
|
@ -46,11 +45,11 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <param name="asset">Basic metadata about the asset being loaded.</param>
|
||||
public bool CanIntercept(IAssetInfo asset)
|
||||
{
|
||||
MethodInfo canIntercept = this.GetType().GetMethod(nameof(this.CanInterceptImpl), BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
MethodInfo? canIntercept = this.GetType().GetMethod(nameof(this.CanInterceptImpl), BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (canIntercept == null)
|
||||
throw new InvalidOperationException($"SMAPI couldn't access the {nameof(AssetInterceptorChange)}.{nameof(this.CanInterceptImpl)} implementation.");
|
||||
|
||||
return (bool)canIntercept.MakeGenericMethod(asset.DataType).Invoke(this, new object[] { asset });
|
||||
return (bool)canIntercept.MakeGenericMethod(asset.DataType).Invoke(this, new object[] { asset })!;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using StardewModdingAPI.Events;
|
||||
|
||||
|
@ -15,7 +13,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
public IModMetadata Mod { get; }
|
||||
|
||||
/// <summary>The content pack on whose behalf the asset is being loaded, if any.</summary>
|
||||
public IModMetadata OnBehalfOf { get; }
|
||||
public IModMetadata? OnBehalfOf { get; }
|
||||
|
||||
/// <summary>If there are multiple loads that apply to the same asset, the priority with which this one should be applied.</summary>
|
||||
public AssetLoadPriority Priority { get; }
|
||||
|
@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <param name="priority">If there are multiple loads that apply to the same asset, the priority with which this one should be applied.</param>
|
||||
/// <param name="onBehalfOf">The content pack on whose behalf the asset is being loaded, if any.</param>
|
||||
/// <param name="getData">Load the initial value for an asset.</param>
|
||||
public AssetLoadOperation(IModMetadata mod, AssetLoadPriority priority, IModMetadata onBehalfOf, Func<IAssetInfo, object> getData)
|
||||
public AssetLoadOperation(IModMetadata mod, AssetLoadPriority priority, IModMetadata? onBehalfOf, Func<IAssetInfo, object> getData)
|
||||
{
|
||||
this.Mod = mod;
|
||||
this.Priority = priority;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -71,7 +69,7 @@ namespace StardewModdingAPI.Framework
|
|||
private readonly ReaderWriterLockSlim ContentManagerLock = new();
|
||||
|
||||
/// <summary>A cache of ordered tilesheet IDs used by vanilla maps.</summary>
|
||||
private readonly Dictionary<string, TilesheetReference[]> VanillaTilesheets = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, TilesheetReference[]?> VanillaTilesheets = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>An unmodified content manager which doesn't intercept assets, used to compare asset data.</summary>
|
||||
private readonly LocalizedContentManager VanillaContentManager;
|
||||
|
@ -230,7 +228,7 @@ namespace StardewModdingAPI.Framework
|
|||
public void OnAdditionalLanguagesInitialized()
|
||||
{
|
||||
// update locale cache for custom languages, and load it now (since languages added later won't work)
|
||||
var customLanguages = this.MainContentManager.Load<List<ModLanguage>>("Data/AdditionalLanguages");
|
||||
var customLanguages = this.MainContentManager.Load<List<ModLanguage?>>("Data/AdditionalLanguages");
|
||||
this.LocaleCodes = new Lazy<Dictionary<string, LocalizedContentManager.LanguageCode>>(() => this.GetLocaleCodes(customLanguages));
|
||||
_ = this.LocaleCodes.Value;
|
||||
}
|
||||
|
@ -303,7 +301,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="contentManagerID">The unique name for the content manager which should load this asset.</param>
|
||||
/// <param name="relativePath">The asset name within the mod folder.</param>
|
||||
/// <returns>Returns whether the asset was parsed successfully.</returns>
|
||||
public bool TryParseManagedAssetKey(string key, out string contentManagerID, out IAssetName relativePath)
|
||||
public bool TryParseManagedAssetKey(string key, [NotNullWhen(true)] out string? contentManagerID, [NotNullWhen(true)] out IAssetName? relativePath)
|
||||
{
|
||||
contentManagerID = null;
|
||||
relativePath = null;
|
||||
|
@ -333,9 +331,10 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="contentManagerID">The unique name for the content manager which should load this asset.</param>
|
||||
/// <param name="assetName">The asset name within the mod folder.</param>
|
||||
public bool DoesManagedAssetExist<T>(string contentManagerID, IAssetName assetName)
|
||||
where T : notnull
|
||||
{
|
||||
// get content manager
|
||||
IContentManager contentManager = this.ContentManagerLock.InReadLock(() =>
|
||||
IContentManager? contentManager = this.ContentManagerLock.InReadLock(() =>
|
||||
this.ContentManagers.FirstOrDefault(p => p.IsNamespaced && p.Name == contentManagerID)
|
||||
);
|
||||
if (contentManager == null)
|
||||
|
@ -350,9 +349,10 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="contentManagerID">The unique name for the content manager which should load this asset.</param>
|
||||
/// <param name="relativePath">The asset name within the mod folder.</param>
|
||||
public T LoadManagedAsset<T>(string contentManagerID, IAssetName relativePath)
|
||||
where T : notnull
|
||||
{
|
||||
// get content manager
|
||||
IContentManager contentManager = this.ContentManagerLock.InReadLock(() =>
|
||||
IContentManager? contentManager = this.ContentManagerLock.InReadLock(() =>
|
||||
this.ContentManagers.FirstOrDefault(p => p.IsNamespaced && p.Name == contentManagerID)
|
||||
);
|
||||
if (contentManager == null)
|
||||
|
@ -461,6 +461,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <typeparam name="T">The asset type.</typeparam>
|
||||
/// <param name="info">The asset info to load or edit.</param>
|
||||
public IEnumerable<AssetOperationGroup> GetAssetOperations<T>(IAssetInfo info)
|
||||
where T : notnull
|
||||
{
|
||||
return this.AssetOperationsByKey.GetOrSet(
|
||||
info.Name,
|
||||
|
@ -491,7 +492,7 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
rootPath = PathUtilities.NormalizePath(rootPath);
|
||||
|
||||
if (!this.CaseInsensitivePathCaches.TryGetValue(rootPath, out CaseInsensitivePathCache cache))
|
||||
if (!this.CaseInsensitivePathCaches.TryGetValue(rootPath, out CaseInsensitivePathCache? cache))
|
||||
this.CaseInsensitivePathCaches[rootPath] = cache = new CaseInsensitivePathCache(rootPath);
|
||||
|
||||
return cache;
|
||||
|
@ -501,9 +502,9 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
|
||||
public TilesheetReference[] GetVanillaTilesheetIds(string assetName)
|
||||
{
|
||||
if (!this.VanillaTilesheets.TryGetValue(assetName, out TilesheetReference[] tilesheets))
|
||||
if (!this.VanillaTilesheets.TryGetValue(assetName, out TilesheetReference[]? tilesheets))
|
||||
{
|
||||
tilesheets = this.TryLoadVanillaAsset(assetName, out Map map)
|
||||
tilesheets = this.TryLoadVanillaAsset(assetName, out Map? map)
|
||||
? map.TileSheets.Select((sheet, index) => new TilesheetReference(index, sheet.Id, sheet.ImageSource, sheet.SheetSize, sheet.TileSize)).ToArray()
|
||||
: null;
|
||||
|
||||
|
@ -516,7 +517,7 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
/// <summary>Get the locale code which corresponds to a language enum (e.g. <c>fr-FR</c> given <see cref="LocalizedContentManager.LanguageCode.fr"/>).</summary>
|
||||
/// <param name="language">The language enum to search.</param>
|
||||
public string GetLocaleCode(LocalizedContentManager.LanguageCode language)
|
||||
public string? GetLocaleCode(LocalizedContentManager.LanguageCode language)
|
||||
{
|
||||
if (language == LocalizedContentManager.LanguageCode.mod && LocalizedContentManager.CurrentModLanguage == null)
|
||||
return null;
|
||||
|
@ -535,7 +536,7 @@ namespace StardewModdingAPI.Framework
|
|||
foreach (IContentManager contentManager in this.ContentManagers)
|
||||
contentManager.Dispose();
|
||||
this.ContentManagers.Clear();
|
||||
this.MainContentManager = null;
|
||||
this.MainContentManager = null!; // instance no longer usable
|
||||
|
||||
this.ContentManagerLock.Dispose();
|
||||
}
|
||||
|
@ -560,7 +561,8 @@ namespace StardewModdingAPI.Framework
|
|||
/// <typeparam name="T">The type of asset to load.</typeparam>
|
||||
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
|
||||
/// <param name="asset">The loaded asset data.</param>
|
||||
private bool TryLoadVanillaAsset<T>(string assetName, out T asset)
|
||||
private bool TryLoadVanillaAsset<T>(string assetName, [NotNullWhen(true)] out T? asset)
|
||||
where T : notnull
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -576,12 +578,12 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
/// <summary>Get the language enums (like <see cref="LocalizedContentManager.LanguageCode.ja"/>) indexed by locale code (like <c>ja-JP</c>).</summary>
|
||||
/// <param name="customLanguages">The custom languages to add to the lookup.</param>
|
||||
private Dictionary<string, LocalizedContentManager.LanguageCode> GetLocaleCodes(IEnumerable<ModLanguage> customLanguages)
|
||||
private Dictionary<string, LocalizedContentManager.LanguageCode> GetLocaleCodes(IEnumerable<ModLanguage?> customLanguages)
|
||||
{
|
||||
var map = new Dictionary<string, LocalizedContentManager.LanguageCode>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// custom languages
|
||||
foreach (ModLanguage language in customLanguages)
|
||||
foreach (ModLanguage? language in customLanguages)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(language?.LanguageCode))
|
||||
map[language.LanguageCode] = LocalizedContentManager.LanguageCode.mod;
|
||||
|
@ -590,7 +592,7 @@ namespace StardewModdingAPI.Framework
|
|||
// vanilla languages (override custom language if they conflict)
|
||||
foreach (LocalizedContentManager.LanguageCode code in Enum.GetValues(typeof(LocalizedContentManager.LanguageCode)))
|
||||
{
|
||||
string locale = this.GetLocaleCode(code);
|
||||
string? locale = this.GetLocaleCode(code);
|
||||
if (locale != null)
|
||||
map[locale] = code;
|
||||
}
|
||||
|
@ -602,6 +604,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <typeparam name="T">The asset type.</typeparam>
|
||||
/// <param name="info">The asset info to load or edit.</param>
|
||||
private IEnumerable<AssetOperationGroup> GetAssetOperationsWithoutCache<T>(IAssetInfo info)
|
||||
where T : notnull
|
||||
{
|
||||
IAssetInfo legacyInfo = this.GetLegacyAssetInfo(info);
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -70,7 +69,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
return true;
|
||||
|
||||
// managed asset
|
||||
if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath))
|
||||
if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath))
|
||||
return this.Coordinator.DoesManagedAssetExist<T>(contentManagerID, relativePath);
|
||||
|
||||
// custom asset from a loader
|
||||
|
@ -78,7 +77,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName);
|
||||
AssetLoadOperation[] loaders = this.GetLoaders<object>(info).ToArray();
|
||||
|
||||
if (!this.AssertMaxOneRequiredLoader(info, loaders, out string error))
|
||||
if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error))
|
||||
{
|
||||
this.Monitor.Log(error, LogLevel.Warn);
|
||||
return false;
|
||||
|
@ -102,7 +101,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
return this.RawLoad<T>(assetName, useCache: true);
|
||||
|
||||
// get managed asset
|
||||
if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath))
|
||||
if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath))
|
||||
{
|
||||
T managedAsset = this.Coordinator.LoadManagedAsset<T>(contentManagerID, relativePath);
|
||||
this.TrackAsset(assetName, managedAsset, useCache);
|
||||
|
@ -151,14 +150,15 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <summary>Load the initial asset from the registered loaders.</summary>
|
||||
/// <param name="info">The basic asset metadata.</param>
|
||||
/// <returns>Returns the loaded asset metadata, or <c>null</c> if no loader matched.</returns>
|
||||
private IAssetData ApplyLoader<T>(IAssetInfo info)
|
||||
private IAssetData? ApplyLoader<T>(IAssetInfo info)
|
||||
where T : notnull
|
||||
{
|
||||
// find matching loader
|
||||
AssetLoadOperation loader;
|
||||
AssetLoadOperation? loader;
|
||||
{
|
||||
AssetLoadOperation[] loaders = this.GetLoaders<T>(info).OrderByDescending(p => p.Priority).ToArray();
|
||||
|
||||
if (!this.AssertMaxOneRequiredLoader(info, loaders, out string error))
|
||||
if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error))
|
||||
{
|
||||
this.Monitor.Log(error, LogLevel.Warn);
|
||||
return null;
|
||||
|
@ -196,20 +196,21 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <param name="info">The basic asset metadata.</param>
|
||||
/// <param name="asset">The loaded asset.</param>
|
||||
private IAssetData ApplyEditors<T>(IAssetInfo info, IAssetData asset)
|
||||
where T : notnull
|
||||
{
|
||||
IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName, this.Reflection);
|
||||
|
||||
// special case: if the asset was loaded with a more general type like 'object', call editors with the actual type instead.
|
||||
{
|
||||
Type actualType = asset.Data.GetType();
|
||||
Type actualOpenType = actualType.IsGenericType ? actualType.GetGenericTypeDefinition() : null;
|
||||
Type? actualOpenType = actualType.IsGenericType ? actualType.GetGenericTypeDefinition() : null;
|
||||
|
||||
if (typeof(T) != actualType && (actualOpenType == typeof(Dictionary<,>) || actualOpenType == typeof(List<>) || actualType == typeof(Texture2D) || actualType == typeof(Map)))
|
||||
{
|
||||
return (IAssetData)this.GetType()
|
||||
.GetMethod(nameof(this.ApplyEditors), BindingFlags.NonPublic | BindingFlags.Instance)!
|
||||
.MakeGenericMethod(actualType)
|
||||
.Invoke(this, new object[] { info, asset });
|
||||
.Invoke(this, new object[] { info, asset })!;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,6 +233,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
}
|
||||
|
||||
// validate edit
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse -- it's only guaranteed non-null after this method
|
||||
if (asset.Data == null)
|
||||
{
|
||||
mod.LogAsMod($"Mod incorrectly set asset '{info.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)} to a null value; ignoring override.", LogLevel.Warn);
|
||||
|
@ -252,6 +254,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <typeparam name="T">The asset type.</typeparam>
|
||||
/// <param name="info">The basic asset metadata.</param>
|
||||
private IEnumerable<AssetLoadOperation> GetLoaders<T>(IAssetInfo info)
|
||||
where T : notnull
|
||||
{
|
||||
return this.Coordinator
|
||||
.GetAssetOperations<T>(info)
|
||||
|
@ -262,6 +265,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <typeparam name="T">The asset type.</typeparam>
|
||||
/// <param name="info">The basic asset metadata.</param>
|
||||
private IEnumerable<AssetEditOperation> GetEditors<T>(IAssetInfo info)
|
||||
where T : notnull
|
||||
{
|
||||
return this.Coordinator
|
||||
.GetAssetOperations<T>(info)
|
||||
|
@ -273,7 +277,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <param name="loaders">The asset loaders to apply.</param>
|
||||
/// <param name="error">The error message to show to the user, if the method returns false.</param>
|
||||
/// <returns>Returns true if only one loader will apply, else false.</returns>
|
||||
private bool AssertMaxOneRequiredLoader(IAssetInfo info, AssetLoadOperation[] loaders, out string error)
|
||||
private bool AssertMaxOneRequiredLoader(IAssetInfo info, AssetLoadOperation[] loaders, [NotNullWhen(false)] out string? error)
|
||||
{
|
||||
AssetLoadOperation[] required = loaders.Where(p => p.Priority == AssetLoadPriority.Exclusive).ToArray();
|
||||
if (required.Length <= 1)
|
||||
|
@ -299,7 +303,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <param name="onBehalfOf">The content pack on whose behalf the action is being performed.</param>
|
||||
/// <param name="parenthetical">whether to format the label as a parenthetical shown after the mod name like <c> (for the 'X' content pack)</c>, instead of a standalone label like <c>the 'X' content pack</c>.</param>
|
||||
/// <returns>Returns the on-behalf-of label if applicable, else <c>null</c>.</returns>
|
||||
private string GetOnBehalfOfLabel(IModMetadata onBehalfOf, bool parenthetical = true)
|
||||
[return: NotNullIfNotNull("onBehalfOf")]
|
||||
private string? GetOnBehalfOfLabel(IModMetadata? onBehalfOf, bool parenthetical = true)
|
||||
{
|
||||
if (onBehalfOf == null)
|
||||
return null;
|
||||
|
@ -315,7 +320,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <param name="data">The loaded asset data.</param>
|
||||
/// <param name="loader">The loader which loaded the asset.</param>
|
||||
/// <returns>Returns whether the asset passed validation checks (after any fixes were applied).</returns>
|
||||
private bool TryFixAndValidateLoadedAsset<T>(IAssetInfo info, T data, AssetLoadOperation loader)
|
||||
private bool TryFixAndValidateLoadedAsset<T>(IAssetInfo info, [NotNullWhen(true)] T? data, AssetLoadOperation loader)
|
||||
where T : notnull
|
||||
{
|
||||
IModMetadata mod = loader.Mod;
|
||||
|
||||
|
@ -335,7 +341,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
// add missing tilesheet
|
||||
if (loadedMap.GetTileSheet(vanillaSheet.Id) == null)
|
||||
{
|
||||
mod.Monitor.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn);
|
||||
mod.Monitor!.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn);
|
||||
this.Monitor.Log($"Fixed broken map replacement: {mod.DisplayName} loaded '{info.Name}' without a required tilesheet (id: {vanillaSheet.Id}, source: {vanillaSheet.ImageSource}).");
|
||||
|
||||
loadedMap.AddTileSheet(new TileSheet(vanillaSheet.Id, loadedMap, vanillaSheet.ImageSource, vanillaSheet.SheetSize, vanillaSheet.TileSize));
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
|
||||
/// <summary>Get whether a texture was loaded by this content manager.</summary>
|
||||
/// <param name="texture">The texture to check.</param>
|
||||
public bool IsResponsibleFor(Texture2D texture)
|
||||
public bool IsResponsibleFor(Texture2D? texture)
|
||||
{
|
||||
return
|
||||
texture?.Tag is string tag
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
|
@ -33,25 +31,28 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <summary>Get whether an asset exists and can be loaded.</summary>
|
||||
/// <typeparam name="T">The expected asset type.</typeparam>
|
||||
/// <param name="assetName">The normalized asset name.</param>
|
||||
bool DoesAssetExist<T>(IAssetName assetName);
|
||||
bool DoesAssetExist<T>(IAssetName assetName)
|
||||
where T: notnull;
|
||||
|
||||
/// <summary>Load an asset through the content pipeline, using a localized variant of the <paramref name="assetName"/> if available.</summary>
|
||||
/// <typeparam name="T">The type of asset to load.</typeparam>
|
||||
/// <param name="assetName">The asset name relative to the loader root directory.</param>
|
||||
/// <param name="language">The language for which to load the asset.</param>
|
||||
/// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
|
||||
T LoadLocalized<T>(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache);
|
||||
T LoadLocalized<T>(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>Load an asset through the content pipeline, using the exact asset name without checking for localized variants.</summary>
|
||||
/// <typeparam name="T">The type of asset to load.</typeparam>
|
||||
/// <param name="assetName">The asset name relative to the loader root directory.</param>
|
||||
/// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
|
||||
T LoadExact<T>(IAssetName assetName, bool useCache);
|
||||
T LoadExact<T>(IAssetName assetName, bool useCache)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>Assert that the given key has a valid format and return a normalized form consistent with the underlying cache.</summary>
|
||||
/// <param name="assetName">The asset key to check.</param>
|
||||
/// <exception cref="SContentLoadException">The asset key is empty or contains invalid characters.</exception>
|
||||
string AssertAndNormalizeAssetName(string assetName);
|
||||
string AssertAndNormalizeAssetName(string? assetName);
|
||||
|
||||
/// <summary>Get the current content locale.</summary>
|
||||
string GetLocale();
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using StardewModdingAPI.Framework.ModHelpers;
|
||||
|
@ -69,12 +67,12 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TModel ReadJsonFile<TModel>(string path) where TModel : class
|
||||
public TModel? ReadJsonFile<TModel>(string path) where TModel : class
|
||||
{
|
||||
path = PathUtilities.NormalizePath(path);
|
||||
|
||||
FileInfo file = this.GetFile(path);
|
||||
return file.Exists && this.JsonHelper.ReadJsonFileIfExists(file.FullName, out TModel model)
|
||||
return file.Exists && this.JsonHelper.ReadJsonFileIfExists(file.FullName, out TModel? model)
|
||||
? model
|
||||
: null;
|
||||
}
|
||||
|
@ -93,6 +91,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <inheritdoc />
|
||||
[Obsolete]
|
||||
public T LoadAsset<T>(string key)
|
||||
where T : notnull
|
||||
{
|
||||
return this.ModContent.Load<T>(key);
|
||||
}
|
||||
|
@ -101,7 +100,7 @@ namespace StardewModdingAPI.Framework
|
|||
[Obsolete]
|
||||
public string GetActualAssetKey(string key)
|
||||
{
|
||||
return this.ModContent.GetInternalAssetName(key)?.Name;
|
||||
return this.ModContent.GetInternalAssetName(key).Name;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using StardewValley;
|
||||
|
||||
|
@ -41,7 +39,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(ICursorPosition other)
|
||||
public bool Equals(ICursorPosition? other)
|
||||
{
|
||||
return other != null && this.AbsolutePixels == other.AbsolutePixels;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -23,7 +21,7 @@ namespace StardewModdingAPI.Framework.Events
|
|||
private readonly List<ManagedEventHandler<TEventArgs>> Handlers = new();
|
||||
|
||||
/// <summary>A cached snapshot of <see cref="Handlers"/>, or <c>null</c> to rebuild it next raise.</summary>
|
||||
private ManagedEventHandler<TEventArgs>[] CachedHandlers = Array.Empty<ManagedEventHandler<TEventArgs>>();
|
||||
private ManagedEventHandler<TEventArgs>[]? CachedHandlers = Array.Empty<ManagedEventHandler<TEventArgs>>();
|
||||
|
||||
/// <summary>The total number of event handlers registered for this events, regardless of whether they're still registered.</summary>
|
||||
private int RegistrationIndex;
|
||||
|
@ -100,7 +98,7 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Raise the event and notify all handlers.</summary>
|
||||
/// <param name="args">The event arguments to pass.</param>
|
||||
/// <param name="match">A lambda which returns true if the event should be raised for the given mod.</param>
|
||||
public void Raise(TEventArgs args, Func<IModMetadata, bool> match = null)
|
||||
public void Raise(TEventArgs args, Func<IModMetadata, bool>? match = null)
|
||||
{
|
||||
this.Raise((_, invoke) => invoke(args), match);
|
||||
}
|
||||
|
@ -108,7 +106,7 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Raise the event and notify all handlers.</summary>
|
||||
/// <param name="invoke">Invoke an event handler. This receives the mod which registered the handler, and should invoke the callback with the event arguments to pass it.</param>
|
||||
/// <param name="match">A lambda which returns true if the event should be raised for the given mod.</param>
|
||||
public void Raise(Action<IModMetadata, Action<TEventArgs>> invoke, Func<IModMetadata, bool> match = null)
|
||||
public void Raise(Action<IModMetadata, Action<TEventArgs>> invoke, Func<IModMetadata, bool>? match = null)
|
||||
{
|
||||
// skip if no handlers
|
||||
if (this.Handlers.Count == 0)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using StardewModdingAPI.Events;
|
||||
|
||||
|
@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework.Events
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CompareTo(object obj)
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if (obj is not ManagedEventHandler<TEventArgs> other)
|
||||
throw new ArgumentException("Can't compare to an unrelated object type.");
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
|
||||
|
@ -14,7 +12,7 @@ namespace StardewModdingAPI.Framework.Exceptions
|
|||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="message">The error message.</param>
|
||||
/// <param name="ex">The underlying exception, if any.</param>
|
||||
public SContentLoadException(string message, Exception ex = null)
|
||||
public SContentLoadException(string message, Exception? ex = null)
|
||||
: base(message, ex) { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
@ -55,7 +53,7 @@ namespace StardewModdingAPI.Framework
|
|||
private static string GetSemanticVersionString(string gameVersion)
|
||||
{
|
||||
// mapped version
|
||||
return GameVersion.VersionMap.TryGetValue(gameVersion, out string semanticVersion)
|
||||
return GameVersion.VersionMap.TryGetValue(gameVersion, out string? semanticVersion)
|
||||
? semanticVersion
|
||||
: gameVersion;
|
||||
}
|
||||
|
@ -64,10 +62,10 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="semanticVersion">The semantic version string.</param>
|
||||
private static string GetGameVersionString(string semanticVersion)
|
||||
{
|
||||
foreach (var mapping in GameVersion.VersionMap)
|
||||
foreach ((string gameVersion, string equivalentSemanticVersion) in GameVersion.VersionMap)
|
||||
{
|
||||
if (mapping.Value.Equals(semanticVersion, StringComparison.OrdinalIgnoreCase))
|
||||
return mapping.Key;
|
||||
if (equivalentSemanticVersion.Equals(semanticVersion, StringComparison.OrdinalIgnoreCase))
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
return semanticVersion;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using StardewModdingAPI.Framework.ModHelpers;
|
||||
|
@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework
|
|||
string RelativeDirectoryPath { get; }
|
||||
|
||||
/// <summary>Metadata about the mod from SMAPI's internal data (if any).</summary>
|
||||
ModDataRecordVersionedFields DataRecord { get; }
|
||||
ModDataRecordVersionedFields? DataRecord { get; }
|
||||
|
||||
/// <summary>The metadata resolution status.</summary>
|
||||
ModMetadataStatus Status { get; }
|
||||
|
@ -41,31 +39,31 @@ namespace StardewModdingAPI.Framework
|
|||
ModWarning Warnings { get; }
|
||||
|
||||
/// <summary>The reason the metadata is invalid, if any.</summary>
|
||||
string Error { get; }
|
||||
string? Error { get; }
|
||||
|
||||
/// <summary>A detailed technical message for <see cref="Error"/>, if any.</summary>
|
||||
public string ErrorDetails { get; }
|
||||
string? ErrorDetails { get; }
|
||||
|
||||
/// <summary>Whether the mod folder should be ignored. This is <c>true</c> if it was found within a folder whose name starts with a dot.</summary>
|
||||
bool IsIgnored { get; }
|
||||
|
||||
/// <summary>The mod instance (if loaded and <see cref="IModInfo.IsContentPack"/> is false).</summary>
|
||||
IMod Mod { get; }
|
||||
IMod? Mod { get; }
|
||||
|
||||
/// <summary>The content pack instance (if loaded and <see cref="IModInfo.IsContentPack"/> is true).</summary>
|
||||
IContentPack ContentPack { get; }
|
||||
IContentPack? ContentPack { get; }
|
||||
|
||||
/// <summary>The translations for this mod (if loaded).</summary>
|
||||
TranslationHelper Translations { get; }
|
||||
TranslationHelper? Translations { get; }
|
||||
|
||||
/// <summary>Writes messages to the console and log file as this mod.</summary>
|
||||
IMonitor Monitor { get; }
|
||||
IMonitor? Monitor { get; }
|
||||
|
||||
/// <summary>The mod-provided API (if any).</summary>
|
||||
object Api { get; }
|
||||
object? Api { get; }
|
||||
|
||||
/// <summary>The update-check metadata for this mod (if any).</summary>
|
||||
ModEntryModel UpdateCheckData { get; }
|
||||
ModEntryModel? UpdateCheckData { get; }
|
||||
|
||||
/// <summary>The fake content packs created by this mod, if any.</summary>
|
||||
ISet<WeakReference<ContentPack>> FakeContentPacks { get; }
|
||||
|
@ -84,7 +82,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="error">The reason the metadata is invalid, if any.</param>
|
||||
/// <param name="errorDetails">A detailed technical message, if any.</param>
|
||||
/// <returns>Return the instance for chaining.</returns>
|
||||
IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string error, string errorDetails = null);
|
||||
IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string? error, string? errorDetails = null);
|
||||
|
||||
/// <summary>Set a warning flag for the mod.</summary>
|
||||
/// <param name="warning">The warning to set.</param>
|
||||
|
@ -103,7 +101,7 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
/// <summary>Set the mod-provided API instance.</summary>
|
||||
/// <param name="api">The mod-provided API.</param>
|
||||
IModMetadata SetApi(object api);
|
||||
IModMetadata SetApi(object? api);
|
||||
|
||||
/// <summary>Set the update-check metadata for this mod.</summary>
|
||||
/// <param name="data">The update-check metadata.</param>
|
||||
|
@ -117,7 +115,7 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
/// <summary>Whether the mod has the given ID.</summary>
|
||||
/// <param name="id">The mod ID to check.</param>
|
||||
bool HasID(string id);
|
||||
bool HasID(string? id);
|
||||
|
||||
/// <summary>Get the defined update keys.</summary>
|
||||
/// <param name="validOnly">Only return valid update keys.</param>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
|
@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.Logging
|
|||
this.Path = path;
|
||||
|
||||
// create log directory if needed
|
||||
string logDir = System.IO.Path.GetDirectoryName(path);
|
||||
string? logDir = System.IO.Path.GetDirectoryName(path);
|
||||
if (logDir == null)
|
||||
throw new ArgumentException($"The log path '{path}' is not valid.");
|
||||
Directory.CreateDirectory(logDir);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
@ -114,6 +112,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
|
||||
/// <inheritdoc />
|
||||
public T Load<T>(string key, ContentSource source = ContentSource.ModFolder)
|
||||
where T : notnull
|
||||
{
|
||||
IAssetName assetName = this.ContentCore.ParseAssetName(key, allowLocales: source == ContentSource.GameContent);
|
||||
|
||||
|
@ -140,7 +139,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
|
||||
/// <inheritdoc />
|
||||
[Pure]
|
||||
public string NormalizeAssetName(string assetName)
|
||||
public string NormalizeAssetName(string? assetName)
|
||||
{
|
||||
return this.ModContentManager.AssertAndNormalizeAssetName(assetName);
|
||||
}
|
||||
|
@ -171,6 +170,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
|
||||
/// <inheritdoc />
|
||||
public bool InvalidateCache<T>()
|
||||
where T : notnull
|
||||
{
|
||||
this.Monitor.Log($"Requested cache invalidation for all assets of type {typeof(T)}. This is an expensive operation and should be avoided if possible.");
|
||||
return this.ContentCore.InvalidateCache((_, _, type) => typeof(T).IsAssignableFrom(type)).Any();
|
||||
|
@ -184,7 +184,8 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAssetData GetPatchHelper<T>(T data, string assetName = null)
|
||||
public IAssetData GetPatchHelper<T>(T data, string? assetName = null)
|
||||
where T : notnull
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value.");
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -42,19 +40,21 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
** JSON file
|
||||
****/
|
||||
/// <inheritdoc />
|
||||
public TModel ReadJsonFile<TModel>(string path) where TModel : class
|
||||
public TModel? ReadJsonFile<TModel>(string path)
|
||||
where TModel : class
|
||||
{
|
||||
if (!PathUtilities.IsSafeRelativePath(path))
|
||||
throw new InvalidOperationException($"You must call {nameof(IModHelper.Data)}.{nameof(this.ReadJsonFile)} with a relative path.");
|
||||
|
||||
path = Path.Combine(this.ModFolderPath, PathUtilities.NormalizePath(path));
|
||||
return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data)
|
||||
return this.JsonHelper.ReadJsonFileIfExists(path, out TModel? data)
|
||||
? data
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteJsonFile<TModel>(string path, TModel data) where TModel : class
|
||||
public void WriteJsonFile<TModel>(string path, TModel? data)
|
||||
where TModel : class
|
||||
{
|
||||
if (!PathUtilities.IsSafeRelativePath(path))
|
||||
throw new InvalidOperationException($"You must call {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteJsonFile)} with a relative path (without directory climbing).");
|
||||
|
@ -71,7 +71,8 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
** Save file
|
||||
****/
|
||||
/// <inheritdoc />
|
||||
public TModel ReadSaveData<TModel>(string key) where TModel : class
|
||||
public TModel? ReadSaveData<TModel>(string key)
|
||||
where TModel : class
|
||||
{
|
||||
if (Context.LoadStage == LoadStage.None)
|
||||
throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.ReadSaveData)} when a save file isn't loaded.");
|
||||
|
@ -82,14 +83,15 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
string internalKey = this.GetSaveFileKey(key);
|
||||
foreach (IDictionary<string, string> dataField in this.GetDataFields(Context.LoadStage))
|
||||
{
|
||||
if (dataField.TryGetValue(internalKey, out string value))
|
||||
if (dataField.TryGetValue(internalKey, out string? value))
|
||||
return this.JsonHelper.Deserialize<TModel>(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteSaveData<TModel>(string key, TModel model) where TModel : class
|
||||
public void WriteSaveData<TModel>(string key, TModel? model)
|
||||
where TModel : class
|
||||
{
|
||||
if (Context.LoadStage == LoadStage.None)
|
||||
throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteSaveData)} when a save file isn't loaded.");
|
||||
|
@ -97,7 +99,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteSaveData)} when connected to a remote host. (Save files are stored on the main player's computer.)");
|
||||
|
||||
string internalKey = this.GetSaveFileKey(key);
|
||||
string data = model != null
|
||||
string? data = model != null
|
||||
? this.JsonHelper.Serialize(model, Formatting.None)
|
||||
: null;
|
||||
|
||||
|
@ -114,16 +116,18 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
** Global app data
|
||||
****/
|
||||
/// <inheritdoc />
|
||||
public TModel ReadGlobalData<TModel>(string key) where TModel : class
|
||||
public TModel? ReadGlobalData<TModel>(string key)
|
||||
where TModel : class
|
||||
{
|
||||
string path = this.GetGlobalDataPath(key);
|
||||
return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data)
|
||||
return this.JsonHelper.ReadJsonFileIfExists(path, out TModel? data)
|
||||
? data
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteGlobalData<TModel>(string key, TModel data) where TModel : class
|
||||
public void WriteGlobalData<TModel>(string key, TModel? data)
|
||||
where TModel : class
|
||||
{
|
||||
string path = this.GetGlobalDataPath(key);
|
||||
if (data != null)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Framework.Content;
|
||||
|
@ -71,6 +69,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
|
||||
/// <inheritdoc />
|
||||
public T Load<T>(string key)
|
||||
where T : notnull
|
||||
{
|
||||
IAssetName assetName = this.ContentCore.ParseAssetName(key, allowLocales: true);
|
||||
return this.Load<T>(assetName);
|
||||
|
@ -78,6 +77,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
|
||||
/// <inheritdoc />
|
||||
public T Load<T>(IAssetName assetName)
|
||||
where T : notnull
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -105,6 +105,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
|
||||
/// <inheritdoc />
|
||||
public bool InvalidateCache<T>()
|
||||
where T : notnull
|
||||
{
|
||||
this.Monitor.Log($"Requested cache invalidation for all assets of type {typeof(T)}. This is an expensive operation and should be avoided if possible.");
|
||||
return this.ContentCore.InvalidateCache((_, _, type) => typeof(T).IsAssignableFrom(type)).Any();
|
||||
|
@ -118,7 +119,8 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAssetData GetPatchHelper<T>(T data, string assetName = null)
|
||||
public IAssetData GetPatchHelper<T>(T data, string? assetName = null)
|
||||
where T : notnull
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value.");
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using StardewModdingAPI.Framework.Content;
|
||||
|
@ -57,6 +55,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
|
||||
/// <inheritdoc />
|
||||
public T Load<T>(string relativePath)
|
||||
where T : notnull
|
||||
{
|
||||
relativePath = this.RelativePathCache.GetAssetName(relativePath);
|
||||
|
||||
|
@ -80,7 +79,8 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAssetData GetPatchHelper<T>(T data, string relativePath = null)
|
||||
public IAssetData GetPatchHelper<T>(T data, string? relativePath = null)
|
||||
where T : notnull
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value.");
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
|
||||
|
@ -47,7 +45,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IModInfo Get(string uniqueID)
|
||||
public IModInfo? Get(string uniqueID)
|
||||
{
|
||||
return this.Registry.Get(uniqueID);
|
||||
}
|
||||
|
@ -59,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetApi(string uniqueID)
|
||||
public object? GetApi(string uniqueID)
|
||||
{
|
||||
// validate ready
|
||||
if (!this.Registry.AreAllModsInitialized)
|
||||
|
@ -69,17 +67,18 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
}
|
||||
|
||||
// get raw API
|
||||
IModMetadata mod = this.Registry.Get(uniqueID);
|
||||
IModMetadata? mod = this.Registry.Get(uniqueID);
|
||||
if (mod?.Api != null && this.AccessedModApis.Add(mod.Manifest.UniqueID))
|
||||
this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}.");
|
||||
return mod?.Api;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TInterface GetApi<TInterface>(string uniqueID) where TInterface : class
|
||||
public TInterface? GetApi<TInterface>(string uniqueID)
|
||||
where TInterface : class
|
||||
{
|
||||
// get raw API
|
||||
object api = this.GetApi(uniqueID);
|
||||
object? api = this.GetApi(uniqueID);
|
||||
if (api == null)
|
||||
return null;
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using StardewModdingAPI.Framework.Networking;
|
||||
using StardewValley;
|
||||
|
@ -41,9 +39,9 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMultiplayerPeer GetConnectedPlayer(long id)
|
||||
public IMultiplayerPeer? GetConnectedPlayer(long id)
|
||||
{
|
||||
return this.Multiplayer.Peers.TryGetValue(id, out MultiplayerPeer peer)
|
||||
return this.Multiplayer.Peers.TryGetValue(id, out MultiplayerPeer? peer)
|
||||
? peer
|
||||
: null;
|
||||
}
|
||||
|
@ -55,7 +53,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendMessage<TMessage>(TMessage message, string messageType, string[] modIDs = null, long[] playerIDs = null)
|
||||
public void SendMessage<TMessage>(TMessage message, string messageType, string[]? modIDs = null, long[]? playerIDs = null)
|
||||
{
|
||||
this.Multiplayer.BroadcastModMessage(
|
||||
message: message,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
|
@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
{
|
||||
return this.AssertAccessAllowed(
|
||||
this.Reflector.GetField<TValue>(obj, name, required)
|
||||
);
|
||||
)!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -47,7 +45,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
{
|
||||
return this.AssertAccessAllowed(
|
||||
this.Reflector.GetField<TValue>(type, name, required)
|
||||
);
|
||||
)!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -55,7 +53,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
{
|
||||
return this.AssertAccessAllowed(
|
||||
this.Reflector.GetProperty<TValue>(obj, name, required)
|
||||
);
|
||||
)!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -63,7 +61,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
{
|
||||
return this.AssertAccessAllowed(
|
||||
this.Reflector.GetProperty<TValue>(type, name, required)
|
||||
);
|
||||
)!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -71,7 +69,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
{
|
||||
return this.AssertAccessAllowed(
|
||||
this.Reflector.GetMethod(obj, name, required)
|
||||
);
|
||||
)!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -79,7 +77,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
{
|
||||
return this.AssertAccessAllowed(
|
||||
this.Reflector.GetMethod(type, name, required)
|
||||
);
|
||||
)!;
|
||||
}
|
||||
|
||||
|
||||
|
@ -90,7 +88,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
/// <typeparam name="T">The field value type.</typeparam>
|
||||
/// <param name="field">The field being accessed.</param>
|
||||
/// <returns>Returns the same field instance for convenience.</returns>
|
||||
private IReflectedField<T> AssertAccessAllowed<T>(IReflectedField<T> field)
|
||||
private IReflectedField<T>? AssertAccessAllowed<T>(IReflectedField<T>? field)
|
||||
{
|
||||
this.AssertAccessAllowed(field?.FieldInfo);
|
||||
return field;
|
||||
|
@ -100,7 +98,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
/// <typeparam name="T">The property value type.</typeparam>
|
||||
/// <param name="property">The property being accessed.</param>
|
||||
/// <returns>Returns the same property instance for convenience.</returns>
|
||||
private IReflectedProperty<T> AssertAccessAllowed<T>(IReflectedProperty<T> property)
|
||||
private IReflectedProperty<T>? AssertAccessAllowed<T>(IReflectedProperty<T>? property)
|
||||
{
|
||||
this.AssertAccessAllowed(property?.PropertyInfo.GetMethod?.GetBaseDefinition());
|
||||
this.AssertAccessAllowed(property?.PropertyInfo.SetMethod?.GetBaseDefinition());
|
||||
|
@ -110,7 +108,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
/// <summary>Assert that mods can use the reflection helper to access the given member.</summary>
|
||||
/// <param name="method">The method being accessed.</param>
|
||||
/// <returns>Returns the same method instance for convenience.</returns>
|
||||
private IReflectedMethod AssertAccessAllowed(IReflectedMethod method)
|
||||
private IReflectedMethod? AssertAccessAllowed(IReflectedMethod? method)
|
||||
{
|
||||
this.AssertAccessAllowed(method?.MethodInfo.GetBaseDefinition());
|
||||
return method;
|
||||
|
@ -118,18 +116,18 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
|
||||
/// <summary>Assert that mods can use the reflection helper to access the given member.</summary>
|
||||
/// <param name="member">The member being accessed.</param>
|
||||
private void AssertAccessAllowed(MemberInfo member)
|
||||
private void AssertAccessAllowed(MemberInfo? member)
|
||||
{
|
||||
if (member == null)
|
||||
return;
|
||||
|
||||
// get type which defines the member
|
||||
Type declaringType = member.DeclaringType;
|
||||
Type? declaringType = member.DeclaringType;
|
||||
if (declaringType == null)
|
||||
throw new InvalidOperationException($"Can't validate access to {member.MemberType} {member.Name} because it has no declaring type."); // should never happen
|
||||
|
||||
// validate access
|
||||
string rootNamespace = typeof(Program).Namespace;
|
||||
string? rootNamespace = typeof(Program).Namespace;
|
||||
if (declaringType.Namespace == rootNamespace || declaringType.Namespace?.StartsWith(rootNamespace + ".") == true)
|
||||
throw new InvalidOperationException($"SMAPI blocked access by {this.ModName} to its internals through the reflection API. Accessing the SMAPI internals is strongly discouraged since they're subject to change, which means the mod can break without warning. (Detected access to {declaringType.FullName}.{member.Name}.)");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Mono.Cecil;
|
||||
|
||||
|
@ -38,6 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
|
||||
/// <summary>Resolve an assembly reference.</summary>
|
||||
/// <param name="name">The assembly name.</param>
|
||||
/// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception>
|
||||
public override AssemblyDefinition Resolve(AssemblyNameReference name)
|
||||
{
|
||||
return this.ResolveName(name.Name) ?? base.Resolve(name);
|
||||
|
@ -46,6 +45,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
/// <summary>Resolve an assembly reference.</summary>
|
||||
/// <param name="name">The assembly name.</param>
|
||||
/// <param name="parameters">The assembly reader parameters.</param>
|
||||
/// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception>
|
||||
public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
|
||||
{
|
||||
return this.ResolveName(name.Name) ?? base.Resolve(name, parameters);
|
||||
|
@ -57,9 +57,9 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
*********/
|
||||
/// <summary>Resolve a known assembly definition based on its short or full name.</summary>
|
||||
/// <param name="name">The assembly's short or full name.</param>
|
||||
private AssemblyDefinition ResolveName(string name)
|
||||
private AssemblyDefinition? ResolveName(string name)
|
||||
{
|
||||
return this.Lookup.TryGetValue(name, out AssemblyDefinition match)
|
||||
return this.Lookup.TryGetValue(name, out AssemblyDefinition? match)
|
||||
? match
|
||||
: null;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
|
@ -57,7 +55,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
{
|
||||
if (this.MethodNames.Any())
|
||||
{
|
||||
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
if (methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName && this.MethodNames.Contains(methodRef.Name))
|
||||
{
|
||||
string eventName = methodRef.Name.Split(new[] { '_' }, 2)[1];
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
|
@ -51,7 +49,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
{
|
||||
if (this.FieldNames.Any())
|
||||
{
|
||||
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
if (fieldRef != null && fieldRef.DeclaringType.FullName == this.FullTypeName && this.FieldNames.Contains(fieldRef.Name))
|
||||
{
|
||||
this.FieldNames.Remove(fieldRef.Name);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using StardewModdingAPI.Framework.ModLoading.Framework;
|
||||
|
@ -54,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
/// <param name="instruction">The IL instruction.</param>
|
||||
protected bool IsMatch(Instruction instruction)
|
||||
{
|
||||
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
return
|
||||
methodRef != null
|
||||
&& methodRef.DeclaringType.FullName == this.FullTypeName
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using StardewModdingAPI.Framework.ModLoading.Framework;
|
||||
|
@ -54,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
/// <param name="instruction">The IL instruction.</param>
|
||||
protected bool IsMatch(Instruction instruction)
|
||||
{
|
||||
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
return
|
||||
methodRef != null
|
||||
&& methodRef.DeclaringType.FullName == this.FullTypeName
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
|
@ -34,11 +33,11 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
|
||||
{
|
||||
// field reference
|
||||
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
if (fieldRef != null && this.ShouldValidate(fieldRef.DeclaringType))
|
||||
{
|
||||
// get target field
|
||||
FieldDefinition targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name);
|
||||
FieldDefinition? targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name);
|
||||
if (targetField == null)
|
||||
return false;
|
||||
|
||||
|
@ -51,16 +50,16 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
}
|
||||
|
||||
// method reference
|
||||
MethodReference methodReference = RewriteHelper.AsMethodReference(instruction);
|
||||
MethodReference? methodReference = RewriteHelper.AsMethodReference(instruction);
|
||||
if (methodReference != null && !this.IsUnsupported(methodReference) && this.ShouldValidate(methodReference.DeclaringType))
|
||||
{
|
||||
// get potential targets
|
||||
MethodDefinition[] candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray();
|
||||
MethodDefinition[]? candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray();
|
||||
if (candidateMethods == null || !candidateMethods.Any())
|
||||
return false;
|
||||
|
||||
// compare return types
|
||||
MethodDefinition methodDef = methodReference.Resolve();
|
||||
MethodDefinition? methodDef = methodReference.Resolve();
|
||||
if (methodDef == null)
|
||||
return false; // validated by ReferenceToMissingMemberFinder
|
||||
|
||||
|
@ -80,7 +79,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
*********/
|
||||
/// <summary>Whether references to the given type should be validated.</summary>
|
||||
/// <param name="type">The type reference.</param>
|
||||
private bool ShouldValidate(TypeReference type)
|
||||
private bool ShouldValidate([NotNullWhen(true)] TypeReference? type)
|
||||
{
|
||||
return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using StardewModdingAPI.Framework.ModLoading.Framework;
|
||||
|
@ -33,10 +32,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
|
||||
{
|
||||
// field reference
|
||||
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
if (fieldRef != null && this.ShouldValidate(fieldRef.DeclaringType))
|
||||
{
|
||||
FieldDefinition target = fieldRef.Resolve();
|
||||
FieldDefinition? target = fieldRef.Resolve();
|
||||
if (target == null || target.HasConstant)
|
||||
{
|
||||
this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (no such field)");
|
||||
|
@ -45,10 +44,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
}
|
||||
|
||||
// method reference
|
||||
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
if (methodRef != null && this.ShouldValidate(methodRef.DeclaringType) && !this.IsUnsupported(methodRef))
|
||||
{
|
||||
MethodDefinition target = methodRef.Resolve();
|
||||
MethodDefinition? target = methodRef.Resolve();
|
||||
if (target == null)
|
||||
{
|
||||
string phrase;
|
||||
|
@ -73,7 +72,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
*********/
|
||||
/// <summary>Whether references to the given type should be validated.</summary>
|
||||
/// <param name="type">The type reference.</param>
|
||||
private bool ShouldValidate(TypeReference type)
|
||||
private bool ShouldValidate([NotNullWhen(true)] TypeReference? type)
|
||||
{
|
||||
return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Mono.Cecil;
|
||||
using StardewModdingAPI.Framework.ModLoading.Framework;
|
||||
|
@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
private readonly InstructionHandleResult Result;
|
||||
|
||||
/// <summary>Get whether a matched type should be ignored.</summary>
|
||||
private readonly Func<TypeReference, bool> ShouldIgnore;
|
||||
private readonly Func<TypeReference, bool>? ShouldIgnore;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
/// <param name="assemblyName">The full assembly name to which to find references.</param>
|
||||
/// <param name="result">The result to return for matching instructions.</param>
|
||||
/// <param name="shouldIgnore">Get whether a matched type should be ignored.</param>
|
||||
public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null)
|
||||
public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func<TypeReference, bool>? shouldIgnore = null)
|
||||
: base(defaultPhrase: $"{assemblyName} assembly")
|
||||
{
|
||||
this.AssemblyName = assemblyName;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Mono.Cecil;
|
||||
|
@ -20,7 +18,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
private readonly InstructionHandleResult Result;
|
||||
|
||||
/// <summary>Get whether a matched type should be ignored.</summary>
|
||||
private readonly Func<TypeReference, bool> ShouldIgnore;
|
||||
private readonly Func<TypeReference, bool>? ShouldIgnore;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -30,7 +28,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
/// <param name="fullTypeNames">The full type names to match.</param>
|
||||
/// <param name="result">The result to return for matching instructions.</param>
|
||||
/// <param name="shouldIgnore">Get whether a matched type should be ignored.</param>
|
||||
public TypeFinder(string[] fullTypeNames, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null)
|
||||
public TypeFinder(string[] fullTypeNames, InstructionHandleResult result, Func<TypeReference, bool>? shouldIgnore = null)
|
||||
: base(defaultPhrase: $"{string.Join(", ", fullTypeNames)} type{(fullTypeNames.Length != 1 ? "s" : "")}") // default phrase should never be used
|
||||
{
|
||||
this.FullTypeNames = new HashSet<string>(fullTypeNames);
|
||||
|
@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
/// <param name="fullTypeName">The full type name to match.</param>
|
||||
/// <param name="result">The result to return for matching instructions.</param>
|
||||
/// <param name="shouldIgnore">Get whether a matched type should be ignored.</param>
|
||||
public TypeFinder(string fullTypeName, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null)
|
||||
public TypeFinder(string fullTypeName, InstructionHandleResult result, Func<TypeReference, bool>? shouldIgnore = null)
|
||||
: this(new[] { fullTypeName }, result, shouldIgnore) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Mono.Cecil;
|
||||
|
@ -59,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
/// <param name="flag">The result flag to set.</param>
|
||||
/// <param name="resultMessage">The result message to add.</param>
|
||||
/// <returns>Returns true for convenience.</returns>
|
||||
protected bool MarkFlag(InstructionHandleResult flag, string resultMessage = null)
|
||||
protected bool MarkFlag(InstructionHandleResult flag, string? resultMessage = null)
|
||||
{
|
||||
this.Flags.Add(flag);
|
||||
if (resultMessage != null)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
|
@ -10,6 +9,7 @@ using Mono.Collections.Generic;
|
|||
namespace StardewModdingAPI.Framework.ModLoading.Framework
|
||||
{
|
||||
/// <summary>Handles recursively rewriting loaded assembly code.</summary>
|
||||
[SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "Rewrite callbacks are invoked immediately.")]
|
||||
internal class RecursiveRewriter
|
||||
{
|
||||
/*********
|
||||
|
@ -77,7 +77,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
{
|
||||
changed |= this.RewriteModuleImpl(this.Module);
|
||||
|
||||
foreach (var type in types)
|
||||
foreach (TypeDefinition type in types)
|
||||
changed |= this.RewriteTypeDefinition(type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -129,9 +129,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
ILProcessor cil = method.Body.GetILProcessor();
|
||||
Collection<Instruction> instructions = cil.Body.Instructions;
|
||||
bool addedInstructions = false;
|
||||
// ReSharper disable once ForCanBeConvertedToForeach -- deliberate to allow changing the collection
|
||||
for (int i = 0; i < instructions.Count; i++)
|
||||
{
|
||||
var instruction = instructions[i];
|
||||
Instruction instruction = instructions[i];
|
||||
if (instruction.OpCode.Code == Code.Nop)
|
||||
continue;
|
||||
|
||||
|
@ -174,7 +175,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
bool rewritten = false;
|
||||
|
||||
// field reference
|
||||
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
if (fieldRef != null)
|
||||
{
|
||||
rewritten |= this.RewriteTypeReference(fieldRef.DeclaringType, newType => fieldRef.DeclaringType = newType);
|
||||
|
@ -182,7 +183,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
}
|
||||
|
||||
// method reference
|
||||
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
if (methodRef != null)
|
||||
this.RewriteMethodReference(methodRef);
|
||||
|
||||
|
@ -212,7 +213,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
});
|
||||
rewritten |= this.RewriteTypeReference(methodRef.ReturnType, newType => methodRef.ReturnType = newType);
|
||||
|
||||
foreach (var parameter in methodRef.Parameters)
|
||||
foreach (ParameterDefinition parameter in methodRef.Parameters)
|
||||
rewritten |= this.RewriteTypeReference(parameter.ParameterType, newType => parameter.ParameterType = newType);
|
||||
|
||||
if (methodRef is GenericInstanceMethod genericRef)
|
||||
|
@ -264,7 +265,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
bool curChanged = false;
|
||||
|
||||
// attribute type
|
||||
TypeReference newAttrType = null;
|
||||
TypeReference? newAttrType = null;
|
||||
rewritten |= this.RewriteTypeReference(attribute.AttributeType, newType =>
|
||||
{
|
||||
newAttrType = newType;
|
||||
|
@ -289,9 +290,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
if (curChanged)
|
||||
{
|
||||
// get constructor
|
||||
MethodDefinition constructor = (newAttrType ?? attribute.AttributeType)
|
||||
MethodDefinition? constructor = (newAttrType ?? attribute.AttributeType)
|
||||
.Resolve()
|
||||
.Methods
|
||||
?.Methods
|
||||
.Where(method => method.IsConstructor)
|
||||
.FirstOrDefault(ctor => RewriteHelper.HasMatchingSignature(ctor, attribute.Constructor));
|
||||
if (constructor == null)
|
||||
|
@ -301,9 +302,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
var newAttr = new CustomAttribute(this.Module.ImportReference(constructor));
|
||||
for (int i = 0; i < argTypes.Length; i++)
|
||||
newAttr.ConstructorArguments.Add(new CustomAttributeArgument(argTypes[i], attribute.ConstructorArguments[i].Value));
|
||||
foreach (var prop in attribute.Properties)
|
||||
foreach (CustomAttributeNamedArgument prop in attribute.Properties)
|
||||
newAttr.Properties.Add(new CustomAttributeNamedArgument(prop.Name, prop.Argument));
|
||||
foreach (var field in attribute.Fields)
|
||||
foreach (CustomAttributeNamedArgument field in attribute.Fields)
|
||||
newAttr.Fields.Add(new CustomAttributeNamedArgument(field.Name, field.Argument));
|
||||
|
||||
// swap attribute
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
@ -23,7 +21,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
*********/
|
||||
/// <summary>Get the field reference from an instruction if it matches.</summary>
|
||||
/// <param name="instruction">The IL instruction.</param>
|
||||
public static FieldReference AsFieldReference(Instruction 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
|
||||
|
@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
|
||||
/// <summary>Get the method reference from an instruction if it matches.</summary>
|
||||
/// <param name="instruction">The IL instruction.</param>
|
||||
public static MethodReference AsMethodReference(Instruction instruction)
|
||||
public static MethodReference? AsMethodReference(Instruction instruction)
|
||||
{
|
||||
return instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt || instruction.OpCode == OpCodes.Newobj
|
||||
? (MethodReference)instruction.Operand
|
||||
|
@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
/// <summary>Get the CIL instruction to load a value onto the stack.</summary>
|
||||
/// <param name="rawValue">The constant value to inject.</param>
|
||||
/// <returns>Returns the instruction, or <c>null</c> if the value type isn't supported.</returns>
|
||||
public static Instruction GetLoadValueInstruction(object rawValue)
|
||||
public static Instruction? GetLoadValueInstruction(object? rawValue)
|
||||
{
|
||||
return rawValue switch
|
||||
{
|
||||
|
@ -151,7 +149,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
|||
/// <param name="typeA">The type ID to compare.</param>
|
||||
/// <param name="typeB">The other type ID to compare.</param>
|
||||
/// <returns>true if the type IDs look like the same type, false if not.</returns>
|
||||
public static bool LooksLikeSameType(TypeReference typeA, TypeReference typeB)
|
||||
public static bool LooksLikeSameType(TypeReference? typeA, TypeReference? typeB)
|
||||
{
|
||||
return RewriteHelper.TypeDefinitionComparer.Equals(typeA, typeB);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
|
||||
namespace StardewModdingAPI.Framework.ModLoading
|
||||
|
@ -10,7 +8,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="message">The error message.</param>
|
||||
/// <param name="ex">The underlying exception, if any.</param>
|
||||
public InvalidModStateException(string message, Exception ex = null)
|
||||
public InvalidModStateException(string message, Exception? ex = null)
|
||||
: base(message, ex) { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Framework.ModHelpers;
|
||||
|
@ -44,7 +43,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
public IManifest Manifest { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ModDataRecordVersionedFields DataRecord { get; }
|
||||
public ModDataRecordVersionedFields? DataRecord { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ModMetadataStatus Status { get; private set; }
|
||||
|
@ -56,33 +55,35 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
public ModWarning Warnings => this.ActualWarnings & ~(this.DataRecord?.DataRecord.SuppressWarnings ?? ModWarning.None);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Error { get; private set; }
|
||||
public string? Error { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ErrorDetails { get; private set; }
|
||||
public string? ErrorDetails { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsIgnored { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMod Mod { get; private set; }
|
||||
public IMod? Mod { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IContentPack ContentPack { get; private set; }
|
||||
public IContentPack? ContentPack { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public TranslationHelper Translations { get; private set; }
|
||||
public TranslationHelper? Translations { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMonitor Monitor { get; private set; }
|
||||
public IMonitor? Monitor { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Api { get; private set; }
|
||||
public object? Api { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ModEntryModel UpdateCheckData { get; private set; }
|
||||
public ModEntryModel? UpdateCheckData { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[MemberNotNullWhen(true, nameof(ModMetadata.ContentPack))]
|
||||
[SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The manifest may be null for broken mods while loading.")]
|
||||
public bool IsContentPack => this.Manifest?.ContentPackFor != null;
|
||||
|
||||
/// <summary>The fake content packs created by this mod, if any.</summary>
|
||||
|
@ -99,13 +100,13 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
/// <param name="manifest">The mod manifest.</param>
|
||||
/// <param name="dataRecord">Metadata about the mod from SMAPI's internal data (if any).</param>
|
||||
/// <param name="isIgnored">Whether the mod folder should be ignored. This should be <c>true</c> if it was found within a folder whose name starts with a dot.</param>
|
||||
public ModMetadata(string displayName, string directoryPath, string rootPath, IManifest manifest, ModDataRecordVersionedFields dataRecord, bool isIgnored)
|
||||
public ModMetadata(string displayName, string directoryPath, string rootPath, IManifest? manifest, ModDataRecordVersionedFields? dataRecord, bool isIgnored)
|
||||
{
|
||||
this.DisplayName = displayName;
|
||||
this.DirectoryPath = directoryPath;
|
||||
this.RootPath = rootPath;
|
||||
this.RelativeDirectoryPath = PathUtilities.GetRelativePath(this.RootPath, this.DirectoryPath);
|
||||
this.Manifest = manifest;
|
||||
this.Manifest = manifest!; // manifest may be null in low-level SMAPI code, but won't be null once it's received by mods via IModInfo
|
||||
this.DataRecord = dataRecord;
|
||||
this.IsIgnored = isIgnored;
|
||||
|
||||
|
@ -121,7 +122,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string error, string errorDetails = null)
|
||||
public IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string? error, string? errorDetails = null)
|
||||
{
|
||||
this.Status = status;
|
||||
this.FailReason = reason;
|
||||
|
@ -162,7 +163,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IModMetadata SetApi(object api)
|
||||
public IModMetadata SetApi(object? api)
|
||||
{
|
||||
this.Api = api;
|
||||
return this;
|
||||
|
@ -176,6 +177,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MemberNotNullWhen(true, nameof(IModInfo.Manifest))]
|
||||
public bool HasManifest()
|
||||
{
|
||||
return this.Manifest != null;
|
||||
|
@ -190,7 +192,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasID(string id)
|
||||
public bool HasID(string? id)
|
||||
{
|
||||
return
|
||||
this.HasID()
|
||||
|
@ -245,7 +247,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
/// <inheritdoc />
|
||||
public string GetRelativePathWithRoot()
|
||||
{
|
||||
string rootFolderName = Path.GetFileName(this.RootPath) ?? "";
|
||||
string rootFolderName = Path.GetFileName(this.RootPath);
|
||||
return Path.Combine(rootFolderName, this.RelativeDirectoryPath);
|
||||
}
|
||||
|
||||
|
@ -254,7 +256,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
{
|
||||
foreach (var reference in this.FakeContentPacks.ToArray())
|
||||
{
|
||||
if (!reference.TryGetTarget(out ContentPack pack))
|
||||
if (!reference.TryGetTarget(out ContentPack? pack))
|
||||
{
|
||||
this.FakeContentPacks.Remove(reference);
|
||||
continue;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
public static ConstructorInfo DeclaredConstructor(Type type, Type[] parameters = null)
|
||||
public static ConstructorInfo DeclaredConstructor(Type type, Type[]? parameters = null)
|
||||
{
|
||||
// Harmony 1.x matched both static and instance constructors
|
||||
return
|
||||
|
@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
?? AccessTools.DeclaredConstructor(type, parameters, searchForStatic: true);
|
||||
}
|
||||
|
||||
public static ConstructorInfo Constructor(Type type, Type[] parameters = null)
|
||||
public static ConstructorInfo Constructor(Type type, Type[]? parameters = null)
|
||||
{
|
||||
// Harmony 1.x matched both static and instance constructors
|
||||
return
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -30,7 +28,8 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
return new Harmony(id);
|
||||
}
|
||||
|
||||
public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null)
|
||||
[SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "If the user passes a null original method, we let it fail in the underlying Harmony instance instead of handling it here.")]
|
||||
public DynamicMethod Patch(MethodBase original, HarmonyMethod? prefix = null, HarmonyMethod? postfix = null, HarmonyMethod? transpiler = null)
|
||||
{
|
||||
// In Harmony 1.x you could target a virtual method that's not implemented by the
|
||||
// target type, but in Harmony 2.0 you need to target the concrete implementation.
|
||||
|
@ -60,7 +59,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
/// <param name="prefix">The prefix method, if any.</param>
|
||||
/// <param name="postfix">The postfix method, if any.</param>
|
||||
/// <param name="transpiler">The transpiler method, if any.</param>
|
||||
private string GetPatchTypesLabel(HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null)
|
||||
private string GetPatchTypesLabel(HarmonyMethod? prefix = null, HarmonyMethod? postfix = null, HarmonyMethod? transpiler = null)
|
||||
{
|
||||
var patchTypes = new List<string>();
|
||||
|
||||
|
@ -76,7 +75,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
|
||||
/// <summary>Get a human-readable label for the method being patched.</summary>
|
||||
/// <param name="method">The method being patched.</param>
|
||||
private string GetMethodLabel(MethodBase method)
|
||||
private string GetMethodLabel(MethodBase? method)
|
||||
{
|
||||
return method != null
|
||||
? $"method {method.DeclaringType?.FullName}.{method.Name}"
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
|
@ -23,7 +21,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
this.ImportMethodImpl(method);
|
||||
}
|
||||
|
||||
public HarmonyMethodFacade(Type type, string name, Type[] parameters = null)
|
||||
public HarmonyMethodFacade(Type type, string name, Type[]? parameters = null)
|
||||
{
|
||||
this.ImportMethodImpl(AccessTools.Method(type, name, parameters));
|
||||
}
|
||||
|
@ -40,7 +38,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
// internal code still handles null fine. For backwards compatibility, this bypasses
|
||||
// the new restriction when the mod hasn't been updated for Harmony 2.0 yet.
|
||||
|
||||
MethodInfo importMethod = typeof(HarmonyMethod).GetMethod("ImportMethod", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
MethodInfo? importMethod = typeof(HarmonyMethod).GetMethod("ImportMethod", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (importMethod == null)
|
||||
throw new InvalidOperationException("Can't find 'HarmonyMethod.ImportMethod' method");
|
||||
importMethod.Invoke(this, new object[] { methodInfo });
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using HarmonyLib;
|
||||
using Mono.Cecil;
|
||||
|
@ -59,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
if (this.ShouldRewrite)
|
||||
{
|
||||
// rewrite Harmony 1.x methods to Harmony 2.0
|
||||
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
if (this.TryRewriteMethodsToFacade(module, methodRef))
|
||||
{
|
||||
this.OnChanged();
|
||||
|
@ -67,7 +65,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
}
|
||||
|
||||
// rewrite renamed fields
|
||||
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
if (fieldRef != null)
|
||||
{
|
||||
if (fieldRef.DeclaringType.FullName == "HarmonyLib.HarmonyMethod" && fieldRef.Name == "prioritiy")
|
||||
|
@ -95,13 +93,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
/// <summary>Rewrite methods to use Harmony facades if needed.</summary>
|
||||
/// <param name="module">The assembly module containing the method reference.</param>
|
||||
/// <param name="methodRef">The method reference to map.</param>
|
||||
private bool TryRewriteMethodsToFacade(ModuleDefinition module, MethodReference methodRef)
|
||||
private bool TryRewriteMethodsToFacade(ModuleDefinition module, MethodReference? methodRef)
|
||||
{
|
||||
if (!this.ReplacedTypes)
|
||||
return false; // not Harmony (or already using Harmony 2.0)
|
||||
|
||||
// get facade type
|
||||
Type toType = methodRef?.DeclaringType.FullName switch
|
||||
Type? toType = methodRef?.DeclaringType.FullName switch
|
||||
{
|
||||
"HarmonyLib.Harmony" => typeof(HarmonyInstanceFacade),
|
||||
"HarmonyLib.AccessTools" => typeof(AccessToolsFacade),
|
||||
|
@ -112,9 +110,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
return false;
|
||||
|
||||
// map if there's a matching method
|
||||
if (RewriteHelper.HasMatchingSignature(toType, methodRef))
|
||||
if (RewriteHelper.HasMatchingSignature(toType, methodRef!))
|
||||
{
|
||||
methodRef.DeclaringType = module.ImportReference(toType);
|
||||
methodRef!.DeclaringType = module.ImportReference(toType);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -139,7 +137,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
{
|
||||
string fullName = type.FullName.Replace("Harmony.", "HarmonyLib.");
|
||||
string targetName = typeof(Harmony).AssemblyQualifiedName!.Replace(typeof(Harmony).FullName!, fullName);
|
||||
return Type.GetType(targetName, throwOnError: true);
|
||||
return Type.GetType(targetName, throwOnError: true)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
|
@ -33,17 +32,17 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
|
||||
{
|
||||
// get field ref
|
||||
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction);
|
||||
if (fieldRef == null || !this.ShouldValidate(fieldRef.DeclaringType))
|
||||
return false;
|
||||
|
||||
// skip if not broken
|
||||
FieldDefinition fieldDefinition = fieldRef.Resolve();
|
||||
FieldDefinition? fieldDefinition = fieldRef.Resolve();
|
||||
if (fieldDefinition?.HasConstant == false)
|
||||
return false;
|
||||
|
||||
// rewrite if possible
|
||||
TypeDefinition declaringType = fieldRef.DeclaringType.Resolve();
|
||||
TypeDefinition? declaringType = fieldRef.DeclaringType.Resolve();
|
||||
bool isRead = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld;
|
||||
return
|
||||
this.TryRewriteToProperty(module, instruction, fieldRef, declaringType, isRead)
|
||||
|
@ -56,7 +55,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
*********/
|
||||
/// <summary>Whether references to the given type should be validated.</summary>
|
||||
/// <param name="type">The type reference.</param>
|
||||
private bool ShouldValidate(TypeReference type)
|
||||
private bool ShouldValidate([NotNullWhen(true)] TypeReference? type)
|
||||
{
|
||||
return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name);
|
||||
}
|
||||
|
@ -70,8 +69,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
private bool TryRewriteToProperty(ModuleDefinition module, Instruction instruction, FieldReference fieldRef, TypeDefinition declaringType, bool isRead)
|
||||
{
|
||||
// get equivalent property
|
||||
PropertyDefinition property = declaringType?.Properties.FirstOrDefault(p => p.Name == fieldRef.Name);
|
||||
MethodDefinition method = isRead ? property?.GetMethod : property?.SetMethod;
|
||||
PropertyDefinition? property = declaringType?.Properties.FirstOrDefault(p => p.Name == fieldRef.Name);
|
||||
MethodDefinition? method = isRead ? property?.GetMethod : property?.SetMethod;
|
||||
if (method == null)
|
||||
return false;
|
||||
|
||||
|
@ -86,14 +85,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
/// <summary>Try rewriting the field into a matching const field.</summary>
|
||||
/// <param name="instruction">The CIL instruction to rewrite.</param>
|
||||
/// <param name="field">The field definition.</param>
|
||||
private bool TryRewriteToConstField(Instruction instruction, FieldDefinition field)
|
||||
private bool TryRewriteToConstField(Instruction instruction, FieldDefinition? field)
|
||||
{
|
||||
// must have been a static field read, and the new field must be const
|
||||
if (instruction.OpCode != OpCodes.Ldsfld || field?.HasConstant != true)
|
||||
return false;
|
||||
|
||||
// get opcode for value type
|
||||
Instruction loadInstruction = RewriteHelper.GetLoadValueInstruction(field.Constant);
|
||||
Instruction? loadInstruction = RewriteHelper.GetLoadValueInstruction(field.Constant);
|
||||
if (loadInstruction == null)
|
||||
return false;
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
|
@ -33,7 +32,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
|
||||
{
|
||||
// get method ref
|
||||
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
if (methodRef == null || !this.ShouldValidate(methodRef.DeclaringType))
|
||||
return false;
|
||||
|
||||
|
@ -42,13 +41,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
return false;
|
||||
|
||||
// get type
|
||||
var type = methodRef.DeclaringType.Resolve();
|
||||
TypeDefinition? type = methodRef.DeclaringType.Resolve();
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
// get method definition
|
||||
MethodDefinition method = null;
|
||||
foreach (var match in type.Methods.Where(p => p.Name == methodRef.Name))
|
||||
MethodDefinition? method = null;
|
||||
foreach (MethodDefinition match in type.Methods.Where(p => p.Name == methodRef.Name))
|
||||
{
|
||||
// reference matches initial parameters of definition
|
||||
if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match))
|
||||
|
@ -72,7 +71,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
return false; // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized
|
||||
|
||||
// rewrite method reference
|
||||
foreach (Instruction loadInstruction in loadInstructions)
|
||||
foreach (Instruction? loadInstruction in loadInstructions)
|
||||
cil.InsertBefore(instruction, loadInstruction);
|
||||
instruction.Operand = module.ImportReference(method);
|
||||
|
||||
|
@ -86,7 +85,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
*********/
|
||||
/// <summary>Whether references to the given type should be validated.</summary>
|
||||
/// <param name="type">The type reference.</param>
|
||||
private bool ShouldValidate(TypeReference type)
|
||||
private bool ShouldValidate([NotNullWhen(true)] TypeReference? type)
|
||||
{
|
||||
return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
|
@ -28,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
/// <param name="fromType">The type whose methods to remap.</param>
|
||||
/// <param name="toType">The type with methods to map to.</param>
|
||||
/// <param name="nounPhrase">A brief noun phrase indicating what the instruction finder matches (or <c>null</c> to generate one).</param>
|
||||
public MethodParentRewriter(string fromType, Type toType, string nounPhrase = null)
|
||||
public MethodParentRewriter(string fromType, Type toType, string? nounPhrase = null)
|
||||
: base(nounPhrase ?? $"{fromType.Split('.').Last()} methods")
|
||||
{
|
||||
this.FromType = fromType;
|
||||
|
@ -39,14 +38,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
/// <param name="fromType">The type whose methods to remap.</param>
|
||||
/// <param name="toType">The type with methods to map to.</param>
|
||||
/// <param name="nounPhrase">A brief noun phrase indicating what the instruction finder matches (or <c>null</c> to generate one).</param>
|
||||
public MethodParentRewriter(Type fromType, Type toType, string nounPhrase = null)
|
||||
: this(fromType.FullName, toType, nounPhrase) { }
|
||||
public MethodParentRewriter(Type fromType, Type toType, string? nounPhrase = null)
|
||||
: this(fromType.FullName!, toType, nounPhrase) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
|
||||
{
|
||||
// get method ref
|
||||
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction);
|
||||
if (!this.IsMatch(methodRef))
|
||||
return false;
|
||||
|
||||
|
@ -61,7 +60,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
*********/
|
||||
/// <summary>Get whether a CIL instruction matches.</summary>
|
||||
/// <param name="methodRef">The method reference.</param>
|
||||
private bool IsMatch(MethodReference methodRef)
|
||||
private bool IsMatch([NotNullWhen(true)] MethodReference? methodRef)
|
||||
{
|
||||
return
|
||||
methodRef != null
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Mono.Cecil;
|
||||
using StardewModdingAPI.Framework.ModLoading.Framework;
|
||||
|
@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
private readonly Type ToType;
|
||||
|
||||
/// <summary>Get whether a matched type should be ignored.</summary>
|
||||
private readonly Func<TypeReference, bool> ShouldIgnore;
|
||||
private readonly Func<TypeReference, bool>? ShouldIgnore;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
/// <param name="fromTypeFullName">The full type name to which to find references.</param>
|
||||
/// <param name="toType">The new type to reference.</param>
|
||||
/// <param name="shouldIgnore">Get whether a matched type should be ignored.</param>
|
||||
public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func<TypeReference, bool> shouldIgnore = null)
|
||||
public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func<TypeReference, bool>? shouldIgnore = null)
|
||||
: base($"{fromTypeFullName} type")
|
||||
{
|
||||
this.FromTypeName = fromTypeFullName;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -38,7 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Symbols
|
|||
/// <param name="fileName">The assembly file name.</param>
|
||||
public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName)
|
||||
{
|
||||
return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream symbolData)
|
||||
return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream? symbolData)
|
||||
? new SymbolReader(module, symbolData)
|
||||
: this.BaseProvider.GetSymbolReader(module, fileName);
|
||||
}
|
||||
|
@ -48,7 +46,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Symbols
|
|||
/// <param name="symbolStream">The loaded symbol file stream.</param>
|
||||
public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream)
|
||||
{
|
||||
return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream symbolData)
|
||||
return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream? symbolData)
|
||||
? new SymbolReader(module, symbolData)
|
||||
: this.BaseProvider.GetSymbolReader(module, symbolStream);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -18,7 +16,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
/// <c>TKey</c> in the above example). If all components are equal after substitution, and the
|
||||
/// tokens can all be mapped to the same generic type, the types are considered equal.
|
||||
/// </remarks>
|
||||
internal class TypeReferenceComparer : IEqualityComparer<TypeReference>
|
||||
internal class TypeReferenceComparer : IEqualityComparer<TypeReference?>
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -26,7 +24,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
/// <summary>Get whether the specified objects are equal.</summary>
|
||||
/// <param name="a">The first object to compare.</param>
|
||||
/// <param name="b">The second object to compare.</param>
|
||||
public bool Equals(TypeReference a, TypeReference b)
|
||||
public bool Equals(TypeReference? a, TypeReference? b)
|
||||
{
|
||||
if (a == null || b == null)
|
||||
return a == b;
|
||||
|
@ -54,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
/// <param name="typeB">The second type to compare.</param>
|
||||
private bool HeuristicallyEquals(TypeReference typeA, TypeReference typeB)
|
||||
{
|
||||
bool HeuristicallyEquals(string typeNameA, string typeNameB, IDictionary<string, string> tokenMap)
|
||||
bool HeuristicallyEqualsImpl(string typeNameA, string typeNameB, IDictionary<string, string> tokenMap)
|
||||
{
|
||||
// analyze type names
|
||||
bool hasTokensA = typeNameA.Contains("!");
|
||||
|
@ -82,14 +80,14 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
|
||||
for (int i = 0; i < symbolsA.Length; i++)
|
||||
{
|
||||
if (!HeuristicallyEquals(symbolsA[i], symbolsB[i], tokenMap))
|
||||
if (!HeuristicallyEqualsImpl(symbolsA[i], symbolsB[i], tokenMap))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return HeuristicallyEquals(typeA.FullName, typeB.FullName, new Dictionary<string, string>());
|
||||
return HeuristicallyEqualsImpl(typeA.FullName, typeB.FullName, new Dictionary<string, string>());
|
||||
}
|
||||
|
||||
/// <summary>Map a generic type placeholder (like <c>!0</c>) to its actual type.</summary>
|
||||
|
@ -99,7 +97,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
/// <returns>Returns the previously-mapped type if applicable, else the <paramref name="type"/>.</returns>
|
||||
private string MapPlaceholder(string placeholder, string type, IDictionary<string, string> map)
|
||||
{
|
||||
if (map.TryGetValue(placeholder, out string result))
|
||||
if (map.TryGetValue(placeholder, out string? result))
|
||||
return result;
|
||||
|
||||
map[placeholder] = type;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="modAssembly">The mod assembly.</param>
|
||||
public void TrackAssemblies(IModMetadata metadata, Assembly modAssembly)
|
||||
{
|
||||
this.ModNamesByAssembly[modAssembly.FullName] = metadata;
|
||||
this.ModNamesByAssembly[modAssembly.FullName!] = metadata;
|
||||
}
|
||||
|
||||
/// <summary>Get metadata for all loaded mods.</summary>
|
||||
|
@ -62,7 +60,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>Get metadata for a loaded mod.</summary>
|
||||
/// <param name="uniqueID">The mod's unique ID.</param>
|
||||
/// <returns>Returns the matching mod's metadata, or <c>null</c> if not found.</returns>
|
||||
public IModMetadata Get(string uniqueID)
|
||||
public IModMetadata? Get(string uniqueID)
|
||||
{
|
||||
// normalize search ID
|
||||
if (string.IsNullOrWhiteSpace(uniqueID))
|
||||
|
@ -76,14 +74,14 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>Get the mod metadata from one of its assemblies.</summary>
|
||||
/// <param name="type">The type to check.</param>
|
||||
/// <returns>Returns the mod name, or <c>null</c> if the type isn't part of a known mod.</returns>
|
||||
public IModMetadata GetFrom(Type type)
|
||||
public IModMetadata? GetFrom(Type? type)
|
||||
{
|
||||
// null
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
// known type
|
||||
string assemblyName = type.Assembly.FullName;
|
||||
string assemblyName = type.Assembly.FullName!;
|
||||
if (this.ModNamesByAssembly.ContainsKey(assemblyName))
|
||||
return this.ModNamesByAssembly[assemblyName];
|
||||
|
||||
|
@ -93,7 +91,7 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
/// <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>
|
||||
public IModMetadata GetFromStack()
|
||||
public IModMetadata? GetFromStack()
|
||||
{
|
||||
// get stack frames
|
||||
StackTrace stack = new();
|
||||
|
@ -102,8 +100,8 @@ namespace StardewModdingAPI.Framework
|
|||
// search stack for a source assembly
|
||||
foreach (StackFrame frame in frames)
|
||||
{
|
||||
MethodBase method = frame.GetMethod();
|
||||
IModMetadata mod = this.GetFrom(method.ReflectedType);
|
||||
MethodBase? method = frame.GetMethod();
|
||||
IModMetadata? mod = this.GetFrom(method?.ReflectedType);
|
||||
if (mod != null)
|
||||
return mod;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
@ -15,8 +13,8 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
/// <summary>The type that has the field.</summary>
|
||||
private readonly Type ParentType;
|
||||
|
||||
/// <summary>The object that has the instance field (if applicable).</summary>
|
||||
private readonly object Parent;
|
||||
/// <summary>The object that has the instance field, or <c>null</c> for a static field.</summary>
|
||||
private readonly object? Parent;
|
||||
|
||||
/// <summary>The display name shown in error messages.</summary>
|
||||
private string DisplayName => $"{this.ParentType.FullName}::{this.FieldInfo.Name}";
|
||||
|
@ -34,12 +32,12 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="parentType">The type that has the field.</param>
|
||||
/// <param name="obj">The object that has the instance field (if applicable).</param>
|
||||
/// <param name="obj">The object that has the instance field, or <c>null</c> for a static field.</param>
|
||||
/// <param name="field">The reflection metadata.</param>
|
||||
/// <param name="isStatic">Whether the field is static.</param>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="parentType"/> or <paramref name="field"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">The <paramref name="obj"/> is null for a non-static field, or not null for a static field.</exception>
|
||||
public ReflectedField(Type parentType, object obj, FieldInfo field, bool isStatic)
|
||||
public ReflectedField(Type parentType, object? obj, FieldInfo field, bool isStatic)
|
||||
{
|
||||
// validate
|
||||
if (parentType == null)
|
||||
|
@ -58,11 +56,11 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TValue GetValue()
|
||||
public TValue? GetValue()
|
||||
{
|
||||
try
|
||||
{
|
||||
return (TValue)this.FieldInfo.GetValue(this.Parent);
|
||||
return (TValue?)this.FieldInfo.GetValue(this.Parent);
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
|
@ -75,7 +73,7 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValue(TValue value)
|
||||
public void SetValue(TValue? value)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
@ -14,8 +12,8 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
/// <summary>The type that has the method.</summary>
|
||||
private readonly Type ParentType;
|
||||
|
||||
/// <summary>The object that has the instance method (if applicable).</summary>
|
||||
private readonly object Parent;
|
||||
/// <summary>The object that has the instance method, or <c>null</c> for a static method.</summary>
|
||||
private readonly object? Parent;
|
||||
|
||||
/// <summary>The display name shown in error messages.</summary>
|
||||
private string DisplayName => $"{this.ParentType.FullName}::{this.MethodInfo.Name}";
|
||||
|
@ -33,12 +31,12 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="parentType">The type that has the method.</param>
|
||||
/// <param name="obj">The object that has the instance method(if applicable).</param>
|
||||
/// <param name="obj">The object that has the instance method, or <c>null</c> for a static method.</param>
|
||||
/// <param name="method">The reflection metadata.</param>
|
||||
/// <param name="isStatic">Whether the method is static.</param>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="parentType"/> or <paramref name="method"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">The <paramref name="obj"/> is null for a non-static method, or not null for a static method.</exception>
|
||||
public ReflectedMethod(Type parentType, object obj, MethodInfo method, bool isStatic)
|
||||
public ReflectedMethod(Type parentType, object? obj, MethodInfo method, bool isStatic)
|
||||
{
|
||||
// validate
|
||||
if (parentType == null)
|
||||
|
@ -57,10 +55,10 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TValue Invoke<TValue>(params object[] arguments)
|
||||
public TValue? Invoke<TValue>(params object?[] arguments)
|
||||
{
|
||||
// invoke method
|
||||
object result;
|
||||
object? result;
|
||||
try
|
||||
{
|
||||
result = this.MethodInfo.Invoke(this.Parent, arguments);
|
||||
|
@ -77,7 +75,7 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
// cast return value
|
||||
try
|
||||
{
|
||||
return (TValue)result;
|
||||
return (TValue?)result;
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
|
@ -86,7 +84,7 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Invoke(params object[] arguments)
|
||||
public void Invoke(params object?[] arguments)
|
||||
{
|
||||
// invoke method
|
||||
try
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
@ -16,10 +14,10 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
private readonly string DisplayName;
|
||||
|
||||
/// <summary>The underlying property getter.</summary>
|
||||
private readonly Func<TValue> GetMethod;
|
||||
private readonly Func<TValue?>? GetMethod;
|
||||
|
||||
/// <summary>The underlying property setter.</summary>
|
||||
private readonly Action<TValue> SetMethod;
|
||||
private readonly Action<TValue?>? SetMethod;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -34,12 +32,12 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="parentType">The type that has the property.</param>
|
||||
/// <param name="obj">The object that has the instance property (if applicable).</param>
|
||||
/// <param name="obj">The object that has the instance property, or <c>null</c> for a static property.</param>
|
||||
/// <param name="property">The reflection metadata.</param>
|
||||
/// <param name="isStatic">Whether the property is static.</param>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="parentType"/> or <paramref name="property"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">The <paramref name="obj"/> is null for a non-static property, or not null for a static property.</exception>
|
||||
public ReflectedProperty(Type parentType, object obj, PropertyInfo property, bool isStatic)
|
||||
public ReflectedProperty(Type parentType, object? obj, PropertyInfo property, bool isStatic)
|
||||
{
|
||||
// validate input
|
||||
if (parentType == null)
|
||||
|
@ -58,13 +56,13 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
this.PropertyInfo = property;
|
||||
|
||||
if (this.PropertyInfo.GetMethod != null)
|
||||
this.GetMethod = (Func<TValue>)Delegate.CreateDelegate(typeof(Func<TValue>), obj, this.PropertyInfo.GetMethod);
|
||||
this.GetMethod = (Func<TValue?>)Delegate.CreateDelegate(typeof(Func<TValue?>), obj, this.PropertyInfo.GetMethod);
|
||||
if (this.PropertyInfo.SetMethod != null)
|
||||
this.SetMethod = (Action<TValue>)Delegate.CreateDelegate(typeof(Action<TValue>), obj, this.PropertyInfo.SetMethod);
|
||||
this.SetMethod = (Action<TValue?>)Delegate.CreateDelegate(typeof(Action<TValue?>), obj, this.PropertyInfo.SetMethod);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TValue GetValue()
|
||||
public TValue? GetValue()
|
||||
{
|
||||
if (this.GetMethod == null)
|
||||
throw new InvalidOperationException($"The {this.DisplayName} property has no get method.");
|
||||
|
@ -84,7 +82,7 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValue(TValue value)
|
||||
public void SetValue(TValue? value)
|
||||
{
|
||||
if (this.SetMethod == null)
|
||||
throw new InvalidOperationException($"The {this.DisplayName} property has no set method.");
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
|
@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.Rendering
|
|||
/// <param name="tile">The tile to draw.</param>
|
||||
/// <param name="location">The tile position to draw.</param>
|
||||
/// <param name="layerDepth">The layer depth at which to draw.</param>
|
||||
public override void DrawTile(Tile tile, Location location, float layerDepth)
|
||||
public override void DrawTile(Tile? tile, Location location, float layerDepth)
|
||||
{
|
||||
// identical to XnaDisplayDevice
|
||||
if (tile == null)
|
||||
|
@ -58,7 +56,7 @@ namespace StardewModdingAPI.Framework.Rendering
|
|||
/// <param name="tile">The tile being drawn.</param>
|
||||
private SpriteEffects GetSpriteEffects(Tile tile)
|
||||
{
|
||||
return tile.Properties.TryGetValue("@Flip", out PropertyValue propertyValue) && int.TryParse(propertyValue, out int value)
|
||||
return tile.Properties.TryGetValue("@Flip", out PropertyValue? propertyValue) && int.TryParse(propertyValue, out int value)
|
||||
? (SpriteEffects)value
|
||||
: SpriteEffects.None;
|
||||
}
|
||||
|
@ -67,7 +65,7 @@ namespace StardewModdingAPI.Framework.Rendering
|
|||
/// <param name="tile">The tile being drawn.</param>
|
||||
private float GetRotation(Tile tile)
|
||||
{
|
||||
if (!tile.Properties.TryGetValue("@Rotation", out PropertyValue propertyValue) || !int.TryParse(propertyValue, out int value))
|
||||
if (!tile.Properties.TryGetValue("@Rotation", out PropertyValue? propertyValue) || !int.TryParse(propertyValue, out int value))
|
||||
return 0;
|
||||
|
||||
value %= 360;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -91,7 +89,7 @@ namespace StardewModdingAPI.Framework.Rendering
|
|||
/// <param name="tile">The tile to draw.</param>
|
||||
/// <param name="location">The tile position to draw.</param>
|
||||
/// <param name="layerDepth">The layer depth at which to draw.</param>
|
||||
public virtual void DrawTile(Tile tile, Location location, float layerDepth)
|
||||
public virtual void DrawTile(Tile? tile, Location location, float layerDepth)
|
||||
{
|
||||
if (tile == null)
|
||||
return;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -48,10 +46,10 @@ namespace StardewModdingAPI.Framework
|
|||
private readonly Action<string> ExitGameImmediately;
|
||||
|
||||
/// <summary>The initial override for <see cref="Input"/>. This value is null after initialization.</summary>
|
||||
private SInputState InitialInput;
|
||||
private SInputState? InitialInput;
|
||||
|
||||
/// <summary>The initial override for <see cref="Multiplayer"/>. This value is null after initialization.</summary>
|
||||
private SMultiplayer InitialMultiplayer;
|
||||
private SMultiplayer? InitialMultiplayer;
|
||||
|
||||
/// <summary>Raised when the instance is updating its state (roughly 60 times per second).</summary>
|
||||
private readonly Action<SGame, GameTime, Action> OnUpdating;
|
||||
|
@ -70,7 +68,7 @@ namespace StardewModdingAPI.Framework
|
|||
public Task NewDayTask => Game1._newDayTask;
|
||||
|
||||
/// <summary>Monitors the entire game state for changes.</summary>
|
||||
public WatcherCore Watchers { get; private set; }
|
||||
public WatcherCore Watchers { get; private set; } = null!; // initialized on first update tick
|
||||
|
||||
/// <summary>A snapshot of the current <see cref="Watchers"/> state.</summary>
|
||||
public WatcherSnapshot WatcherSnapshot { get; } = new();
|
||||
|
@ -94,7 +92,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>Construct a content manager to read game content files.</summary>
|
||||
/// <remarks>This must be static because the game accesses it before the <see cref="SGame"/> constructor is called.</remarks>
|
||||
[NonInstancedStatic]
|
||||
public static Func<IServiceProvider, string, LocalizedContentManager> CreateContentManagerImpl;
|
||||
public static Func<IServiceProvider, string, LocalizedContentManager>? CreateContentManagerImpl;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -138,11 +136,10 @@ namespace StardewModdingAPI.Framework
|
|||
/// <remarks>This is intended for use by <see cref="Keybind"/> and shouldn't be used directly in most cases.</remarks>
|
||||
internal static SButtonState GetInputState(SButton button)
|
||||
{
|
||||
SInputState input = Game1.input as SInputState;
|
||||
if (input == null)
|
||||
if (Game1.input is not SInputState inputHandler)
|
||||
throw new InvalidOperationException("SMAPI's input state is not in a ready state yet.");
|
||||
|
||||
return input.GetState(button);
|
||||
return inputHandler.GetState(button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -172,13 +169,11 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
base.Initialize();
|
||||
|
||||
// The game resets public static fields after the class is constructed (see
|
||||
// GameRunner.SetInstanceDefaults), so SMAPI needs to re-override them here.
|
||||
// The game resets public static fields after the class is constructed (see GameRunner.SetInstanceDefaults), so SMAPI needs to re-override them here.
|
||||
Game1.input = this.InitialInput;
|
||||
Game1.multiplayer = this.InitialMultiplayer;
|
||||
|
||||
// The Initial* fields should no longer be used after this point, since mods may
|
||||
// further override them after initialization.
|
||||
// The Initial* fields should no longer be used after this point, since mods may further override them after initialization.
|
||||
this.InitialInput = null;
|
||||
this.InitialMultiplayer = null;
|
||||
}
|
||||
|
@ -251,6 +246,7 @@ namespace StardewModdingAPI.Framework
|
|||
Context.IsInDrawLoop = false;
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
/// <summary>Replicate the game's draw logic with some changes for SMAPI.</summary>
|
||||
/// <param name="gameTime">A snapshot of the game timing state.</param>
|
||||
/// <param name="target_screen">The render target, if any.</param>
|
||||
|
@ -258,6 +254,7 @@ namespace StardewModdingAPI.Framework
|
|||
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "LocalVariableHidesMember", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "MergeIntoPattern", 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", "RedundantBaseQualifier", Justification = "copied from game code as-is")]
|
||||
|
@ -265,8 +262,9 @@ namespace StardewModdingAPI.Framework
|
|||
[SuppressMessage("ReSharper", "RedundantExplicitNullableCreation", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "RedundantTypeArgumentsOfMethod", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("SMAPI.CommonErrors", "AvoidNetField", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "MergeIntoPattern", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("SMAPI.CommonErrors", "AvoidImplicitNetFieldCast", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("SMAPI.CommonErrors", "AvoidNetField", Justification = "copied from game code as-is")]
|
||||
private void DrawImpl(GameTime gameTime, RenderTarget2D target_screen)
|
||||
{
|
||||
var events = this.Events;
|
||||
|
@ -952,5 +950,6 @@ namespace StardewModdingAPI.Framework
|
|||
this.drawOverlays(Game1.spriteBatch);
|
||||
Game1.PopUIMode();
|
||||
}
|
||||
#nullable enable
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using StardewValley;
|
||||
|
@ -35,7 +33,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="action">The vanilla <see cref="Game1.newDayAfterFade"/> logic.</param>
|
||||
public override void OnGame1_NewDayAfterFade(Action action)
|
||||
{
|
||||
this.BeforeNewDayAfterFade?.Invoke();
|
||||
this.BeforeNewDayAfterFade();
|
||||
action();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using StardewModdingAPI.Framework.StateTracking;
|
||||
|
||||
namespace StardewModdingAPI.Framework
|
||||
|
@ -15,10 +13,10 @@ namespace StardewModdingAPI.Framework
|
|||
public bool IsChanged { get; private set; }
|
||||
|
||||
/// <summary>The previous value.</summary>
|
||||
public T Old { get; private set; }
|
||||
public T? Old { get; private set; }
|
||||
|
||||
/// <summary>The current value.</summary>
|
||||
public T New { get; private set; }
|
||||
public T? New { get; private set; }
|
||||
|
||||
|
||||
/*********
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Events;
|
||||
using StardewValley;
|
||||
|
@ -48,7 +47,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="stackSizes">The items with their previous stack sizes.</param>
|
||||
/// <param name="changes">The inventory changes, or <c>null</c> if nothing changed.</param>
|
||||
/// <returns>Returns whether anything changed.</returns>
|
||||
public static bool TryGetChanges(ISet<Item> added, ISet<Item> removed, IDictionary<Item, int> stackSizes, out SnapshotItemListDiff changes)
|
||||
public static bool TryGetChanges(ISet<Item> added, ISet<Item> removed, IDictionary<Item, int> stackSizes, [NotNullWhen(true)] out SnapshotItemListDiff? changes)
|
||||
{
|
||||
KeyValuePair<Item, int>[] sizesChanged = stackSizes.Where(p => p.Key.Stack != p.Value).ToArray();
|
||||
if (sizesChanged.Any() || added.Any() || removed.Any())
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using StardewModdingAPI.Framework.StateTracking;
|
||||
|
||||
|
@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="isChanged">Whether the value changed since the last update.</param>
|
||||
/// <param name="removed">The removed values.</param>
|
||||
/// <param name="added">The added values.</param>
|
||||
public void Update(bool isChanged, IEnumerable<T> removed, IEnumerable<T> added)
|
||||
public void Update(bool isChanged, IEnumerable<T>? removed, IEnumerable<T>? added)
|
||||
{
|
||||
this.IsChanged = isChanged;
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Framework.StateTracking.Comparers;
|
||||
using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
|
||||
|
@ -86,7 +85,7 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
/// <summary>Get the inventory changes since the last update, if anything changed.</summary>
|
||||
/// <param name="changes">The inventory changes, or <c>null</c> if nothing changed.</param>
|
||||
/// <returns>Returns whether anything changed.</returns>
|
||||
public bool TryGetInventoryChanges(out SnapshotItemListDiff changes)
|
||||
public bool TryGetInventoryChanges([NotNullWhen(true)] out SnapshotItemListDiff? changes)
|
||||
{
|
||||
return SnapshotItemListDiff.TryGetChanges(added: this.Added, removed: this.Removed, stackSizes: this.StackSizes, out changes);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
@ -17,7 +15,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Comparers
|
|||
/// <returns>true if the specified objects are equal; otherwise, false.</returns>
|
||||
/// <param name="x">The first object to compare.</param>
|
||||
/// <param name="y">The second object to compare.</param>
|
||||
public bool Equals(T x, T y)
|
||||
public bool Equals(T? x, T? y)
|
||||
{
|
||||
if (x == null)
|
||||
return y == null;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
@ -16,7 +14,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Comparers
|
|||
/// <returns>true if the specified objects are equal; otherwise, false.</returns>
|
||||
/// <param name="x">The first object to compare.</param>
|
||||
/// <param name="y">The second object to compare.</param>
|
||||
public bool Equals(T x, T y)
|
||||
public bool Equals(T? x, T? y)
|
||||
{
|
||||
if (x == null)
|
||||
return y == null;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
@ -16,7 +14,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Comparers
|
|||
/// <returns>true if the specified objects are equal; otherwise, false.</returns>
|
||||
/// <param name="x">The first object to compare.</param>
|
||||
/// <param name="y">The second object to compare.</param>
|
||||
public bool Equals(T x, T y)
|
||||
public bool Equals(T? x, T? y)
|
||||
{
|
||||
return object.ReferenceEquals(x, y);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Netcode;
|
||||
|
||||
|
@ -12,6 +10,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
|
|||
/// <typeparam name="TSerialDict">The serializable dictionary type that can store the keys and values.</typeparam>
|
||||
/// <typeparam name="TSelf">The net field instance type.</typeparam>
|
||||
internal class NetDictionaryWatcher<TKey, TValue, TField, TSerialDict, TSelf> : BaseDisposableWatcher, IDictionaryWatcher<TKey, TValue>
|
||||
where TKey : notnull
|
||||
where TField : class, INetObject<INetSerializable>, new()
|
||||
where TSerialDict : IDictionary<TKey, TValue>, new()
|
||||
where TSelf : NetDictionary<TKey, TValue, TField, TSerialDict, TSelf>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
|
@ -81,7 +79,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
|
|||
/// <summary>A callback invoked when an entry is added or removed from the collection.</summary>
|
||||
/// <param name="sender">The event sender.</param>
|
||||
/// <param name="e">The event arguments.</param>
|
||||
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
|
@ -90,8 +88,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
|
|||
}
|
||||
else
|
||||
{
|
||||
TValue[] added = e.NewItems?.Cast<TValue>().ToArray();
|
||||
TValue[] removed = e.OldItems?.Cast<TValue>().ToArray();
|
||||
TValue[]? added = e.NewItems?.Cast<TValue>().ToArray();
|
||||
TValue[]? removed = e.OldItems?.Cast<TValue>().ToArray();
|
||||
|
||||
if (removed != null)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
@ -20,7 +18,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
|
|||
/// <summary>Get a watcher which compares values using their <see cref="object.Equals(object)"/> method. This method should only be used when <see cref="ForEquatable{T}"/> won't work, since this doesn't validate whether they're comparable.</summary>
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
/// <param name="getValue">Get the current value.</param>
|
||||
public static IValueWatcher<T> ForGenericEquality<T>(Func<T> getValue) where T : struct
|
||||
public static IValueWatcher<T> ForGenericEquality<T>(Func<T> getValue)
|
||||
where T : struct
|
||||
{
|
||||
return new ComparableWatcher<T>(getValue, new GenericEqualsComparer<T>());
|
||||
}
|
||||
|
@ -28,7 +27,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
|
|||
/// <summary>Get a watcher for an <see cref="IEquatable{T}"/> value.</summary>
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
/// <param name="getValue">Get the current value.</param>
|
||||
public static IValueWatcher<T> ForEquatable<T>(Func<T> getValue) where T : IEquatable<T>
|
||||
public static IValueWatcher<T> ForEquatable<T>(Func<T> getValue)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
return new ComparableWatcher<T>(getValue, new EquatableComparer<T>());
|
||||
}
|
||||
|
@ -79,7 +79,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
|
|||
/// <summary>Get a watcher for a net collection.</summary>
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
/// <param name="collection">The net collection.</param>
|
||||
public static ICollectionWatcher<T> ForNetCollection<T>(NetCollection<T> collection) where T : class, INetObject<INetSerializable>
|
||||
public static ICollectionWatcher<T> ForNetCollection<T>(NetCollection<T> collection)
|
||||
where T : class, INetObject<INetSerializable>
|
||||
{
|
||||
return new NetCollectionWatcher<T>(collection);
|
||||
}
|
||||
|
@ -87,7 +88,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
|
|||
/// <summary>Get a watcher for a net list.</summary>
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
/// <param name="collection">The net list.</param>
|
||||
public static ICollectionWatcher<T> ForNetList<T>(NetList<T, NetRef<T>> collection) where T : class, INetObject<INetSerializable>
|
||||
public static ICollectionWatcher<T> ForNetList<T>(NetList<T, NetRef<T>> collection)
|
||||
where T : class, INetObject<INetSerializable>
|
||||
{
|
||||
return new NetListWatcher<T>(collection);
|
||||
}
|
||||
|
@ -100,6 +102,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
|
|||
/// <typeparam name="TSelf">The net field instance type.</typeparam>
|
||||
/// <param name="field">The net field.</param>
|
||||
public static NetDictionaryWatcher<TKey, TValue, TField, TSerialDict, TSelf> ForNetDictionary<TKey, TValue, TField, TSerialDict, TSelf>(NetDictionary<TKey, TValue, TField, TSerialDict, TSelf> field)
|
||||
where TKey : notnull
|
||||
where TField : class, INetObject<INetSerializable>, new()
|
||||
where TSerialDict : IDictionary<TKey, TValue>, new()
|
||||
where TSelf : NetDictionary<TKey, TValue, TField, TSerialDict, TSelf>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -132,20 +130,20 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
private void UpdateChestWatcherList(IEnumerable<KeyValuePair<Vector2, SObject>> added, IEnumerable<KeyValuePair<Vector2, SObject>> removed)
|
||||
{
|
||||
// remove unused watchers
|
||||
foreach (KeyValuePair<Vector2, SObject> pair in removed)
|
||||
foreach ((Vector2 tile, SObject? obj) in removed)
|
||||
{
|
||||
if (pair.Value is Chest && this.ChestWatchers.TryGetValue(pair.Key, out ChestTracker watcher))
|
||||
if (obj is Chest && this.ChestWatchers.TryGetValue(tile, out ChestTracker? watcher))
|
||||
{
|
||||
watcher.Dispose();
|
||||
this.ChestWatchers.Remove(pair.Key);
|
||||
this.ChestWatchers.Remove(tile);
|
||||
}
|
||||
}
|
||||
|
||||
// add new watchers
|
||||
foreach (KeyValuePair<Vector2, SObject> pair in added)
|
||||
foreach ((Vector2 tile, SObject? obj) in added)
|
||||
{
|
||||
if (pair.Value is Chest chest && !this.ChestWatchers.ContainsKey(pair.Key))
|
||||
this.ChestWatchers.Add(pair.Key, new ChestTracker(chest));
|
||||
if (obj is Chest chest && !this.ChestWatchers.ContainsKey(tile))
|
||||
this.ChestWatchers.Add(tile, new ChestTracker(chest));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using StardewValley;
|
||||
|
@ -70,7 +68,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots
|
|||
this.ChestItems.Clear();
|
||||
foreach (ChestTracker tracker in watcher.ChestWatchers.Values)
|
||||
{
|
||||
if (tracker.TryGetInventoryChanges(out SnapshotItemListDiff changes))
|
||||
if (tracker.TryGetInventoryChanges(out SnapshotItemListDiff? changes))
|
||||
this.ChestItems[tracker.Chest] = changes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using StardewValley;
|
||||
using StardewValley.Menus;
|
||||
|
@ -16,7 +14,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots
|
|||
public SnapshotDiff<Point> WindowSize { get; } = new();
|
||||
|
||||
/// <summary>Tracks changes to the current player.</summary>
|
||||
public PlayerSnapshot CurrentPlayer { get; private set; }
|
||||
public PlayerSnapshot? CurrentPlayer { get; private set; }
|
||||
|
||||
/// <summary>Tracks changes to the time of day (in 24-hour military format).</summary>
|
||||
public SnapshotDiff<int> Time { get; } = new();
|
||||
|
@ -56,7 +54,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots
|
|||
// update snapshots
|
||||
this.WindowSize.Update(watchers.WindowSizeWatcher);
|
||||
this.Locale.Update(watchers.LocaleWatcher);
|
||||
this.CurrentPlayer?.Update(watchers.CurrentPlayerTracker);
|
||||
this.CurrentPlayer?.Update(watchers.CurrentPlayerTracker!);
|
||||
this.Time.Update(watchers.TimeWatcher);
|
||||
this.SaveID.Update(watchers.SaveIdWatcher);
|
||||
this.Locations.Update(watchers.LocationsWatcher);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Framework.StateTracking.Comparers;
|
||||
|
@ -44,7 +42,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots
|
|||
// update locations
|
||||
foreach (LocationTracker locationWatcher in watcher.Locations)
|
||||
{
|
||||
if (!this.LocationsDict.TryGetValue(locationWatcher.Location, out LocationSnapshot snapshot))
|
||||
if (!this.LocationsDict.TryGetValue(locationWatcher.Location, out LocationSnapshot? snapshot))
|
||||
this.LocationsDict[locationWatcher.Location] = snapshot = new LocationSnapshot(locationWatcher.Location);
|
||||
|
||||
snapshot.Update(locationWatcher);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
@ -27,10 +25,10 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
private readonly ICollectionWatcher<GameLocation> VolcanoLocationListWatcher;
|
||||
|
||||
/// <summary>A lookup of the tracked locations.</summary>
|
||||
private IDictionary<GameLocation, LocationTracker> LocationDict { get; } = new Dictionary<GameLocation, LocationTracker>(new ObjectReferenceComparer<GameLocation>());
|
||||
private Dictionary<GameLocation, LocationTracker> LocationDict { get; } = new(new ObjectReferenceComparer<GameLocation>());
|
||||
|
||||
/// <summary>A lookup of registered buildings and their indoor location.</summary>
|
||||
private readonly IDictionary<Building, GameLocation> BuildingIndoors = new Dictionary<Building, GameLocation>(new ObjectReferenceComparer<Building>());
|
||||
private readonly Dictionary<Building, GameLocation?> BuildingIndoors = new(new ObjectReferenceComparer<Building>());
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -101,10 +99,9 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
}
|
||||
|
||||
// detect building interiors changed (e.g. construction completed)
|
||||
foreach (KeyValuePair<Building, GameLocation> pair in this.BuildingIndoors.Where(p => !object.Equals(p.Key.indoors.Value, p.Value)))
|
||||
foreach ((Building building, GameLocation? oldIndoors) in this.BuildingIndoors.Where(p => !object.Equals(p.Key.indoors.Value, p.Value)))
|
||||
{
|
||||
GameLocation oldIndoors = pair.Value;
|
||||
GameLocation newIndoors = pair.Key.indoors.Value;
|
||||
GameLocation? newIndoors = building.indoors.Value;
|
||||
|
||||
if (oldIndoors != null)
|
||||
this.Added.Add(oldIndoors);
|
||||
|
@ -189,19 +186,19 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
****/
|
||||
/// <summary>Add the given building.</summary>
|
||||
/// <param name="building">The building to add.</param>
|
||||
public void Add(Building building)
|
||||
public void Add(Building? building)
|
||||
{
|
||||
if (building == null)
|
||||
return;
|
||||
|
||||
GameLocation indoors = building.indoors.Value;
|
||||
GameLocation? indoors = building.indoors.Value;
|
||||
this.BuildingIndoors[building] = indoors;
|
||||
this.Add(indoors);
|
||||
}
|
||||
|
||||
/// <summary>Add the given location.</summary>
|
||||
/// <param name="location">The location to add.</param>
|
||||
public void Add(GameLocation location)
|
||||
public void Add(GameLocation? location)
|
||||
{
|
||||
if (location == null)
|
||||
return;
|
||||
|
@ -220,7 +217,7 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
|
||||
/// <summary>Remove the given building.</summary>
|
||||
/// <param name="building">The building to remove.</param>
|
||||
public void Remove(Building building)
|
||||
public void Remove(Building? building)
|
||||
{
|
||||
if (building == null)
|
||||
return;
|
||||
|
@ -231,12 +228,12 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
|
||||
/// <summary>Remove the given location.</summary>
|
||||
/// <param name="location">The location to remove.</param>
|
||||
public void Remove(GameLocation location)
|
||||
public void Remove(GameLocation? location)
|
||||
{
|
||||
if (location == null)
|
||||
return;
|
||||
|
||||
if (this.LocationDict.TryGetValue(location, out LocationTracker watcher))
|
||||
if (this.LocationDict.TryGetValue(location, out LocationTracker? watcher))
|
||||
{
|
||||
// track change
|
||||
this.Removed.Add(location);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewValley;
|
||||
|
||||
|
@ -23,7 +22,7 @@ namespace StardewModdingAPI.Framework
|
|||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The current locale.</summary>
|
||||
/// <summary>The current locale code like <c>fr-FR</c>, or an empty string for English.</summary>
|
||||
public string Locale { get; private set; }
|
||||
|
||||
/// <summary>The game's current language code.</summary>
|
||||
|
@ -39,9 +38,10 @@ namespace StardewModdingAPI.Framework
|
|||
this.SetLocale(string.Empty, LocalizedContentManager.LanguageCode.en);
|
||||
}
|
||||
|
||||
/// <summary>Set the current locale and precache translations.</summary>
|
||||
/// <summary>Set the current locale and pre-cache translations.</summary>
|
||||
/// <param name="locale">The current locale.</param>
|
||||
/// <param name="localeEnum">The game's current language code.</param>
|
||||
[MemberNotNull(nameof(Translator.ForLocale), nameof(Translator.Locale))]
|
||||
public void SetLocale(string locale, LocalizedContentManager.LanguageCode localeEnum)
|
||||
{
|
||||
this.Locale = locale.ToLower().Trim();
|
||||
|
@ -50,7 +50,7 @@ namespace StardewModdingAPI.Framework
|
|||
this.ForLocale = new Dictionary<string, Translation>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (string key in this.GetAllKeysRaw())
|
||||
{
|
||||
string text = this.GetRaw(key, locale, withFallback: true);
|
||||
string? text = this.GetRaw(key, locale, withFallback: true);
|
||||
this.ForLocale.Add(key, new Translation(this.Locale, key, text));
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="key">The translation key.</param>
|
||||
public Translation Get(string key)
|
||||
{
|
||||
this.ForLocale.TryGetValue(key, out Translation translation);
|
||||
this.ForLocale.TryGetValue(key, out Translation? translation);
|
||||
return translation ?? new Translation(this.Locale, key, null);
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ namespace StardewModdingAPI.Framework
|
|||
foreach (var localeSet in this.All)
|
||||
{
|
||||
string locale = localeSet.Key;
|
||||
string text = this.GetRaw(key, locale, withFallback);
|
||||
string? text = this.GetRaw(key, locale, withFallback);
|
||||
|
||||
if (text != null)
|
||||
translations[locale] = new Translation(locale, key, text);
|
||||
|
@ -128,13 +128,13 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="key">The translation key.</param>
|
||||
/// <param name="locale">The locale to get.</param>
|
||||
/// <param name="withFallback">Whether to add duplicate translations for locale fallback. For example, if a translation is defined in <c>default.json</c> but not <c>fr.json</c>, setting this to true will add a <c>fr</c> entry which duplicates the default text.</param>
|
||||
private string GetRaw(string key, string locale, bool withFallback)
|
||||
private string? GetRaw(string key, string locale, bool withFallback)
|
||||
{
|
||||
foreach (string next in this.GetRelevantLocales(locale))
|
||||
{
|
||||
string translation = null;
|
||||
string? translation = null;
|
||||
bool hasTranslation =
|
||||
this.All.TryGetValue(next, out IDictionary<string, string> translations)
|
||||
this.All.TryGetValue(next, out IDictionary<string, string>? translations)
|
||||
&& translations.TryGetValue(key, out translation);
|
||||
|
||||
if (hasTranslation)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
@ -9,6 +7,7 @@ namespace StardewModdingAPI.Framework.Utilities
|
|||
/// <typeparam name="TKey">The dictionary key type.</typeparam>
|
||||
/// <typeparam name="TValue">The dictionary value type.</typeparam>
|
||||
internal class TickCacheDictionary<TKey, TValue>
|
||||
where TKey : notnull
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -36,7 +35,7 @@ namespace StardewModdingAPI.Framework.Utilities
|
|||
}
|
||||
|
||||
// fetch value
|
||||
if (!this.Cache.TryGetValue(cacheKey, out TValue cached))
|
||||
if (!this.Cache.TryGetValue(cacheKey, out TValue? cached))
|
||||
this.Cache[cacheKey] = cached = get();
|
||||
return cached;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework
|
|||
public readonly IValueWatcher<Point> WindowSizeWatcher;
|
||||
|
||||
/// <summary>Tracks changes to the current player.</summary>
|
||||
public PlayerTracker CurrentPlayerTracker;
|
||||
public PlayerTracker? CurrentPlayerTracker;
|
||||
|
||||
/// <summary>Tracks changes to the time of day (in 24-hour military format).</summary>
|
||||
public readonly IValueWatcher<int> TimeWatcher;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
|
@ -7,6 +5,7 @@ namespace StardewModdingAPI
|
|||
/// <summary>Generic metadata and methods for a content asset being loaded.</summary>
|
||||
/// <typeparam name="TValue">The expected data type.</typeparam>
|
||||
public interface IAssetData<TValue> : IAssetInfo
|
||||
where TValue : notnull
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
|
@ -11,7 +9,7 @@ namespace StardewModdingAPI
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The content's locale code, if the content is localized.</summary>
|
||||
string Locale { get; }
|
||||
string? Locale { get; }
|
||||
|
||||
/// <summary>The asset name being read.</summary>
|
||||
/// <remarks>NOTE: when reading this field from an <see cref="IAssetLoader"/> or <see cref="IAssetEditor"/> implementation, it's always equivalent to <see cref="NameWithoutLocale"/> for backwards compatibility.</remarks>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using StardewValley;
|
||||
|
||||
|
@ -18,7 +16,7 @@ namespace StardewModdingAPI
|
|||
string BaseName { get; }
|
||||
|
||||
/// <summary>The locale code specified in the <see cref="Name"/>, if it's a valid code recognized by the game content.</summary>
|
||||
string LocaleCode { get; }
|
||||
string? LocaleCode { get; }
|
||||
|
||||
/// <summary>The language code matching the <see cref="LocaleCode"/>, if applicable.</summary>
|
||||
LocalizedContentManager.LanguageCode? LanguageCode { get; }
|
||||
|
@ -30,23 +28,23 @@ namespace StardewModdingAPI
|
|||
/// <summary>Get whether the given asset name is equivalent, ignoring capitalization and formatting.</summary>
|
||||
/// <param name="assetName">The asset name to compare this instance to.</param>
|
||||
/// <param name="useBaseName">Whether to compare the given name with the <see cref="BaseName"/> (if true) or <see cref="Name"/> (if false). This has no effect on any locale included in the given <paramref name="assetName"/>.</param>
|
||||
bool IsEquivalentTo(string assetName, bool useBaseName = false);
|
||||
bool IsEquivalentTo(string? assetName, bool useBaseName = false);
|
||||
|
||||
/// <summary>Get whether the given asset name is equivalent, ignoring capitalization and formatting.</summary>
|
||||
/// <param name="assetName">The asset name to compare this instance to.</param>
|
||||
/// <param name="useBaseName">Whether to compare the given name with the <see cref="BaseName"/> (if true) or <see cref="Name"/> (if false).</param>
|
||||
bool IsEquivalentTo(IAssetName assetName, bool useBaseName = false);
|
||||
bool IsEquivalentTo(IAssetName? assetName, bool useBaseName = false);
|
||||
|
||||
/// <summary>Get whether the asset name starts with the given value, ignoring capitalization and formatting. This can be used with a trailing slash to test for an asset folder, like <c>Data/</c>.</summary>
|
||||
/// <param name="prefix">The prefix to match.</param>
|
||||
/// <param name="allowPartialWord">Whether to match if the prefix occurs mid-word, so <c>Data/AchievementsToIgnore</c> matches prefix <c>Data/Achievements</c>. If this is false, the prefix only matches if the asset name starts with the prefix followed by a non-alphanumeric character (including <c>.</c>, <c>/</c>, or <c>\\</c>) or the end of string.</param>
|
||||
/// <param name="allowSubfolder">Whether to match the prefix if there's a subfolder path after it, so <c>Data/Achievements/Example</c> matches prefix <c>Data/Achievements</c>. If this is false, the prefix only matches if the asset name has no <c>/</c> or <c>\\</c> characters after the prefix.</param>
|
||||
bool StartsWith(string prefix, bool allowPartialWord = true, bool allowSubfolder = true);
|
||||
bool StartsWith(string? prefix, bool allowPartialWord = true, bool allowSubfolder = true);
|
||||
|
||||
/// <summary>Get whether the asset is directly within the given asset path.</summary>
|
||||
/// <remarks>For example, <c>Characters/Dialogue/Abigail</c> is directly under <c>Characters/Dialogue</c> but not <c>Characters</c> or <c>Characters/Dialogue/Ab</c>. To allow sub-paths, use <see cref="StartsWith"/> instead.</remarks>
|
||||
/// <param name="assetFolder">The asset path to check. This doesn't need a trailing slash.</param>
|
||||
bool IsDirectlyUnderPath(string assetFolder);
|
||||
bool IsDirectlyUnderPath(string? assetFolder);
|
||||
|
||||
/// <summary>Get an asset name representing the <see cref="BaseName"/> without locale.</summary>
|
||||
internal IAssetName GetBaseAssetName();
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
@ -42,12 +40,14 @@ namespace StardewModdingAPI
|
|||
/// <param name="source">Where to search for a matching content asset.</param>
|
||||
/// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception>
|
||||
/// <exception cref="ContentLoadException">The content asset couldn't be loaded (e.g. because it doesn't exist).</exception>
|
||||
T Load<T>(string key, ContentSource source = ContentSource.ModFolder);
|
||||
T Load<T>(string key, ContentSource source = ContentSource.ModFolder)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>Normalize an asset name so it's consistent with those generated by the game. This is mainly useful for string comparisons like <see cref="string.StartsWith(string)"/> on generated asset names, and isn't necessary when passing asset names into other content helper methods.</summary>
|
||||
/// <param name="assetName">The asset key.</param>
|
||||
/// <exception cref="ContentLoadException">The asset key is empty or contains invalid characters.</exception>
|
||||
[Pure]
|
||||
string NormalizeAssetName(string assetName);
|
||||
string NormalizeAssetName(string? assetName);
|
||||
|
||||
/// <summary>Get the underlying key in the game's content cache for an asset. This can be used to load custom map tilesheets, but should be avoided when you can use the content API instead. This does not validate whether the asset exists.</summary>
|
||||
/// <param name="key">The asset key to fetch (if the <paramref name="source"/> is <see cref="ContentSource.GameContent"/>), or the local path to a content file relative to the mod folder.</param>
|
||||
|
@ -64,7 +64,8 @@ namespace StardewModdingAPI
|
|||
/// <summary>Remove all assets of the given type from the cache so they're reloaded on the next request. <b>This can be a very expensive operation and should only be used in very specific cases.</b> This will reload core game assets if needed, but references to the former assets will still show the previous content.</summary>
|
||||
/// <typeparam name="T">The asset type to remove from the cache.</typeparam>
|
||||
/// <returns>Returns whether any assets were invalidated.</returns>
|
||||
bool InvalidateCache<T>();
|
||||
bool InvalidateCache<T>()
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>Remove matching assets from the content cache so they're reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content.</summary>
|
||||
/// <param name="predicate">A predicate matching the assets to invalidate.</param>
|
||||
|
@ -75,6 +76,7 @@ namespace StardewModdingAPI
|
|||
/// <typeparam name="T">The data type.</typeparam>
|
||||
/// <param name="data">The asset data.</param>
|
||||
/// <param name="assetName">The asset name. This is only used for tracking purposes and has no effect on the patch helper.</param>
|
||||
IAssetData GetPatchHelper<T>(T data, string assetName = null);
|
||||
IAssetData GetPatchHelper<T>(T data, string? assetName = null)
|
||||
where T : notnull;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
@ -38,14 +36,16 @@ namespace StardewModdingAPI
|
|||
/// <param name="path">The relative file path within the content pack (case-insensitive).</param>
|
||||
/// <returns>Returns the deserialized model, or <c>null</c> if the file doesn't exist or is empty.</returns>
|
||||
/// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
|
||||
TModel ReadJsonFile<TModel>(string path) where TModel : class;
|
||||
TModel? ReadJsonFile<TModel>(string path)
|
||||
where TModel : class;
|
||||
|
||||
/// <summary>Save data to a JSON file in the content pack's folder.</summary>
|
||||
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
|
||||
/// <param name="path">The relative file path within the content pack (case-insensitive).</param>
|
||||
/// <param name="data">The arbitrary data to save.</param>
|
||||
/// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
|
||||
void WriteJsonFile<TModel>(string path, TModel data) where TModel : class;
|
||||
void WriteJsonFile<TModel>(string path, TModel data)
|
||||
where TModel : class;
|
||||
|
||||
/// <summary>Load content from the content pack folder (if not already cached), and return it. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary>
|
||||
/// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, and dictionaries; other types may be supported by the game's content pipeline.</typeparam>
|
||||
|
@ -53,7 +53,8 @@ namespace StardewModdingAPI
|
|||
/// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception>
|
||||
/// <exception cref="ContentLoadException">The content asset couldn't be loaded (e.g. because it doesn't exist).</exception>
|
||||
[Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.Load)} instead. This method will be removed in SMAPI 4.0.0.")]
|
||||
T LoadAsset<T>(string key);
|
||||
T LoadAsset<T>(string key)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>Get the underlying key in the game's content cache for an asset. This can be used to load custom map tilesheets, but should be avoided when you can use the content API instead. This does not validate whether the asset exists.</summary>
|
||||
/// <param name="key">The relative file path within the content pack (case-insensitive).</param>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
|
@ -18,14 +16,16 @@ namespace StardewModdingAPI
|
|||
/// <param name="path">The file path relative to the mod folder.</param>
|
||||
/// <returns>Returns the deserialized model, or <c>null</c> if the file doesn't exist or is empty.</returns>
|
||||
/// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
|
||||
TModel ReadJsonFile<TModel>(string path) where TModel : class;
|
||||
TModel? ReadJsonFile<TModel>(string path)
|
||||
where TModel : class;
|
||||
|
||||
/// <summary>Save data to a JSON file in the mod's folder.</summary>
|
||||
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
|
||||
/// <param name="path">The file path relative to the mod folder.</param>
|
||||
/// <param name="data">The arbitrary data to save, or <c>null</c> to delete the file.</param>
|
||||
/// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
|
||||
void WriteJsonFile<TModel>(string path, TModel data) where TModel : class;
|
||||
void WriteJsonFile<TModel>(string path, TModel? data)
|
||||
where TModel : class;
|
||||
|
||||
/****
|
||||
** Save file
|
||||
|
@ -35,14 +35,16 @@ namespace StardewModdingAPI
|
|||
/// <param name="key">The unique key identifying the data.</param>
|
||||
/// <returns>Returns the parsed data, or <c>null</c> if the entry doesn't exist or is empty.</returns>
|
||||
/// <exception cref="InvalidOperationException">The player hasn't loaded a save file yet or isn't the main player.</exception>
|
||||
TModel ReadSaveData<TModel>(string key) where TModel : class;
|
||||
TModel? ReadSaveData<TModel>(string key)
|
||||
where TModel : class;
|
||||
|
||||
/// <summary>Save arbitrary data to the current save slot. This is only possible if a save has been loaded, and the data will be lost if the player exits without saving the current day.</summary>
|
||||
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
|
||||
/// <param name="key">The unique key identifying the data.</param>
|
||||
/// <param name="data">The arbitrary data to save, or <c>null</c> to remove the entry.</param>
|
||||
/// <exception cref="InvalidOperationException">The player hasn't loaded a save file yet or isn't the main player.</exception>
|
||||
void WriteSaveData<TModel>(string key, TModel data) where TModel : class;
|
||||
void WriteSaveData<TModel>(string key, TModel? data)
|
||||
where TModel : class;
|
||||
|
||||
|
||||
/****
|
||||
|
@ -52,12 +54,14 @@ namespace StardewModdingAPI
|
|||
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
|
||||
/// <param name="key">The unique key identifying the data.</param>
|
||||
/// <returns>Returns the parsed data, or <c>null</c> if the entry doesn't exist or is empty.</returns>
|
||||
TModel ReadGlobalData<TModel>(string key) where TModel : class;
|
||||
TModel? ReadGlobalData<TModel>(string key)
|
||||
where TModel : class;
|
||||
|
||||
/// <summary>Save arbitrary data to the local computer, synchronised by GOG/Steam if applicable.</summary>
|
||||
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
|
||||
/// <param name="key">The unique key identifying the data.</param>
|
||||
/// <param name="data">The arbitrary data to save, or <c>null</c> to delete the file.</param>
|
||||
void WriteGlobalData<TModel>(string key, TModel data) where TModel : class;
|
||||
void WriteGlobalData<TModel>(string key, TModel? data)
|
||||
where TModel : class;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
@ -35,14 +33,16 @@ namespace StardewModdingAPI
|
|||
/// <param name="assetName">The asset name to load.</param>
|
||||
/// <exception cref="ArgumentException">The <paramref name="assetName"/> is empty or contains invalid characters.</exception>
|
||||
/// <exception cref="ContentLoadException">The content asset couldn't be loaded (e.g. because it doesn't exist).</exception>
|
||||
T Load<T>(string assetName);
|
||||
T Load<T>(string assetName)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>Load content from the game folder or mod folder (if not already cached), and return it. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary>
|
||||
/// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, dictionaries, and lists; other types may be supported by the game's content pipeline.</typeparam>
|
||||
/// <param name="assetName">The asset name to load.</param>
|
||||
/// <exception cref="ArgumentException">The <paramref name="assetName"/> is empty or contains invalid characters.</exception>
|
||||
/// <exception cref="ContentLoadException">The content asset couldn't be loaded (e.g. because it doesn't exist).</exception>
|
||||
T Load<T>(IAssetName assetName);
|
||||
T Load<T>(IAssetName assetName)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>Remove an asset from the content cache so it's reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content.</summary>
|
||||
/// <param name="assetName">The asset key to invalidate in the content folder.</param>
|
||||
|
@ -59,7 +59,8 @@ namespace StardewModdingAPI
|
|||
/// <summary>Remove all assets of the given type from the cache so they're reloaded on the next request. <b>This can be a very expensive operation and should only be used in very specific cases.</b> This will reload core game assets if needed, but references to the former assets will still show the previous content.</summary>
|
||||
/// <typeparam name="T">The asset type to remove from the cache.</typeparam>
|
||||
/// <returns>Returns whether any assets were invalidated.</returns>
|
||||
bool InvalidateCache<T>();
|
||||
bool InvalidateCache<T>()
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>Remove matching assets from the content cache so they're reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content.</summary>
|
||||
/// <param name="predicate">A predicate matching the assets to invalidate.</param>
|
||||
|
@ -70,6 +71,7 @@ namespace StardewModdingAPI
|
|||
/// <typeparam name="T">The data type.</typeparam>
|
||||
/// <param name="data">The asset data.</param>
|
||||
/// <param name="assetName">The asset name. This is only used for tracking purposes and has no effect on the patch helper.</param>
|
||||
IAssetData GetPatchHelper<T>(T data, string assetName = null);
|
||||
IAssetData GetPatchHelper<T>(T data, string? assetName = null)
|
||||
where T : notnull;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>The implementation for a Stardew Valley mod.</summary>
|
||||
|
@ -26,6 +24,6 @@ namespace StardewModdingAPI
|
|||
void Entry(IModHelper helper);
|
||||
|
||||
/// <summary>Get an API that other mods can access. This is always called after <see cref="Entry"/>.</summary>
|
||||
object GetApi();
|
||||
object? GetApi();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
@ -18,7 +16,8 @@ namespace StardewModdingAPI
|
|||
/// <param name="relativePath">The local path to a content file relative to the mod folder.</param>
|
||||
/// <exception cref="ArgumentException">The <paramref name="relativePath"/> is empty or contains invalid characters.</exception>
|
||||
/// <exception cref="ContentLoadException">The content asset couldn't be loaded (e.g. because it doesn't exist).</exception>
|
||||
T Load<T>(string relativePath);
|
||||
T Load<T>(string relativePath)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>Get the internal asset name which allows loading a mod file through any of the game's content managers. This can be used when passing asset names directly to the game (e.g. for map tilesheets), but should be avoided if you can use <see cref="Load{T}"/> instead. This does not validate whether the asset exists.</summary>
|
||||
/// <param name="relativePath">The local path to a content file relative to the mod folder.</param>
|
||||
|
@ -29,6 +28,7 @@ namespace StardewModdingAPI
|
|||
/// <typeparam name="T">The data type.</typeparam>
|
||||
/// <param name="data">The asset data.</param>
|
||||
/// <param name="relativePath">The local path to the content file being edited relative to the mod folder. This is only used for tracking purposes and has no effect on the patch helper.</param>
|
||||
IAssetData GetPatchHelper<T>(T data, string relativePath = null);
|
||||
IAssetData GetPatchHelper<T>(T data, string? relativePath = null)
|
||||
where T : notnull;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
|
@ -13,7 +11,7 @@ namespace StardewModdingAPI
|
|||
/// <summary>Get metadata for a loaded mod.</summary>
|
||||
/// <param name="uniqueID">The mod's unique ID.</param>
|
||||
/// <returns>Returns the matching mod's metadata, or <c>null</c> if not found.</returns>
|
||||
IModInfo Get(string uniqueID);
|
||||
IModInfo? Get(string uniqueID);
|
||||
|
||||
/// <summary>Get whether a mod has been loaded.</summary>
|
||||
/// <param name="uniqueID">The mod's unique ID.</param>
|
||||
|
@ -21,11 +19,12 @@ namespace StardewModdingAPI
|
|||
|
||||
/// <summary>Get the API provided by a mod, or <c>null</c> if it has none. This signature requires using the <see cref="IModHelper.Reflection"/> API to access the API's properties and methods.</summary>
|
||||
/// <param name="uniqueID">The mod's unique ID.</param>
|
||||
object GetApi(string uniqueID);
|
||||
object? GetApi(string uniqueID);
|
||||
|
||||
/// <summary>Get the API provided by a mod, mapped to a given interface which specifies the expected properties and methods. If the mod has no API or it's not compatible with the given interface, get <c>null</c>.</summary>
|
||||
/// <typeparam name="TInterface">The interface which matches the properties and methods you intend to access.</typeparam>
|
||||
/// <param name="uniqueID">The mod's unique ID.</param>
|
||||
TInterface GetApi<TInterface>(string uniqueID) where TInterface : class;
|
||||
TInterface? GetApi<TInterface>(string uniqueID)
|
||||
where TInterface : class;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using StardewValley;
|
||||
|
@ -18,7 +16,7 @@ namespace StardewModdingAPI
|
|||
/// <summary>Get a connected player.</summary>
|
||||
/// <param name="id">The player's unique ID.</param>
|
||||
/// <returns>Returns the connected player, or <c>null</c> if no such player is connected.</returns>
|
||||
IMultiplayerPeer GetConnectedPlayer(long id);
|
||||
IMultiplayerPeer? GetConnectedPlayer(long id);
|
||||
|
||||
/// <summary>Get all connected players.</summary>
|
||||
IEnumerable<IMultiplayerPeer> GetConnectedPlayers();
|
||||
|
@ -30,6 +28,6 @@ namespace StardewModdingAPI
|
|||
/// <param name="modIDs">The mod IDs which should receive the message on the destination computers, or <c>null</c> for all mods. Specifying mod IDs is recommended to improve performance, unless it's a general-purpose broadcast.</param>
|
||||
/// <param name="playerIDs">The <see cref="Farmer.UniqueMultiplayerID" /> values for the players who should receive the message, or <c>null</c> for all players. If you don't need to broadcast to all players, specifying player IDs is recommended to reduce latency.</param>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="message"/> or <paramref name="messageType" /> is null.</exception>
|
||||
void SendMessage<TMessage>(TMessage message, string messageType, string[] modIDs = null, long[] playerIDs = null);
|
||||
void SendMessage<TMessage>(TMessage message, string messageType, string[]? modIDs = null, long[]? playerIDs = null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
|
@ -20,6 +19,7 @@ namespace StardewModdingAPI
|
|||
bool IsSplitScreen { get; }
|
||||
|
||||
/// <summary>Whether the player has SMAPI installed.</summary>
|
||||
[MemberNotNullWhen(true, nameof(IMultiplayerPeer.Platform), nameof(IMultiplayerPeer.GameVersion), nameof(IMultiplayerPeer.ApiVersion), nameof(IMultiplayerPeer.Mods))]
|
||||
bool HasSmapi { get; }
|
||||
|
||||
/// <summary>The player's screen ID, if applicable.</summary>
|
||||
|
@ -30,10 +30,10 @@ namespace StardewModdingAPI
|
|||
GamePlatform? Platform { get; }
|
||||
|
||||
/// <summary>The installed version of Stardew Valley, if <see cref="HasSmapi"/> is true.</summary>
|
||||
ISemanticVersion GameVersion { get; }
|
||||
ISemanticVersion? GameVersion { get; }
|
||||
|
||||
/// <summary>The installed version of SMAPI, if <see cref="HasSmapi"/> is true.</summary>
|
||||
ISemanticVersion ApiVersion { get; }
|
||||
ISemanticVersion? ApiVersion { get; }
|
||||
|
||||
/// <summary>The installed mods, if <see cref="HasSmapi"/> is true.</summary>
|
||||
IEnumerable<IMultiplayerPeerMod> Mods { get; }
|
||||
|
@ -45,6 +45,6 @@ namespace StardewModdingAPI
|
|||
/// <summary>Get metadata for a mod installed by the player.</summary>
|
||||
/// <param name="id">The unique mod ID.</param>
|
||||
/// <returns>Returns the mod info, or <c>null</c> if the player doesn't have that mod.</returns>
|
||||
IMultiplayerPeerMod GetMod(string id);
|
||||
IMultiplayerPeerMod? GetMod(string? id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
|
@ -19,10 +17,10 @@ namespace StardewModdingAPI
|
|||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get the field value.</summary>
|
||||
TValue GetValue();
|
||||
TValue? GetValue();
|
||||
|
||||
/// <summary>Set the field value.</summary>
|
||||
//// <param name="value">The value to set.</param>
|
||||
void SetValue(TValue value);
|
||||
void SetValue(TValue? value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
|
@ -20,10 +18,10 @@ namespace StardewModdingAPI
|
|||
/// <summary>Invoke the method.</summary>
|
||||
/// <typeparam name="TValue">The return type.</typeparam>
|
||||
/// <param name="arguments">The method arguments to pass in.</param>
|
||||
TValue Invoke<TValue>(params object[] arguments);
|
||||
TValue? Invoke<TValue>(params object?[] arguments);
|
||||
|
||||
/// <summary>Invoke the method.</summary>
|
||||
/// <param name="arguments">The method arguments to pass in.</param>
|
||||
void Invoke(params object[] arguments);
|
||||
void Invoke(params object?[] arguments);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
|
@ -19,10 +17,10 @@ namespace StardewModdingAPI
|
|||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get the property value.</summary>
|
||||
TValue GetValue();
|
||||
TValue? GetValue();
|
||||
|
||||
/// <summary>Set the property value.</summary>
|
||||
//// <param name="value">The value to set.</param>
|
||||
void SetValue(TValue value);
|
||||
void SetValue(TValue? value);
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue