summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs2
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs8
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs9
-rw-r--r--src/SMAPI/Framework/ContentManagers/IContentManager.cs3
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs3
-rw-r--r--src/SMAPI/Framework/DeprecationManager.cs31
-rw-r--r--src/SMAPI/Framework/ModHelpers/CommandHelper.cs8
-rw-r--r--src/SMAPI/Framework/SCore.cs37
-rw-r--r--src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs15
-rw-r--r--src/SMAPI/Framework/WatcherCore.cs2
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[]
{