From 63111621c9375ac2e9a68eefa73ffe1d817000dd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 23 Dec 2020 19:11:41 -0500 Subject: fix world events not raised for volcano levels --- .../Framework/StateTracking/WorldLocationsTracker.cs | 15 ++++++++++++++- src/SMAPI/Framework/WatcherCore.cs | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs index 303a4f3a..e968d79c 100644 --- a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs +++ b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs @@ -21,6 +21,9 @@ namespace StardewModdingAPI.Framework.StateTracking /// Tracks changes to the list of active mine locations. private readonly ICollectionWatcher MineLocationListWatcher; + /// Tracks changes to the list of active volcano locations. + private readonly ICollectionWatcher VolcanoLocationListWatcher; + /// A lookup of the tracked locations. private IDictionary LocationDict { get; } = new Dictionary(new ObjectReferenceComparer()); @@ -53,10 +56,12 @@ namespace StardewModdingAPI.Framework.StateTracking /// Construct an instance. /// The game's list of locations. /// The game's list of active mine locations. - public WorldLocationsTracker(ObservableCollection locations, IList activeMineLocations) + /// The game's list of active volcano locations. + public WorldLocationsTracker(ObservableCollection locations, IList activeMineLocations, IList activeVolcanoLocations) { this.LocationListWatcher = WatcherFactory.ForObservableCollection(locations); this.MineLocationListWatcher = WatcherFactory.ForReferenceList(activeMineLocations); + this.VolcanoLocationListWatcher = WatcherFactory.ForReferenceList(activeVolcanoLocations); } /// Update the current value if needed. @@ -65,6 +70,7 @@ namespace StardewModdingAPI.Framework.StateTracking // update watchers this.LocationListWatcher.Update(); this.MineLocationListWatcher.Update(); + this.VolcanoLocationListWatcher.Update(); foreach (LocationTracker watcher in this.Locations) watcher.Update(); @@ -79,6 +85,11 @@ namespace StardewModdingAPI.Framework.StateTracking this.Remove(this.MineLocationListWatcher.Removed); this.Add(this.MineLocationListWatcher.Added); } + if (this.VolcanoLocationListWatcher.IsChanged) + { + this.Remove(this.VolcanoLocationListWatcher.Removed); + this.Add(this.VolcanoLocationListWatcher.Added); + } // detect building changed foreach (LocationTracker watcher in this.Locations.Where(p => p.BuildingsWatcher.IsChanged).ToArray()) @@ -107,6 +118,7 @@ namespace StardewModdingAPI.Framework.StateTracking this.Added.Clear(); this.LocationListWatcher.Reset(); this.MineLocationListWatcher.Reset(); + this.VolcanoLocationListWatcher.Reset(); } /// Set the current value as the baseline. @@ -243,6 +255,7 @@ namespace StardewModdingAPI.Framework.StateTracking { yield return this.LocationListWatcher; yield return this.MineLocationListWatcher; + yield return this.VolcanoLocationListWatcher; foreach (LocationTracker watcher in this.Locations) yield return watcher; } diff --git a/src/SMAPI/Framework/WatcherCore.cs b/src/SMAPI/Framework/WatcherCore.cs index 393f6a37..62a0c3b8 100644 --- a/src/SMAPI/Framework/WatcherCore.cs +++ b/src/SMAPI/Framework/WatcherCore.cs @@ -66,7 +66,7 @@ namespace StardewModdingAPI.Framework this.WindowSizeWatcher = WatcherFactory.ForEquatable(() => new Point(Game1.viewport.Width, Game1.viewport.Height)); this.TimeWatcher = WatcherFactory.ForEquatable(() => Game1.timeOfDay); this.ActiveMenuWatcher = WatcherFactory.ForReference(() => Game1.activeClickableMenu); - this.LocationsWatcher = new WorldLocationsTracker(gameLocations, MineShaft.activeMines); + this.LocationsWatcher = new WorldLocationsTracker(gameLocations, MineShaft.activeMines, VolcanoDungeon.activeLevels); this.LocaleWatcher = WatcherFactory.ForGenericEquality(() => LocalizedContentManager.CurrentLanguageCode); this.Watchers.AddRange(new IWatcher[] { -- cgit From 2406380495cc5176d3cbd2309e6f17080389f9af Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Dec 2020 01:28:00 -0500 Subject: fix SMAPI using a cached translation when the game asks for an untranslated asset This mainly affects community center bundles in Stardew Valley 1.5, --- docs/release-notes.md | 5 ++++- src/SMAPI/Framework/ContentCoordinator.cs | 2 +- src/SMAPI/Framework/ContentManagers/BaseContentManager.cs | 8 +++++--- src/SMAPI/Framework/ContentManagers/GameContentManager.cs | 9 +++++---- src/SMAPI/Framework/ContentManagers/IContentManager.cs | 3 ++- src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 3 ++- 6 files changed, 19 insertions(+), 11 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index e464154f..2df7467a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,8 +8,11 @@ --> ## Upcoming release +* For players: + * Fixed community center bundle corruption for non-English players. + * For modders: - * Fixed world events not raised for volcano levels. + * World events are now raised for the volcano levels. For the web UI: * Fixed edge cases in SMAPI log parsing. diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 93371415..f9027972 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -278,7 +278,7 @@ namespace StardewModdingAPI.Framework return this.ContentManagerLock.InReadLock(() => { List values = new List(); - foreach (IContentManager content in this.ContentManagers.Where(p => !p.IsNamespaced && p.IsLoaded(assetName))) + foreach (IContentManager content in this.ContentManagers.Where(p => !p.IsNamespaced && p.IsLoaded(assetName, p.Language))) { object value = content.Load(assetName, this.Language, useCache: true); values.Add(value); diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 6bc3a505..92264f8c 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -169,10 +169,11 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get whether the content manager has already loaded and cached the given asset. /// The asset path relative to the loader root directory, not including the .xnb extension. - public bool IsLoaded(string assetName) + /// The language. + public bool IsLoaded(string assetName, LanguageCode language) { assetName = this.Cache.NormalizeKey(assetName); - return this.IsNormalizedKeyLoaded(assetName); + return this.IsNormalizedKeyLoaded(assetName, language); } /// Get the cached asset keys. @@ -315,7 +316,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get whether an asset has already been loaded. /// The normalized asset name. - protected abstract bool IsNormalizedKeyLoaded(string normalizedAssetName); + /// The language to check. + protected abstract bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language); /// Get the locale codes (like ja-JP) used in asset keys. private IDictionary GetKeyLocales() diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 83a63986..ad8f2ef1 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -78,7 +78,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return this.Load(newAssetName, newLanguage, useCache); // get from cache - if (useCache && this.IsLoaded(assetName)) + if (useCache && this.IsLoaded(assetName, language)) return this.RawLoad(assetName, language, useCache: true); // get managed asset @@ -151,11 +151,12 @@ namespace StardewModdingAPI.Framework.ContentManagers *********/ /// Get whether an asset has already been loaded. /// The normalized asset name. - protected override bool IsNormalizedKeyLoaded(string normalizedAssetName) + /// The language to check. + protected override bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language) { string cachedKey = null; bool localized = - this.Language != LocalizedContentManager.LanguageCode.en + language != LocalizedContentManager.LanguageCode.en && !this.Coordinator.IsManagedAssetKey(normalizedAssetName) && this.LocalizedAssetNames.TryGetValue(normalizedAssetName, out cachedKey); @@ -214,7 +215,7 @@ namespace StardewModdingAPI.Framework.ContentManagers private T RawLoad(string assetName, LanguageCode language, bool useCache) { // use cached key - if (this.LocalizedAssetNames.TryGetValue(assetName, out string cachedKey)) + if (language == this.Language && this.LocalizedAssetNames.TryGetValue(assetName, out string cachedKey)) return base.RawLoad(cachedKey, useCache); // try translated key diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index 8da9a777..0e7edd8f 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -58,7 +58,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get whether the content manager has already loaded and cached the given asset. /// The asset path relative to the loader root directory, not including the .xnb extension. - bool IsLoaded(string assetName); + /// The language. + bool IsLoaded(string assetName, LocalizedContentManager.LanguageCode language); /// Get the cached asset keys. IEnumerable GetAssetKeys(); diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 127705ea..753ec188 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -211,7 +211,8 @@ namespace StardewModdingAPI.Framework.ContentManagers *********/ /// Get whether an asset has already been loaded. /// The normalized asset name. - protected override bool IsNormalizedKeyLoaded(string normalizedAssetName) + /// The language to check. + protected override bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language) { return this.Cache.ContainsKey(normalizedAssetName); } -- cgit From 49c192fc4772417428d490dabf93b790f82c94c9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Dec 2020 02:08:53 -0500 Subject: detect & fix broken community center bundles --- docs/release-notes.md | 2 +- src/SMAPI/Framework/SCore.cs | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index be51695e..fe614303 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,7 +9,7 @@ ## Upcoming release * For players: - * Fixed community center bundle corruption for non-English players. + * Fixed community center bundles broken for non-English saves created in SMAPI 3.8.0. Affected saves will be fixed automatically next time you load them. * For modders: * World events are now raised for the volcano levels. diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index a7f8fbed..6b8098cd 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -765,6 +765,9 @@ namespace StardewModdingAPI.Framework this.Monitor.Log(context); + // apply save fixes + this.ApplySaveFixes(); + // raise events this.OnLoadStageChanged(LoadStage.Ready); events.SaveLoaded.RaiseEmpty(); @@ -1054,6 +1057,44 @@ namespace StardewModdingAPI.Framework this.EventManager.ReturnedToTitle.RaiseEmpty(); } + /// Apply fixes to the save after it's loaded. + private void ApplySaveFixes() + { + // get last SMAPI version used with this save + const string migrationKey = "Pathoschild.SMAPI/last-version"; + if (!Game1.CustomData.TryGetValue(migrationKey, out string rawVersion) || !SemanticVersion.TryParse(rawVersion, out ISemanticVersion lastVersionRun)) + lastVersionRun = new SemanticVersion(3, 8, 0); + + // fix bundle corruption in SMAPI 3.8.0 + // For non-English players who created a new save in SMAPI 3.8.0, bundle data was + // incorrectly translated which caused the code to crash whenever the game tried to + // read it. + if (lastVersionRun.IsOlderThan(new SemanticVersion(3, 8, 1))) + { + bool? hasInvalidBundleData = Game1.netWorldState?.Value + ?.BundleData + ?.Values + ?.Any(raw => raw != null && raw.Split('/').Length > 5); + + if (hasInvalidBundleData == true) + { + try + { + Game1.applySaveFix(SaveGame.SaveFixes.FixBotchedBundleData); + this.Monitor.Log("Found corrupted community center data due to a previous version of SMAPI, and fixed it automatically.", LogLevel.Info); + } + catch (Exception ex) + { + this.Monitor.Log("Found corrupted community center data due to a previous version of SMAPI, but was unable to fix it automatically.", LogLevel.Error); + this.Monitor.Log($"Technical details: {ex}"); + } + } + } + + // update last run + Game1.CustomData[migrationKey] = Constants.ApiVersion.ToString(); + } + /// Raised after custom content is removed from the save data to avoid a crash. internal void OnSaveContentRemoved() { -- cgit From 8895021696fd2129af6de775e275f3bd83edf034 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Dec 2020 10:41:39 -0500 Subject: rewrite migration to avoid repeating game checks --- src/SMAPI/Framework/SCore.cs | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 6b8098cd..e05213f0 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1061,33 +1061,29 @@ namespace StardewModdingAPI.Framework private void ApplySaveFixes() { // get last SMAPI version used with this save - const string migrationKey = "Pathoschild.SMAPI/last-version"; - if (!Game1.CustomData.TryGetValue(migrationKey, out string rawVersion) || !SemanticVersion.TryParse(rawVersion, out ISemanticVersion lastVersionRun)) - lastVersionRun = new SemanticVersion(3, 8, 0); + const string migrationKey = "Pathoschild.SMAPI/api-version"; + if (!Game1.CustomData.TryGetValue(migrationKey, out string rawVersion) || !SemanticVersion.TryParse(rawVersion, out ISemanticVersion lastVersion)) + lastVersion = new SemanticVersion(3, 8, 0); // fix bundle corruption in SMAPI 3.8.0 // For non-English players who created a new save in SMAPI 3.8.0, bundle data was // incorrectly translated which caused the code to crash whenever the game tried to // read it. - if (lastVersionRun.IsOlderThan(new SemanticVersion(3, 8, 1))) + if (lastVersion.IsOlderThan(new SemanticVersion(3, 8, 1)) && Game1.netWorldState?.Value?.BundleData != null) { - bool? hasInvalidBundleData = Game1.netWorldState?.Value - ?.BundleData - ?.Values - ?.Any(raw => raw != null && raw.Split('/').Length > 5); + var oldData = new Dictionary(Game1.netWorldState.Value.BundleData); - if (hasInvalidBundleData == true) + try { - try - { - Game1.applySaveFix(SaveGame.SaveFixes.FixBotchedBundleData); - this.Monitor.Log("Found corrupted community center data due to a previous version of SMAPI, and fixed it automatically.", LogLevel.Info); - } - catch (Exception ex) - { - this.Monitor.Log("Found corrupted community center data due to a previous version of SMAPI, but was unable to fix it automatically.", LogLevel.Error); - this.Monitor.Log($"Technical details: {ex}"); - } + Game1.applySaveFix(SaveGame.SaveFixes.FixBotchedBundleData); + bool changed = Game1.netWorldState.Value.BundleData.Any(p => oldData.TryGetValue(p.Key, out string oldValue) && oldValue != p.Value); + if (changed) + this.Monitor.Log("Found broken community center bundles and fixed them automatically.", LogLevel.Info); + } + catch (Exception ex) + { + this.Monitor.Log("Failed to verify community center data.", LogLevel.Error); // should never happen + this.Monitor.Log($"Technical details: {ex}"); } } -- cgit From 5cc069476e1cb63c8fef2dd245a540b6b5130e68 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Dec 2020 11:20:47 -0500 Subject: deprecate ConsoleCommands.Trigger method --- docs/release-notes.md | 1 + src/SMAPI/Framework/DeprecationManager.cs | 31 ++++++++++--------------- src/SMAPI/Framework/ModHelpers/CommandHelper.cs | 8 +++++++ 3 files changed, 21 insertions(+), 19 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index fe614303..0cc45116 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -14,6 +14,7 @@ * For modders: * World events are now raised for the volcano levels. * Added `apply_save_fix` command to reapply a save migration in exceptional cases. This should be used with extreme care. Type `help apply_save_fix` for details. + * **Deprecation notice:** the `Helper.ConsoleCommands.Trigger` method is now deprecated and should no longer be used. See [integration APIs](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations) for better mod integration options. It will eventually be removed in SMAPI 4.0. For the web UI: * Fixed edge cases in SMAPI log parsing. diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index 94a2da85..c22b5718 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -35,19 +35,17 @@ namespace StardewModdingAPI.Framework this.ModRegistry = modRegistry; } - /// Log a deprecation warning for the old-style events. - public void WarnForOldEvents() + /// Get the source name for a mod from its unique ID. + public string GetSourceNameFromStack() { - this.Warn("legacy events", "2.9", DeprecationLevel.PendingRemoval); + return this.ModRegistry.GetFromStack()?.DisplayName; } - /// Log a deprecation warning. - /// A noun phrase describing what is deprecated. - /// The SMAPI version which deprecated it. - /// How deprecated the code is. - public void Warn(string nounPhrase, string version, DeprecationLevel severity) + /// Get the source name for a mod from its unique ID. + /// The mod's unique ID. + public string GetSourceName(string modId) { - this.Warn(this.ModRegistry.GetFromStack()?.DisplayName, nounPhrase, version, severity); + return this.ModRegistry.Get(modId)?.DisplayName; } /// Log a deprecation warning. @@ -58,7 +56,7 @@ namespace StardewModdingAPI.Framework public void Warn(string source, string nounPhrase, string version, DeprecationLevel severity) { // ignore if already warned - if (!this.MarkWarned(source ?? "", nounPhrase, version)) + if (!this.MarkWarned(source ?? this.GetSourceNameFromStack() ?? "", nounPhrase, version)) return; // queue warning @@ -111,21 +109,16 @@ namespace StardewModdingAPI.Framework this.QueuedWarnings.Clear(); } - /// Mark a deprecation warning as already logged. - /// A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method"). - /// The SMAPI version which deprecated it. - /// Returns whether the deprecation was successfully marked as warned. Returns false if it was already marked. - public bool MarkWarned(string nounPhrase, string version) - { - return this.MarkWarned(this.ModRegistry.GetFromStack()?.DisplayName, nounPhrase, version); - } + /********* + ** Private methods + *********/ /// Mark a deprecation warning as already logged. /// The friendly name of the assembly which used the deprecated code. /// A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method"). /// The SMAPI version which deprecated it. /// Returns whether the deprecation was successfully marked as warned. Returns false if it was already marked. - public bool MarkWarned(string source, string nounPhrase, string version) + private bool MarkWarned(string source, string nounPhrase, string version) { if (string.IsNullOrWhiteSpace(source)) throw new InvalidOperationException("The deprecation source cannot be empty."); diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index 600f867f..69382009 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -36,8 +36,16 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// + [Obsolete] public bool Trigger(string name, string[] arguments) { + SCore.DeprecationManager.Warn( + source: SCore.DeprecationManager.GetSourceName(this.ModID), + nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.ConsoleCommands)}.{nameof(ICommandHelper.Trigger)}", + version: "3.8.1", + severity: DeprecationLevel.Notice + ); + return this.CommandManager.Trigger(name, arguments); } } -- cgit