summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/GlobalAssemblyInfo.cs4
-rw-r--r--build/common.targets12
-rw-r--r--docs/release-notes.md31
-rw-r--r--docs/technical-docs.md4
-rw-r--r--src/SMAPI.Installer/windows-install.bat9
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs2
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs14
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/ModEntry.cs4
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj2
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Tests/Utilities/SemanticVersionTests.cs2
-rw-r--r--src/SMAPI.Web/Controllers/ModsApiController.cs7
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/IModDropClient.cs17
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs96
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/ModDropMod.cs24
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/FileDataModel.cs21
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModDataModel.cs15
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModListModel.cs11
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModModel.cs12
-rw-r--r--src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs9
-rw-r--r--src/SMAPI.Web/Framework/ModRepositories/ModDropRepository.cs59
-rw-r--r--src/SMAPI.Web/Startup.cs8
-rw-r--r--src/SMAPI.Web/ViewModels/ModModel.cs5
-rw-r--r--src/SMAPI.Web/Views/Index/Index.cshtml1
-rw-r--r--src/SMAPI.Web/Views/Mods/Index.cshtml11
-rw-r--r--src/SMAPI.Web/appsettings.json3
-rw-r--r--src/SMAPI.Web/wwwroot/Content/js/mods.js84
-rw-r--r--src/SMAPI/Constants.cs7
-rw-r--r--src/SMAPI/Context.cs2
-rw-r--r--src/SMAPI/Events/BuildingListChangedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/ContentEvents.cs17
-rw-r--r--src/SMAPI/Events/ControlEvents.cs59
-rw-r--r--src/SMAPI/Events/CursorMovedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/DebrisListChangedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/EventArgsClickableMenuChanged.cs4
-rw-r--r--src/SMAPI/Events/EventArgsClickableMenuClosed.cs4
-rw-r--r--src/SMAPI/Events/EventArgsControllerButtonPressed.cs2
-rw-r--r--src/SMAPI/Events/EventArgsControllerButtonReleased.cs4
-rw-r--r--src/SMAPI/Events/EventArgsControllerTriggerPressed.cs2
-rw-r--r--src/SMAPI/Events/EventArgsControllerTriggerReleased.cs4
-rw-r--r--src/SMAPI/Events/EventArgsInput.cs2
-rw-r--r--src/SMAPI/Events/EventArgsIntChanged.cs2
-rw-r--r--src/SMAPI/Events/EventArgsInventoryChanged.cs2
-rw-r--r--src/SMAPI/Events/EventArgsKeyPressed.cs2
-rw-r--r--src/SMAPI/Events/EventArgsKeyboardStateChanged.cs2
-rw-r--r--src/SMAPI/Events/EventArgsLevelUp.cs2
-rw-r--r--src/SMAPI/Events/EventArgsLocationBuildingsChanged.cs2
-rw-r--r--src/SMAPI/Events/EventArgsLocationObjectsChanged.cs2
-rw-r--r--src/SMAPI/Events/EventArgsLocationsChanged.cs2
-rw-r--r--src/SMAPI/Events/EventArgsMineLevelChanged.cs4
-rw-r--r--src/SMAPI/Events/EventArgsMouseStateChanged.cs4
-rw-r--r--src/SMAPI/Events/EventArgsPlayerWarped.cs2
-rw-r--r--src/SMAPI/Events/EventArgsValueChanged.cs6
-rw-r--r--src/SMAPI/Events/GameEvents.cs59
-rw-r--r--src/SMAPI/Events/GraphicsEvents.cs53
-rw-r--r--src/SMAPI/Events/InputEvents.cs23
-rw-r--r--src/SMAPI/Events/InventoryChangedEventArgs.cs5
-rw-r--r--src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/LevelChangedEventArgs.cs5
-rw-r--r--src/SMAPI/Events/LocationEvents.cs29
-rw-r--r--src/SMAPI/Events/LocationListChangedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/MenuChangedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/MenuEvents.cs23
-rw-r--r--src/SMAPI/Events/MineEvents.cs17
-rw-r--r--src/SMAPI/Events/MouseWheelScrolledEventArgs.cs2
-rw-r--r--src/SMAPI/Events/MultiplayerEvents.cs35
-rw-r--r--src/SMAPI/Events/NpcListChangedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/ObjectListChangedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/PlayerEvents.cs29
-rw-r--r--src/SMAPI/Events/SaveEvents.cs47
-rw-r--r--src/SMAPI/Events/SpecialisedEvents.cs17
-rw-r--r--src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/TimeChangedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/TimeEvents.cs23
-rw-r--r--src/SMAPI/Events/UnvalidatedUpdateTickedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/UnvalidatedUpdateTickingEventArgs.cs2
-rw-r--r--src/SMAPI/Events/UpdateTickedEventArgs.cs2
-rw-r--r--src/SMAPI/Events/UpdateTickingEventArgs.cs2
-rw-r--r--src/SMAPI/Events/WarpedEventArgs.cs5
-rw-r--r--src/SMAPI/Events/WindowResizedEventArgs.cs2
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs16
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs20
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs27
-rw-r--r--src/SMAPI/Framework/ContentManagers/IContentManager.cs4
-rw-r--r--src/SMAPI/Framework/DeprecationManager.cs6
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs8
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModHelper.cs36
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs9
-rw-r--r--src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/ModWarning.cs2
-rw-r--r--src/SMAPI/Framework/SCore.cs86
-rw-r--r--src/SMAPI/Framework/SGame.cs127
-rw-r--r--src/SMAPI/Framework/SMultiplayer.cs2
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs83
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs13
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs3
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs2
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs14
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs3
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs8
-rw-r--r--src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs50
-rw-r--r--src/SMAPI/Framework/WatcherCore.cs3
-rw-r--r--src/SMAPI/IModHelper.cs16
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs82
-rw-r--r--src/SMAPI/Metadata/InstructionMetadata.cs80
-rw-r--r--src/SMAPI/SemanticVersion.cs2
-rw-r--r--src/SMAPI/StardewModdingAPI.csproj3
-rw-r--r--src/StardewModdingAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs2
-rw-r--r--src/StardewModdingAPI.Toolkit.CoreInterfaces/StardewModdingAPI.Toolkit.CoreInterfaces.csproj4
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs4
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs4
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs3
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs15
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs3
-rw-r--r--src/StardewModdingAPI.Toolkit/SemanticVersion.cs38
-rw-r--r--src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs24
-rw-r--r--src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj4
118 files changed, 1493 insertions, 331 deletions
diff --git a/build/GlobalAssemblyInfo.cs b/build/GlobalAssemblyInfo.cs
index 7a6de93c..207c11c3 100644
--- a/build/GlobalAssemblyInfo.cs
+++ b/build/GlobalAssemblyInfo.cs
@@ -1,5 +1,5 @@
using System.Reflection;
[assembly: AssemblyProduct("SMAPI")]
-[assembly: AssemblyVersion("2.8.2")]
-[assembly: AssemblyFileVersion("2.8.2")]
+[assembly: AssemblyVersion("2.9.0")]
+[assembly: AssemblyFileVersion("2.9.0")]
diff --git a/build/common.targets b/build/common.targets
index d9ad89f4..e08e7066 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -97,9 +97,9 @@
<Error Condition="!Exists('$(GamePath)')" Text="Failed to find the game install path automatically; edit the *.csproj file and manually add a &lt;GamePath&gt; setting with the full directory path containing the Stardew Valley executable." />
</Target>
- <!-- copy files into game directory and enable debugging (only in debug mode) -->
+ <!-- copy files into game directory and enable debugging -->
<Target Name="AfterBuild">
- <CallTarget Targets="CopySMAPI;CopyDefaultMods" Condition="'$(Configuration)' == 'Debug'" />
+ <CallTarget Targets="CopySMAPI;CopyDefaultMods" />
</Target>
<Target Name="CopySMAPI" Condition="'$(MSBuildProjectName)' == 'StardewModdingAPI'">
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFolder="$(GamePath)" />
@@ -116,19 +116,19 @@
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" Condition="Exists('$(TargetDir)\$(TargetName).pdb')" />
<Copy SourceFiles="$(TargetDir)\manifest.json" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" />
</Target>
- <Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'StardewModdingAPI.Toolkit' AND '$(Configuration)' == 'Debug' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
+ <Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'StardewModdingAPI.Toolkit' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
</Target>
- <Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'StardewModdingAPI.Toolkit.CoreInterfaces' AND '$(Configuration)' == 'Debug' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
+ <Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'StardewModdingAPI.Toolkit.CoreInterfaces' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
</Target>
- <!-- launch SMAPI on debug -->
- <PropertyGroup Condition="$(Configuration) == 'Debug'">
+ <!-- launch SMAPI through Visual Studio -->
+ <PropertyGroup>
<StartAction>Program</StartAction>
<StartProgram>$(GamePath)\StardewModdingAPI.exe</StartProgram>
<StartWorkingDirectory>$(GamePath)</StartWorkingDirectory>
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 90986cd4..cf11df7c 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,9 +1,36 @@
# Release notes
+## 2.9
+* For players:
+ * Added support for ModDrop in update checks and the mod compatibility list.
+ * Added friendly error for Steam players when Steam isn't loaded.
+ * Fixed cryptic error when running the installer from inside a zip file on Windows.
+ * Fixed error when leaving and rejoining a multiplayer server in the same session.
+ * Fixed empty "mods with warnings" list in some cases due to hidden warnings.
+ * Fixed Console Commands' handling of tool upgrade levels for item commands.
+
+* For modders:
+ * Added ModDrop update keys (see [docs](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest#Update_checks)).
+ * Added `IsLocalPlayer` to new player events.
+ * Added `helper.CreateTemporaryContentPack` to replace the deprecated `CreateTransitionalContentPack`.
+ * Reloading a map asset will now update affected locations.
+ * Reloading the `Data\NPCDispositions` asset will now update affected NPCs.
+ * Disabled trace messages related to paranoid mode when it's disabled.
+ * Fixed world events like `ObjectListChanged` not working in the mines.
+ * Fixed some map tilesheets not editable if not playing in English.
+ * Fixed newlines in manifest fields not being ignored.
+ * Fixed `Display.RenderedWorld` event invoked after overlays are rendered.
+ * **Deprecations:**
+ * All static events are deprecated and will be removed in SMAPI 3.0. Mods should use the new event system available through `helper.Events` instead; see [_migrate to SMAPI 3.0_](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0) for details.
+
+* For the web UI:
+ * Added stats to compatibility list.
+ * Fixed compatibility list showing beta header when there's no beta in progress.
+
## 2.8.2
-* Fixed game crash in MacOS since SMAPI 2.8.
+* Fixed game crash in MacOS with SMAPI 2.8.
## 2.8.1
-* Fixed installer error on Windows.
+* Fixed installer error on Windows with SMAPI 2.8.
## 2.8
* For players:
diff --git a/docs/technical-docs.md b/docs/technical-docs.md
index 5883ee00..1d69f868 100644
--- a/docs/technical-docs.md
+++ b/docs/technical-docs.md
@@ -105,8 +105,8 @@ SMAPI uses a small number of conditional compilation constants, which you can se
flag | purpose
---- | -------
-`SMAPI_FOR_WINDOWS` | Indicates that SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`.
-
+`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`.
+`SMAPI_3_0_STRICT` | Whether to exclude all deprecated APIs from compilation. This is useful for testing mods for SMAPI 3.0 compatibility.
# SMAPI web services
## Overview
diff --git a/src/SMAPI.Installer/windows-install.bat b/src/SMAPI.Installer/windows-install.bat
index 7a8b409b..d02dd4c6 100644
--- a/src/SMAPI.Installer/windows-install.bat
+++ b/src/SMAPI.Installer/windows-install.bat
@@ -1 +1,8 @@
-START /WAIT /B internal/windows-install.exe
+@echo off
+echo %~dp0 | findstr /C:"%TEMP%" 1>nul
+if not errorlevel 1 (
+ echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first.
+ pause
+) else (
+ start /WAIT /B internal/windows-install.exe
+)
diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs
index 7644ee46..a6075013 100644
--- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs
+++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs
@@ -47,7 +47,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
{
// define conversion between game time and TimeSpan
TimeSpan ToTimeSpan(int value) => new TimeSpan(0, value / 100, value % 100, 0);
- int FromTimeSpan(TimeSpan span) => (int)((span.Hours * 100) + span.Minutes);
+ int FromTimeSpan(TimeSpan span) => (span.Hours * 100) + span.Minutes;
// transition to new time
int intervals = (int)((ToTimeSpan(time) - ToTimeSpan(Game1.timeOfDay)).TotalMinutes / 10);
diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs
index 7a3d8694..f4a38403 100644
--- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs
+++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs
@@ -25,15 +25,11 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework
public IEnumerable<SearchableItem> GetAll()
{
// get tools
- for (int quality = Tool.stone; quality <= Tool.iridium; quality++)
- {
- yield return new SearchableItem(ItemType.Tool, ToolFactory.axe, ToolFactory.getToolFromDescription(ToolFactory.axe, quality));
- yield return new SearchableItem(ItemType.Tool, ToolFactory.hoe, ToolFactory.getToolFromDescription(ToolFactory.hoe, quality));
- yield return new SearchableItem(ItemType.Tool, ToolFactory.pickAxe, ToolFactory.getToolFromDescription(ToolFactory.pickAxe, quality));
- yield return new SearchableItem(ItemType.Tool, ToolFactory.wateringCan, ToolFactory.getToolFromDescription(ToolFactory.wateringCan, quality));
- if (quality != Tool.iridium)
- yield return new SearchableItem(ItemType.Tool, ToolFactory.fishingRod, ToolFactory.getToolFromDescription(ToolFactory.fishingRod, quality));
- }
+ yield return new SearchableItem(ItemType.Tool, ToolFactory.axe, ToolFactory.getToolFromDescription(ToolFactory.axe, 0));
+ yield return new SearchableItem(ItemType.Tool, ToolFactory.hoe, ToolFactory.getToolFromDescription(ToolFactory.hoe, 0));
+ yield return new SearchableItem(ItemType.Tool, ToolFactory.pickAxe, ToolFactory.getToolFromDescription(ToolFactory.pickAxe, 0));
+ yield return new SearchableItem(ItemType.Tool, ToolFactory.wateringCan, ToolFactory.getToolFromDescription(ToolFactory.wateringCan, 0));
+ yield return new SearchableItem(ItemType.Tool, ToolFactory.fishingRod, ToolFactory.getToolFromDescription(ToolFactory.fishingRod, 0));
yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset, new MilkPail()); // these don't have any sort of ID, so we'll just assign some arbitrary ones
yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 1, new Shears());
yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 2, new Pan());
diff --git a/src/SMAPI.Mods.ConsoleCommands/ModEntry.cs b/src/SMAPI.Mods.ConsoleCommands/ModEntry.cs
index 7588043d..30951064 100644
--- a/src/SMAPI.Mods.ConsoleCommands/ModEntry.cs
+++ b/src/SMAPI.Mods.ConsoleCommands/ModEntry.cs
@@ -29,7 +29,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands
helper.ConsoleCommands.Add(command.Name, command.Description, (name, args) => this.HandleCommand(command, name, args));
// hook events
- GameEvents.UpdateTick += this.GameEvents_UpdateTick;
+ helper.Events.GameLoop.UpdateTicked += this.OnUpdateTicked;
}
@@ -39,7 +39,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands
/// <summary>The method invoked when the game updates its state.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
- private void GameEvents_UpdateTick(object sender, EventArgs e)
+ private void OnUpdateTicked(object sender, EventArgs e)
{
if (!Context.IsWorldReady)
return;
diff --git a/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj b/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj
index 1137bb11..d1f16e41 100644
--- a/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj
+++ b/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj
@@ -15,7 +15,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
- <Optimize>true</Optimize>
+ <Optimize>false</Optimize>
<OutputPath>$(SolutionDir)\..\bin\Debug\Mods\ConsoleCommands\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index 94bab67f..c723d845 100644
--- a/src/SMAPI.Mods.ConsoleCommands/manifest.json
+++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Console Commands",
"Author": "SMAPI",
- "Version": "2.8.0",
+ "Version": "2.9.0",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "2.8.2"
+ "MinimumApiVersion": "2.9.0"
}
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index 1afb84ae..b6aa4149 100644
--- a/src/SMAPI.Mods.SaveBackup/manifest.json
+++ b/src/SMAPI.Mods.SaveBackup/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Save Backup",
"Author": "SMAPI",
- "Version": "2.8.0",
+ "Version": "2.9.0",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "2.8.2"
+ "MinimumApiVersion": "2.9.0"
}
diff --git a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs
index 1782308b..2e7719eb 100644
--- a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs
+++ b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs
@@ -47,7 +47,7 @@ namespace StardewModdingAPI.Tests.Utilities
Assert.AreEqual(major, version.MajorVersion, "The major version doesn't match the given value.");
Assert.AreEqual(minor, version.MinorVersion, "The minor version doesn't match the given value.");
Assert.AreEqual(patch, version.PatchVersion, "The patch version doesn't match the given value.");
- Assert.AreEqual(string.IsNullOrWhiteSpace(tag) ? null : tag.Trim(), version.Build, "The tag doesn't match the given value.");
+ Assert.AreEqual(string.IsNullOrWhiteSpace(tag) ? null : tag.Trim(), version.PrereleaseTag, "The tag doesn't match the given value.");
return version.ToString();
}
diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs
index f0835592..12d349e0 100644
--- a/src/SMAPI.Web/Controllers/ModsApiController.cs
+++ b/src/SMAPI.Web/Controllers/ModsApiController.cs
@@ -15,6 +15,7 @@ using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
using StardewModdingAPI.Web.Framework.Clients.Chucklefish;
using StardewModdingAPI.Web.Framework.Clients.GitHub;
+using StardewModdingAPI.Web.Framework.Clients.ModDrop;
using StardewModdingAPI.Web.Framework.Clients.Nexus;
using StardewModdingAPI.Web.Framework.ConfigModels;
using StardewModdingAPI.Web.Framework.ModRepositories;
@@ -60,8 +61,9 @@ namespace StardewModdingAPI.Web.Controllers
/// <param name="configProvider">The config settings for mod update checks.</param>
/// <param name="chucklefish">The Chucklefish API client.</param>
/// <param name="github">The GitHub API client.</param>
+ /// <param name="modDrop">The ModDrop API client.</param>
/// <param name="nexus">The Nexus API client.</param>
- public ModsApiController(IHostingEnvironment environment, IMemoryCache cache, IOptions<ModUpdateCheckConfig> configProvider, IChucklefishClient chucklefish, IGitHubClient github, INexusClient nexus)
+ public ModsApiController(IHostingEnvironment environment, IMemoryCache cache, IOptions<ModUpdateCheckConfig> configProvider, IChucklefishClient chucklefish, IGitHubClient github, IModDropClient modDrop, INexusClient nexus)
{
this.ModDatabase = new ModToolkit().GetModDatabase(Path.Combine(environment.WebRootPath, "StardewModdingAPI.metadata.json"));
ModUpdateCheckConfig config = configProvider.Value;
@@ -76,6 +78,7 @@ namespace StardewModdingAPI.Web.Controllers
{
new ChucklefishRepository(chucklefish),
new GitHubRepository(github),
+ new ModDropRepository(modDrop),
new NexusRepository(nexus)
}
.ToDictionary(p => p.VendorKey);
@@ -291,6 +294,8 @@ namespace StardewModdingAPI.Web.Controllers
yield return $"Nexus:{entry.NexusID}";
if (entry.ChucklefishID.HasValue)
yield return $"Chucklefish:{entry.ChucklefishID}";
+ if (entry.ModDropID.HasValue)
+ yield return $"ModDrop:{entry.ModDropID}";
}
}
diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/IModDropClient.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/IModDropClient.cs
new file mode 100644
index 00000000..3ede46e2
--- /dev/null
+++ b/src/SMAPI.Web/Framework/Clients/ModDrop/IModDropClient.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Threading.Tasks;
+
+namespace StardewModdingAPI.Web.Framework.Clients.ModDrop
+{
+ /// <summary>An HTTP client for fetching mod metadata from the ModDrop API.</summary>
+ internal interface IModDropClient : IDisposable
+ {
+ /*********
+ ** Methods
+ *********/
+ /// <summary>Get metadata about a mod.</summary>
+ /// <param name="id">The ModDrop mod ID.</param>
+ /// <returns>Returns the mod info if found, else <c>null</c>.</returns>
+ Task<ModDropMod> GetModAsync(long id);
+ }
+}
diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs
new file mode 100644
index 00000000..19b0b24d
--- /dev/null
+++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs
@@ -0,0 +1,96 @@
+using System.Threading.Tasks;
+using Pathoschild.Http.Client;
+using StardewModdingAPI.Toolkit;
+using StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels;
+
+namespace StardewModdingAPI.Web.Framework.Clients.ModDrop
+{
+ /// <summary>An HTTP client for fetching mod metadata from the ModDrop API.</summary>
+ internal class ModDropClient : IModDropClient
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The underlying HTTP client.</summary>
+ private readonly IClient Client;
+
+ /// <summary>The URL for a ModDrop mod page for the user, where {0} is the mod ID.</summary>
+ private readonly string ModUrlFormat;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="userAgent">The user agent for the API client.</param>
+ /// <param name="apiUrl">The base URL for the ModDrop API.</param>
+ /// <param name="modUrlFormat">The URL for a ModDrop mod page for the user, where {0} is the mod ID.</param>
+ public ModDropClient(string userAgent, string apiUrl, string modUrlFormat)
+ {
+ this.Client = new FluentClient(apiUrl).SetUserAgent(userAgent);
+ this.ModUrlFormat = modUrlFormat;
+ }
+
+ /// <summary>Get metadata about a mod.</summary>
+ /// <param name="id">The ModDrop mod ID.</param>
+ /// <returns>Returns the mod info if found, else <c>null</c>.</returns>
+ public async Task<ModDropMod> GetModAsync(long id)
+ {
+ // get raw data
+ ModListModel response = await this.Client
+ .PostAsync("")
+ .WithBody(new
+ {
+ ModIDs = new[] { id },
+ Files = true,
+ Mods = true
+ })
+ .As<ModListModel>();
+ ModModel mod = response.Mods[id];
+ if (mod.Mod?.Title == null || mod.Mod.ErrorCode.HasValue)
+ return null;
+
+ // get latest versions
+ ISemanticVersion latest = null;
+ ISemanticVersion optional = null;
+ foreach (FileDataModel file in mod.Files)
+ {
+ if (file.IsOld || file.IsDeleted || file.IsHidden)
+ continue;
+
+ if (!SemanticVersion.TryParse(file.Version, out ISemanticVersion version))
+ continue;
+
+ if (file.IsDefault)
+ {
+ if (latest == null || version.IsNewerThan(latest))
+ latest = version;
+ }
+ else if (optional == null || version.IsNewerThan(optional))
+ optional = version;
+ }
+ if (latest == null)
+ {
+ latest = optional;
+ optional = null;
+ }
+ if (optional != null && latest.IsNewerThan(optional))
+ optional = null;
+
+ // generate result
+ return new ModDropMod
+ {
+ Name = mod.Mod?.Title,
+ LatestDefaultVersion = latest,
+ LatestOptionalVersion = optional,
+ Url = string.Format(this.ModUrlFormat, id)
+ };
+ }
+
+ /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
+ public void Dispose()
+ {
+ this.Client?.Dispose();
+ }
+ }
+}
diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropMod.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropMod.cs
new file mode 100644
index 00000000..291fb353
--- /dev/null
+++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropMod.cs
@@ -0,0 +1,24 @@
+namespace StardewModdingAPI.Web.Framework.Clients.ModDrop
+{
+ /// <summary>Mod metadata from the ModDrop API.</summary>
+ internal class ModDropMod
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The mod name.</summary>
+ public string Name { get; set; }
+
+ /// <summary>The latest default file version.</summary>
+ public ISemanticVersion LatestDefaultVersion { get; set; }
+
+ /// <summary>The latest optional file version.</summary>
+ public ISemanticVersion LatestOptionalVersion { get; set; }
+
+ /// <summary>The mod's web URL.</summary>
+ public string Url { get; set; }
+
+ /// <summary>A user-friendly error which indicates why fetching the mod info failed (if applicable).</summary>
+ public string Error { get; set; }
+ }
+}
diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/FileDataModel.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/FileDataModel.cs
new file mode 100644
index 00000000..fa84b287
--- /dev/null
+++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/FileDataModel.cs
@@ -0,0 +1,21 @@
+namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels
+{
+ /// <summary>Metadata from the ModDrop API about a mod file.</summary>
+ public class FileDataModel
+ {
+ /// <summary>Whether the file is deleted.</summary>
+ public bool IsDeleted { get; set; }
+
+ /// <summary>Whether the file is hidden from users.</summary>
+ public bool IsHidden { get; set; }
+
+ /// <summary>Whether this is the default file for the mod.</summary>
+ public bool IsDefault { get; set; }
+
+ /// <summary>Whether this is an archived file.</summary>
+ public bool IsOld { get; set; }
+
+ /// <summary>The file version.</summary>
+ public string Version { get; set; }
+ }
+}
diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModDataModel.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModDataModel.cs
new file mode 100644
index 00000000..cfdd6a4e
--- /dev/null
+++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModDataModel.cs
@@ -0,0 +1,15 @@
+namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels
+{
+ /// <summary>Metadata about a mod from the ModDrop API.</summary>
+ public class ModDataModel
+ {
+ /// <summary>The mod's unique ID on ModDrop.</summary>
+ public int ID { get; set; }
+
+ /// <summary>The error code, if any.</summary>
+ public int? ErrorCode { get; set; }
+
+ /// <summary>The mod name.</summary>
+ public string Title { get; set; }
+ }
+}
diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModListModel.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModListModel.cs
new file mode 100644
index 00000000..7f692ca1
--- /dev/null
+++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModListModel.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels
+{
+ /// <summary>A list of mods from the ModDrop API.</summary>
+ public class ModListModel
+ {
+ /// <summary>The mod data.</summary>
+ public IDictionary<long, ModModel> Mods { get; set; }
+ }
+}
diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModModel.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModModel.cs
new file mode 100644
index 00000000..9f4b2c6f
--- /dev/null
+++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModModel.cs
@@ -0,0 +1,12 @@
+namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels
+{
+ /// <summary>An entry in a mod list from the ModDrop API.</summary>
+ public class ModModel
+ {
+ /// <summary>The available file downloads.</summary>
+ public FileDataModel[] Files { get; set; }
+
+ /// <summary>The mod metadata.</summary>
+ public ModDataModel Mod { get; set; }
+ }
+}
diff --git a/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs
index ae8f18d2..c27cadab 100644
--- a/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs
+++ b/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs
@@ -45,6 +45,15 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
public string GitHubPassword { get; set; }
/****
+ ** ModDrop
+ ****/
+ /// <summary>The base URL for the ModDrop API.</summary>
+ public string ModDropApiUrl { get; set; }
+
+ /// <summary>The URL for a ModDrop mod page for the user, where {0} is the mod ID.</summary>
+ public string ModDropModPageUrl { get; set; }
+
+ /****
** Nexus Mods
****/
/// <summary>The base URL for the Nexus Mods API.</summary>
diff --git a/src/SMAPI.Web/Framework/ModRepositories/ModDropRepository.cs b/src/SMAPI.Web/Framework/ModRepositories/ModDropRepository.cs
new file mode 100644
index 00000000..09484aa8
--- /dev/null
+++ b/src/SMAPI.Web/Framework/ModRepositories/ModDropRepository.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Threading.Tasks;
+using StardewModdingAPI.Toolkit.Framework.UpdateData;
+using StardewModdingAPI.Web.Framework.Clients.ModDrop;
+
+namespace StardewModdingAPI.Web.Framework.ModRepositories
+{
+ /// <summary>An HTTP client for fetching mod metadata from the ModDrop API.</summary>
+ internal class ModDropRepository : RepositoryBase
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The underlying ModDrop API client.</summary>
+ private readonly IModDropClient Client;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="client">The underlying Nexus Mods API client.</param>
+ public ModDropRepository(IModDropClient client)
+ : base(ModRepositoryKey.ModDrop)
+ {
+ this.Client = client;
+ }
+
+ /// <summary>Get metadata about a mod in the repository.</summary>
+ /// <param name="id">The mod ID in this repository.</param>
+ public override async Task<ModInfoModel> GetModInfoAsync(string id)
+ {
+ // validate ID format
+ if (!long.TryParse(id, out long modDropID))
+ return new ModInfoModel($"The value '{id}' isn't a valid ModDrop mod ID, must be an integer ID.");
+
+ // fetch info
+ try
+ {
+ ModDropMod mod = await this.Client.GetModAsync(modDropID);
+ if (mod == null)
+ return new ModInfoModel("Found no mod with this ID.");
+ if (mod.Error != null)
+ return new ModInfoModel(mod.Error);
+ return new ModInfoModel(name: mod.Name, version: mod.LatestDefaultVersion?.ToString(), previewVersion: mod.LatestOptionalVersion?.ToString(), url: mod.Url);
+ }
+ catch (Exception ex)
+ {
+ return new ModInfoModel(ex.ToString());
+ }
+ }
+
+ /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
+ public override void Dispose()
+ {
+ this.Client.Dispose();
+ }
+ }
+}
diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs
index 60a16053..0bd71d26 100644
--- a/src/SMAPI.Web/Startup.cs
+++ b/src/SMAPI.Web/Startup.cs
@@ -11,6 +11,7 @@ using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Web.Framework;
using StardewModdingAPI.Web.Framework.Clients.Chucklefish;
using StardewModdingAPI.Web.Framework.Clients.GitHub;
+using StardewModdingAPI.Web.Framework.Clients.ModDrop;
using StardewModdingAPI.Web.Framework.Clients.Nexus;
using StardewModdingAPI.Web.Framework.Clients.Pastebin;
using StardewModdingAPI.Web.Framework.ConfigModels;
@@ -86,6 +87,12 @@ namespace StardewModdingAPI.Web
password: api.GitHubPassword
));
+ services.AddSingleton<IModDropClient>(new ModDropClient(
+ userAgent: userAgent,
+ apiUrl: api.ModDropApiUrl,
+ modUrlFormat: api.ModDropModPageUrl
+ ));
+
services.AddSingleton<INexusClient>(new NexusWebScrapeClient(
userAgent: userAgent,
baseUrl: api.NexusBaseUrl,
@@ -155,6 +162,7 @@ namespace StardewModdingAPI.Web
// shortcut redirects
redirects.Add(new RedirectToUrlRule(@"^/buildmsg(?:/?(.*))$", "https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md#$1"));
redirects.Add(new RedirectToUrlRule(@"^/compat\.?$", "https://mods.smapi.io"));
+ redirects.Add(new RedirectToUrlRule(@"^/3\.0\.?$", "https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0"));
redirects.Add(new RedirectToUrlRule(@"^/docs\.?$", "https://stardewvalleywiki.com/Modding:Index"));
redirects.Add(new RedirectToUrlRule(@"^/install\.?$", "https://stardewvalleywiki.com/Modding:Player_Guide/Getting_Started#Install_SMAPI"));
diff --git a/src/SMAPI.Web/ViewModels/ModModel.cs b/src/SMAPI.Web/ViewModels/ModModel.cs
index 0e7d2076..5c1840fc 100644
--- a/src/SMAPI.Web/ViewModels/ModModel.cs
+++ b/src/SMAPI.Web/ViewModels/ModModel.cs
@@ -96,6 +96,11 @@ namespace StardewModdingAPI.Web.ViewModels
anyFound = true;
yield return new ModLinkModel($"https://community.playstarbound.com/resources/{entry.ChucklefishID}", "Chucklefish");
}
+ if (entry.ModDropID.HasValue)
+ {
+ anyFound = true;
+ yield return new ModLinkModel($"https://www.moddrop.com/sdv/mod/467243/{entry.ModDropID}", "ModDrop");
+ }
// fallback
if (!anyFound && !string.IsNullOrWhiteSpace(entry.CustomUrl))
diff --git a/src/SMAPI.Web/Views/Index/Index.cshtml b/src/SMAPI.Web/Views/Index/Index.cshtml
index 01874f50..ef092cc8 100644
--- a/src/SMAPI.Web/Views/Index/Index.cshtml
+++ b/src/SMAPI.Web/Views/Index/Index.cshtml
@@ -108,6 +108,7 @@ else
<a href="https://www.nexusmods.com/users/12252523">Karmylla</a>,
Pucklynn,
Robby LaFarge,
+ Tarryn K.,
and a few anonymous users for their ongoing support; you're awesome! 🏅
</p>
diff --git a/src/SMAPI.Web/Views/Mods/Index.cshtml b/src/SMAPI.Web/Views/Mods/Index.cshtml
index 372d6706..a49a24d9 100644
--- a/src/SMAPI.Web/Views/Mods/Index.cshtml
+++ b/src/SMAPI.Web/Views/Mods/Index.cshtml
@@ -4,11 +4,11 @@
ViewData["Title"] = "SMAPI mod compatibility";
}
@section Head {
- <link rel="stylesheet" href="~/Content/css/mods.css?r=20181109" />
+ <link rel="stylesheet" href="~/Content/css/mods.css?r=20181122" />
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/tablesorter@2.31.0/dist/js/jquery.tablesorter.combined.min.js" crossorigin="anonymous"></script>
- <script src="~/Content/js/mods.js?r=20181109"></script>
+ <script src="~/Content/js/mods.js?r=20181122"></script>
<script>
$(function() {
var data = @Json.Serialize(Model.Mods, new JsonSerializerSettings { Formatting = Formatting.None });
@@ -36,7 +36,7 @@
</div>
<div id="filter-area">
<input type="checkbox" id="show-advanced" v-model="showAdvanced" />
- <label for="show-advanced">show detailed options</label>
+ <label for="show-advanced">show advanced info and options</label>
<div id="filters" v-show="showAdvanced">
<div v-for="(filterGroup, key) in filters">
{{key}}: <span v-for="filter in filterGroup" v-bind:class="{ active: filter.value }"><input type="checkbox" v-bind:id="filter.id" v-model="filter.value" v-on:change="applyFilters" /> <label v-bind:for="filter.id">{{filter.label}}</label></span>
@@ -44,7 +44,10 @@
</div>
</div>
</div>
- <div id="mod-count" v-show="showAdvanced">{{visibleCount}} mods shown.</div>
+ <div id="mod-count" v-show="showAdvanced">
+ <span v-if="visibleStats.total > 0">{{visibleStats.total}} mods shown ({{Math.round((visibleStats.compatible + visibleStats.workaround) / visibleStats.total * 100)}}% compatible or have a workaround, {{Math.round((visibleStats.soon + visibleStats.broken) / visibleStats.total * 100)}}% broken, {{Math.round(visibleStats.abandoned / visibleStats.total * 100)}}% obsolete).</span>
+ <span v-else>No matching mods found.</span>
+ </div>
<table class="wikitable" id="mod-list">
<thead>
<tr>
diff --git a/src/SMAPI.Web/appsettings.json b/src/SMAPI.Web/appsettings.json
index aba8c448..89505a45 100644
--- a/src/SMAPI.Web/appsettings.json
+++ b/src/SMAPI.Web/appsettings.json
@@ -35,6 +35,9 @@
"GitHubUsername": null, // see top note
"GitHubPassword": null, // see top note
+ "ModDropApiUrl": "https://www.moddrop.com/api/mods/data",
+ "ModDropModPageUrl": "https://www.moddrop.com/sdv/mod/{0}",
+
"NexusBaseUrl": "https://www.nexusmods.com/stardewvalley/",
"NexusModUrlFormat": "mods/{0}",
"NexusModScrapeUrlFormat": "mods/{0}?tab=files",
diff --git a/src/SMAPI.Web/wwwroot/Content/js/mods.js b/src/SMAPI.Web/wwwroot/Content/js/mods.js
index 2cff551f..f7a8501e 100644
--- a/src/SMAPI.Web/wwwroot/Content/js/mods.js
+++ b/src/SMAPI.Web/wwwroot/Content/js/mods.js
@@ -4,10 +4,19 @@ var smapi = smapi || {};
var app;
smapi.modList = function (mods) {
// init data
+ var defaultStats = {
+ total: 0,
+ compatible: 0,
+ workaround: 0,
+ soon: 0,
+ broken: 0,
+ abandoned: 0,
+ invalid: 0
+ };
var data = {
mods: mods,
- visibleCount: mods.length,
showAdvanced: false,
+ visibleStats: $.extend({}, defaultStats),
filters: {
source: {
open: {
@@ -64,6 +73,11 @@ smapi.modList = function (mods) {
id: "show-chucklefish",
value: true
},
+ moddrop: {
+ label: "ModDrop",
+ id: "show-moddrop",
+ value: true
+ },
nexus: {
label: "Nexus",
id: "show-nexus",
@@ -130,27 +144,16 @@ smapi.modList = function (mods) {
var words = data.search.toLowerCase().split(" ");
// apply criteria
- data.visibleCount = data.mods.length;
+ var stats = data.visibleStats = $.extend({}, defaultStats);
for (var i = 0; i < data.mods.length; i++) {
var mod = data.mods[i];
mod.Visible = true;
// check filters
- if (!this.matchesFilters(mod)) {
- mod.Visible = false;
- data.visibleCount--;
- continue;
- }
-
- // check search terms (all search words should match)
- if (words.length) {
- for (var w = 0; w < words.length; w++) {
- if (mod.SearchableText.indexOf(words[w]) === -1) {
- mod.Visible = false;
- data.visibleCount--;
- break;
- }
- }
+ mod.Visible = this.matchesFilters(mod, words);
+ if (mod.Visible) {
+ stats.total++;
+ stats[this.getCompatibilityGroup(mod)]++;
}
}
},
@@ -159,9 +162,10 @@ smapi.modList = function (mods) {
/**
* Get whether a mod matches the current filters.
* @param {object} mod The mod to check.
+ * @param {string[]} searchWords The search words to match.
* @returns {bool} Whether the mod matches the filters.
*/
- matchesFilters: function(mod) {
+ matchesFilters: function(mod, searchWords) {
var filters = data.filters;
// check source
@@ -180,6 +184,8 @@ smapi.modList = function (mods) {
if (!filters.download.chucklefish.value)
ignoreSites.push("Chucklefish");
+ if (!filters.download.moddrop.value)
+ ignoreSites.push("ModDrop");
if (!filters.download.nexus.value)
ignoreSites.push("Nexus");
if (!filters.download.custom.value)
@@ -198,8 +204,50 @@ smapi.modList = function (mods) {
return false;
}
+ // check search terms
+ for (var w = 0; w < searchWords.length; w++) {
+ if (mod.SearchableText.indexOf(searchWords[w]) === -1)
+ return false;
+ }
+
return true;
+ },
+
+ /**
+ * Get a mod's compatibility group for mod metrics.
+ * @param {object} mod The mod to check.
+ * @returns {string} The compatibility group (one of 'compatible', 'workaround', 'soon', 'broken', 'abandoned', or 'invalid').
+ */
+ getCompatibilityGroup: function (mod) {
+ var status = (mod.BetaCompatibility || mod.Compatibility).Status;
+ switch (status) {
+ // obsolete
+ case "abandoned":
+ case "obsolete":
+ return "abandoned";
+
+ // compatible
+ case "ok":
+ case "optional":
+ return "compatible";
+
+ // workaround
+ case "workaround":
+ case "unofficial":
+ return "workaround";
+
+ // soon/broken
+ case "broken":
+ if (mod.SourceUrl)
+ return "soon";
+ else
+ return "broken";
+
+ default:
+ return "invalid";
+ }
}
}
});
+ app.applyFilters();
};
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index b71c6195..6a1cd188 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -29,7 +29,7 @@ namespace StardewModdingAPI
** Public
****/
/// <summary>SMAPI's current semantic version.</summary>
- public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.8.2");
+ public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.9.0");
/// <summary>The minimum supported version of Stardew Valley.</summary>
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.32");
@@ -91,9 +91,12 @@ namespace StardewModdingAPI
/// <summary>The file path which stores the detected update version for the next run.</summary>
internal static string UpdateMarker => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.update.marker");
- /// <summary>The full path to the folder containing mods.</summary>
+ /// <summary>The default full path to search for mods.</summary>
internal static string DefaultModsPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods");
+ /// <summary>The actual full path to search for mods.</summary>
+ internal static string ModsPath { get; set; }
+
/// <summary>The game's current semantic version.</summary>
internal static ISemanticVersion GameVersion { get; } = new GameVersion(Constants.GetGameVersion());
diff --git a/src/SMAPI/Context.cs b/src/SMAPI/Context.cs
index c7aed81d..cd1cf1c2 100644
--- a/src/SMAPI/Context.cs
+++ b/src/SMAPI/Context.cs
@@ -22,7 +22,7 @@ namespace StardewModdingAPI
/// <summary>Whether <see cref="IsPlayerFree"/> is true and the player is free to move (e.g. not using a tool).</summary>
public static bool CanPlayerMove => Context.IsPlayerFree && Game1.player.CanMove;
- /// <summary>Whether the game is currently running the draw loop. This isn't relevant to most mods, since you should use <see cref="GraphicsEvents.OnPostRenderEvent"/> to draw to the screen.</summary>
+ /// <summary>Whether the game is currently running the draw loop. This isn't relevant to most mods, since you should use <see cref="IDisplayEvents"/> events to draw to the screen.</summary>
public static bool IsInDrawLoop { get; internal set; }
/// <summary>Whether <see cref="IsWorldReady"/> and the player loaded the save in multiplayer mode (regardless of whether any other players are connected).</summary>
diff --git a/src/SMAPI/Events/BuildingListChangedEventArgs.cs b/src/SMAPI/Events/BuildingListChangedEventArgs.cs
index 9bc691fc..0237342f 100644
--- a/src/SMAPI/Events/BuildingListChangedEventArgs.cs
+++ b/src/SMAPI/Events/BuildingListChangedEventArgs.cs
@@ -29,7 +29,7 @@ namespace StardewModdingAPI.Events
/// <param name="location">The location which changed.</param>
/// <param name="added">The buildings added to the location.</param>
/// <param name="removed">The buildings removed from the location.</param>
- public BuildingListChangedEventArgs(GameLocation location, IEnumerable<Building> added, IEnumerable<Building> removed)
+ internal BuildingListChangedEventArgs(GameLocation location, IEnumerable<Building> added, IEnumerable<Building> removed)
{
this.Location = location;
this.Added = added.ToArray();
diff --git a/src/SMAPI/Events/ContentEvents.cs b/src/SMAPI/Events/ContentEvents.cs
index 3ee0560b..99369cae 100644
--- a/src/SMAPI/Events/ContentEvents.cs
+++ b/src/SMAPI/Events/ContentEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when the game loads content.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class ContentEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,7 +25,11 @@ namespace StardewModdingAPI.Events
/// <summary>Raised after the content language changes.</summary>
public static event EventHandler<EventArgsValueChanged<string>> AfterLocaleChanged
{
- add => ContentEvents.EventManager.Legacy_LocaleChanged.Add(value);
+ add
+ {
+ ContentEvents.DeprecationManager.WarnForOldEvents();
+ ContentEvents.EventManager.Legacy_LocaleChanged.Add(value);
+ }
remove => ContentEvents.EventManager.Legacy_LocaleChanged.Remove(value);
}
@@ -29,9 +39,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
ContentEvents.EventManager = eventManager;
+ ContentEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/ControlEvents.cs b/src/SMAPI/Events/ControlEvents.cs
index 56a4fa3f..5626ff81 100644
--- a/src/SMAPI/Events/ControlEvents.cs
+++ b/src/SMAPI/Events/ControlEvents.cs
@@ -1,10 +1,13 @@
+#if !SMAPI_3_0_STRICT
using System;
using Microsoft.Xna.Framework.Input;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when the player uses a controller, keyboard, or mouse.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class ControlEvents
{
/*********
@@ -13,6 +16,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -20,56 +26,88 @@ namespace StardewModdingAPI.Events
/// <summary>Raised when the <see cref="KeyboardState"/> changes. That happens when the player presses or releases a key.</summary>
public static event EventHandler<EventArgsKeyboardStateChanged> KeyboardChanged
{
- add => ControlEvents.EventManager.Legacy_KeyboardChanged.Add(value);
+ add
+ {
+ ControlEvents.DeprecationManager.WarnForOldEvents();
+ ControlEvents.EventManager.Legacy_KeyboardChanged.Add(value);
+ }
remove => ControlEvents.EventManager.Legacy_KeyboardChanged.Remove(value);
}
/// <summary>Raised after the player presses a keyboard key.</summary>
public static event EventHandler<EventArgsKeyPressed> KeyPressed
{
- add => ControlEvents.EventManager.Legacy_KeyPressed.Add(value);
+ add
+ {
+ ControlEvents.DeprecationManager.WarnForOldEvents();
+ ControlEvents.EventManager.Legacy_KeyPressed.Add(value);
+ }
remove => ControlEvents.EventManager.Legacy_KeyPressed.Remove(value);
}
/// <summary>Raised after the player releases a keyboard key.</summary>
public static event EventHandler<EventArgsKeyPressed> KeyReleased
{
- add => ControlEvents.EventManager.Legacy_KeyReleased.Add(value);
+ add
+ {
+ ControlEvents.DeprecationManager.WarnForOldEvents();
+ ControlEvents.EventManager.Legacy_KeyReleased.Add(value);
+ }
remove => ControlEvents.EventManager.Legacy_KeyReleased.Remove(value);
}
/// <summary>Raised when the <see cref="MouseState"/> changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button.</summary>
public static event EventHandler<EventArgsMouseStateChanged> MouseChanged
{
- add => ControlEvents.EventManager.Legacy_MouseChanged.Add(value);
+ add
+ {
+ ControlEvents.DeprecationManager.WarnForOldEvents();
+ ControlEvents.EventManager.Legacy_MouseChanged.Add(value);
+ }
remove => ControlEvents.EventManager.Legacy_MouseChanged.Remove(value);
}
/// <summary>The player pressed a controller button. This event isn't raised for trigger buttons.</summary>
public static event EventHandler<EventArgsControllerButtonPressed> ControllerButtonPressed
{
- add => ControlEvents.EventManager.Legacy_ControllerButtonPressed.Add(value);
+ add
+ {
+ ControlEvents.DeprecationManager.WarnForOldEvents();
+ ControlEvents.EventManager.Legacy_ControllerButtonPressed.Add(value);
+ }
remove => ControlEvents.EventManager.Legacy_ControllerButtonPressed.Remove(value);
}
/// <summary>The player released a controller button. This event isn't raised for trigger buttons.</summary>
public static event EventHandler<EventArgsControllerButtonReleased> ControllerButtonReleased
{
- add => ControlEvents.EventManager.Legacy_ControllerButtonReleased.Add(value);
+ add
+ {
+ ControlEvents.DeprecationManager.WarnForOldEvents();
+ ControlEvents.EventManager.Legacy_ControllerButtonReleased.Add(value);
+ }
remove => ControlEvents.EventManager.Legacy_ControllerButtonReleased.Remove(value);
}
/// <summary>The player pressed a controller trigger button.</summary>
public static event EventHandler<EventArgsControllerTriggerPressed> ControllerTriggerPressed
{
- add => ControlEvents.EventManager.Legacy_ControllerTriggerPressed.Add(value);
+ add
+ {
+ ControlEvents.DeprecationManager.WarnForOldEvents();
+ ControlEvents.EventManager.Legacy_ControllerTriggerPressed.Add(value);
+ }
remove => ControlEvents.EventManager.Legacy_ControllerTriggerPressed.Remove(value);
}
/// <summary>The player released a controller trigger button.</summary>
public static event EventHandler<EventArgsControllerTriggerReleased> ControllerTriggerReleased
{
- add => ControlEvents.EventManager.Legacy_ControllerTriggerReleased.Add(value);
+ add
+ {
+ ControlEvents.DeprecationManager.WarnForOldEvents();
+ ControlEvents.EventManager.Legacy_ControllerTriggerReleased.Add(value);
+ }
remove => ControlEvents.EventManager.Legacy_ControllerTriggerReleased.Remove(value);
}
@@ -79,9 +117,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
ControlEvents.EventManager = eventManager;
+ ControlEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/CursorMovedEventArgs.cs b/src/SMAPI/Events/CursorMovedEventArgs.cs
index 453743b9..43ff90ce 100644
--- a/src/SMAPI/Events/CursorMovedEventArgs.cs
+++ b/src/SMAPI/Events/CursorMovedEventArgs.cs
@@ -21,7 +21,7 @@ namespace StardewModdingAPI.Events
/// <summary>Construct an instance.</summary>
/// <param name="oldPosition">The previous cursor position.</param>
/// <param name="newPosition">The new cursor position.</param>
- public CursorMovedEventArgs(ICursorPosition oldPosition, ICursorPosition newPosition)
+ internal CursorMovedEventArgs(ICursorPosition oldPosition, ICursorPosition newPosition)
{
this.OldPosition = oldPosition;
this.NewPosition = newPosition;
diff --git a/src/SMAPI/Events/DebrisListChangedEventArgs.cs b/src/SMAPI/Events/DebrisListChangedEventArgs.cs
index 1337bd3b..68328885 100644
--- a/src/SMAPI/Events/DebrisListChangedEventArgs.cs
+++ b/src/SMAPI/Events/DebrisListChangedEventArgs.cs
@@ -28,7 +28,7 @@ namespace StardewModdingAPI.Events
/// <param name="location">The location which changed.</param>
/// <param name="added">The debris added to the location.</param>
/// <param name="removed">The debris removed from the location.</param>
- public DebrisListChangedEventArgs(GameLocation location, IEnumerable<Debris> added, IEnumerable<Debris> removed)
+ internal DebrisListChangedEventArgs(GameLocation location, IEnumerable<Debris> added, IEnumerable<Debris> removed)
{
this.Location = location;
this.Added = added.ToArray();
diff --git a/src/SMAPI/Events/EventArgsClickableMenuChanged.cs b/src/SMAPI/Events/EventArgsClickableMenuChanged.cs
index 2a2aa163..a0b903b7 100644
--- a/src/SMAPI/Events/EventArgsClickableMenuChanged.cs
+++ b/src/SMAPI/Events/EventArgsClickableMenuChanged.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_3_0_STRICT
+using System;
using StardewValley.Menus;
namespace StardewModdingAPI.Events
@@ -29,3 +30,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsClickableMenuClosed.cs b/src/SMAPI/Events/EventArgsClickableMenuClosed.cs
index 5e6585f0..77db69ea 100644
--- a/src/SMAPI/Events/EventArgsClickableMenuClosed.cs
+++ b/src/SMAPI/Events/EventArgsClickableMenuClosed.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_3_0_STRICT
+using System;
using StardewValley.Menus;
namespace StardewModdingAPI.Events
@@ -24,3 +25,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsControllerButtonPressed.cs b/src/SMAPI/Events/EventArgsControllerButtonPressed.cs
index 3243b80b..949446e1 100644
--- a/src/SMAPI/Events/EventArgsControllerButtonPressed.cs
+++ b/src/SMAPI/Events/EventArgsControllerButtonPressed.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
@@ -30,3 +31,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsControllerButtonReleased.cs b/src/SMAPI/Events/EventArgsControllerButtonReleased.cs
index e05a080b..d6d6d840 100644
--- a/src/SMAPI/Events/EventArgsControllerButtonReleased.cs
+++ b/src/SMAPI/Events/EventArgsControllerButtonReleased.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_3_0_STRICT
+using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
@@ -30,3 +31,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsControllerTriggerPressed.cs b/src/SMAPI/Events/EventArgsControllerTriggerPressed.cs
index a2087733..33be2fa3 100644
--- a/src/SMAPI/Events/EventArgsControllerTriggerPressed.cs
+++ b/src/SMAPI/Events/EventArgsControllerTriggerPressed.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
@@ -35,3 +36,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsControllerTriggerReleased.cs b/src/SMAPI/Events/EventArgsControllerTriggerReleased.cs
index d2eecbec..e90ff712 100644
--- a/src/SMAPI/Events/EventArgsControllerTriggerReleased.cs
+++ b/src/SMAPI/Events/EventArgsControllerTriggerReleased.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_3_0_STRICT
+using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
@@ -35,3 +36,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsInput.cs b/src/SMAPI/Events/EventArgsInput.cs
index 0cafdba5..837de2f8 100644
--- a/src/SMAPI/Events/EventArgsInput.cs
+++ b/src/SMAPI/Events/EventArgsInput.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
using System.Collections.Generic;
@@ -60,3 +61,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsIntChanged.cs b/src/SMAPI/Events/EventArgsIntChanged.cs
index a018695c..76ec6d08 100644
--- a/src/SMAPI/Events/EventArgsIntChanged.cs
+++ b/src/SMAPI/Events/EventArgsIntChanged.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
namespace StardewModdingAPI.Events
@@ -28,3 +29,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsInventoryChanged.cs b/src/SMAPI/Events/EventArgsInventoryChanged.cs
index 3a2354b6..488dd23f 100644
--- a/src/SMAPI/Events/EventArgsInventoryChanged.cs
+++ b/src/SMAPI/Events/EventArgsInventoryChanged.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
using System.Collections.Generic;
using System.Linq;
@@ -39,3 +40,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsKeyPressed.cs b/src/SMAPI/Events/EventArgsKeyPressed.cs
index d9d81e10..6204d821 100644
--- a/src/SMAPI/Events/EventArgsKeyPressed.cs
+++ b/src/SMAPI/Events/EventArgsKeyPressed.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
using Microsoft.Xna.Framework.Input;
@@ -24,3 +25,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsKeyboardStateChanged.cs b/src/SMAPI/Events/EventArgsKeyboardStateChanged.cs
index 14e397ce..2c3203b1 100644
--- a/src/SMAPI/Events/EventArgsKeyboardStateChanged.cs
+++ b/src/SMAPI/Events/EventArgsKeyboardStateChanged.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
using Microsoft.Xna.Framework.Input;
@@ -29,3 +30,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsLevelUp.cs b/src/SMAPI/Events/EventArgsLevelUp.cs
index e9a697e7..06c70088 100644
--- a/src/SMAPI/Events/EventArgsLevelUp.cs
+++ b/src/SMAPI/Events/EventArgsLevelUp.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
using StardewModdingAPI.Enums;
@@ -51,3 +52,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsLocationBuildingsChanged.cs b/src/SMAPI/Events/EventArgsLocationBuildingsChanged.cs
index e8184ebe..25e84722 100644
--- a/src/SMAPI/Events/EventArgsLocationBuildingsChanged.cs
+++ b/src/SMAPI/Events/EventArgsLocationBuildingsChanged.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
using System.Collections.Generic;
using System.Linq;
@@ -37,3 +38,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs b/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs
index 3bb387d5..9ca2e3e2 100644
--- a/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs
+++ b/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
using System.Collections.Generic;
using System.Linq;
@@ -38,3 +39,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsLocationsChanged.cs b/src/SMAPI/Events/EventArgsLocationsChanged.cs
index 20984f45..1a59e612 100644
--- a/src/SMAPI/Events/EventArgsLocationsChanged.cs
+++ b/src/SMAPI/Events/EventArgsLocationsChanged.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
using System.Collections.Generic;
using System.Linq;
@@ -31,3 +32,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsMineLevelChanged.cs b/src/SMAPI/Events/EventArgsMineLevelChanged.cs
index c82fed35..c63b04e9 100644
--- a/src/SMAPI/Events/EventArgsMineLevelChanged.cs
+++ b/src/SMAPI/Events/EventArgsMineLevelChanged.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_3_0_STRICT
+using System;
namespace StardewModdingAPI.Events
{
@@ -28,3 +29,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsMouseStateChanged.cs b/src/SMAPI/Events/EventArgsMouseStateChanged.cs
index 57298164..09f3f759 100644
--- a/src/SMAPI/Events/EventArgsMouseStateChanged.cs
+++ b/src/SMAPI/Events/EventArgsMouseStateChanged.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_3_0_STRICT
+using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
@@ -40,3 +41,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsPlayerWarped.cs b/src/SMAPI/Events/EventArgsPlayerWarped.cs
index 93026aea..d1aa1588 100644
--- a/src/SMAPI/Events/EventArgsPlayerWarped.cs
+++ b/src/SMAPI/Events/EventArgsPlayerWarped.cs
@@ -1,3 +1,4 @@
+#if !SMAPI_3_0_STRICT
using System;
using StardewValley;
@@ -30,3 +31,4 @@ namespace StardewModdingAPI.Events
}
}
}
+#endif
diff --git a/src/SMAPI/Events/EventArgsValueChanged.cs b/src/SMAPI/Events/EventArgsValueChanged.cs
index 1d25af49..7bfac7a2 100644
--- a/src/SMAPI/Events/EventArgsValueChanged.cs
+++ b/src/SMAPI/Events/EventArgsValueChanged.cs
@@ -1,4 +1,5 @@
-using System;
+#if !SMAPI_3_0_STRICT
+using System;
namespace StardewModdingAPI.Events
{
@@ -28,4 +29,5 @@ namespace StardewModdingAPI.Events
this.NewValue = newValue;
}
}
-} \ No newline at end of file
+}
+#endif
diff --git a/src/SMAPI/Events/GameEvents.cs b/src/SMAPI/Events/GameEvents.cs
index 952b3570..39b77f99 100644
--- a/src/SMAPI/Events/GameEvents.cs
+++ b/src/SMAPI/Events/GameEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when the game changes state.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class GameEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,56 +25,88 @@ namespace StardewModdingAPI.Events
/// <summary>Raised when the game updates its state (≈60 times per second).</summary>
public static event EventHandler UpdateTick
{
- add => GameEvents.EventManager.Legacy_UpdateTick.Add(value);
+ add
+ {
+ GameEvents.DeprecationManager.WarnForOldEvents();
+ GameEvents.EventManager.Legacy_UpdateTick.Add(value);
+ }
remove => GameEvents.EventManager.Legacy_UpdateTick.Remove(value);
}
/// <summary>Raised every other tick (≈30 times per second).</summary>
public static event EventHandler SecondUpdateTick
{
- add => GameEvents.EventManager.Legacy_SecondUpdateTick.Add(value);
+ add
+ {
+ GameEvents.DeprecationManager.WarnForOldEvents();
+ GameEvents.EventManager.Legacy_SecondUpdateTick.Add(value);
+ }
remove => GameEvents.EventManager.Legacy_SecondUpdateTick.Remove(value);
}
/// <summary>Raised every fourth tick (≈15 times per second).</summary>
public static event EventHandler FourthUpdateTick
{
- add => GameEvents.EventManager.Legacy_FourthUpdateTick.Add(value);
+ add
+ {
+ GameEvents.DeprecationManager.WarnForOldEvents();
+ GameEvents.EventManager.Legacy_FourthUpdateTick.Add(value);
+ }
remove => GameEvents.EventManager.Legacy_FourthUpdateTick.Remove(value);
}
/// <summary>Raised every eighth tick (≈8 times per second).</summary>
public static event EventHandler EighthUpdateTick
{
- add => GameEvents.EventManager.Legacy_EighthUpdateTick.Add(value);
+ add
+ {
+ GameEvents.DeprecationManager.WarnForOldEvents();
+ GameEvents.EventManager.Legacy_EighthUpdateTick.Add(value);
+ }
remove => GameEvents.EventManager.Legacy_EighthUpdateTick.Remove(value);
}
/// <summary>Raised every 15th tick (≈4 times per second).</summary>
public static event EventHandler QuarterSecondTick
{
- add => GameEvents.EventManager.Legacy_QuarterSecondTick.Add(value);
+ add
+ {
+ GameEvents.DeprecationManager.WarnForOldEvents();
+ GameEvents.EventManager.Legacy_QuarterSecondTick.Add(value);
+ }
remove => GameEvents.EventManager.Legacy_QuarterSecondTick.Remove(value);
}
/// <summary>Raised every 30th tick (≈twice per second).</summary>
public static event EventHandler HalfSecondTick
{
- add => GameEvents.EventManager.Legacy_HalfSecondTick.Add(value);
+ add
+ {
+ GameEvents.DeprecationManager.WarnForOldEvents();
+ GameEvents.EventManager.Legacy_HalfSecondTick.Add(value);
+ }
remove => GameEvents.EventManager.Legacy_HalfSecondTick.Remove(value);
}
/// <summary>Raised every 60th tick (≈once per second).</summary>
public static event EventHandler OneSecondTick
{
- add => GameEvents.EventManager.Legacy_OneSecondTick.Add(value);
+ add
+ {
+ GameEvents.DeprecationManager.WarnForOldEvents();
+ GameEvents.EventManager.Legacy_OneSecondTick.Add(value);
+ }
remove => GameEvents.EventManager.Legacy_OneSecondTick.Remove(value);
}
/// <summary>Raised once after the game initialises and all <see cref="IMod.Entry"/> methods have been called.</summary>
public static event EventHandler FirstUpdateTick
{
- add => GameEvents.EventManager.Legacy_FirstUpdateTick.Add(value);
+ add
+ {
+ GameEvents.DeprecationManager.WarnForOldEvents();
+ GameEvents.EventManager.Legacy_FirstUpdateTick.Add(value);
+ }
remove => GameEvents.EventManager.Legacy_FirstUpdateTick.Remove(value);
}
@@ -78,9 +116,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
GameEvents.EventManager = eventManager;
+ GameEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/GraphicsEvents.cs b/src/SMAPI/Events/GraphicsEvents.cs
index 53f04822..be29bf11 100644
--- a/src/SMAPI/Events/GraphicsEvents.cs
+++ b/src/SMAPI/Events/GraphicsEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised during the game's draw loop, when the game is rendering content to the window.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class GraphicsEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,7 +25,11 @@ namespace StardewModdingAPI.Events
/// <summary>Raised after the game window is resized.</summary>
public static event EventHandler Resize
{
- add => GraphicsEvents.EventManager.Legacy_Resize.Add(value);
+ add
+ {
+ GraphicsEvents.DeprecationManager.WarnForOldEvents();
+ GraphicsEvents.EventManager.Legacy_Resize.Add(value);
+ }
remove => GraphicsEvents.EventManager.Legacy_Resize.Remove(value);
}
@@ -29,14 +39,22 @@ namespace StardewModdingAPI.Events
/// <summary>Raised before drawing the world to the screen.</summary>
public static event EventHandler OnPreRenderEvent
{
- add => GraphicsEvents.EventManager.Legacy_OnPreRenderEvent.Add(value);
+ add
+ {
+ GraphicsEvents.DeprecationManager.WarnForOldEvents();
+ GraphicsEvents.EventManager.Legacy_OnPreRenderEvent.Add(value);
+ }
remove => GraphicsEvents.EventManager.Legacy_OnPreRenderEvent.Remove(value);
}
/// <summary>Raised after drawing the world to the screen.</summary>
public static event EventHandler OnPostRenderEvent
{
- add => GraphicsEvents.EventManager.Legacy_OnPostRenderEvent.Add(value);
+ add
+ {
+ GraphicsEvents.DeprecationManager.WarnForOldEvents();
+ GraphicsEvents.EventManager.Legacy_OnPostRenderEvent.Add(value);
+ }
remove => GraphicsEvents.EventManager.Legacy_OnPostRenderEvent.Remove(value);
}
@@ -46,14 +64,22 @@ namespace StardewModdingAPI.Events
/// <summary>Raised before drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.)</summary>
public static event EventHandler OnPreRenderHudEvent
{
- add => GraphicsEvents.EventManager.Legacy_OnPreRenderHudEvent.Add(value);
+ add
+ {
+ GraphicsEvents.DeprecationManager.WarnForOldEvents();
+ GraphicsEvents.EventManager.Legacy_OnPreRenderHudEvent.Add(value);
+ }
remove => GraphicsEvents.EventManager.Legacy_OnPreRenderHudEvent.Remove(value);
}
/// <summary>Raised after drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.)</summary>
public static event EventHandler OnPostRenderHudEvent
{
- add => GraphicsEvents.EventManager.Legacy_OnPostRenderHudEvent.Add(value);
+ add
+ {
+ GraphicsEvents.DeprecationManager.WarnForOldEvents();
+ GraphicsEvents.EventManager.Legacy_OnPostRenderHudEvent.Add(value);
+ }
remove => GraphicsEvents.EventManager.Legacy_OnPostRenderHudEvent.Remove(value);
}
@@ -63,14 +89,22 @@ namespace StardewModdingAPI.Events
/// <summary>Raised before drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen.</summary>
public static event EventHandler OnPreRenderGuiEvent
{
- add => GraphicsEvents.EventManager.Legacy_OnPreRenderGuiEvent.Add(value);
+ add
+ {
+ GraphicsEvents.DeprecationManager.WarnForOldEvents();
+ GraphicsEvents.EventManager.Legacy_OnPreRenderGuiEvent.Add(value);
+ }
remove => GraphicsEvents.EventManager.Legacy_OnPreRenderGuiEvent.Remove(value);
}
/// <summary>Raised after drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen.</summary>
public static event EventHandler OnPostRenderGuiEvent
{
- add => GraphicsEvents.EventManager.Legacy_OnPostRenderGuiEvent.Add(value);
+ add
+ {
+ GraphicsEvents.DeprecationManager.WarnForOldEvents();
+ GraphicsEvents.EventManager.Legacy_OnPostRenderGuiEvent.Add(value);
+ }
remove => GraphicsEvents.EventManager.Legacy_OnPostRenderGuiEvent.Remove(value);
}
@@ -80,9 +114,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
GraphicsEvents.EventManager = eventManager;
+ GraphicsEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/InputEvents.cs b/src/SMAPI/Events/InputEvents.cs
index 4c1781a5..255b9c8a 100644
--- a/src/SMAPI/Events/InputEvents.cs
+++ b/src/SMAPI/Events/InputEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when the player uses a controller, keyboard, or mouse button.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class InputEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,14 +25,22 @@ namespace StardewModdingAPI.Events
/// <summary>Raised when the player presses a button on the keyboard, controller, or mouse.</summary>
public static event EventHandler<EventArgsInput> ButtonPressed
{
- add => InputEvents.EventManager.Legacy_ButtonPressed.Add(value);
+ add
+ {
+ InputEvents.DeprecationManager.WarnForOldEvents();
+ InputEvents.EventManager.Legacy_ButtonPressed.Add(value);
+ }
remove => InputEvents.EventManager.Legacy_ButtonPressed.Remove(value);
}
/// <summary>Raised when the player releases a keyboard key on the keyboard, controller, or mouse.</summary>
public static event EventHandler<EventArgsInput> ButtonReleased
{
- add => InputEvents.EventManager.Legacy_ButtonReleased.Add(value);
+ add
+ {
+ InputEvents.DeprecationManager.WarnForOldEvents();
+ InputEvents.EventManager.Legacy_ButtonReleased.Add(value);
+ }
remove => InputEvents.EventManager.Legacy_ButtonReleased.Remove(value);
}
@@ -36,9 +50,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
InputEvents.EventManager = eventManager;
+ InputEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/InventoryChangedEventArgs.cs b/src/SMAPI/Events/InventoryChangedEventArgs.cs
index a081611b..874c2e48 100644
--- a/src/SMAPI/Events/InventoryChangedEventArgs.cs
+++ b/src/SMAPI/Events/InventoryChangedEventArgs.cs
@@ -23,6 +23,9 @@ namespace StardewModdingAPI.Events
/// <summary>The items whose stack sizes changed, with the relative change.</summary>
public IEnumerable<ItemStackSizeChange> QuantityChanged { get; }
+ /// <summary>Whether the affected player is the local one.</summary>
+ public bool IsLocalPlayer => this.Player.IsLocalPlayer;
+
/*********
** Public methods
@@ -30,7 +33,7 @@ namespace StardewModdingAPI.Events
/// <summary>Construct an instance.</summary>
/// <param name="player">The player whose inventory changed.</param>
/// <param name="changedItems">The inventory changes.</param>
- public InventoryChangedEventArgs(Farmer player, ItemStackChange[] changedItems)
+ internal InventoryChangedEventArgs(Farmer player, ItemStackChange[] changedItems)
{
this.Player = player;
this.Added = changedItems
diff --git a/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs b/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs
index 63b12687..c7d55bf8 100644
--- a/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs
+++ b/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs
@@ -29,7 +29,7 @@ namespace StardewModdingAPI.Events
/// <param name="location">The location which changed.</param>
/// <param name="added">The large terrain features added to the location.</param>
/// <param name="removed">The large terrain features removed from the location.</param>
- public LargeTerrainFeatureListChangedEventArgs(GameLocation location, IEnumerable<LargeTerrainFeature> added, IEnumerable<LargeTerrainFeature> removed)
+ internal LargeTerrainFeatureListChangedEventArgs(GameLocation location, IEnumerable<LargeTerrainFeature> added, IEnumerable<LargeTerrainFeature> removed)
{
this.Location = location;
this.Added = added.ToArray();
diff --git a/src/SMAPI/Events/LevelChangedEventArgs.cs b/src/SMAPI/Events/LevelChangedEventArgs.cs
index 174094c7..c7303603 100644
--- a/src/SMAPI/Events/LevelChangedEventArgs.cs
+++ b/src/SMAPI/Events/LevelChangedEventArgs.cs
@@ -22,6 +22,9 @@ namespace StardewModdingAPI.Events
/// <summary>The new skill level.</summary>
public int NewLevel { get; }
+ /// <summary>Whether the affected player is the local one.</summary>
+ public bool IsLocalPlayer => this.Player.IsLocalPlayer;
+
/*********
** Public methods
@@ -31,7 +34,7 @@ namespace StardewModdingAPI.Events
/// <param name="skill">The skill whose level changed.</param>
/// <param name="oldLevel">The previous skill level.</param>
/// <param name="newLevel">The new skill level.</param>
- public LevelChangedEventArgs(Farmer player, SkillType skill, int oldLevel, int newLevel)
+ internal LevelChangedEventArgs(Farmer player, SkillType skill, int oldLevel, int newLevel)
{
this.Player = player;
this.Skill = skill;
diff --git a/src/SMAPI/Events/LocationEvents.cs b/src/SMAPI/Events/LocationEvents.cs
index 81f547ae..e0bcd853 100644
--- a/src/SMAPI/Events/LocationEvents.cs
+++ b/src/SMAPI/Events/LocationEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when the player transitions between game locations, a location is added or removed, or the objects in the current location change.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class LocationEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,21 +25,33 @@ namespace StardewModdingAPI.Events
/// <summary>Raised after a game location is added or removed.</summary>
public static event EventHandler<EventArgsLocationsChanged> LocationsChanged
{
- add => LocationEvents.EventManager.Legacy_LocationsChanged.Add(value);
+ add
+ {
+ LocationEvents.DeprecationManager.WarnForOldEvents();
+ LocationEvents.EventManager.Legacy_LocationsChanged.Add(value);
+ }
remove => LocationEvents.EventManager.Legacy_LocationsChanged.Remove(value);
}
/// <summary>Raised after buildings are added or removed in a location.</summary>
public static event EventHandler<EventArgsLocationBuildingsChanged> BuildingsChanged
{
- add => LocationEvents.EventManager.Legacy_BuildingsChanged.Add(value);
+ add
+ {
+ LocationEvents.DeprecationManager.WarnForOldEvents();
+ LocationEvents.EventManager.Legacy_BuildingsChanged.Add(value);
+ }
remove => LocationEvents.EventManager.Legacy_BuildingsChanged.Remove(value);
}
/// <summary>Raised after objects are added or removed in a location.</summary>
public static event EventHandler<EventArgsLocationObjectsChanged> ObjectsChanged
{
- add => LocationEvents.EventManager.Legacy_ObjectsChanged.Add(value);
+ add
+ {
+ LocationEvents.DeprecationManager.WarnForOldEvents();
+ LocationEvents.EventManager.Legacy_ObjectsChanged.Add(value);
+ }
remove => LocationEvents.EventManager.Legacy_ObjectsChanged.Remove(value);
}
@@ -43,9 +61,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
LocationEvents.EventManager = eventManager;
+ LocationEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/LocationListChangedEventArgs.cs b/src/SMAPI/Events/LocationListChangedEventArgs.cs
index e93f0a80..1ebb3e2d 100644
--- a/src/SMAPI/Events/LocationListChangedEventArgs.cs
+++ b/src/SMAPI/Events/LocationListChangedEventArgs.cs
@@ -24,7 +24,7 @@ namespace StardewModdingAPI.Events
/// <summary>Construct an instance.</summary>
/// <param name="added">The added locations.</param>
/// <param name="removed">The removed locations.</param>
- public LocationListChangedEventArgs(IEnumerable<GameLocation> added, IEnumerable<GameLocation> removed)
+ internal LocationListChangedEventArgs(IEnumerable<GameLocation> added, IEnumerable<GameLocation> removed)
{
this.Added = added.ToArray();
this.Removed = removed.ToArray();
diff --git a/src/SMAPI/Events/MenuChangedEventArgs.cs b/src/SMAPI/Events/MenuChangedEventArgs.cs
index e1c049a2..977ba38b 100644
--- a/src/SMAPI/Events/MenuChangedEventArgs.cs
+++ b/src/SMAPI/Events/MenuChangedEventArgs.cs
@@ -22,7 +22,7 @@ namespace StardewModdingAPI.Events
/// <summary>Construct an instance.</summary>
/// <param name="oldMenu">The previous menu.</param>
/// <param name="newMenu">The current menu.</param>
- public MenuChangedEventArgs(IClickableMenu oldMenu, IClickableMenu newMenu)
+ internal MenuChangedEventArgs(IClickableMenu oldMenu, IClickableMenu newMenu)
{
this.OldMenu = oldMenu;
this.NewMenu = newMenu;
diff --git a/src/SMAPI/Events/MenuEvents.cs b/src/SMAPI/Events/MenuEvents.cs
index 362b5070..e36cb498 100644
--- a/src/SMAPI/Events/MenuEvents.cs
+++ b/src/SMAPI/Events/MenuEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when a game menu is opened or closed (including internal menus like the title screen).</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class MenuEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,14 +25,22 @@ namespace StardewModdingAPI.Events
/// <summary>Raised after a game menu is opened or replaced with another menu. This event is not invoked when a menu is closed.</summary>
public static event EventHandler<EventArgsClickableMenuChanged> MenuChanged
{
- add => MenuEvents.EventManager.Legacy_MenuChanged.Add(value);
+ add
+ {
+ MenuEvents.DeprecationManager.WarnForOldEvents();
+ MenuEvents.EventManager.Legacy_MenuChanged.Add(value);
+ }
remove => MenuEvents.EventManager.Legacy_MenuChanged.Remove(value);
}
/// <summary>Raised after a game menu is closed.</summary>
public static event EventHandler<EventArgsClickableMenuClosed> MenuClosed
{
- add => MenuEvents.EventManager.Legacy_MenuClosed.Add(value);
+ add
+ {
+ MenuEvents.DeprecationManager.WarnForOldEvents();
+ MenuEvents.EventManager.Legacy_MenuClosed.Add(value);
+ }
remove => MenuEvents.EventManager.Legacy_MenuClosed.Remove(value);
}
@@ -36,9 +50,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
MenuEvents.EventManager = eventManager;
+ MenuEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/MineEvents.cs b/src/SMAPI/Events/MineEvents.cs
index f5565a76..954c844a 100644
--- a/src/SMAPI/Events/MineEvents.cs
+++ b/src/SMAPI/Events/MineEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when something happens in the mines.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class MineEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,7 +25,11 @@ namespace StardewModdingAPI.Events
/// <summary>Raised after the player warps to a new level of the mine.</summary>
public static event EventHandler<EventArgsMineLevelChanged> MineLevelChanged
{
- add => MineEvents.EventManager.Legacy_MineLevelChanged.Add(value);
+ add
+ {
+ MineEvents.DeprecationManager.WarnForOldEvents();
+ MineEvents.EventManager.Legacy_MineLevelChanged.Add(value);
+ }
remove => MineEvents.EventManager.Legacy_MineLevelChanged.Remove(value);
}
@@ -29,9 +39,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
MineEvents.EventManager = eventManager;
+ MineEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/MouseWheelScrolledEventArgs.cs b/src/SMAPI/Events/MouseWheelScrolledEventArgs.cs
index 3ab9d412..0c736b39 100644
--- a/src/SMAPI/Events/MouseWheelScrolledEventArgs.cs
+++ b/src/SMAPI/Events/MouseWheelScrolledEventArgs.cs
@@ -28,7 +28,7 @@ namespace StardewModdingAPI.Events
/// <param name="position">The cursor position.</param>
/// <param name="oldValue">The old scroll value.</param>
/// <param name="newValue">The new scroll value.</param>
- public MouseWheelScrolledEventArgs(ICursorPosition position, int oldValue, int newValue)
+ internal MouseWheelScrolledEventArgs(ICursorPosition position, int oldValue, int newValue)
{
this.Position = position;
this.OldValue = oldValue;
diff --git a/src/SMAPI/Events/MultiplayerEvents.cs b/src/SMAPI/Events/MultiplayerEvents.cs
index 49de380e..7e8328a4 100644
--- a/src/SMAPI/Events/MultiplayerEvents.cs
+++ b/src/SMAPI/Events/MultiplayerEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised during the multiplayer sync process.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class MultiplayerEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,28 +25,44 @@ namespace StardewModdingAPI.Events
/// <summary>Raised before the game syncs changes from other players.</summary>
public static event EventHandler BeforeMainSync
{
- add => MultiplayerEvents.EventManager.Legacy_BeforeMainSync.Add(value);
+ add
+ {
+ MultiplayerEvents.DeprecationManager.WarnForOldEvents();
+ MultiplayerEvents.EventManager.Legacy_BeforeMainSync.Add(value);
+ }
remove => MultiplayerEvents.EventManager.Legacy_BeforeMainSync.Remove(value);
}
/// <summary>Raised after the game syncs changes from other players.</summary>
public static event EventHandler AfterMainSync
{
- add => MultiplayerEvents.EventManager.Legacy_AfterMainSync.Add(value);
+ add
+ {
+ MultiplayerEvents.DeprecationManager.WarnForOldEvents();
+ MultiplayerEvents.EventManager.Legacy_AfterMainSync.Add(value);
+ }
remove => MultiplayerEvents.EventManager.Legacy_AfterMainSync.Remove(value);
}
/// <summary>Raised before the game broadcasts changes to other players.</summary>
public static event EventHandler BeforeMainBroadcast
{
- add => MultiplayerEvents.EventManager.Legacy_BeforeMainBroadcast.Add(value);
+ add
+ {
+ MultiplayerEvents.DeprecationManager.WarnForOldEvents();
+ MultiplayerEvents.EventManager.Legacy_BeforeMainBroadcast.Add(value);
+ }
remove => MultiplayerEvents.EventManager.Legacy_BeforeMainBroadcast.Remove(value);
}
/// <summary>Raised after the game broadcasts changes to other players.</summary>
public static event EventHandler AfterMainBroadcast
{
- add => MultiplayerEvents.EventManager.Legacy_AfterMainBroadcast.Add(value);
+ add
+ {
+ MultiplayerEvents.DeprecationManager.WarnForOldEvents();
+ MultiplayerEvents.EventManager.Legacy_AfterMainBroadcast.Add(value);
+ }
remove => MultiplayerEvents.EventManager.Legacy_AfterMainBroadcast.Remove(value);
}
@@ -50,9 +72,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
MultiplayerEvents.EventManager = eventManager;
+ MultiplayerEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/NpcListChangedEventArgs.cs b/src/SMAPI/Events/NpcListChangedEventArgs.cs
index eca28244..a9ec2a3b 100644
--- a/src/SMAPI/Events/NpcListChangedEventArgs.cs
+++ b/src/SMAPI/Events/NpcListChangedEventArgs.cs
@@ -28,7 +28,7 @@ namespace StardewModdingAPI.Events
/// <param name="location">The location which changed.</param>
/// <param name="added">The NPCs added to the location.</param>
/// <param name="removed">The NPCs removed from the location.</param>
- public NpcListChangedEventArgs(GameLocation location, IEnumerable<NPC> added, IEnumerable<NPC> removed)
+ internal NpcListChangedEventArgs(GameLocation location, IEnumerable<NPC> added, IEnumerable<NPC> removed)
{
this.Location = location;
this.Added = added.ToArray();
diff --git a/src/SMAPI/Events/ObjectListChangedEventArgs.cs b/src/SMAPI/Events/ObjectListChangedEventArgs.cs
index 55a4034f..d0cf9e7b 100644
--- a/src/SMAPI/Events/ObjectListChangedEventArgs.cs
+++ b/src/SMAPI/Events/ObjectListChangedEventArgs.cs
@@ -30,7 +30,7 @@ namespace StardewModdingAPI.Events
/// <param name="location">The location which changed.</param>
/// <param name="added">The objects added to the location.</param>
/// <param name="removed">The objects removed from the location.</param>
- public ObjectListChangedEventArgs(GameLocation location, IEnumerable<KeyValuePair<Vector2, Object>> added, IEnumerable<KeyValuePair<Vector2, Object>> removed)
+ internal ObjectListChangedEventArgs(GameLocation location, IEnumerable<KeyValuePair<Vector2, Object>> added, IEnumerable<KeyValuePair<Vector2, Object>> removed)
{
this.Location = location;
this.Added = added.ToArray();
diff --git a/src/SMAPI/Events/PlayerEvents.cs b/src/SMAPI/Events/PlayerEvents.cs
index bfc1b569..1193675f 100644
--- a/src/SMAPI/Events/PlayerEvents.cs
+++ b/src/SMAPI/Events/PlayerEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when the player data changes.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class PlayerEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,21 +25,33 @@ namespace StardewModdingAPI.Events
/// <summary>Raised after the player's inventory changes in any way (added or removed item, sorted, etc).</summary>
public static event EventHandler<EventArgsInventoryChanged> InventoryChanged
{
- add => PlayerEvents.EventManager.Legacy_InventoryChanged.Add(value);
+ add
+ {
+ PlayerEvents.DeprecationManager.WarnForOldEvents();
+ PlayerEvents.EventManager.Legacy_InventoryChanged.Add(value);
+ }
remove => PlayerEvents.EventManager.Legacy_InventoryChanged.Remove(value);
}
/// <summary>Raised after the player levels up a skill. This happens as soon as they level up, not when the game notifies the player after their character goes to bed.</summary>
public static event EventHandler<EventArgsLevelUp> LeveledUp
{
- add => PlayerEvents.EventManager.Legacy_LeveledUp.Add(value);
+ add
+ {
+ PlayerEvents.DeprecationManager.WarnForOldEvents();
+ PlayerEvents.EventManager.Legacy_LeveledUp.Add(value);
+ }
remove => PlayerEvents.EventManager.Legacy_LeveledUp.Remove(value);
}
/// <summary>Raised after the player warps to a new location.</summary>
public static event EventHandler<EventArgsPlayerWarped> Warped
{
- add => PlayerEvents.EventManager.Legacy_PlayerWarped.Add(value);
+ add
+ {
+ PlayerEvents.DeprecationManager.WarnForOldEvents();
+ PlayerEvents.EventManager.Legacy_PlayerWarped.Add(value);
+ }
remove => PlayerEvents.EventManager.Legacy_PlayerWarped.Remove(value);
}
@@ -44,9 +62,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
PlayerEvents.EventManager = eventManager;
+ PlayerEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/SaveEvents.cs b/src/SMAPI/Events/SaveEvents.cs
index 731bf9d1..156d3047 100644
--- a/src/SMAPI/Events/SaveEvents.cs
+++ b/src/SMAPI/Events/SaveEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised before and after the player saves/loads the game.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class SaveEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,42 +25,66 @@ namespace StardewModdingAPI.Events
/// <summary>Raised before the game creates the save file.</summary>
public static event EventHandler BeforeCreate
{
- add => SaveEvents.EventManager.Legacy_BeforeCreateSave.Add(value);
+ add
+ {
+ SaveEvents.DeprecationManager.WarnForOldEvents();
+ SaveEvents.EventManager.Legacy_BeforeCreateSave.Add(value);
+ }
remove => SaveEvents.EventManager.Legacy_BeforeCreateSave.Remove(value);
}
/// <summary>Raised after the game finishes creating the save file.</summary>
public static event EventHandler AfterCreate
{
- add => SaveEvents.EventManager.Legacy_AfterCreateSave.Add(value);
+ add
+ {
+ SaveEvents.DeprecationManager.WarnForOldEvents();
+ SaveEvents.EventManager.Legacy_AfterCreateSave.Add(value);
+ }
remove => SaveEvents.EventManager.Legacy_AfterCreateSave.Remove(value);
}
/// <summary>Raised before the game begins writes data to the save file.</summary>
public static event EventHandler BeforeSave
{
- add => SaveEvents.EventManager.Legacy_BeforeSave.Add(value);
+ add
+ {
+ SaveEvents.DeprecationManager.WarnForOldEvents();
+ SaveEvents.EventManager.Legacy_BeforeSave.Add(value);
+ }
remove => SaveEvents.EventManager.Legacy_BeforeSave.Remove(value);
}
/// <summary>Raised after the game finishes writing data to the save file.</summary>
public static event EventHandler AfterSave
{
- add => SaveEvents.EventManager.Legacy_AfterSave.Add(value);
+ add
+ {
+ SaveEvents.DeprecationManager.WarnForOldEvents();
+ SaveEvents.EventManager.Legacy_AfterSave.Add(value);
+ }
remove => SaveEvents.EventManager.Legacy_AfterSave.Remove(value);
}
/// <summary>Raised after the player loads a save slot.</summary>
public static event EventHandler AfterLoad
{
- add => SaveEvents.EventManager.Legacy_AfterLoad.Add(value);
+ add
+ {
+ SaveEvents.DeprecationManager.WarnForOldEvents();
+ SaveEvents.EventManager.Legacy_AfterLoad.Add(value);
+ }
remove => SaveEvents.EventManager.Legacy_AfterLoad.Remove(value);
}
/// <summary>Raised after the game returns to the title screen.</summary>
public static event EventHandler AfterReturnToTitle
{
- add => SaveEvents.EventManager.Legacy_AfterReturnToTitle.Add(value);
+ add
+ {
+ SaveEvents.DeprecationManager.WarnForOldEvents();
+ SaveEvents.EventManager.Legacy_AfterReturnToTitle.Add(value);
+ }
remove => SaveEvents.EventManager.Legacy_AfterReturnToTitle.Remove(value);
}
@@ -64,9 +94,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
SaveEvents.EventManager = eventManager;
+ SaveEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/SpecialisedEvents.cs b/src/SMAPI/Events/SpecialisedEvents.cs
index bdf25ccb..0dd726e9 100644
--- a/src/SMAPI/Events/SpecialisedEvents.cs
+++ b/src/SMAPI/Events/SpecialisedEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events serving specialised edge cases that shouldn't be used by most mods.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class SpecialisedEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,7 +25,11 @@ namespace StardewModdingAPI.Events
/// <summary>Raised when the game updates its state (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this method will trigger a stability warning in the SMAPI console.</summary>
public static event EventHandler UnvalidatedUpdateTick
{
- add => SpecialisedEvents.EventManager.Legacy_UnvalidatedUpdateTick.Add(value);
+ add
+ {
+ SpecialisedEvents.DeprecationManager.WarnForOldEvents();
+ SpecialisedEvents.EventManager.Legacy_UnvalidatedUpdateTick.Add(value);
+ }
remove => SpecialisedEvents.EventManager.Legacy_UnvalidatedUpdateTick.Remove(value);
}
@@ -29,9 +39,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
SpecialisedEvents.EventManager = eventManager;
+ SpecialisedEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs b/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs
index 562b1d3c..0992633e 100644
--- a/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs
+++ b/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs
@@ -30,7 +30,7 @@ namespace StardewModdingAPI.Events
/// <param name="location">The location which changed.</param>
/// <param name="added">The terrain features added to the location.</param>
/// <param name="removed">The terrain features removed from the location.</param>
- public TerrainFeatureListChangedEventArgs(GameLocation location, IEnumerable<KeyValuePair<Vector2, TerrainFeature>> added, IEnumerable<KeyValuePair<Vector2, TerrainFeature>> removed)
+ internal TerrainFeatureListChangedEventArgs(GameLocation location, IEnumerable<KeyValuePair<Vector2, TerrainFeature>> added, IEnumerable<KeyValuePair<Vector2, TerrainFeature>> removed)
{
this.Location = location;
this.Added = added.ToArray();
diff --git a/src/SMAPI/Events/TimeChangedEventArgs.cs b/src/SMAPI/Events/TimeChangedEventArgs.cs
index fd472092..d8349bd8 100644
--- a/src/SMAPI/Events/TimeChangedEventArgs.cs
+++ b/src/SMAPI/Events/TimeChangedEventArgs.cs
@@ -21,7 +21,7 @@ namespace StardewModdingAPI.Events
/// <summary>Construct an instance.</summary>
/// <param name="oldTime">The previous time of day in 24-hour notation (like 1600 for 4pm).</param>
/// <param name="newTime">The current time of day in 24-hour notation (like 1600 for 4pm).</param>
- public TimeChangedEventArgs(int oldTime, int newTime)
+ internal TimeChangedEventArgs(int oldTime, int newTime)
{
this.OldTime = oldTime;
this.NewTime = newTime;
diff --git a/src/SMAPI/Events/TimeEvents.cs b/src/SMAPI/Events/TimeEvents.cs
index 311ffe9e..61491dc8 100644
--- a/src/SMAPI/Events/TimeEvents.cs
+++ b/src/SMAPI/Events/TimeEvents.cs
@@ -1,9 +1,12 @@
+#if !SMAPI_3_0_STRICT
using System;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when the in-game date or time changes.</summary>
+ [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")]
public static class TimeEvents
{
/*********
@@ -12,6 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
+ /// <summary>Manages deprecation warnings.</summary>
+ private static DeprecationManager DeprecationManager;
+
/*********
** Events
@@ -19,14 +25,22 @@ namespace StardewModdingAPI.Events
/// <summary>Raised after the game begins a new day, including when loading a save.</summary>
public static event EventHandler AfterDayStarted
{
- add => TimeEvents.EventManager.Legacy_AfterDayStarted.Add(value);
+ add
+ {
+ TimeEvents.DeprecationManager.WarnForOldEvents();
+ TimeEvents.EventManager.Legacy_AfterDayStarted.Add(value);
+ }
remove => TimeEvents.EventManager.Legacy_AfterDayStarted.Remove(value);
}
/// <summary>Raised after the in-game clock changes.</summary>
public static event EventHandler<EventArgsIntChanged> TimeOfDayChanged
{
- add => TimeEvents.EventManager.Legacy_TimeOfDayChanged.Add(value);
+ add
+ {
+ TimeEvents.DeprecationManager.WarnForOldEvents();
+ TimeEvents.EventManager.Legacy_TimeOfDayChanged.Add(value);
+ }
remove => TimeEvents.EventManager.Legacy_TimeOfDayChanged.Remove(value);
}
@@ -36,9 +50,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
- internal static void Init(EventManager eventManager)
+ /// <param name="deprecationManager">Manages deprecation warnings.</param>
+ internal static void Init(EventManager eventManager, DeprecationManager deprecationManager)
{
TimeEvents.EventManager = eventManager;
+ TimeEvents.DeprecationManager = deprecationManager;
}
}
}
+#endif
diff --git a/src/SMAPI/Events/UnvalidatedUpdateTickedEventArgs.cs b/src/SMAPI/Events/UnvalidatedUpdateTickedEventArgs.cs
index 5638bdb7..95ae59d8 100644
--- a/src/SMAPI/Events/UnvalidatedUpdateTickedEventArgs.cs
+++ b/src/SMAPI/Events/UnvalidatedUpdateTickedEventArgs.cs
@@ -20,7 +20,7 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Construct an instance.</summary>
/// <param name="ticks">The number of ticks elapsed since the game started, including the current tick.</param>
- public UnvalidatedUpdateTickedEventArgs(uint ticks)
+ internal UnvalidatedUpdateTickedEventArgs(uint ticks)
{
this.Ticks = ticks;
this.IsOneSecond = this.IsMultipleOf(60);
diff --git a/src/SMAPI/Events/UnvalidatedUpdateTickingEventArgs.cs b/src/SMAPI/Events/UnvalidatedUpdateTickingEventArgs.cs
index ebadbb99..4ed781e0 100644
--- a/src/SMAPI/Events/UnvalidatedUpdateTickingEventArgs.cs
+++ b/src/SMAPI/Events/UnvalidatedUpdateTickingEventArgs.cs
@@ -20,7 +20,7 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Construct an instance.</summary>
/// <param name="ticks">The number of ticks elapsed since the game started, including the current tick.</param>
- public UnvalidatedUpdateTickingEventArgs(uint ticks)
+ internal UnvalidatedUpdateTickingEventArgs(uint ticks)
{
this.Ticks = ticks;
this.IsOneSecond = this.IsMultipleOf(60);
diff --git a/src/SMAPI/Events/UpdateTickedEventArgs.cs b/src/SMAPI/Events/UpdateTickedEventArgs.cs
index 56912643..3466b731 100644
--- a/src/SMAPI/Events/UpdateTickedEventArgs.cs
+++ b/src/SMAPI/Events/UpdateTickedEventArgs.cs
@@ -20,7 +20,7 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Construct an instance.</summary>
/// <param name="ticks">The number of ticks elapsed since the game started, including the current tick.</param>
- public UpdateTickedEventArgs(uint ticks)
+ internal UpdateTickedEventArgs(uint ticks)
{
this.Ticks = ticks;
this.IsOneSecond = this.IsMultipleOf(60);
diff --git a/src/SMAPI/Events/UpdateTickingEventArgs.cs b/src/SMAPI/Events/UpdateTickingEventArgs.cs
index 5998fd9b..d4913268 100644
--- a/src/SMAPI/Events/UpdateTickingEventArgs.cs
+++ b/src/SMAPI/Events/UpdateTickingEventArgs.cs
@@ -20,7 +20,7 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Construct an instance.</summary>
/// <param name="ticks">The number of ticks elapsed since the game started, including the current tick.</param>
- public UpdateTickingEventArgs(uint ticks)
+ internal UpdateTickingEventArgs(uint ticks)
{
this.Ticks = ticks;
this.IsOneSecond = this.IsMultipleOf(60);
diff --git a/src/SMAPI/Events/WarpedEventArgs.cs b/src/SMAPI/Events/WarpedEventArgs.cs
index 1b1c7381..95c53ad9 100644
--- a/src/SMAPI/Events/WarpedEventArgs.cs
+++ b/src/SMAPI/Events/WarpedEventArgs.cs
@@ -18,6 +18,9 @@ namespace StardewModdingAPI.Events
/// <summary>The player's current location.</summary>
public GameLocation NewLocation { get; }
+ /// <summary>Whether the affected player is the local one.</summary>
+ public bool IsLocalPlayer => this.Player.IsLocalPlayer;
+
/*********
@@ -27,7 +30,7 @@ namespace StardewModdingAPI.Events
/// <param name="player">The player who warped to a new location.</param>
/// <param name="oldLocation">The player's previous location.</param>
/// <param name="newLocation">The player's current location.</param>
- public WarpedEventArgs(Farmer player, GameLocation oldLocation, GameLocation newLocation)
+ internal WarpedEventArgs(Farmer player, GameLocation oldLocation, GameLocation newLocation)
{
this.Player = player;
this.NewLocation = newLocation;
diff --git a/src/SMAPI/Events/WindowResizedEventArgs.cs b/src/SMAPI/Events/WindowResizedEventArgs.cs
index a990ba9d..1852636a 100644
--- a/src/SMAPI/Events/WindowResizedEventArgs.cs
+++ b/src/SMAPI/Events/WindowResizedEventArgs.cs
@@ -22,7 +22,7 @@ namespace StardewModdingAPI.Events
/// <summary>Construct an instance.</summary>
/// <param name="oldSize">The previous window size.</param>
/// <param name="newSize">The current window size.</param>
- public WindowResizedEventArgs(Point oldSize, Point newSize)
+ internal WindowResizedEventArgs(Point oldSize, Point newSize)
{
this.OldSize = oldSize;
this.NewSize = newSize;
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 9eb7b5f9..08a32a9b 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -238,28 +238,30 @@ namespace StardewModdingAPI.Framework
public IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
{
// invalidate cache
- HashSet<string> removedAssetNames = new HashSet<string>();
+ IDictionary<string, Type> removedAssetNames = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
foreach (IContentManager contentManager in this.ContentManagers)
{
- foreach (string name in contentManager.InvalidateCache(predicate, dispose))
- removedAssetNames.Add(name);
+ foreach (Tuple<string, Type> asset in contentManager.InvalidateCache(predicate, dispose))
+ removedAssetNames[asset.Item1] = asset.Item2;
}
// reload core game assets
int reloaded = 0;
- foreach (string key in removedAssetNames)
+ foreach (var pair in removedAssetNames)
{
- if (this.CoreAssets.Propagate(this.MainContentManager, key)) // use an intercepted content manager
+ string key = pair.Key;
+ Type type = pair.Value;
+ if (this.CoreAssets.Propagate(this.MainContentManager, key, type)) // use an intercepted content manager
reloaded++;
}
// report result
if (removedAssetNames.Any())
- this.Monitor.Log($"Invalidated {removedAssetNames.Count} asset names: {string.Join(", ", removedAssetNames.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}. Reloaded {reloaded} core assets.", LogLevel.Trace);
+ this.Monitor.Log($"Invalidated {removedAssetNames.Count} asset names: {string.Join(", ", removedAssetNames.Keys.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}. Reloaded {reloaded} core assets.", LogLevel.Trace);
else
this.Monitor.Log("Invalidated 0 cache entries.", LogLevel.Trace);
- return removedAssetNames;
+ return removedAssetNames.Keys;
}
/// <summary>Dispose held resources.</summary>
diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
index 18aae05b..724a6e1c 100644
--- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
@@ -32,12 +32,12 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Whether the content coordinator has been disposed.</summary>
private bool IsDisposed;
- /// <summary>The language enum values indexed by locale code.</summary>
- private readonly IDictionary<string, LanguageCode> LanguageCodes;
-
/// <summary>A callback to invoke when the content manager is being disposed.</summary>
private readonly Action<BaseContentManager> OnDisposing;
+ /// <summary>The language enum values indexed by locale code.</summary>
+ protected IDictionary<string, LanguageCode> LanguageCodes { get; }
+
/*********
** Accessors
@@ -200,23 +200,25 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Purge matched assets from the cache.</summary>
/// <param name="predicate">Matches the asset keys to invalidate.</param>
/// <param name="dispose">Whether to dispose invalidated assets. This should only be <c>true</c> when they're being invalidated as part of a dispose, to avoid crashing the game.</param>
- /// <returns>Returns the number of invalidated assets.</returns>
- public IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
+ /// <returns>Returns the invalidated asset names and types.</returns>
+ public IEnumerable<Tuple<string, Type>> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
{
- HashSet<string> removeAssetNames = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
+ Dictionary<string, Type> removeAssetNames = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
this.Cache.Remove((key, type) =>
{
this.ParseCacheKey(key, out string assetName, out _);
- if (removeAssetNames.Contains(assetName) || predicate(assetName, type))
+ if (removeAssetNames.ContainsKey(assetName))
+ return true;
+ if (predicate(assetName, type))
{
- removeAssetNames.Add(assetName);
+ removeAssetNames[assetName] = type;
return true;
}
return false;
});
- return removeAssetNames;
+ return removeAssetNames.Select(p => Tuple.Create(p.Key, p.Value));
}
/// <summary>Dispose held resources.</summary>
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index a53840bc..4f3b6fbc 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StardewModdingAPI.Framework.Content;
+using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Utilities;
using StardewValley;
@@ -52,7 +53,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="language">The language code for which to load content.</param>
public override T Load<T>(string assetName, LanguageCode language)
{
+ // normalise asset name
assetName = this.AssertAndNormaliseAssetName(assetName);
+ if (this.TryParseExplicitLanguageAssetKey(assetName, out string newAssetName, out LanguageCode newLanguage))
+ return this.Load<T>(newAssetName, newLanguage);
// get from cache
if (this.IsLoaded(assetName))
@@ -124,6 +128,29 @@ namespace StardewModdingAPI.Framework.ContentManagers
return false;
}
+ /// <summary>Parse an asset key that contains an explicit language into its asset name and language, if applicable.</summary>
+ /// <param name="rawAsset">The asset key to parse.</param>
+ /// <param name="assetName">The asset name without the language code.</param>
+ /// <param name="language">The language code removed from the asset name.</param>
+ private bool TryParseExplicitLanguageAssetKey(string rawAsset, out string assetName, out LanguageCode language)
+ {
+ if (string.IsNullOrWhiteSpace(rawAsset))
+ throw new SContentLoadException("The asset key is empty.");
+
+ // extract language code
+ int splitIndex = rawAsset.LastIndexOf('.');
+ if (splitIndex != -1 && this.LanguageCodes.TryGetValue(rawAsset.Substring(splitIndex + 1), out language))
+ {
+ assetName = rawAsset.Substring(0, splitIndex);
+ return true;
+ }
+
+ // no explicit language code found
+ assetName = rawAsset;
+ language = this.Language;
+ return false;
+ }
+
/// <summary>Load the initial asset from the registered <see cref="Loaders"/>.</summary>
/// <param name="info">The basic asset metadata.</param>
/// <returns>Returns the loaded asset metadata, or <c>null</c> if no loader matched.</returns>
diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
index 1eb8b0ac..17618edd 100644
--- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
@@ -80,7 +80,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Purge matched assets from the cache.</summary>
/// <param name="predicate">Matches the asset keys to invalidate.</param>
/// <param name="dispose">Whether to dispose invalidated assets. This should only be <c>true</c> when they're being invalidated as part of a dispose, to avoid crashing the game.</param>
- /// <returns>Returns the number of invalidated assets.</returns>
- IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false);
+ /// <returns>Returns the invalidated asset names and types.</returns>
+ IEnumerable<Tuple<string, Type>> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false);
}
}
diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs
index 0fde67ee..be564c22 100644
--- a/src/SMAPI/Framework/DeprecationManager.cs
+++ b/src/SMAPI/Framework/DeprecationManager.cs
@@ -35,6 +35,12 @@ namespace StardewModdingAPI.Framework
this.ModRegistry = modRegistry;
}
+ /// <summary>Log a deprecation warning for the old-style events.</summary>
+ public void WarnForOldEvents()
+ {
+ this.Warn("legacy events", "2.9", DeprecationLevel.Notice);
+ }
+
/// <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>
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index b9d1c453..0ad85adf 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -1,5 +1,7 @@
using System.Diagnostics.CodeAnalysis;
+#if !SMAPI_3_0_STRICT
using Microsoft.Xna.Framework.Input;
+#endif
using StardewModdingAPI.Events;
namespace StardewModdingAPI.Framework.Events
@@ -156,6 +158,7 @@ namespace StardewModdingAPI.Framework.Events
public readonly ManagedEvent<UnvalidatedUpdateTickedEventArgs> UnvalidatedUpdateTicked;
+#if !SMAPI_3_0_STRICT
/*********
** Events (old)
*********/
@@ -342,6 +345,7 @@ namespace StardewModdingAPI.Framework.Events
/// <summary>Raised after the in-game clock changes.</summary>
public readonly ManagedEvent<EventArgsIntChanged> Legacy_TimeOfDayChanged;
+#endif
/*********
@@ -354,7 +358,9 @@ namespace StardewModdingAPI.Framework.Events
{
// create shortcut initialisers
ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName) => new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", monitor, modRegistry);
+#if !SMAPI_3_0_STRICT
ManagedEvent ManageEvent(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry);
+#endif
// init events (new)
this.MenuChanged = ManageEventOf<MenuChangedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.MenuChanged));
@@ -405,6 +411,7 @@ namespace StardewModdingAPI.Framework.Events
this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicking));
this.UnvalidatedUpdateTicked = ManageEventOf<UnvalidatedUpdateTickedEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicked));
+#if !SMAPI_3_0_STRICT
// init events (old)
this.Legacy_LocaleChanged = ManageEventOf<EventArgsValueChanged<string>>(nameof(ContentEvents), nameof(ContentEvents.AfterLocaleChanged));
@@ -466,6 +473,7 @@ namespace StardewModdingAPI.Framework.Events
this.Legacy_AfterDayStarted = ManageEvent(nameof(TimeEvents), nameof(TimeEvents.AfterDayStarted));
this.Legacy_TimeOfDayChanged = ManageEventOf<EventArgsIntChanged>(nameof(TimeEvents), nameof(TimeEvents.TimeOfDayChanged));
+#endif
}
}
}
diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
index 5e190e55..070d9c65 100644
--- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
@@ -21,8 +21,10 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// <summary>Create a transitional content pack.</summary>
private readonly Func<string, IManifest, IContentPack> CreateContentPack;
+#if !SMAPI_3_0_STRICT
/// <summary>Manages deprecation warnings.</summary>
private readonly DeprecationManager DeprecationManager;
+#endif
/*********
@@ -31,8 +33,10 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// <summary>The full path to the mod's folder.</summary>
public string DirectoryPath { get; }
+#if !SMAPI_3_0_STRICT
/// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
private readonly JsonHelper JsonHelper;
+#endif
/// <summary>Manages access to events raised by SMAPI, which let your mod react when something happens in the game.</summary>
public IModEvents Events { get; }
@@ -94,7 +98,6 @@ namespace StardewModdingAPI.Framework.ModHelpers
// initialise
this.DirectoryPath = modDirectory;
- this.JsonHelper = jsonHelper ?? throw new ArgumentNullException(nameof(jsonHelper));
this.Content = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper));
this.Data = dataHelper ?? throw new ArgumentNullException(nameof(dataHelper));
this.Input = new InputHelper(modID, inputState);
@@ -105,8 +108,11 @@ namespace StardewModdingAPI.Framework.ModHelpers
this.Translation = translationHelper ?? throw new ArgumentNullException(nameof(translationHelper));
this.ContentPacks = new Lazy<IContentPack[]>(contentPacks);
this.CreateContentPack = createContentPack;
- this.DeprecationManager = deprecationManager;
this.Events = events;
+#if !SMAPI_3_0_STRICT
+ this.JsonHelper = jsonHelper ?? throw new ArgumentNullException(nameof(jsonHelper));
+ this.DeprecationManager = deprecationManager;
+#endif
}
/****
@@ -131,6 +137,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
this.Data.WriteJsonFile("config.json", config);
}
+#if !SMAPI_3_0_STRICT
/****
** Generic JSON files
****/
@@ -159,23 +166,20 @@ namespace StardewModdingAPI.Framework.ModHelpers
path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path));
this.JsonHelper.WriteJsonFile(path, model);
}
+#endif
/****
** Content packs
****/
- /// <summary>Manually create a transitional content pack to support pre-SMAPI content packs. This provides a way to access legacy content packs using the SMAPI content pack APIs, but the content pack will not be visible in the log or validated by SMAPI.</summary>
+ /// <summary>Create a temporary content pack to read files from a directory. Temporary content packs will not appear in the SMAPI log and update checks will not be performed.</summary>
/// <param name="directoryPath">The absolute directory path containing the content pack files.</param>
/// <param name="id">The content pack's unique ID.</param>
/// <param name="name">The content pack name.</param>
/// <param name="description">The content pack description.</param>
/// <param name="author">The content pack author's name.</param>
/// <param name="version">The content pack version.</param>
- [Obsolete("This method supports mods which previously had their own content packs, and shouldn't be used by new mods. It will be removed in SMAPI 3.0.")]
- public IContentPack CreateTransitionalContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version)
+ public IContentPack CreateTemporaryContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version)
{
- // raise deprecation notice
- this.DeprecationManager.Warn($"{nameof(IModHelper)}.{nameof(IModHelper.CreateTransitionalContentPack)}", "2.5", DeprecationLevel.Notice);
-
// validate
if (string.IsNullOrWhiteSpace(directoryPath))
throw new ArgumentNullException(nameof(directoryPath));
@@ -200,6 +204,22 @@ namespace StardewModdingAPI.Framework.ModHelpers
return this.CreateContentPack(directoryPath, manifest);
}
+#if !SMAPI_3_0_STRICT
+ /// <summary>Manually create a transitional content pack to support pre-SMAPI content packs. This provides a way to access legacy content packs using the SMAPI content pack APIs, but the content pack will not be visible in the log or validated by SMAPI.</summary>
+ /// <param name="directoryPath">The absolute directory path containing the content pack files.</param>
+ /// <param name="id">The content pack's unique ID.</param>
+ /// <param name="name">The content pack name.</param>
+ /// <param name="description">The content pack description.</param>
+ /// <param name="author">The content pack author's name.</param>
+ /// <param name="version">The content pack version.</param>
+ [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.CreateTemporaryContentPack) + " instead")]
+ public IContentPack CreateTransitionalContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version)
+ {
+ this.DeprecationManager.Warn($"{nameof(IModHelper)}.{nameof(IModHelper.CreateTransitionalContentPack)}", "2.5", DeprecationLevel.Notice);
+ return this.CreateTemporaryContentPack(directoryPath, id, name, description, author, version);
+ }
+#endif
+
/// <summary>Get all content packs loaded for this mod.</summary>
public IEnumerable<IContentPack> GetContentPacks()
{
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index fdbfdd8d..7292cf3f 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -20,6 +20,9 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
+ /// <summary>Whether to detect paranoid mode issues.</summary>
+ private readonly bool ParanoidMode;
+
/// <summary>Metadata for mapping assemblies to the current platform.</summary>
private readonly PlatformAssemblyMap AssemblyMap;
@@ -39,9 +42,11 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>Construct an instance.</summary>
/// <param name="targetPlatform">The current game platform.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
- public AssemblyLoader(Platform targetPlatform, IMonitor monitor)
+ /// <param name="paranoidMode">Whether to detect paranoid mode issues.</param>
+ public AssemblyLoader(Platform targetPlatform, IMonitor monitor, bool paranoidMode)
{
this.Monitor = monitor;
+ this.ParanoidMode = paranoidMode;
this.AssemblyMap = this.TrackForDisposal(Constants.GetAssemblyMap(targetPlatform));
this.AssemblyDefinitionResolver = this.TrackForDisposal(new AssemblyDefinitionResolver());
this.AssemblyDefinitionResolver.AddSearchDirectory(Constants.ExecutionPath);
@@ -275,7 +280,7 @@ namespace StardewModdingAPI.Framework.ModLoading
// find (and optionally rewrite) incompatible instructions
bool anyRewritten = false;
- IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers().ToArray();
+ IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode).ToArray();
foreach (MethodDefinition method in this.GetMethods(module))
{
// check method definition
diff --git a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs
index f3555c2d..6592760e 100644
--- a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs
+++ b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs
@@ -23,7 +23,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>The instruction is compatible, but uses the <c>dynamic</c> keyword which won't work on Linux/Mac.</summary>
DetectedDynamic,
- /// <summary>The instruction is compatible, but references <see cref="SpecialisedEvents.UnvalidatedUpdateTick"/> which may impact stability.</summary>
+ /// <summary>The instruction is compatible, but references <see cref="ISpecialisedEvents.UnvalidatedUpdateTicking"/> or <see cref="ISpecialisedEvents.UnvalidatedUpdateTicked"/> which may impact stability.</summary>
DetectedUnvalidatedUpdateTick,
/// <summary>The instruction accesses the filesystem directly.</summary>
diff --git a/src/SMAPI/Framework/ModLoading/ModWarning.cs b/src/SMAPI/Framework/ModLoading/ModWarning.cs
index c62199b2..e643cb05 100644
--- a/src/SMAPI/Framework/ModLoading/ModWarning.cs
+++ b/src/SMAPI/Framework/ModLoading/ModWarning.cs
@@ -22,7 +22,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/Mac.</summary>
UsesDynamic = 8,
- /// <summary>The mod references <see cref="SpecialisedEvents.UnvalidatedUpdateTick"/> which may impact stability.</summary>
+ /// <summary>The mod references <see cref="ISpecialisedEvents.UnvalidatedUpdateTicking"/> or <see cref="ISpecialisedEvents.UnvalidatedUpdateTicked"/> which may impact stability.</summary>
UsesUnvalidatedUpdateTick = 16,
/// <summary>The mod has no update keys set.</summary>
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 4b95917b..800b9c09 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -99,11 +99,25 @@ namespace StardewModdingAPI.Framework
new Regex(@"^static SerializableDictionary<.+>\(\) called\.$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
};
+ /// <summary>Regex patterns which match console messages to show a more friendly error for.</summary>
+ private readonly Tuple<Regex, string, LogLevel>[] ReplaceConsolePatterns =
+ {
+ Tuple.Create(
+ new Regex(@"^System\.InvalidOperationException: Steamworks is not initialized\.", RegexOptions.Compiled | RegexOptions.CultureInvariant),
+#if SMAPI_FOR_WINDOWS
+ "Oops! Steam achievements won't work because Steam isn't loaded. You can launch the game through Steam to fix that (see 'Part 2: Configure Steam' in the install guide for more info: https://smapi.io/install).",
+#else
+ "Oops! Steam achievements won't work because Steam isn't loaded. You can launch the game through Steam to fix that.",
+#endif
+ LogLevel.Error
+ )
+ };
+
/// <summary>The mod toolkit used for generic mod interactions.</summary>
private readonly ModToolkit Toolkit = new ModToolkit();
/// <summary>The path to search for mods.</summary>
- private readonly string ModsPath;
+ private string ModsPath => Constants.ModsPath;
/*********
@@ -117,7 +131,7 @@ namespace StardewModdingAPI.Framework
// init paths
this.VerifyPath(modsPath);
this.VerifyPath(Constants.LogDir);
- this.ModsPath = modsPath;
+ Constants.ModsPath = modsPath;
// init log file
this.PurgeNormalLogs();
@@ -180,20 +194,22 @@ namespace StardewModdingAPI.Framework
// initialise SMAPI
try
{
+#if !SMAPI_3_0_STRICT
// hook up events
- ContentEvents.Init(this.EventManager);
- ControlEvents.Init(this.EventManager);
- GameEvents.Init(this.EventManager);
- GraphicsEvents.Init(this.EventManager);
- InputEvents.Init(this.EventManager);
- LocationEvents.Init(this.EventManager);
- MenuEvents.Init(this.EventManager);
- MineEvents.Init(this.EventManager);
- MultiplayerEvents.Init(this.EventManager);
- PlayerEvents.Init(this.EventManager);
- SaveEvents.Init(this.EventManager);
- SpecialisedEvents.Init(this.EventManager);
- TimeEvents.Init(this.EventManager);
+ ContentEvents.Init(this.EventManager, this.DeprecationManager);
+ ControlEvents.Init(this.EventManager, this.DeprecationManager);
+ GameEvents.Init(this.EventManager, this.DeprecationManager);
+ GraphicsEvents.Init(this.EventManager, this.DeprecationManager);
+ InputEvents.Init(this.EventManager, this.DeprecationManager);
+ LocationEvents.Init(this.EventManager, this.DeprecationManager);
+ MenuEvents.Init(this.EventManager, this.DeprecationManager);
+ MineEvents.Init(this.EventManager, this.DeprecationManager);
+ MultiplayerEvents.Init(this.EventManager, this.DeprecationManager);
+ PlayerEvents.Init(this.EventManager, this.DeprecationManager);
+ SaveEvents.Init(this.EventManager, this.DeprecationManager);
+ SpecialisedEvents.Init(this.EventManager, this.DeprecationManager);
+ TimeEvents.Init(this.EventManager, this.DeprecationManager);
+#endif
// init JSON parser
JsonConverter[] converters = {
@@ -216,7 +232,7 @@ namespace StardewModdingAPI.Framework
// override game
SGame.ConstructorHack = new SGameConstructorHack(this.Monitor, this.Reflection, this.Toolkit.JsonHelper);
- this.GameInstance = new SGame(this.Monitor, this.MonitorForGame, this.Reflection, this.EventManager, this.Toolkit.JsonHelper, this.ModRegistry, this.DeprecationManager, this.InitialiseAfterGameStart, this.Dispose);
+ this.GameInstance = new SGame(this.Monitor, this.MonitorForGame, this.Reflection, this.EventManager, this.Toolkit.JsonHelper, this.ModRegistry, this.DeprecationManager, this.OnLocaleChanged, this.InitialiseAfterGameStart, this.Dispose);
StardewValley.Program.gamePtr = this.GameInstance;
// add exit handler
@@ -239,12 +255,13 @@ namespace StardewModdingAPI.Framework
}
}).Start();
- // hook into game events
- ContentEvents.AfterLocaleChanged += (sender, e) => this.OnLocaleChanged();
-
// set window titles
this.GameInstance.Window.Title = $"Stardew Valley {Constants.GameVersion} - running SMAPI {Constants.ApiVersion}";
Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion}";
+#if SMAPI_3_0_STRICT
+ this.GameInstance.Window.Title += " [SMAPI 3.0 strict mode]";
+ Console.Title += " [SMAPI 3.0 strict mode]";
+#endif
}
catch (Exception ex)
{
@@ -348,8 +365,11 @@ namespace StardewModdingAPI.Framework
private void InitialiseAfterGameStart()
{
// add headers
+#if SMAPI_3_0_STRICT
+ this.Monitor.Log($"You're running SMAPI 3.0 strict mode, so most mods won't work correctly. If that wasn't intended, install the normal version of SMAPI from https://smapi.io instead.", LogLevel.Warn);
+#endif
if (this.Settings.DeveloperMode)
- this.Monitor.Log($"You configured SMAPI to run in developer mode. The console may be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing {Constants.ApiConfigPath}.", LogLevel.Info);
+ this.Monitor.Log($"You have SMAPI for developers, so the console will be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing {Constants.ApiConfigPath}.", LogLevel.Info);
if (!this.Settings.CheckForUpdates)
this.Monitor.Log($"You configured SMAPI to not check for updates. Running an old version of SMAPI is not recommended. You can enable update checks by reinstalling SMAPI or editing {Constants.ApiConfigPath}.", LogLevel.Warn);
if (!this.Monitor.WriteToConsole)
@@ -409,6 +429,11 @@ namespace StardewModdingAPI.Framework
int modsLoaded = this.ModRegistry.GetAll().Count();
this.GameInstance.Window.Title = $"Stardew Valley {Constants.GameVersion} - running SMAPI {Constants.ApiVersion} with {modsLoaded} mods";
Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion} with {modsLoaded} mods";
+#if SMAPI_3_0_STRICT
+ this.GameInstance.Window.Title += " [SMAPI 3.0 strict mode]";
+ Console.Title += " [SMAPI 3.0 strict mode]";
+#endif
+
// start SMAPI console
new Thread(this.RunConsoleLoop).Start();
@@ -701,7 +726,7 @@ namespace StardewModdingAPI.Framework
// load mods
IDictionary<IModMetadata, Tuple<string, string>> skippedMods = new Dictionary<IModMetadata, Tuple<string, string>>();
- using (AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.Platform, this.Monitor))
+ using (AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.Platform, this.Monitor, this.Settings.ParanoidWarnings))
{
// init
HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.InvariantCultureIgnoreCase);
@@ -891,11 +916,13 @@ namespace StardewModdingAPI.Framework
return false;
}
+#if !SMAPI_3_0_STRICT
// add deprecation warning for old version format
{
if (mod.Manifest?.Version is Toolkit.SemanticVersion version && version.IsLegacyFormat)
this.DeprecationManager.Warn(mod.DisplayName, "non-string manifest version", "2.8", DeprecationLevel.Notice);
}
+#endif
// validate dependencies
// Although dependences are validated before mods are loaded, a dependency may have failed to load.
@@ -1262,9 +1289,9 @@ namespace StardewModdingAPI.Framework
}
/// <summary>Redirect messages logged directly to the console to the given monitor.</summary>
- /// <param name="monitor">The monitor with which to log messages.</param>
+ /// <param name="gameMonitor">The monitor with which to log messages as the game.</param>
/// <param name="message">The message to log.</param>
- private void HandleConsoleMessage(IMonitor monitor, string message)
+ private void HandleConsoleMessage(IMonitor gameMonitor, string message)
{
// detect exception
LogLevel level = message.Contains("Exception") ? LogLevel.Error : LogLevel.Trace;
@@ -1273,8 +1300,19 @@ namespace StardewModdingAPI.Framework
if (level != LogLevel.Error && this.SuppressConsolePatterns.Any(p => p.IsMatch(message)))
return;
+ // show friendly error if applicable
+ foreach (var entry in this.ReplaceConsolePatterns)
+ {
+ if (entry.Item1.IsMatch(message))
+ {
+ this.Monitor.Log(entry.Item2, entry.Item3);
+ gameMonitor.Log(message, LogLevel.Trace);
+ return;
+ }
+ }
+
// forward to monitor
- monitor.Log(message, level);
+ gameMonitor.Log(message, level);
}
/// <summary>Show a 'press any key to exit' message, and exit when they press a key.</summary>
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index 75cf4c52..7b3335b7 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -9,7 +9,9 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
+#if !SMAPI_3_0_STRICT
using Microsoft.Xna.Framework.Input;
+#endif
using Netcode;
using StardewModdingAPI.Enums;
using StardewModdingAPI.Events;
@@ -29,7 +31,7 @@ using StardewValley.TerrainFeatures;
using StardewValley.Tools;
using xTile.Dimensions;
using xTile.Layers;
-using Object = StardewValley.Object;
+using SObject = StardewValley.Object;
namespace StardewModdingAPI.Framework
{
@@ -70,12 +72,15 @@ namespace StardewModdingAPI.Framework
/// <summary>Whether the after-load events were raised for this session.</summary>
private bool RaisedAfterLoadEvent;
- /// <summary>Whether the game is saving and SMAPI has already raised <see cref="SaveEvents.BeforeSave"/>.</summary>
+ /// <summary>Whether the game is saving and SMAPI has already raised <see cref="IGameLoopEvents.Saving"/>.</summary>
private bool IsBetweenSaveEvents;
- /// <summary>Whether the game is creating the save file and SMAPI has already raised <see cref="SaveEvents.BeforeCreate"/>.</summary>
+ /// <summary>Whether the game is creating the save file and SMAPI has already raised <see cref="IGameLoopEvents.SaveCreating"/>.</summary>
private bool IsBetweenCreateEvents;
+ /// <summary>A callback to invoke after the content language changes.</summary>
+ private readonly Action OnLocaleChanged;
+
/// <summary>A callback to invoke after the game finishes initialising.</summary>
private readonly Action OnGameInitialised;
@@ -138,9 +143,10 @@ namespace StardewModdingAPI.Framework
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
/// <param name="modRegistry">Tracks the installed mods.</param>
/// <param name="deprecationManager">Manages deprecation warnings.</param>
+ /// <param name="onLocaleChanged">A callback to invoke after the content language changes.</param>
/// <param name="onGameInitialised">A callback to invoke after the game finishes initialising.</param>
/// <param name="onGameExiting">A callback to invoke when the game exits.</param>
- internal SGame(IMonitor monitor, IMonitor monitorForGame, Reflector reflection, EventManager eventManager, JsonHelper jsonHelper, ModRegistry modRegistry, DeprecationManager deprecationManager, Action onGameInitialised, Action onGameExiting)
+ internal SGame(IMonitor monitor, IMonitor monitorForGame, Reflector reflection, EventManager eventManager, JsonHelper jsonHelper, ModRegistry modRegistry, DeprecationManager deprecationManager, Action onLocaleChanged, Action onGameInitialised, Action onGameExiting)
{
SGame.ConstructorHack = null;
@@ -158,6 +164,7 @@ namespace StardewModdingAPI.Framework
this.ModRegistry = modRegistry;
this.Reflection = reflection;
this.DeprecationManager = deprecationManager;
+ this.OnLocaleChanged = onLocaleChanged;
this.OnGameInitialised = onGameInitialised;
this.OnGameExiting = onGameExiting;
Game1.input = new SInputState();
@@ -206,6 +213,17 @@ namespace StardewModdingAPI.Framework
this.Events.ModMessageReceived.RaiseForMods(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID));
}
+ /// <summary>A callback raised when the player quits a save and returns to the title screen.</summary>
+ private void OnReturnedToTitle()
+ {
+ this.Monitor.Log("Context: returned to title", LogLevel.Trace);
+ this.Multiplayer.Peers.Clear();
+ this.Events.ReturnedToTitle.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
+ this.Events.Legacy_AfterReturnToTitle.Raise();
+#endif
+ }
+
/// <summary>Constructor a content manager to read XNB files.</summary>
/// <param name="serviceProvider">The service provider to use to locate services.</param>
/// <param name="rootDirectory">The root directory to search for content.</param>
@@ -287,7 +305,9 @@ namespace StardewModdingAPI.Framework
this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed));
base.Update(gameTime);
this.Events.UnvalidatedUpdateTicked.Raise(new UnvalidatedUpdateTickedEventArgs(this.TicksElapsed));
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_UnvalidatedUpdateTick.Raise();
+#endif
return;
}
@@ -334,7 +354,9 @@ namespace StardewModdingAPI.Framework
// This should *always* run, even when suppressing mod events, since the game uses
// this too. For example, doing this after mod event suppression would prevent the
// user from doing anything on the overnight shipping screen.
+#if !SMAPI_3_0_STRICT
SInputState previousInputState = this.Input.Clone();
+#endif
SInputState inputState = this.Input;
if (this.IsActive)
inputState.TrueUpdate();
@@ -355,7 +377,9 @@ namespace StardewModdingAPI.Framework
this.IsBetweenCreateEvents = true;
this.Monitor.Log("Context: before save creation.", LogLevel.Trace);
this.Events.SaveCreating.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_BeforeCreateSave.Raise();
+#endif
}
// raise before-save
@@ -364,14 +388,18 @@ namespace StardewModdingAPI.Framework
this.IsBetweenSaveEvents = true;
this.Monitor.Log("Context: before save.", LogLevel.Trace);
this.Events.Saving.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_BeforeSave.Raise();
+#endif
}
// suppress non-save events
this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed));
base.Update(gameTime);
this.Events.UnvalidatedUpdateTicked.Raise(new UnvalidatedUpdateTickedEventArgs(this.TicksElapsed));
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_UnvalidatedUpdateTick.Raise();
+#endif
return;
}
if (this.IsBetweenCreateEvents)
@@ -380,7 +408,9 @@ namespace StardewModdingAPI.Framework
this.IsBetweenCreateEvents = false;
this.Monitor.Log($"Context: after save creation, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
this.Events.SaveCreated.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_AfterCreateSave.Raise();
+#endif
}
if (this.IsBetweenSaveEvents)
{
@@ -389,9 +419,10 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($"Context: after save, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
this.Events.Saved.RaiseEmpty();
this.Events.DayStarted.RaiseEmpty();
-
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_AfterSave.Raise();
this.Events.Legacy_AfterDayStarted.Raise();
+#endif
}
/*********
@@ -421,7 +452,11 @@ namespace StardewModdingAPI.Framework
var now = this.Watchers.LocaleWatcher.CurrentValue;
this.Monitor.Log($"Context: locale set to {now}.", LogLevel.Trace);
+
+ this.OnLocaleChanged();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_LocaleChanged.Raise(new EventArgsValueChanged<string>(was.ToString(), now.ToString()));
+#endif
this.Watchers.LocaleWatcher.Reset();
}
@@ -430,11 +465,7 @@ namespace StardewModdingAPI.Framework
** Load / return-to-title events
*********/
if (wasWorldReady && !Context.IsWorldReady)
- {
- this.Monitor.Log("Context: returned to title", LogLevel.Trace);
- this.Events.ReturnedToTitle.RaiseEmpty();
- this.Events.Legacy_AfterReturnToTitle.Raise();
- }
+ this.OnReturnedToTitle();
else if (!this.RaisedAfterLoadEvent && Context.IsWorldReady)
{
// print context
@@ -452,9 +483,10 @@ namespace StardewModdingAPI.Framework
this.RaisedAfterLoadEvent = true;
this.Events.SaveLoaded.RaiseEmpty();
this.Events.DayStarted.RaiseEmpty();
-
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_AfterLoad.Raise();
this.Events.Legacy_AfterDayStarted.Raise();
+#endif
}
/*********
@@ -473,7 +505,9 @@ namespace StardewModdingAPI.Framework
Point newSize = this.Watchers.WindowSizeWatcher.CurrentValue;
this.Events.WindowResized.Raise(new WindowResizedEventArgs(oldSize, newSize));
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_Resize.Raise();
+#endif
this.Watchers.WindowSizeWatcher.Reset();
}
@@ -522,9 +556,10 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($"Events: button {button} pressed.", LogLevel.Trace);
this.Events.ButtonPressed.Raise(new ButtonPressedEventArgs(button, cursor, inputState));
- this.Events.Legacy_ButtonPressed.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons));
+#if !SMAPI_3_0_STRICT
// legacy events
+ this.Events.Legacy_ButtonPressed.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons));
if (button.TryGetKeyboard(out Keys key))
{
if (key != Keys.None)
@@ -537,6 +572,7 @@ namespace StardewModdingAPI.Framework
else
this.Events.Legacy_ControllerButtonPressed.Raise(new EventArgsControllerButtonPressed(PlayerIndex.One, controllerButton));
}
+#endif
}
else if (status == InputStatus.Released)
{
@@ -544,9 +580,10 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($"Events: button {button} released.", LogLevel.Trace);
this.Events.ButtonReleased.Raise(new ButtonReleasedEventArgs(button, cursor, inputState));
- this.Events.Legacy_ButtonReleased.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons));
+#if !SMAPI_3_0_STRICT
// legacy events
+ this.Events.Legacy_ButtonReleased.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons));
if (button.TryGetKeyboard(out Keys key))
{
if (key != Keys.None)
@@ -559,14 +596,17 @@ namespace StardewModdingAPI.Framework
else
this.Events.Legacy_ControllerButtonReleased.Raise(new EventArgsControllerButtonReleased(PlayerIndex.One, controllerButton));
}
+#endif
}
}
+#if !SMAPI_3_0_STRICT
// raise legacy state-changed events
if (inputState.RealKeyboard != previousInputState.RealKeyboard)
this.Events.Legacy_KeyboardChanged.Raise(new EventArgsKeyboardStateChanged(previousInputState.RealKeyboard, inputState.RealKeyboard));
if (inputState.RealMouse != previousInputState.RealMouse)
this.Events.Legacy_MouseChanged.Raise(new EventArgsMouseStateChanged(previousInputState.RealMouse, inputState.RealMouse, new Point((int)previousInputState.CursorPosition.ScreenPixels.X, (int)previousInputState.CursorPosition.ScreenPixels.Y), new Point((int)inputState.CursorPosition.ScreenPixels.X, (int)inputState.CursorPosition.ScreenPixels.Y)));
+#endif
}
}
@@ -584,10 +624,12 @@ namespace StardewModdingAPI.Framework
// raise menu events
this.Events.MenuChanged.Raise(new MenuChangedEventArgs(was, now));
+#if !SMAPI_3_0_STRICT
if (now != null)
this.Events.Legacy_MenuChanged.Raise(new EventArgsClickableMenuChanged(was, now));
else
this.Events.Legacy_MenuClosed.Raise(new EventArgsClickableMenuClosed(was));
+#endif
}
/*********
@@ -615,7 +657,9 @@ namespace StardewModdingAPI.Framework
}
this.Events.LocationListChanged.Raise(new LocationListChangedEventArgs(added, removed));
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_LocationsChanged.Raise(new EventArgsLocationsChanged(added, removed));
+#endif
}
// raise location contents changed
@@ -632,7 +676,9 @@ namespace StardewModdingAPI.Framework
watcher.BuildingsWatcher.Reset();
this.Events.BuildingListChanged.Raise(new BuildingListChangedEventArgs(location, added, removed));
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_BuildingsChanged.Raise(new EventArgsLocationBuildingsChanged(location, added, removed));
+#endif
}
// debris changed
@@ -672,12 +718,14 @@ namespace StardewModdingAPI.Framework
if (watcher.ObjectsWatcher.IsChanged)
{
GameLocation location = watcher.Location;
- KeyValuePair<Vector2, Object>[] added = watcher.ObjectsWatcher.Added.ToArray();
- KeyValuePair<Vector2, Object>[] removed = watcher.ObjectsWatcher.Removed.ToArray();
+ KeyValuePair<Vector2, SObject>[] added = watcher.ObjectsWatcher.Added.ToArray();
+ KeyValuePair<Vector2, SObject>[] removed = watcher.ObjectsWatcher.Removed.ToArray();
watcher.ObjectsWatcher.Reset();
this.Events.ObjectListChanged.Raise(new ObjectListChangedEventArgs(location, added, removed));
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_ObjectsChanged.Raise(new EventArgsLocationObjectsChanged(location, added, removed));
+#endif
}
// terrain features changed
@@ -707,7 +755,9 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($"Events: time changed from {was} to {now}.", LogLevel.Trace);
this.Events.TimeChanged.Raise(new TimeChangedEventArgs(was, now));
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_TimeOfDayChanged.Raise(new EventArgsIntChanged(was, now));
+#endif
}
else
this.Watchers.TimeWatcher.Reset();
@@ -725,7 +775,9 @@ namespace StardewModdingAPI.Framework
GameLocation oldLocation = playerTracker.LocationWatcher.PreviousValue;
this.Events.Warped.Raise(new WarpedEventArgs(playerTracker.Player, oldLocation, newLocation));
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_PlayerWarped.Raise(new EventArgsPlayerWarped(oldLocation, newLocation));
+#endif
}
// raise player leveled up a skill
@@ -735,7 +787,9 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($"Events: player skill '{pair.Key}' changed from {pair.Value.PreviousValue} to {pair.Value.CurrentValue}.", LogLevel.Trace);
this.Events.LevelChanged.Raise(new LevelChangedEventArgs(playerTracker.Player, pair.Key, pair.Value.PreviousValue, pair.Value.CurrentValue));
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_LeveledUp.Raise(new EventArgsLevelUp((EventArgsLevelUp.LevelType)pair.Key, pair.Value.CurrentValue));
+#endif
}
// raise player inventory changed
@@ -745,7 +799,9 @@ namespace StardewModdingAPI.Framework
if (this.Monitor.IsVerbose)
this.Monitor.Log("Events: player inventory changed.", LogLevel.Trace);
this.Events.InventoryChanged.Raise(new InventoryChangedEventArgs(playerTracker.Player, changedItems));
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_InventoryChanged.Raise(new EventArgsInventoryChanged(Game1.player.Items, changedItems));
+#endif
}
// raise mine level changed
@@ -753,7 +809,9 @@ namespace StardewModdingAPI.Framework
{
if (this.Monitor.IsVerbose)
this.Monitor.Log($"Context: mine level changed to {mineLevel}.", LogLevel.Trace);
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_MineLevelChanged.Raise(new EventArgsMineLevelChanged(playerTracker.MineLevelWatcher.PreviousValue, mineLevel));
+#endif
}
}
this.Watchers.CurrentPlayerTracker?.Reset();
@@ -785,6 +843,7 @@ namespace StardewModdingAPI.Framework
/*********
** Update events
*********/
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_UnvalidatedUpdateTick.Raise();
if (this.TicksElapsed == 1)
this.Events.Legacy_FirstUpdateTick.Raise();
@@ -801,6 +860,7 @@ namespace StardewModdingAPI.Framework
this.Events.Legacy_HalfSecondTick.Raise();
if (this.CurrentUpdateTick % 60 == 0)
this.Events.Legacy_OneSecondTick.Raise();
+#endif
this.CurrentUpdateTick += 1;
if (this.CurrentUpdateTick >= 60)
this.CurrentUpdateTick = 0;
@@ -890,10 +950,14 @@ namespace StardewModdingAPI.Framework
try
{
this.Events.RenderingActiveMenu.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPreRenderGuiEvent.Raise();
+#endif
activeClickableMenu.draw(Game1.spriteBatch);
this.Events.RenderedActiveMenu.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPostRenderGuiEvent.Raise();
+#endif
}
catch (Exception ex)
{
@@ -901,7 +965,9 @@ namespace StardewModdingAPI.Framework
activeClickableMenu.exitThisMenu();
}
this.Events.Rendered.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPostRenderEvent.Raise();
+#endif
Game1.spriteBatch.End();
}
@@ -925,10 +991,14 @@ namespace StardewModdingAPI.Framework
{
Game1.activeClickableMenu.drawBackground(Game1.spriteBatch);
this.Events.RenderingActiveMenu.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPreRenderGuiEvent.Raise();
+#endif
Game1.activeClickableMenu.draw(Game1.spriteBatch);
this.Events.RenderedActiveMenu.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPostRenderGuiEvent.Raise();
+#endif
}
catch (Exception ex)
{
@@ -936,7 +1006,9 @@ namespace StardewModdingAPI.Framework
Game1.activeClickableMenu.exitThisMenu();
}
this.Events.Rendered.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPostRenderEvent.Raise();
+#endif
Game1.spriteBatch.End();
this.drawOverlays(Game1.spriteBatch);
if ((double)Game1.options.zoomLevel != 1.0)
@@ -961,7 +1033,9 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3686"), new Vector2(16f, 32f), new Color(0, (int)byte.MaxValue, 0));
Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.parseText(Game1.errorMessage, Game1.dialogueFont, Game1.graphics.GraphicsDevice.Viewport.Width), new Vector2(16f, 48f), Color.White);
this.Events.Rendered.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPostRenderEvent.Raise();
+#endif
Game1.spriteBatch.End();
}
else if (Game1.currentMinigame != null)
@@ -974,7 +1048,9 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.End();
}
this.drawOverlays(Game1.spriteBatch);
+#if !SMAPI_3_0_STRICT
this.RaisePostRender(needsNewBatch: true);
+#endif
if ((double)Game1.options.zoomLevel == 1.0)
return;
this.GraphicsDevice.SetRenderTarget((RenderTarget2D)null);
@@ -992,10 +1068,14 @@ namespace StardewModdingAPI.Framework
try
{
this.Events.RenderingActiveMenu.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPreRenderGuiEvent.Raise();
+#endif
Game1.activeClickableMenu.draw(Game1.spriteBatch);
this.Events.RenderedActiveMenu.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPostRenderGuiEvent.Raise();
+#endif
}
catch (Exception ex)
{
@@ -1085,7 +1165,9 @@ namespace StardewModdingAPI.Framework
if (++batchOpens == 1)
this.Events.Rendering.RaiseEmpty();
this.Events.RenderingWorld.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPreRenderEvent.Raise();
+#endif
if (Game1.background != null)
Game1.background.draw(Game1.spriteBatch);
Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
@@ -1340,6 +1422,7 @@ namespace StardewModdingAPI.Framework
}
Game1.spriteBatch.End();
}
+ this.Events.RenderedWorld.RaiseEmpty();
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
if (Game1.drawGrid)
{
@@ -1397,10 +1480,14 @@ namespace StardewModdingAPI.Framework
if ((Game1.displayHUD || Game1.eventUp) && (Game1.currentBillboard == 0 && Game1.gameMode == (byte)3) && (!Game1.freezeControls && !Game1.panMode && !Game1.HostPaused))
{
this.Events.RenderingHud.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPreRenderHudEvent.Raise();
+#endif
this.drawHUD();
this.Events.RenderedHud.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPostRenderHudEvent.Raise();
+#endif
}
else if (Game1.activeClickableMenu == null && Game1.farmEvent == null)
Game1.spriteBatch.Draw(Game1.mouseCursors, new Vector2((float)Game1.getOldMouseX(), (float)Game1.getOldMouseY()), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.mouseCursors, 0, 16, 16)), Color.White, 0.0f, Vector2.Zero, (float)(4.0 + (double)Game1.dialogueButtonScale / 150.0), SpriteEffects.None, 1f);
@@ -1509,10 +1596,14 @@ namespace StardewModdingAPI.Framework
try
{
this.Events.RenderingActiveMenu.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPreRenderGuiEvent.Raise();
+#endif
Game1.activeClickableMenu.draw(Game1.spriteBatch);
this.Events.RenderedActiveMenu.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPostRenderGuiEvent.Raise();
+#endif
}
catch (Exception ex)
{
@@ -1527,9 +1618,11 @@ namespace StardewModdingAPI.Framework
string s = Game1.content.LoadString("Strings\\StringsFromCSFiles:DayTimeMoneyBox.cs.10378");
SpriteText.drawStringWithScrollBackground(Game1.spriteBatch, s, 96, 32, "", 1f, -1);
}
- this.Events.RenderedWorld.RaiseEmpty();
+
this.Events.Rendered.RaiseEmpty();
+#if !SMAPI_3_0_STRICT
this.Events.Legacy_OnPostRenderEvent.Raise();
+#endif
Game1.spriteBatch.End();
this.drawOverlays(Game1.spriteBatch);
this.renderScreenBuffer();
@@ -1549,6 +1642,7 @@ namespace StardewModdingAPI.Framework
this.RaisedAfterLoadEvent = false;
}
+#if !SMAPI_3_0_STRICT
/// <summary>Raise the <see cref="GraphicsEvents.OnPostRenderEvent"/> if there are any listeners.</summary>
/// <param name="needsNewBatch">Whether to create a new sprite batch.</param>
private void RaisePostRender(bool needsNewBatch = false)
@@ -1562,5 +1656,6 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.End();
}
}
+#endif
}
}
diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs
index 629fce1d..12cd2d46 100644
--- a/src/SMAPI/Framework/SMultiplayer.cs
+++ b/src/SMAPI/Framework/SMultiplayer.cs
@@ -82,6 +82,7 @@ namespace StardewModdingAPI.Framework
this.OnModMessageReceived = onModMessageReceived;
}
+#if !SMAPI_3_0_STRICT
/// <summary>Handle sync messages from other players and perform other initial sync logic.</summary>
public override void UpdateEarly()
{
@@ -97,6 +98,7 @@ namespace StardewModdingAPI.Framework
base.UpdateLate(forceSync);
this.EventManager.Legacy_AfterMainBroadcast.Raise();
}
+#endif
/// <summary>Initialise a client before the game connects to a remote server.</summary>
/// <param name="client">The client to initialise.</param>
diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs
new file mode 100644
index 00000000..2ea6609a
--- /dev/null
+++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs
@@ -0,0 +1,83 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
+{
+ /// <summary>A watcher which detects changes to a collection of values using a specified <see cref="IEqualityComparer{T}"/> instance.</summary>
+ /// <typeparam name="TValue">The value type within the collection.</typeparam>
+ internal class ComparableListWatcher<TValue> : BaseDisposableWatcher, ICollectionWatcher<TValue>
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The collection to watch.</summary>
+ private readonly ICollection<TValue> CurrentValues;
+
+ /// <summary>The values during the previous update.</summary>
+ private HashSet<TValue> LastValues;
+
+ /// <summary>The pairs added since the last reset.</summary>
+ private readonly List<TValue> AddedImpl = new List<TValue>();
+
+ /// <summary>The pairs removed since the last reset.</summary>
+ private readonly List<TValue> RemovedImpl = new List<TValue>();
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>Whether the value changed since the last reset.</summary>
+ public bool IsChanged => this.AddedImpl.Count > 0 || this.RemovedImpl.Count > 0;
+
+ /// <summary>The values added since the last reset.</summary>
+ public IEnumerable<TValue> Added => this.AddedImpl;
+
+ /// <summary>The values removed since the last reset.</summary>
+ public IEnumerable<TValue> Removed => this.RemovedImpl;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="values">The collection to watch.</param>
+ /// <param name="comparer">The equality comparer which indicates whether two values are the same.</param>
+ public ComparableListWatcher(ICollection<TValue> values, IEqualityComparer<TValue> comparer)
+ {
+ this.CurrentValues = values;
+ this.LastValues = new HashSet<TValue>(comparer);
+ }
+
+ /// <summary>Update the current value if needed.</summary>
+ public void Update()
+ {
+ this.AssertNotDisposed();
+
+ // optimise for zero items
+ if (this.CurrentValues.Count == 0)
+ {
+ if (this.LastValues.Count > 0)
+ {
+ this.AddedImpl.AddRange(this.LastValues);
+ this.LastValues.Clear();
+ }
+ return;
+ }
+
+ // detect changes
+ HashSet<TValue> curValues = new HashSet<TValue>(this.CurrentValues, this.LastValues.Comparer);
+ this.RemovedImpl.AddRange(from value in this.LastValues where !curValues.Contains(value) select value);
+ this.AddedImpl.AddRange(from value in curValues where !this.LastValues.Contains(value) select value);
+ this.LastValues = curValues;
+ }
+
+ /// <summary>Set the current value as the baseline.</summary>
+ public void Reset()
+ {
+ this.AssertNotDisposed();
+
+ this.AddedImpl.Clear();
+ this.RemovedImpl.Clear();
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs
index d51fc2ac..dda30a15 100644
--- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs
+++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs
@@ -4,26 +4,27 @@ using System.Collections.Generic;
namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
{
/// <summary>A watcher which detects changes to a value using a specified <see cref="IEqualityComparer{T}"/> instance.</summary>
- internal class ComparableWatcher<T> : IValueWatcher<T>
+ /// <typeparam name="TValue">The comparable value type.</typeparam>
+ internal class ComparableWatcher<TValue> : IValueWatcher<TValue>
{
/*********
** Properties
*********/
/// <summary>Get the current value.</summary>
- private readonly Func<T> GetValue;
+ private readonly Func<TValue> GetValue;
/// <summary>The equality comparer.</summary>
- private readonly IEqualityComparer<T> Comparer;
+ private readonly IEqualityComparer<TValue> Comparer;
/*********
** Accessors
*********/
/// <summary>The field value at the last reset.</summary>
- public T PreviousValue { get; private set; }
+ public TValue PreviousValue { get; private set; }
/// <summary>The latest value.</summary>
- public T CurrentValue { get; private set; }
+ public TValue CurrentValue { get; private set; }
/// <summary>Whether the value changed since the last reset.</summary>
public bool IsChanged { get; private set; }
@@ -35,7 +36,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
/// <summary>Construct an instance.</summary>
/// <param name="getValue">Get the current value.</param>
/// <param name="comparer">The equality comparer which indicates whether two values are the same.</param>
- public ComparableWatcher(Func<T> getValue, IEqualityComparer<T> comparer)
+ public ComparableWatcher(Func<TValue> getValue, IEqualityComparer<TValue> comparer)
{
this.GetValue = getValue;
this.Comparer = comparer;
diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs
index 8a841a79..d3022a69 100644
--- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs
+++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs
@@ -4,6 +4,7 @@ using Netcode;
namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
{
/// <summary>A watcher which detects changes to a Netcode collection.</summary>
+ /// <typeparam name="TValue">The value type within the collection.</typeparam>
internal class NetCollectionWatcher<TValue> : BaseDisposableWatcher, ICollectionWatcher<TValue>
where TValue : class, INetObject<INetSerializable>
{
@@ -16,7 +17,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
/// <summary>The pairs added since the last reset.</summary>
private readonly List<TValue> AddedImpl = new List<TValue>();
- /// <summary>The pairs demoved since the last reset.</summary>
+ /// <summary>The pairs removed since the last reset.</summary>
private readonly List<TValue> RemovedImpl = new List<TValue>();
diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs
index 7a2bf84e..7a7ab89d 100644
--- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs
+++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs
@@ -20,7 +20,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
/// <summary>The pairs added since the last reset.</summary>
private readonly IDictionary<TKey, TValue> PairsAdded = new Dictionary<TKey, TValue>();
- /// <summary>The pairs demoved since the last reset.</summary>
+ /// <summary>The pairs removed since the last reset.</summary>
private readonly IDictionary<TKey, TValue> PairsRemoved = new Dictionary<TKey, TValue>();
/// <summary>The field being watched.</summary>
diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs
index 188ed9f3..85099988 100644
--- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs
+++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs
@@ -3,13 +3,15 @@ using Netcode;
namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
{
/// <summary>A watcher which detects changes to a net value field.</summary>
- internal class NetValueWatcher<T, TSelf> : BaseDisposableWatcher, IValueWatcher<T> where TSelf : NetFieldBase<T, TSelf>
+ /// <typeparam name="TValue">The value type wrapped by the net field.</typeparam>
+ /// <typeparam name="TNetField">The net field type.</typeparam>
+ internal class NetValueWatcher<TValue, TNetField> : BaseDisposableWatcher, IValueWatcher<TValue> where TNetField : NetFieldBase<TValue, TNetField>
{
/*********
** Properties
*********/
/// <summary>The field being watched.</summary>
- private readonly NetFieldBase<T, TSelf> Field;
+ private readonly NetFieldBase<TValue, TNetField> Field;
/*********
@@ -19,10 +21,10 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
public bool IsChanged { get; private set; }
/// <summary>The field value at the last reset.</summary>
- public T PreviousValue { get; private set; }
+ public TValue PreviousValue { get; private set; }
/// <summary>The latest value.</summary>
- public T CurrentValue { get; private set; }
+ public TValue CurrentValue { get; private set; }
/*********
@@ -30,7 +32,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
*********/
/// <summary>Construct an instance.</summary>
/// <param name="field">The field to watch.</param>
- public NetValueWatcher(NetFieldBase<T, TSelf> field)
+ public NetValueWatcher(NetFieldBase<TValue, TNetField> field)
{
this.Field = field;
this.PreviousValue = field.Value;
@@ -74,7 +76,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
/// <param name="field">The field being watched.</param>
/// <param name="oldValue">The old field value.</param>
/// <param name="newValue">The new field value.</param>
- private void OnValueChanged(TSelf field, T oldValue, T newValue)
+ private void OnValueChanged(TNetField field, TValue oldValue, TValue newValue)
{
this.CurrentValue = newValue;
this.IsChanged = true;
diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs
index 34a97097..0c65789f 100644
--- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs
+++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs
@@ -6,6 +6,7 @@ using System.Linq;
namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
{
/// <summary>A watcher which detects changes to an observable collection.</summary>
+ /// <typeparam name="TValue">The value type within the collection.</typeparam>
internal class ObservableCollectionWatcher<TValue> : BaseDisposableWatcher, ICollectionWatcher<TValue>
{
/*********
@@ -17,7 +18,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
/// <summary>The pairs added since the last reset.</summary>
private readonly List<TValue> AddedImpl = new List<TValue>();
- /// <summary>The pairs demoved since the last reset.</summary>
+ /// <summary>The pairs removed since the last reset.</summary>
private readonly List<TValue> RemovedImpl = new List<TValue>();
diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs
index ab4ab0d5..8301351e 100644
--- a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs
+++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs
@@ -36,6 +36,14 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
return new ComparableWatcher<T>(getValue, new ObjectReferenceComparer<T>());
}
+ /// <summary>Get a watcher which detects when an object reference in a collection changes.</summary>
+ /// <typeparam name="T">The value type.</typeparam>
+ /// <param name="collection">The observable collection.</param>
+ public static ComparableListWatcher<T> ForReferenceList<T>(ICollection<T> collection)
+ {
+ return new ComparableListWatcher<T>(collection, new ObjectReferenceComparer<T>());
+ }
+
/// <summary>Get a watcher for an observable collection.</summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="collection">The observable collection.</param>
diff --git a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs
index 5a259663..d9d598f8 100644
--- a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs
+++ b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -19,6 +18,9 @@ namespace StardewModdingAPI.Framework.StateTracking
/// <summary>Tracks changes to the location list.</summary>
private readonly ICollectionWatcher<GameLocation> LocationListWatcher;
+ /// <summary>Tracks changes to the list of active mine locations.</summary>
+ private readonly ICollectionWatcher<MineShaft> MineLocationListWatcher;
+
/// <summary>A lookup of the tracked locations.</summary>
private IDictionary<GameLocation, LocationTracker> LocationDict { get; } = new Dictionary<GameLocation, LocationTracker>(new ObjectReferenceComparer<GameLocation>());
@@ -50,24 +52,34 @@ namespace StardewModdingAPI.Framework.StateTracking
*********/
/// <summary>Construct an instance.</summary>
/// <param name="locations">The game's list of locations.</param>
- public WorldLocationsTracker(ObservableCollection<GameLocation> locations)
+ /// <param name="activeMineLocations">The game's list of active mine locations.</param>
+ public WorldLocationsTracker(ObservableCollection<GameLocation> locations, IList<MineShaft> activeMineLocations)
{
this.LocationListWatcher = WatcherFactory.ForObservableCollection(locations);
+ this.MineLocationListWatcher = WatcherFactory.ForReferenceList(activeMineLocations);
}
/// <summary>Update the current value if needed.</summary>
public void Update()
{
- // detect location changes
+ // detect added/removed locations
+ this.LocationListWatcher.Update();
+ this.MineLocationListWatcher.Update();
if (this.LocationListWatcher.IsChanged)
{
this.Remove(this.LocationListWatcher.Removed);
this.Add(this.LocationListWatcher.Added);
}
+ if (this.MineLocationListWatcher.IsChanged)
+ {
+ this.Remove(this.MineLocationListWatcher.Removed);
+ this.Add(this.MineLocationListWatcher.Added);
+ }
- // detect building changes
+ // detect building changed
foreach (LocationTracker watcher in this.Locations.ToArray())
{
+ watcher.Update();
if (watcher.BuildingsWatcher.IsChanged)
{
this.Remove(watcher.BuildingsWatcher.Removed);
@@ -75,7 +87,7 @@ namespace StardewModdingAPI.Framework.StateTracking
}
}
- // detect building interior changed (e.g. construction completed)
+ // detect building interiors changed (e.g. construction completed)
foreach (KeyValuePair<Building, GameLocation> pair in this.BuildingIndoors.Where(p => !object.Equals(p.Key.indoors.Value, p.Value)))
{
GameLocation oldIndoors = pair.Value;
@@ -86,10 +98,6 @@ namespace StardewModdingAPI.Framework.StateTracking
if (newIndoors != null)
this.Removed.Add(newIndoors);
}
-
- // update watchers
- foreach (IWatcher watcher in this.Locations)
- watcher.Update();
}
/// <summary>Set the current location list as the baseline.</summary>
@@ -98,21 +106,21 @@ namespace StardewModdingAPI.Framework.StateTracking
this.Removed.Clear();
this.Added.Clear();
this.LocationListWatcher.Reset();
+ this.MineLocationListWatcher.Reset();
}
/// <summary>Set the current value as the baseline.</summary>
public void Reset()
{
this.ResetLocationList();
- foreach (IWatcher watcher in this.Locations)
+ foreach (IWatcher watcher in this.GetWatchers())
watcher.Reset();
}
/// <summary>Stop watching the player fields and release all references.</summary>
public void Dispose()
{
- this.LocationListWatcher.Dispose();
- foreach (IWatcher watcher in this.Locations)
+ foreach (IWatcher watcher in this.GetWatchers())
watcher.Dispose();
}
@@ -180,11 +188,11 @@ namespace StardewModdingAPI.Framework.StateTracking
// remove old location if needed
this.Remove(location);
- // track change
+ // add location
this.Added.Add(location);
-
- // add
this.LocationDict[location] = new LocationTracker(location);
+
+ // add buildings
if (location is BuildableGameLocation buildableLocation)
this.Add(buildableLocation.buildings);
}
@@ -219,5 +227,17 @@ namespace StardewModdingAPI.Framework.StateTracking
this.Remove(buildableLocation.buildings);
}
}
+
+ /****
+ ** Helpers
+ ****/
+ /// <summary>The underlying watchers.</summary>
+ private IEnumerable<IWatcher> GetWatchers()
+ {
+ yield return this.LocationListWatcher;
+ yield return this.MineLocationListWatcher;
+ foreach (LocationTracker watcher in this.Locations)
+ yield return watcher;
+ }
}
}
diff --git a/src/SMAPI/Framework/WatcherCore.cs b/src/SMAPI/Framework/WatcherCore.cs
index e06423b9..8d29cf18 100644
--- a/src/SMAPI/Framework/WatcherCore.cs
+++ b/src/SMAPI/Framework/WatcherCore.cs
@@ -5,6 +5,7 @@ using StardewModdingAPI.Framework.Input;
using StardewModdingAPI.Framework.StateTracking;
using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
using StardewValley;
+using StardewValley.Locations;
using StardewValley.Menus;
namespace StardewModdingAPI.Framework
@@ -64,7 +65,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((ObservableCollection<GameLocation>)Game1.locations);
+ this.LocationsWatcher = new WorldLocationsTracker((ObservableCollection<GameLocation>)Game1.locations, MineShaft.activeMines);
this.LocaleWatcher = WatcherFactory.ForGenericEquality(() => LocalizedContentManager.CurrentLanguageCode);
this.Watchers.AddRange(new IWatcher[]
{
diff --git a/src/SMAPI/IModHelper.cs b/src/SMAPI/IModHelper.cs
index e4b5d390..fbe3d51f 100644
--- a/src/SMAPI/IModHelper.cs
+++ b/src/SMAPI/IModHelper.cs
@@ -14,7 +14,6 @@ namespace StardewModdingAPI
string DirectoryPath { get; }
/// <summary>Manages access to events raised by SMAPI, which let your mod react when something happens in the game.</summary>
- [Obsolete("This is an experimental interface which may change at any time. Don't depend on this for released mods.")]
IModEvents Events { get; }
/// <summary>An API for managing console commands.</summary>
@@ -57,6 +56,7 @@ namespace StardewModdingAPI
/// <param name="config">The config settings to save.</param>
void WriteConfig<TConfig>(TConfig config) where TConfig : class, new();
+#if !SMAPI_3_0_STRICT
/****
** Generic JSON files
****/
@@ -73,10 +73,21 @@ namespace StardewModdingAPI
/// <param name="model">The model to save.</param>
[Obsolete("Use " + nameof(IModHelper.Data) + "." + nameof(IDataHelper.WriteJsonFile) + " instead")]
void WriteJsonFile<TModel>(string path, TModel model) where TModel : class;
+#endif
/****
** Content packs
****/
+ /// <summary>Create a temporary content pack to read files from a directory. Temporary content packs will not appear in the SMAPI log and update checks will not be performed.</summary>
+ /// <param name="directoryPath">The absolute directory path containing the content pack files.</param>
+ /// <param name="id">The content pack's unique ID.</param>
+ /// <param name="name">The content pack name.</param>
+ /// <param name="description">The content pack description.</param>
+ /// <param name="author">The content pack author's name.</param>
+ /// <param name="version">The content pack version.</param>
+ IContentPack CreateTemporaryContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version);
+
+#if !SMAPI_3_0_STRICT
/// <summary>Manually create a transitional content pack to support pre-SMAPI content packs. This provides a way to access legacy content packs using the SMAPI content pack APIs, but the content pack will not be visible in the log or validated by SMAPI.</summary>
/// <param name="directoryPath">The absolute directory path containing the content pack files.</param>
/// <param name="id">The content pack's unique ID.</param>
@@ -84,8 +95,9 @@ namespace StardewModdingAPI
/// <param name="description">The content pack description.</param>
/// <param name="author">The content pack author's name.</param>
/// <param name="version">The content pack version.</param>
- [Obsolete("This method supports mods which previously had their own content packs, and shouldn't be used by new mods. It will be removed in SMAPI 3.0.")]
+ [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.CreateTemporaryContentPack) + " instead")]
IContentPack CreateTransitionalContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version);
+#endif
/// <summary>Get all content packs loaded for this mod.</summary>
IEnumerable<IContentPack> GetContentPacks();
diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs
index 8487b6be..d273c251 100644
--- a/src/SMAPI/Metadata/CoreAssetPropagator.cs
+++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Reflection;
using StardewValley;
@@ -13,6 +14,7 @@ using StardewValley.Menus;
using StardewValley.Objects;
using StardewValley.Projectiles;
using StardewValley.TerrainFeatures;
+using xTile;
using xTile.Tiles;
namespace StardewModdingAPI.Metadata
@@ -45,10 +47,11 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload one of the game's core assets (if applicable).</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
/// <param name="key">The asset key to reload.</param>
+ /// <param name="type">The asset type to reload.</param>
/// <returns>Returns whether an asset was reloaded.</returns>
- public bool Propagate(LocalizedContentManager content, string key)
+ public bool Propagate(LocalizedContentManager content, string key, Type type)
{
- object result = this.PropagateImpl(content, key);
+ object result = this.PropagateImpl(content, key, type);
if (result is bool b)
return b;
return result != null;
@@ -61,9 +64,12 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload one of the game's core assets (if applicable).</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
/// <param name="key">The asset key to reload.</param>
- /// <returns>Returns any non-null value to indicate an asset was loaded.</returns>
- private object PropagateImpl(LocalizedContentManager content, string key)
+ /// <param name="type">The asset type to reload.</param>
+ /// <returns>Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true.</returns>
+ private object PropagateImpl(LocalizedContentManager content, string key, Type type)
{
+ key = this.GetNormalisedPath(key);
+
/****
** Special case: current map tilesheet
** We only need to do this for the current location, since tilesheets are reloaded when you enter a location.
@@ -79,6 +85,24 @@ namespace StardewModdingAPI.Metadata
}
/****
+ ** Propagate map changes
+ ****/
+ if (type == typeof(Map))
+ {
+ bool anyChanged = false;
+ foreach (GameLocation location in this.GetLocations())
+ {
+ if (this.GetNormalisedPath(location.mapPath.Value) == key)
+ {
+ this.Reflection.GetMethod(location, "reloadMap").Invoke();
+ this.Reflection.GetMethod(location, "updateWarps").Invoke();
+ anyChanged = true;
+ }
+ }
+ return anyChanged;
+ }
+
+ /****
** Propagate by key
****/
Reflector reflection = this.Reflection;
@@ -141,6 +165,9 @@ namespace StardewModdingAPI.Metadata
case "data\\craftingrecipes": // CraftingRecipe.InitShared
return CraftingRecipe.craftingRecipes = content.Load<Dictionary<string, string>>(key);
+ case "data\\npcdispositions": // NPC constructor
+ return this.ReloadNpcDispositions(content, key);
+
case "data\\npcgifttastes": // Game1.loadContent
return Game1.NPCGiftTastes = content.Load<Dictionary<string, string>>(key);
@@ -460,6 +487,35 @@ namespace StardewModdingAPI.Metadata
return true;
}
+ /// <summary>Reload the disposition data for matching NPCs.</summary>
+ /// <param name="content">The content manager through which to reload the asset.</param>
+ /// <param name="key">The asset key to reload.</param>
+ /// <returns>Returns whether any NPCs were affected.</returns>
+ private bool ReloadNpcDispositions(LocalizedContentManager content, string key)
+ {
+ IDictionary<string, string> dispositions = content.Load<Dictionary<string, string>>(key);
+ foreach (NPC character in this.GetCharacters())
+ {
+ if (!character.isVillager() || !dispositions.ContainsKey(character.Name))
+ continue;
+
+ NPC clone = new NPC(null, Vector2.Zero, 0, character.Name);
+ character.Age = clone.Age;
+ character.Manners = clone.Manners;
+ character.SocialAnxiety = clone.SocialAnxiety;
+ character.Optimism = clone.Optimism;
+ character.Gender = clone.Gender;
+ character.datable.Value = clone.datable.Value;
+ character.homeRegion = clone.homeRegion;
+ character.Birthday_Season = clone.Birthday_Season;
+ character.Birthday_Day = clone.Birthday_Day;
+ character.id = clone.id;
+ character.displayName = clone.displayName;
+ }
+
+ return true;
+ }
+
/// <summary>Reload the sprites for matching NPCs.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
/// <param name="key">The asset key to reload.</param>
@@ -586,24 +642,6 @@ namespace StardewModdingAPI.Metadata
this.Reflection.GetField<Texture2D>(sprite, "spriteTexture").SetValue(texture);
}
- /// <summary>Get an NPC name from the name of their file under <c>Content/Characters</c>.</summary>
- /// <param name="name">The file name.</param>
- /// <remarks>Derived from <see cref="NPC.reloadSprite"/>.</remarks>
- private string GetNpcNameFromFileName(string name)
- {
- switch (name)
- {
- case "Mariner":
- return "Old Mariner";
- case "DwarfKing":
- return "Dwarf King";
- case "MrQi":
- return "Mister Qi";
- default:
- return name;
- }
- }
-
/// <summary>Get all NPCs in the game (excluding farm animals).</summary>
private IEnumerable<NPC> GetCharacters()
{
diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs
index ff8d54e3..7c840b2f 100644
--- a/src/SMAPI/Metadata/InstructionMetadata.cs
+++ b/src/SMAPI/Metadata/InstructionMetadata.cs
@@ -24,51 +24,55 @@ namespace StardewModdingAPI.Metadata
** Public methods
*********/
/// <summary>Get rewriters which detect or fix incompatible CIL instructions in mod assemblies.</summary>
- public IEnumerable<IInstructionHandler> GetHandlers()
+ /// <param name="paranoidMode">Whether to detect paranoid mode issues.</param>
+ public IEnumerable<IInstructionHandler> GetHandlers(bool paranoidMode)
{
- return new IInstructionHandler[]
- {
- /****
- ** rewrite CIL to fix incompatible code
- ****/
- // rewrite for crossplatform compatibility
- new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchMethods), onlyIfPlatformChanged: true),
+ /****
+ ** rewrite CIL to fix incompatible code
+ ****/
+ // rewrite for crossplatform compatibility
+ yield return new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchMethods), onlyIfPlatformChanged: true);
+
+ // rewrite for Stardew Valley 1.3
+ yield return new StaticFieldToConstantRewriter<int>(typeof(Game1), "tileSize", Game1.tileSize);
- // rewrite for Stardew Valley 1.3
- new StaticFieldToConstantRewriter<int>(typeof(Game1), "tileSize", Game1.tileSize),
+ /****
+ ** detect mod issues
+ ****/
+ // detect broken code
+ yield return new ReferenceToMissingMemberFinder(this.ValidateReferencesToAssemblies);
+ yield return new ReferenceToMemberWithUnexpectedTypeFinder(this.ValidateReferencesToAssemblies);
- /****
- ** detect mod issues
- ****/
- // detect broken code
- new ReferenceToMissingMemberFinder(this.ValidateReferencesToAssemblies),
- new ReferenceToMemberWithUnexpectedTypeFinder(this.ValidateReferencesToAssemblies),
+ /****
+ ** detect code which may impact game stability
+ ****/
+ yield return new TypeFinder("Harmony.HarmonyInstance", InstructionHandleResult.DetectedGamePatch);
+ yield return new TypeFinder("System.Runtime.CompilerServices.CallSite", InstructionHandleResult.DetectedDynamic);
+ yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.serializer), InstructionHandleResult.DetectedSaveSerialiser);
+ yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.farmerSerializer), InstructionHandleResult.DetectedSaveSerialiser);
+ yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.locationSerializer), InstructionHandleResult.DetectedSaveSerialiser);
+ yield return new TypeFinder(typeof(ISpecialisedEvents).FullName, InstructionHandleResult.DetectedUnvalidatedUpdateTick);
+#if !SMAPI_3_0_STRICT
+ yield return new EventFinder(typeof(SpecialisedEvents).FullName, nameof(SpecialisedEvents.UnvalidatedUpdateTick), InstructionHandleResult.DetectedUnvalidatedUpdateTick);
+#endif
- /****
- ** detect code which may impact game stability
- ****/
- new TypeFinder("Harmony.HarmonyInstance", InstructionHandleResult.DetectedGamePatch),
- new TypeFinder("System.Runtime.CompilerServices.CallSite", InstructionHandleResult.DetectedDynamic),
- new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.serializer), InstructionHandleResult.DetectedSaveSerialiser),
- new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.farmerSerializer), InstructionHandleResult.DetectedSaveSerialiser),
- new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.locationSerializer), InstructionHandleResult.DetectedSaveSerialiser),
- new EventFinder(typeof(SpecialisedEvents).FullName, nameof(SpecialisedEvents.UnvalidatedUpdateTick), InstructionHandleResult.DetectedUnvalidatedUpdateTick),
-
- /****
- ** detect paranoid issues
- ****/
+ /****
+ ** detect paranoid issues
+ ****/
+ if (paranoidMode)
+ {
// filesystem access
- new TypeFinder(typeof(System.IO.File).FullName, InstructionHandleResult.DetectedFilesystemAccess),
- new TypeFinder(typeof(System.IO.FileStream).FullName, InstructionHandleResult.DetectedFilesystemAccess),
- new TypeFinder(typeof(System.IO.FileInfo).FullName, InstructionHandleResult.DetectedFilesystemAccess),
- new TypeFinder(typeof(System.IO.Directory).FullName, InstructionHandleResult.DetectedFilesystemAccess),
- new TypeFinder(typeof(System.IO.DirectoryInfo).FullName, InstructionHandleResult.DetectedFilesystemAccess),
- new TypeFinder(typeof(System.IO.DriveInfo).FullName, InstructionHandleResult.DetectedFilesystemAccess),
- new TypeFinder(typeof(System.IO.FileSystemWatcher).FullName, InstructionHandleResult.DetectedFilesystemAccess),
+ yield return new TypeFinder(typeof(System.IO.File).FullName, InstructionHandleResult.DetectedFilesystemAccess);
+ yield return new TypeFinder(typeof(System.IO.FileStream).FullName, InstructionHandleResult.DetectedFilesystemAccess);
+ yield return new TypeFinder(typeof(System.IO.FileInfo).FullName, InstructionHandleResult.DetectedFilesystemAccess);
+ yield return new TypeFinder(typeof(System.IO.Directory).FullName, InstructionHandleResult.DetectedFilesystemAccess);
+ yield return new TypeFinder(typeof(System.IO.DirectoryInfo).FullName, InstructionHandleResult.DetectedFilesystemAccess);
+ yield return new TypeFinder(typeof(System.IO.DriveInfo).FullName, InstructionHandleResult.DetectedFilesystemAccess);
+ yield return new TypeFinder(typeof(System.IO.FileSystemWatcher).FullName, InstructionHandleResult.DetectedFilesystemAccess);
// shell access
- new TypeFinder(typeof(System.Diagnostics.Process).FullName, InstructionHandleResult.DetectedShellAccess)
- };
+ yield return new TypeFinder(typeof(System.Diagnostics.Process).FullName, InstructionHandleResult.DetectedShellAccess);
+ }
}
}
}
diff --git a/src/SMAPI/SemanticVersion.cs b/src/SMAPI/SemanticVersion.cs
index 401f62c2..f75105df 100644
--- a/src/SMAPI/SemanticVersion.cs
+++ b/src/SMAPI/SemanticVersion.cs
@@ -29,6 +29,7 @@ namespace StardewModdingAPI
/// <summary>The patch version for backwards-compatible bug fixes.</summary>
public int PatchVersion => this.Version.PatchVersion;
+#if !SMAPI_3_0_STRICT
/// <summary>An optional build tag.</summary>
[Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")]
public string Build
@@ -39,6 +40,7 @@ namespace StardewModdingAPI
return this.Version.PrereleaseTag;
}
}
+#endif
/// <summary>An optional prerelease tag.</summary>
public string PrereleaseTag => this.Version.PrereleaseTag;
diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj
index 5a098b8a..49a88f37 100644
--- a/src/SMAPI/StardewModdingAPI.csproj
+++ b/src/SMAPI/StardewModdingAPI.csproj
@@ -32,7 +32,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<PlatformTarget>x86</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <DefineConstants>DEBUG;TRACE;SMAPI_3_0_STRICT</DefineConstants>
<UseVSHostingProcess>true</UseVSHostingProcess>
<Optimize>false</Optimize>
<OutputPath>$(SolutionDir)\..\bin\Debug\SMAPI</OutputPath>
@@ -205,6 +205,7 @@
<Compile Include="Framework\SModHooks.cs" />
<Compile Include="Framework\Singleton.cs" />
<Compile Include="Framework\StateTracking\Comparers\GenericEqualsComparer.cs" />
+ <Compile Include="Framework\StateTracking\FieldWatchers\ComparableListWatcher.cs" />
<Compile Include="Framework\WatcherCore.cs" />
<Compile Include="IDataHelper.cs" />
<Compile Include="IInputHelper.cs" />
diff --git a/src/StardewModdingAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs b/src/StardewModdingAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs
index 6631b01d..0a6e5758 100644
--- a/src/StardewModdingAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs
+++ b/src/StardewModdingAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs
@@ -17,9 +17,11 @@ namespace StardewModdingAPI
/// <summary>The patch version for backwards-compatible bug fixes.</summary>
int PatchVersion { get; }
+#if !SMAPI_3_0_STRICT
/// <summary>An optional build tag.</summary>
[Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")]
string Build { get; }
+#endif
/// <summary>An optional prerelease tag.</summary>
string PrereleaseTag { get; }
diff --git a/src/StardewModdingAPI.Toolkit.CoreInterfaces/StardewModdingAPI.Toolkit.CoreInterfaces.csproj b/src/StardewModdingAPI.Toolkit.CoreInterfaces/StardewModdingAPI.Toolkit.CoreInterfaces.csproj
index 525931e5..539cb5d8 100644
--- a/src/StardewModdingAPI.Toolkit.CoreInterfaces/StardewModdingAPI.Toolkit.CoreInterfaces.csproj
+++ b/src/StardewModdingAPI.Toolkit.CoreInterfaces/StardewModdingAPI.Toolkit.CoreInterfaces.csproj
@@ -8,6 +8,10 @@
<DocumentationFile>..\..\bin\$(Configuration)\SMAPI.Toolkit.CoreInterfaces\$(TargetFramework)\StardewModdingAPI.Toolkit.CoreInterfaces.xml</DocumentationFile>
</PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+ <DefineConstants>$(DefineConstants);SMAPI_3_0_STRICT</DefineConstants>
+ </PropertyGroup>
+
<ItemGroup>
<Compile Include="..\..\build\GlobalAssemblyInfo.cs" Link="Properties\GlobalAssemblyInfo.cs" />
</ItemGroup>
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs
index 247730d7..c9d9f916 100644
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs
+++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs
@@ -28,6 +28,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
/// <summary>The mod ID in the Chucklefish mod repo.</summary>
public int? ChucklefishID { get; set; }
+ /// <summary>The mod ID in the ModDrop mod repo.</summary>
+ public int? ModDropID { get; set; }
+
/// <summary>The GitHub repository in the form 'owner/repo'.</summary>
public string GitHubRepo { get; set; }
@@ -77,6 +80,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
this.Name = wiki.Name.FirstOrDefault();
this.NexusID = wiki.NexusID;
this.ChucklefishID = wiki.ChucklefishID;
+ this.ModDropID = wiki.ModDropID;
this.GitHubRepo = wiki.GitHubRepo;
this.CustomSourceUrl = wiki.CustomSourceUrl;
this.CustomUrl = wiki.CustomUrl;
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
index 7197bf2c..91078b08 100644
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
+++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
@@ -52,6 +52,8 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
// fetch game versions
string stableVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-stable-version']")?.InnerText;
string betaVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-beta-version']")?.InnerText;
+ if (betaVersion == stableVersion)
+ betaVersion = null;
// find mod entries
HtmlNodeCollection modNodes = doc.DocumentNode.SelectNodes("table[@id='mod-list']//tr[@class='mod']");
@@ -91,6 +93,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
string[] warnings = this.GetAttributeAsCsv(node, "data-warnings");
int? nexusID = this.GetAttributeAsNullableInt(node, "data-nexus-id");
int? chucklefishID = this.GetAttributeAsNullableInt(node, "data-cf-id");
+ int? modDropID = this.GetAttributeAsNullableInt(node, "data-moddrop-id");
string githubRepo = this.GetAttribute(node, "data-github");
string customSourceUrl = this.GetAttribute(node, "data-custom-source");
string customUrl = this.GetAttribute(node, "data-url");
@@ -131,6 +134,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
Author = authors,
NexusID = nexusID,
ChucklefishID = chucklefishID,
+ ModDropID = modDropID,
GitHubRepo = githubRepo,
CustomSourceUrl = customSourceUrl,
CustomUrl = customUrl,
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs
index ce8d6c5f..f7b7839b 100644
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs
+++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs
@@ -21,6 +21,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
/// <summary>The mod ID in the Chucklefish mod repo.</summary>
public int? ChucklefishID { get; set; }
+ /// <summary>The mod ID in the ModDrop mod repo.</summary>
+ public int? ModDropID { get; set; }
+
/// <summary>The GitHub repository in the form 'owner/repo'.</summary>
public string GitHubRepo { get; set; }
diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs
index 106c294f..61d0d6f2 100644
--- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs
+++ b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs
@@ -90,6 +90,14 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
}
}
+ // normalise display fields
+ if (manifest != null)
+ {
+ manifest.Name = this.StripNewlines(manifest.Name);
+ manifest.Description = this.StripNewlines(manifest.Description);
+ manifest.Author = this.StripNewlines(manifest.Author);
+ }
+
return new ModFolder(root, manifestFile.Directory, manifest, manifestError);
}
@@ -164,5 +172,12 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
{
return !this.IgnoreFilesystemEntries.Contains(entry.Name);
}
+
+ /// <summary>Strip newlines from a string.</summary>
+ /// <param name="input">The input to strip.</param>
+ private string StripNewlines(string input)
+ {
+ return input?.Replace("\r", "").Replace("\n", "");
+ }
}
}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs
index 7ca32f04..f6c402d5 100644
--- a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs
+++ b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs
@@ -12,6 +12,9 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <summary>A GitHub project containing releases.</summary>
GitHub,
+ /// <summary>The ModDrop mod repository.</summary>
+ ModDrop,
+
/// <summary>The Nexus Mods mod repository.</summary>
Nexus
}
diff --git a/src/StardewModdingAPI.Toolkit/SemanticVersion.cs b/src/StardewModdingAPI.Toolkit/SemanticVersion.cs
index a7990d13..19da0a76 100644
--- a/src/StardewModdingAPI.Toolkit/SemanticVersion.cs
+++ b/src/StardewModdingAPI.Toolkit/SemanticVersion.cs
@@ -40,14 +40,16 @@ namespace StardewModdingAPI.Toolkit
public int PatchVersion { get; }
/// <summary>An optional prerelease tag.</summary>
- [Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")]
- public string Build => this.PrereleaseTag;
+ public string PrereleaseTag { get; }
+#if !SMAPI_3_0_STRICT
/// <summary>An optional prerelease tag.</summary>
- public string PrereleaseTag { get; }
+ [Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")]
+ public string Build => this.PrereleaseTag;
/// <summary>Whether the version was parsed from the legacy object format.</summary>
public bool IsLegacyFormat { get; }
+#endif
/*********
@@ -57,15 +59,21 @@ namespace StardewModdingAPI.Toolkit
/// <param name="major">The major version incremented for major API changes.</param>
/// <param name="minor">The minor version incremented for backwards-compatible changes.</param>
/// <param name="patch">The patch version for backwards-compatible fixes.</param>
- /// <param name="tag">An optional prerelease tag.</param>
+ /// <param name="prereleaseTag">An optional prerelease tag.</param>
/// <param name="isLegacyFormat">Whether the version was parsed from the legacy object format.</param>
- public SemanticVersion(int major, int minor, int patch, string tag = null, bool isLegacyFormat = false)
+ public SemanticVersion(int major, int minor, int patch, string prereleaseTag = null
+#if !SMAPI_3_0_STRICT
+ , bool isLegacyFormat = false
+#endif
+ )
{
this.MajorVersion = major;
this.MinorVersion = minor;
this.PatchVersion = patch;
- this.PrereleaseTag = this.GetNormalisedTag(tag);
+ this.PrereleaseTag = this.GetNormalisedTag(prereleaseTag);
+#if !SMAPI_3_0_STRICT
this.IsLegacyFormat = isLegacyFormat;
+#endif
this.AssertValid();
}
@@ -114,7 +122,7 @@ namespace StardewModdingAPI.Toolkit
{
if (other == null)
throw new ArgumentNullException(nameof(other));
- return this.CompareTo(other.MajorVersion, other.MinorVersion, other.PatchVersion, other.Build);
+ return this.CompareTo(other.MajorVersion, other.MinorVersion, other.PatchVersion, other.PrereleaseTag);
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
@@ -128,7 +136,7 @@ namespace StardewModdingAPI.Toolkit
/// <summary>Whether this is a pre-release version.</summary>
public bool IsPrerelease()
{
- return !string.IsNullOrWhiteSpace(this.Build);
+ return !string.IsNullOrWhiteSpace(this.PrereleaseTag);
}
/// <summary>Get whether this version is older than the specified version.</summary>
@@ -187,7 +195,7 @@ namespace StardewModdingAPI.Toolkit
: $"{this.MajorVersion}.{this.MinorVersion}";
// tag
- string tag = this.Build;
+ string tag = this.PrereleaseTag;
if (tag != null)
result += $"-{tag}";
return result;
@@ -241,11 +249,11 @@ namespace StardewModdingAPI.Toolkit
return this.MinorVersion.CompareTo(otherMinor);
if (this.PatchVersion != otherPatch)
return this.PatchVersion.CompareTo(otherPatch);
- if (this.Build == otherTag)
+ if (this.PrereleaseTag == otherTag)
return same;
// stable supercedes pre-release
- bool curIsStable = string.IsNullOrWhiteSpace(this.Build);
+ bool curIsStable = string.IsNullOrWhiteSpace(this.PrereleaseTag);
bool otherIsStable = string.IsNullOrWhiteSpace(otherTag);
if (curIsStable)
return curNewer;
@@ -253,7 +261,7 @@ namespace StardewModdingAPI.Toolkit
return curOlder;
// compare two pre-release tag values
- string[] curParts = this.Build.Split('.', '-');
+ string[] curParts = this.PrereleaseTag.Split('.', '-');
string[] otherParts = otherTag.Split('.', '-');
for (int i = 0; i < curParts.Length; i++)
{
@@ -292,11 +300,11 @@ namespace StardewModdingAPI.Toolkit
throw new FormatException($"{this} isn't a valid semantic version. The major, minor, and patch numbers can't be negative.");
if (this.MajorVersion == 0 && this.MinorVersion == 0 && this.PatchVersion == 0)
throw new FormatException($"{this} isn't a valid semantic version. At least one of the major, minor, and patch numbers must be more than zero.");
- if (this.Build != null)
+ if (this.PrereleaseTag != null)
{
- if (this.Build.Trim() == "")
+ if (this.PrereleaseTag.Trim() == "")
throw new FormatException($"{this} isn't a valid semantic version. The tag cannot be a blank string (but may be omitted).");
- if (!Regex.IsMatch(this.Build, $"^{SemanticVersion.TagPattern}$", RegexOptions.IgnoreCase))
+ if (!Regex.IsMatch(this.PrereleaseTag, $"^{SemanticVersion.TagPattern}$", RegexOptions.IgnoreCase))
throw new FormatException($"{this} isn't a valid semantic version. The tag is invalid.");
}
}
diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs
index e0e185c9..aca06849 100644
--- a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs
+++ b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs
@@ -63,14 +63,24 @@ namespace StardewModdingAPI.Toolkit.Serialisation.Converters
/// <param name="obj">The JSON object to read.</param>
private ISemanticVersion ReadObject(JObject obj)
{
- int major = obj.ValueIgnoreCase<int>("MajorVersion");
- int minor = obj.ValueIgnoreCase<int>("MinorVersion");
- int patch = obj.ValueIgnoreCase<int>("PatchVersion");
- string build = obj.ValueIgnoreCase<string>("Build");
- if (build == "0")
- build = null; // '0' from incorrect examples in old SMAPI documentation
+ int major = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MajorVersion));
+ int minor = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MinorVersion));
+ int patch = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.PatchVersion));
+ string prereleaseTag = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.PrereleaseTag));
+#if !SMAPI_3_0_STRICT
+ if (string.IsNullOrWhiteSpace(prereleaseTag))
+ {
+ prereleaseTag = obj.ValueIgnoreCase<string>("Build");
+ if (prereleaseTag == "0")
+ prereleaseTag = null; // '0' from incorrect examples in old SMAPI documentation
+ }
+#endif
- return new SemanticVersion(major, minor, patch, build, isLegacyFormat: true);
+ return new SemanticVersion(major, minor, patch, prereleaseTag
+#if !SMAPI_3_0_STRICT
+ , isLegacyFormat: true
+#endif
+ );
}
/// <summary>Read a JSON string.</summary>
diff --git a/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj b/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj
index 3fa28d19..29667b1e 100644
--- a/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj
+++ b/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj
@@ -7,6 +7,10 @@
<DocumentationFile>..\..\bin\$(Configuration)\SMAPI.Toolkit\$(TargetFramework)\StardewModdingAPI.Toolkit.xml</DocumentationFile>
</PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+ <DefineConstants>$(DefineConstants);SMAPI_3_0_STRICT</DefineConstants>
+ </PropertyGroup>
+
<ItemGroup>
<Compile Include="..\..\build\GlobalAssemblyInfo.cs" Link="Properties\GlobalAssemblyInfo.cs" />
</ItemGroup>