From 834f8452d1b88f34799a827284ca77bb7b82bd23 Mon Sep 17 00:00:00 2001 From: Trent Monahan Date: Sun, 21 Jun 2026 16:50:08 +1000 Subject: [PATCH] Change patch.reload to fully reload the content pack This enables the reload command to work on ConfigSchema, CustomLocations, Token Aliases and Dynamic Tokens --- .../Framework/Commands/CommandHandler.cs | 1 + .../Commands/Commands/ReloadCommand.cs | 28 +++++++++++-------- ContentPatcher/Framework/ConfigFileHandler.cs | 2 +- ...cModConfigMenuIntegrationForContentPack.cs | 2 +- ContentPatcher/Framework/LoadedContentPack.cs | 21 ++++++++++++-- .../Locations/CustomLocationManager.cs | 6 ++++ ContentPatcher/Framework/PatchLoader.cs | 1 + ContentPatcher/Framework/PatchManager.cs | 5 ++++ ContentPatcher/Framework/ScreenManager.cs | 17 +++++++++++ ContentPatcher/Framework/TokenManager.cs | 6 ++++ ContentPatcher/ModEntry.cs | 8 ++++-- 11 files changed, 78 insertions(+), 19 deletions(-) diff --git a/ContentPatcher/Framework/Commands/CommandHandler.cs b/ContentPatcher/Framework/Commands/CommandHandler.cs index 975e32aa3..0dfa9699d 100644 --- a/ContentPatcher/Framework/Commands/CommandHandler.cs +++ b/ContentPatcher/Framework/Commands/CommandHandler.cs @@ -57,6 +57,7 @@ private static ICommand[] BuildCommands(PerScreen screenManager, monitor: monitor, getPatchLoader: () => screenManager.Value.PatchLoader, getPatchManager: () => screenManager.Value.PatchManager, + getScreenManager: () => screenManager.Value, contentPacks: contentPacks, updateContext: updateContext ), diff --git a/ContentPatcher/Framework/Commands/Commands/ReloadCommand.cs b/ContentPatcher/Framework/Commands/Commands/ReloadCommand.cs index 16ed8db0f..e8662af8f 100644 --- a/ContentPatcher/Framework/Commands/Commands/ReloadCommand.cs +++ b/ContentPatcher/Framework/Commands/Commands/ReloadCommand.cs @@ -21,6 +21,9 @@ internal class ReloadCommand : BaseCommand /// Manages loaded patches. private readonly Func GetPatchManager; + /// Manages content assets for a screen. + private readonly Func GetScreenManager; + /// The loaded content packs. private readonly LoadedContentPack[] ContentPacks; @@ -37,11 +40,12 @@ internal class ReloadCommand : BaseCommand /// Manages loaded patches. /// The loaded content packs. /// A callback which immediately updates the current condition context. - public ReloadCommand(IMonitor monitor, Func getPatchLoader, Func getPatchManager, LoadedContentPack[] contentPacks, Action updateContext) + public ReloadCommand(IMonitor monitor, Func getPatchLoader, Func getPatchManager, Func getScreenManager, LoadedContentPack[] contentPacks, Action updateContext) : base(monitor, "reload") { this.GetPatchLoader = getPatchLoader; this.GetPatchManager = getPatchManager; + this.GetScreenManager = getScreenManager; this.ContentPacks = contentPacks; this.UpdateContext = updateContext; } @@ -65,6 +69,7 @@ public override void Handle(string[] args) { var patchLoader = this.GetPatchLoader(); var patchManager = this.GetPatchManager(); + var screenManager = this.GetScreenManager(); // get args string packId = ArgUtility.Get(args, 0, allowBlank: false); @@ -76,7 +81,7 @@ public override void Handle(string[] args) } // get pack - RawContentPack? pack = this.ContentPacks.SingleOrDefault(p => p.Manifest.UniqueID == packId); + LoadedContentPack? pack = this.ContentPacks.SingleOrDefault(p => p.Manifest.UniqueID == packId); if (pack == null) { this.Monitor.Log($"No Content Patcher content pack with the unique ID \"{packId}\".", LogLevel.Error); @@ -121,15 +126,16 @@ public override void Handle(string[] args) return; } - // reload patches - patchLoader.LoadPatches( - contentPack: pack, - rawPatches: pack.Content.Changes, - inheritLocalTokens: null, - rootIndexPath: [pack.Index], - path: new LogPathBuilder(pack.Manifest.Name), - parentPatch: null - ); + try + { + pack.ReloadConfig(); + } + catch (Exception ex) + { + this.Monitor.Log($"Error reloading configuration for content pack '{pack.ContentPack.Manifest.Name}'. Technical details:\n{ex}", LogLevel.Error); + } + + screenManager.ReloadContentPack(pack); } // make the changes apply diff --git a/ContentPatcher/Framework/ConfigFileHandler.cs b/ContentPatcher/Framework/ConfigFileHandler.cs index 04c59fb0d..7834820ff 100644 --- a/ContentPatcher/Framework/ConfigFileHandler.cs +++ b/ContentPatcher/Framework/ConfigFileHandler.cs @@ -54,7 +54,7 @@ public InvariantDictionary Read(IContentPack contentPack, Invariant /// The content pack. /// The configuration to save. /// The mod helper through which to save the file. - public void Save(IContentPack contentPack, InvariantDictionary config, IModHelper modHelper) + public void Save(IContentPack contentPack, InvariantDictionary config) { // save if settings valid if (config.Any()) diff --git a/ContentPatcher/Framework/GenericModConfigMenuIntegrationForContentPack.cs b/ContentPatcher/Framework/GenericModConfigMenuIntegrationForContentPack.cs index 2dff4c37c..97fea6395 100644 --- a/ContentPatcher/Framework/GenericModConfigMenuIntegrationForContentPack.cs +++ b/ContentPatcher/Framework/GenericModConfigMenuIntegrationForContentPack.cs @@ -20,7 +20,7 @@ internal class GenericModConfigMenuIntegrationForContentPack : IGenericModConfig private readonly IContentPack ContentPack; /// The config model. - private readonly InvariantDictionary Config; + public InvariantDictionary Config { get; set; } /// Parse a comma-delimited set of case-insensitive condition values. private readonly Func ParseCommaDelimitedField; diff --git a/ContentPatcher/Framework/LoadedContentPack.cs b/ContentPatcher/Framework/LoadedContentPack.cs index 51012356e..9ea85037b 100644 --- a/ContentPatcher/Framework/LoadedContentPack.cs +++ b/ContentPatcher/Framework/LoadedContentPack.cs @@ -1,5 +1,7 @@ using ContentPatcher.Framework.ConfigModels; +using Pathoschild.Stardew.Common.Integrations.GenericModConfigMenu; using Pathoschild.Stardew.Common.Utilities; +using StardewModdingAPI; namespace ContentPatcher.Framework; @@ -13,8 +15,10 @@ internal class LoadedContentPack : RawContentPack public ConfigFileHandler ConfigFileHandler { get; } /// The content pack's configuration. - public InvariantDictionary Config { get; } - + public InvariantDictionary Config { get; private set; } + public GenericModConfigMenuIntegrationForContentPack GMCMConfigMenu { get; internal set; } + public GenericModConfigMenuIntegration> GMCMAPI { get; internal set; } + private IMonitor Monitor { get; } /********* ** Public methods @@ -23,10 +27,21 @@ internal class LoadedContentPack : RawContentPack /// The raw content pack instance. /// Handles reading, normalizing, and saving the configuration for the content pack. /// The content pack's configuration. - public LoadedContentPack(RawContentPack contentPack, ConfigFileHandler configFileHandler, InvariantDictionary config) + public LoadedContentPack(RawContentPack contentPack, ConfigFileHandler configFileHandler, InvariantDictionary config, IMonitor monitor) : base(contentPack) { this.ConfigFileHandler = configFileHandler; this.Config = config; + this.Monitor = monitor; + } + + public void ReloadConfig() + { + var config = this.ConfigFileHandler.Read(this.ContentPack, this.Content.ConfigSchema, this.Content.Format!); + this.ConfigFileHandler.Save(this.ContentPack, config); + this.Config = config; + + this.GMCMConfigMenu.Config = config; + this.GMCMConfigMenu.Register(this.GMCMAPI, this.Monitor); } } diff --git a/ContentPatcher/Framework/Locations/CustomLocationManager.cs b/ContentPatcher/Framework/Locations/CustomLocationManager.cs index 95e595262..ef10cda24 100644 --- a/ContentPatcher/Framework/Locations/CustomLocationManager.cs +++ b/ContentPatcher/Framework/Locations/CustomLocationManager.cs @@ -7,6 +7,7 @@ using StardewModdingAPI; using StardewModdingAPI.Events; using StardewValley; +using StardewValley.Extensions; using StardewValley.GameData.Locations; using xTile; @@ -66,6 +67,11 @@ public bool TryAddCustomLocationConfig(CustomLocationConfig? config, IContentPac return true; } + public int ClearContentPack(IContentPack contentPack) + { + return this.CustomLocations.RemoveWhere(location => location.ContentPack.Manifest.UniqueID == contentPack.Manifest.UniqueID); + } + /// Enforce that custom location maps have a unique name. public void EnforceUniqueNames() { diff --git a/ContentPatcher/Framework/PatchLoader.cs b/ContentPatcher/Framework/PatchLoader.cs index fb874adfb..d5efe29a2 100644 --- a/ContentPatcher/Framework/PatchLoader.cs +++ b/ContentPatcher/Framework/PatchLoader.cs @@ -137,6 +137,7 @@ public void UnloadPatchesLoadedBy(IPatch parentPatch) public void UnloadPatchesLoadedBy(RawContentPack pack) { this.UnloadPatches(patch => patch.ContentPack == pack.ContentPack); + this.PatchManager.ClearPermanentlyDisabledPatchesForPack(pack.ContentPack); } /// Normalize and parse the given condition values. diff --git a/ContentPatcher/Framework/PatchManager.cs b/ContentPatcher/Framework/PatchManager.cs index b9e36d8f1..f82dd47ad 100644 --- a/ContentPatcher/Framework/PatchManager.cs +++ b/ContentPatcher/Framework/PatchManager.cs @@ -408,6 +408,11 @@ public IEnumerable GetPermanentlyDisabledPatches() return this.PermanentlyDisabledPatches; } + public void ClearPermanentlyDisabledPatchesForPack(IContentPack contentPack) + { + this.PermanentlyDisabledPatches.RemoveWhere(patch => patch.ContentPack.Manifest.UniqueID == contentPack.Manifest.UniqueID); + } + /// Get patches which load the given asset in the current context. /// The asset request being intercepted. public IEnumerable GetCurrentLoaders(AssetRequestedEventArgs request) diff --git a/ContentPatcher/Framework/ScreenManager.cs b/ContentPatcher/Framework/ScreenManager.cs index 0ff49f0bd..189168adb 100644 --- a/ContentPatcher/Framework/ScreenManager.cs +++ b/ContentPatcher/Framework/ScreenManager.cs @@ -55,6 +55,8 @@ internal class ScreenManager /// Whether has been called for this instance. public bool IsInitialized { get; private set; } + private IInvariantSet InstalledMods; + /********* ** Public methods @@ -70,6 +72,7 @@ public ScreenManager(IModHelper helper, IMonitor monitor, IInvariantSet installe { this.Helper = helper; this.Monitor = monitor; + this.InstalledMods = installedMods; this.TokenManager = new TokenManager(helper.GameContent, installedMods, modTokens); this.PatchManager = new PatchManager(this.Monitor, this.TokenManager, assetValidators, profiler); this.PatchLoader = new PatchLoader(this.PatchManager, this.TokenManager, this.Monitor, installedMods, helper.GameContent.ParseAssetName); @@ -215,6 +218,20 @@ public void UpdateContext(ContextUpdateType updateType) this.PatchManager.UpdateContext(this.Helper.GameContent, changedGlobalTokens, updateType); } + public void ReloadContentPack(LoadedContentPack contentPack) + { + this.TokenManager.ClearLocalToken(contentPack.ContentPack); + int oldLocations = this.CustomLocationManager.ClearContentPack(contentPack.ContentPack); + + this.LoadContentPacks([contentPack], this.InstalledMods); + + int newLocations = this.CustomLocationManager.GetCustomLocationData().Where(location => location.ContentPack.Manifest.UniqueID == contentPack.Manifest.UniqueID).Count(); + + if (oldLocations > 0 || newLocations > 0) + { + this.Helper.GameContent.InvalidateCache("Data/Locations"); + } + } /********* ** Private methods diff --git a/ContentPatcher/Framework/TokenManager.cs b/ContentPatcher/Framework/TokenManager.cs index 1c2c97e3d..681386274 100644 --- a/ContentPatcher/Framework/TokenManager.cs +++ b/ContentPatcher/Framework/TokenManager.cs @@ -76,6 +76,12 @@ public TokenManager(IGameContentHelper contentHelper, IInvariantSet installedMod this.GlobalContext.Save(new Token(valueProvider)); } + public bool ClearLocalToken(IContentPack pack) + { + string scope = pack.Manifest.UniqueID.Trim(); + return this.LocalTokens.Remove(scope); + } + /// Get the tokens which are defined for a specific content pack. This returns a reference to the list, which can be held for a live view of the tokens. If the content pack isn't currently tracked, this will add it. /// The content pack to manage. public ModTokenContext TrackLocalTokens(IContentPack pack) diff --git a/ContentPatcher/ModEntry.cs b/ContentPatcher/ModEntry.cs index a285b246b..6e15a8e8a 100644 --- a/ContentPatcher/ModEntry.cs +++ b/ContentPatcher/ModEntry.cs @@ -325,6 +325,8 @@ group token by token.Mod into modGroup break; var configMenu = new GenericModConfigMenuIntegrationForContentPack(contentPack.ContentPack, this.ParseCommaDelimitedField, config); + contentPack.GMCMAPI = api; + contentPack.GMCMConfigMenu = configMenu; configMenu.Register(api, this.Monitor); } } @@ -371,7 +373,7 @@ private IInvariantSet GetInstalledMods() private void OnContentPackConfigChanged(LoadedContentPack contentPack) { // re-save config.json - contentPack.ConfigFileHandler.Save(contentPack.ContentPack, contentPack.Config, this.Helper); + contentPack.ConfigFileHandler.Save(contentPack.ContentPack, contentPack.Config); // update tokens foreach (var screenManager in this.ScreenManager.GetActiveValues()) @@ -476,7 +478,7 @@ private IEnumerable GetContentPacks() { configFileHandler = new ConfigFileHandler("config.json", this.ParseCommaDelimitedField, (pack, label, reason) => this.Monitor.Log($"Ignored {pack.Manifest.Name} > {label}: {reason}", LogLevel.Warn)); config = configFileHandler.Read(contentPack, rawContentPack.Content.ConfigSchema, rawContentPack.Content.Format!); - configFileHandler.Save(contentPack, config, this.Helper); + configFileHandler.Save(contentPack, config); } catch (Exception ex) { @@ -485,7 +487,7 @@ private IEnumerable GetContentPacks() } // build content pack - yield return new LoadedContentPack(rawContentPack, configFileHandler, config); + yield return new LoadedContentPack(rawContentPack, configFileHandler, config, this.Monitor); } }