Merge branch 'develop' into stable
This commit is contained in:
commit
d51ffe58f7
|
@ -1,10 +1,19 @@
|
|||
<!--
|
||||
|
||||
This MSBuild file sets the common configuration and build scripts used by all the projects in this
|
||||
repo. It imports the other MSBuild files as needed.
|
||||
|
||||
-->
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<!--set general build properties -->
|
||||
<Version>3.15.0</Version>
|
||||
<Version>3.15.1</Version>
|
||||
<Product>SMAPI</Product>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||
<DefineConstants>$(DefineConstants);SMAPI_DEPRECATED</DefineConstants>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
<!--enable nullable annotations, except in .NET Standard 2.0 where they aren't supported-->
|
||||
<Nullable Condition="'$(TargetFramework)' != 'netstandard2.0'">enable</Nullable>
|
||||
|
@ -20,91 +29,28 @@
|
|||
<!--
|
||||
suppress warnings that don't apply, so it's easier to spot actual issues.
|
||||
|
||||
warning | builds | summary | rationale
|
||||
┄┄┄┄┄┄┄ | ┄┄┄┄┄┄┄ | ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ | ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
|
||||
CS0436 | all | local type conflicts with imported type | SMAPI needs to use certain low-level code during very early compatibility checks, before it's safe to load any other DLLs.
|
||||
CA1416 | all | platform code available on all platforms | Compiler doesn't recognize the #if constants used by SMAPI.
|
||||
CS0809 | all | obsolete overload for non-onsolete member | This is deliberate to signal to mods that certain APIs are only implemented for the game and shouldn't be called by mods.
|
||||
NU1701 | all | NuGet package targets older .NET version | All such packages are carefully tested to make sure they do work.
|
||||
warning | builds | summary | rationale
|
||||
┄┄┄┄┄┄┄ | ┄┄┄┄┄┄┄┄┄┄ | ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ | ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
|
||||
CS0436 | all | local type conflicts with imported type | SMAPI needs to use certain low-level code during very early compatibility checks, before it's safe to load any other DLLs.
|
||||
CS0612 | deprecated | member is obsolete | internal references to deprecated code when deprecated code is enabled.
|
||||
CS0618 | deprecated | member is obsolete (with message) | internal references to deprecated code when deprecated code is enabled.
|
||||
CA1416 | all | platform code available on all platforms | Compiler doesn't recognize the #if constants used by SMAPI.
|
||||
CS0809 | all | obsolete overload for non-obsolete member | This is deliberate to signal to mods that certain APIs are only implemented for the game and shouldn't be called by mods.
|
||||
NU1701 | all | NuGet package targets older .NET version | All such packages are carefully tested to make sure they do work.
|
||||
-->
|
||||
<NoWarn>$(NoWarn);CS0436;CA1416;CS0809;NU1701</NoWarn>
|
||||
<NoWarn Condition="$(DefineConstants.Contains(SMAPI_DEPRECATED))">$(NoWarn);CS0612;CS0618</NoWarn>
|
||||
<NoWarn>$(NoWarn);CS0436;CA1416;CS0809;NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--find game folder-->
|
||||
<Import Project="find-game-folder.targets" />
|
||||
|
||||
<!-- if game path is invalid, show one user-friendly error instead of a slew of reference errors -->
|
||||
<Target Name="ValidateInstallPath" AfterTargets="BeforeBuild">
|
||||
<!-- if game path is invalid, show one user-friendly error instead of a slew of reference errors -->
|
||||
<Error Condition="!Exists('$(GamePath)')" Text="Failed to find the game install path automatically. You can specify where to find it; see https://smapi.io/package/custom-game-path." />
|
||||
</Target>
|
||||
|
||||
<!-- copy files into game directory and enable debugging -->
|
||||
<Target Name="CopySmapiFiles" AfterTargets="AfterBuild" Condition="'$(CopyToGameFolder)' == 'true'">
|
||||
<CallTarget Targets="CopySMAPI;CopyDefaultMods" />
|
||||
</Target>
|
||||
<Target Name="CopySMAPI" Condition="'$(MSBuildProjectName)' == 'SMAPI'">
|
||||
<ItemGroup>
|
||||
<TranslationFiles Include="$(TargetDir)\i18n\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- SMAPI -->
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFolder="$(GamePath)" Condition="$(OS) == 'Windows_NT'" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName)" DestinationFolder="$(GamePath)" Condition="$(OS) != 'Windows_NT'" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)" />
|
||||
<Copy SourceFiles="$(TargetDir)\SMAPI.config.json" DestinationFiles="$(GamePath)\smapi-internal\config.json" />
|
||||
<Copy SourceFiles="$(TargetDir)\SMAPI.metadata.json" DestinationFiles="$(GamePath)\smapi-internal\metadata.json" />
|
||||
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\TMXTile.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Pintail.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\smapi-internal\i18n" />
|
||||
|
||||
<!-- Harmony + dependencies -->
|
||||
<Copy SourceFiles="$(TargetDir)\0Harmony.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\0Harmony.xml" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.Mdb.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.Pdb.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\MonoMod.Common.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
|
||||
<!-- .NET dependencies -->
|
||||
<Copy SourceFiles="$(TargetDir)\System.Management.dll" DestinationFolder="$(GamePath)\smapi-internal" Condition="$(OS) == 'Windows_NT'" />
|
||||
|
||||
<!-- Legacy .NET dependencies (remove in SMAPI 4.0.0) -->
|
||||
<Copy SourceFiles="$(TargetDir)\System.Configuration.ConfigurationManager.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\System.Runtime.Caching.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\System.Security.Permissions.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyDefaultMods" Condition="'$(MSBuildProjectName)' == 'SMAPI.Mods.ConsoleCommands' OR '$(MSBuildProjectName)' == 'SMAPI.Mods.ErrorHandler' OR '$(MSBuildProjectName)' == 'SMAPI.Mods.SaveBackup'">
|
||||
<ItemGroup>
|
||||
<TranslationFiles Include="$(TargetDir)\i18n\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" Condition="Exists('$(TargetDir)\$(TargetName).pdb')" />
|
||||
<Copy SourceFiles="$(TargetDir)\manifest.json" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" />
|
||||
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)\i18n" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit'" 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)' == 'SMAPI.Toolkit.CoreInterfaces'" 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>
|
||||
|
||||
<!-- common build settings -->
|
||||
<PropertyGroup>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<!--deploy local files-->
|
||||
<Import Project="deploy-local-smapi.targets" Condition="'$(CopyToGameFolder)' == 'true'" />
|
||||
|
||||
<!-- launch SMAPI through Visual Studio -->
|
||||
<PropertyGroup Condition="'$(MSBuildProjectName)' == 'SMAPI'">
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<!--
|
||||
|
||||
This MSBuild file copies SMAPI and the bundled mods into the local Stardew Valley folder on build
|
||||
to simplify testing. This just avoids needing to run the SMAPI installer each time.
|
||||
|
||||
This assumes `find-game-folder.targets` has already been imported and validated.
|
||||
|
||||
-->
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Target Name="CopySmapiFiles" AfterTargets="AfterBuild">
|
||||
<CallTarget Targets="CopySMAPI;CopyDefaultMods" />
|
||||
</Target>
|
||||
<Target Name="CopySMAPI" Condition="'$(MSBuildProjectName)' == 'SMAPI'">
|
||||
<!-- SMAPI -->
|
||||
<ItemGroup>
|
||||
<TranslationFiles Include="$(TargetDir)\i18n\*.json" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFolder="$(GamePath)" Condition="$(OS) == 'Windows_NT'" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName)" DestinationFolder="$(GamePath)" Condition="$(OS) != 'Windows_NT'" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)" />
|
||||
<Copy SourceFiles="$(TargetDir)\SMAPI.config.json" DestinationFiles="$(GamePath)\smapi-internal\config.json" />
|
||||
<Copy SourceFiles="$(TargetDir)\SMAPI.metadata.json" DestinationFiles="$(GamePath)\smapi-internal\metadata.json" />
|
||||
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\TMXTile.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Pintail.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\smapi-internal\i18n" />
|
||||
|
||||
<!-- Harmony + dependencies -->
|
||||
<Copy SourceFiles="$(TargetDir)\0Harmony.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\0Harmony.xml" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.Mdb.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.Pdb.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\MonoMod.Common.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
|
||||
<!-- FluentHttpClient + dependencies -->
|
||||
<Copy SourceFiles="$(TargetDir)\Pathoschild.Http.Client.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\System.Net.Http.Formatting.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
|
||||
<!-- .NET dependencies -->
|
||||
<Copy SourceFiles="$(TargetDir)\System.Management.dll" DestinationFolder="$(GamePath)\smapi-internal" Condition="$(OS) == 'Windows_NT'" />
|
||||
|
||||
<!-- Legacy .NET dependencies (remove in SMAPI 4.0.0) -->
|
||||
<Copy SourceFiles="$(TargetDir)\System.Configuration.ConfigurationManager.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\System.Runtime.Caching.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\System.Security.Permissions.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
</Target>
|
||||
|
||||
<!-- .NET metadata files -->
|
||||
<Target Name="CopyNetMetadata" Condition="'$(MSBuildProjectName)' == 'SMAPI.Installer'" AfterTargets="PostBuildEvent">
|
||||
<Copy SourceFiles="$(TargetDir)\assets\runtimeconfig.json" DestinationFiles="$(GamePath)\StardewModdingAPI.runtimeconfig.json" />
|
||||
<Copy SourceFiles="$(TargetDir)\assets\windows-exe-config.xml" DestinationFiles="$(GamePath)\StardewModdingAPI.exe.config" Condition="$(OS) == 'Windows_NT'" />
|
||||
<Copy SourceFiles="$(GamePath)\Stardew Valley.deps.json" DestinationFiles="$(GamePath)\StardewModdingAPI.deps.json" Condition="!Exists('$(GamePath)\StardewModdingAPI.deps.json')" />
|
||||
</Target>
|
||||
|
||||
<!-- bundled mods -->
|
||||
<Target Name="CopyDefaultMods" Condition="'$(MSBuildProjectName)' == 'SMAPI.Mods.ConsoleCommands' OR '$(MSBuildProjectName)' == 'SMAPI.Mods.ErrorHandler' OR '$(MSBuildProjectName)' == 'SMAPI.Mods.SaveBackup'">
|
||||
<ItemGroup>
|
||||
<TranslationFiles Include="$(TargetDir)\i18n\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" Condition="Exists('$(TargetDir)\$(TargetName).pdb')" />
|
||||
<Copy SourceFiles="$(TargetDir)\manifest.json" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" />
|
||||
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)\i18n" />
|
||||
</Target>
|
||||
|
||||
<!-- toolkit -->
|
||||
<Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit'" 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)' == 'SMAPI.Toolkit.CoreInterfaces'" 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>
|
||||
</Project>
|
|
@ -1,3 +1,9 @@
|
|||
<!--
|
||||
|
||||
This MSBuild file detects the Stardew Valley install path if possible, and sets the 'GamePath'
|
||||
property.
|
||||
|
||||
-->
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!-- import developer's custom path (if any) -->
|
||||
<Import Condition="$(OS) != 'Windows_NT' AND Exists('$(HOME)\stardewvalley.targets')" Project="$(HOME)\stardewvalley.targets" />
|
||||
|
|
|
@ -134,7 +134,7 @@ for folder in ${folders[@]}; do
|
|||
cp -r "$smapiBin/i18n" "$bundlePath/smapi-internal"
|
||||
|
||||
# bundle smapi-internal
|
||||
for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.pdb" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.pdb" "SMAPI.Toolkit.CoreInterfaces.xml"; do
|
||||
for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.pdb" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.pdb" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do
|
||||
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
|
||||
done
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ foreach ($folder in $folders) {
|
|||
cp -Recurse "$smapiBin/i18n" "$bundlePath/smapi-internal"
|
||||
|
||||
# bundle smapi-internal
|
||||
foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.pdb", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.pdb", "SMAPI.Toolkit.CoreInterfaces.xml")) {
|
||||
foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.pdb", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.pdb", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) {
|
||||
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,32 @@
|
|||
← [README](README.md)
|
||||
|
||||
# Release notes
|
||||
<!--
|
||||
## 4.0.0
|
||||
* The installer no longer supports updating from SMAPI 2.11.3 or earlier (released in 2019).
|
||||
_If needed, you can update to SMAPI 3.15.0 first and then install to the latest version._
|
||||
-->
|
||||
|
||||
## 3.15.1
|
||||
Released 06 July 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Added current version to update alerts (thanks to ishan!).
|
||||
* Fixed lag for some players since Stardew Valley 1.5.5.
|
||||
* Fixed `smapi-internal/config.user.json` overrides not applied after SMAPI 3.14.0.
|
||||
* Fixed PyTK not rescaling images correctly in some cases.
|
||||
_When PyTK 1.23.0 or earlier is installed, this will disable the main performance improvements in SMAPI 3.15.0._
|
||||
* Updated compatibility list.
|
||||
|
||||
* For mod authors:
|
||||
* The [FluentHttpClient package](https://github.com/Pathoschild/FluentHttpClient#readme) is now loaded by SMAPI.
|
||||
* Fixed `TRACE` logs not tracking reloaded map tilesheets as a propagated asset.
|
||||
|
||||
* For the web UI:
|
||||
* Added log parser suggested fix for missing/outdated Error Handler, and improved visual styles.
|
||||
* Updated the JSON validator/schema for Content Patcher 1.27.0.
|
||||
* Fixed the mod count in the log parser metadata.
|
||||
|
||||
## 3.15.0
|
||||
Released 17 June 2022 for Stardew Valley 1.5.6 or later. See [release highlights](https://www.patreon.com/posts/67877219).
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ SMAPI uses a small number of conditional compilation constants, which you can se
|
|||
flag | purpose
|
||||
---- | -------
|
||||
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled for Windows; if not set, the code assumes Linux/macOS. Set automatically in `common.targets`.
|
||||
`SMAPI_DEPRECATED` | Whether to include deprecated code in the build.
|
||||
|
||||
## Compile from source code
|
||||
### Main project
|
||||
|
|
|
@ -54,6 +54,7 @@ namespace StardewModdingApi.Installer
|
|||
yield return GetInstallPath("smapi-internal");
|
||||
yield return GetInstallPath("steam_appid.txt");
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
// obsolete
|
||||
yield return GetInstallPath("libgdiplus.dylib"); // before 3.13 (macOS only)
|
||||
yield return GetInstallPath(Path.Combine("Mods", ".cache")); // 1.3-1.4
|
||||
|
@ -82,6 +83,7 @@ namespace StardewModdingApi.Installer
|
|||
foreach (DirectoryInfo modDir in modsDir.EnumerateDirectories())
|
||||
yield return Path.Combine(modDir.FullName, ".cache"); // 1.4–1.7
|
||||
}
|
||||
#endif
|
||||
|
||||
yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs"); // remove old log files
|
||||
}
|
||||
|
@ -451,6 +453,7 @@ namespace StardewModdingApi.Installer
|
|||
}
|
||||
|
||||
// find target folder
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract -- avoid error if the Mods folder has invalid mods, since they're not validated yet
|
||||
ModFolder? targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase) == true);
|
||||
DirectoryInfo defaultTargetFolder = new(Path.Combine(paths.ModsPath, sourceMod.Directory.Name));
|
||||
DirectoryInfo targetFolder = targetMod?.Directory ?? defaultTargetFolder;
|
||||
|
@ -477,8 +480,10 @@ namespace StardewModdingApi.Installer
|
|||
File.WriteAllText(paths.ApiConfigPath, text);
|
||||
}
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
// remove obsolete appdata mods
|
||||
this.InteractivelyRemoveAppDataMods(paths.ModsDir, bundledModsDir);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Console.WriteLine();
|
||||
|
@ -805,6 +810,7 @@ namespace StardewModdingApi.Installer
|
|||
}
|
||||
}
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>Interactively move mods out of the app data directory.</summary>
|
||||
/// <param name="properModsDir">The directory which should contain all mods.</param>
|
||||
/// <param name="packagedModsDir">The installer directory containing packaged mods.</param>
|
||||
|
@ -887,6 +893,7 @@ namespace StardewModdingApi.Installer
|
|||
directory.Delete(recursive: true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>Get whether a file or folder should be copied from the installer files.</summary>
|
||||
/// <param name="entry">The file or folder info.</param>
|
||||
|
|
|
@ -227,8 +227,7 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
string fromPath = entry.Value.FullName;
|
||||
string toPath = Path.Combine(modFolderPath, entry.Key);
|
||||
|
||||
// ReSharper disable once AssignNullToNotNullAttribute -- not applicable in this context
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(toPath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(toPath)!);
|
||||
|
||||
File.Copy(fromPath, toPath, overwrite: true);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework
|
|||
/// <summary>Get all spawnable items.</summary>
|
||||
/// <param name="itemTypes">The item types to fetch (or null for any type).</param>
|
||||
/// <param name="includeVariants">Whether to include flavored variants like "Sunflower Honey".</param>
|
||||
[SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "TryCreate invokes the lambda immediately.")]
|
||||
[SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = $"{nameof(ItemRepository.TryCreate)} invokes the lambda immediately.")]
|
||||
public IEnumerable<SearchableItem> GetAll(ItemType[]? itemTypes = null, bool includeVariants = true)
|
||||
{
|
||||
//
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Name": "Console Commands",
|
||||
"Author": "SMAPI",
|
||||
"Version": "3.15.0",
|
||||
"Version": "3.15.1",
|
||||
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
||||
"UniqueID": "SMAPI.ConsoleCommands",
|
||||
"EntryDll": "ConsoleCommands.dll",
|
||||
"MinimumApiVersion": "3.15.0"
|
||||
"MinimumApiVersion": "3.15.1"
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Name": "Error Handler",
|
||||
"Author": "SMAPI",
|
||||
"Version": "3.15.0",
|
||||
"Version": "3.15.1",
|
||||
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
|
||||
"UniqueID": "SMAPI.ErrorHandler",
|
||||
"EntryDll": "ErrorHandler.dll",
|
||||
"MinimumApiVersion": "3.15.0"
|
||||
"MinimumApiVersion": "3.15.1"
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Name": "Save Backup",
|
||||
"Author": "SMAPI",
|
||||
"Version": "3.15.0",
|
||||
"Version": "3.15.1",
|
||||
"Description": "Automatically backs up all your saves once per day into its folder.",
|
||||
"UniqueID": "SMAPI.SaveBackup",
|
||||
"EntryDll": "SaveBackup.dll",
|
||||
"MinimumApiVersion": "3.15.0"
|
||||
"MinimumApiVersion": "3.15.1"
|
||||
}
|
||||
|
|
|
@ -1,27 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Pathoschild.Http.Client;
|
||||
using StardewModdingAPI.Toolkit.Serialization;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||
{
|
||||
/// <summary>Provides methods for interacting with the SMAPI web API.</summary>
|
||||
public class WebApiClient
|
||||
public class WebApiClient : IDisposable
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The base URL for the web API.</summary>
|
||||
private readonly Uri BaseUrl;
|
||||
|
||||
/// <summary>The API version number.</summary>
|
||||
private readonly ISemanticVersion Version;
|
||||
|
||||
/// <summary>The JSON serializer settings to use.</summary>
|
||||
private readonly JsonSerializerSettings JsonSettings = new JsonHelper().JsonSettings;
|
||||
/// <summary>The underlying HTTP client.</summary>
|
||||
private readonly IClient Client;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -32,8 +29,11 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
/// <param name="version">The web API version.</param>
|
||||
public WebApiClient(string baseUrl, ISemanticVersion version)
|
||||
{
|
||||
this.BaseUrl = new Uri(baseUrl);
|
||||
this.Version = version;
|
||||
this.Client = new FluentClient(baseUrl)
|
||||
.SetUserAgent($"SMAPI/{version}");
|
||||
|
||||
this.Client.Formatters.JsonFormatter.SerializerSettings = JsonHelper.CreateDefaultSettings();
|
||||
}
|
||||
|
||||
/// <summary>Get metadata about a set of mods from the web API.</summary>
|
||||
|
@ -42,36 +42,22 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
/// <param name="gameVersion">The Stardew Valley version installed by the player.</param>
|
||||
/// <param name="platform">The OS on which the player plays.</param>
|
||||
/// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
|
||||
public IDictionary<string, ModEntryModel> GetModInfo(ModSearchEntryModel[] mods, ISemanticVersion apiVersion, ISemanticVersion gameVersion, Platform platform, bool includeExtendedMetadata = false)
|
||||
public async Task<IDictionary<string, ModEntryModel>> GetModInfoAsync(ModSearchEntryModel[] mods, ISemanticVersion apiVersion, ISemanticVersion gameVersion, Platform platform, bool includeExtendedMetadata = false)
|
||||
{
|
||||
return this.Post<ModSearchModel, ModEntryModel[]>(
|
||||
$"v{this.Version}/mods",
|
||||
new ModSearchModel(mods, apiVersion, gameVersion, platform, includeExtendedMetadata)
|
||||
).ToDictionary(p => p.ID);
|
||||
ModEntryModel[] result = await this.Client
|
||||
.PostAsync(
|
||||
$"v{this.Version}/mods",
|
||||
new ModSearchModel(mods, apiVersion, gameVersion, platform, includeExtendedMetadata)
|
||||
)
|
||||
.As<ModEntryModel[]>();
|
||||
|
||||
return result.ToDictionary(p => p.ID);
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Fetch the response from the backend API.</summary>
|
||||
/// <typeparam name="TBody">The body content type.</typeparam>
|
||||
/// <typeparam name="TResult">The expected response type.</typeparam>
|
||||
/// <param name="url">The request URL, optionally excluding the base URL.</param>
|
||||
/// <param name="content">The body content to post.</param>
|
||||
private TResult Post<TBody, TResult>(string url, TBody content)
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
// note: avoid HttpClient for macOS compatibility
|
||||
using WebClient client = new();
|
||||
|
||||
Uri fullUrl = new(this.BaseUrl, url);
|
||||
string data = JsonConvert.SerializeObject(content);
|
||||
|
||||
client.Headers["Content-Type"] = "application/json";
|
||||
client.Headers["User-Agent"] = $"SMAPI/{this.Version}";
|
||||
string response = client.UploadString(fullUrl, data);
|
||||
return JsonConvert.DeserializeObject<TResult>(response, this.JsonSettings)
|
||||
?? throw new InvalidOperationException($"Could not parse the response from POST {url}.");
|
||||
this.Client.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -283,8 +283,8 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
}
|
||||
|
||||
/// <summary>The response model for the MediaWiki parse API.</summary>
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")]
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local", Justification = "Used via JSON deserialization.")]
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used via JSON deserialization.")]
|
||||
private class ResponseModel
|
||||
{
|
||||
/*********
|
||||
|
@ -306,9 +306,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
}
|
||||
|
||||
/// <summary>The inner response model for the MediaWiki parse API.</summary>
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")]
|
||||
[SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")]
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local", Justification = "Used via JSON deserialization.")]
|
||||
[SuppressMessage("ReSharper", "CollectionNeverUpdated.Local", Justification = "Used via JSON deserialization.")]
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used via JSON deserialization.")]
|
||||
private class ResponseParseModel
|
||||
{
|
||||
/*********
|
||||
|
|
|
@ -21,7 +21,8 @@ namespace StardewModdingAPI.Toolkit.Framework
|
|||
/// <summary>Get the OS name from the system uname command.</summary>
|
||||
/// <param name="buffer">The buffer to fill with the resulting string.</param>
|
||||
[DllImport("libc")]
|
||||
static extern int uname(IntPtr buffer);
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "This is the actual external command name.")]
|
||||
private static extern int uname(IntPtr buffer);
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -51,7 +52,6 @@ namespace StardewModdingAPI.Toolkit.Framework
|
|||
|
||||
/// <summary>Get the human-readable OS name and version.</summary>
|
||||
/// <param name="platform">The current platform.</param>
|
||||
[SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")]
|
||||
public static string GetFriendlyPlatformName(string platform)
|
||||
{
|
||||
#if SMAPI_FOR_WINDOWS
|
||||
|
@ -65,7 +65,10 @@ namespace StardewModdingAPI.Toolkit.Framework
|
|||
|
||||
return result ?? "Windows";
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
// fallback to default behavior
|
||||
}
|
||||
#endif
|
||||
|
||||
string name = Environment.OSVersion.ToString();
|
||||
|
|
|
@ -18,9 +18,11 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
|||
/// <summary>The mod patches the game in a way that may impact stability.</summary>
|
||||
PatchesGame = 4,
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/macOS.</summary>
|
||||
[Obsolete("This value is no longer used by SMAPI and will be removed in the upcoming SMAPI 4.0.0.")]
|
||||
UsesDynamic = 8,
|
||||
#endif
|
||||
|
||||
/// <summary>The mod references specialized 'unvalidated update tick' events which may impact stability.</summary>
|
||||
UsesUnvalidatedUpdateTick = 16,
|
||||
|
@ -37,6 +39,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
|||
/// <summary>Uses .NET APIs for shell or process access.</summary>
|
||||
AccessesShell = 256,
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>References the legacy <c>System.Configuration.ConfigurationManager</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary>
|
||||
DetectedLegacyConfigurationDll = 512,
|
||||
|
||||
|
@ -45,5 +48,6 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
|||
|
||||
/// <summary>References the legacy <c>System.Security.Permissions</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary>
|
||||
DetectedLegacyPermissionsDll = 2048
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ namespace StardewModdingAPI.Toolkit
|
|||
/// <param name="metadataPath">The file path for the SMAPI metadata file.</param>
|
||||
public ModDatabase GetModDatabase(string metadataPath)
|
||||
{
|
||||
MetadataModel metadata = JsonConvert.DeserializeObject<MetadataModel>(File.ReadAllText(metadataPath));
|
||||
MetadataModel metadata = JsonConvert.DeserializeObject<MetadataModel>(File.ReadAllText(metadataPath)) ?? new MetadataModel();
|
||||
ModDataRecord[] records = metadata.ModData.Select(pair => new ModDataRecord(pair.Key, pair.Value)).ToArray();
|
||||
return new ModDatabase(records, this.GetUpdateUrl);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.0" />
|
||||
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.1" />
|
||||
<PackageReference Include="System.Management" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -48,7 +48,12 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
|
|||
return this.ReadObject(JObject.Load(reader));
|
||||
|
||||
case JsonToken.String:
|
||||
return this.ReadString(JToken.Load(reader).Value<string>(), path);
|
||||
{
|
||||
string? value = JToken.Load(reader).Value<string>();
|
||||
return value is not null
|
||||
? this.ReadString(value, path)
|
||||
: null;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new SParseException($"Can't parse {nameof(ISemanticVersion)} from {reader.TokenType} node (path: {reader.Path}).");
|
||||
|
|
|
@ -42,7 +42,12 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
|
|||
return this.ReadObject(JObject.Load(reader), path);
|
||||
|
||||
case JsonToken.String:
|
||||
return this.ReadString(JToken.Load(reader).Value<string>(), path);
|
||||
{
|
||||
string? value = JToken.Load(reader).Value<string>();
|
||||
return value is not null
|
||||
? this.ReadString(value, path)
|
||||
: null;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new SParseException($"Can't parse {typeof(T).Name} from {reader.TokenType} node (path: {reader.Path}).");
|
||||
|
|
|
@ -15,21 +15,27 @@ namespace StardewModdingAPI.Toolkit.Serialization
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The JSON settings to use when serializing and deserializing files.</summary>
|
||||
public JsonSerializerSettings JsonSettings { get; } = new()
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded
|
||||
Converters = new List<JsonConverter>
|
||||
{
|
||||
new SemanticVersionConverter(),
|
||||
new StringEnumConverter()
|
||||
}
|
||||
};
|
||||
public JsonSerializerSettings JsonSettings { get; } = JsonHelper.CreateDefaultSettings();
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Create an instance of the default JSON serializer settings.</summary>
|
||||
public static JsonSerializerSettings CreateDefaultSettings()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded
|
||||
Converters = new List<JsonConverter>
|
||||
{
|
||||
new SemanticVersionConverter(),
|
||||
new StringEnumConverter()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Read a JSON file.</summary>
|
||||
/// <typeparam name="TModel">The model type.</typeparam>
|
||||
/// <param name="fullPath">The absolute file path.</param>
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.ModDrop
|
|||
|
||||
/// <summary>Get update check info about a mod.</summary>
|
||||
/// <param name="id">The mod ID.</param>
|
||||
[SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The nullability is validated in this method.")]
|
||||
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The nullability is validated in this method.")]
|
||||
public async Task<IModPage?> GetModData(string id)
|
||||
{
|
||||
IModPage page = new GenericModPage(this.SiteKey, id);
|
||||
|
|
|
@ -77,8 +77,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
|
|||
};
|
||||
|
||||
// parse log messages
|
||||
LogModInfo smapiMod = new(name: "SMAPI", author: "Pathoschild", version: "", description: "", loaded: true);
|
||||
LogModInfo gameMod = new(name: "game", author: "", version: "", description: "", loaded: true);
|
||||
LogModInfo smapiMod = new(name: "SMAPI", author: "Pathoschild", version: "", description: "", loaded: true, isMod: false);
|
||||
LogModInfo gameMod = new(name: "game", author: "", version: "", description: "", loaded: true, isMod: false);
|
||||
IDictionary<string, List<LogModInfo>> mods = new Dictionary<string, List<LogModInfo>>();
|
||||
bool inModList = false;
|
||||
bool inContentPackList = false;
|
||||
|
@ -200,6 +200,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
|
|||
log.GameVersion = match.Groups["gameVersion"].Value;
|
||||
log.OperatingSystem = match.Groups["os"].Value;
|
||||
smapiMod.OverrideVersion(log.ApiVersion);
|
||||
|
||||
log.ApiVersionParsed = smapiMod.GetParsedVersion();
|
||||
}
|
||||
|
||||
// mod path line
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewModdingAPI.Toolkit;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.LogParsing.Models
|
||||
{
|
||||
/// <summary>Metadata about a mod or content pack in the log.</summary>
|
||||
public class LogModInfo
|
||||
{
|
||||
/*********
|
||||
** Private fields
|
||||
*********/
|
||||
/// <summary>The parsed mod version, if valid.</summary>
|
||||
private Lazy<ISemanticVersion?> ParsedVersionImpl;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
|
@ -39,9 +48,15 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
|
|||
[MemberNotNullWhen(true, nameof(LogModInfo.UpdateVersion), nameof(LogModInfo.UpdateLink))]
|
||||
public bool HasUpdate => this.UpdateVersion != null && this.Version != this.UpdateVersion;
|
||||
|
||||
/// <summary>Whether the mod is a content pack for another mod.</summary>
|
||||
/// <summary>Whether this is an actual mod (rather than a special entry for SMAPI or the game itself).</summary>
|
||||
public bool IsMod { get; }
|
||||
|
||||
/// <summary>Whether this is a C# code mod.</summary>
|
||||
public bool IsCodeMod { get; }
|
||||
|
||||
/// <summary>Whether this is a content pack for another mod.</summary>
|
||||
[MemberNotNullWhen(true, nameof(LogModInfo.ContentPackFor))]
|
||||
public bool IsContentPack => !string.IsNullOrWhiteSpace(this.ContentPackFor);
|
||||
public bool IsContentPack { get; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -57,17 +72,26 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
|
|||
/// <param name="contentPackFor">The name of the mod for which this is a content pack (if applicable).</param>
|
||||
/// <param name="errors">The number of errors logged by this mod.</param>
|
||||
/// <param name="loaded">Whether the mod was loaded into the game.</param>
|
||||
public LogModInfo(string name, string author, string version, string description, string? updateVersion = null, string? updateLink = null, string? contentPackFor = null, int errors = 0, bool loaded = true)
|
||||
/// <param name="isMod">Whether this is an actual mod (instead of a special entry for SMAPI or the game).</param>
|
||||
public LogModInfo(string name, string author, string version, string description, string? updateVersion = null, string? updateLink = null, string? contentPackFor = null, int errors = 0, bool loaded = true, bool isMod = true)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Author = author;
|
||||
this.Version = version;
|
||||
this.Description = description;
|
||||
this.UpdateVersion = updateVersion;
|
||||
this.UpdateLink = updateLink;
|
||||
this.ContentPackFor = contentPackFor;
|
||||
this.Errors = errors;
|
||||
this.Loaded = loaded;
|
||||
|
||||
if (isMod)
|
||||
{
|
||||
this.IsMod = true;
|
||||
this.IsContentPack = !string.IsNullOrWhiteSpace(this.ContentPackFor);
|
||||
this.IsCodeMod = !this.IsContentPack;
|
||||
}
|
||||
|
||||
this.OverrideVersion(version);
|
||||
}
|
||||
|
||||
/// <summary>Add an update alert for this mod.</summary>
|
||||
|
@ -81,9 +105,29 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
|
|||
|
||||
/// <summary>Override the version number, for cases like SMAPI itself where the version is only known later during parsing.</summary>
|
||||
/// <param name="version">The new mod version.</param>
|
||||
[MemberNotNull(nameof(LogModInfo.Version), nameof(LogModInfo.ParsedVersionImpl))]
|
||||
public void OverrideVersion(string version)
|
||||
{
|
||||
this.Version = version;
|
||||
this.ParsedVersionImpl = new Lazy<ISemanticVersion?>(this.ParseVersion);
|
||||
}
|
||||
|
||||
/// <summary>Get the semantic version for this mod, if it's valid.</summary>
|
||||
public ISemanticVersion? GetParsedVersion()
|
||||
{
|
||||
return this.ParsedVersionImpl.Value;
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Get the semantic version for this mod, if it's valid.</summary>
|
||||
public ISemanticVersion? ParseVersion()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(this.Version) && SemanticVersion.TryParse(this.Version, out ISemanticVersion? version)
|
||||
? version
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
|
|||
/// <summary>The SMAPI version.</summary>
|
||||
public string? ApiVersion { get; set; }
|
||||
|
||||
/// <summary>The parsed SMAPI version, if it's valid.</summary>
|
||||
public ISemanticVersion? ApiVersionParsed { get; set; }
|
||||
|
||||
/// <summary>The game version.</summary>
|
||||
public string? GameVersion { get; set; }
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.5" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||
<PackageReference Include="Pathoschild.FluentNexus" Version="1.0.5" />
|
||||
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.0" />
|
||||
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.1" />
|
||||
|
|
|
@ -199,7 +199,7 @@ namespace StardewModdingAPI.Web
|
|||
/// <param name="settings">The serializer settings to edit.</param>
|
||||
private void ConfigureJsonNet(JsonSerializerSettings settings)
|
||||
{
|
||||
foreach (JsonConverter converter in new JsonHelper().JsonSettings.Converters)
|
||||
foreach (JsonConverter converter in JsonHelper.CreateDefaultSettings().Converters)
|
||||
settings.Converters.Add(converter);
|
||||
|
||||
settings.Formatting = Formatting.Indented;
|
||||
|
|
|
@ -8,24 +8,29 @@
|
|||
@{
|
||||
ViewData["Title"] = "SMAPI log parser";
|
||||
|
||||
// get log info
|
||||
ParsedLog? log = Model!.ParsedLog;
|
||||
|
||||
IDictionary<string, LogModInfo[]> contentPacks = Model.GetContentPacksByMod();
|
||||
ISet<int> screenIds = new HashSet<int>(log?.Messages.Select(p => p.ScreenId) ?? Array.Empty<int>());
|
||||
|
||||
// detect suggested fixes
|
||||
LogModInfo[] outdatedMods = log?.Mods.Where(mod => mod.HasUpdate).ToArray() ?? Array.Empty<LogModInfo>();
|
||||
LogModInfo? errorHandler = log?.Mods.FirstOrDefault(p => p.IsCodeMod && p.Name == "Error Handler");
|
||||
bool hasOlderErrorHandler = errorHandler?.GetParsedVersion() is not null && log?.ApiVersionParsed is not null && log.ApiVersionParsed.IsNewerThan(errorHandler.GetParsedVersion());
|
||||
|
||||
// get filters
|
||||
IDictionary<string, bool> defaultFilters = Enum
|
||||
.GetValues<LogLevel>()
|
||||
.ToDictionary(level => level.ToString().ToLower(), level => level != LogLevel.Trace);
|
||||
|
||||
IDictionary<int, string> logLevels = Enum
|
||||
.GetValues<LogLevel>()
|
||||
.ToDictionary(level => (int)level, level => level.ToString().ToLower());
|
||||
|
||||
IDictionary<int, string> logSections = Enum
|
||||
.GetValues<LogSection>()
|
||||
.ToDictionary(section => (int)section, section => section.ToString());
|
||||
|
||||
// get form
|
||||
string curPageUrl = this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID }, absoluteUrl: true)!;
|
||||
|
||||
ISet<int> screenIds = new HashSet<int>(log?.Messages.Select(p => p.ScreenId) ?? Array.Empty<int>());
|
||||
}
|
||||
|
||||
@section Head {
|
||||
|
@ -34,7 +39,7 @@
|
|||
<meta name="robots" content="noindex" />
|
||||
}
|
||||
<link rel="stylesheet" href="~/Content/css/file-upload.css" />
|
||||
<link rel="stylesheet" href="~/Content/css/log-parser.css?r=20220409" />
|
||||
<link rel="stylesheet" href="~/Content/css/log-parser.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tabbyjs@12.0.3/dist/css/tabby-ui-vertical.min.css" />
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/tabbyjs@12.0.3" crossorigin="anonymous"></script>
|
||||
|
@ -69,7 +74,7 @@
|
|||
</text>
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
smapi.logParser(
|
||||
|
@ -158,7 +163,7 @@ else if (log?.IsValid == true)
|
|||
<div id="os-instructions">
|
||||
<div>
|
||||
<ul data-tabs>
|
||||
@foreach (Platform platform in new[] {Platform.Android, Platform.Linux, Platform.Mac, Platform.Windows})
|
||||
@foreach (Platform platform in new[] { Platform.Android, Platform.Linux, Platform.Mac, Platform.Windows })
|
||||
{
|
||||
@if (platform == Platform.Windows)
|
||||
{
|
||||
|
@ -237,55 +242,66 @@ else if (log?.IsValid == true)
|
|||
@if (log?.IsValid == true)
|
||||
{
|
||||
<div id="output">
|
||||
@if (log.Mods.Any(mod => mod.HasUpdate))
|
||||
@if (outdatedMods.Any() || errorHandler is null || hasOlderErrorHandler)
|
||||
{
|
||||
<h2>Suggested fixes</h2>
|
||||
<ul id="fix-list">
|
||||
<li>
|
||||
Consider updating these mods to fix problems:
|
||||
@if (errorHandler is null)
|
||||
{
|
||||
<li class="important">You don't have the <strong>Error Handler</strong> mod installed. This automatically prevents many game or mod errors. You can <a href="https://stardewvalleywiki.com/Modding:Player_Guide#Install_SMAPI">reinstall SMAPI</a> to re-add it.</li>
|
||||
}
|
||||
@if (hasOlderErrorHandler)
|
||||
{
|
||||
<li>Your <strong>Error Handler</strong> mod is older than SMAPI. You may be missing some game/mod error fixes. You can <a href="https://stardewvalleywiki.com/Modding:Player_Guide#Install_SMAPI">reinstall SMAPI</a> to update it.</li>
|
||||
}
|
||||
@if (outdatedMods.Any())
|
||||
{
|
||||
<li>
|
||||
Consider updating these mods to fix problems:
|
||||
|
||||
<table id="updates" class="table">
|
||||
@foreach (LogModInfo mod in log.Mods.Where(mod => (mod.HasUpdate && !mod.IsContentPack) || (contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList) && contentPackList.Any(pack => pack.HasUpdate))))
|
||||
{
|
||||
<tr class="mod-entry">
|
||||
<td>
|
||||
<strong class=@(!mod.HasUpdate ? "hidden" : "")>@mod.Name</strong>
|
||||
@if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList))
|
||||
{
|
||||
<div class="content-packs">
|
||||
@foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate))
|
||||
{
|
||||
<text>+ @contentPack.Name</text><br />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (mod.HasUpdate)
|
||||
{
|
||||
<a href="@mod.UpdateLink" target="_blank">
|
||||
@(mod.Version == null ? mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}")
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<text> </text>
|
||||
}
|
||||
<table id="updates" class="table">
|
||||
@foreach (LogModInfo mod in log.Mods.Where(mod => (mod.HasUpdate && !mod.IsContentPack) || (contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList) && contentPackList.Any(pack => pack.HasUpdate))))
|
||||
{
|
||||
<tr class="mod-entry">
|
||||
<td>
|
||||
<strong class=@(!mod.HasUpdate ? "hidden" : "")>@mod.Name</strong>
|
||||
@if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList))
|
||||
{
|
||||
<div class="content-packs">
|
||||
@foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate))
|
||||
{
|
||||
<text>+ @contentPack.Name</text><br />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (mod.HasUpdate)
|
||||
{
|
||||
<a href="@mod.UpdateLink" target="_blank">
|
||||
@(mod.Version == null ? mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}")
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<text> </text>
|
||||
}
|
||||
|
||||
@if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out contentPackList))
|
||||
{
|
||||
<div>
|
||||
@foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate))
|
||||
{
|
||||
<a href="@contentPack.UpdateLink" target="_blank">@contentPack.Version → @contentPack.UpdateVersion</a><br />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
</li>
|
||||
@if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out contentPackList))
|
||||
{
|
||||
<div>
|
||||
@foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate))
|
||||
{
|
||||
<a href="@contentPack.UpdateLink" target="_blank">@contentPack.Version → @contentPack.UpdateVersion</a><br />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
|
@ -293,7 +309,7 @@ else if (log?.IsValid == true)
|
|||
<table
|
||||
id="metadata"
|
||||
class="table"
|
||||
data-code-mods="@log.Mods.Count(p => !p.IsContentPack)"
|
||||
data-code-mods="@log.Mods.Count(p => p.IsCodeMod)"
|
||||
data-content-packs="@log.Mods.Count(p => p.IsContentPack)"
|
||||
data-os="@log.OperatingSystem"
|
||||
data-game-version="@log.GameVersion"
|
||||
|
@ -434,7 +450,7 @@ else if (log?.IsValid == true)
|
|||
<div>
|
||||
This website uses JavaScript to display a filterable table. To view this log, please enable JavaScript or <a href="@this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID, format = LogViewFormat.RawView })">view the raw log</a>.
|
||||
</div>
|
||||
<br/>
|
||||
<br />
|
||||
</noscript>
|
||||
|
||||
<log-table>
|
||||
|
|
|
@ -53,6 +53,36 @@ table caption {
|
|||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/*********
|
||||
** Suggested fixes
|
||||
*********/
|
||||
#fix-list {
|
||||
padding-left: 1em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
#fix-list li {
|
||||
padding: 0.5em;
|
||||
background: #FFC;
|
||||
border: 1px solid #880;
|
||||
border-radius: 5px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#fix-list li:not(:last-child) {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
#fix-list li.important {
|
||||
background: #FCC;
|
||||
border-color: #800;
|
||||
}
|
||||
|
||||
#fix-list li::before {
|
||||
content: "⚠ ";
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Log metadata & filters
|
||||
*********/
|
||||
|
@ -84,10 +114,6 @@ table caption {
|
|||
min-height: 1.3em;
|
||||
}
|
||||
|
||||
#fix-list {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
#updates {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
|
|
@ -107,12 +107,6 @@
|
|||
"Default | UpdateKey": "Nexus:1726"
|
||||
},
|
||||
|
||||
"Rubydew": {
|
||||
"ID": "bwdy.rubydew",
|
||||
"SuppressWarnings": "UsesDynamic", // mod explicitly loads DLLs for Linux/macOS compatibility
|
||||
"Default | UpdateKey": "Nexus:3656"
|
||||
},
|
||||
|
||||
"SpaceCore": {
|
||||
"ID": "spacechase0.SpaceCore",
|
||||
"Default | UpdateKey": "Nexus:1348"
|
||||
|
@ -172,8 +166,8 @@
|
|||
*********/
|
||||
"CFAutomate": {
|
||||
"ID": "Platonymous.CFAutomate",
|
||||
"~2.12.9 | Status": "AssumeBroken",
|
||||
"~2.12.9 | StatusReasonDetails": "causes runtime errors in newer versions of Automate"
|
||||
"~2.12.11 | Status": "AssumeBroken",
|
||||
"~2.12.11 | StatusReasonDetails": "causes runtime errors in newer versions of Automate"
|
||||
},
|
||||
"Dynamic Game Assets": {
|
||||
"ID": "spacechase0.DynamicGameAssets",
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
"title": "Format version",
|
||||
"description": "The format version. You should always use the latest version to enable the latest features, avoid obsolete behavior, and reduce load times.",
|
||||
"type": "string",
|
||||
"const": "1.26.0",
|
||||
"pattern": "^1\\.27\\.[0-9]+$",
|
||||
"@errorMessages": {
|
||||
"const": "Incorrect value '@value'. You should always use the latest format version (currently 1.26.0) to enable the latest features, avoid obsolete behavior, and reduce load times."
|
||||
"pattern": "Incorrect value '@value'. You should always use the latest format version (currently 1.27.0) to enable the latest features, avoid obsolete behavior, and reduce load times."
|
||||
}
|
||||
},
|
||||
"ConfigSchema": {
|
||||
|
|
|
@ -27,6 +27,7 @@ EndProject
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{09CF91E5-5BAB-4650-A200-E5EA9A633046}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\build\common.targets = ..\build\common.targets
|
||||
..\build\deploy-local-smapi.targets = ..\build\deploy-local-smapi.targets
|
||||
..\build\find-game-folder.targets = ..\build\find-game-folder.targets
|
||||
EndProjectSection
|
||||
EndProject
|
||||
|
|
|
@ -6,7 +6,9 @@ using System.Reflection;
|
|||
using Mono.Cecil;
|
||||
using StardewModdingAPI.Enums;
|
||||
using StardewModdingAPI.Framework;
|
||||
#if SMAPI_DEPRECATED
|
||||
using StardewModdingAPI.Framework.Deprecations;
|
||||
#endif
|
||||
using StardewModdingAPI.Framework.ModLoading;
|
||||
using StardewModdingAPI.Toolkit.Framework;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
@ -50,7 +52,7 @@ namespace StardewModdingAPI
|
|||
internal static int? LogScreenId { get; set; }
|
||||
|
||||
/// <summary>SMAPI's current raw semantic version.</summary>
|
||||
internal static string RawApiVersion = "3.15.0";
|
||||
internal static string RawApiVersion = "3.15.1";
|
||||
}
|
||||
|
||||
/// <summary>Contains SMAPI's constants and assumptions.</summary>
|
||||
|
@ -77,6 +79,7 @@ namespace StardewModdingAPI
|
|||
/// <summary>The game framework running the game.</summary>
|
||||
public static GameFramework GameFramework { get; } = EarlyConstants.GameFramework;
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>The path to the game folder.</summary>
|
||||
[Obsolete($"Use {nameof(Constants)}.{nameof(GamePath)} instead. This property will be removed in SMAPI 4.0.0.")]
|
||||
public static string ExecutionPath
|
||||
|
@ -93,6 +96,7 @@ namespace StardewModdingAPI
|
|||
return Constants.GamePath;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>The path to the game folder.</summary>
|
||||
public static string GamePath { get; } = EarlyConstants.GamePath;
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI.Framework
|
||||
{
|
||||
/// <summary>A thread-safe command queue optimized for infrequent changes.</summary>
|
||||
internal class CommandQueue
|
||||
{
|
||||
/********
|
||||
** Fields
|
||||
********/
|
||||
/// <summary>The underlying list of queued commands to parse and execute.</summary>
|
||||
private readonly List<string> RawCommandQueue = new();
|
||||
|
||||
|
||||
/********
|
||||
** Public methods
|
||||
********/
|
||||
/// <summary>Add a command to the queue.</summary>
|
||||
/// <param name="command">The command to add.</param>
|
||||
public void Add(string command)
|
||||
{
|
||||
lock (this.RawCommandQueue)
|
||||
this.RawCommandQueue.Add(command);
|
||||
}
|
||||
|
||||
/// <summary>Remove and return all queued commands, if any.</summary>
|
||||
/// <param name="queued">The commands that were dequeued, in the order they were originally queued.</param>
|
||||
/// <returns>Returns whether any values were dequeued.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentlySynchronizedField", Justification = "Deliberately check if it's empty before locking unnecessarily.")]
|
||||
public bool TryDequeue([NotNullWhen(true)] out string[]? queued)
|
||||
{
|
||||
if (this.RawCommandQueue.Count is 0)
|
||||
{
|
||||
queued = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (this.RawCommandQueue)
|
||||
{
|
||||
queued = this.RawCommandQueue.ToArray();
|
||||
this.RawCommandQueue.Clear();
|
||||
return queued.Length > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
#if SMAPI_DEPRECATED
|
||||
using StardewModdingAPI.Framework.Deprecations;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingAPI.Framework.Content
|
||||
{
|
||||
|
@ -29,6 +31,10 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <inheritdoc />
|
||||
public IAssetName NameWithoutLocale => this.NameWithoutLocaleImpl ??= this.Name.GetBaseAssetName();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type DataType { get; }
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <inheritdoc />
|
||||
[Obsolete($"Use {nameof(AssetInfo.Name)} or {nameof(AssetInfo.NameWithoutLocale)} instead. This property will be removed in SMAPI 4.0.0.")]
|
||||
public string AssetName
|
||||
|
@ -50,9 +56,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
return this.NameWithoutLocale.Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type DataType { get; }
|
||||
#endif
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -71,6 +75,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
this.GetNormalizedPath = getNormalizedPath;
|
||||
}
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <inheritdoc />
|
||||
[Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} or {nameof(AssetInfo.NameWithoutLocale)}.{nameof(IAssetName.IsEquivalentTo)} instead. This method will be removed in SMAPI 4.0.0.")]
|
||||
public bool AssetNameEquals(string path)
|
||||
|
@ -90,6 +95,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
|
||||
return this.NameWithoutLocale.IsEquivalentTo(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*********
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#if SMAPI_DEPRECATED
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using StardewModdingAPI.Internal;
|
||||
|
||||
#pragma warning disable CS0618 // obsolete asset interceptors deliberately supported here
|
||||
namespace StardewModdingAPI.Framework.Content
|
||||
{
|
||||
/// <summary>A wrapper for <see cref="IAssetEditor"/> and <see cref="IAssetLoader"/> for internal cache invalidation.</summary>
|
||||
|
@ -103,3 +103,4 @@ namespace StardewModdingAPI.Framework.Content
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -12,7 +12,9 @@ using StardewModdingAPI.Framework.Content;
|
|||
using StardewModdingAPI.Framework.ContentManagers;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Framework.Utilities;
|
||||
#if SMAPI_DEPRECATED
|
||||
using StardewModdingAPI.Internal;
|
||||
#endif
|
||||
using StardewModdingAPI.Metadata;
|
||||
using StardewModdingAPI.Toolkit.Serialization;
|
||||
using StardewModdingAPI.Toolkit.Utilities.PathLookups;
|
||||
|
@ -84,6 +86,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>The cached asset load/edit operations to apply, indexed by asset name.</summary>
|
||||
private readonly TickCacheDictionary<IAssetName, AssetOperationGroup?> AssetOperationsByKey = new();
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>A cache of asset operation groups created for legacy <see cref="IAssetLoader"/> implementations.</summary>
|
||||
[Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
|
||||
private readonly Dictionary<IAssetLoader, Dictionary<Type, AssetLoadOperation>> LegacyLoaderCache = new(ReferenceEqualityComparer.Instance);
|
||||
|
@ -91,6 +94,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>A cache of asset operation groups created for legacy <see cref="IAssetEditor"/> implementations.</summary>
|
||||
[Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
|
||||
private readonly Dictionary<IAssetEditor, Dictionary<Type, AssetEditOperation>> LegacyEditorCache = new(ReferenceEqualityComparer.Instance);
|
||||
#endif
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -102,6 +106,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>The current language as a constant.</summary>
|
||||
public LocalizedContentManager.LanguageCode Language => this.MainContentManager.Language;
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>Interceptors which provide the initial versions of matching assets.</summary>
|
||||
[Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
|
||||
public IList<ModLinked<IAssetLoader>> Loaders { get; } = new List<ModLinked<IAssetLoader>>();
|
||||
|
@ -109,6 +114,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>Interceptors which edit matching assets after they're loaded.</summary>
|
||||
[Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
|
||||
public IList<ModLinked<IAssetEditor>> Editors { get; } = new List<ModLinked<IAssetEditor>>();
|
||||
#endif
|
||||
|
||||
/// <summary>The absolute path to the <see cref="ContentManager.RootDirectory"/>.</summary>
|
||||
public string FullRootDirectory { get; }
|
||||
|
@ -498,15 +504,25 @@ namespace StardewModdingAPI.Framework
|
|||
return invalidatedAssets.Keys;
|
||||
}
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>Get the asset load and edit operations to apply to a given asset if it's (re)loaded now.</summary>
|
||||
/// <typeparam name="T">The asset type.</typeparam>
|
||||
/// <param name="info">The asset info to load or edit.</param>
|
||||
public AssetOperationGroup? GetAssetOperations<T>(IAssetInfo info)
|
||||
where T : notnull
|
||||
#else
|
||||
/// <summary>Get the asset load and edit operations to apply to a given asset if it's (re)loaded now.</summary>
|
||||
/// <param name="info">The asset info to load or edit.</param>
|
||||
public AssetOperationGroup? GetAssetOperations(IAssetInfo info)
|
||||
#endif
|
||||
{
|
||||
return this.AssetOperationsByKey.GetOrSet(
|
||||
info.Name,
|
||||
#if SMAPI_DEPRECATED
|
||||
() => this.GetAssetOperationsWithoutCache<T>(info)
|
||||
#else
|
||||
() => this.RequestAssetOperations(info)
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -629,6 +645,7 @@ namespace StardewModdingAPI.Framework
|
|||
return map;
|
||||
}
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>Get the asset load and edit operations to apply to a given asset if it's (re)loaded now, ignoring the <see cref="AssetOperationsByKey"/> cache.</summary>
|
||||
/// <typeparam name="T">The asset type.</typeparam>
|
||||
/// <param name="info">The asset info to load or edit.</param>
|
||||
|
@ -639,7 +656,6 @@ namespace StardewModdingAPI.Framework
|
|||
AssetOperationGroup? group = this.RequestAssetOperations(info);
|
||||
|
||||
// legacy load operations
|
||||
#pragma warning disable CS0612, CS0618 // deprecated code
|
||||
if (this.Editors.Count > 0 || this.Loaders.Count > 0)
|
||||
{
|
||||
IAssetInfo legacyInfo = this.GetLegacyAssetInfo(info);
|
||||
|
@ -738,7 +754,6 @@ namespace StardewModdingAPI.Framework
|
|||
);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0612, CS0618
|
||||
|
||||
return group;
|
||||
}
|
||||
|
@ -818,5 +833,6 @@ namespace StardewModdingAPI.Framework
|
|||
// else no change needed
|
||||
return asset;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,7 +111,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "Copied as-is from game code")]
|
||||
public sealed override string LoadBaseString(string path)
|
||||
{
|
||||
try
|
||||
|
@ -119,7 +118,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
// copied as-is from LocalizedContentManager.LoadBaseString
|
||||
// This is only changed to call this.Load instead of base.Load, to support mod assets
|
||||
this.ParseStringPath(path, out string assetName, out string key);
|
||||
Dictionary<string, string> strings = this.Load<Dictionary<string, string>>(assetName, LanguageCode.en);
|
||||
Dictionary<string, string>? strings = this.Load<Dictionary<string, string>?>(assetName, LanguageCode.en);
|
||||
return strings != null && strings.ContainsKey(key)
|
||||
? this.GetString(strings, key)
|
||||
: path;
|
||||
|
|
|
@ -76,7 +76,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
// custom asset from a loader
|
||||
string locale = this.GetLocale();
|
||||
IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName);
|
||||
AssetOperationGroup? operations = this.Coordinator.GetAssetOperations<T>(info);
|
||||
AssetOperationGroup? operations = this.Coordinator.GetAssetOperations
|
||||
#if SMAPI_DEPRECATED
|
||||
<T>
|
||||
#endif
|
||||
(info);
|
||||
if (operations?.LoadOperations.Count > 0)
|
||||
{
|
||||
if (!this.AssertMaxOneRequiredLoader(info, operations.LoadOperations, out string? error))
|
||||
|
@ -129,7 +133,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
data = this.AssetsBeingLoaded.Track(assetName.Name, () =>
|
||||
{
|
||||
IAssetInfo info = new AssetInfo(assetName.LocaleCode, assetName, typeof(T), this.AssertAndNormalizeAssetName);
|
||||
AssetOperationGroup? operations = this.Coordinator.GetAssetOperations<T>(info);
|
||||
AssetOperationGroup? operations = this.Coordinator.GetAssetOperations
|
||||
#if SMAPI_DEPRECATED
|
||||
<T>
|
||||
#endif
|
||||
(info);
|
||||
IAssetData asset =
|
||||
this.ApplyLoader<T>(info, operations?.LoadOperations)
|
||||
?? new AssetDataForObject(info, this.RawLoad<T>(assetName, useCache), this.AssertAndNormalizeAssetName, this.Reflection);
|
||||
|
@ -294,7 +302,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
? $"Multiple mods want to provide the '{info.Name}' asset: {string.Join(", ", loaderNames)}"
|
||||
: $"The '{loaderNames[0]}' mod wants to provide the '{info.Name}' asset multiple times";
|
||||
|
||||
error = $"{errorPhrase}. An asset can't be loaded multiple times, so SMAPI will use the default asset instead. Uninstall one of the mods to fix this. (Message for modders: you should avoid {nameof(AssetLoadPriority)}.{nameof(AssetLoadPriority.Exclusive)} and {nameof(IAssetLoader)} if possible to avoid conflicts.)";
|
||||
error = $"{errorPhrase}. An asset can't be loaded multiple times, so SMAPI will use the default asset instead. Uninstall one of the mods to fix this. (Message for modders: you should avoid {nameof(AssetLoadPriority)}.{nameof(AssetLoadPriority.Exclusive)}"
|
||||
#if SMAPI_DEPRECATED
|
||||
+ " and {nameof(IAssetLoader)}"
|
||||
#endif
|
||||
+ " if possible to avoid conflicts.)";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -349,6 +361,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
// handle mismatch
|
||||
if (loadedMap.TileSheets.Count <= vanillaSheet.Index || loadedMap.TileSheets[vanillaSheet.Index].Id != vanillaSheet.Id)
|
||||
{
|
||||
#if SMAPI_DEPRECATED
|
||||
// only show warning if not farm map
|
||||
// This is temporary: mods shouldn't do this for any vanilla map, but these are the ones we know will crash. Showing a warning for others instead gives modders time to update their mods, while still simplifying troubleshooting.
|
||||
bool isFarmMap = info.Name.IsEquivalentTo("Maps/Farm") || info.Name.IsEquivalentTo("Maps/Farm_Combat") || info.Name.IsEquivalentTo("Maps/Farm_Fishing") || info.Name.IsEquivalentTo("Maps/Farm_Foraging") || info.Name.IsEquivalentTo("Maps/Farm_FourCorners") || info.Name.IsEquivalentTo("Maps/Farm_Island") || info.Name.IsEquivalentTo("Maps/Farm_Mining");
|
||||
|
@ -361,7 +374,12 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
mod.LogAsMod($"SMAPI blocked a '{info.Name}' map load: {reason}", LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
mod.LogAsMod($"SMAPI found an issue with a '{info.Name}' map load: {reason}", LogLevel.Warn);
|
||||
#else
|
||||
mod.LogAsMod($"SMAPI found an issue with a '{info.Name}' map load: {this.GetOnBehalfOfLabel(loader.OnBehalfOf, parenthetical: false) ?? "mod"} reordered the original tilesheets, which often causes crashes.\nTechnical details for mod author: Expected order: {string.Join(", ", vanillaTilesheetRefs.Select(p => p.Id))}. See https://stardewvalleywiki.com/Modding:Maps#Tilesheet_order for help.", LogLevel.Error);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
|
@ -47,9 +46,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <summary>If a map tilesheet's image source has no file extensions, the file extensions to check for in the local mod folder.</summary>
|
||||
private static readonly string[] LocalTilesheetExtensions = { ".png", ".xnb" };
|
||||
|
||||
/// <summary>A lookup of image file paths to whether they have PyTK scaling information.</summary>
|
||||
private static readonly Dictionary<string, bool> IsPyTkScaled = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
|
@ -211,24 +207,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
{
|
||||
if (ModContentManager.EnablePyTkLegacyMode)
|
||||
{
|
||||
if (!ModContentManager.IsPyTkScaled.TryGetValue(file.FullName, out bool isScaled))
|
||||
{
|
||||
string? dirPath = file.DirectoryName;
|
||||
string fileName = $"{Path.GetFileNameWithoutExtension(file.Name)}.pytk.json";
|
||||
|
||||
string path = dirPath is not null
|
||||
? Path.Combine(dirPath, fileName)
|
||||
: fileName;
|
||||
|
||||
ModContentManager.IsPyTkScaled[file.FullName] = isScaled = File.Exists(path);
|
||||
}
|
||||
|
||||
asRawData = !isScaled;
|
||||
if (!asRawData)
|
||||
this.Monitor.LogOnce("Enabled compatibility mode for PyTK scaled textures. This won't cause any issues, but may impact performance.", LogLevel.Warn);
|
||||
// PyTK intercepts Texture2D file loads to rescale them (e.g. for HD portraits),
|
||||
// but doesn't support IRawTextureData loads yet. We can't just check if the
|
||||
// current file has a '.pytk.json' rescale file though, since PyTK may still
|
||||
// rescale it if the original asset or another edit gets rescaled.
|
||||
asRawData = false;
|
||||
this.Monitor.LogOnce("Enabled compatibility mode for PyTK 1.23.0 or earlier. This won't cause any issues, but may impact performance.", LogLevel.Warn);
|
||||
}
|
||||
else
|
||||
asRawData = true;
|
||||
}
|
||||
|
||||
// load
|
||||
|
|
|
@ -96,6 +96,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
}
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <inheritdoc />
|
||||
[Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.Load)} instead. This method will be removed in SMAPI 4.0.0.")]
|
||||
public T LoadAsset<T>(string key)
|
||||
|
@ -110,6 +111,7 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
return this.ModContent.GetInternalAssetName(key).Name;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*********
|
||||
|
|
|
@ -400,7 +400,7 @@ namespace StardewModdingAPI.Framework.Logging
|
|||
/// <param name="mods">The loaded mods.</param>
|
||||
/// <param name="skippedMods">The mods which could not be loaded.</param>
|
||||
/// <param name="logParanoidWarnings">Whether to log issues for mods which directly use potentially sensitive .NET APIs like file or shell access.</param>
|
||||
[SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "Manifests aren't guaranteed non-null at this point in the loading process.")]
|
||||
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "Manifests aren't guaranteed non-null at this point in the loading process.")]
|
||||
private void LogModWarnings(IEnumerable<IModMetadata> mods, IModMetadata[] skippedMods, bool logParanoidWarnings)
|
||||
{
|
||||
// get mods with warnings
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
#if SMAPI_DEPRECATED
|
||||
using StardewModdingAPI.Framework.Deprecations;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingAPI.Framework.ModHelpers
|
||||
{
|
||||
|
@ -32,6 +34,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
return this;
|
||||
}
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use mod-provided APIs to integrate with mods instead. This method will be removed in SMAPI 4.0.0.")]
|
||||
public bool Trigger(string name, string[] arguments)
|
||||
|
@ -45,5 +48,6 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
|
||||
return this.CommandManager.Trigger(name, arguments);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#if SMAPI_DEPRECATED
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
@ -249,3 +250,4 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using StardewModdingAPI.Events;
|
||||
#if SMAPI_DEPRECATED
|
||||
using StardewModdingAPI.Framework.Deprecations;
|
||||
#endif
|
||||
using StardewModdingAPI.Framework.Input;
|
||||
|
||||
namespace StardewModdingAPI.Framework.ModHelpers
|
||||
|
@ -9,12 +11,14 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
/// <summary>Provides simplified APIs for writing mods.</summary>
|
||||
internal class ModHelper : BaseHelper, IModHelper, IDisposable
|
||||
{
|
||||
#if SMAPI_DEPRECATED
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The backing field for <see cref="Content"/>.</summary>
|
||||
[Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
|
||||
private readonly ContentHelper ContentImpl;
|
||||
#endif
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -26,6 +30,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
/// <inheritdoc />
|
||||
public IModEvents Events { get; }
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <inheritdoc />
|
||||
[Obsolete($"Use {nameof(IGameContentHelper)} or {nameof(IModContentHelper)} instead.")]
|
||||
public IContentHelper Content
|
||||
|
@ -42,6 +47,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
return this.ContentImpl;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc />
|
||||
public IGameContentHelper GameContent { get; }
|
||||
|
@ -96,9 +102,9 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
/// <exception cref="InvalidOperationException">The <paramref name="modDirectory"/> path does not exist on disk.</exception>
|
||||
public ModHelper(
|
||||
IModMetadata mod, string modDirectory, Func<SInputState> currentInputState, IModEvents events,
|
||||
#pragma warning disable CS0612 // deprecated code
|
||||
#if SMAPI_DEPRECATED
|
||||
ContentHelper contentHelper,
|
||||
#pragma warning restore CS0612
|
||||
#endif
|
||||
IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper
|
||||
)
|
||||
: base(mod)
|
||||
|
@ -111,9 +117,9 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
|
||||
// initialize
|
||||
this.DirectoryPath = modDirectory;
|
||||
#pragma warning disable CS0612 // deprecated code
|
||||
#if SMAPI_DEPRECATED
|
||||
this.ContentImpl = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper));
|
||||
#pragma warning restore CS0612
|
||||
#endif
|
||||
this.GameContent = gameContentHelper ?? throw new ArgumentNullException(nameof(gameContentHelper));
|
||||
this.ModContent = modContentHelper ?? throw new ArgumentNullException(nameof(modContentHelper));
|
||||
this.ContentPacks = contentPackHelper ?? throw new ArgumentNullException(nameof(contentPackHelper));
|
||||
|
@ -127,12 +133,14 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
this.Events = events;
|
||||
}
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>Get the underlying instance for <see cref="IContentHelper"/>.</summary>
|
||||
[Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
|
||||
public ContentHelper GetLegacyContentHelper()
|
||||
{
|
||||
return this.ContentImpl;
|
||||
}
|
||||
#endif
|
||||
|
||||
/****
|
||||
** Mod config file
|
||||
|
|
|
@ -163,6 +163,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
this.AssemblyDefinitionResolver.Add(assembly.Definition);
|
||||
}
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
// special case: clear legacy-DLL warnings if the mod bundles a copy
|
||||
if (mod.Warnings.HasFlag(ModWarning.DetectedLegacyCachingDll))
|
||||
{
|
||||
|
@ -185,6 +186,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
if (File.Exists(Path.Combine(mod.DirectoryPath, "System.Security.Permissions.dll")))
|
||||
mod.RemoveWarning(ModWarning.DetectedLegacyPermissionsDll);
|
||||
}
|
||||
#endif
|
||||
|
||||
// throw if incompatibilities detected
|
||||
if (!assumeCompatible && mod.Warnings.HasFlag(ModWarning.BrokenCodeLoaded))
|
||||
|
@ -452,6 +454,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
mod.SetWarning(ModWarning.AccessesShell);
|
||||
break;
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
case InstructionHandleResult.DetectedLegacyCachingDll:
|
||||
template = $"{logPrefix}Detected reference to System.Runtime.Caching.dll, which will be removed in SMAPI 4.0.0.";
|
||||
mod.SetWarning(ModWarning.DetectedLegacyCachingDll);
|
||||
|
@ -466,6 +469,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
template = $"{logPrefix}Detected reference to System.Security.Permissions.dll, which will be removed in SMAPI 4.0.0.";
|
||||
mod.SetWarning(ModWarning.DetectedLegacyPermissionsDll);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case InstructionHandleResult.None:
|
||||
break;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#if SMAPI_DEPRECATED
|
||||
using Mono.Cecil;
|
||||
using StardewModdingAPI.Framework.ModLoading.Framework;
|
||||
|
||||
|
@ -47,3 +48,4 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -32,6 +32,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
/// <summary>The instruction accesses the OS shell or processes directly.</summary>
|
||||
DetectedShellAccess,
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>The module references the legacy <c>System.Configuration.ConfigurationManager</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary>
|
||||
DetectedLegacyConfigurationDll,
|
||||
|
||||
|
@ -40,5 +41,6 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
|
||||
/// <summary>The module references the legacy <c>System.Security.Permissions</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary>
|
||||
DetectedLegacyPermissionsDll
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
|
||||
/// <inheritdoc />
|
||||
[MemberNotNullWhen(true, nameof(ModMetadata.ContentPack))]
|
||||
[SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The manifest may be null for broken mods while loading.")]
|
||||
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The manifest may be null for broken mods while loading.")]
|
||||
public bool IsContentPack => this.Manifest?.ContentPackFor != null;
|
||||
|
||||
/// <summary>The fake content packs created by this mod, if any.</summary>
|
||||
|
|
|
@ -60,8 +60,8 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
/// <param name="getUpdateUrl">Get an update URL for an update key (if valid).</param>
|
||||
/// <param name="getFileLookup">Get a file lookup for the given directory.</param>
|
||||
/// <param name="validateFilesExist">Whether to validate that files referenced in the manifest (like <see cref="IManifest.EntryDll"/>) exist on disk. This can be disabled to only validate the manifest itself.</param>
|
||||
[SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "Manifest values may be null before they're validated.")]
|
||||
[SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "Manifest values may be null before they're validated.")]
|
||||
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "Manifest values may be null before they're validated.")]
|
||||
[SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract", Justification = "Manifest values may be null before they're validated.")]
|
||||
public void ValidateManifests(IEnumerable<IModMetadata> mods, ISemanticVersion apiVersion, Func<string, string?> getUpdateUrl, Func<string, IFileLookup> getFileLookup, bool validateFilesExist = true)
|
||||
{
|
||||
mods = mods.ToArray();
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
return new Harmony(id);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "If the user passes a null original method, we let it fail in the underlying Harmony instance instead of handling it here.")]
|
||||
[SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract", Justification = "If the user passes a null original method, we let it fail in the underlying Harmony instance instead of handling it here.")]
|
||||
public DynamicMethod Patch(MethodBase original, HarmonyMethod? prefix = null, HarmonyMethod? postfix = null, HarmonyMethod? transpiler = null)
|
||||
{
|
||||
// In Harmony 1.x you could target a virtual method that's not implemented by the
|
||||
|
|
|
@ -10,7 +10,6 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
/// <summary>Provides <see cref="SpriteBatch"/> method signatures that can be injected into mod code for compatibility with mods written for XNA Framework before Stardew Valley 1.5.5.</summary>
|
||||
/// <remarks>This is public to support SMAPI rewriting and should not be referenced directly by mods.</remarks>
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")]
|
||||
[SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/macOS.")]
|
||||
[SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")]
|
||||
public class SpriteBatchFacade : SpriteBatch
|
||||
{
|
||||
|
|
|
@ -38,45 +38,49 @@ namespace StardewModdingAPI.Framework.Models
|
|||
/********
|
||||
** Accessors
|
||||
********/
|
||||
//
|
||||
// Note: properties must be writable to support merging config.user.json into it.
|
||||
//
|
||||
|
||||
/// <summary>Whether to enable development features.</summary>
|
||||
public bool DeveloperMode { get; private set; }
|
||||
public bool DeveloperMode { get; set; }
|
||||
|
||||
/// <summary>Whether to check for newer versions of SMAPI and mods on startup.</summary>
|
||||
public bool CheckForUpdates { get; }
|
||||
public bool CheckForUpdates { get; set; }
|
||||
|
||||
/// <summary>Whether to add a section to the 'mod issues' list for mods which which directly use potentially sensitive .NET APIs like file or shell access.</summary>
|
||||
public bool ParanoidWarnings { get; }
|
||||
public bool ParanoidWarnings { get; set; }
|
||||
|
||||
/// <summary>Whether to show beta versions as valid updates.</summary>
|
||||
public bool UseBetaChannel { get; }
|
||||
public bool UseBetaChannel { get; set; }
|
||||
|
||||
/// <summary>SMAPI's GitHub project name, used to perform update checks.</summary>
|
||||
public string GitHubProjectName { get; }
|
||||
public string GitHubProjectName { get; set; }
|
||||
|
||||
/// <summary>The base URL for SMAPI's web API, used to perform update checks.</summary>
|
||||
public string WebApiBaseUrl { get; }
|
||||
public string WebApiBaseUrl { get; set; }
|
||||
|
||||
/// <summary>The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</summary>
|
||||
/// <remarks>The possible values are "*" (everything is verbose), "SMAPI", (SMAPI itself), or mod IDs.</remarks>
|
||||
public HashSet<string> VerboseLogging { get; }
|
||||
public HashSet<string> VerboseLogging { get; set; }
|
||||
|
||||
/// <summary>Whether SMAPI should rewrite mods for compatibility.</summary>
|
||||
public bool RewriteMods { get; }
|
||||
public bool RewriteMods { get; set; }
|
||||
|
||||
/// <summary>Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU.</summary>
|
||||
public bool UseRawImageLoading { get; }
|
||||
public bool UseRawImageLoading { get; set; }
|
||||
|
||||
/// <summary>Whether to make SMAPI file APIs case-insensitive, even on Linux.</summary>
|
||||
public bool UseCaseInsensitivePaths { get; }
|
||||
public bool UseCaseInsensitivePaths { get; set; }
|
||||
|
||||
/// <summary>Whether SMAPI should log network traffic. Best combined with <see cref="VerboseLogging"/>, which includes network metadata.</summary>
|
||||
public bool LogNetworkTraffic { get; }
|
||||
public bool LogNetworkTraffic { get; set; }
|
||||
|
||||
/// <summary>The colors to use for text written to the SMAPI console.</summary>
|
||||
public ColorSchemeConfig ConsoleColors { get; }
|
||||
public ColorSchemeConfig ConsoleColors { get; set; }
|
||||
|
||||
/// <summary>The mod IDs SMAPI should ignore when performing update checks or validating update keys.</summary>
|
||||
public HashSet<string> SuppressUpdateChecks { get; }
|
||||
public HashSet<string> SuppressUpdateChecks { get; set; }
|
||||
|
||||
|
||||
/********
|
||||
|
@ -96,7 +100,7 @@ namespace StardewModdingAPI.Framework.Models
|
|||
/// <param name="logNetworkTraffic">Whether SMAPI should log network traffic.</param>
|
||||
/// <param name="consoleColors">The colors to use for text written to the SMAPI console.</param>
|
||||
/// <param name="suppressUpdateChecks">The mod IDs SMAPI should ignore when performing update checks or validating update keys.</param>
|
||||
public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? usePintail, bool? useRawImageLoading, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks)
|
||||
public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useRawImageLoading, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks)
|
||||
{
|
||||
this.DeveloperMode = developerMode;
|
||||
this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)];
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace StardewModdingAPI.Framework.Networking
|
|||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="mod">The mod metadata.</param>
|
||||
[SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The ID shouldn't be null, but we should handle it to avoid an error just in case.")]
|
||||
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The ID shouldn't be null, but we should handle it to avoid an error just in case.")]
|
||||
public MultiplayerPeerMod(RemoteContextModModel mod)
|
||||
{
|
||||
this.Name = mod.Name;
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace StardewModdingAPI.Framework.Rendering
|
|||
{
|
||||
/// <summary>A map display device which reimplements the default logic.</summary>
|
||||
/// <remarks>This is an exact copy of <see cref="XnaDisplayDevice"/>, except that private fields are protected and all methods are virtual.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Field naming deliberately matches " + nameof(XnaDisplayDevice) + " to minimize differences.")]
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = $"Field naming deliberately matches {nameof(XnaDisplayDevice)} to minimize differences.")]
|
||||
internal class SXnaDisplayDevice : IDisplayDevice
|
||||
{
|
||||
/*********
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -11,6 +10,7 @@ using System.Runtime.ExceptionServices;
|
|||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Xna.Framework;
|
||||
#if SMAPI_FOR_WINDOWS
|
||||
using Microsoft.Win32;
|
||||
|
@ -32,7 +32,9 @@ using StardewModdingAPI.Framework.Networking;
|
|||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Framework.Rendering;
|
||||
using StardewModdingAPI.Framework.Serialization;
|
||||
#if SMAPI_DEPRECATED
|
||||
using StardewModdingAPI.Framework.StateTracking.Comparers;
|
||||
#endif
|
||||
using StardewModdingAPI.Framework.StateTracking.Snapshots;
|
||||
using StardewModdingAPI.Framework.Utilities;
|
||||
using StardewModdingAPI.Internal;
|
||||
|
@ -65,8 +67,8 @@ namespace StardewModdingAPI.Framework
|
|||
/****
|
||||
** Low-level components
|
||||
****/
|
||||
/// <summary>Tracks whether the game should exit immediately and any pending initialization should be cancelled.</summary>
|
||||
private readonly CancellationTokenSource CancellationToken = new();
|
||||
/// <summary>Whether the game should exit immediately and any pending initialization should be cancelled.</summary>
|
||||
private bool IsExiting;
|
||||
|
||||
/// <summary>Manages the SMAPI console window and log file.</summary>
|
||||
private readonly LogManager LogManager;
|
||||
|
@ -139,12 +141,13 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>The maximum number of consecutive attempts SMAPI should make to recover from an update error.</summary>
|
||||
private readonly Countdown UpdateCrashTimer = new(60); // 60 ticks = roughly one second
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>Asset interceptors added or removed since the last tick.</summary>
|
||||
private readonly List<AssetInterceptorChange> ReloadAssetInterceptorsQueue = new();
|
||||
#endif
|
||||
|
||||
/// <summary>A list of queued commands to parse and execute.</summary>
|
||||
/// <remarks>This property must be thread-safe, since it's accessed from a separate console input thread.</remarks>
|
||||
private readonly ConcurrentQueue<string> RawCommandQueue = new();
|
||||
private readonly CommandQueue RawCommandQueue = new();
|
||||
|
||||
/// <summary>A list of commands to execute on each screen.</summary>
|
||||
private readonly PerScreen<List<QueuedCommand>> ScreenCommandQueue = new(() => new List<QueuedCommand>());
|
||||
|
@ -188,7 +191,7 @@ namespace StardewModdingAPI.Framework
|
|||
string logPath = this.GetLogPath();
|
||||
|
||||
// init basics
|
||||
this.Settings = JsonConvert.DeserializeObject<SConfig>(File.ReadAllText(Constants.ApiConfigPath));
|
||||
this.Settings = JsonConvert.DeserializeObject<SConfig>(File.ReadAllText(Constants.ApiConfigPath)) ?? throw new InvalidOperationException("The 'smapi-internal/config.json' file is missing or invalid. You can reinstall SMAPI to fix this.");
|
||||
if (File.Exists(Constants.ApiUserConfigPath))
|
||||
JsonConvert.PopulateObject(File.ReadAllText(Constants.ApiUserConfigPath), this.Settings);
|
||||
if (developerMode.HasValue)
|
||||
|
@ -270,16 +273,6 @@ namespace StardewModdingAPI.Framework
|
|||
new TitleMenuPatcher(this.OnLoadStageChanged)
|
||||
);
|
||||
|
||||
// add exit handler
|
||||
this.CancellationToken.Token.Register(() =>
|
||||
{
|
||||
if (this.IsGameRunning)
|
||||
{
|
||||
this.LogManager.WriteCrashLog();
|
||||
this.Game.Exit();
|
||||
}
|
||||
});
|
||||
|
||||
// set window titles
|
||||
this.UpdateWindowTitles();
|
||||
}
|
||||
|
@ -332,7 +325,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
|
||||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
[SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "May be disposed before SMAPI is fully initialized.")]
|
||||
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "May be disposed before SMAPI is fully initialized.")]
|
||||
public void Dispose()
|
||||
{
|
||||
// skip if already disposed
|
||||
|
@ -356,8 +349,8 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
// dispose core components
|
||||
this.IsGameRunning = false;
|
||||
this.IsExiting = true;
|
||||
this.ContentCore?.Dispose();
|
||||
this.CancellationToken.Dispose();
|
||||
this.Game?.Dispose();
|
||||
this.LogManager.Dispose(); // dispose last to allow for any last-second log messages
|
||||
|
||||
|
@ -372,7 +365,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>Initialize mods before the first game asset is loaded. At this point the core content managers are loaded (so mods can load their own assets), but the game is mostly uninitialized.</summary>
|
||||
private void InitializeBeforeFirstAssetLoaded()
|
||||
{
|
||||
if (this.CancellationToken.IsCancellationRequested)
|
||||
if (this.IsExiting)
|
||||
{
|
||||
this.Monitor.Log("SMAPI shutting down: aborting initialization.", LogLevel.Warn);
|
||||
return;
|
||||
|
@ -414,7 +407,7 @@ namespace StardewModdingAPI.Framework
|
|||
this.CheckForSoftwareConflicts();
|
||||
|
||||
// check for updates
|
||||
this.CheckForUpdatesAsync(mods);
|
||||
_ = this.CheckForUpdatesAsync(mods); // ignore task since the main thread doesn't need to wait for it
|
||||
}
|
||||
|
||||
// update window titles
|
||||
|
@ -433,8 +426,8 @@ namespace StardewModdingAPI.Framework
|
|||
() => this.LogManager.RunConsoleInputLoop(
|
||||
commandManager: this.CommandManager,
|
||||
reloadTranslations: this.ReloadTranslations,
|
||||
handleInput: input => this.RawCommandQueue.Enqueue(input),
|
||||
continueWhile: () => this.IsGameRunning && !this.CancellationToken.IsCancellationRequested
|
||||
handleInput: input => this.RawCommandQueue.Add(input),
|
||||
continueWhile: () => this.IsGameRunning && !this.IsExiting
|
||||
)
|
||||
).Start();
|
||||
}
|
||||
|
@ -477,12 +470,13 @@ namespace StardewModdingAPI.Framework
|
|||
** Special cases
|
||||
*********/
|
||||
// Abort if SMAPI is exiting.
|
||||
if (this.CancellationToken.IsCancellationRequested)
|
||||
if (this.IsExiting)
|
||||
{
|
||||
this.Monitor.Log("SMAPI shutting down: aborting update.");
|
||||
return;
|
||||
}
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/*********
|
||||
** Reload assets when interceptors are added/removed
|
||||
*********/
|
||||
|
@ -515,33 +509,37 @@ namespace StardewModdingAPI.Framework
|
|||
// reload affected assets
|
||||
this.ContentCore.InvalidateCache(asset => interceptors.Any(p => p.CanIntercept(asset)));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*********
|
||||
** Parse commands
|
||||
*********/
|
||||
while (this.RawCommandQueue.TryDequeue(out string? rawInput))
|
||||
if (this.RawCommandQueue.TryDequeue(out string[]? rawCommands))
|
||||
{
|
||||
// parse command
|
||||
string? name;
|
||||
string[]? args;
|
||||
Command? command;
|
||||
int screenId;
|
||||
try
|
||||
foreach (string rawInput in rawCommands)
|
||||
{
|
||||
if (!this.CommandManager.TryParse(rawInput, out name, out args, out command, out screenId))
|
||||
// parse command
|
||||
string? name;
|
||||
string[]? args;
|
||||
Command? command;
|
||||
int screenId;
|
||||
try
|
||||
{
|
||||
this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error);
|
||||
if (!this.CommandManager.TryParse(rawInput, out name, out args, out command, out screenId))
|
||||
{
|
||||
this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Monitor.Log($"Failed parsing that command:\n{ex.GetLogSummary()}", LogLevel.Error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Monitor.Log($"Failed parsing that command:\n{ex.GetLogSummary()}", LogLevel.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
// queue command for screen
|
||||
this.ScreenCommandQueue.GetValueForScreen(screenId).Add(new(command, name, args));
|
||||
// queue command for screen
|
||||
this.ScreenCommandQueue.GetValueForScreen(screenId).Add(new(command, name, args));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1287,7 +1285,7 @@ namespace StardewModdingAPI.Framework
|
|||
private LocalizedContentManager CreateContentManager(IServiceProvider serviceProvider, string rootDirectory)
|
||||
{
|
||||
// Game1._temporaryContent initializing from SGame constructor
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse -- this is the method that initializes it
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- this is the method that initializes it
|
||||
if (this.ContentCore == null)
|
||||
{
|
||||
this.ContentCore = new ContentCoordinator(
|
||||
|
@ -1453,16 +1451,15 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
/// <summary>Asynchronously check for a new version of SMAPI and any installed mods, and print alerts to the console if an update is available.</summary>
|
||||
/// <param name="mods">The mods to include in the update check (if eligible).</param>
|
||||
private void CheckForUpdatesAsync(IModMetadata[] mods)
|
||||
private async Task CheckForUpdatesAsync(IModMetadata[] mods)
|
||||
{
|
||||
if (!this.Settings.CheckForUpdates)
|
||||
return;
|
||||
|
||||
new Thread(() =>
|
||||
try
|
||||
{
|
||||
if (!this.Settings.CheckForUpdates)
|
||||
return;
|
||||
|
||||
// create client
|
||||
string url = this.Settings.WebApiBaseUrl;
|
||||
WebApiClient client = new(url, Constants.ApiVersion);
|
||||
using WebApiClient client = new(this.Settings.WebApiBaseUrl, Constants.ApiVersion);
|
||||
this.Monitor.Log("Checking for updates...");
|
||||
|
||||
// check SMAPI version
|
||||
|
@ -1472,9 +1469,15 @@ namespace StardewModdingAPI.Framework
|
|||
try
|
||||
{
|
||||
// fetch update check
|
||||
ModEntryModel response = client.GetModInfo(new[] { new ModSearchEntryModel("Pathoschild.SMAPI", Constants.ApiVersion, new[] { $"GitHub:{this.Settings.GitHubProjectName}" }) }, apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform).Single().Value;
|
||||
updateFound = response.SuggestedUpdate?.Version;
|
||||
updateUrl = response.SuggestedUpdate?.Url;
|
||||
IDictionary<string, ModEntryModel> response = await client.GetModInfoAsync(
|
||||
mods: new[] { new ModSearchEntryModel("Pathoschild.SMAPI", Constants.ApiVersion, new[] { $"GitHub:{this.Settings.GitHubProjectName}" }) },
|
||||
apiVersion: Constants.ApiVersion,
|
||||
gameVersion: Constants.GameVersion,
|
||||
platform: Constants.Platform
|
||||
);
|
||||
ModEntryModel updateInfo = response.Single().Value;
|
||||
updateFound = updateInfo.SuggestedUpdate?.Version;
|
||||
updateUrl = updateInfo.SuggestedUpdate?.Url;
|
||||
|
||||
// log message
|
||||
if (updateFound != null)
|
||||
|
@ -1483,10 +1486,10 @@ namespace StardewModdingAPI.Framework
|
|||
this.Monitor.Log(" SMAPI okay.");
|
||||
|
||||
// show errors
|
||||
if (response.Errors.Any())
|
||||
if (updateInfo.Errors.Any())
|
||||
{
|
||||
this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.", LogLevel.Warn);
|
||||
this.Monitor.Log($"Error: {string.Join("\n", response.Errors)}");
|
||||
this.Monitor.Log($"Error: {string.Join("\n", updateInfo.Errors)}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -1526,7 +1529,7 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
// fetch results
|
||||
this.Monitor.Log($" Checking for updates to {searchMods.Count} mods...");
|
||||
IDictionary<string, ModEntryModel> results = client.GetModInfo(searchMods.ToArray(), apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform);
|
||||
IDictionary<string, ModEntryModel> results = await client.GetModInfoAsync(searchMods.ToArray(), apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform);
|
||||
|
||||
// extract update alerts & errors
|
||||
var updates = new List<Tuple<IModMetadata, ISemanticVersion, string>>();
|
||||
|
@ -1562,7 +1565,7 @@ namespace StardewModdingAPI.Framework
|
|||
this.Monitor.Newline();
|
||||
this.Monitor.Log($"You can update {updates.Count} mod{(updates.Count != 1 ? "s" : "")}:", LogLevel.Alert);
|
||||
foreach ((IModMetadata mod, ISemanticVersion newVersion, string newUrl) in updates)
|
||||
this.Monitor.Log($" {mod.DisplayName} {newVersion}: {newUrl}", LogLevel.Alert);
|
||||
this.Monitor.Log($" {mod.DisplayName} {newVersion}: {newUrl} (you have {mod.Manifest.Version})", LogLevel.Alert);
|
||||
}
|
||||
else
|
||||
this.Monitor.Log(" All mods up to date.");
|
||||
|
@ -1576,7 +1579,15 @@ namespace StardewModdingAPI.Framework
|
|||
);
|
||||
}
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Monitor.Log("Couldn't check for updates. This won't affect your game, but you won't be notified of SMAPI or mod updates if this keeps happening.", LogLevel.Warn);
|
||||
this.Monitor.Log(ex is WebException && ex.InnerException == null
|
||||
? ex.Message
|
||||
: ex.ToString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Create a directory path if it doesn't exist.</summary>
|
||||
|
@ -1646,9 +1657,9 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
// initialize loaded non-content-pack mods
|
||||
this.Monitor.Log("Launching mods...", LogLevel.Debug);
|
||||
#pragma warning disable CS0612, CS0618 // deprecated code
|
||||
foreach (IModMetadata metadata in loadedMods)
|
||||
{
|
||||
#if SMAPI_DEPRECATED
|
||||
// add interceptors
|
||||
if (metadata.Mod?.Helper is ModHelper helper)
|
||||
{
|
||||
|
@ -1684,7 +1695,6 @@ namespace StardewModdingAPI.Framework
|
|||
content.ObservableAssetEditors.CollectionChanged += (_, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast<IAssetEditor>(), e.OldItems?.Cast<IAssetEditor>(), this.ContentCore.Editors);
|
||||
content.ObservableAssetLoaders.CollectionChanged += (_, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast<IAssetLoader>(), e.OldItems?.Cast<IAssetLoader>(), this.ContentCore.Loaders);
|
||||
}
|
||||
#pragma warning restore CS0612, CS0618
|
||||
|
||||
// log deprecation warnings
|
||||
if (metadata.HasWarnings(ModWarning.DetectedLegacyCachingDll, ModWarning.DetectedLegacyConfigurationDll, ModWarning.DetectedLegacyPermissionsDll))
|
||||
|
@ -1710,6 +1720,7 @@ namespace StardewModdingAPI.Framework
|
|||
);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// call entry method
|
||||
Context.HeuristicModsRunningCode.Push(metadata);
|
||||
|
@ -1750,6 +1761,7 @@ namespace StardewModdingAPI.Framework
|
|||
this.Monitor.Log("Mods loaded and ready!", LogLevel.Debug);
|
||||
}
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>Raised after a mod adds or removes asset interceptors.</summary>
|
||||
/// <typeparam name="T">The asset interceptor type (one of <see cref="IAssetEditor"/> or <see cref="IAssetLoader"/>).</typeparam>
|
||||
/// <param name="mod">The mod metadata.</param>
|
||||
|
@ -1772,6 +1784,7 @@ namespace StardewModdingAPI.Framework
|
|||
list.Remove(entry);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>Load a given mod.</summary>
|
||||
/// <param name="mod">The mod to load.</param>
|
||||
|
@ -1795,7 +1808,7 @@ namespace StardewModdingAPI.Framework
|
|||
string relativePath = mod.GetRelativePathWithRoot();
|
||||
if (mod.IsContentPack)
|
||||
this.Monitor.Log($" {mod.DisplayName} (from {relativePath}) [content pack]...");
|
||||
// ReSharper disable once ConstantConditionalAccessQualifier -- mod may be invalid at this point
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract -- mod may be invalid at this point
|
||||
else if (mod.Manifest?.EntryDll != null)
|
||||
this.Monitor.Log($" {mod.DisplayName} (from {relativePath}{Path.DirectorySeparatorChar}{mod.Manifest.EntryDll})..."); // don't use Path.Combine here, since EntryDLL might not be valid
|
||||
else
|
||||
|
@ -1915,9 +1928,9 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
IModEvents events = new ModEvents(mod, this.EventManager);
|
||||
ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager);
|
||||
#pragma warning disable CS0612 // deprecated code
|
||||
#if SMAPI_DEPRECATED
|
||||
ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, mod, monitor, this.Reflection);
|
||||
#pragma warning restore CS0612
|
||||
#endif
|
||||
GameContentHelper gameContentHelper = new(contentCore, mod, mod.DisplayName, monitor, this.Reflection);
|
||||
IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, mod, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), this.Reflection);
|
||||
IContentPackHelper contentPackHelper = new ContentPackHelper(
|
||||
|
@ -1930,7 +1943,11 @@ namespace StardewModdingAPI.Framework
|
|||
IModRegistry modRegistryHelper = new ModRegistryHelper(mod, this.ModRegistry, proxyFactory, monitor);
|
||||
IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(mod, this.Multiplayer);
|
||||
|
||||
modHelper = new ModHelper(mod, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, contentHelper, gameContentHelper, modContentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper);
|
||||
modHelper = new ModHelper(mod, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events,
|
||||
#if SMAPI_DEPRECATED
|
||||
contentHelper,
|
||||
#endif
|
||||
gameContentHelper, modContentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper);
|
||||
}
|
||||
|
||||
// init mod
|
||||
|
@ -2220,7 +2237,10 @@ namespace StardewModdingAPI.Framework
|
|||
private void ExitGameImmediately(string message)
|
||||
{
|
||||
this.Monitor.LogFatal(message);
|
||||
this.CancellationToken.Cancel();
|
||||
this.LogManager.WriteCrashLog();
|
||||
|
||||
this.IsExiting = true;
|
||||
this.Game.Exit();
|
||||
}
|
||||
|
||||
/// <summary>Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.</summary>
|
||||
|
|
|
@ -252,6 +252,7 @@ namespace StardewModdingAPI.Framework
|
|||
[SuppressMessage("ReSharper", "LocalVariableHidesMember", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "MergeIntoPattern", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "PossibleLossOfFraction", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "PossibleNullReferenceException", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "RedundantArgumentDefaultValue", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "RedundantBaseQualifier", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "RedundantCast", Justification = "copied from game code as-is")]
|
||||
|
@ -261,6 +262,8 @@ namespace StardewModdingAPI.Framework
|
|||
[SuppressMessage("ReSharper", "MergeIntoPattern", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("SMAPI.CommonErrors", "AvoidImplicitNetFieldCast", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("SMAPI.CommonErrors", "AvoidNetField", Justification = "copied from game code as-is")]
|
||||
|
||||
[SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "Deliberate to minimize chance of errors when copying event calls into new versions of this code.")]
|
||||
private void DrawImpl(GameTime gameTime, RenderTarget2D target_screen)
|
||||
{
|
||||
var events = this.Events;
|
||||
|
|
|
@ -49,7 +49,10 @@ namespace StardewModdingAPI.Framework.Serialization
|
|||
|
||||
case JsonToken.String:
|
||||
{
|
||||
string str = JToken.Load(reader).Value<string>();
|
||||
string? str = JToken.Load(reader).Value<string>();
|
||||
|
||||
if (str is null)
|
||||
return new Keybind(Array.Empty<SButton>());
|
||||
|
||||
if (objectType == typeof(Keybind))
|
||||
{
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
#if SMAPI_DEPRECATED
|
||||
using System;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>The game framework running the game.</summary>
|
||||
public enum GameFramework
|
||||
{
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>The XNA Framework, previously used on Windows.</summary>
|
||||
[Obsolete("Stardew Valley no longer uses XNA Framework on any supported platform. This value will be removed in SMAPI 4.0.0.")]
|
||||
Xna,
|
||||
#endif
|
||||
|
||||
/// <summary>The MonoGame framework.</summary>
|
||||
MonoGame
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#if SMAPI_DEPRECATED
|
||||
using System;
|
||||
using StardewModdingAPI.Events;
|
||||
|
||||
|
@ -19,3 +20,4 @@ namespace StardewModdingAPI
|
|||
void Edit<T>(IAssetData asset);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -8,25 +8,34 @@ namespace StardewModdingAPI
|
|||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>The content's locale code, if the content is localized.</summary>
|
||||
/// <remarks>LEGACY NOTE: when reading this field from an <see cref="IAssetLoader"/> or <see cref="IAssetEditor"/> implementation, for non-localized assets it will return the current game locale (or an empty string for English) instead of null.</remarks>
|
||||
#else
|
||||
/// <summary>The content's locale code, if the content is localized.</summary>
|
||||
#endif
|
||||
string? Locale { get; }
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>The asset name being read.</summary>
|
||||
/// <remarks>LEGACY NOTE: when reading this field from an <see cref="IAssetLoader"/> or <see cref="IAssetEditor"/> implementation, it's always equivalent to <see cref="NameWithoutLocale"/> for backwards compatibility.</remarks>
|
||||
#else
|
||||
/// <summary>The asset name being read.</summary>
|
||||
#endif
|
||||
public IAssetName Name { get; }
|
||||
|
||||
/// <summary>The <see cref="Name"/> with any locale codes stripped.</summary>
|
||||
/// <remarks>For example, if <see cref="Name"/> contains a locale like <c>Data/Bundles.fr-FR</c>, this will be the name without locale like <c>Data/Bundles</c>. If the name has no locale, this field is equivalent.</remarks>
|
||||
public IAssetName NameWithoutLocale { get; }
|
||||
|
||||
/// <summary>The content data type.</summary>
|
||||
Type DataType { get; }
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>The normalized asset name being read. The format may change between platforms; see <see cref="AssetNameEquals"/> to compare with a known path.</summary>
|
||||
[Obsolete($"Use {nameof(Name)} or {nameof(NameWithoutLocale)} instead. This property will be removed in SMAPI 4.0.0.")]
|
||||
string AssetName { get; }
|
||||
|
||||
/// <summary>The content data type.</summary>
|
||||
Type DataType { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -35,5 +44,6 @@ namespace StardewModdingAPI
|
|||
/// <param name="path">The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation').</param>
|
||||
[Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} or {nameof(NameWithoutLocale)}.{nameof(IAssetName.IsEquivalentTo)} instead. This method will be removed in SMAPI 4.0.0.")]
|
||||
bool AssetNameEquals(string path);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#if SMAPI_DEPRECATED
|
||||
using System;
|
||||
using StardewModdingAPI.Events;
|
||||
|
||||
|
@ -19,3 +20,4 @@ namespace StardewModdingAPI
|
|||
T Load<T>(IAssetInfo asset);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -17,11 +17,13 @@ namespace StardewModdingAPI
|
|||
/// <exception cref="ArgumentException">There's already a command with that name.</exception>
|
||||
ICommandHelper Add(string name, string documentation, Action<string, string[]> callback);
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>Trigger a command.</summary>
|
||||
/// <param name="name">The command name.</param>
|
||||
/// <param name="arguments">The command arguments.</param>
|
||||
/// <returns>Returns whether a matching command was triggered.</returns>
|
||||
[Obsolete("Use mod-provided APIs to integrate with mods instead. This method will be removed in SMAPI 4.0.0.")]
|
||||
bool Trigger(string name, string[] arguments);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#if SMAPI_DEPRECATED
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
@ -80,3 +81,4 @@ namespace StardewModdingAPI
|
|||
where T : notnull;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
#if SMAPI_DEPRECATED
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using xTile;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
|
@ -47,6 +49,7 @@ namespace StardewModdingAPI
|
|||
void WriteJsonFile<TModel>(string path, TModel data)
|
||||
where TModel : class;
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>Load content from the content pack folder (if not already cached), and return it. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary>
|
||||
/// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, <see cref="IRawTextureData"/>, and data structures; other types may be supported by the game's content pipeline.</typeparam>
|
||||
/// <param name="key">The relative file path within the content pack (case-insensitive).</param>
|
||||
|
@ -61,5 +64,6 @@ namespace StardewModdingAPI
|
|||
/// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception>
|
||||
[Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.GetInternalAssetName)} instead. This method will be removed in SMAPI 4.0.0.")]
|
||||
string GetActualAssetKey(string key);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#if SMAPI_DEPRECATED
|
||||
using System;
|
||||
#endif
|
||||
using StardewModdingAPI.Events;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
|
@ -25,9 +27,11 @@ namespace StardewModdingAPI
|
|||
/// <remarks>This API is intended for reading content assets from the mod files (like game data, images, etc); see also <see cref="Data"/> which is intended for persisting internal mod data.</remarks>
|
||||
IModContentHelper ModContent { get; }
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
/// <summary>An API for loading content assets.</summary>
|
||||
[Obsolete($"Use {nameof(IGameContentHelper)} or {nameof(IModContentHelper)} instead.")]
|
||||
IContentHelper Content { get; }
|
||||
#endif
|
||||
|
||||
/// <summary>An API for managing content packs.</summary>
|
||||
IContentPackHelper ContentPacks { get; }
|
||||
|
|
|
@ -164,6 +164,7 @@ namespace StardewModdingAPI.Metadata
|
|||
var content = this.MainContentManager;
|
||||
string key = assetName.BaseName;
|
||||
changedWarpRoutes = false;
|
||||
bool changed = false;
|
||||
|
||||
/****
|
||||
** Special case: current map tilesheet
|
||||
|
@ -175,7 +176,10 @@ namespace StardewModdingAPI.Metadata
|
|||
foreach (TileSheet tilesheet in Game1.currentLocation.map.TileSheets)
|
||||
{
|
||||
if (this.IsSameBaseName(assetName, tilesheet.ImageSource))
|
||||
{
|
||||
Game1.mapDisplayDevice.LoadTileSheet(tilesheet);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,8 +188,6 @@ namespace StardewModdingAPI.Metadata
|
|||
****/
|
||||
if (type == typeof(Map))
|
||||
{
|
||||
bool anyChanged = false;
|
||||
|
||||
if (!ignoreWorld)
|
||||
{
|
||||
foreach (LocationInfo info in this.GetLocationsWithInfo())
|
||||
|
@ -206,12 +208,12 @@ namespace StardewModdingAPI.Metadata
|
|||
var newWarps = GetWarpSet(location);
|
||||
|
||||
changedWarpRoutes = changedWarpRoutes || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p));
|
||||
anyChanged = true;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return anyChanged;
|
||||
return changed;
|
||||
}
|
||||
|
||||
/****
|
||||
|
@ -223,7 +225,7 @@ namespace StardewModdingAPI.Metadata
|
|||
** Animals
|
||||
****/
|
||||
case "animals/horse":
|
||||
return !ignoreWorld && this.UpdatePetOrHorseSprites<Horse>(assetName);
|
||||
return changed | (!ignoreWorld && this.UpdatePetOrHorseSprites<Horse>(assetName));
|
||||
|
||||
/****
|
||||
** Buildings
|
||||
|
@ -239,7 +241,7 @@ namespace StardewModdingAPI.Metadata
|
|||
Farm farm = Game1.getFarm();
|
||||
farm?.ApplyHousePaint();
|
||||
|
||||
return removedFromCache || farm != null;
|
||||
return changed | (removedFromCache || farm != null);
|
||||
}
|
||||
|
||||
/****
|
||||
|
@ -253,7 +255,7 @@ namespace StardewModdingAPI.Metadata
|
|||
case "characters/farmer/farmer_base_bald":
|
||||
case "characters/farmer/farmer_girl_base":
|
||||
case "characters/farmer/farmer_girl_base_bald":
|
||||
return !ignoreWorld && this.UpdatePlayerSprites(assetName);
|
||||
return changed | (!ignoreWorld && this.UpdatePlayerSprites(assetName));
|
||||
|
||||
case "characters/farmer/hairstyles": // Game1.LoadContent
|
||||
FarmerRenderer.hairStylesTexture = this.LoadTexture(key);
|
||||
|
@ -305,10 +307,10 @@ namespace StardewModdingAPI.Metadata
|
|||
return true;
|
||||
|
||||
case "data/farmanimals": // FarmAnimal constructor
|
||||
return !ignoreWorld && this.UpdateFarmAnimalData();
|
||||
return changed | (!ignoreWorld && this.UpdateFarmAnimalData());
|
||||
|
||||
case "data/hairdata": // Farmer.GetHairStyleMetadataFile
|
||||
return this.UpdateHairData();
|
||||
return changed | this.UpdateHairData();
|
||||
|
||||
case "data/movies": // MovieTheater.GetMovieData
|
||||
case "data/moviesreactions": // MovieTheater.GetMovieReactions
|
||||
|
@ -316,7 +318,7 @@ namespace StardewModdingAPI.Metadata
|
|||
return true;
|
||||
|
||||
case "data/npcdispositions": // NPC constructor
|
||||
return !ignoreWorld && this.UpdateNpcDispositions(content, assetName);
|
||||
return changed | (!ignoreWorld && this.UpdateNpcDispositions(content, assetName));
|
||||
|
||||
case "data/npcgifttastes": // Game1.LoadContent
|
||||
Game1.NPCGiftTastes = content.Load<Dictionary<string, string>>(key);
|
||||
|
@ -428,7 +430,7 @@ namespace StardewModdingAPI.Metadata
|
|||
return true;
|
||||
|
||||
case "loosesprites/suspensionbridge": // SuspensionBridge constructor
|
||||
return !ignoreWorld && this.UpdateSuspensionBridges(content, assetName);
|
||||
return changed | (!ignoreWorld && this.UpdateSuspensionBridges(content, assetName));
|
||||
|
||||
/****
|
||||
** Content\Maps
|
||||
|
@ -456,16 +458,16 @@ namespace StardewModdingAPI.Metadata
|
|||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return changed;
|
||||
|
||||
case "minigames/titlebuttons": // TitleMenu
|
||||
return this.UpdateTitleButtons(content, assetName);
|
||||
return changed | this.UpdateTitleButtons(content, assetName);
|
||||
|
||||
/****
|
||||
** Content\Strings
|
||||
****/
|
||||
case "strings/stringsfromcsfiles":
|
||||
return this.UpdateStringsFromCsFiles(content);
|
||||
return changed | this.UpdateStringsFromCsFiles(content);
|
||||
|
||||
/****
|
||||
** Content\TileSheets
|
||||
|
@ -490,7 +492,7 @@ namespace StardewModdingAPI.Metadata
|
|||
return true;
|
||||
|
||||
case "tilesheets/critters": // Critter constructor
|
||||
return !ignoreWorld && this.UpdateCritterTextures(assetName);
|
||||
return changed | (!ignoreWorld && this.UpdateCritterTextures(assetName));
|
||||
|
||||
case "tilesheets/crops": // Game1.LoadContent
|
||||
Game1.cropSpriteSheet = content.Load<Texture2D>(key);
|
||||
|
@ -559,27 +561,27 @@ namespace StardewModdingAPI.Metadata
|
|||
return true;
|
||||
|
||||
case "terrainfeatures/mushroom_tree": // from Tree
|
||||
return !ignoreWorld && this.UpdateTreeTextures(Tree.mushroomTree);
|
||||
return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.mushroomTree));
|
||||
|
||||
case "terrainfeatures/tree_palm": // from Tree
|
||||
return !ignoreWorld && this.UpdateTreeTextures(Tree.palmTree);
|
||||
return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.palmTree));
|
||||
|
||||
case "terrainfeatures/tree1_fall": // from Tree
|
||||
case "terrainfeatures/tree1_spring": // from Tree
|
||||
case "terrainfeatures/tree1_summer": // from Tree
|
||||
case "terrainfeatures/tree1_winter": // from Tree
|
||||
return !ignoreWorld && this.UpdateTreeTextures(Tree.bushyTree);
|
||||
return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.bushyTree));
|
||||
|
||||
case "terrainfeatures/tree2_fall": // from Tree
|
||||
case "terrainfeatures/tree2_spring": // from Tree
|
||||
case "terrainfeatures/tree2_summer": // from Tree
|
||||
case "terrainfeatures/tree2_winter": // from Tree
|
||||
return !ignoreWorld && this.UpdateTreeTextures(Tree.leafyTree);
|
||||
return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.leafyTree));
|
||||
|
||||
case "terrainfeatures/tree3_fall": // from Tree
|
||||
case "terrainfeatures/tree3_spring": // from Tree
|
||||
case "terrainfeatures/tree3_winter": // from Tree
|
||||
return !ignoreWorld && this.UpdateTreeTextures(Tree.pineTree);
|
||||
return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.pineTree));
|
||||
}
|
||||
|
||||
/****
|
||||
|
@ -588,25 +590,29 @@ namespace StardewModdingAPI.Metadata
|
|||
if (!ignoreWorld)
|
||||
{
|
||||
// dynamic textures
|
||||
if (assetName.StartsWith("animals/cat"))
|
||||
return this.UpdatePetOrHorseSprites<Cat>(assetName);
|
||||
if (assetName.StartsWith("animals/dog"))
|
||||
return this.UpdatePetOrHorseSprites<Dog>(assetName);
|
||||
if (assetName.IsDirectlyUnderPath("Animals"))
|
||||
return this.UpdateFarmAnimalSprites(assetName);
|
||||
{
|
||||
if (assetName.StartsWith("animals/cat"))
|
||||
return changed | this.UpdatePetOrHorseSprites<Cat>(assetName);
|
||||
|
||||
if (assetName.StartsWith("animals/dog"))
|
||||
return changed | this.UpdatePetOrHorseSprites<Dog>(assetName);
|
||||
|
||||
return changed | this.UpdateFarmAnimalSprites(assetName);
|
||||
}
|
||||
|
||||
if (assetName.IsDirectlyUnderPath("Buildings"))
|
||||
return this.UpdateBuildings(assetName);
|
||||
return changed | this.UpdateBuildings(assetName);
|
||||
|
||||
if (assetName.StartsWith("LooseSprites/Fence"))
|
||||
return this.UpdateFenceTextures(assetName);
|
||||
return changed | this.UpdateFenceTextures(assetName);
|
||||
|
||||
// dynamic data
|
||||
if (assetName.IsDirectlyUnderPath("Characters/Dialogue"))
|
||||
return this.UpdateNpcDialogue(assetName);
|
||||
return changed | this.UpdateNpcDialogue(assetName);
|
||||
|
||||
if (assetName.IsDirectlyUnderPath("Characters/schedules"))
|
||||
return this.UpdateNpcSchedules(assetName);
|
||||
return changed | this.UpdateNpcSchedules(assetName);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -960,7 +966,7 @@ namespace StardewModdingAPI.Metadata
|
|||
{
|
||||
// get suspension bridges field
|
||||
var field = this.Reflection.GetField<IEnumerable<SuspensionBridge>?>(location, nameof(IslandNorth.suspensionBridges), required: false);
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse -- field is nullable when required: false
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- field is nullable when required: false
|
||||
if (field == null || !typeof(IEnumerable<SuspensionBridge>).IsAssignableFrom(field.FieldInfo.FieldType))
|
||||
continue;
|
||||
|
||||
|
|
|
@ -54,8 +54,10 @@ namespace StardewModdingAPI.Metadata
|
|||
// detect Harmony & rewrite for SMAPI 3.12 (Harmony 1.x => 2.0 update)
|
||||
yield return new HarmonyRewriter();
|
||||
|
||||
#if SMAPI_DEPRECATED
|
||||
// detect issues for SMAPI 4.0.0
|
||||
yield return new LegacyAssemblyFinder();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
yield return new HarmonyRewriter(shouldRewrite: false);
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
|
||||
<PackageReference Include="MonoMod.Common" Version="22.3.5.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.1" />
|
||||
<PackageReference Include="Pintail" Version="2.2.0" />
|
||||
<PackageReference Include="Platonymous.TMXTile" Version="1.5.9" />
|
||||
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
|
||||
|
|
|
@ -121,7 +121,7 @@ namespace StardewModdingAPI
|
|||
/// <summary>Get a string representation of the given translation.</summary>
|
||||
/// <param name="translation">The translation key.</param>
|
||||
/// <remarks><strong>Limitation with nullable reference types: if there's no text and you disabled the fallback via <see cref="UsePlaceholder"/>, this will return null but the return value will still be marked non-nullable.</strong></remarks>
|
||||
[SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The null check is required due to limitations in nullable type annotations (see remarks).")]
|
||||
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The null check is required due to limitations in nullable type annotations (see remarks).")]
|
||||
public static implicit operator string(Translation translation)
|
||||
{
|
||||
return translation?.ToString()!;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
#if SMAPI_DEPRECATED
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.Deprecations;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingAPI.Utilities
|
||||
{
|
||||
|
@ -41,12 +43,31 @@ namespace StardewModdingAPI.Utilities
|
|||
/// <summary>Construct an instance.</summary>
|
||||
/// <remarks><strong>Limitation with nullable reference types:</strong> when the underlying type <typeparamref name="T"/> is nullable, this sets the default value to null regardless of whether you marked the type parameter nullable. To avoid that, set the default value with the 'createNewState' argument instead.</remarks>
|
||||
public PerScreen()
|
||||
: this(null, nullExpected: true) { }
|
||||
{
|
||||
this.CreateNewState = (() => default!);
|
||||
}
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="createNewState">Create the initial state for a screen.</param>
|
||||
public PerScreen(Func<T> createNewState)
|
||||
: this(createNewState, nullExpected: false) { }
|
||||
{
|
||||
if (createNewState is null)
|
||||
{
|
||||
#if SMAPI_DEPRECATED
|
||||
createNewState = (() => default!);
|
||||
SCore.DeprecationManager.Warn(
|
||||
null,
|
||||
$"calling the {nameof(PerScreen<T>)} constructor with null",
|
||||
"3.14.0",
|
||||
DeprecationLevel.Notice
|
||||
);
|
||||
#else
|
||||
throw new ArgumentNullException(nameof(createNewState));
|
||||
#endif
|
||||
}
|
||||
|
||||
this.CreateNewState = createNewState;
|
||||
}
|
||||
|
||||
/// <summary>Get all active values by screen ID. This doesn't initialize the value for a screen ID if it's not created yet.</summary>
|
||||
public IEnumerable<KeyValuePair<int, T>> GetActiveValues()
|
||||
|
@ -84,30 +105,6 @@ namespace StardewModdingAPI.Utilities
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="createNewState">Create the initial state for a screen.</param>
|
||||
/// <param name="nullExpected">Whether a null <paramref name="createNewState"/> value is expected.</param>
|
||||
/// <remarks>This constructor only exists to maintain backwards compatibility. In SMAPI 4.0.0, the overload that passes <c>nullExpected: false</c> should throw an exception instead.</remarks>
|
||||
private PerScreen(Func<T>? createNewState, bool nullExpected)
|
||||
{
|
||||
if (createNewState is null)
|
||||
{
|
||||
createNewState = (() => default!);
|
||||
|
||||
if (!nullExpected)
|
||||
{
|
||||
SCore.DeprecationManager.Warn(
|
||||
null,
|
||||
$"calling the {nameof(PerScreen<T>)} constructor with null",
|
||||
"3.14.0",
|
||||
DeprecationLevel.Notice
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.CreateNewState = createNewState;
|
||||
}
|
||||
|
||||
/// <summary>Remove screens which are no longer active.</summary>
|
||||
private void RemoveDeadScreens()
|
||||
{
|
||||
|
|
|
@ -250,7 +250,7 @@ namespace StardewModdingAPI.Utilities
|
|||
/// <param name="year">The year.</param>
|
||||
/// <param name="allowDayZero">Whether to allow 0 spring Y1 as a valid date.</param>
|
||||
/// <exception cref="ArgumentException">One of the arguments has an invalid value (like day 35).</exception>
|
||||
[SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The nullability is validated in this constructor.")]
|
||||
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The nullability is validated in this constructor.")]
|
||||
private SDate(int day, string season, int year, bool allowDayZero)
|
||||
{
|
||||
season = season?.Trim().ToLowerInvariant()!; // null-checked below
|
||||
|
@ -278,11 +278,11 @@ namespace StardewModdingAPI.Utilities
|
|||
|
||||
/// <summary>Get whether a date represents 0 spring Y1, which is the date during the in-game intro.</summary>
|
||||
/// <param name="day">The day of month.</param>
|
||||
/// <param name="season">The season name.</param>
|
||||
/// <param name="season">The normalized season name.</param>
|
||||
/// <param name="year">The year.</param>
|
||||
private bool IsDayZero(int day, string season, int year)
|
||||
{
|
||||
return day == 0 && season?.Trim().ToLower() == "spring" && year == 1;
|
||||
return day == 0 && season == "spring" && year == 1;
|
||||
}
|
||||
|
||||
/// <summary>Get the day of week for a given date.</summary>
|
||||
|
|
Loading…
Reference in New Issue