summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs4
-rw-r--r--src/SMAPI/Framework/Events/ModWorldEvents.cs7
-rw-r--r--src/SMAPI/Framework/ModLoading/ModMetadata.cs24
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs26
-rw-r--r--src/SMAPI/Framework/Models/SConfig.cs2
-rw-r--r--src/SMAPI/Framework/SCore.cs87
-rw-r--r--src/SMAPI/Framework/StateTracking/LocationTracker.cs7
-rw-r--r--src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs4
8 files changed, 136 insertions, 25 deletions
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index f4abfffe..dfc289ed 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -162,6 +162,9 @@ namespace StardewModdingAPI.Framework.Events
/// <summary>Raised after terrain features (like floors and trees) are added or removed in a location.</summary>
public readonly ManagedEvent<TerrainFeatureListChangedEventArgs> TerrainFeatureListChanged;
+ /// <summary>Raised after furniture are added or removed in a location.</summary>
+ public readonly ManagedEvent<FurnitureListChangedEventArgs> FurnitureListChanged;
+
/****
** Specialized
****/
@@ -238,6 +241,7 @@ namespace StardewModdingAPI.Framework.Events
this.ObjectListChanged = ManageEventOf<ObjectListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.ObjectListChanged));
this.ChestInventoryChanged = ManageEventOf<ChestInventoryChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.ChestInventoryChanged));
this.TerrainFeatureListChanged = ManageEventOf<TerrainFeatureListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged));
+ this.FurnitureListChanged = ManageEventOf<FurnitureListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.FurnitureListChanged));
this.LoadStageChanged = ManageEventOf<LoadStageChangedEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.LoadStageChanged));
this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicking), isPerformanceCritical: true);
diff --git a/src/SMAPI/Framework/Events/ModWorldEvents.cs b/src/SMAPI/Framework/Events/ModWorldEvents.cs
index 21b1b664..f4c40abc 100644
--- a/src/SMAPI/Framework/Events/ModWorldEvents.cs
+++ b/src/SMAPI/Framework/Events/ModWorldEvents.cs
@@ -65,6 +65,13 @@ namespace StardewModdingAPI.Framework.Events
remove => this.EventManager.TerrainFeatureListChanged.Remove(value);
}
+ /// <summary>Raised after furniture are added or removed in a location.</summary>
+ public event EventHandler<FurnitureListChangedEventArgs> FurnitureListChanged
+ {
+ add => this.EventManager.FurnitureListChanged.Add(value, this.Mod);
+ remove => this.EventManager.FurnitureListChanged.Remove(value);
+ }
+
/*********
** Public methods
diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs
index 17e6d59a..0ace084f 100644
--- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs
+++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs
@@ -195,7 +195,10 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <inheritdoc />
public IEnumerable<UpdateKey> GetUpdateKeys(bool validOnly = false)
{
- foreach (string rawKey in this.Manifest?.UpdateKeys ?? new string[0])
+ if (!this.HasManifest())
+ yield break;
+
+ foreach (string rawKey in this.Manifest.UpdateKeys)
{
UpdateKey updateKey = UpdateKey.Parse(rawKey);
if (updateKey.LooksValid || !validOnly)
@@ -251,17 +254,20 @@ namespace StardewModdingAPI.Framework.ModLoading
{
var ids = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
- // yield dependencies
- if (this.Manifest?.Dependencies != null)
+ if (this.HasManifest())
{
- foreach (var entry in this.Manifest?.Dependencies)
- ids[entry.UniqueID] = entry.IsRequired;
+ // yield dependencies
+ foreach (IManifestDependency entry in this.Manifest.Dependencies)
+ {
+ if (!string.IsNullOrWhiteSpace(entry.UniqueID))
+ ids[entry.UniqueID] = entry.IsRequired;
+ }
+
+ // yield content pack parent
+ if (!string.IsNullOrWhiteSpace(this.Manifest.ContentPackFor?.UniqueID))
+ ids[this.Manifest.ContentPackFor.UniqueID] = true;
}
- // yield content pack parent
- if (this.Manifest?.ContentPackFor?.UniqueID != null)
- ids[this.Manifest.ContentPackFor.UniqueID] = true;
-
return ids;
}
}
diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs
index c70820e4..2f506571 100644
--- a/src/SMAPI/Framework/ModLoading/ModResolver.cs
+++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs
@@ -82,7 +82,7 @@ namespace StardewModdingAPI.Framework.ModLoading
// get update URLs
List<string> updateUrls = new List<string>();
- foreach (string key in mod.Manifest.UpdateKeys ?? new string[0])
+ foreach (string key in mod.Manifest.UpdateKeys)
{
string url = getUpdateUrl(key);
if (url != null)
@@ -173,7 +173,7 @@ namespace StardewModdingAPI.Framework.ModLoading
if (string.IsNullOrWhiteSpace(mod.Manifest.Name))
missingFields.Add(nameof(IManifest.Name));
- if (mod.Manifest.Version == null || mod.Manifest.Version.ToString() == "0.0")
+ if (mod.Manifest.Version == null || mod.Manifest.Version.ToString() == "0.0.0")
missingFields.Add(nameof(IManifest.Version));
if (string.IsNullOrWhiteSpace(mod.Manifest.UniqueID))
missingFields.Add(nameof(IManifest.UniqueID));
@@ -188,6 +188,28 @@ namespace StardewModdingAPI.Framework.ModLoading
// validate ID format
if (!PathUtilities.IsSlug(mod.Manifest.UniqueID))
mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, "its manifest specifies an invalid ID (IDs must only contain letters, numbers, underscores, periods, or hyphens).");
+
+ // validate dependencies
+ foreach (var dependency in mod.Manifest.Dependencies)
+ {
+ // null dependency
+ if (dependency == null)
+ {
+ mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has a null entry under {nameof(IManifest.Dependencies)}.");
+ continue;
+ }
+
+ // missing ID
+ if (string.IsNullOrWhiteSpace(dependency.UniqueID))
+ {
+ mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has a {nameof(IManifest.Dependencies)} entry with no {nameof(IManifestDependency.UniqueID)} field.");
+ continue;
+ }
+
+ // invalid ID
+ if (!PathUtilities.IsSlug(dependency.UniqueID))
+ mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has a {nameof(IManifest.Dependencies)} entry with an invalid {nameof(IManifestDependency.UniqueID)} field (IDs must only contain letters, numbers, underscores, periods, or hyphens).");
+ }
}
// validate IDs are unique
diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs
index a71bafd9..10bf9f94 100644
--- a/src/SMAPI/Framework/Models/SConfig.cs
+++ b/src/SMAPI/Framework/Models/SConfig.cs
@@ -22,7 +22,7 @@ namespace StardewModdingAPI.Framework.Models
[nameof(VerboseLogging)] = false,
[nameof(LogNetworkTraffic)] = false,
[nameof(RewriteMods)] = true,
- [nameof(AggressiveMemoryOptimizations)] = true
+ [nameof(AggressiveMemoryOptimizations)] = false
};
/// <summary>The default values for <see cref="SuppressUpdateChecks"/>, to log changes if different.</summary>
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 5862b112..c3285979 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -11,6 +11,9 @@ using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+#if SMAPI_FOR_WINDOWS
+using Microsoft.Win32;
+#endif
using Microsoft.Xna.Framework;
#if SMAPI_FOR_XNA
using System.Windows.Forms;
@@ -294,7 +297,14 @@ namespace StardewModdingAPI.Framework
}
finally
{
- this.Dispose();
+ try
+ {
+ this.Dispose();
+ }
+ catch (Exception ex)
+ {
+ this.Monitor.Log($"The game ended, but SMAPI wasn't able to dispose correctly. Technical details: {ex}", LogLevel.Error);
+ }
}
}
@@ -376,6 +386,9 @@ namespace StardewModdingAPI.Framework
mods = resolver.ProcessDependencies(mods, modDatabase).ToArray();
this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase);
+ // check for software likely to cause issues
+ this.CheckForSoftwareConflicts();
+
// check for updates
this.CheckForUpdatesAsync(mods);
}
@@ -914,6 +927,10 @@ namespace StardewModdingAPI.Framework
// terrain features changed
if (locState.TerrainFeatures.IsChanged)
events.TerrainFeatureListChanged.Raise(new TerrainFeatureListChangedEventArgs(location, locState.TerrainFeatures.Added, locState.TerrainFeatures.Removed));
+
+ // furniture changed
+ if (locState.Furniture.IsChanged)
+ events.FurnitureListChanged.Raise(new FurnitureListChangedEventArgs(location, locState.Furniture.Added, locState.Furniture.Removed));
}
}
@@ -1247,6 +1264,55 @@ namespace StardewModdingAPI.Framework
this.LogManager.SetConsoleTitle(consoleTitle);
}
+ /// <summary>Log a warning if software known to cause issues is installed.</summary>
+ private void CheckForSoftwareConflicts()
+ {
+#if SMAPI_FOR_WINDOWS
+ this.Monitor.Log("Checking for known software conflicts...");
+
+ try
+ {
+ string[] registryKeys = { @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall", @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" };
+
+ string[] installedNames = registryKeys
+ .SelectMany(registryKey =>
+ {
+ using RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKey);
+ if (key == null)
+ return new string[0];
+
+ return key
+ .GetSubKeyNames()
+ .Select(subkeyName =>
+ {
+ using RegistryKey subkey = key.OpenSubKey(subkeyName);
+ string displayName = (string)subkey?.GetValue("DisplayName");
+ string displayVersion = (string)subkey?.GetValue("DisplayVersion");
+
+ if (displayName != null && displayVersion != null && displayName.EndsWith($" {displayVersion}"))
+ displayName = displayName.Substring(0, displayName.Length - displayVersion.Length - 1);
+
+ return displayName;
+ })
+ .ToArray();
+ })
+ .Where(name => name != null && (name.Contains("MSI Afterburner") || name.Contains("RivaTuner")))
+ .Distinct()
+ .OrderBy(name => name)
+ .ToArray();
+
+ if (installedNames.Any())
+ this.Monitor.Log($" Found {string.Join(" and ", installedNames)} installed, which can conflict with SMAPI. If you experience errors or crashes, try disabling that software or adding an exception for SMAPI / Stardew Valley.");
+ else
+ this.Monitor.Log(" None found!");
+ }
+ catch (Exception ex)
+ {
+ this.Monitor.Log($"Failed when checking for conflicting software. Technical details:\n{ex}");
+ }
+#endif
+ }
+
/// <summary>Asynchronously check for a new version of SMAPI and any installed mods, and print alerts to the console if an update is available.</summary>
/// <param name="mods">The mods to include in the update check (if eligible).</param>
private void CheckForUpdatesAsync(IModMetadata[] mods)
@@ -1593,19 +1659,16 @@ namespace StardewModdingAPI.Framework
// validate dependencies
// Although dependencies are validated before mods are loaded, a dependency may have failed to load.
- if (mod.Manifest.Dependencies?.Any() == true)
+ foreach (IManifestDependency dependency in mod.Manifest.Dependencies.Where(p => p.IsRequired))
{
- foreach (IManifestDependency dependency in mod.Manifest.Dependencies.Where(p => p.IsRequired))
+ if (this.ModRegistry.Get(dependency.UniqueID) == null)
{
- if (this.ModRegistry.Get(dependency.UniqueID) == null)
- {
- string dependencyName = mods
- .FirstOrDefault(otherMod => otherMod.HasID(dependency.UniqueID))
- ?.DisplayName ?? dependency.UniqueID;
- errorReasonPhrase = $"it needs the '{dependencyName}' mod, which couldn't be loaded.";
- failReason = ModFailReason.MissingDependencies;
- return false;
- }
+ string dependencyName = mods
+ .FirstOrDefault(otherMod => otherMod.HasID(dependency.UniqueID))
+ ?.DisplayName ?? dependency.UniqueID;
+ errorReasonPhrase = $"it needs the '{dependencyName}' mod, which couldn't be loaded.";
+ failReason = ModFailReason.MissingDependencies;
+ return false;
}
}
diff --git a/src/SMAPI/Framework/StateTracking/LocationTracker.cs b/src/SMAPI/Framework/StateTracking/LocationTracker.cs
index 519fe8f4..6d3a62bb 100644
--- a/src/SMAPI/Framework/StateTracking/LocationTracker.cs
+++ b/src/SMAPI/Framework/StateTracking/LocationTracker.cs
@@ -48,6 +48,9 @@ namespace StardewModdingAPI.Framework.StateTracking
/// <summary>Tracks added or removed terrain features.</summary>
public IDictionaryWatcher<Vector2, TerrainFeature> TerrainFeaturesWatcher { get; }
+ /// <summary>Tracks added or removed furniture.</summary>
+ public ICollectionWatcher<Furniture> FurnitureWatcher { get; }
+
/// <summary>Tracks items added or removed to chests.</summary>
public IDictionary<Vector2, ChestTracker> ChestWatchers { get; } = new Dictionary<Vector2, ChestTracker>();
@@ -68,6 +71,7 @@ namespace StardewModdingAPI.Framework.StateTracking
this.NpcsWatcher = WatcherFactory.ForNetCollection(location.characters);
this.ObjectsWatcher = WatcherFactory.ForNetDictionary(location.netObjects);
this.TerrainFeaturesWatcher = WatcherFactory.ForNetDictionary(location.terrainFeatures);
+ this.FurnitureWatcher = WatcherFactory.ForNetCollection(location.furniture);
this.Watchers.AddRange(new IWatcher[]
{
@@ -76,7 +80,8 @@ namespace StardewModdingAPI.Framework.StateTracking
this.LargeTerrainFeaturesWatcher,
this.NpcsWatcher,
this.ObjectsWatcher,
- this.TerrainFeaturesWatcher
+ this.TerrainFeaturesWatcher,
+ this.FurnitureWatcher
});
this.UpdateChestWatcherList(added: location.Objects.Pairs, removed: new KeyValuePair<Vector2, SObject>[0]);
diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs
index 6ae52fd0..6c9cc4f5 100644
--- a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs
+++ b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs
@@ -34,6 +34,9 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots
/// <summary>Tracks added or removed terrain features.</summary>
public SnapshotListDiff<KeyValuePair<Vector2, TerrainFeature>> TerrainFeatures { get; } = new SnapshotListDiff<KeyValuePair<Vector2, TerrainFeature>>();
+ /// <summary>Tracks added or removed furniture.</summary>
+ public SnapshotListDiff<Furniture> Furniture { get; } = new SnapshotListDiff<Furniture>();
+
/// <summary>Tracks changed chest inventories.</summary>
public IDictionary<Chest, SnapshotItemListDiff> ChestItems { get; } = new Dictionary<Chest, SnapshotItemListDiff>();
@@ -59,6 +62,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots
this.Npcs.Update(watcher.NpcsWatcher);
this.Objects.Update(watcher.ObjectsWatcher);
this.TerrainFeatures.Update(watcher.TerrainFeaturesWatcher);
+ this.Furniture.Update(watcher.FurnitureWatcher);
// chest inventories
this.ChestItems.Clear();