diff options
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r-- | src/SMAPI/Framework/ContentCoordinator.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/ContentManagers/BaseContentManager.cs | 8 | ||||
-rw-r--r-- | src/SMAPI/Framework/ContentManagers/GameContentManager.cs | 9 | ||||
-rw-r--r-- | src/SMAPI/Framework/ContentManagers/IContentManager.cs | 3 | ||||
-rw-r--r-- | src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 3 | ||||
-rw-r--r-- | src/SMAPI/Framework/DeprecationManager.cs | 31 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModHelpers/CommandHelper.cs | 8 | ||||
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 37 | ||||
-rw-r--r-- | src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs | 15 | ||||
-rw-r--r-- | src/SMAPI/Framework/WatcherCore.cs | 2 |
10 files changed, 87 insertions, 31 deletions
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<object> values = new List<object>(); - 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<object>(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 /// <summary>Get whether the content manager has already loaded and cached the given asset.</summary> /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param> - public bool IsLoaded(string assetName) + /// <param name="language">The language.</param> + public bool IsLoaded(string assetName, LanguageCode language) { assetName = this.Cache.NormalizeKey(assetName); - return this.IsNormalizedKeyLoaded(assetName); + return this.IsNormalizedKeyLoaded(assetName, language); } /// <summary>Get the cached asset keys.</summary> @@ -315,7 +316,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <summary>Get whether an asset has already been loaded.</summary> /// <param name="normalizedAssetName">The normalized asset name.</param> - protected abstract bool IsNormalizedKeyLoaded(string normalizedAssetName); + /// <param name="language">The language to check.</param> + protected abstract bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language); /// <summary>Get the locale codes (like <c>ja-JP</c>) used in asset keys.</summary> private IDictionary<LanguageCode, string> 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<T>(newAssetName, newLanguage, useCache); // get from cache - if (useCache && this.IsLoaded(assetName)) + if (useCache && this.IsLoaded(assetName, language)) return this.RawLoad<T>(assetName, language, useCache: true); // get managed asset @@ -151,11 +151,12 @@ namespace StardewModdingAPI.Framework.ContentManagers *********/ /// <summary>Get whether an asset has already been loaded.</summary> /// <param name="normalizedAssetName">The normalized asset name.</param> - protected override bool IsNormalizedKeyLoaded(string normalizedAssetName) + /// <param name="language">The language to check.</param> + 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<T>(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<T>(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 /// <summary>Get whether the content manager has already loaded and cached the given asset.</summary> /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param> - bool IsLoaded(string assetName); + /// <param name="language">The language.</param> + bool IsLoaded(string assetName, LocalizedContentManager.LanguageCode language); /// <summary>Get the cached asset keys.</summary> IEnumerable<string> 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 *********/ /// <summary>Get whether an asset has already been loaded.</summary> /// <param name="normalizedAssetName">The normalized asset name.</param> - protected override bool IsNormalizedKeyLoaded(string normalizedAssetName) + /// <param name="language">The language to check.</param> + protected override bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language) { return this.Cache.ContainsKey(normalizedAssetName); } 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; } - /// <summary>Log a deprecation warning for the old-style events.</summary> - public void WarnForOldEvents() + /// <summary>Get the source name for a mod from its unique ID.</summary> + public string GetSourceNameFromStack() { - this.Warn("legacy events", "2.9", DeprecationLevel.PendingRemoval); + return this.ModRegistry.GetFromStack()?.DisplayName; } - /// <summary>Log a deprecation warning.</summary> - /// <param name="nounPhrase">A noun phrase describing what is deprecated.</param> - /// <param name="version">The SMAPI version which deprecated it.</param> - /// <param name="severity">How deprecated the code is.</param> - public void Warn(string nounPhrase, string version, DeprecationLevel severity) + /// <summary>Get the source name for a mod from its unique ID.</summary> + /// <param name="modId">The mod's unique ID.</param> + public string GetSourceName(string modId) { - this.Warn(this.ModRegistry.GetFromStack()?.DisplayName, nounPhrase, version, severity); + return this.ModRegistry.Get(modId)?.DisplayName; } /// <summary>Log a deprecation warning.</summary> @@ -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 ?? "<unknown>", nounPhrase, version)) + if (!this.MarkWarned(source ?? this.GetSourceNameFromStack() ?? "<unknown>", nounPhrase, version)) return; // queue warning @@ -111,21 +109,16 @@ namespace StardewModdingAPI.Framework this.QueuedWarnings.Clear(); } - /// <summary>Mark a deprecation warning as already logged.</summary> - /// <param name="nounPhrase">A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method").</param> - /// <param name="version">The SMAPI version which deprecated it.</param> - /// <returns>Returns whether the deprecation was successfully marked as warned. Returns <c>false</c> if it was already marked.</returns> - public bool MarkWarned(string nounPhrase, string version) - { - return this.MarkWarned(this.ModRegistry.GetFromStack()?.DisplayName, nounPhrase, version); - } + /********* + ** Private methods + *********/ /// <summary>Mark a deprecation warning as already logged.</summary> /// <param name="source">The friendly name of the assembly which used the deprecated code.</param> /// <param name="nounPhrase">A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method").</param> /// <param name="version">The SMAPI version which deprecated it.</param> /// <returns>Returns whether the deprecation was successfully marked as warned. Returns <c>false</c> if it was already marked.</returns> - 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 } /// <inheritdoc /> + [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); } } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index a7f8fbed..e05213f0 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,40 @@ namespace StardewModdingAPI.Framework this.EventManager.ReturnedToTitle.RaiseEmpty(); } + /// <summary>Apply fixes to the save after it's loaded.</summary> + private void ApplySaveFixes() + { + // get last SMAPI version used with this save + 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 (lastVersion.IsOlderThan(new SemanticVersion(3, 8, 1)) && Game1.netWorldState?.Value?.BundleData != null) + { + var oldData = new Dictionary<string, string>(Game1.netWorldState.Value.BundleData); + + try + { + 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}"); + } + } + + // update last run + Game1.CustomData[migrationKey] = Constants.ApiVersion.ToString(); + } + /// <summary>Raised after custom content is removed from the save data to avoid a crash.</summary> internal void OnSaveContentRemoved() { 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 /// <summary>Tracks changes to the list of active mine locations.</summary> private readonly ICollectionWatcher<MineShaft> MineLocationListWatcher; + /// <summary>Tracks changes to the list of active volcano locations.</summary> + private readonly ICollectionWatcher<GameLocation> VolcanoLocationListWatcher; + /// <summary>A lookup of the tracked locations.</summary> private IDictionary<GameLocation, LocationTracker> LocationDict { get; } = new Dictionary<GameLocation, LocationTracker>(new ObjectReferenceComparer<GameLocation>()); @@ -53,10 +56,12 @@ namespace StardewModdingAPI.Framework.StateTracking /// <summary>Construct an instance.</summary> /// <param name="locations">The game's list of locations.</param> /// <param name="activeMineLocations">The game's list of active mine locations.</param> - public WorldLocationsTracker(ObservableCollection<GameLocation> locations, IList<MineShaft> activeMineLocations) + /// <param name="activeVolcanoLocations">The game's list of active volcano locations.</param> + public WorldLocationsTracker(ObservableCollection<GameLocation> locations, IList<MineShaft> activeMineLocations, IList<VolcanoDungeon> activeVolcanoLocations) { this.LocationListWatcher = WatcherFactory.ForObservableCollection(locations); this.MineLocationListWatcher = WatcherFactory.ForReferenceList(activeMineLocations); + this.VolcanoLocationListWatcher = WatcherFactory.ForReferenceList(activeVolcanoLocations); } /// <summary>Update the current value if needed.</summary> @@ -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(); } /// <summary>Set the current value as the baseline.</summary> @@ -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[] { |