/// <summary> /// Remove a game 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 name of the game asset whose game cache you want to invalidate. If you want to invalidate /// the game caches for all game assets this asset editor can edit, use <see cref="string.Empty"/>. /// </param> /// <exception cref="ArgumentNullException">The specified <paramref name="assetName"/> is <c>null</c>.</exception> /// <exception cref="ArgumentException">The specified <paramref name="assetName"/>is not the name of an asset this asset editor can edit.</exception> public void RequestAssetCacheRefresh(string assetName = "") { if (assetName == null) { throw new ArgumentNullException(nameof(assetName)); } if (assetName.Equals("")) { RequestAssetCacheRefresh(this.gameAssetNames.Values.ToList()); } else if (gameAssetNames.ContainsKey(assetName)) { contentHelper.InvalidateCache(assetName); } }
/// <summary>If none of the build in content handlers are sufficient, and making a custom one is overkill, this method lets you handle the injection for a specific type of asset.</summary> /// <typeparam name="T">The Type the asset is loaded as.</typeparam> /// <param name="helper">The <see cref="IContentHelper" /> this extension method is attached to.</param> /// <param name="assetInjector">The delegate assigned to handle loading for this type.</param> public static void RegisterInjector <T>(this IContentHelper helper, AssetInjector <T> assetInjector) { if (DeferredTypeHandler.EditMap.ContainsKey(typeof(T))) { DeferredTypeHandler.EditMap.Add(typeof(T), new List <Delegate>()); } DeferredTypeHandler.EditMap[typeof(T)].Add(assetInjector); helper.InvalidateCache <T>(); }
/// <summary> /// Lets you replace a region of pixels in one texture with the contents of another texture /// </summary> /// <param name="helper">The <see cref="IContentHelper"/> this extension method is attached to</param> /// <param name="assetName">The texture asset (Relative to Content and without extension) that you wish to modify</param> /// <param name="patchAssetName">The texture used for the modification</param> /// <param name="region">The area you wish to replace</param> /// <param name="source">The area you wish to use for replacement, if omitted the full patch texture is used</param> public static void RegisterTexturePatch(this IContentHelper helper, string assetName, Texture2D patchAsset, Rectangle?destination = null, Rectangle?source = null) { assetName = helper.GetActualAssetKey(assetName, ContentSource.GameContent); if (!TextureInjector._Map.ContainsKey(assetName)) { TextureInjector._Map.Add(assetName, new List <(Texture2D, Rectangle?, Rectangle?)>()); } TextureInjector._Map[assetName].Add((patchAsset, source, destination)); helper.InvalidateCache(assetName); }
/// <summary>If none of the build in content handlers are sufficient, and making a custom one is overkill, this method lets you handle the injection for one specific asset.</summary> /// <typeparam name="T">The Type the asset is loaded as.</typeparam> /// <param name="helper">The <see cref="IContentHelper" /> this extension method is attached to.</param> /// <param name="assetName">The asset (Relative to Content and without extension) to handle.</param> /// <param name="assetInjector">The delegate assigned to handle injection for this asset.</param> public static void RegisterInjector <T>(this IContentHelper helper, string assetName, AssetInjector <T> assetInjector) { assetName = helper.GetActualAssetKey(assetName, ContentSource.GameContent); if (!DeferredAssetHandler.EditMap.ContainsKey(assetName)) { DeferredAssetHandler.EditMap.Add(assetName, new List <DeferredAssetInfo>()); } DeferredAssetHandler.EditMap[assetName].Add(new DeferredAssetInfo(typeof(T), assetInjector)); helper.InvalidateCache(assetName); }
/// <summary>If none of the build in content handlers are sufficient, and making a custom one is overkill, this method lets you handle the loading for one specific asset.</summary> /// <typeparam name="T">The Type the asset is loaded as.</typeparam> /// <param name="helper">The <see cref="IContentHelper" /> this extension method is attached to.</param> /// <param name="assetName">The asset (Relative to Content and without extension) to handle.</param> /// <param name="assetLoader">The delegate assigned to handle loading for this asset.</param> public static void RegisterLoader <T>(this IContentHelper helper, string assetName, AssetLoader <T> assetLoader) { assetName = helper.GetActualAssetKey(assetName, ContentSource.GameContent); if (DeferredAssetHandler.LoadMap.ContainsKey(assetName)) { EntoaroxFrameworkMod.Logger.Log($"[IContentHelper] The `{Globals.GetModName(helper)}` mod\'s attempt to register a replacement asset for the `{assetName}` asset of type `{typeof(T).FullName}` failed, as another mod has already done so.", LogLevel.Error); } else { DeferredAssetHandler.LoadMap.Add(assetName, new DeferredAssetInfo(typeof(T), assetLoader)); helper.InvalidateCache(assetName); } }
/// <summary> /// Lets you define a xnb file to completely replace with another /// </summary> /// <param name="helper">The <see cref="IContentHelper"/> this extension method is attached to</param> /// <param name="assetName">The asset (Relative to Content and without extension) to replace</param> /// <param name="replacementAssetName">The asset (Relative to your mod directory and without extension) to use instead</param> public static void RegisterXnbReplacement(this IContentHelper helper, string assetName, string replacementAssetName) { assetName = helper.GetActualAssetKey(assetName, ContentSource.GameContent); replacementAssetName = helper.GetActualAssetKey(replacementAssetName, ContentSource.GameContent); if (XnbLoader._Map.ContainsKey(assetName)) { EntoaroxFrameworkMod.Logger.Log("[IContentHelper] The `" + Globals.GetModName(helper) + "` mod's attempt to register a replacement asset for the `" + assetName + "` asset failed, as another mod has already done so.", LogLevel.Error); } else { XnbLoader._Map.Add(assetName, (helper, replacementAssetName)); helper.InvalidateCache(assetName); } }
/// <summary>Get all texture asset names in the given content helper.</summary> /// <param name="contentHelper">The content helper to search.</param> private IEnumerable <string> GetTextureNames(IContentHelper contentHelper) { // get all texture keys from the content helper (this is such a hack) IList <string> textureKeys = new List <string>(); contentHelper.InvalidateCache(asset => { if (asset.DataType == typeof(Texture2D) && !asset.AssetName.Contains("..") && !asset.AssetName.StartsWith(Constants.ExecutionPath)) { textureKeys.Add(asset.AssetName); } return(false); }); return(textureKeys); }
public static void RegisterDictionaryPatch <TKey, TValue>(this IContentHelper helper, string assetName, Dictionary <TKey, TValue> patchAsset) { try { var check = Game1.content.Load <Dictionary <TKey, TValue> >(assetName); assetName = helper.GetActualAssetKey(assetName, ContentSource.GameContent); if (!DictionaryInjector._Map.ContainsKey(assetName)) { DictionaryInjector._Map.Add(assetName, new DictionaryWrapper <TKey, TValue>()); } (DictionaryInjector._Map[assetName] as DictionaryWrapper <TKey, TValue>).Register(helper, patchAsset); helper.InvalidateCache(assetName); } catch { EntoaroxFrameworkMod.Logger.Log("[IContentHelper] The `" + Globals.GetModName(helper) + "` mod's attempt to inject data into the `" + assetName + "` asset failed, as the TKey and TValue of the injected asset do not match the original.", LogLevel.Error); } }
/// <summary> /// Lets you define a xnb file to completely replace with another /// </summary> /// <param name="helper">The <see cref="IContentHelper"/> this extension method is attached to</param> /// <param name="assetMapping">Dictionary with the asset (Relative to Content and without extension) to replace as key, and the asset (Relative to your mod directory and without extension) to use instead as value</param> public static void RegisterXnbReplacements(this IContentHelper helper, Dictionary <string, string> assetMapping) { List <string> matchedAssets = new List <string>(); foreach (var pair in assetMapping) { string asset = helper.GetActualAssetKey(pair.Key, ContentSource.GameContent); string replacement = helper.GetActualAssetKey(pair.Value, ContentSource.GameContent); if (XnbLoader._Map.ContainsKey(asset)) { EntoaroxFrameworkMod.Logger.Log("[IContentHelper] The `" + Globals.GetModName(helper) + "` mod's attempt to register a replacement asset for the `" + pair.Key + "` asset failed, as another mod has already done so.", LogLevel.Error); } else { XnbLoader._Map.Add(asset, (helper, replacement)); matchedAssets.Add(asset); } } helper.InvalidateCache((assetInfo) => matchedAssets.Contains(assetInfo.AssetName)); }
/// <summary>Update the current context.</summary> /// <param name="contentHelper">The content helper which manages game assets.</param> /// <param name="language">The current language.</param> /// <param name="date">The current in-game date (if applicable).</param> /// <param name="weather">The current in-game weather (if applicable).</param> /// <param name="spouse">The current player's internal spouse name (if applicable).</param> /// <param name="dayEvent">The day event (e.g. wedding or festival) occurring today (if applicable).</param> /// <param name="seenEvents">The event IDs which the player has seen.</param> /// <param name="mailFlags">The mail flags set for the player.</param> /// <param name="friendships">The current player's friendship details.</param> public void UpdateContext(IContentHelper contentHelper, LocalizedContentManager.LanguageCode language, SDate date, Weather?weather, string dayEvent, string spouse, int[] seenEvents, string[] mailFlags, IEnumerable <KeyValuePair <string, Friendship> > friendships) { this.VerboseLog("Propagating context..."); // update context this.ConditionContext.Set(language: language, date: date, weather: weather, dayEvent: dayEvent, spouse: spouse, seenEvents: seenEvents, mailFlags: mailFlags, friendships: friendships); IDictionary <ConditionKey, string> tokenisableConditions = this.ConditionContext.GetSingleValueConditions(); // update patches InvariantHashSet reloadAssetNames = new InvariantHashSet(); string prevAssetName = null; foreach (IPatch patch in this.Patches.OrderBy(p => p.AssetName).ThenBy(p => p.LogName)) { // log asset name if (this.Verbose && prevAssetName != patch.AssetName) { this.VerboseLog($" {patch.AssetName}:"); prevAssetName = patch.AssetName; } // track old values string wasAssetName = patch.AssetName; bool wasApplied = patch.MatchesContext; // update patch bool changed = patch.UpdateContext(this.ConditionContext, tokenisableConditions); bool shouldApply = patch.MatchesContext; // track patches to reload bool reload = (wasApplied && changed) || (!wasApplied && shouldApply); if (reload) { patch.IsApplied = false; if (wasApplied) { reloadAssetNames.Add(wasAssetName); } if (shouldApply) { reloadAssetNames.Add(patch.AssetName); } } // log change if (this.Verbose) { IList <string> changes = new List <string>(); if (wasApplied != shouldApply) { changes.Add(shouldApply ? "enabled" : "disabled"); } if (wasAssetName != patch.AssetName) { changes.Add($"target: {wasAssetName} => {patch.AssetName}"); } string changesStr = string.Join(", ", changes); this.VerboseLog($" [{(shouldApply ? "X" : " ")}] {patch.LogName}: {(changes.Any() ? changesStr : "OK")}"); } } // rebuild asset name lookup this.PatchesByCurrentTarget = new InvariantDictionary <HashSet <IPatch> >( from patchGroup in this.Patches.GroupBy(p => p.AssetName, StringComparer.InvariantCultureIgnoreCase) let key = patchGroup.Key let value = new HashSet <IPatch>(patchGroup) select new KeyValuePair <string, HashSet <IPatch> >(key, value) ); // reload assets if needed if (reloadAssetNames.Any()) { this.VerboseLog($" reloading {reloadAssetNames.Count} assets: {string.Join(", ", reloadAssetNames.OrderBy(p => p))}"); contentHelper.InvalidateCache(asset => { this.VerboseLog($" [{(reloadAssetNames.Contains(asset.AssetName) ? "X" : " ")}] reload {asset.AssetName}"); return(reloadAssetNames.Contains(asset.AssetName)); }); } }
/// <summary>Update the current context.</summary> /// <param name="contentHelper">The content helper through which to invalidate assets.</param> public void UpdateContext(IContentHelper contentHelper) { this.VerboseLog("Propagating context..."); // update patches InvariantHashSet reloadAssetNames = new InvariantHashSet(); string prevAssetName = null; foreach (IPatch patch in this.Patches.OrderByIgnoreCase(p => p.AssetName).ThenByIgnoreCase(p => p.LogName)) { // log asset name if (this.Verbose && prevAssetName != patch.AssetName) { this.VerboseLog($" {patch.AssetName}:"); prevAssetName = patch.AssetName; } // track old values string wasAssetName = patch.AssetName; bool wasApplied = patch.MatchesContext; // update patch IContext tokenContext = this.TokenManager.TrackLocalTokens(patch.ContentPack.Pack); bool changed = patch.UpdateContext(tokenContext); bool shouldApply = patch.MatchesContext; // track patches to reload bool reload = (wasApplied && changed) || (!wasApplied && shouldApply); if (reload) { patch.IsApplied = false; if (wasApplied) { reloadAssetNames.Add(wasAssetName); } if (shouldApply) { reloadAssetNames.Add(patch.AssetName); } } // log change if (this.Verbose) { IList <string> changes = new List <string>(); if (wasApplied != shouldApply) { changes.Add(shouldApply ? "enabled" : "disabled"); } if (wasAssetName != patch.AssetName) { changes.Add($"target: {wasAssetName} => {patch.AssetName}"); } string changesStr = string.Join(", ", changes); this.VerboseLog($" [{(shouldApply ? "X" : " ")}] {patch.LogName}: {(changes.Any() ? changesStr : "OK")}"); } // warn for invalid load patch if (patch is LoadPatch loadPatch && patch.MatchesContext && !patch.ContentPack.FileExists(loadPatch.LocalAsset.Value)) { this.Monitor.Log($"Patch error: {patch.LogName} has a {nameof(PatchConfig.FromFile)} which matches non-existent file '{loadPatch.LocalAsset.Value}'.", LogLevel.Error); } } // rebuild asset name lookup this.PatchesByCurrentTarget = new InvariantDictionary <HashSet <IPatch> >( from patchGroup in this.Patches.GroupByIgnoreCase(p => p.AssetName) let key = patchGroup.Key let value = new HashSet <IPatch>(patchGroup) select new KeyValuePair <string, HashSet <IPatch> >(key, value) ); // reload assets if needed if (reloadAssetNames.Any()) { this.VerboseLog($" reloading {reloadAssetNames.Count} assets: {string.Join(", ", reloadAssetNames.OrderByIgnoreCase(p => p))}"); contentHelper.InvalidateCache(asset => { this.VerboseLog($" [{(reloadAssetNames.Contains(asset.AssetName) ? "X" : " ")}] reload {asset.AssetName}"); return(reloadAssetNames.Contains(asset.AssetName)); }); } }