using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Exceptions;
using StardewValley;
using xTile;
namespace StardewModdingAPI.Framework.ModHelpers
{
/// Provides an API for loading content assets.
internal class ContentHelper : BaseHelper, IContentHelper
{
/*********
** Fields
*********/
/// SMAPI's core content logic.
private readonly ContentCoordinator ContentCore;
/// A content manager for this mod which manages files from the game's Content folder.
private readonly IContentManager GameContentManager;
/// A content manager for this mod which manages files from the mod's folder.
private readonly ModContentManager ModContentManager;
/// The friendly mod name for use in errors.
private readonly string ModName;
/// Encapsulates monitoring and logging.
private readonly IMonitor Monitor;
/*********
** Accessors
*********/
///
public string CurrentLocale => this.GameContentManager.GetLocale();
///
public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.GameContentManager.Language;
/// The observable implementation of .
internal ObservableCollection ObservableAssetEditors { get; } = new ObservableCollection();
/// The observable implementation of .
internal ObservableCollection ObservableAssetLoaders { get; } = new ObservableCollection();
///
public IList AssetLoaders => this.ObservableAssetLoaders;
///
public IList AssetEditors => this.ObservableAssetEditors;
/*********
** Public methods
*********/
/// Construct an instance.
/// SMAPI's core content logic.
/// The absolute path to the mod folder.
/// The unique ID of the relevant mod.
/// The friendly mod name for use in errors.
/// Encapsulates monitoring and logging.
public ContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IMonitor monitor)
: base(modID)
{
string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID);
this.ContentCore = contentCore;
this.GameContentManager = contentCore.CreateGameContentManager(managedAssetPrefix + ".content");
this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, this.GameContentManager);
this.ModName = modName;
this.Monitor = monitor;
}
///
public T Load(string key, ContentSource source = ContentSource.ModFolder)
{
try
{
this.AssertAndNormalizeAssetName(key);
switch (source)
{
case ContentSource.GameContent:
return this.GameContentManager.Load(key, this.CurrentLocaleConstant, useCache: false);
case ContentSource.ModFolder:
return this.ModContentManager.Load(key, Constants.DefaultLanguage, useCache: false);
default:
throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}: unknown content source '{source}'.");
}
}
catch (Exception ex) when (!(ex is SContentLoadException))
{
throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}.", ex);
}
}
///
[Pure]
public string NormalizeAssetName(string assetName)
{
return this.ModContentManager.AssertAndNormalizeAssetName(assetName);
}
///
public string GetActualAssetKey(string key, ContentSource source = ContentSource.ModFolder)
{
switch (source)
{
case ContentSource.GameContent:
return this.GameContentManager.AssertAndNormalizeAssetName(key);
case ContentSource.ModFolder:
return this.ModContentManager.GetInternalAssetKey(key);
default:
throw new NotSupportedException($"Unknown content source '{source}'.");
}
}
///
public bool InvalidateCache(string key)
{
string actualKey = this.GetActualAssetKey(key, ContentSource.GameContent);
this.Monitor.Log($"Requested cache invalidation for '{actualKey}'.", LogLevel.Trace);
return this.ContentCore.InvalidateCache(asset => asset.AssetNameEquals(actualKey)).Any();
}
///
public bool InvalidateCache()
{
this.Monitor.Log($"Requested cache invalidation for all assets of type {typeof(T)}. This is an expensive operation and should be avoided if possible.", LogLevel.Trace);
return this.ContentCore.InvalidateCache((key, type) => typeof(T).IsAssignableFrom(type)).Any();
}
///
public bool InvalidateCache(Func predicate)
{
this.Monitor.Log("Requested cache invalidation for all assets matching a predicate.", LogLevel.Trace);
return this.ContentCore.InvalidateCache(predicate).Any();
}
///
public IAssetData GetPatchHelper(T data, string assetName = null)
{
if (data == null)
throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value.");
assetName ??= $"temp/{Guid.NewGuid():N}";
return new AssetDataForObject(this.CurrentLocale, assetName, data, this.NormalizeAssetName);
}
/*********
** Private methods
*********/
/// Assert that the given key has a valid format.
/// The asset key to check.
/// The asset key is empty or contains invalid characters.
[SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local", Justification = "Parameter is only used for assertion checks by design.")]
private void AssertAndNormalizeAssetName(string key)
{
this.ModContentManager.AssertAndNormalizeAssetName(key);
if (Path.IsPathRooted(key))
throw new ArgumentException("The asset key must not be an absolute path.");
}
}
}