using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Microsoft.Xna.Framework.Graphics;
using Netcode;
using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Toolkit.Utilities;
using StardewValley;
using StardewValley.BellsAndWhistles;
using StardewValley.Buildings;
using StardewValley.Characters;
using StardewValley.GameData.Movies;
using StardewValley.Locations;
using StardewValley.Menus;
using StardewValley.Network;
using StardewValley.Objects;
using StardewValley.Projectiles;
using StardewValley.TerrainFeatures;
using xTile;
using xTile.Tiles;
namespace StardewModdingAPI.Metadata
{
/// <summary>Propagates changes to core assets to the game state.</summary>
internal class CoreAssetPropagator
{
/*********
** Fields
*********/
/// <summary>The main content manager through which to reload assets.</summary>
private readonly LocalizedContentManager MainContentManager;
/// <summary>An internal content manager used only for asset propagation. See remarks on <see cref="GameContentManagerForAssetPropagation"/>.</summary>
private readonly GameContentManagerForAssetPropagation DisposableContentManager;
/// <summary>Whether to enable more aggressive memory optimizations.</summary>
private readonly bool AggressiveMemoryOptimizations;
/// <summary>Normalizes an asset key to match the cache key and assert that it's valid.</summary>
private readonly Func<string, string> AssertAndNormalizeAssetName;
/// <summary>Simplifies access to private game code.</summary>
private readonly Reflector Reflection;
/// <summary>Optimized bucket categories for batch reloading assets.</summary>
private enum AssetBucket
{
/// <summary>NPC overworld sprites.</summary>
Sprite,
/// <summary>Villager dialogue portraits.</summary>
Portrait,
/// <summary>Any other asset.</summary>
Other
};
/*********
** Public methods
*********/
/// <summary>Initialize the core asset data.</summary>
/// <param name="mainContent">The main content manager through which to reload assets.</param>
/// <param name="disposableContent">An internal content manager used only for asset propagation.</param>
/// <param name="reflection">Simplifies access to private code.</param>
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
public CoreAssetPropagator(LocalizedContentManager mainContent, GameContentManagerForAssetPropagation disposableContent, Reflector reflection, bool aggressiveMemoryOptimizations)
{
this.MainContentManager = mainContent;
this.DisposableContentManager = disposableContent;
this.Reflection = reflection;
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations;
this.AssertAndNormalizeAssetName = disposableContent.AssertAndNormalizeAssetName;
}
/// <summary>Reload one of the game's core assets (if applicable).</summary>
/// <param name="assets">The asset keys and types to reload.</param>
/// <returns>Returns a lookup of asset names to whether they've been propagated.</returns>
public IDictionary<string, bool> Propagate(IDictionary<string, Type> assets)
{
// group into optimized lists
var buckets = assets.GroupBy(p =>
{
if (this.IsInFolder(p.Key, "Characters") || this.IsInFolder(p.Key, "Characters\\Monsters"))
return AssetBucket.Sprite;
if (this.IsInFolder(p.Key, "Portraits"))
return AssetBucket.Portrait;
return AssetBucket.Other;
});
// reload assets
IDictionary<string, bool> propagated = assets.ToDictionary(p => p.Key, _ => false, StringComparer.OrdinalIgnoreCase);
foreach (var bucket in buckets)
{
switch