rewrite content interception using latest proposed API (#255)
This commit is contained in:
parent
7b6b2742f6
commit
49c75de5fc
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using StardewModdingAPI.Framework;
|
using StardewModdingAPI.Framework;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Events
|
namespace StardewModdingAPI.Events
|
||||||
|
@ -8,21 +6,6 @@ namespace StardewModdingAPI.Events
|
||||||
/// <summary>Events raised when the game loads content.</summary>
|
/// <summary>Events raised when the game loads content.</summary>
|
||||||
public static class ContentEvents
|
public static class ContentEvents
|
||||||
{
|
{
|
||||||
/*********
|
|
||||||
** Properties
|
|
||||||
*********/
|
|
||||||
/// <summary>Tracks the installed mods.</summary>
|
|
||||||
private static ModRegistry ModRegistry;
|
|
||||||
|
|
||||||
/// <summary>Encapsulates monitoring and logging.</summary>
|
|
||||||
private static IMonitor Monitor;
|
|
||||||
|
|
||||||
/// <summary>The mods using the experimental API for which a warning has been raised.</summary>
|
|
||||||
private static readonly HashSet<string> WarnedMods = new HashSet<string>();
|
|
||||||
|
|
||||||
/// <summary>The backing field for <see cref="AfterAssetLoaded"/>.</summary>
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
|
||||||
private static event EventHandler<IContentEventHelper> _AfterAssetLoaded;
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Events
|
** Events
|
||||||
|
@ -30,35 +13,10 @@ namespace StardewModdingAPI.Events
|
||||||
/// <summary>Raised after the content language changes.</summary>
|
/// <summary>Raised after the content language changes.</summary>
|
||||||
public static event EventHandler<EventArgsValueChanged<string>> AfterLocaleChanged;
|
public static event EventHandler<EventArgsValueChanged<string>> AfterLocaleChanged;
|
||||||
|
|
||||||
/// <summary>Raised when an XNB file is being read into the cache. Mods can change the data here before it's cached.</summary>
|
|
||||||
#if EXPERIMENTAL
|
|
||||||
public
|
|
||||||
#else
|
|
||||||
internal
|
|
||||||
#endif
|
|
||||||
static event EventHandler<IContentEventHelper> AfterAssetLoaded
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
ContentEvents.RaiseContentExperimentalWarning();
|
|
||||||
ContentEvents._AfterAssetLoaded += value;
|
|
||||||
}
|
|
||||||
remove => ContentEvents._AfterAssetLoaded -= value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Internal methods
|
** Internal methods
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Injects types required for backwards compatibility.</summary>
|
|
||||||
/// <param name="modRegistry">Tracks the installed mods.</param>
|
|
||||||
/// <param name="monitor">Encapsulates monitoring and logging.</param>
|
|
||||||
internal static void Shim(ModRegistry modRegistry, IMonitor monitor)
|
|
||||||
{
|
|
||||||
ContentEvents.ModRegistry = modRegistry;
|
|
||||||
ContentEvents.Monitor = monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Raise an <see cref="AfterLocaleChanged"/> event.</summary>
|
/// <summary>Raise an <see cref="AfterLocaleChanged"/> event.</summary>
|
||||||
/// <param name="monitor">Encapsulates monitoring and logging.</param>
|
/// <param name="monitor">Encapsulates monitoring and logging.</param>
|
||||||
/// <param name="oldLocale">The previous locale.</param>
|
/// <param name="oldLocale">The previous locale.</param>
|
||||||
|
@ -67,28 +25,5 @@ namespace StardewModdingAPI.Events
|
||||||
{
|
{
|
||||||
monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AfterLocaleChanged)}", ContentEvents.AfterLocaleChanged?.GetInvocationList(), null, new EventArgsValueChanged<string>(oldLocale, newLocale));
|
monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AfterLocaleChanged)}", ContentEvents.AfterLocaleChanged?.GetInvocationList(), null, new EventArgsValueChanged<string>(oldLocale, newLocale));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Raise an <see cref="AfterAssetLoaded"/> event.</summary>
|
|
||||||
/// <param name="monitor">Encapsulates monitoring and logging.</param>
|
|
||||||
/// <param name="contentHelper">Encapsulates access and changes to content being read from a data file.</param>
|
|
||||||
internal static void InvokeAfterAssetLoaded(IMonitor monitor, IContentEventHelper contentHelper)
|
|
||||||
{
|
|
||||||
monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AfterAssetLoaded)}", ContentEvents._AfterAssetLoaded?.GetInvocationList(), null, contentHelper);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*********
|
|
||||||
** Private methods
|
|
||||||
*********/
|
|
||||||
/// <summary>Raise an 'experimental API' warning for a mod using the content API.</summary>
|
|
||||||
private static void RaiseContentExperimentalWarning()
|
|
||||||
{
|
|
||||||
string modName = ContentEvents.ModRegistry.GetModFromStack() ?? "An unknown mod";
|
|
||||||
if (!ContentEvents.WarnedMods.Contains(modName))
|
|
||||||
{
|
|
||||||
ContentEvents.WarnedMods.Add(modName);
|
|
||||||
ContentEvents.Monitor.Log($"{modName} used the undocumented and experimental content API, which may change or be removed without warning.", LogLevel.Warn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
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>
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <summary>The content data being read.</summary>
|
||||||
|
public TValue Data { get; protected set; }
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="locale">The content's locale code, if the content is localised.</param>
|
||||||
|
/// <param name="assetName">The normalised asset name being read.</param>
|
||||||
|
/// <param name="data">The content data being read.</param>
|
||||||
|
/// <param name="getNormalisedPath">Normalises an asset key to match the cache key.</param>
|
||||||
|
public AssetData(string locale, string assetName, TValue data, Func<string, string> getNormalisedPath)
|
||||||
|
: base(locale, assetName, data.GetType(), getNormalisedPath)
|
||||||
|
{
|
||||||
|
this.Data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game.</summary>
|
||||||
|
/// <param name="value">The new content value.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">The <paramref name="value"/> is null.</exception>
|
||||||
|
/// <exception cref="InvalidCastException">The <paramref name="value"/>'s type is not compatible with the loaded asset's type.</exception>
|
||||||
|
public void ReplaceWith(TValue value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
throw new ArgumentNullException(nameof(value), "Can't set a loaded asset to a null value.");
|
||||||
|
if (!this.DataType.IsInstanceOfType(value))
|
||||||
|
throw new InvalidCastException($"Can't replace loaded asset of type {this.GetFriendlyTypeName(this.DataType)} with value of type {this.GetFriendlyTypeName(value.GetType())}. The new type must be compatible to prevent game errors.");
|
||||||
|
|
||||||
|
this.Data = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
||||||
namespace StardewModdingAPI.Framework.Content
|
namespace StardewModdingAPI.Framework.Content
|
||||||
{
|
{
|
||||||
/// <summary>Encapsulates access and changes to dictionary content being read from a data file.</summary>
|
/// <summary>Encapsulates access and changes to dictionary content being read from a data file.</summary>
|
||||||
internal class ContentEventHelperForDictionary<TKey, TValue> : ContentEventData<IDictionary<TKey, TValue>>, IContentEventHelperForDictionary<TKey, TValue>
|
internal class AssetDataForDictionary<TKey, TValue> : AssetData<IDictionary<TKey, TValue>>, IAssetDataForDictionary<TKey, TValue>
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
||||||
|
@ -15,7 +15,7 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
/// <param name="assetName">The normalised asset name being read.</param>
|
/// <param name="assetName">The normalised asset name being read.</param>
|
||||||
/// <param name="data">The content data being read.</param>
|
/// <param name="data">The content data being read.</param>
|
||||||
/// <param name="getNormalisedPath">Normalises an asset key to match the cache key.</param>
|
/// <param name="getNormalisedPath">Normalises an asset key to match the cache key.</param>
|
||||||
public ContentEventHelperForDictionary(string locale, string assetName, IDictionary<TKey, TValue> data, Func<string, string> getNormalisedPath)
|
public AssetDataForDictionary(string locale, string assetName, IDictionary<TKey, TValue> data, Func<string, string> getNormalisedPath)
|
||||||
: base(locale, assetName, data, getNormalisedPath) { }
|
: base(locale, assetName, data, getNormalisedPath) { }
|
||||||
|
|
||||||
/// <summary>Add or replace an entry in the dictionary.</summary>
|
/// <summary>Add or replace an entry in the dictionary.</summary>
|
|
@ -5,7 +5,7 @@ using Microsoft.Xna.Framework.Graphics;
|
||||||
namespace StardewModdingAPI.Framework.Content
|
namespace StardewModdingAPI.Framework.Content
|
||||||
{
|
{
|
||||||
/// <summary>Encapsulates access and changes to dictionary content being read from a data file.</summary>
|
/// <summary>Encapsulates access and changes to dictionary content being read from a data file.</summary>
|
||||||
internal class ContentEventHelperForImage : ContentEventData<Texture2D>, IContentEventHelperForImage
|
internal class AssetDataForImage : AssetData<Texture2D>, IAssetDataForImage
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
||||||
|
@ -15,7 +15,7 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
/// <param name="assetName">The normalised asset name being read.</param>
|
/// <param name="assetName">The normalised asset name being read.</param>
|
||||||
/// <param name="data">The content data being read.</param>
|
/// <param name="data">The content data being read.</param>
|
||||||
/// <param name="getNormalisedPath">Normalises an asset key to match the cache key.</param>
|
/// <param name="getNormalisedPath">Normalises an asset key to match the cache key.</param>
|
||||||
public ContentEventHelperForImage(string locale, string assetName, Texture2D data, Func<string, string> getNormalisedPath)
|
public AssetDataForImage(string locale, string assetName, Texture2D data, Func<string, string> getNormalisedPath)
|
||||||
: base(locale, assetName, data, getNormalisedPath) { }
|
: base(locale, assetName, data, getNormalisedPath) { }
|
||||||
|
|
||||||
/// <summary>Overwrite part of the image.</summary>
|
/// <summary>Overwrite part of the image.</summary>
|
|
@ -5,7 +5,7 @@ using Microsoft.Xna.Framework.Graphics;
|
||||||
namespace StardewModdingAPI.Framework.Content
|
namespace StardewModdingAPI.Framework.Content
|
||||||
{
|
{
|
||||||
/// <summary>Encapsulates access and changes to content being read from a data file.</summary>
|
/// <summary>Encapsulates access and changes to content being read from a data file.</summary>
|
||||||
internal class ContentEventHelper : ContentEventData<object>, IContentEventHelper
|
internal class AssetDataForObject : AssetData<object>, IAssetData
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
||||||
|
@ -15,23 +15,23 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
/// <param name="assetName">The normalised asset name being read.</param>
|
/// <param name="assetName">The normalised asset name being read.</param>
|
||||||
/// <param name="data">The content data being read.</param>
|
/// <param name="data">The content data being read.</param>
|
||||||
/// <param name="getNormalisedPath">Normalises an asset key to match the cache key.</param>
|
/// <param name="getNormalisedPath">Normalises an asset key to match the cache key.</param>
|
||||||
public ContentEventHelper(string locale, string assetName, object data, Func<string, string> getNormalisedPath)
|
public AssetDataForObject(string locale, string assetName, object data, Func<string, string> getNormalisedPath)
|
||||||
: base(locale, assetName, data, getNormalisedPath) { }
|
: base(locale, assetName, data, getNormalisedPath) { }
|
||||||
|
|
||||||
/// <summary>Get a helper to manipulate the data as a dictionary.</summary>
|
/// <summary>Get a helper to manipulate the data as a dictionary.</summary>
|
||||||
/// <typeparam name="TKey">The expected dictionary key.</typeparam>
|
/// <typeparam name="TKey">The expected dictionary key.</typeparam>
|
||||||
/// <typeparam name="TValue">The expected dictionary balue.</typeparam>
|
/// <typeparam name="TValue">The expected dictionary balue.</typeparam>
|
||||||
/// <exception cref="InvalidOperationException">The content being read isn't a dictionary.</exception>
|
/// <exception cref="InvalidOperationException">The content being read isn't a dictionary.</exception>
|
||||||
public IContentEventHelperForDictionary<TKey, TValue> AsDictionary<TKey, TValue>()
|
public IAssetDataForDictionary<TKey, TValue> AsDictionary<TKey, TValue>()
|
||||||
{
|
{
|
||||||
return new ContentEventHelperForDictionary<TKey, TValue>(this.Locale, this.AssetName, this.GetData<IDictionary<TKey, TValue>>(), this.GetNormalisedPath);
|
return new AssetDataForDictionary<TKey, TValue>(this.Locale, this.AssetName, this.GetData<IDictionary<TKey, TValue>>(), this.GetNormalisedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get a helper to manipulate the data as an image.</summary>
|
/// <summary>Get a helper to manipulate the data as an image.</summary>
|
||||||
/// <exception cref="InvalidOperationException">The content being read isn't an image.</exception>
|
/// <exception cref="InvalidOperationException">The content being read isn't an image.</exception>
|
||||||
public IContentEventHelperForImage AsImage()
|
public IAssetDataForImage AsImage()
|
||||||
{
|
{
|
||||||
return new ContentEventHelperForImage(this.Locale, this.AssetName, this.GetData<Texture2D>(), this.GetNormalisedPath);
|
return new AssetDataForImage(this.Locale, this.AssetName, this.GetData<Texture2D>(), this.GetNormalisedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get the data as a given type.</summary>
|
/// <summary>Get the data as a given type.</summary>
|
|
@ -4,9 +4,7 @@ using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Framework.Content
|
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>
|
internal class AssetInfo : IAssetInfo
|
||||||
/// <typeparam name="TValue">The interface value type.</typeparam>
|
|
||||||
internal class ContentEventData<TValue> : EventArgs, IContentEventData<TValue>
|
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
** Properties
|
** Properties
|
||||||
|
@ -24,9 +22,6 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
/// <summary>The normalised asset name being read. The format may change between platforms; see <see cref="IsAssetName"/> to compare with a known path.</summary>
|
/// <summary>The normalised asset name being read. The format may change between platforms; see <see cref="IsAssetName"/> to compare with a known path.</summary>
|
||||||
public string AssetName { get; }
|
public string AssetName { get; }
|
||||||
|
|
||||||
/// <summary>The content data being read.</summary>
|
|
||||||
public TValue Data { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>The content data type.</summary>
|
/// <summary>The content data type.</summary>
|
||||||
public Type DataType { get; }
|
public Type DataType { get; }
|
||||||
|
|
||||||
|
@ -37,23 +32,13 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="locale">The content's locale code, if the content is localised.</param>
|
/// <param name="locale">The content's locale code, if the content is localised.</param>
|
||||||
/// <param name="assetName">The normalised asset name being read.</param>
|
/// <param name="assetName">The normalised asset name being read.</param>
|
||||||
/// <param name="data">The content data being read.</param>
|
/// <param name="type">The content type being read.</param>
|
||||||
/// <param name="getNormalisedPath">Normalises an asset key to match the cache key.</param>
|
/// <param name="getNormalisedPath">Normalises an asset key to match the cache key.</param>
|
||||||
public ContentEventData(string locale, string assetName, TValue data, Func<string, string> getNormalisedPath)
|
public AssetInfo(string locale, string assetName, Type type, Func<string, string> getNormalisedPath)
|
||||||
: this(locale, assetName, data, data.GetType(), getNormalisedPath) { }
|
|
||||||
|
|
||||||
/// <summary>Construct an instance.</summary>
|
|
||||||
/// <param name="locale">The content's locale code, if the content is localised.</param>
|
|
||||||
/// <param name="assetName">The normalised asset name being read.</param>
|
|
||||||
/// <param name="data">The content data being read.</param>
|
|
||||||
/// <param name="dataType">The content data type being read.</param>
|
|
||||||
/// <param name="getNormalisedPath">Normalises an asset key to match the cache key.</param>
|
|
||||||
public ContentEventData(string locale, string assetName, TValue data, Type dataType, Func<string, string> getNormalisedPath)
|
|
||||||
{
|
{
|
||||||
this.Locale = locale;
|
this.Locale = locale;
|
||||||
this.AssetName = assetName;
|
this.AssetName = assetName;
|
||||||
this.Data = data;
|
this.DataType = type;
|
||||||
this.DataType = dataType;
|
|
||||||
this.GetNormalisedPath = getNormalisedPath;
|
this.GetNormalisedPath = getNormalisedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,20 +50,6 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
return this.AssetName.Equals(path, StringComparison.InvariantCultureIgnoreCase);
|
return this.AssetName.Equals(path, StringComparison.InvariantCultureIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game.</summary>
|
|
||||||
/// <param name="value">The new content value.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">The <paramref name="value"/> is null.</exception>
|
|
||||||
/// <exception cref="InvalidCastException">The <paramref name="value"/>'s type is not compatible with the loaded asset's type.</exception>
|
|
||||||
public void ReplaceWith(TValue value)
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
throw new ArgumentNullException(nameof(value), "Can't set a loaded asset to a null value.");
|
|
||||||
if (!this.DataType.IsInstanceOfType(value))
|
|
||||||
throw new InvalidCastException($"Can't replace loaded asset of type {this.GetFriendlyTypeName(this.DataType)} with value of type {this.GetFriendlyTypeName(value.GetType())}. The new type must be compatible to prevent game errors.");
|
|
||||||
|
|
||||||
this.Data = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Protected methods
|
** Protected methods
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -32,6 +33,13 @@ namespace StardewModdingAPI.Framework
|
||||||
private readonly string ModName;
|
private readonly string ModName;
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <summary>Editors which change content assets after they're loaded.</summary>
|
||||||
|
internal IList<IAssetEditor> AssetEditors { get; } = new List<IAssetEditor>();
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
||||||
*********/
|
*********/
|
||||||
|
|
|
@ -3,11 +3,9 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Content;
|
using Microsoft.Xna.Framework.Content;
|
||||||
using StardewModdingAPI.AssemblyRewriters;
|
using StardewModdingAPI.AssemblyRewriters;
|
||||||
using StardewModdingAPI.Events;
|
|
||||||
using StardewModdingAPI.Framework.Content;
|
using StardewModdingAPI.Framework.Content;
|
||||||
using StardewModdingAPI.Framework.Reflection;
|
using StardewModdingAPI.Framework.Reflection;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
|
@ -42,6 +40,9 @@ namespace StardewModdingAPI.Framework
|
||||||
/*********
|
/*********
|
||||||
** Accessors
|
** Accessors
|
||||||
*********/
|
*********/
|
||||||
|
/// <summary>Implementations which change assets after they're loaded.</summary>
|
||||||
|
internal IDictionary<IModMetadata, IList<IAssetEditor>> Editors { get; } = new Dictionary<IModMetadata, IList<IAssetEditor>>();
|
||||||
|
|
||||||
/// <summary>The absolute path to the <see cref="ContentManager.RootDirectory"/>.</summary>
|
/// <summary>The absolute path to the <see cref="ContentManager.RootDirectory"/>.</summary>
|
||||||
public string FullRootDirectory => Path.Combine(Constants.ExecutionPath, this.RootDirectory);
|
public string FullRootDirectory => Path.Combine(Constants.ExecutionPath, this.RootDirectory);
|
||||||
|
|
||||||
|
@ -49,13 +50,6 @@ namespace StardewModdingAPI.Framework
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Construct an instance.</summary>
|
|
||||||
/// <param name="serviceProvider">The service provider to use to locate services.</param>
|
|
||||||
/// <param name="rootDirectory">The root directory to search for content.</param>
|
|
||||||
/// <param name="monitor">Encapsulates monitoring and logging.</param>
|
|
||||||
public SContentManager(IServiceProvider serviceProvider, string rootDirectory, IMonitor monitor)
|
|
||||||
: this(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, null, monitor) { }
|
|
||||||
|
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="serviceProvider">The service provider to use to locate services.</param>
|
/// <param name="serviceProvider">The service provider to use to locate services.</param>
|
||||||
/// <param name="rootDirectory">The root directory to search for content.</param>
|
/// <param name="rootDirectory">The root directory to search for content.</param>
|
||||||
|
@ -66,8 +60,8 @@ namespace StardewModdingAPI.Framework
|
||||||
: base(serviceProvider, rootDirectory, currentCulture, languageCodeOverride)
|
: base(serviceProvider, rootDirectory, currentCulture, languageCodeOverride)
|
||||||
{
|
{
|
||||||
// initialise
|
// initialise
|
||||||
this.Monitor = monitor;
|
|
||||||
IReflectionHelper reflection = new ReflectionHelper();
|
IReflectionHelper reflection = new ReflectionHelper();
|
||||||
|
this.Monitor = monitor;
|
||||||
|
|
||||||
// get underlying fields for interception
|
// get underlying fields for interception
|
||||||
this.Cache = reflection.GetPrivateField<Dictionary<string, object>>(this, "loadedAssets").GetValue();
|
this.Cache = reflection.GetPrivateField<Dictionary<string, object>>(this, "loadedAssets").GetValue();
|
||||||
|
@ -125,14 +119,20 @@ namespace StardewModdingAPI.Framework
|
||||||
if (this.IsNormalisedKeyLoaded(assetName))
|
if (this.IsNormalisedKeyLoaded(assetName))
|
||||||
return base.Load<T>(assetName);
|
return base.Load<T>(assetName);
|
||||||
|
|
||||||
// load data
|
|
||||||
T data = base.Load<T>(assetName);
|
|
||||||
|
|
||||||
// let mods intercept content
|
// let mods intercept content
|
||||||
IContentEventHelper helper = new ContentEventHelper(cacheLocale, assetName, data, this.NormaliseAssetName);
|
IAssetInfo info = new AssetInfo(cacheLocale, assetName, typeof(T), this.NormaliseAssetName);
|
||||||
ContentEvents.InvokeAfterAssetLoaded(this.Monitor, helper);
|
Lazy<IAssetData> data = new Lazy<IAssetData>(() => new AssetDataForObject(info.Locale, info.AssetName, base.Load<T>(assetName), this.NormaliseAssetName));
|
||||||
this.Cache[assetName] = helper.Data;
|
if (this.TryOverrideAssetLoad(info, data, out T result))
|
||||||
return (T)helper.Data;
|
{
|
||||||
|
if (result == null)
|
||||||
|
throw new InvalidCastException($"Can't override asset '{assetName}' with a null value.");
|
||||||
|
|
||||||
|
this.Cache[assetName] = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to default behavior
|
||||||
|
return base.Load<T>(assetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Inject an asset into the cache.</summary>
|
/// <summary>Inject an asset into the cache.</summary>
|
||||||
|
@ -171,5 +171,35 @@ namespace StardewModdingAPI.Framework
|
||||||
? locale
|
? locale
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Try to override an asset being loaded.</summary>
|
||||||
|
/// <typeparam name="T">The asset type.</typeparam>
|
||||||
|
/// <param name="info">The asset metadata.</param>
|
||||||
|
/// <param name="data">The loaded asset data.</param>
|
||||||
|
/// <param name="result">The asset to use instead.</param>
|
||||||
|
/// <returns>Returns whether the asset should be overridden by <paramref name="result"/>.</returns>
|
||||||
|
private bool TryOverrideAssetLoad<T>(IAssetInfo info, Lazy<IAssetData> data, out T result)
|
||||||
|
{
|
||||||
|
bool edited = false;
|
||||||
|
|
||||||
|
// apply editors
|
||||||
|
foreach (var modEditors in this.Editors)
|
||||||
|
{
|
||||||
|
IModMetadata mod = modEditors.Key;
|
||||||
|
foreach (IAssetEditor editor in modEditors.Value)
|
||||||
|
{
|
||||||
|
if (!editor.CanEdit<T>(info))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.Monitor.Log($"{mod.DisplayName} intercepted {info.AssetName}.", LogLevel.Trace);
|
||||||
|
editor.Edit<T>(data.Value);
|
||||||
|
edited = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return result
|
||||||
|
result = edited ? (T)data.Value.Data : default(T);
|
||||||
|
return edited;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
@ -29,6 +30,9 @@ namespace StardewModdingAPI.Framework
|
||||||
/****
|
/****
|
||||||
** SMAPI state
|
** SMAPI state
|
||||||
****/
|
****/
|
||||||
|
/// <summary>Encapsulates monitoring and logging.</summary>
|
||||||
|
private readonly IMonitor Monitor;
|
||||||
|
|
||||||
/// <summary>The maximum number of consecutive attempts SMAPI should make to recover from a draw error.</summary>
|
/// <summary>The maximum number of consecutive attempts SMAPI should make to recover from a draw error.</summary>
|
||||||
private readonly Countdown DrawCrashTimer = new Countdown(60); // 60 ticks = roughly one second
|
private readonly Countdown DrawCrashTimer = new Countdown(60); // 60 ticks = roughly one second
|
||||||
|
|
||||||
|
@ -48,8 +52,6 @@ namespace StardewModdingAPI.Framework
|
||||||
/// <summary>Whether the game's zoom level is at 100% (i.e. nothing should be scaled).</summary>
|
/// <summary>Whether the game's zoom level is at 100% (i.e. nothing should be scaled).</summary>
|
||||||
public bool ZoomLevelIsOne => Game1.options.zoomLevel.Equals(1.0f);
|
public bool ZoomLevelIsOne => Game1.options.zoomLevel.Equals(1.0f);
|
||||||
|
|
||||||
/// <summary>Encapsulates monitoring and logging.</summary>
|
|
||||||
private readonly IMonitor Monitor;
|
|
||||||
|
|
||||||
/****
|
/****
|
||||||
** Game state
|
** Game state
|
||||||
|
@ -189,7 +191,7 @@ namespace StardewModdingAPI.Framework
|
||||||
// 2. Since Game1.content isn't initialised yet, and we need one main instance to
|
// 2. Since Game1.content isn't initialised yet, and we need one main instance to
|
||||||
// support custom map tilesheets, detect when Game1.content is being initialised
|
// support custom map tilesheets, detect when Game1.content is being initialised
|
||||||
// and use the same instance.
|
// and use the same instance.
|
||||||
this.Content = new SContentManager(this.Content.ServiceProvider, this.Content.RootDirectory, this.Monitor);
|
this.Content = new SContentManager(this.Content.ServiceProvider, this.Content.RootDirectory, Thread.CurrentThread.CurrentUICulture, null, this.Monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/****
|
/****
|
||||||
|
@ -206,7 +208,7 @@ namespace StardewModdingAPI.Framework
|
||||||
return mainContentManager;
|
return mainContentManager;
|
||||||
|
|
||||||
// build new instance
|
// build new instance
|
||||||
return new SContentManager(this.Content.ServiceProvider, this.Content.RootDirectory, this.Monitor);
|
return new SContentManager(this.Content.ServiceProvider, this.Content.RootDirectory, Thread.CurrentThread.CurrentUICulture, null, this.Monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>The method called when the game is updating its state. This happens roughly 60 times per second.</summary>
|
/// <summary>The method called when the game is updating its state. This happens roughly 60 times per second.</summary>
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <summary>The content data being read.</summary>
|
||||||
|
TValue Data { get; }
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game.</summary>
|
||||||
|
/// <param name="value">The new content value.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">The <paramref name="value"/> is null.</exception>
|
||||||
|
/// <exception cref="InvalidCastException">The <paramref name="value"/>'s type is not compatible with the loaded asset's type.</exception>
|
||||||
|
void ReplaceWith(TValue value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Generic metadata and methods for a content asset being loaded.</summary>
|
||||||
|
public interface IAssetData : IAssetData<object>
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Get a helper to manipulate the data as a dictionary.</summary>
|
||||||
|
/// <typeparam name="TKey">The expected dictionary key.</typeparam>
|
||||||
|
/// <typeparam name="TValue">The expected dictionary value.</typeparam>
|
||||||
|
/// <exception cref="InvalidOperationException">The content being read isn't a dictionary.</exception>
|
||||||
|
IAssetDataForDictionary<TKey, TValue> AsDictionary<TKey, TValue>();
|
||||||
|
|
||||||
|
/// <summary>Get a helper to manipulate the data as an image.</summary>
|
||||||
|
/// <exception cref="InvalidOperationException">The content being read isn't an image.</exception>
|
||||||
|
IAssetDataForImage AsImage();
|
||||||
|
|
||||||
|
/// <summary>Get the data as a given type.</summary>
|
||||||
|
/// <typeparam name="TData">The expected data type.</typeparam>
|
||||||
|
/// <exception cref="InvalidCastException">The data can't be converted to <typeparamref name="TData"/>.</exception>
|
||||||
|
TData GetData<TData>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ using System.Collections.Generic;
|
||||||
namespace StardewModdingAPI
|
namespace StardewModdingAPI
|
||||||
{
|
{
|
||||||
/// <summary>Encapsulates access and changes to dictionary content being read from a data file.</summary>
|
/// <summary>Encapsulates access and changes to dictionary content being read from a data file.</summary>
|
||||||
public interface IContentEventHelperForDictionary<TKey, TValue> : IContentEventData<IDictionary<TKey, TValue>>
|
public interface IAssetDataForDictionary<TKey, TValue> : IAssetData<IDictionary<TKey, TValue>>
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
|
@ -5,7 +5,7 @@ using Microsoft.Xna.Framework.Graphics;
|
||||||
namespace StardewModdingAPI
|
namespace StardewModdingAPI
|
||||||
{
|
{
|
||||||
/// <summary>Encapsulates access and changes to dictionary content being read from a data file.</summary>
|
/// <summary>Encapsulates access and changes to dictionary content being read from a data file.</summary>
|
||||||
public interface IContentEventHelperForImage : IContentEventData<Texture2D>
|
public interface IAssetDataForImage : IAssetData<Texture2D>
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
|
@ -0,0 +1,17 @@
|
||||||
|
namespace StardewModdingAPI
|
||||||
|
{
|
||||||
|
/// <summary>Edits a loaded content asset.</summary>
|
||||||
|
public interface IAssetEditor
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Get whether this instance can edit the given asset.</summary>
|
||||||
|
/// <param name="asset">Basic metadata about the asset being loaded.</param>
|
||||||
|
bool CanEdit<T>(IAssetInfo asset);
|
||||||
|
|
||||||
|
/// <summary>Edit a matched asset.</summary>
|
||||||
|
/// <param name="asset">A helper which encapsulates metadata about an asset and enables changes to it.</param>
|
||||||
|
void Edit<T>(IAssetData asset);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace StardewModdingAPI
|
namespace StardewModdingAPI
|
||||||
{
|
{
|
||||||
/// <summary>Generic metadata and methods for a content asset being loaded.</summary>
|
/// <summary>Basic metadata for a content asset.</summary>
|
||||||
/// <typeparam name="TValue">The expected data type.</typeparam>
|
public interface IAssetInfo
|
||||||
public interface IContentEventData<TValue>
|
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
** Accessors
|
** Accessors
|
||||||
|
@ -15,9 +14,6 @@ namespace StardewModdingAPI
|
||||||
/// <summary>The normalised asset name being read. The format may change between platforms; see <see cref="IsAssetName"/> to compare with a known path.</summary>
|
/// <summary>The normalised asset name being read. The format may change between platforms; see <see cref="IsAssetName"/> to compare with a known path.</summary>
|
||||||
string AssetName { get; }
|
string AssetName { get; }
|
||||||
|
|
||||||
/// <summary>The content data being read.</summary>
|
|
||||||
TValue Data { get; }
|
|
||||||
|
|
||||||
/// <summary>The content data type.</summary>
|
/// <summary>The content data type.</summary>
|
||||||
Type DataType { get; }
|
Type DataType { get; }
|
||||||
|
|
||||||
|
@ -28,11 +24,5 @@ namespace StardewModdingAPI
|
||||||
/// <summary>Get whether the asset name being loaded matches a given name after normalisation.</summary>
|
/// <summary>Get whether the asset name being loaded matches a given name after normalisation.</summary>
|
||||||
/// <param name="path">The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation').</param>
|
/// <param name="path">The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation').</param>
|
||||||
bool IsAssetName(string path);
|
bool IsAssetName(string path);
|
||||||
|
|
||||||
/// <summary>Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game.</summary>
|
|
||||||
/// <param name="value">The new content value.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">The <paramref name="value"/> is null.</exception>
|
|
||||||
/// <exception cref="InvalidCastException">The <paramref name="value"/>'s type is not compatible with the loaded asset's type.</exception>
|
|
||||||
void ReplaceWith(TValue value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,26 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace StardewModdingAPI
|
|
||||||
{
|
|
||||||
/// <summary>Encapsulates access and changes to content being read from a data file.</summary>
|
|
||||||
public interface IContentEventHelper : IContentEventData<object>
|
|
||||||
{
|
|
||||||
/*********
|
|
||||||
** Public methods
|
|
||||||
*********/
|
|
||||||
/// <summary>Get a helper to manipulate the data as a dictionary.</summary>
|
|
||||||
/// <typeparam name="TKey">The expected dictionary key.</typeparam>
|
|
||||||
/// <typeparam name="TValue">The expected dictionary balue.</typeparam>
|
|
||||||
/// <exception cref="InvalidOperationException">The content being read isn't a dictionary.</exception>
|
|
||||||
IContentEventHelperForDictionary<TKey, TValue> AsDictionary<TKey, TValue>();
|
|
||||||
|
|
||||||
/// <summary>Get a helper to manipulate the data as an image.</summary>
|
|
||||||
/// <exception cref="InvalidOperationException">The content being read isn't an image.</exception>
|
|
||||||
IContentEventHelperForImage AsImage();
|
|
||||||
|
|
||||||
/// <summary>Get the data as a given type.</summary>
|
|
||||||
/// <typeparam name="TData">The expected data type.</typeparam>
|
|
||||||
/// <exception cref="InvalidCastException">The data can't be converted to <typeparamref name="TData"/>.</exception>
|
|
||||||
TData GetData<TData>();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -330,7 +330,6 @@ namespace StardewModdingAPI
|
||||||
Config.Shim(this.DeprecationManager);
|
Config.Shim(this.DeprecationManager);
|
||||||
Log.Shim(this.DeprecationManager, this.GetSecondaryMonitor("legacy mod"), this.ModRegistry);
|
Log.Shim(this.DeprecationManager, this.GetSecondaryMonitor("legacy mod"), this.ModRegistry);
|
||||||
Mod.Shim(this.DeprecationManager);
|
Mod.Shim(this.DeprecationManager);
|
||||||
ContentEvents.Shim(this.ModRegistry, this.Monitor);
|
|
||||||
GameEvents.Shim(this.DeprecationManager);
|
GameEvents.Shim(this.DeprecationManager);
|
||||||
PlayerEvents.Shim(this.DeprecationManager);
|
PlayerEvents.Shim(this.DeprecationManager);
|
||||||
TimeEvents.Shim(this.DeprecationManager);
|
TimeEvents.Shim(this.DeprecationManager);
|
||||||
|
@ -489,7 +488,8 @@ namespace StardewModdingAPI
|
||||||
this.Monitor.Log("Detecting common issues...", LogLevel.Trace);
|
this.Monitor.Log("Detecting common issues...", LogLevel.Trace);
|
||||||
bool issuesFound = false;
|
bool issuesFound = false;
|
||||||
|
|
||||||
// object format (commonly broken by outdated mods)
|
|
||||||
|
// object format (commonly broken by outdated files)
|
||||||
{
|
{
|
||||||
// detect issues
|
// detect issues
|
||||||
bool hasObjectIssues = false;
|
bool hasObjectIssues = false;
|
||||||
|
@ -689,11 +689,14 @@ namespace StardewModdingAPI
|
||||||
// initialise loaded mods
|
// initialise loaded mods
|
||||||
foreach (IModMetadata metadata in this.ModRegistry.GetMods())
|
foreach (IModMetadata metadata in this.ModRegistry.GetMods())
|
||||||
{
|
{
|
||||||
|
// add interceptors
|
||||||
|
if (metadata.Mod.Helper.Content is ContentHelper helper)
|
||||||
|
this.ContentManager.Editors[metadata] = helper.AssetEditors;
|
||||||
|
|
||||||
|
// call entry method
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IMod mod = metadata.Mod;
|
IMod mod = metadata.Mod;
|
||||||
|
|
||||||
// call entry methods
|
|
||||||
(mod as Mod)?.Entry(); // deprecated since 1.0
|
(mod as Mod)?.Entry(); // deprecated since 1.0
|
||||||
mod.Entry(mod.Helper);
|
mod.Entry(mod.Helper);
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,7 @@
|
||||||
<Compile Include="ContentSource.cs" />
|
<Compile Include="ContentSource.cs" />
|
||||||
<Compile Include="Events\ContentEvents.cs" />
|
<Compile Include="Events\ContentEvents.cs" />
|
||||||
<Compile Include="Events\EventArgsValueChanged.cs" />
|
<Compile Include="Events\EventArgsValueChanged.cs" />
|
||||||
|
<Compile Include="Framework\Content\AssetInfo.cs" />
|
||||||
<Compile Include="Framework\Exceptions\SContentLoadException.cs" />
|
<Compile Include="Framework\Exceptions\SContentLoadException.cs" />
|
||||||
<Compile Include="Framework\Command.cs" />
|
<Compile Include="Framework\Command.cs" />
|
||||||
<Compile Include="Config.cs" />
|
<Compile Include="Config.cs" />
|
||||||
|
@ -136,10 +137,10 @@
|
||||||
<Compile Include="Framework\ModLoading\AssemblyParseResult.cs" />
|
<Compile Include="Framework\ModLoading\AssemblyParseResult.cs" />
|
||||||
<Compile Include="Framework\CommandManager.cs" />
|
<Compile Include="Framework\CommandManager.cs" />
|
||||||
<Compile Include="Framework\ContentHelper.cs" />
|
<Compile Include="Framework\ContentHelper.cs" />
|
||||||
<Compile Include="Framework\Content\ContentEventData.cs" />
|
<Compile Include="Framework\Content\AssetData.cs" />
|
||||||
<Compile Include="Framework\Content\ContentEventHelper.cs" />
|
<Compile Include="Framework\Content\AssetDataForObject.cs" />
|
||||||
<Compile Include="Framework\Content\ContentEventHelperForDictionary.cs" />
|
<Compile Include="Framework\Content\AssetDataForDictionary.cs" />
|
||||||
<Compile Include="Framework\Content\ContentEventHelperForImage.cs" />
|
<Compile Include="Framework\Content\AssetDataForImage.cs" />
|
||||||
<Compile Include="Context.cs" />
|
<Compile Include="Context.cs" />
|
||||||
<Compile Include="Framework\Logging\ConsoleInterceptionManager.cs" />
|
<Compile Include="Framework\Logging\ConsoleInterceptionManager.cs" />
|
||||||
<Compile Include="Framework\Logging\InterceptingTextWriter.cs" />
|
<Compile Include="Framework\Logging\InterceptingTextWriter.cs" />
|
||||||
|
@ -156,11 +157,12 @@
|
||||||
<Compile Include="Framework\Serialisation\SelectiveStringEnumConverter.cs" />
|
<Compile Include="Framework\Serialisation\SelectiveStringEnumConverter.cs" />
|
||||||
<Compile Include="Framework\Serialisation\ManifestFieldConverter.cs" />
|
<Compile Include="Framework\Serialisation\ManifestFieldConverter.cs" />
|
||||||
<Compile Include="Framework\TranslationHelper.cs" />
|
<Compile Include="Framework\TranslationHelper.cs" />
|
||||||
|
<Compile Include="IAssetEditor.cs" />
|
||||||
|
<Compile Include="IAssetInfo.cs" />
|
||||||
<Compile Include="ICommandHelper.cs" />
|
<Compile Include="ICommandHelper.cs" />
|
||||||
<Compile Include="IContentEventData.cs" />
|
<Compile Include="IAssetData.cs" />
|
||||||
<Compile Include="IContentEventHelper.cs" />
|
<Compile Include="IAssetDataForDictionary.cs" />
|
||||||
<Compile Include="IContentEventHelperForDictionary.cs" />
|
<Compile Include="IAssetDataForImage.cs" />
|
||||||
<Compile Include="IContentEventHelperForImage.cs" />
|
|
||||||
<Compile Include="IContentHelper.cs" />
|
<Compile Include="IContentHelper.cs" />
|
||||||
<Compile Include="IManifestDependency.cs" />
|
<Compile Include="IManifestDependency.cs" />
|
||||||
<Compile Include="IModRegistry.cs" />
|
<Compile Include="IModRegistry.cs" />
|
||||||
|
|
Loading…
Reference in New Issue