Merge remote-tracking branch 'pathoschild/stable' into develop
# Conflicts: # .gitignore # build/common.targets # src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj # src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj # src/SMAPI.Tests/SMAPI.Tests.csproj # src/SMAPI.Toolkit/ModToolkit.cs # src/SMAPI.Toolkit/SMAPI.Toolkit.csproj # src/SMAPI.sln # src/SMAPI/Constants.cs # src/SMAPI/Framework/ContentManagers/ModContentManager.cs # src/SMAPI/Framework/Input/GamePadStateBuilder.cs # src/SMAPI/Framework/Logging/LogManager.cs # src/SMAPI/Framework/ModLoading/AssemblyLoader.cs # src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs # src/SMAPI/Framework/Models/SConfig.cs # src/SMAPI/Framework/Patching/GamePatcher.cs # src/SMAPI/Framework/Reflection/Reflector.cs # src/SMAPI/Framework/SCore.cs # src/SMAPI/Framework/SGame.cs # src/SMAPI/Framework/SMultiplayer.cs # src/SMAPI/Framework/StateTracking/LocationTracker.cs # src/SMAPI/Metadata/CoreAssetPropagator.cs # src/SMAPI/Metadata/InstructionMetadata.cs # src/SMAPI/SMAPI.csproj
This commit is contained in:
commit
3e43d69745
|
@ -22,6 +22,9 @@ insert_final_newline = false
|
|||
[README.txt]
|
||||
end_of_line=crlf
|
||||
|
||||
[*.{command,sh}]
|
||||
end_of_line=lf
|
||||
|
||||
##########
|
||||
## C# formatting
|
||||
## documentation: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# normalize line endings
|
||||
* text=auto
|
||||
README.txt text=crlf
|
||||
README.txt text eol=crlf
|
||||
|
||||
*.command text eol=lf
|
||||
*.sh text eol=lf
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[Oo]bj/
|
||||
|
||||
# Visual Studio cache/options
|
||||
.config/
|
||||
.vs/
|
||||
|
||||
# ReSharper
|
||||
|
@ -32,6 +33,10 @@ appsettings.Development.json
|
|||
|
||||
# Azure generated files
|
||||
src/SMAPI.Web/Properties/PublishProfiles/*.pubxml
|
||||
src/SMAPI.Web/Properties/ServiceDependencies/* - Web Deploy/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Loader Game Asserts
|
||||
src/Loader/Assets/Content/
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -1,15 +1,44 @@
|
|||
<!--
|
||||
|
||||
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">
|
||||
|
||||
<Import Project="find-game-folder.targets" />
|
||||
|
||||
<!--set properties -->
|
||||
<PropertyGroup>
|
||||
<Version>3.7.6</Version>
|
||||
<!--set general build properties -->
|
||||
<Version>3.18.2</Version>
|
||||
<Product>SMAPI</Product>
|
||||
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||
<DefineConstants Condition="$(OS) == 'Windows_NT' AND '$(BUILD_FOR_MOBILE)' == ''">$(DefineConstants);SMAPI_FOR_WINDOWS</DefineConstants>
|
||||
<DefineConstants Condition="$(OS) == 'Windows_NT' AND '$(BUILD_FOR_MOBILE)' == ''">$(DefineConstants);SMAPI_DEPRECATED;SMAPI_FOR_WINDOWS</DefineConstants>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
<!--enable nullable annotations, except in .NET Standard 2.0 where they aren't supported-->
|
||||
<Nullable Condition="'$(TargetFramework)' != 'netstandard2.0'">enable</Nullable>
|
||||
<NoWarn Condition="'$(TargetFramework)' == 'netstandard2.0'">$(NoWarn);CS8632</NoWarn>
|
||||
|
||||
<!--set platform-->
|
||||
<DefineConstants Condition="$(OS) == 'Windows_NT'">$(DefineConstants);SMAPI_FOR_WINDOWS</DefineConstants>
|
||||
<CopyToGameFolder>true</CopyToGameFolder>
|
||||
|
||||
<!-- allow mods to be compiled as AnyCPU for compatibility with older platforms -->
|
||||
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||
|
||||
<!--
|
||||
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.
|
||||
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 Condition="$(DefineConstants.Contains(SMAPI_DEPRECATED))">$(NoWarn);CS0612;CS0618</NoWarn>
|
||||
<NoWarn>$(NoWarn);CS0436;CA1416;CS0809;NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="$(COMPILE_WITH_PLUGIN) == 'True'">
|
||||
|
@ -19,52 +48,20 @@
|
|||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- if game path is invalid, show one user-friendly error instead of a slew of reference errors -->
|
||||
<!--find game folder-->
|
||||
<Import Project="find-game-folder.targets" />
|
||||
<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="!$(DefineConstants.Contains('SMAPI_FOR_MOBILE'))">
|
||||
<CallTarget Targets="CopySMAPI;CopyDefaultMods" />
|
||||
</Target>
|
||||
<Target Name="CopySMAPI" Condition="'$(MSBuildProjectName)' == 'SMAPI'">
|
||||
<ItemGroup>
|
||||
<TranslationFiles Include="$(TargetDir)\i18n\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFolder="$(GamePath)" />
|
||||
<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)\0Harmony.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\TMXTile.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\smapi-internal\i18n" />
|
||||
</Target>
|
||||
<Target Name="CopyDefaultMods" Condition="'$(MSBuildProjectName)' == 'SMAPI.Mods.ConsoleCommands' OR '$(MSBuildProjectName)' == 'SMAPI.Mods.SaveBackup'">
|
||||
<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)" />
|
||||
</Target>
|
||||
<Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
</Target>
|
||||
<Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit.CoreInterfaces' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
</Target>
|
||||
|
||||
<!-- 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'">
|
||||
|
@ -73,7 +70,6 @@
|
|||
<StartWorkingDirectory>$(GamePath)</StartWorkingDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Somehow this makes Visual Studio for Mac recognise the previous section. Nobody knows why. -->
|
||||
<!-- Somehow this makes Visual Studio for macOS recognise the previous section. Nobody knows why. -->
|
||||
<PropertyGroup Condition="'$(RunConfiguration)' == 'Default'" />
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -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" />
|
||||
|
@ -11,23 +17,15 @@
|
|||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/GOG Games/Stardew Valley/game</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.steam/steam/steamapps/common/Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.local/share/Steam/steamapps/common/Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.var/app/com.valvesoftware.Steam/data/Steam/steamapps/common/Stardew Valley</GamePath>
|
||||
|
||||
<!-- Mac (may be 'Unix' or 'OSX') -->
|
||||
<!-- macOS (may be 'Unix' or 'OSX') -->
|
||||
<GamePath Condition="!Exists('$(GamePath)')">/Applications/Stardew Valley.app/Contents/MacOS</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS</GamePath>
|
||||
</PropertyGroup>
|
||||
</When>
|
||||
<When Condition="$(OS) == 'Windows_NT'">
|
||||
<PropertyGroup>
|
||||
<!-- default paths -->
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GalaxyClient\Games\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GOG Galaxy\Games\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\Steam\steamapps\common\Stardew Valley</GamePath>
|
||||
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GOG Galaxy\Games\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley</GamePath>
|
||||
|
||||
<!-- registry paths -->
|
||||
<GamePath Condition="!Exists('$(GamePath)')">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\GOG.com\Games\1453375253', 'PATH', null, RegistryView.Registry32))</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150', 'InstallLocation', null, RegistryView.Registry64, RegistryView.Registry32))</GamePath>
|
||||
|
@ -35,13 +33,34 @@
|
|||
<!-- derive from Steam library path -->
|
||||
<_SteamLibraryPath>$([MSBuild]::GetRegistryValueFromView('HKEY_CURRENT_USER\SOFTWARE\Valve\Steam', 'SteamPath', null, RegistryView.Registry32))</_SteamLibraryPath>
|
||||
<GamePath Condition="!Exists('$(GamePath)') AND '$(_SteamLibraryPath)' != ''">$(_SteamLibraryPath)\steamapps\common\Stardew Valley</GamePath>
|
||||
|
||||
<!-- GOG paths -->
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GalaxyClient\Games\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GOG Galaxy\Games\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GOG Games\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GOG Galaxy\Games\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GOG Games\Stardew Valley</GamePath>
|
||||
|
||||
<!-- Xbox app paths -->
|
||||
<!--
|
||||
The Xbox app saves the install path to the registry, but we can't use it here since it
|
||||
saves the internal readonly path (like C:\Program Files\WindowsApps\Mutable\<package ID>)
|
||||
instead of the mods-enabled path (like C:\Program Files\ModifiableWindowsApps\Stardew Valley).
|
||||
Fortunately we can cheat a bit: players can customize the install drive, but they can't
|
||||
change the install path on the drive.
|
||||
-->
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">D:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">E:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">F:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">G:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">H:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
|
||||
|
||||
<!-- Steam paths -->
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\Steam\steamapps\common\Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley</GamePath>
|
||||
</PropertyGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
|
||||
<!-- set game metadata -->
|
||||
<PropertyGroup>
|
||||
<GameExecutableName>Stardew Valley</GameExecutableName>
|
||||
<GameExecutableName Condition="$(OS) != 'Windows_NT'">StardewValley</GameExecutableName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!--
|
||||
|
||||
This build task is run from the installer project after all projects have been compiled, and
|
||||
creates the build package in the bin\Packages folder.
|
||||
|
||||
-->
|
||||
<Target Name="PrepareInstaller" AfterTargets="AfterBuild">
|
||||
<PropertyGroup>
|
||||
<PlatformName>windows</PlatformName>
|
||||
<PlatformName Condition="$(OS) != 'Windows_NT'">unix</PlatformName>
|
||||
|
||||
<BuildRootPath>$(SolutionDir)</BuildRootPath>
|
||||
<OutRootPath>$(SolutionDir)\..\bin</OutRootPath>
|
||||
|
||||
<SmapiBin>$(BuildRootPath)\SMAPI\bin\$(Configuration)</SmapiBin>
|
||||
<ToolkitBin>$(BuildRootPath)\SMAPI.Toolkit\bin\$(Configuration)\net4.5</ToolkitBin>
|
||||
<ConsoleCommandsBin>$(BuildRootPath)\SMAPI.Mods.ConsoleCommands\bin\$(Configuration)</ConsoleCommandsBin>
|
||||
<SaveBackupBin>$(BuildRootPath)\SMAPI.Mods.SaveBackup\bin\$(Configuration)</SaveBackupBin>
|
||||
|
||||
<PackagePath>$(OutRootPath)\SMAPI installer</PackagePath>
|
||||
<PackageDevPath>$(OutRootPath)\SMAPI installer for developers</PackageDevPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<TranslationFiles Include="$(SmapiBin)\i18n\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- reset package directory -->
|
||||
<RemoveDir Directories="$(PackagePath)" />
|
||||
<RemoveDir Directories="$(PackageDevPath)" />
|
||||
|
||||
<!-- copy installer files -->
|
||||
<Copy SourceFiles="$(TargetDir)\assets\unix-install.sh" DestinationFiles="$(PackagePath)\install on Linux.sh" />
|
||||
<Copy SourceFiles="$(TargetDir)\assets\unix-install.sh" DestinationFiles="$(PackagePath)\install on Mac.command" />
|
||||
<Copy SourceFiles="$(TargetDir)\assets\windows-install.bat" DestinationFiles="$(PackagePath)\install on Windows.bat" />
|
||||
<Copy SourceFiles="$(TargetDir)\assets\README.txt" DestinationFiles="$(PackagePath)\README.txt" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(PackagePath)\internal\$(PlatformName)-install.exe" />
|
||||
<Copy Condition="$(PlatformName) == 'windows'" SourceFiles="$(TargetDir)\assets\windows-exe-config.xml" DestinationFiles="$(PackagePath)\internal\$(PlatformName)-install.exe.config" />
|
||||
|
||||
<!--copy bundle files-->
|
||||
<Copy SourceFiles="$(SmapiBin)\StardewModdingAPI.exe" DestinationFolder="$(PackagePath)\bundle" />
|
||||
<Copy SourceFiles="$(SmapiBin)\StardewModdingAPI.pdb" DestinationFolder="$(PackagePath)\bundle" />
|
||||
<Copy SourceFiles="$(SmapiBin)\StardewModdingAPI.xml" DestinationFolder="$(PackagePath)\bundle" />
|
||||
<Copy SourceFiles="$(SmapiBin)\steam_appid.txt" DestinationFolder="$(PackagePath)\bundle" />
|
||||
<Copy SourceFiles="$(SmapiBin)\0Harmony.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(SmapiBin)\Mono.Cecil.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(SmapiBin)\Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(SmapiBin)\TMXTile.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(SmapiBin)\SMAPI.config.json" DestinationFiles="$(PackagePath)\bundle\smapi-internal\config.json" />
|
||||
<Copy SourceFiles="$(SmapiBin)\SMAPI.metadata.json" DestinationFiles="$(PackagePath)\bundle\smapi-internal\metadata.json" />
|
||||
<Copy SourceFiles="$(ToolkitBin)\SMAPI.Toolkit.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(ToolkitBin)\SMAPI.Toolkit.pdb" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(ToolkitBin)\SMAPI.Toolkit.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(ToolkitBin)\SMAPI.Toolkit.CoreInterfaces.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(ToolkitBin)\SMAPI.Toolkit.CoreInterfaces.pdb" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(ToolkitBin)\SMAPI.Toolkit.CoreInterfaces.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(PackagePath)\bundle\smapi-internal\i18n" />
|
||||
<Copy Condition="$(PlatformName) == 'unix'" SourceFiles="$(TargetDir)\assets\unix-launcher.sh" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI" />
|
||||
<Copy Condition="$(PlatformName) == 'unix'" SourceFiles="$(SmapiBin)\System.Numerics.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy Condition="$(PlatformName) == 'unix'" SourceFiles="$(SmapiBin)\System.Runtime.Caching.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy Condition="$(PlatformName) == 'windows'" SourceFiles="$(TargetDir)\assets\windows-exe-config.xml" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI.exe.config" />
|
||||
|
||||
<!--copy bundled mods-->
|
||||
<Copy SourceFiles="$(ConsoleCommandsBin)\ConsoleCommands.dll" DestinationFolder="$(PackagePath)\bundle\Mods\ConsoleCommands" />
|
||||
<Copy SourceFiles="$(ConsoleCommandsBin)\ConsoleCommands.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\ConsoleCommands" />
|
||||
<Copy SourceFiles="$(ConsoleCommandsBin)\manifest.json" DestinationFolder="$(PackagePath)\bundle\Mods\ConsoleCommands" />
|
||||
<Copy SourceFiles="$(SaveBackupBin)\SaveBackup.dll" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
|
||||
<Copy SourceFiles="$(SaveBackupBin)\SaveBackup.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
|
||||
<Copy SourceFiles="$(SaveBackupBin)\manifest.json" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
|
||||
|
||||
<!-- fix errors on Linux/Mac (sample: https://smapi.io/log/mMdFUpgB) -->
|
||||
<Copy Condition="$(PlatformName) == 'unix'" SourceFiles="$(TargetDir)\assets\System.Numerics.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy Condition="$(PlatformName) == 'unix'" SourceFiles="$(TargetDir)\assets\System.Runtime.Caching.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
|
||||
<!-- fix Linux/Mac permissions -->
|
||||
<Exec Condition="$(PlatformName) == 'unix'" Command="chmod 755 "$(PackagePath)\install on Linux.sh"" />
|
||||
<Exec Condition="$(PlatformName) == 'unix'" Command="chmod 755 "$(PackagePath)\install on Mac.command"" />
|
||||
|
||||
<!-- finalise 'for developers' installer -->
|
||||
<ItemGroup>
|
||||
<PackageFiles Include="$(PackagePath)\**\*.*" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(PackageFiles)" DestinationFolder="$(PackageDevPath)\%(RecursiveDir)" />
|
||||
<ZipDirectory FromDirPath="$(PackageDevPath)\bundle" ToFilePath="$(PackageDevPath)\internal\$(PlatformName)-install.dat" />
|
||||
<RemoveDir Directories="$(PackageDevPath)\bundle" />
|
||||
|
||||
<!-- finalise normal installer -->
|
||||
<ReplaceFileText FilePath="$(PackagePath)\bundle\smapi-internal\config.json" Search=""DeveloperMode": true" Replace=""DeveloperMode": false" />
|
||||
<ZipDirectory FromDirPath="$(PackagePath)\bundle" ToFilePath="$(PackagePath)\internal\$(PlatformName)-install.dat" />
|
||||
<RemoveDir Directories="$(PackagePath)\bundle" />
|
||||
</Target>
|
||||
|
||||
<!-- Create a zip file with the contents of a given folder path. Derived from https://stackoverflow.com/a/38127938/262123. -->
|
||||
<UsingTask TaskName="ZipDirectory" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
|
||||
<ParameterGroup>
|
||||
<FromDirPath ParameterType="System.String" Required="true" />
|
||||
<ToFilePath ParameterType="System.String" Required="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Using Namespace="System.IO.Compression" />
|
||||
<Code Type="Fragment" Language="cs">
|
||||
<![CDATA[
|
||||
try
|
||||
{
|
||||
ZipFile.CreateFromDirectory(FromDirPath, ToFilePath);
|
||||
return true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.LogErrorFromException(ex);
|
||||
return false;
|
||||
}
|
||||
]]>
|
||||
</Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
|
||||
<!-- Replace text in a file based on a regex pattern. Derived from https://stackoverflow.com/a/22571621/262123. -->
|
||||
<UsingTask TaskName="ReplaceFileText" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
|
||||
<ParameterGroup>
|
||||
<FilePath ParameterType="System.String" Required="true" />
|
||||
<Search ParameterType="System.String" Required="true" />
|
||||
<Replace ParameterType="System.String" Required="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Reference Include="System.Core" />
|
||||
<Using Namespace="System" />
|
||||
<Using Namespace="System.IO" />
|
||||
<Using Namespace="System.Text.RegularExpressions" />
|
||||
<Code Type="Fragment" Language="cs">
|
||||
<![CDATA[
|
||||
File.WriteAllText(
|
||||
FilePath,
|
||||
Regex.Replace(File.ReadAllText(FilePath), Search, Replace)
|
||||
);
|
||||
]]>
|
||||
</Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
</Project>
|
|
@ -0,0 +1,213 @@
|
|||
#!/bin/bash
|
||||
|
||||
#
|
||||
#
|
||||
# This is the Bash equivalent of ../windows/prepare-install-package.ps1.
|
||||
# When making changes, both scripts should be updated.
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
##########
|
||||
## Fetch values
|
||||
##########
|
||||
# paths
|
||||
gamePath="/home/pathoschild/Stardew Valley"
|
||||
bundleModNames=("ConsoleCommands" "ErrorHandler" "SaveBackup")
|
||||
|
||||
# build configuration
|
||||
buildConfig="Release"
|
||||
folders=("linux" "macOS" "windows")
|
||||
declare -A runtimes=(["linux"]="linux-x64" ["macOS"]="osx-x64" ["windows"]="win-x64")
|
||||
declare -A msBuildPlatformNames=(["linux"]="Unix" ["macOS"]="OSX" ["windows"]="Windows_NT")
|
||||
|
||||
# version number
|
||||
version="$1"
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "SMAPI release version (like '4.0.0'):"
|
||||
read version
|
||||
fi
|
||||
|
||||
|
||||
##########
|
||||
## Move to SMAPI root
|
||||
##########
|
||||
cd "`dirname "$0"`/../.."
|
||||
|
||||
|
||||
##########
|
||||
## Clear old build files
|
||||
##########
|
||||
echo "Clearing old builds..."
|
||||
echo "-------------------------------------------------"
|
||||
for path in bin */**/bin */**/obj; do
|
||||
echo "$path"
|
||||
rm -rf $path
|
||||
done
|
||||
echo ""
|
||||
|
||||
##########
|
||||
## Compile files
|
||||
##########
|
||||
. ${0%/*}/set-smapi-version.sh "$version"
|
||||
for folder in ${folders[@]}; do
|
||||
runtime=${runtimes[$folder]}
|
||||
msbuildPlatformName=${msBuildPlatformNames[$folder]}
|
||||
|
||||
echo "Compiling SMAPI for $folder..."
|
||||
echo "-------------------------------------------------"
|
||||
dotnet publish src/SMAPI --configuration $buildConfig -v minimal --runtime "$runtime" -p:OS="$msbuildPlatformName" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" --self-contained true
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
echo "Compiling installer for $folder..."
|
||||
echo "-------------------------------------------------"
|
||||
dotnet publish src/SMAPI.Installer --configuration $buildConfig -v minimal --runtime "$runtime" -p:OS="$msbuildPlatformName" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" -p:PublishTrimmed=True -p:TrimMode=Link --self-contained true
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
for modName in ${bundleModNames[@]}; do
|
||||
echo "Compiling $modName for $folder..."
|
||||
echo "-------------------------------------------------"
|
||||
dotnet publish src/SMAPI.Mods.$modName --configuration $buildConfig -v minimal --runtime "$runtime" -p:OS="$msbuildPlatformName" -p:GamePath="$gamePath" -p:CopyToGameFolder="false"
|
||||
echo ""
|
||||
echo ""
|
||||
done
|
||||
done
|
||||
|
||||
|
||||
##########
|
||||
## Prepare install package
|
||||
##########
|
||||
echo "Preparing install package..."
|
||||
echo "-------------------------------------------------"
|
||||
|
||||
# init paths
|
||||
installAssets="src/SMAPI.Installer/assets"
|
||||
packagePath="bin/SMAPI installer"
|
||||
packageDevPath="bin/SMAPI installer for developers"
|
||||
|
||||
# init structure
|
||||
for folder in ${folders[@]}; do
|
||||
mkdir "$packagePath/internal/$folder/bundle/smapi-internal" --parents
|
||||
done
|
||||
|
||||
# copy base installer files
|
||||
for name in "install on Linux.sh" "install on macOS.command" "install on Windows.bat" "README.txt"; do
|
||||
cp "$installAssets/$name" "$packagePath"
|
||||
done
|
||||
|
||||
# copy per-platform files
|
||||
for folder in ${folders[@]}; do
|
||||
runtime=${runtimes[$folder]}
|
||||
|
||||
# get paths
|
||||
smapiBin="src/SMAPI/bin/$buildConfig/$runtime/publish"
|
||||
internalPath="$packagePath/internal/$folder"
|
||||
bundlePath="$internalPath/bundle"
|
||||
|
||||
# installer files
|
||||
cp -r "src/SMAPI.Installer/bin/$buildConfig/$runtime/publish"/* "$internalPath"
|
||||
rm -rf "$internalPath/assets"
|
||||
|
||||
# runtime config for SMAPI
|
||||
# This is identical to the one generated by the build, except that the min runtime version is
|
||||
# set to 5.0.0 (instead of whatever version it was built with) and rollForward is set to latestMinor instead of
|
||||
# minor.
|
||||
cp "$installAssets/runtimeconfig.json" "$bundlePath/StardewModdingAPI.runtimeconfig.json"
|
||||
|
||||
# installer DLL config
|
||||
if [ $folder == "windows" ]; then
|
||||
cp "$installAssets/windows-exe-config.xml" "$packagePath/internal/windows/install.exe.config"
|
||||
fi
|
||||
|
||||
# bundle root files
|
||||
for name in "StardewModdingAPI" "StardewModdingAPI.dll" "StardewModdingAPI.pdb" "StardewModdingAPI.xml" "steam_appid.txt"; do
|
||||
if [ $name == "StardewModdingAPI" ] && [ $folder == "windows" ]; then
|
||||
name="$name.exe"
|
||||
fi
|
||||
|
||||
cp "$smapiBin/$name" "$bundlePath"
|
||||
done
|
||||
|
||||
# bundle i18n
|
||||
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" "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
|
||||
|
||||
cp "$smapiBin/SMAPI.config.json" "$bundlePath/smapi-internal/config.json"
|
||||
cp "$smapiBin/SMAPI.metadata.json" "$bundlePath/smapi-internal/metadata.json"
|
||||
if [ $folder == "linux" ] || [ $folder == "macOS" ]; then
|
||||
cp "$installAssets/unix-launcher.sh" "$bundlePath"
|
||||
else
|
||||
cp "$installAssets/windows-exe-config.xml" "$bundlePath/StardewModdingAPI.exe.config"
|
||||
fi
|
||||
|
||||
# copy .NET dependencies
|
||||
if [ $folder == "windows" ]; then
|
||||
cp "$smapiBin/System.Management.dll" "$bundlePath/smapi-internal"
|
||||
fi
|
||||
|
||||
# copy legacy .NET dependencies (remove in SMAPI 4.0.0)
|
||||
cp "$smapiBin/System.Configuration.ConfigurationManager.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Runtime.Caching.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Security.Permissions.dll" "$bundlePath/smapi-internal"
|
||||
|
||||
# copy bundled mods
|
||||
for modName in ${bundleModNames[@]}; do
|
||||
fromPath="src/SMAPI.Mods.$modName/bin/$buildConfig/$runtime/publish"
|
||||
targetPath="$bundlePath/Mods/$modName"
|
||||
|
||||
mkdir "$targetPath" --parents
|
||||
|
||||
cp "$fromPath/$modName.dll" "$targetPath"
|
||||
cp "$fromPath/$modName.pdb" "$targetPath"
|
||||
cp "$fromPath/manifest.json" "$targetPath"
|
||||
if [ -d "$fromPath/i18n" ]; then
|
||||
cp -r "$fromPath/i18n" "$targetPath"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# mark scripts executable
|
||||
for path in "install on Linux.sh" "install on macOS.command" "bundle/unix-launcher.sh"; do
|
||||
if [ -f "$packagePath/$path" ]; then
|
||||
chmod 755 "$packagePath/$path"
|
||||
fi
|
||||
done
|
||||
|
||||
# split into main + for-dev folders
|
||||
cp -r "$packagePath" "$packageDevPath"
|
||||
for folder in ${folders[@]}; do
|
||||
# disable developer mode in main package
|
||||
sed --in-place --expression="s/\"DeveloperMode\": true/\"DeveloperMode\": false/" "$packagePath/internal/$folder/bundle/smapi-internal/config.json"
|
||||
|
||||
# convert bundle folder into final 'install.dat' files
|
||||
for path in "$packagePath/internal/$folder" "$packageDevPath/internal/$folder"; do
|
||||
pushd "$path/bundle" > /dev/null
|
||||
zip "install.dat" * --recurse-paths --quiet
|
||||
popd > /dev/null
|
||||
mv "$path/bundle/install.dat" "$path/install.dat"
|
||||
rm -rf "$path/bundle"
|
||||
done
|
||||
done
|
||||
|
||||
|
||||
##########
|
||||
## Create release zips
|
||||
##########
|
||||
# rename folders
|
||||
mv "$packagePath" "bin/SMAPI $version installer"
|
||||
mv "$packageDevPath" "bin/SMAPI $version installer for developers"
|
||||
|
||||
# package files
|
||||
pushd bin > /dev/null
|
||||
zip -9 "SMAPI $version installer.zip" "SMAPI $version installer" --recurse-paths --quiet
|
||||
zip -9 "SMAPI $version installer for developers.zip" "SMAPI $version installer for developers" --recurse-paths --quiet
|
||||
popd > /dev/null
|
||||
|
||||
echo ""
|
||||
echo "Done! Package created in $(pwd)/bin"
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
|
||||
#
|
||||
#
|
||||
# This is the Bash equivalent of ../windows/set-smapi-version.ps1.
|
||||
# When making changes, both scripts should be updated.
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
# get version number
|
||||
version="$1"
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "SMAPI release version (like '4.0.0'):"
|
||||
read version
|
||||
fi
|
||||
|
||||
# move to SMAPI root
|
||||
cd "`dirname "$0"`/../.."
|
||||
|
||||
# apply changes
|
||||
sed "s/<Version>.+<\/Version>/<Version>$version<\/Version>/" "build/common.targets" --in-place --regexp-extended
|
||||
sed "s/RawApiVersion = \".+?\";/RawApiVersion = \"$version\";/" "src/SMAPI/Constants.cs" --in-place --regexp-extended
|
||||
for modName in "ConsoleCommands" "ErrorHandler" "SaveBackup"; do
|
||||
sed "s/\"(Version|MinimumApiVersion)\": \".+?\"/\"\1\": \"$version\"/g" "src/SMAPI.Mods.$modName/manifest.json" --in-place --regexp-extended
|
||||
done
|
|
@ -0,0 +1,67 @@
|
|||
#!/bin/bash
|
||||
|
||||
##########
|
||||
## Read config
|
||||
##########
|
||||
# get SMAPI version
|
||||
version="$1"
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "SMAPI release version (like '4.0.0'):"
|
||||
read version
|
||||
fi
|
||||
|
||||
# get Windows bin path
|
||||
windowsBinPath="$2"
|
||||
if [ $# -le 1 ]; then
|
||||
echo "Windows compiled bin path:"
|
||||
read windowsBinPath
|
||||
fi
|
||||
|
||||
# installer internal folders
|
||||
buildFolders=("linux" "macOS" "windows")
|
||||
|
||||
|
||||
##########
|
||||
## Finalize release package
|
||||
##########
|
||||
for folderName in "SMAPI $version installer" "SMAPI $version installer for developers"; do
|
||||
# move files to Linux filesystem
|
||||
echo "Preparing $folderName.zip..."
|
||||
echo "-------------------------------------------------"
|
||||
echo "copying '$windowsBinPath/$folderName' to Linux filesystem..."
|
||||
cp -r "$windowsBinPath/$folderName" .
|
||||
|
||||
# fix permissions
|
||||
echo "fixing permissions..."
|
||||
find "$folderName" -type d -exec chmod 755 {} \;
|
||||
find "$folderName" -type f -exec chmod 644 {} \;
|
||||
find "$folderName" -name "*.sh" -exec chmod 755 {} \;
|
||||
find "$folderName" -name "*.command" -exec chmod 755 {} \;
|
||||
find "$folderName" -name "SMAPI.Installer" -exec chmod 755 {} \;
|
||||
find "$folderName" -name "StardewModdingAPI" -exec chmod 755 {} \;
|
||||
|
||||
# convert bundle folder into final 'install.dat' files
|
||||
for build in ${buildFolders[@]}; do
|
||||
echo "packaging $folderName/internal/$build/install.dat..."
|
||||
pushd "$folderName/internal/$build/bundle" > /dev/null
|
||||
zip "install.dat" * --recurse-paths --quiet
|
||||
mv install.dat ../
|
||||
popd > /dev/null
|
||||
|
||||
rm -rf "$folderName/internal/$build/bundle"
|
||||
done
|
||||
|
||||
# zip installer
|
||||
echo "packaging installer..."
|
||||
zip -9 "$folderName.zip" "$folderName" --recurse-paths --quiet
|
||||
|
||||
# move zip back to Windows bin path
|
||||
echo "moving release zip to $windowsBinPath/$folderName.zip..."
|
||||
mv "$folderName.zip" "$windowsBinPath"
|
||||
rm -rf "$folderName"
|
||||
|
||||
echo ""
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "Done!"
|
|
@ -0,0 +1,11 @@
|
|||
function In-Place-Regex {
|
||||
param (
|
||||
[Parameter(Mandatory)][string]$Path,
|
||||
[Parameter(Mandatory)][string]$Search,
|
||||
[Parameter(Mandatory)][string]$Replace
|
||||
)
|
||||
|
||||
$content = (Get-Content "$Path" -Encoding UTF8)
|
||||
$content = ($content -replace "$Search", "$Replace")
|
||||
[System.IO.File]::WriteAllLines((Get-Item "$Path").FullName, $content)
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
#
|
||||
#
|
||||
# This is the PowerShell equivalent of ../unix/prepare-install-package.sh, *except* that it doesn't
|
||||
# set Linux permissions, create the install.dat files, or create the final zip (unless you specify
|
||||
# --windows-only). Due to limitations in PowerShell, the final changes are handled by the
|
||||
# windows/finalize-install-package.sh file in WSL.
|
||||
#
|
||||
# When making changes, make sure to update ../unix/prepare-install-package.ps1 too.
|
||||
#
|
||||
#
|
||||
|
||||
. "$PSScriptRoot/lib/in-place-regex.ps1"
|
||||
|
||||
|
||||
##########
|
||||
## Fetch values
|
||||
##########
|
||||
# paths
|
||||
$gamePath = "C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley"
|
||||
$bundleModNames = "ConsoleCommands", "ErrorHandler", "SaveBackup"
|
||||
|
||||
# build configuration
|
||||
$buildConfig = "Release"
|
||||
$folders = "linux", "macOS", "windows"
|
||||
$runtimes = @{ linux = "linux-x64"; macOS = "osx-x64"; windows = "win-x64" }
|
||||
$msBuildPlatformNames = @{ linux = "Unix"; macOS = "OSX"; windows = "Windows_NT" }
|
||||
|
||||
# version number
|
||||
$version = $args[0]
|
||||
if (!$version) {
|
||||
$version = Read-Host "SMAPI release version (like '4.0.0')"
|
||||
}
|
||||
|
||||
# Windows-only build
|
||||
$windowsOnly = $false
|
||||
foreach ($arg in $args) {
|
||||
if ($arg -eq "--windows-only") {
|
||||
$windowsOnly = $true
|
||||
$folders = "windows"
|
||||
$runtimes = @{ windows = "win-x64" }
|
||||
$msBuildPlatformNames = @{ windows = "Windows_NT" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
##########
|
||||
## Move to SMAPI root
|
||||
##########
|
||||
cd "$PSScriptRoot/../.."
|
||||
|
||||
|
||||
##########
|
||||
## Clear old build files
|
||||
##########
|
||||
echo "Clearing old builds..."
|
||||
echo "-------------------------------------------------"
|
||||
|
||||
foreach ($path in (dir -Recurse -Include ('bin', 'obj'))) {
|
||||
echo "$path"
|
||||
rm -Recurse -Force "$path"
|
||||
}
|
||||
echo ""
|
||||
|
||||
|
||||
##########
|
||||
## Compile files
|
||||
##########
|
||||
. "$PSScriptRoot/set-smapi-version.ps1" "$version"
|
||||
foreach ($folder in $folders) {
|
||||
$runtime = $runtimes[$folder]
|
||||
$msbuildPlatformName = $msBuildPlatformNames[$folder]
|
||||
|
||||
echo "Compiling SMAPI for $folder..."
|
||||
echo "-------------------------------------------------"
|
||||
dotnet publish src/SMAPI --configuration $buildConfig -v minimal --runtime "$runtime" -p:OS="$msbuildPlatformName" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" --self-contained true
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
echo "Compiling installer for $folder..."
|
||||
echo "-------------------------------------------------"
|
||||
dotnet publish src/SMAPI.Installer --configuration $buildConfig -v minimal --runtime "$runtime" -p:OS="$msbuildPlatformName" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" -p:PublishTrimmed=True -p:TrimMode=Link --self-contained true
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
foreach ($modName in $bundleModNames) {
|
||||
echo "Compiling $modName for $folder..."
|
||||
echo "-------------------------------------------------"
|
||||
dotnet publish src/SMAPI.Mods.$modName --configuration $buildConfig -v minimal --runtime "$runtime" -p:OS="$msbuildPlatformName" -p:GamePath="$gamePath" -p:CopyToGameFolder="false"
|
||||
echo ""
|
||||
echo ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
##########
|
||||
## Prepare install package
|
||||
##########
|
||||
echo "Preparing install package..."
|
||||
echo "----------------------------"
|
||||
|
||||
# init paths
|
||||
$installAssets = "src/SMAPI.Installer/assets"
|
||||
$packagePath = "bin/SMAPI installer"
|
||||
$packageDevPath = "bin/SMAPI installer for developers"
|
||||
|
||||
# init structure
|
||||
foreach ($folder in $folders) {
|
||||
mkdir "$packagePath/internal/$folder/bundle/smapi-internal" > $null
|
||||
}
|
||||
|
||||
# copy base installer files
|
||||
foreach ($name in @("install on Linux.sh", "install on macOS.command", "install on Windows.bat", "README.txt")) {
|
||||
if ($windowsOnly -and ($name -eq "install on Linux.sh" -or $name -eq "install on macOS.command")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cp "$installAssets/$name" "$packagePath"
|
||||
}
|
||||
|
||||
# copy per-platform files
|
||||
foreach ($folder in $folders) {
|
||||
$runtime = $runtimes[$folder]
|
||||
|
||||
# get paths
|
||||
$smapiBin = "src/SMAPI/bin/$buildConfig/$runtime/publish"
|
||||
$internalPath = "$packagePath/internal/$folder"
|
||||
$bundlePath = "$internalPath/bundle"
|
||||
|
||||
# installer files
|
||||
cp "src/SMAPI.Installer/bin/$buildConfig/$runtime/publish/*" "$internalPath" -Recurse
|
||||
rm -Recurse -Force "$internalPath/assets"
|
||||
|
||||
# runtime config for SMAPI
|
||||
# This is identical to the one generated by the build, except that the min runtime version is
|
||||
# set to 5.0.0 (instead of whatever version it was built with) and rollForward is set to latestMinor instead of
|
||||
# minor.
|
||||
cp "$installAssets/runtimeconfig.json" "$bundlePath/StardewModdingAPI.runtimeconfig.json"
|
||||
|
||||
# installer DLL config
|
||||
if ($folder -eq "windows") {
|
||||
cp "$installAssets/windows-exe-config.xml" "$packagePath/internal/windows/install.exe.config"
|
||||
}
|
||||
|
||||
# bundle root files
|
||||
foreach ($name in @("StardewModdingAPI", "StardewModdingAPI.dll", "StardewModdingAPI.pdb", "StardewModdingAPI.xml", "steam_appid.txt")) {
|
||||
if ($name -eq "StardewModdingAPI" -and $folder -eq "windows") {
|
||||
$name = "$name.exe"
|
||||
}
|
||||
|
||||
cp "$smapiBin/$name" "$bundlePath"
|
||||
}
|
||||
|
||||
# bundle i18n
|
||||
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", "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"
|
||||
}
|
||||
|
||||
if ($folder -eq "windows") {
|
||||
cp "$smapiBin/VdfConverter.dll" "$bundlePath/smapi-internal"
|
||||
}
|
||||
|
||||
cp "$smapiBin/SMAPI.config.json" "$bundlePath/smapi-internal/config.json"
|
||||
cp "$smapiBin/SMAPI.metadata.json" "$bundlePath/smapi-internal/metadata.json"
|
||||
if ($folder -eq "linux" -or $folder -eq "macOS") {
|
||||
cp "$installAssets/unix-launcher.sh" "$bundlePath"
|
||||
}
|
||||
else {
|
||||
cp "$installAssets/windows-exe-config.xml" "$bundlePath/StardewModdingAPI.exe.config"
|
||||
}
|
||||
|
||||
# copy .NET dependencies
|
||||
if ($folder -eq "windows") {
|
||||
cp "$smapiBin/System.Management.dll" "$bundlePath/smapi-internal"
|
||||
}
|
||||
|
||||
# copy legacy .NET dependencies (remove in SMAPI 4.0.0)
|
||||
cp "$smapiBin/System.Configuration.ConfigurationManager.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Runtime.Caching.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Security.Permissions.dll" "$bundlePath/smapi-internal"
|
||||
|
||||
# copy bundled mods
|
||||
foreach ($modName in $bundleModNames) {
|
||||
$fromPath = "src/SMAPI.Mods.$modName/bin/$buildConfig/$runtime/publish"
|
||||
$targetPath = "$bundlePath/Mods/$modName"
|
||||
|
||||
mkdir "$targetPath" > $null
|
||||
|
||||
cp "$fromPath/$modName.dll" "$targetPath"
|
||||
cp "$fromPath/$modName.pdb" "$targetPath"
|
||||
cp "$fromPath/manifest.json" "$targetPath"
|
||||
if (Test-Path "$fromPath/i18n" -PathType Container) {
|
||||
cp -Recurse "$fromPath/i18n" "$targetPath"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# DISABLED: will be handled by Linux script
|
||||
# mark scripts executable
|
||||
#ForEach ($path in @("install on Linux.sh", "install on macOS.command", "bundle/unix-launcher.sh")) {
|
||||
# if (Test-Path "$packagePath/$path" -PathType Leaf) {
|
||||
# chmod 755 "$packagePath/$path"
|
||||
# }
|
||||
#}
|
||||
|
||||
# split into main + for-dev folders
|
||||
cp -Recurse "$packagePath" "$packageDevPath"
|
||||
foreach ($folder in $folders) {
|
||||
# disable developer mode in main package
|
||||
In-Place-Regex -Path "$packagePath/internal/$folder/bundle/smapi-internal/config.json" -Search "`"DeveloperMode`": true" -Replace "`"DeveloperMode`": false"
|
||||
|
||||
# convert bundle folder into final 'install.dat' files
|
||||
if ($windowsOnly)
|
||||
{
|
||||
foreach ($path in @("$packagePath/internal/$folder", "$packageDevPath/internal/$folder"))
|
||||
{
|
||||
Compress-Archive -Path "$path/bundle/*" -CompressionLevel Optimal -DestinationPath "$path/install.zip"
|
||||
mv "$path/install.zip" "$path/install.dat"
|
||||
rm -Recurse -Force "$path/bundle"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
###########
|
||||
### Create release zips
|
||||
###########
|
||||
# rename folders
|
||||
mv "$packagePath" "bin/SMAPI $version installer"
|
||||
mv "$packageDevPath" "bin/SMAPI $version installer for developers"
|
||||
|
||||
# package files
|
||||
if ($windowsOnly)
|
||||
{
|
||||
Compress-Archive -Path "bin/SMAPI $version installer" -DestinationPath "bin/SMAPI $version installer.zip" -CompressionLevel Optimal
|
||||
Compress-Archive -Path "bin/SMAPI $version installer for developers" -DestinationPath "bin/SMAPI $version installer for developers.zip" -CompressionLevel Optimal
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "Done! See docs/technical/smapi.md to create the release zips."
|
|
@ -0,0 +1,25 @@
|
|||
#
|
||||
#
|
||||
# This is the PowerShell equivalent of ../unix/set-smapi-version.sh.
|
||||
# When making changes, both scripts should be updated.
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
. "$PSScriptRoot\lib\in-place-regex.ps1"
|
||||
|
||||
# get version number
|
||||
$version=$args[0]
|
||||
if (!$version) {
|
||||
$version = Read-Host "SMAPI release version (like '4.0.0')"
|
||||
}
|
||||
|
||||
# move to SMAPI root
|
||||
cd "$PSScriptRoot/../.."
|
||||
|
||||
# apply changes
|
||||
In-Place-Regex -Path "build/common.targets" -Search "<Version>.+</Version>" -Replace "<Version>$version</Version>"
|
||||
In-Place-Regex -Path "src/SMAPI/Constants.cs" -Search "RawApiVersion = `".+?`";" -Replace "RawApiVersion = `"$version`";"
|
||||
ForEach ($modName in "ConsoleCommands","ErrorHandler","SaveBackup") {
|
||||
In-Place-Regex -Path "src/SMAPI.Mods.$modName/manifest.json" -Search "`"(Version|MinimumApiVersion)`": `".+?`"" -Replace "`"`$1`": `"$version`""
|
||||
}
|
|
@ -11,9 +11,9 @@ doesn't change any of your game files. It serves seven main purposes:
|
|||
couldn't._
|
||||
|
||||
3. **Rewrite mods for compatibility.**
|
||||
_SMAPI rewrites mods' compiled code before loading them so they work on Linux/Mac/Windows
|
||||
without the mods needing to handle differences between the Linux/Mac and Windows versions of the
|
||||
game. In some cases it also rewrites code broken by a game update so the mod doesn't break._
|
||||
_SMAPI rewrites mods' compiled code before loading them so they work on Linux/macOS/Windows
|
||||
without the mods needing to handle differences between the Linux/macOS and Windows versions of
|
||||
the game. In some cases it also rewrites code broken by a game update so the mod doesn't break._
|
||||
|
||||
5. **Intercept errors and automatically fix saves.**
|
||||
_SMAPI intercepts errors, shows the error info in the SMAPI console, and in most cases
|
||||
|
@ -56,17 +56,24 @@ SMAPI rarely shows text in-game, so it only has a few translations. Contribution
|
|||
[Modding:Translations](https://stardewvalleywiki.com/Modding:Translations) on the wiki for help
|
||||
contributing translations.
|
||||
|
||||
locale | status
|
||||
---------- | :----------------
|
||||
default | ✓ [fully translated](../src/SMAPI/i18n/default.json)
|
||||
Chinese | ✓ [fully translated](../src/SMAPI/i18n/zh.json)
|
||||
French | ✓ [fully translated](../src/SMAPI/i18n/fr.json)
|
||||
German | ✓ [fully translated](../src/SMAPI/i18n/de.json)
|
||||
Hungarian | ✓ [fully translated](../src/SMAPI/i18n/hu.json)
|
||||
Italian | ✓ [fully translated](../src/SMAPI/i18n/it.json)
|
||||
Japanese | ✓ [fully translated](../src/SMAPI/i18n/ja.json)
|
||||
Korean | ✓ [fully translated](../src/SMAPI/i18n/ko.json)
|
||||
Portuguese | ✓ [fully translated](../src/SMAPI/i18n/pt.json)
|
||||
Russian | ✓ [fully translated](../src/SMAPI/i18n/ru.json)
|
||||
Spanish | ✓ [fully translated](../src/SMAPI/i18n/es.json)
|
||||
Turkish | ✓ [fully translated](../src/SMAPI/i18n/tr.json)
|
||||
locale | status
|
||||
----------- | :----------------
|
||||
default | ✓ [fully translated](../src/SMAPI/i18n/default.json)
|
||||
Chinese | ✓ [fully translated](../src/SMAPI/i18n/zh.json)
|
||||
French | ✓ [fully translated](../src/SMAPI/i18n/fr.json)
|
||||
German | ✓ [fully translated](../src/SMAPI/i18n/de.json)
|
||||
Hungarian | ✓ [fully translated](../src/SMAPI/i18n/hu.json)
|
||||
Italian | ✓ [fully translated](../src/SMAPI/i18n/it.json)
|
||||
Japanese | ✓ [fully translated](../src/SMAPI/i18n/ja.json)
|
||||
Korean | ✓ [fully translated](../src/SMAPI/i18n/ko.json)
|
||||
[Polish] | ✓ [fully translated](../src/SMAPI/i18n/pl.json)
|
||||
Portuguese | ✓ [fully translated](../src/SMAPI/i18n/pt.json)
|
||||
Russian | ✓ [fully translated](../src/SMAPI/i18n/ru.json)
|
||||
Spanish | ✓ [fully translated](../src/SMAPI/i18n/es.json)
|
||||
[Thai] | ✓ [fully translated](../src/SMAPI/i18n/th.json)
|
||||
Turkish | ✓ [fully translated](../src/SMAPI/i18n/tr.json)
|
||||
[Ukrainian] | ✓ [fully translated](../src/SMAPI/i18n/uk.json)
|
||||
|
||||
[Polish]: https://www.nexusmods.com/stardewvalley/mods/3616
|
||||
[Thai]: https://www.nexusmods.com/stardewvalley/mods/7052
|
||||
[Ukrainian]: https://www.nexusmods.com/stardewvalley/mods/8427
|
||||
|
|
|
@ -19,7 +19,7 @@ Released 13 September 2019 for Stardew Valley 1.3.36.
|
|||
* Added log parser instructions for Android.
|
||||
* Fixed log parser failing in some cases due to time format localization.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* `this.Monitor.Log` now defaults to the `Trace` log level instead of `Debug`. The change will only take effect when you recompile the mod.
|
||||
* Fixed 'location list changed' verbose log not correctly listing changes.
|
||||
* Fixed mods able to directly load (and in some cases edit) a different mod's local assets using internal asset key forwarding.
|
||||
|
@ -30,7 +30,7 @@ Released 13 September 2019 for Stardew Valley 1.3.36.
|
|||
Released 23 April 2019 for Stardew Valley 1.3.36.
|
||||
|
||||
* For players:
|
||||
* Fixed error when a custom map references certain vanilla tilesheets on Linux/Mac.
|
||||
* Fixed error when a custom map references certain vanilla tilesheets on Linux/macOS.
|
||||
* Fixed compatibility with some Linux distros.
|
||||
|
||||
## 2.11.1
|
||||
|
@ -42,7 +42,7 @@ Released 17 March 2019 for Stardew Valley 1.3.36.
|
|||
* Updated mod compatibility list.
|
||||
* Fixed `world_clear` console command removing chests edited to have a debris name.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added support for suppressing false-positive warnings in rare cases.
|
||||
|
||||
* For the web UI:
|
||||
|
@ -55,7 +55,7 @@ Released 01 March 2019 for Stardew Valley 1.3.36.
|
|||
* For players:
|
||||
* Updated for Stardew Valley 1.3.36.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Bumped all deprecation levels to _pending removal_.
|
||||
|
||||
* For the web UI:
|
||||
|
@ -68,7 +68,7 @@ Released 09 January 2019 for Stardew Valley 1.3.32–33.
|
|||
|
||||
* For players:
|
||||
* SMAPI now keeps the first save backup created for the day, instead of the last one.
|
||||
* Fixed save backup for some Linux/Mac players. (When compression isn't available, SMAPI will now create uncompressed backups instead.)
|
||||
* Fixed save backup for some Linux/macOS players. (When compression isn't available, SMAPI will now create uncompressed backups instead.)
|
||||
* Fixed some common dependencies not linking to the mod page in 'missing mod' errors.
|
||||
* Fixed 'unknown mod' deprecation warnings showing a stack trace when developers mode not enabled.
|
||||
* Fixed 'unknown mod' deprecation warnings when they occur in the Mod constructor.
|
||||
|
@ -80,7 +80,7 @@ Released 09 January 2019 for Stardew Valley 1.3.32–33.
|
|||
* Added beta status filter to compatibility list.
|
||||
* Fixed broken ModDrop links in the compatibility list.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Asset changes are now propagated into the parsed save being loaded if applicable.
|
||||
* Added locale to context trace logs.
|
||||
* Fixed error loading custom map tilesheets in some cases.
|
||||
|
@ -90,7 +90,7 @@ Released 09 January 2019 for Stardew Valley 1.3.32–33.
|
|||
* Fixed 'unknown mod' deprecation warnings showing the wrong stack trace.
|
||||
* Fixed `e.Cursor` in input events showing wrong grab tile when player using a controller moves without moving the viewpoint.
|
||||
* Fixed incorrect 'bypassed safety checks' warning for mods using the new `Specialized.LoadStageChanged` event in 2.10.
|
||||
* Deprecated `EntryDll` values whose capitalization don't match the actual file. (This works on Windows, but causes errors for Linux/Mac players.)
|
||||
* Deprecated `EntryDll` values whose capitalization don't match the actual file. (This works on Windows, but causes errors for Linux/macOS players.)
|
||||
|
||||
## 2.10.1
|
||||
Released 30 December 2018 for Stardew Valley 1.3.32–33.
|
||||
|
@ -106,7 +106,7 @@ Released 29 December 2018 for Stardew Valley 1.3.32–33.
|
|||
* Minor performance improvements.
|
||||
* Tweaked installer to reduce antivirus false positives.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added [events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events): `GameLoop.OneSecondUpdateTicking`, `GameLoop.OneSecondUpdateTicked`, and `Specialized.LoadStageChanged`.
|
||||
* Added `e.IsCurrentLocation` event arg to `World` events.
|
||||
* You can now use `helper.Data.Read/WriteSaveData` as soon as the save is loaded (instead of once the world is initialized).
|
||||
|
@ -133,7 +133,7 @@ Released 16 December 2018 for Stardew Valley 1.3.32.
|
|||
* Fixed game launch errors logged as `SMAPI` instead of `game`.
|
||||
* Fixed Windows installer adding unneeded Unix launcher to game folder.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Moved content pack methods into a new [content pack API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Content_Packs).
|
||||
* Fixed invalid NPC data propagated when a mod changes NPC dispositions.
|
||||
* Fixed `Display.RenderedWorld` event broken in SMAPI 2.9.1.
|
||||
|
@ -162,7 +162,7 @@ Released 07 December 2018 for Stardew Valley 1.3.32.
|
|||
* Fixed empty "mods with warnings" list in some cases due to hidden warnings.
|
||||
* Fixed Console Commands' handling of tool upgrade levels for item commands.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added ModDrop update keys (see [docs](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest#Update_checks)).
|
||||
* Added `IsLocalPlayer` to new player events.
|
||||
* Added `helper.CreateTemporaryContentPack` to replace the deprecated `CreateTransitionalContentPack`.
|
||||
|
@ -183,7 +183,7 @@ Released 07 December 2018 for Stardew Valley 1.3.32.
|
|||
## 2.8.2
|
||||
Released 19 November 2018 for Stardew Valley 1.3.32.
|
||||
|
||||
* Fixed game crash in MacOS with SMAPI 2.8.
|
||||
* Fixed game crash in macOS with SMAPI 2.8.
|
||||
|
||||
## 2.8.1
|
||||
Released 19 November 2018 for Stardew Valley 1.3.32.
|
||||
|
@ -205,7 +205,7 @@ Released 19 November 2018 for Stardew Valley 1.3.32.
|
|||
* SMAPI now recommends a compatible SMAPI version if you have an older game version.
|
||||
* Improved various error messages to be more clear and intuitive.
|
||||
* Improved compatibility with various Linux shells (thanks to lqdev!), and prefer xterm when available.
|
||||
* Fixed transparency issues on Linux/Mac for some mod images.
|
||||
* Fixed transparency issues on Linux/macOS for some mod images.
|
||||
* Fixed error when a mod manifest is corrupted.
|
||||
* Fixed error when a mod adds an unnamed location.
|
||||
* Fixed friendly error no longer shown when SMAPI isn't run from the game folder.
|
||||
|
@ -223,9 +223,9 @@ Released 19 November 2018 for Stardew Valley 1.3.32.
|
|||
* The log parser now has a separate filter for game messages.
|
||||
* The log parser now shows content pack authors (thanks to danvolchek!).
|
||||
* Tweaked log parser UI (thanks to danvolchek!).
|
||||
* Fixed log parser instructions for Mac.
|
||||
* Fixed log parser instructions for macOS.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added [data API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Data) to store mod data in the save file or app data.
|
||||
* Added [multiplayer API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Multiplayer) and [events](https://stardewvalleywiki.com/Modding:Modder_Guide/Apis/Events#Multiplayer_2) to send/receive messages and get connected player info.
|
||||
* Added [verbose logging](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Logging#Verbose_logging) feature.
|
||||
|
@ -267,7 +267,7 @@ Released 14 August 2018 for Stardew Valley 1.3.28.
|
|||
* Improved how mod issues are listed in the console and log.
|
||||
* Revamped installer. It now...
|
||||
* uses a new format that should be more intuitive;
|
||||
* lets players on Linux/Mac choose the console color scheme (SMAPI will auto-detect it on Windows);
|
||||
* lets players on Linux/macOS choose the console color scheme (SMAPI will auto-detect it on Windows);
|
||||
* and validates requirements earlier.
|
||||
* Fixed custom festival maps always using spring tilesheets.
|
||||
* Fixed `player_add` command not recognising return scepter.
|
||||
|
@ -275,7 +275,7 @@ Released 14 August 2018 for Stardew Valley 1.3.28.
|
|||
* Fixed some SMAPI logs not deleted when starting a new session.
|
||||
* Updated compatibility list.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added support for `.json` data files in the content API (including Content Patcher).
|
||||
* Added propagation for asset changes through the content API for...
|
||||
* child sprites;
|
||||
|
@ -314,8 +314,8 @@ Released 01 August 2018 for Stardew Valley 1.3.27.
|
|||
* Removed the `player_setlevel` and `player_setspeed` commands, which weren't implemented in a useful way. Use a mod like CJB Cheats Menu if you need those.
|
||||
* Fixed `SEHException` errors for some players.
|
||||
* Fixed performance issues for some players.
|
||||
* Fixed default color scheme on Mac or in PowerShell (configurable via `StardewModdingAPI.config.json`).
|
||||
* Fixed installer error on Linux/Mac in some cases.
|
||||
* Fixed default color scheme on macOS or in PowerShell (configurable via `StardewModdingAPI.config.json`).
|
||||
* Fixed installer error on Linux/macOS in some cases.
|
||||
* Fixed installer not finding some game paths or showing duplicate paths.
|
||||
* Fixed installer not removing some SMAPI files.
|
||||
* Fixed launch issue for Linux players with some terminals. (Thanks to HanFox and kurumushi!)
|
||||
|
@ -336,7 +336,7 @@ Released 01 August 2018 for Stardew Valley 1.3.27.
|
|||
* Fixed log parser mangling crossplatform paths in some cases.
|
||||
* Fixed `smapi.io/install` not linking to a useful page.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added [input API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Input) for reading and suppressing keyboard, controller, and mouse input.
|
||||
* Added code analysis in the NuGet package to flag common issues as warnings.
|
||||
* Replaced `LocationEvents` to support multiplayer:
|
||||
|
@ -345,7 +345,7 @@ Released 01 August 2018 for Stardew Valley 1.3.27.
|
|||
* each event now provides a list of added/removed values;
|
||||
* added buildings-changed event.
|
||||
* Added `Context.IsMultiplayer` and `Context.IsMainPlayer` flags.
|
||||
* Added `Constants.TargetPlatform` which says whether the game is running on Linux, Mac, or Windows.
|
||||
* Added `Constants.TargetPlatform` which says whether the game is running on Linux, macOS, or Windows.
|
||||
* Added `semanticVersion.IsPrerelease()` method.
|
||||
* Added support for launching multiple instances transparently. This removes the former `--log-path` command-line argument.
|
||||
* Added support for custom seasonal tilesheets when loading an unpacked `.tbin` map.
|
||||
|
@ -376,7 +376,7 @@ Released 01 August 2018 for Stardew Valley 1.3.27.
|
|||
* Mod IDs should only contain letters, numbers, hyphens, dots, and underscores. That allows their use in many contexts like URLs. This restriction is now enforced. (In regex form: `^[a-zA-Z0-9_.-]+$`.)
|
||||
|
||||
* For SMAPI developers:
|
||||
* Added more consistent crossplatform handling, including MacOS detection.
|
||||
* Added more consistent crossplatform handling, including macOS detection.
|
||||
* Added beta update channel.
|
||||
* Added optional mod metadata to the web API (including Nexus info, wiki metadata, etc).
|
||||
* Added early prototype of SMAPI 3.0 events via `helper.Events`.
|
||||
|
@ -411,14 +411,14 @@ Released 26 March 2018 for Stardew Valley 1.2.30–1.2.33.
|
|||
|
||||
* For players:
|
||||
* Fixed some textures not updated when a mod changes them.
|
||||
* Fixed visual bug on Linux/Mac when mods overlay textures.
|
||||
* Fixed visual bug on Linux/macOS when mods overlay textures.
|
||||
* Fixed error when mods remove an asset editor/loader.
|
||||
* Fixed minimum game version incorrectly increased in SMAPI 2.5.3.
|
||||
|
||||
* For the [log parser](https://smapi.io/log):
|
||||
* Fixed error when log text contains certain tokens.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Updated to Json.NET 11.0.2.
|
||||
|
||||
* For SMAPI developers:
|
||||
|
@ -448,7 +448,7 @@ Released 13 March 2018 for Stardew Valley ~~1.2.30~~–1.2.33.
|
|||
## 2.5.2
|
||||
Released 25 February 2018 for Stardew Valley 1.2.30–1.2.33.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Fixed issue where replacing an asset through `asset.AsImage()` or `asset.AsDictionary()` didn't take effect.
|
||||
|
||||
* For the [log parser](https://smapi.io/log):
|
||||
|
@ -467,12 +467,12 @@ Released 24 February 2018 for Stardew Valley 1.2.30–1.2.33.
|
|||
* **Added support for [content packs](https://stardewvalleywiki.com/Modding:Content_packs)**.
|
||||
<small>_Content packs are collections of files for a SMAPI mod to load. These can be installed directly under `Mods` like a normal SMAPI mod, get automatic update and compatibility checks, and provide convenient APIs to the mods that read them._</small>
|
||||
* Added mod detection for unhandled errors (so most errors now mention which mod caused them).
|
||||
* Added install scripts for Linux/Mac (no more manual terminal commands!).
|
||||
* Added install scripts for Linux/macOS (no more manual terminal commands!).
|
||||
* Added the missing mod's name and URL to dependency errors.
|
||||
* Fixed uninstall script not reporting when done on Linux/Mac.
|
||||
* Fixed uninstall script not reporting when done on Linux/macOS.
|
||||
* Updated compatibility list and enabled update checks for more mods.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added support for content packs and new APIs to read them.
|
||||
* Added support for `ISemanticVersion` in JSON models.
|
||||
* Added `SpecializedEvents.UnvalidatedUpdateTick` event for specialized use cases.
|
||||
|
@ -506,7 +506,7 @@ Released 24 January 2018 for Stardew Valley 1.2.30–1.2.33.
|
|||
* For the [log parser](https://smapi.io/log):
|
||||
* Fixed error parsing logs with zero installed mods.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added `SaveEvents.BeforeCreate` and `AfterCreate` events.
|
||||
* Added `SButton` `IsActionButton()` and `IsUseToolButton()` extensions.
|
||||
* Improved JSON parse errors to provide more useful info for troubleshooting.
|
||||
|
@ -524,10 +524,10 @@ Released 26 December 2017 for Stardew Valley 1.2.30–1.2.33.
|
|||
|
||||
* For players:
|
||||
* Added a user-friendly [download page](https://smapi.io).
|
||||
* Improved cryptic libgdiplus errors on Mac when Mono isn't installed.
|
||||
* Improved cryptic libgdiplus errors on macOS when Mono isn't installed.
|
||||
* Fixed mod UIs hidden when menu backgrounds are enabled.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* **Added mod-provided APIs** to allow simple integrations between mods, even without direct assembly references.
|
||||
* Added `GameEvents.FirstUpdateTick` event (called once after all mods are initialized).
|
||||
* Added `IsSuppressed` to input events so mods can optionally avoid handling keys another mod has already handled.
|
||||
|
@ -545,9 +545,9 @@ Released 26 December 2017 for Stardew Valley 1.2.30–1.2.33.
|
|||
Released 02 December 2017 for Stardew Valley 1.2.30–1.2.33.
|
||||
|
||||
* For players:
|
||||
* Fixed error when a mod loads custom assets on Linux/Mac.
|
||||
* Fixed error when checking for updates on Linux/Mac due to API HTTPS redirect.
|
||||
* Fixed error when Mac adds an `mcs` symlink to the installer package.
|
||||
* Fixed error when a mod loads custom assets on Linux/macOS.
|
||||
* Fixed error when checking for updates on Linux/macOS due to API HTTPS redirect.
|
||||
* Fixed error when macOS adds an `mcs` symlink to the installer package.
|
||||
* Fixed `player_add` command not handling tool upgrade levels.
|
||||
* Improved error when a mod has an invalid `EntryDLL` filename format.
|
||||
* Updated compatibility list.
|
||||
|
@ -557,7 +557,7 @@ Released 02 December 2017 for Stardew Valley 1.2.30–1.2.33.
|
|||
* Fixed error when uploading very large logs.
|
||||
* Slightly improved the UI.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added `helper.Content.NormalizeAssetName` method.
|
||||
* Added `SDate.DaysSinceStart` property.
|
||||
* Fixed input events' `e.SuppressButton(button)` method ignoring specified button.
|
||||
|
@ -575,7 +575,7 @@ Released 01 November 2017 for Stardew Valley 1.2.30–1.2.33.
|
|||
* Fixed compatibility check for players with Stardew Valley 1.08.
|
||||
* Fixed `player_setlevel` command not setting XP too.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* The reflection API now works with public code to simplify mod integrations.
|
||||
* The content API now lets you invalidated multiple assets at once.
|
||||
* The `InputEvents` have been improved:
|
||||
|
@ -600,7 +600,7 @@ Released 14 October 2017 for Stardew Valley 1.2.30–1.2.33.
|
|||
* **Mod update checks**
|
||||
SMAPI now checks if your mods have updates available, and will alert you in the console with a convenient link to the
|
||||
mod page. This works with mods from the Chucklefish mod site, GitHub, or Nexus Mods. SMAPI 2.0 launches with
|
||||
update-check support for over 250 existing mods, and more will be added as modders enable the feature.
|
||||
update-check support for over 250 existing mods, and more will be added as mod authors enable the feature.
|
||||
|
||||
* **Mod stability warnings**
|
||||
SMAPI now detects when a mod contains code which can destabilise your game or corrupt your save, and shows a warning
|
||||
|
@ -610,7 +610,7 @@ Released 14 October 2017 for Stardew Valley 1.2.30–1.2.33.
|
|||
The console is now simpler and easier to read, some commands have been streamlined, and the colors now adjust to fit
|
||||
your terminal background color.
|
||||
|
||||
* **New features for modders**
|
||||
* **New features for mod authors**
|
||||
SMAPI 2.0 adds several features to enable new kinds of mods (see
|
||||
[API documentation](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs)).
|
||||
|
||||
|
@ -651,7 +651,7 @@ For players:
|
|||
* The console is now simpler and easier to read, and adjusts its colors to fit your terminal background color.
|
||||
* Renamed installer folder to avoid confusion.
|
||||
* Updated compatibility list.
|
||||
* Fixed update check errors on Linux/Mac.
|
||||
* Fixed update check errors on Linux/macOS.
|
||||
* Fixed collection-changed errors during startup for some players.
|
||||
|
||||
For mod developers:
|
||||
|
@ -685,10 +685,10 @@ For SMAPI developers:
|
|||
Released 09 September 2017 for Stardew Valley 1.2.30–1.2.33.
|
||||
|
||||
For players:
|
||||
* Fixed errors when loading some custom maps on Linux/Mac or using XNB Loader.
|
||||
* Fixed errors when loading some custom maps on Linux/macOS or using XNB Loader.
|
||||
* Fixed errors in rare cases when a mod calculates an in-game date.
|
||||
|
||||
For modders:
|
||||
For mod authors:
|
||||
* Added UTC timestamp to log file.
|
||||
|
||||
For SMAPI developers:
|
||||
|
@ -726,7 +726,7 @@ For players:
|
|||
* Fixed controller mod input broken in 1.15.
|
||||
* Fixed TrainerMod packaging unneeded files.
|
||||
|
||||
For modders:
|
||||
For mod authors:
|
||||
* Fixed mod registry lookups by unique ID not being case-insensitive.
|
||||
|
||||
## 1.15
|
||||
|
@ -744,7 +744,7 @@ For players:
|
|||
* Fixed invalid `ObjectInformation.xnb` causing a flood of warnings; SMAPI now shows one error instead.
|
||||
* Updated mod compatibility list.
|
||||
|
||||
For modders:
|
||||
For mod authors:
|
||||
* Added `SDate` utility for in-game date calculations (see [API reference](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Utilities#Dates)).
|
||||
* Added support for minimum dependency versions in `manifest.json` (see [API reference](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest)).
|
||||
* Added more useful logging when loading mods.
|
||||
|
@ -772,12 +772,12 @@ For players:
|
|||
* you have Stardew Valley 1.11 or earlier (which aren't compatible);
|
||||
* you run `install.exe` from within the downloaded zip file.
|
||||
* Fixed "unknown mod" deprecation warnings by improving how SMAPI detects the mod using the event.
|
||||
* Fixed `libgdiplus.dylib` errors for some players on Mac.
|
||||
* Fixed `libgdiplus.dylib` errors for some players on macOS.
|
||||
* Fixed rare crash when window loses focus for a few players.
|
||||
* Bumped minimum game version to 1.2.30.
|
||||
* Updated mod compatibility list.
|
||||
|
||||
For modders:
|
||||
For mod authors:
|
||||
* You can now add dependencies to `manifest.json` (see [API reference](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest)).
|
||||
* You can now translate your mod (see [API reference](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Translation)).
|
||||
* You can now load unpacked `.tbin` files from your mod folder through the content API.
|
||||
|
@ -788,7 +788,7 @@ For modders:
|
|||
* Fixed `smapi-crash.txt` being copied from the default log even if a different path is specified with `--log-path`.
|
||||
* Fixed the content API not matching XNB filenames with two dots (like `a.b.xnb`) if you don't specify the `.xnb` extension.
|
||||
* Fixed `debug` command output not printed to console.
|
||||
* Deprecated `TimeEvents.DayOfMonthChanged`, `SeasonOfYearChanged`, and `YearOfGameChanged`. These don't do what most modders think they do and aren't very reliable, since they depend on the SMAPI/game lifecycle which can change. You should use `TimeEvents.AfterDayStarted` or `SaveEvents.BeforeSave` instead.
|
||||
* Deprecated `TimeEvents.DayOfMonthChanged`, `SeasonOfYearChanged`, and `YearOfGameChanged`. These don't do what most mod authors think they do and aren't very reliable, since they depend on the SMAPI/game lifecycle which can change. You should use `TimeEvents.AfterDayStarted` or `SaveEvents.BeforeSave` instead.
|
||||
|
||||
## 1.13.1
|
||||
Released 19 May 2017 for Stardew Valley 1.2.26–1.2.29.
|
||||
|
@ -805,8 +805,8 @@ For players:
|
|||
* SMAPI now recovers automatically from errors in the game loop when possible.
|
||||
* SMAPI now remembers if your game crashed and offers help next time you launch it.
|
||||
* Fixed installer sometimes finding redundant game paths.
|
||||
* Fixed save events not being raised after the first day on Linux/Mac.
|
||||
* Fixed error on Linux/Mac when a mod loads a PNG immediately after the save is loaded.
|
||||
* Fixed save events not being raised after the first day on Linux/macOS.
|
||||
* Fixed error on Linux/macOS when a mod loads a PNG immediately after the save is loaded.
|
||||
* Updated mod compatibility list for Stardew Valley 1.2.
|
||||
|
||||
For mod developers:
|
||||
|
@ -826,15 +826,15 @@ Released 03 May 2017 for Stardew Valley 1.2.26–1.2.29.
|
|||
For players:
|
||||
* The installer now lets you choose the install path if you have multiple copies of the game, instead of using the first path found.
|
||||
* Fixed mod draw errors breaking the game.
|
||||
* Fixed mods on Linux/Mac no longer working after the game saves.
|
||||
* Fixed `libgdiplus.dylib` errors on Mac when mods read PNG files.
|
||||
* Fixed mods on Linux/macOS no longer working after the game saves.
|
||||
* Fixed `libgdiplus.dylib` errors on macOS when mods read PNG files.
|
||||
* Adopted pufferchick.
|
||||
|
||||
For mod developers:
|
||||
* Unknown mod manifest fields are now stored in `IManifest::ExtraFields`.
|
||||
* The content API now defaults to `ContentSource.ModFolder`.
|
||||
* Fixed content API error when loading a PNG during early game init (e.g. in mod's `Entry`).
|
||||
* Fixed content API error when loading an XNB from the mod folder on Mac.
|
||||
* Fixed content API error when loading an XNB from the mod folder on macOS.
|
||||
|
||||
## 1.11
|
||||
Released 30 April 2017 for Stardew Valley 1.2.26.
|
||||
|
@ -888,7 +888,7 @@ For players:
|
|||
* Fixed the game-needs-an-update error not pausing before exit.
|
||||
* Fixed installer errors for some players when deleting files.
|
||||
* Fixed installer not ignoring potential game folders that don't contain a Stardew Valley exe.
|
||||
* Fixed installer not recognising Linux/Mac paths starting with `~/` or containing an escaped space.
|
||||
* Fixed installer not recognising Linux/macOS paths starting with `~/` or containing an escaped space.
|
||||
* Fixed TrainerMod letting you add invalid items which may crash the game.
|
||||
* Fixed TrainerMod's `world_downminelevel` command not working.
|
||||
* Fixed rare issue where mod dependencies would override SMAPI dependencies and cause unpredictable bugs.
|
||||
|
@ -912,7 +912,7 @@ For mod developers:
|
|||
* Removed the experimental `IConfigFile`.
|
||||
|
||||
For SMAPI developers:
|
||||
* Added support for debugging SMAPI on Linux/Mac if supported by the editor.
|
||||
* Added support for debugging SMAPI on Linux/macOS if supported by the editor.
|
||||
|
||||
## 1.8
|
||||
Released 04 February 2017 for Stardew Valley 1.1–1.11.
|
||||
|
@ -1004,7 +1004,7 @@ For players:
|
|||
* Improved installer wording to reduce confusion.
|
||||
* Fixed the installer not removing TrainerMod from appdata if it's already in the game mods directory.
|
||||
* Fixed the installer not moving mods out of appdata if the game isn't installed on the same Windows partition.
|
||||
* Fixed the SMAPI console not being shown on Linux and Mac.
|
||||
* Fixed the SMAPI console not being shown on Linux and macOS.
|
||||
|
||||
For developers:
|
||||
* Added a reflection API (via `helper.Reflection`) that simplifies robust access to the game's private fields and methods.
|
||||
|
@ -1016,7 +1016,7 @@ For developers:
|
|||
Released 04 December 2016 for Stardew Valley 1.1–1.11.
|
||||
|
||||
For players:
|
||||
* You can now run most mods on any platform (e.g. run Windows mods on Linux/Mac).
|
||||
* You can now run most mods on any platform (e.g. run Windows mods on Linux/macOS).
|
||||
* Fixed the normal uninstaller not removing files added by the 'SMAPI for developers' installer.
|
||||
|
||||
## 1.2
|
||||
|
@ -1063,7 +1063,7 @@ For developers:
|
|||
Released 11 November 2016 for Stardew Valley 1.1–1.11.
|
||||
|
||||
For players:
|
||||
* Added support for Linux and Mac.
|
||||
* Added support for Linux and macOS.
|
||||
* Added installer to automate adding & removing SMAPI.
|
||||
* Added background update check on launch.
|
||||
* Fixed missing `steam_appid.txt` file.
|
||||
|
|
|
@ -2,11 +2,759 @@
|
|||
|
||||
# Release notes
|
||||
<!--
|
||||
## Future release
|
||||
* For modders:
|
||||
* Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
|
||||
## 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.16.0 first and then install the latest version._
|
||||
-->
|
||||
|
||||
## 3.18.2
|
||||
Released 09 January 2023 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Fixed empty save backups for some macOS players.
|
||||
* Fixed `player_add` console command not handling custom slingshots correctly (thanks too DaLion!).
|
||||
|
||||
* For mod authors:
|
||||
* Added `DelegatingModHooks` utility for mods which need to override SMAPI's mod hooks directly.
|
||||
* Updated to Newtonsoft.Json 13.0.2 (see [changes](https://github.com/JamesNK/Newtonsoft.Json/releases/tag/13.0.2)) and Pintail 2.2.2 (see [changes](https://github.com/Nanoray-pl/Pintail/blob/master/docs/release-notes.md#222)).
|
||||
|
||||
## 3.18.1
|
||||
Released 01 December 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Fixed mod texture edits sometimes cut off (thanks to atravita!).
|
||||
|
||||
* For the web UI:
|
||||
* The log parser no longer warns about missing Error Handler on Android, where it doesn't exist yet (thanks to AnotherPillow!).
|
||||
|
||||
## 3.18.0
|
||||
Released 12 November 2022 for Stardew Valley 1.5.6 or later. See [release highlights](https://www.patreon.com/posts/74565278).
|
||||
|
||||
* For players:
|
||||
* You can now override the mod load order in `smapi-internal/config.json` (thanks to Shockah!).
|
||||
* You can now disable console input in `smapi-internal/config.json`, which may reduce CPU usage on some Linux systems.
|
||||
* Fixed map edits not always applied for farmhands in multiplayer (thanks to SinZ163!).
|
||||
* Internal changes to prepare for the upcoming Stardew Valley 1.6 and SMAPI 4.0.
|
||||
|
||||
* For mod authors:
|
||||
* Optimized asset name comparisons (thanks to atravita!).
|
||||
* Raised all deprecation messages to the 'pending removal' level.
|
||||
* **This is the last major update before SMAPI 4.0.0, which will drop all deprecated APIs.** If you haven't [fixed deprecation warnings in your mod code](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0) (if any), you should do it soon. SMAPI 4.0.0 will release alongside the upcoming Stardew Valley 1.6.
|
||||
|
||||
* For the web UI:
|
||||
* The log parser now detects split-screen mode and shows which screen logged each message.
|
||||
|
||||
## 3.17.2
|
||||
Released 21 October 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Fixed installer crash if Steam's library data is invalid or in an old format; it'll now be ignored instead.
|
||||
* For mod authors:
|
||||
* Fixed image patches sometimes applied one pixel higher than expected after 3.17.0 (thanks to atravita!).
|
||||
|
||||
## 3.17.1
|
||||
Released 10 October 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Fixed installer error on Windows if the Steam library folder exists but doesn't contain Steam's `.vdf` library data file.
|
||||
|
||||
## 3.17.0
|
||||
Released 09 October 2022 for Stardew Valley 1.5.6 or later. See [release highlights](https://www.patreon.com/posts/73090322).
|
||||
|
||||
* For players:
|
||||
* You can now download SMAPI 'strict mode' from [Nexus files](https://www.nexusmods.com/stardewvalley/mods/2400/?tab=files), which removes all deprecated APIs. This may significantly improve performance, but mods which still show deprecation warnings won't work.
|
||||
* The SMAPI installer now also detects game folders in Steam's `.vdf` library data on Windows (thanks to pizzaoverhead!).
|
||||
* SMAPI now prevents mods from enabling Harmony debug mode, which impacts performance and creates a file on your desktop.
|
||||
_You can allow debug mode by editing `smapi-internal/config.json` in your game folder._
|
||||
* Optimized performance and memory usage (thanks to atravita!).
|
||||
* Other internal optimizations.
|
||||
* Added more file extensions to ignore when searching for mod folders: `.7z`, `.tar`, `.tar.gz`, and `.xcf` (thanks to atravita!).
|
||||
* Removed transitional `UseRawImageLoading` option added in 3.15.0. This is now always enabled, except when PyTK is installed.
|
||||
* Fixed update alerts incorrectly shown for prerelease versions on GitHub that aren't marked as prerelease.
|
||||
|
||||
* For mod authors:
|
||||
* When [providing a mod API in a C# mod](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations), you can now get the mod requesting it as an optional parameter (thanks to KhloeLeclair!).
|
||||
* SMAPI now treats square brackets in the manifest `Name` field as round ones to avoid breaking tools which parse log files.
|
||||
* Made deprecation message wording stronger for the upcoming SMAPI 4.0.0 release.
|
||||
* The `Texture2D.Name` field is now set earlier to support mods like SpriteMaster.
|
||||
* Updated dependencies: [Harmony](https://harmony.pardeike.net) 2.2.2 (see [changes](https://github.com/pardeike/Harmony/releases/tag/v2.2.2.0)) and [FluentHttpClient](https://github.com/Pathoschild/FluentHttpClient#readme) 4.2.0 (see [changes](https://github.com/Pathoschild/FluentHttpClient/blob/develop/RELEASE-NOTES.md#420)).
|
||||
* Fixed `LocationListChanged` event not raised & memory leak occurring when a generated mine/volcano is removed (thanks to tylergibbs2!).
|
||||
|
||||
## 3.16.2
|
||||
Released 31 August 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Fixed `NoSuitableGraphicsDeviceException` launch error for some players with compatible GPUs since 3.16.0.
|
||||
|
||||
## 3.16.1
|
||||
Released 29 August 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Updated PyTK compatibility mode for the latest PyTK version.
|
||||
* Fixed broken mods sometimes incorrectly listed as duplicate.
|
||||
|
||||
## 3.16.0
|
||||
Released 22 August 2022 for Stardew Valley 1.5.6 or later. See [release highlights](https://www.patreon.com/posts/70797008).
|
||||
|
||||
* For players:
|
||||
* Added error message if mod files are detected directly under `Mods` (instead of each mod having its own subfolder).
|
||||
* SMAPI now sets a success/error code when the game exits.
|
||||
_This is used by your OS (like Windows) to decide whether to keep the console window open when the game ends._
|
||||
* Fixed SMAPI on Windows applying different DPI awareness settings than the game (thanks to spacechase0!).
|
||||
* Fixed Linux/macOS installer's color scheme question partly unreadable if the terminal background is dark.
|
||||
* Fixed error message when a mod loads an invalid PNG file (thanks to atravita!).
|
||||
* Fixed error message when a mod is duplicated, but one of the copies is also missing the DLL file. This now shows the duplicate-mod message instead of the missing-DLL message.
|
||||
* Fixed macOS launcher using Terminal regardless of the system's default terminal (thanks to ishan!).
|
||||
* Fixed best practices in Linux/macOS launcher scripts (thanks to ishan!).
|
||||
* Improved translations. Thanks to KediDili (updated Turkish)!
|
||||
|
||||
* For mod authors:
|
||||
* While loading your mod, SMAPI now searches for indirect dependencies in your mod's folder (thanks to TehPers)! This mainly enables F# mods.
|
||||
* **Raised deprecation message levels.**
|
||||
_Deprecation warnings are now player-visible in the SMAPI console as faded `DEBUG` messages._
|
||||
* Updated to Pintail 2.2.1 (see [changes](https://github.com/Nanoray-pl/Pintail/blob/master/docs/release-notes.md#221)).
|
||||
* Switched SMAPI's `.pdb` files to the newer 'portable' format. This has no effect on mods.
|
||||
|
||||
* For the web UI:
|
||||
* Added log parser warning about performance of PyTK 1.23.0 or earlier.
|
||||
* Converted images to SVG (thanks to ishan!).
|
||||
* Updated log parser for the new update alert format in SMAPI 3.15.1.
|
||||
* Updated the JSON validator/schema for Content Patcher 1.28.0.
|
||||
* Fixed log parsing for invalid content packs.
|
||||
* Fixed log parsing if a mod logged a null character.
|
||||
|
||||
## 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).
|
||||
|
||||
* For players:
|
||||
* Optimized mod image file loading.
|
||||
* Minor optimizations (thanks to Michael Kuklinski / Ameisen!).
|
||||
* Updated compatibility list.
|
||||
|
||||
* For mod authors:
|
||||
* Added an [`IRawTextureData` asset type](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0#Raw_texture_data), to avoid creating full `Texture2D` instances in many cases.
|
||||
* In `smapi-internal/config.json`, you can now enable verbose logging for specific mods (instead of all or nothing).
|
||||
* Updated dependencies:
|
||||
* Harmony 2.2.1 (see changes in [2.2.0](https://github.com/pardeike/Harmony/releases/tag/v2.2.0.0) and [2.2.1](https://github.com/pardeike/Harmony/releases/tag/v2.2.1.0));
|
||||
* Newtonsoft.Json 13.0.1 (see [changes](https://github.com/JamesNK/Newtonsoft.Json/releases/tag/13.0.1));
|
||||
* Pintail 2.2.0 (see [changes](https://github.com/Nanoray-pl/Pintail/blob/master/docs/release-notes.md#220)).
|
||||
* Removed transitional `UsePintail` option added in 3.14.0 (now always enabled).
|
||||
* Fixed `onBehalfOf` arguments in the new content API being case-sensitive.
|
||||
* Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players.
|
||||
|
||||
## 3.14.7
|
||||
Released 01 June 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Optimized reflection cache to reduce frame skips for some players.
|
||||
|
||||
* For mod authors:
|
||||
* Removed `runtimeconfig.json` setting which impacted hot reload support.
|
||||
|
||||
## 3.14.6
|
||||
Released 27 May 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Fixed error in split-screen mode when a mod provides a localized asset in one screen but not another.
|
||||
* Minor optimizations.
|
||||
|
||||
## 3.14.5
|
||||
Released 22 May 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Improved performance when mods change some asset types (including NPC portraits/sprites).
|
||||
* Fixed _could not find file_ error if a mod provides a localized version of a normally unlocalized asset and then stops providing it.
|
||||
* Fixed CurseForge update checks for the new CurseForge API.
|
||||
|
||||
## 3.14.4
|
||||
Released 15 May 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Improved performance for mods using deprecated APIs.
|
||||
|
||||
* For mod authors:
|
||||
* Removed warning for mods which use `dynamic`.
|
||||
_This no longer causes errors on Linux/macOS after Stardew Valley 1.5.5._
|
||||
|
||||
## 3.14.3
|
||||
Released 12 May 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Reduced in-game performance impact.
|
||||
|
||||
* For mod authors:
|
||||
* Refactored how event handling works under the hood, particularly the new content API. This should have no effect on mod usage.
|
||||
* Verbose mode now logs the in-game time.
|
||||
* Fixed error when loading a `.xnb` file through the old content API without the file extension.
|
||||
* Fixed asset propagation for player sprites not fully updating recolor masks in some cases.
|
||||
|
||||
* For the web UI:
|
||||
* Updated the JSON validator/schema for Content Patcher 1.26.0.
|
||||
|
||||
## 3.14.2
|
||||
Released 08 May 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Enabled case-insensitive file paths by default for Android and Linux players.
|
||||
_This was temporarily disabled in SMAPI 3.14.1, and will remain disabled by default on macOS and Windows since their filesystems are already case-insensitive._
|
||||
* Various performance improvements.
|
||||
* For mod authors:
|
||||
* Dynamic content packs created via `helper.ContentPacks.CreateTemporary` or `CreateFake` are now listed in the log file.
|
||||
* Fixed assets loaded through a fake content pack not working correctly since 3.14.0.
|
||||
|
||||
## 3.14.1
|
||||
Released 06 May 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Improved performance for mods still using the previous content API.
|
||||
* Disabled case-insensitive file paths (introduced in 3.14.0) by default.
|
||||
_You can enable them by editing `smapi-internal/config.json` if needed. They'll be re-enabled in an upcoming version after they're reworked a bit._
|
||||
* Removed experimental 'aggressive memory optimizations' option.
|
||||
_This was disabled by default and is no longer needed in most cases. Memory usage will be better reduced by reworked asset propagation in the upcoming SMAPI 4.0.0._
|
||||
* Fixed 'content file was not found' error when the game tries to load unlocalized text from a localizable mod data asset in 3.14.0.
|
||||
* Fixed error reading empty JSON files. These are now treated as if they didn't exist (matching pre-3.14.0 behavior).
|
||||
* Updated compatibility list.
|
||||
|
||||
## 3.14.0
|
||||
Released 01 May 2022 for Stardew Valley 1.5.6 or later. See [release highlights](https://www.patreon.com/posts/65265507).
|
||||
|
||||
### For players
|
||||
This is a big update, but existing mods should all work fine. If the latest version of a mod breaks in SMAPI 3.14, please report it [on the SMAPI mod page](https://www.nexusmods.com/stardewvalley/mods/2400?tab=posts).
|
||||
|
||||
* Improvements:
|
||||
* SMAPI now ignores dot-prefixed files when searching for mod folders (thanks to Nuztalgia!).
|
||||
* On Linux, SMAPI now fixes many case-sensitive mod path issues automatically.
|
||||
* On Linux/macOS, added `--use-current-shell` [command-line argument](technical/smapi.md#command-line-arguments) to avoid opening a separate terminal window.
|
||||
* Improved performance in some cases.
|
||||
* Improved translations. Thanks to ChulkyBow (updated Ukrainian)!
|
||||
* Dropped update checks for the unofficial 64-bit patcher (obsolete since SMAPI 3.12.6).
|
||||
* Fixes:
|
||||
* Fixed some movie theater textures not translated when loaded through SMAPI (specifically assets with the `_international` suffix).
|
||||
* Fixed the warning text when a mod causes an asset load conflict with itself.
|
||||
* Fixed `--no-terminal` [command-line argument](technical/smapi.md#command-line-arguments) on Linux/macOS still opening a terminal window, even if nothing is logged to it (thanks to Ryhon0!).
|
||||
* Fixed `player_add` console command not handling journal scraps and secret notes correctly.
|
||||
* Fixed `set_farm_type` console command not updating warps.
|
||||
* For the web UI:
|
||||
* Improved log parser UI (thanks to KhloeLeclair!):
|
||||
* Added pagination for big logs.
|
||||
* Added search box to filter the log.
|
||||
* Added option to show/hide content packs in the mod list.
|
||||
* Added jump links in the sidebar.
|
||||
* The filter options now stick to the top of the screen when scrolling.
|
||||
* Rewrote rendering to improve performance.
|
||||
|
||||
### For mod authors
|
||||
This is a big release that includes the new features planned for SMAPI 4.0.0.
|
||||
|
||||
For C# mod authors: your mods should still work fine in SMAPI 3.14.0. However you should review the [migration to SMAPI 4.0](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0) guide and update your mods when possible. Deprecated code will be removed when SMAPI 4.0.0 releases later this year (no sooner than August 2022), and break any mods which haven't updated by that time. You can update affected mods now, there's no need to wait for 4.0.0.
|
||||
|
||||
For content pack authors: SMAPI 3.14.0 and 4.0.0 don't affect content packs. They should work fine as long as
|
||||
the C# mod that loads them is updated.
|
||||
|
||||
* Major changes:
|
||||
* Added [content events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Content), which will replace `IAssetEditor` and `IAssetLoader` in SMAPI 4.0.0.
|
||||
_These include new features not supported by the old API like load conflict resolution, edit priority, and content pack labels. They also support new cases like easily detecting when an asset has changed, and avoid data corruption issues in some edge cases._
|
||||
* Added [nullable reference type annotations](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0#Nullable_reference_type_annotations) for all APIs.
|
||||
* Added [`helper.GameContent` and `helper.ModContent`](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0#Content_loading_API), which will replace `helper.Content` in SMAPI 4.0.0.
|
||||
* Improved [mod-provided API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Mod-provided_APIs) proxying (thanks to Shockah!).
|
||||
_This adds support for custom interfaces in return values or input arguments, custom enums if their values match, generic methods, and more. This is an internal change, you don't need to do anything different in your mod code._
|
||||
* Mod files loaded through SMAPI APIs (including `helper.Content.Load`) are now case-insensitive, even on Linux.
|
||||
* Enabled deprecation notices for all deprecated APIs. These will only be shown in `TRACE` logs for at least a month after SMAPI 3.14.0 releases.
|
||||
* Other improvements:
|
||||
* Added `IAssetDataForImage.ExtendMap` to resize maps in asset editors.
|
||||
* Added `IContentPack.ModContent` property to manage content pack assets.
|
||||
* Added `Constants.ContentPath` to get the full path to the game's `Content` folder.
|
||||
* Added `IAssetName` fields to the info received by `IAssetEditor`, `IAssetLoader`, and content event methods.
|
||||
_This adds methods for working with asset names, parsed locales, etc._
|
||||
* Added `helper.Content.ParseAssetName` to get an `IAssetName` for an arbitrary asset key.
|
||||
* Added [command-line arguments](technical/smapi.md#command-line-arguments) to toggle developer mode (thanks to Tondorian!).
|
||||
* If an asset is loaded multiple times in the same tick, `IAssetLoader.CanLoad` and `IAssetEditor.CanEdit` are now cached unless invalidated by `helper.Content.InvalidateCache`.
|
||||
* The `ISemanticVersion` comparison methods (`CompareTo`, `IsBetween`, `IsNewerThan`, and `IsOlderThan`) now allow null values. A null version is always considered older than any non-null version per [best practices](https://docs.microsoft.com/en-us/dotnet/api/system.icomparable-1.compareto#remarks).
|
||||
* Deprecation notices now show a shorter stack trace in most cases, so it's clearer where the deprecated code is in the mod.
|
||||
* Fixes:
|
||||
* Fixed the `SDate` constructor being case-sensitive.
|
||||
* Fixed support for using locale codes from custom languages in asset names (e.g. `Data/Achievements.eo-EU`).
|
||||
* Fixed issue where suppressing `[Left|Right]Thumbstick[Down|Left]` keys would suppress the opposite direction instead.
|
||||
* Fixed null handling in various edge cases.
|
||||
* For the web UI:
|
||||
* Updated the JSON validator/schema for Content Patcher 1.25.0.
|
||||
* Added `data-*` attributes to the log parser page for external tools.
|
||||
* Fixed JSON validator showing incorrect error for update keys without a subkey.
|
||||
|
||||
### For SMAPI contributors
|
||||
* You no longer need a Nexus API key to launch the `SMAPI.Web` project locally.
|
||||
|
||||
## 3.13.4
|
||||
Released 16 January 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Fixed Linux/macOS launch error in 3.13.3.
|
||||
|
||||
## 3.13.3
|
||||
Released 16 January 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* **SMAPI now needs Stardew Valley 1.5.6 or later.**
|
||||
* Added automatic fix for custom maps which are missing a required tilesheet.
|
||||
* Added automatic save recovery when a custom farm type isn't available anymore.
|
||||
* Added the game's new build number to the SMAPI console + log.
|
||||
* The installer now detects Xbox app game folders.
|
||||
* Reduced mod loading time a bit.
|
||||
* Fixed macOS launch issue when using some terminals (thanks to bruce2409!).
|
||||
* Fixed Linux/macOS terminal ignoring backspaces in Stardew Valley 1.5.5+.
|
||||
* Fixed extra newlines in the SMAPI console.
|
||||
* Fixed outdated instructions in Steam error message.
|
||||
* Fixed uninstaller not removing `StardewModdingAPI.deps.json` file.
|
||||
* Simplified [running without a terminal on Linux/macOS](https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting#SMAPI_doesn.27t_recognize_controller_.28Steam_only.29) when needed.
|
||||
* Updated compatibility list.
|
||||
* Improved translations. Thanks to ChulkyBow (added Ukrainian)!
|
||||
|
||||
* For the web UI:
|
||||
* Added log instructions for Xbox app on Windows.
|
||||
* Added log download option.
|
||||
* Redesigned log instruction UI.
|
||||
* Fixed log parser not correctly handling multiple mods having the exact same name.
|
||||
* Fixed JSON validator not recognizing manifest [update subkeys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks#Update_subkeys).
|
||||
|
||||
## 3.13.2
|
||||
Released 05 December 2021 for Stardew Valley 1.5.5 or later.
|
||||
|
||||
* For players:
|
||||
* You no longer need .NET 5 to install or use SMAPI.
|
||||
* The installer now detects when the game folder contains an incompatible legacy game version.
|
||||
* Updated for the latest Stardew Valley 1.5.5 hotfix.
|
||||
* Updated compatibility list.
|
||||
|
||||
* For the web UI:
|
||||
* Fixed the JSON validator marking `.fnt` files invalid in Content Patcher files.
|
||||
|
||||
* For SMAPI maintainers:
|
||||
* Added [release package scripts](technical/smapi.md) to streamline preparing SMAPI releases.
|
||||
|
||||
## 3.13.1
|
||||
Released 30 November 2021 for Stardew Valley 1.5.5 or later.
|
||||
|
||||
* For players:
|
||||
* Improved .NET 5 validation in Windows installer to better explain how to get the right version.
|
||||
* Fixed installer failing on Windows when run from the game folder.
|
||||
|
||||
## 3.13.0
|
||||
Released 30 November 2021 for Stardew Valley 1.5.5 or later. See [release highlights](https://www.patreon.com/posts/59348226).
|
||||
|
||||
* For players:
|
||||
* Updated for Stardew Valley 1.5.5.
|
||||
* Added `set_farm_type` [console command](https://stardewvalleywiki.com/Modding:Console_commands#Console_commands) to change the current farm type.
|
||||
* Fixed installer window closing immediately if the installer crashed.
|
||||
* Updated compatibility list.
|
||||
|
||||
* For mod authors:
|
||||
* Migrated to 64-bit MonoGame and .NET 5 on all platforms (see [migration guide for mod authors](https://stardewvalleywiki.com/Modding:Migrate_to_Stardew_Valley_1.5.5)).
|
||||
* Added support for [map overlays via `asset.AsMap().PatchMap`](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Content#Edit_a_map).
|
||||
* Added support for loading BmFont `.fnt` files for [custom languages](https://stardewvalleywiki.com/Modding:Custom_languages) through the [content API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Content).
|
||||
|
||||
* For the web UI:
|
||||
* Updated the JSON validator/schema for Content Patcher 1.24.0.
|
||||
|
||||
**Update note for players with older systems:**
|
||||
The game now has two branches: the _main branch_ which you'll get by default, and an optional
|
||||
[_compatibility branch_ for older systems](https://www.stardewvalley.net/compatibility/). The two
|
||||
branches have identical content, but use [different technologies](https://stardewvalleywiki.com/Modding:Migrate_to_Stardew_Valley_1.5.5#Game_compatibility_branch).
|
||||
|
||||
Unfortunately **SMAPI only supports the main branch of the game**. There are formidable difficulties
|
||||
across all mods in supporting all three variations, 32-bit imposes significant restrictions on what
|
||||
mods can do, and the [Steam hardware stats](https://store.steampowered.com/hwsurvey) show that 99.69%
|
||||
of players now have 64-bit.
|
||||
|
||||
## 3.12.8
|
||||
Released 18 October 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Fixed mod edits to the farmhouse shifting the player down one tile in some cases.
|
||||
* Fixed map tile rotations/flips not working for farmhands in split-screen mode.
|
||||
* Improved translations. Thanks to ellipszist (added Thai) and Zangorr (added Polish)!
|
||||
_These are custom languages which require Stardew Valley 1.5.5 and the [Polish](https://www.nexusmods.com/stardewvalley/mods/3616) or [Thai](https://www.nexusmods.com/stardewvalley/mods/7052) mod._
|
||||
|
||||
* For mod authors:
|
||||
* SMAPI now intercepts dictionary duplicate-key errors and adds the key to the error message to simplify troubleshooting. (Due to Harmony limitations, this only works for the dictionary types used by the game.)
|
||||
* Fixed barn/coop exit warps being reset when you edit their interior map.
|
||||
|
||||
* For the web UI:
|
||||
* Added support for unified [mod data overrides](https://stardewvalleywiki.com/Modding:Mod_compatibility#Mod_data_overrides) defined on the wiki.
|
||||
* The mod compatibility list now shows separate beta stats when 'show advanced info' is enabled.
|
||||
|
||||
## 3.12.7
|
||||
Released 18 September 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Added more progress updates in the log during startup.
|
||||
* Simplified asset load error message.
|
||||
* Simplified exception logs.
|
||||
* Fixed crash loading mods with corrupted translation files.
|
||||
|
||||
* For mod authors:
|
||||
* Added asset propagation for `LooseSprites\Giftbox`.
|
||||
* Improved SMAPI's crossplatform read/writing of `Color`, `Point`, `Rectangle`, and `Vector2` in JSON to support nullable fields too.
|
||||
|
||||
* For the web UI:
|
||||
* The mod compatibility list now shows the beta status by default (if any).
|
||||
* Fixed JSON validator line numbers sometimes incorrect.
|
||||
|
||||
## 3.12.6
|
||||
Released 03 September 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Added friendly error when using SMAPI 3.2._x_ with Stardew Valley 1.5.5 or later.
|
||||
* Improved mod compatibility in 64-bit mode (thanks to spacechase0!).
|
||||
* Reduced load time when scanning/rewriting many mods for compatibility.
|
||||
* **Dropped support for unofficial 64-bit mode**. You can now use the [official 64-bit Stardew Valley 1.5.5 beta](https://stardewvalleywiki.com/Modding:Migrate_to_64-bit_on_Windows) instead.
|
||||
* Updated compatibility list.
|
||||
|
||||
* For mod authors:
|
||||
* Added `PathUtilities.NormalizeAssetName` and `PathUtilities.PreferredAssetSeparator` to prepare for the upcoming Stardew Valley 1.5.5.
|
||||
* **SMAPI no longer propagates changes to `Data/Bundles`.**
|
||||
_You can still load/edit the asset like usual, but if bundles have already been loaded for a save, SMAPI will no longer dynamically update the in-game bundles to reflect the changes. Unfortunately this caused bundle corruption when playing in non-English._
|
||||
* Fixed content packs created via `helper.ContentPacks.CreateFake` or `CreateTemporary` not initializing translations correctly.
|
||||
|
||||
* For console commands:
|
||||
* Added `hurry_all` command which immediately warps all NPCs to their scheduled positions.
|
||||
|
||||
**Update note for mod authors:**
|
||||
Stardew Valley 1.5.5 will change how asset names are formatted. If you use `PathUtilities.NormalizePath`
|
||||
to format asset names, you should switch to `PathUtilities.NormalizeAssetName` now so your code will
|
||||
continue working in the next game update.
|
||||
|
||||
## 3.12.5
|
||||
Released 26 August 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* Fixed some mods in unofficial 64-bit mode no longer loading after SMAPI 3.12.3.
|
||||
|
||||
## 3.12.4
|
||||
Released 25 August 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Fixed error loading some mods in SMAPI 3.12.3.
|
||||
|
||||
## 3.12.3
|
||||
Released 25 August 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Added friendly error in 64-bit mode when a mod is 32-bit only.
|
||||
* Fixed console encoding issues on Linux/macOS.
|
||||
* Fixed some installer errors not showing info header.
|
||||
|
||||
* For mod authors:
|
||||
* Added `helper.Translation.GetInAllLocales` to get a translation in every available locale.
|
||||
* Fixed Visual Studio debugger crash when any mods are rewritten for compatibility (thanks to spacechase0!).
|
||||
* Fixed `helper.Data.WriteJsonFile` not deleting the file if the model is null, unlike the other `Write*` methods.
|
||||
* Fixed error-handling for `StackOverflowException` thrown on Linux/macOS.
|
||||
* Internal changes to prepare for Stardew Valley 1.5.5.
|
||||
|
||||
* For the web API:
|
||||
* Fixed update checks not shown for prerelease mod versions when you have a SMAPI beta.
|
||||
* Fixed update checks shown for prerelease mod versions if you have a working non-prerelease version.
|
||||
|
||||
## 3.12.2
|
||||
Released 05 August 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Fixed error creating a new save or joining a multiplayer world in 3.12.1.
|
||||
|
||||
* For mod authors:
|
||||
* Reverted the `Constants.Save*` fix in SMAPI 3.12.1.
|
||||
_The change caused a number of other issues, and is only needed for rare cases where the save folder was invalid. This may be revisited in a future version instead._
|
||||
* Fixed `NullReferenceException` in SMAPI's error-handling when trying to handle an invalid `ReflectionTypeLoadException`.
|
||||
|
||||
## 3.12.1
|
||||
Released 03 August 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* The software conflict message is now shown as a warning to simplify troubleshooting.
|
||||
* Fixed error loading older Harmony mods for some Windows players using unofficial 64-bit Stardew Valley.
|
||||
* Updated compatibility list.
|
||||
|
||||
* For mod authors:
|
||||
* Fixed `Constants.Save*` fields incorrect if the save's folder name and ID don't match.
|
||||
|
||||
## 3.12.0
|
||||
Released 01 August 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/54388616).
|
||||
|
||||
* For players:
|
||||
* Added save recovery when content mods leave null objects in the save (in _Error Handler_).
|
||||
* Added error if the wrong SMAPI bitness is installed (e.g. 32-bit SMAPI with 64-bit game).
|
||||
* Added error if some SMAPI files aren't updated correctly.
|
||||
* Added `removable` option to the `world_clear` console command (in _Console Commands_, thanks to bladeoflight16!).
|
||||
* Fixed handling of Unicode characters in console commands.
|
||||
* Fixed intermittent error if a mod gets mod-provided APIs asynchronously.
|
||||
* Fixed crash when creating a farm name containing characters that aren't allowed in a folder path.
|
||||
|
||||
* For mod authors:
|
||||
* **Updated Harmony 1.2.0.1 to 2.1.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).**
|
||||
* SMAPI now intercepts `KeyNotFoundException` errors and adds the key to the error message to simplify troubleshooting. (Due to Harmony limitations, this only works for the dictionary types used by the game.)
|
||||
* Fixed error loading `.xnb` files from the local mod folder.
|
||||
* Fixed reloading a map not correctly reapplying interior doors.
|
||||
|
||||
## 3.11.0
|
||||
Released 09 July 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/53514295).
|
||||
|
||||
* For players:
|
||||
* Updated for Stardew Valley 1.4.5 multiplayer hotfix on Linux/macOS.
|
||||
* Fixed installer error on Windows when running as administrator (thanks to LostLogic!).
|
||||
* Fixed installer error on some Windows systems (thanks to eddyballs!).
|
||||
* Fixed error if SMAPI fails to dispose on game exit.
|
||||
* Fixed `player_add` and `list_items` console commands not including some shirts _(in Console Commands)_.
|
||||
|
||||
* For mod authors:
|
||||
* Added `World.FurnitureListChanged` event (thanks to DiscipleOfEris!).
|
||||
* Added asset propagation for building/house paint masks.
|
||||
* Added log message for troubleshooting if Windows software which often causes issues is installed (currently MSI Afterburner and RivaTuner).
|
||||
* Improved validation for the manifest `Dependencies` field.
|
||||
* Fixed validation for mods with invalid version `0.0.0`.
|
||||
* Fixed _loaded with custom settings_ trace log added when using default settings.
|
||||
* Fixed `Constants.SaveFolderName` and `Constants.CurrentSavePath` not set correctly in rare cases.
|
||||
|
||||
* For the web UI and JSON validator:
|
||||
* Updated the JSON validator/schema for Content Patcher 1.23.0.
|
||||
* Fixed [JSON schema](technical/web.md#using-a-schema-file-directly) in Visual Studio Code warning about comments and trailing commas.
|
||||
* Fixed JSON schema for `i18n` files requiring the wrong value for the `$schema` field.
|
||||
|
||||
## 3.10.1
|
||||
Released 03 May 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Fixed installer leaving an unneeded `StardewModdingAPI-x64.exe` file in 32-bit game folders.
|
||||
|
||||
## 3.10
|
||||
Released 03 May 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/50764911).
|
||||
|
||||
* For players:
|
||||
* Added full support for the [unofficial 64-bit Stardew Valley patch](https://stardewvalleywiki.com/Modding:Migrate_to_64-bit_on_Windows), which removes memory limits. The installer detects which version of SMAPI you need, and SMAPI shows update alerts for Stardew64Installer if applicable.
|
||||
* Added smarter grouping for skipped mods, so it's easier to see root dependencies to update first.
|
||||
* Added crash recovery when the game can't update a map's seasonal tilesheets _(in Error Handler)_. SMAPI will log an error and keep the previous tilesheets in that case.
|
||||
* Added installer option to enter a custom game path even if it detected a game folder.
|
||||
* `*.ico` files are now ignored when scanning for mods.
|
||||
* Fixed error for non-English players after returning to title, reloading, and entering town with a completed movie theater.
|
||||
* Fixed `world_clear` console command not removing resource clumps outside the farm and secret woods.
|
||||
* Fixed error running SMAPI in a strict sandbox on Linux (thanks to kuesji!).
|
||||
* Fixed `StardewModdingAPI.bin.osx` on macOS overwritten with an identical file on launch which would reset file permissions (thanks to 007wayne!).
|
||||
* Fixed inconsistent spelling/style for 'macOS'.
|
||||
|
||||
* For modders:
|
||||
* Added support for [ignoring local map tilesheet files when loading a map](https://stardewvalleywiki.com/Modding:Maps#Local_copy_of_a_vanilla_tilesheet).
|
||||
* Added asset propagation for `Data\Concessions`.
|
||||
* Added SMAPI version and bitness to the console title before startup to simplify troubleshooting.
|
||||
* If a map loads a tilesheet path with no file extension, SMAPI now automatically links it to a `.png` version in the map folder if possible.
|
||||
* Improved error-handling during asset propagation.
|
||||
* Fixed `Context.IsMainPlayer` returning true for a farmhand in split-screen mode before the screen is initialized.
|
||||
* Fixed error when editing bundle data while a split-screen player is joining.
|
||||
* Fixed update subkeys not working in file descriptions for Nexus mods marked as adult content.
|
||||
|
||||
## 3.9.5
|
||||
Released 21 March 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Added console command to reset community center bundles _(in Console Commands)_.
|
||||
* Disabled aggressive memory optimization by default.
|
||||
_The option was added in SMAPI 3.9.2 to reduce errors for some players, but it can cause multiplayer crashes with some mods. If you often see `OutOfMemoryException` errors, you can edit `smapi-internal/config.json` to re-enable it. We're experimenting with making Stardew Valley 64-bit to address memory issues more systematically._
|
||||
* Fixed bundles corrupted in non-English saves created after SMAPI 3.9.2.
|
||||
_If you have an affected save, you can load your save and then enter the `regenerate_bundles confirm` command in the SMAPI console to fix it._
|
||||
* Internal changes to prepare for unofficial 64-bit.
|
||||
|
||||
* For mod authors:
|
||||
* Improved asset propagation:
|
||||
* Added for interior door sprites.
|
||||
* SMAPI now updates the NPC pathfinding cache when map warps are changed through the content API.
|
||||
* Reduced performance impact of invalidating cached assets before a save is loaded.
|
||||
* Fixed asset changes not reapplied in the edge case where you're playing in non-English, and the changes are only applied after the save is loaded, and the player returns to title and reloads a save, and the game reloads the target asset before the save is loaded.
|
||||
* Added a second `KeybindList` constructor to simplify single-key default bindings.
|
||||
* Added a `Constants.GameFramework` field which indicates whether the game is using XNA Framework or MonoGame.
|
||||
_Note: mods don't need to handle the difference in most cases, but some players may use MonoGame on Windows in upcoming versions. Mods which check `Constants.TargetPlatform` should review usages as needed._
|
||||
|
||||
## 3.9.4
|
||||
Released 07 March 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Fixed installer error if the `Mods` folder doesn't exist in 3.9.3.
|
||||
|
||||
## 3.9.3
|
||||
Released 07 March 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Added descriptive error if possible when a `PathTooLongException` crashes SMAPI or the installer.
|
||||
* The installer window now tries to stay open if it crashed, so you can read the error and ask for help.
|
||||
* Fixed console showing _found 1 mod with warnings_ with no mods listed in some cases.
|
||||
|
||||
* For mod authors:
|
||||
* Added three stages to the specialised [`LoadStageChanged` event](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Specialised): `CreatedInitialLocations`, `SaveAddedLocations`, and `ReturningToTitle`.
|
||||
* Fixed `RewriteMods` option ignored when rewriting for OS compatibility.
|
||||
* Fixed edge case when playing as a farmhand in non-English where translatable assets loaded via `IAssetLoader` weren't reapplied immediately when the server disconnects.
|
||||
|
||||
* For the web UI:
|
||||
* Updated the JSON validator/schema for Content Patcher 1.21.
|
||||
|
||||
## 3.9.2
|
||||
Released 21 February 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Added more aggressive memory optimization to reduce `OutOfMemoryException` errors with some mods.
|
||||
* Improved error when `Stardew Valley.exe` exists but can't be loaded.
|
||||
* Fixed error running `install on Windows.bat` in very rare cases.
|
||||
* Fixed `world_settime` command not always updating outdoor ambient lighting _(in Console Commands)_.
|
||||
|
||||
* For mod authors:
|
||||
* Added early detection of disposed textures so the error details are more relevant _(in Error Handler)_.
|
||||
* Added error details when an event command fails _(in Error Handler)_.
|
||||
* Fixed asset propagation for `TileSheets/ChairTiles` not changing existing map seats.
|
||||
* Fixed edge case when playing in non-English where translatable assets loaded via `IAssetLoader` would no longer be applied after returning to the title screen unless manually invalidated from the cache.
|
||||
|
||||
* For the web UI:
|
||||
* Updated compatibility list for the new wiki.
|
||||
* Updated the JSON validator/schema for Content Patcher 1.20.
|
||||
* Fixed mod compatibility list error if a mod has no name.
|
||||
|
||||
* For SMAPI developers:
|
||||
* Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately.
|
||||
|
||||
## 3.9.1
|
||||
Released 25 January 2021 for Stardew Valley 1.5.4.
|
||||
|
||||
* For players:
|
||||
* Fixed _tile contains an invalid TileSheet reference_ crash after mods change certain maps.
|
||||
* Fixed _patched game code_ issue shown for the bundled Error Handler mod.
|
||||
|
||||
## 3.9
|
||||
Released 22 January 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/46553874).
|
||||
|
||||
* For players:
|
||||
* Updated for Stardew Valley 1.5.4.
|
||||
* Improved game detection in the installer:
|
||||
* The installer now prefers paths registered by Steam or GOG Galaxy.
|
||||
* The installer now detects default manual GOG installs.
|
||||
* Added clearer error text for empty mod folders created by Vortex.
|
||||
* Fixed the game's map changes not always reapplied correctly after mods change certain maps, which caused issues like the community center resetting to its non-repaired texture.
|
||||
* Fixed compatibility for very old content packs which still load maps from `.xnb` files. These were broken by map loading changes in Stardew Valley 1.5, but SMAPI now corrects them automatically.
|
||||
* Fixed some broken mods incorrectly listed as XNB mods under 'skipped mods'.
|
||||
|
||||
* For mod authors:
|
||||
* Added new input APIs:
|
||||
* Added an [API for multi-key bindings](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Input#KeybindList).
|
||||
* Added a new [`Input.ButtonsChanged` event](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Input.ButtonsChanged).
|
||||
* Added a `buttonState.IsDown()` extension.
|
||||
* Added a `helper.Input.SuppressActiveKeybinds` method to suppress the active buttons in a keybind list.
|
||||
* Improved multiplayer APIs:
|
||||
* `PerScreen<T>` now lets you get/set the value for any screen, get all active values, or clear all values.
|
||||
* Peer data from the multiplayer API/events now includes `IsSplitScreen` and `ScreenID` fields.
|
||||
* Fixed network messages through the multiplayer API being sent to players who don't have SMAPI installed in some cases.
|
||||
* Improved asset propagation:
|
||||
* Updated map propagation for the changes in Stardew Valley 1.5.4.
|
||||
* Added propagation for some `Strings\StringsFromCSFiles` keys (mainly short day names).
|
||||
* Fixed quarry bridge not fixed if the mountain map was reloaded.
|
||||
* Added an option to disable rewriting mods for compatibility (thanks to Bpendragon!). This prevents older mods from loading, but bypasses a Visual Studio crash when debugging.
|
||||
* Game errors shown in the chatbox are now logged.
|
||||
* Moved vanilla error-handling into a new Error Handler mod. This simplifies the core SMAPI logic, and lets users disable it if needed.
|
||||
|
||||
* For the Console Commands mod:
|
||||
* Removed the `inf` option for `player_sethealth`, `player_setmoney`, and `player_setstamina`. You can use mods like [CJB Cheats Menu](https://www.nexusmods.com/stardewvalley/mods/4) instead for that.
|
||||
|
||||
* For the Error Handler mod:
|
||||
* Added a detailed message for the _Input string was not in a correct format_ error when the game fails to parse an item text description.
|
||||
|
||||
* For the web UI:
|
||||
* Fixed JSON validator incorrectly marking some manifest update keys as invalid.
|
||||
|
||||
## 3.8.4
|
||||
Released 15 January 2021 for Stardew Valley 1.5.3 or later.
|
||||
|
||||
* For players:
|
||||
* Updated for Stardew Valley 1.5.3.
|
||||
* Fixed issue where title screen music didn't stop after loading a save.
|
||||
|
||||
* For mod authors:
|
||||
* Fixed `SemanticVersion` comparisons returning wrong value in rare cases.
|
||||
|
||||
## 3.8.3
|
||||
Released 08 January 2021 for Stardew Valley 1.5.2 or later.
|
||||
|
||||
* For players:
|
||||
* Updated for Stardew Valley 1.5.2.
|
||||
* Reduced memory usage.
|
||||
* You can now enter console commands for a specific screen in split-screen mode by adding `screen=ID` to the command.
|
||||
* Typing `help` in the SMAPI console is now more helpful.
|
||||
|
||||
* For mod authors:
|
||||
* Simplified tilesheet order warning added in SMAPI 3.8.2.
|
||||
|
||||
* For the Console Commands mod:
|
||||
* Removed experimental `performance` command. Unfortunately this impacted SMAPI's memory usage and performance, and the data was often misinterpreted. This may be replaced with more automatic performance alerts in a future version.
|
||||
|
||||
## 3.8.2
|
||||
Released 03 January 2021 for Stardew Valley 1.5.1 or later.
|
||||
|
||||
* For players:
|
||||
* SMAPI now blocks farm map replacements that would crash the game in Stardew Valley 1.5.
|
||||
* On Linux, the SMAPI installer now auto-detects Flatpak Steam paths.
|
||||
* Updated compatibility list.
|
||||
* Fixed errors when multiple players join in split-screen mode.
|
||||
* Fixed 'skipped mods' section repeating mods in some cases.
|
||||
* Fixed out-of-date error text.
|
||||
|
||||
* For mod authors:
|
||||
* Added warning when a map replacement changes the order/IDs of the original tilesheets, which may cause errors and crashes. Doing so for a farm map is blocked outright since that causes a consistent crash in Stardew Valley 1.5.
|
||||
* Message data from the `ModMessageReceived` event now uses the same serializer settings as the rest of SMAPI. That mainly adds support for sending crossplatform `Color`, `Point`, `Vector2`, `Rectangle`, and `SemanticVersion` fields through network messages.
|
||||
* When a mod is blocked by SMAPI's compatibility override list, the `TRACE` messages while loading it now say so and indicate why.
|
||||
* Fixed how the input API handles UI scaling. This mainly affects `ICursorPosition` values returned by the API; see [the wiki docs](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Input#ICursorPosition) for how to account for UI scaling.
|
||||
|
||||
## 3.8.1
|
||||
Released 26 December 2020 for Stardew Valley 1.5.1 or later.
|
||||
|
||||
* For players:
|
||||
* Fixed broken community center bundles for non-English saves created in Stardew Valley 1.5. Affected saves will be fixed automatically on load.
|
||||
|
||||
* For mod authors:
|
||||
* World events are now raised for volcano dungeon levels.
|
||||
* Added `apply_save_fix` command to reapply a save migration in exceptional cases. This should be used very carefully. Type `help apply_save_fix` for details.
|
||||
* **Deprecation notice:** the `Helper.ConsoleCommands.Trigger` method is now deprecated and should no longer be used. See [integration APIs](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations) for better mod integration options. It will eventually be removed in SMAPI 4.0.
|
||||
|
||||
For the web UI:
|
||||
* Fixed edge cases in SMAPI log parsing.
|
||||
|
||||
## 3.8
|
||||
Released 21 December 2020 for Stardew Valley 1.5 or later. See [release highlights](https://www.patreon.com/posts/45294737).
|
||||
|
||||
* For players:
|
||||
* Updated for Stardew Valley 1.5, including split-screen support.
|
||||
* You can now run the installer from a subfolder of your game folder to auto-detect it. That simplifies installation if you have multiple copies of the game or it can't otherwise auto-detect the game path.
|
||||
* Clarified error when the SMAPI installer is in the `Mods` folder.
|
||||
|
||||
* For mod authors:
|
||||
* Added `PerScreen<T>` utility and new `Context` fields to simplify split-screen support in mods.
|
||||
* Added screen ID to log when playing in split-screen mode.
|
||||
|
||||
* For the Console Commands mod:
|
||||
* Added `furniture` option to `world_clear`.
|
||||
|
||||
* For the web UI:
|
||||
* Updated the JSON validator/schema for Content Patcher 1.19.
|
||||
|
||||
## 3.7.6
|
||||
Released 21 November 2020 for Stardew Valley 1.4.1 or later.
|
||||
|
||||
|
@ -14,7 +762,7 @@ Released 21 November 2020 for Stardew Valley 1.4.1 or later.
|
|||
* Fixed error when heuristically rewriting an outdated mod in rare cases.
|
||||
* Fixed rare 'collection was modified' error when using `harmony summary` console command.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Updated TMXTile to 1.5.8 to fix exported `.tmx` files losing tile index properties.
|
||||
|
||||
* For the Console Commands mod:
|
||||
|
@ -24,7 +772,7 @@ Released 21 November 2020 for Stardew Valley 1.4.1 or later.
|
|||
## 3.7.5
|
||||
Released 16 October 2020 for Stardew Valley 1.4.1 or later.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Fixed changes to the town map asset not reapplying the game's community center, JojaMart, and Pam house changes.
|
||||
|
||||
## 3.7.4
|
||||
|
@ -34,7 +782,7 @@ Released 03 October 2020 for Stardew Valley 1.4.1 or later.
|
|||
* Improved performance on some older computers (thanks to millerscout!).
|
||||
* Fixed update alerts for Chucklefish forum mods broken by a recent site change.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Updated dependencies (including Mono.Cecil 0.11.2 → 0.11.3 and Platonymous.TMXTile 1.3.8 → 1.5.6).
|
||||
* Fixed asset propagation for `Data\MoviesReactions`.
|
||||
* Fixed error in content pack path handling when you pass a null path.
|
||||
|
@ -49,11 +797,11 @@ Released 03 October 2020 for Stardew Valley 1.4.1 or later.
|
|||
Released 16 September 2020 for Stardew Valley 1.4.1 or later.
|
||||
|
||||
* For players:
|
||||
* Fixed errors on Linux/Mac due to content packs with incorrect filename case.
|
||||
* Fixed errors on Linux/macOS due to content packs with incorrect filename case.
|
||||
* Fixed map rendering crash due to conflict between SMAPI and PyTK.
|
||||
* Fixed error in heuristically-rewritten mods in rare cases (thanks to collaboration with ZaneYork!).
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* File paths accessed through `IContentPack` are now case-insensitive (even on Linux).
|
||||
|
||||
* For the web UI:
|
||||
|
@ -65,7 +813,7 @@ Released 08 September 2020 for Stardew Valley 1.4.1 or later.
|
|||
* For players:
|
||||
* Fixed mod recipe changes not always applied in 3.7.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Renamed `PathUtilities.NormalizePathSeparators` to `NormalizePath`, and added normalization for more cases.
|
||||
|
||||
## 3.7.1
|
||||
|
@ -90,10 +838,10 @@ Released 07 September 2020 for Stardew Valley 1.4.1 or later. See [release highl
|
|||
* Removed the experimental `RewriteInParallel` option added in SMAPI 3.6 (it was already disabled by default). Unfortunately this caused intermittent and unpredictable errors when enabled.
|
||||
* Internal changes to prepare for upcoming game updates.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added `PathUtilities` to simplify working with file/asset names.
|
||||
* You can now read/write `SDate` values to JSON (e.g. for `config.json`, network mod messages, etc).
|
||||
* Fixed asset propagation not updating title menu buttons immediately on Linux/Mac.
|
||||
* Fixed asset propagation not updating title menu buttons immediately on Linux/macOS.
|
||||
|
||||
* For the web UI:
|
||||
* Updated the JSON validator/schema for Content Patcher 1.16 and 1.17.
|
||||
|
@ -120,7 +868,7 @@ Released 02 August 2020 for Stardew Valley 1.4.1 or later.
|
|||
* Fixed spawned Floor TV not functional as a TV (thanks to Platonymous!).
|
||||
* Fixed spawned sturgeon roe having incorrect color.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Updated internal dependencies.
|
||||
* SMAPI now ignores more file types when scanning for mod folders (`.doc`, `.docx`, `.rar`, and `.zip`).
|
||||
* Added current GPU to trace logs to simplify troubleshooting.
|
||||
|
@ -138,7 +886,7 @@ Released 20 June 2020 for Stardew Valley 1.4.1 or later. See [release highlights
|
|||
* Added experimental option to reduce startup time when loading mod DLLs (thanks to ZaneYork!). Enable `RewriteInParallel` in the `smapi-internal/config.json` to try it.
|
||||
* Reduced processing time when a mod loads many unpacked images (thanks to Entoarox!).
|
||||
* Mod load warnings are now listed alphabetically.
|
||||
* MacOS files starting with `._` are now ignored and can no longer cause skipped mods.
|
||||
* macOS files starting with `._` are now ignored and can no longer cause skipped mods.
|
||||
* Simplified paranoid warning logs and reduced their log level.
|
||||
* Fixed black maps on Android for mods which use `.tmx` files.
|
||||
* Fixed `BadImageFormatException` error detection.
|
||||
|
@ -153,7 +901,7 @@ Released 20 June 2020 for Stardew Valley 1.4.1 or later. See [release highlights
|
|||
* Updated ModDrop URLs.
|
||||
* Internal changes to improve performance and reliability.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added [event priorities](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Custom_priority) (thanks to spacechase0!).
|
||||
* Added [update subkeys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks#Update_subkeys).
|
||||
* Added [a custom build of Harmony](https://github.com/Pathoschild/Harmony#readme) to provide more useful stack traces in error logs.
|
||||
|
@ -188,13 +936,13 @@ Released 27 April 2020 for Stardew Valley 1.4.1 or later. See [release highlight
|
|||
* Updated the JSON validator/schema for Content Patcher 1.13.
|
||||
* Fixed rare intermittent "CGI application encountered an error" errors.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added map patching to the content API (via `asset.AsMap()`).
|
||||
* Added support for using patch helpers with arbitrary data (via `helper.Content.GetPatchHelper`).
|
||||
* Added `SDate` fields/methods: `SeasonIndex`, `FromDaysSinceStart`, `FromWorldDate`, `ToWorldDate`, and `ToLocaleString` (thanks to kdau!).
|
||||
* Added `SDate` translations taken from the Lookup Anything mod.¹
|
||||
* Fixed asset propagation for certain maps loaded through temporary content managers. This notably fixes unreliable patches to the farmhouse and town maps.
|
||||
* Fixed asset propagation on Linux/Mac for monster sprites, NPC dialogue, and NPC schedules.
|
||||
* Fixed asset propagation on Linux/macOS for monster sprites, NPC dialogue, and NPC schedules.
|
||||
* Fixed asset propagation for NPC dialogue sometimes causing a spouse to skip marriage dialogue or not allow kisses.
|
||||
|
||||
¹ Date format translations were taken from the Lookup Anything mod; thanks to translators FixThisPlz (improved Russian), LeecanIt (added Italian), pomepome (added Japanese), S2SKY (added Korean), Sasara (added German), SteaNN (added Russian), ThomasGabrielDelavault (added Spanish), VincentRoth (added French), Yllelder (improved Spanish), and yuwenlan (added Chinese). Some translations for Korean, Hungarian, and Turkish were derived from the game translations.
|
||||
|
@ -202,7 +950,7 @@ Released 27 April 2020 for Stardew Valley 1.4.1 or later. See [release highlight
|
|||
## 3.4.1
|
||||
Released 24 March 2020 for Stardew Valley 1.4.1 or later.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Asset changes now propagate to NPCs in an event (e.g. wedding sprites).
|
||||
* Fixed mouse input suppression not working in SMAPI 3.4.
|
||||
|
||||
|
@ -210,12 +958,12 @@ Released 24 March 2020 for Stardew Valley 1.4.1 or later.
|
|||
Released 22 March 2020 for Stardew Valley 1.4.1 or later. See [release highlights](https://www.patreon.com/posts/35161371).
|
||||
|
||||
* For players:
|
||||
* Fixed semi-transparency issues on Linux/Mac in recent versions of Mono (e.g. pink shadows).
|
||||
* Fixed semi-transparency issues on Linux/macOS in recent versions of Mono (e.g. pink shadows).
|
||||
* Fixed `player_add` command error if you have broken XNB mods.
|
||||
* Removed invalid-location check now handled by the game.
|
||||
* Updated translations. Thanks to Annosz (added Hungarian)!
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added support for flipped and rotated map tiles (thanks to collaboration with Platonymous!).
|
||||
* Added support for `.tmx` maps using zlib compression (thanks to Platonymous!).
|
||||
* Added `this.Monitor.LogOnce` method.
|
||||
|
@ -252,14 +1000,14 @@ Released 22 February 2020 for Stardew Valley 1.4.1 or later. See [release highli
|
|||
* Updated translations. Thanks to xCarloC (added Italian)!
|
||||
|
||||
* For the Save Backup mod:
|
||||
* Fixed warning on MacOS when you have no saves yet.
|
||||
* Fixed warning on macOS when you have no saves yet.
|
||||
* Reduced log messages.
|
||||
|
||||
* For the web UI:
|
||||
* Updated the JSON validator and Content Patcher schema for `.tmx` support.
|
||||
* The mod compatibility page now has a sticky table header.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added support for [message sending](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Message_sending) to mods on the current computer (in addition to remote computers).
|
||||
* Added `ExtendImage` method to content API when editing files to resize textures.
|
||||
* Added `helper.Input.GetState` to get the low-level state of a button.
|
||||
|
@ -294,7 +1042,7 @@ Released 01 February 2020 for Stardew Valley 1.4.1 or later. See [release highli
|
|||
* Fixed extra files under `Saves` (e.g. manual backups) not being ignored.
|
||||
* Fixed Android issue where game files were backed up.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added support for `.tmx` map files. (Thanks to [Platonymous for the underlying library](https://github.com/Platonymous/TMXTile)!)
|
||||
* Added special handling for `Vector2` values in `.json` files, so they work consistently crossplatform.
|
||||
* Reworked the order that asset editors/loaders are called between multiple mods to support some framework mods like Content Patcher and Json Assets. Note that the order is undefined and should not be depended on.
|
||||
|
@ -343,7 +1091,7 @@ Released 05 January 2019 for Stardew Valley 1.4.1 or later. See [release highlig
|
|||
* Fixed log parser not correctly handling content packs with no author (thanks to danvolchek!).
|
||||
* Fixed main sidebar link pointing to wiki instead of home page.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* Added `World.ChestInventoryChanged` event (thanks to collaboration with wartech0!).
|
||||
* Added asset propagation for...
|
||||
* grass textures;
|
||||
|
@ -372,7 +1120,7 @@ Released 02 December 2019 for Stardew Valley 1.4 or later.
|
|||
* If a log can't be uploaded to Pastebin (e.g. due to rate limits), it's now uploaded to Amazon S3 instead. Logs uploaded to S3 expire after one month.
|
||||
* Fixed JSON validator not letting you drag & drop a file.
|
||||
|
||||
* For modders:
|
||||
* For mod authors:
|
||||
* `SemanticVersion` now supports [semver 2.0](https://semver.org/) build metadata.
|
||||
|
||||
## 3.0
|
||||
|
@ -402,7 +1150,7 @@ For players:
|
|||
|
||||
* **Fixed many bugs and edge cases.**
|
||||
|
||||
For modders:
|
||||
For mod authors:
|
||||
* **New event system.**
|
||||
SMAPI 3.0 removes the deprecated static events in favor of the new `helper.Events` API. The event
|
||||
engine is rewritten to make events more efficient, add events that weren't possible before, make
|
||||
|
@ -486,7 +1234,7 @@ For modders:
|
|||
* Added instructions for Android.
|
||||
* The page now detects your OS and preselects the right instructions (thanks to danvolchek!).
|
||||
|
||||
### For modders
|
||||
### For mod authors
|
||||
* Breaking changes:
|
||||
* Mods are now loaded much earlier in the game launch. This lets mods intercept any content asset, but the game is not fully initialized when `Entry` is called; use the `GameLaunched` event if you need to run code when the game is initialized.
|
||||
* Removed all deprecated APIs.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
← [SMAPI](../README.md)
|
||||
|
||||
The **mod build package** is an open-source NuGet package which automates the MSBuild configuration
|
||||
for SMAPI mods and related tools. The package is fully compatible with Linux, Mac, and Windows.
|
||||
for SMAPI mods and related tools. The package is fully compatible with Linux, macOS, and Windows.
|
||||
|
||||
## Contents
|
||||
* [Use](#use)
|
||||
|
@ -29,20 +29,19 @@ change how these work):
|
|||
* **Detect game path:**
|
||||
The package automatically finds your game folder by scanning the default install paths and
|
||||
Windows registry. It adds two MSBuild properties for use in your `.csproj` file if needed:
|
||||
`$(GamePath)` and `$(GameExecutableName)`.
|
||||
`$(GamePath)` and `$(GameModsPath)`.
|
||||
|
||||
* **Add assembly references:**
|
||||
The package adds assembly references to SMAPI, Stardew Valley, xTile, and the game framework
|
||||
(MonoGame on Linux/Mac, XNA Framework on Windows). It automatically adjusts depending on which OS
|
||||
you're compiling it on. If you use [Harmony](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Harmony),
|
||||
it can optionally add a reference to that too.
|
||||
The package adds assembly references to MonoGame, SMAPI, Stardew Valley, and xTile. It
|
||||
automatically adjusts depending on which OS you're compiling it on. If you use
|
||||
[Harmony](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Harmony), it can optionally add
|
||||
a reference to that too.
|
||||
|
||||
* **Copy files into the `Mods` folder:**
|
||||
The package automatically copies your mod's DLL and PDB files, `manifest.json`, [`i18n`
|
||||
files](https://stardewvalleywiki.com/Modding:Translations) (if any), the `assets` folder (if
|
||||
any), and [build output](https://stackoverflow.com/a/10828462/262123) into your game's `Mods`
|
||||
folder when you rebuild the code, with a subfolder matching the mod's project name. That lets you
|
||||
try the mod in-game right after building it.
|
||||
files](https://stardewvalleywiki.com/Modding:Translations) (if any), and the `assets` folder (if
|
||||
any) into the `Mods` folder when you rebuild the code, with a subfolder matching the mod's project
|
||||
name. That lets you try the mod in-game right after building it.
|
||||
|
||||
* **Create release zip:**
|
||||
The package adds a zip file in your project's `bin` folder when you rebuild the code, in the
|
||||
|
@ -55,7 +54,7 @@ change how these work):
|
|||
breakpoints](https://docs.microsoft.com/en-us/visualstudio/debugger/using-breakpoints?view=vs-2019)
|
||||
in your code while the game is running, or [make simple changes to the mod code without needing to
|
||||
restart the game](https://docs.microsoft.com/en-us/visualstudio/debugger/edit-and-continue?view=vs-2019).
|
||||
This is disabled on Linux/Mac due to limitations with the Mono wrapper.
|
||||
This is disabled on Linux/macOS due to limitations with the Mono wrapper.
|
||||
|
||||
* **Preconfigure common settings:**
|
||||
The package automatically enables `.pdb` files (so error logs show line numbers to simplify
|
||||
|
@ -82,7 +81,7 @@ There are two places you can put them:
|
|||
|
||||
1. Open the home folder on your computer (see instructions for
|
||||
[Linux](https://superuser.com/questions/409218/where-is-my-users-home-folder-in-ubuntu),
|
||||
[MacOS](https://www.cnet.com/how-to/how-to-find-your-macs-home-folder-and-add-it-to-finder/),
|
||||
[macOS](https://www.cnet.com/how-to/how-to-find-your-macs-home-folder-and-add-it-to-finder/),
|
||||
or [Windows](https://www.computerhope.com/issues/ch000109.htm)).
|
||||
2. Create a `stardewvalley.targets` file with this content:
|
||||
```xml
|
||||
|
@ -129,14 +128,6 @@ The absolute path to the folder containing the game's installed mods (defaults t
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GameExecutableName</code></td>
|
||||
<td>
|
||||
|
||||
The filename for the game's executable (i.e. `StardewValley.exe` on Linux/Mac or
|
||||
`Stardew Valley.exe` on Windows). This is auto-detected, and you should almost never change this.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</li>
|
||||
|
||||
|
@ -197,11 +188,63 @@ The folder path where the release zip is created (defaults to the project's `bin
|
|||
<th>effect</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>CopyModReferencesToBuildOutput</code></td>
|
||||
<td><code>BundleExtraAssemblies</code></td>
|
||||
<td>
|
||||
|
||||
Whether to copy game and framework DLLs into the mod folder (default `false`). This is useful for
|
||||
unit test projects, but not needed for mods that'll be run through SMAPI.
|
||||
**Most mods should not change this option.**
|
||||
|
||||
By default (when this is _not_ enabled), only the mod files [normally considered part of the
|
||||
mod](#Features) will be added to the release `.zip` and copied into the `Mods` folder (i.e.
|
||||
"deployed"). That includes the assembly files (`*.dll`, `*.pdb`, and `*.xml`) for your mod project,
|
||||
but any other DLLs won't be deployed.
|
||||
|
||||
Enabling this option will add _all_ dependencies to the build output, then deploy _some_ of them
|
||||
depending on the comma-separated value(s) you set:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>option</th>
|
||||
<th>result</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ThirdParty</code></td>
|
||||
<td>
|
||||
|
||||
Assembly files which don't match any other category.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>System</code></td>
|
||||
<td>
|
||||
|
||||
Assembly files whose names start with `Microsoft.*` or `System.*`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Game</code></td>
|
||||
<td>
|
||||
|
||||
Assembly files which are part of MonoGame, SMAPI, or Stardew Valley.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>All</code></td>
|
||||
<td>
|
||||
|
||||
Equivalent to `System, Game, ThirdParty`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Most mods should omit the option. Some mods may need `ThirdParty` if they bundle third-party DLLs
|
||||
with their mod. The other options are mainly useful for unit tests.
|
||||
|
||||
When enabling this option, you should **manually review which files get deployed** and use the
|
||||
`IgnoreModFilePaths` or `IgnoreModFilePatterns` options to exclude files as needed.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -213,6 +256,20 @@ Whether to configure the project so you can launch or debug the game through the
|
|||
Visual Studio (default `true`). There's usually no reason to change this, unless it's a unit test
|
||||
project.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>IgnoreModFilePaths</code></td>
|
||||
<td>
|
||||
|
||||
A comma-delimited list of literal file paths to ignore, relative to the mod's `bin` folder. Paths
|
||||
are case-sensitive, but path delimiters are normalized automatically. For example, this ignores a
|
||||
set of tilesheets:
|
||||
|
||||
```xml
|
||||
<IgnoreModFilePaths>assets/paths.png, assets/springobjects.png</IgnoreModFilePaths>
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -291,6 +348,15 @@ Warning text:
|
|||
|
||||
Your code accesses a field which is obsolete or no longer works. Use the suggested field instead.
|
||||
|
||||
### Wrong processor architecture
|
||||
Warning text:
|
||||
> The target platform should be set to 'Any CPU' for compatibility with both 32-bit and 64-bit
|
||||
> versions of Stardew Valley (currently set to '{{current platform}}').
|
||||
|
||||
Mods can be used in either 32-bit or 64-bit mode. Your project's target platform isn't set to the
|
||||
default 'Any CPU', so it won't work in both. You can fix it by [setting the target platform to
|
||||
'Any CPU'](https://docs.microsoft.com/en-ca/visualstudio/ide/how-to-configure-projects-to-target-platforms).
|
||||
|
||||
## FAQs
|
||||
### How do I set the game path?<span id="custom-game-path"></span>
|
||||
The package detects where your game is installed automatically, so you usually don't need to set it
|
||||
|
@ -306,22 +372,21 @@ To do that:
|
|||
<GamePath>PATH_HERE</GamePath>
|
||||
</PropertyGroup>
|
||||
```
|
||||
3. Replace `PATH_HERE` with your game's folder path.
|
||||
3. Replace `PATH_HERE` with your game's folder path (don't add quotes).
|
||||
|
||||
The configuration will check your custom path first, then fall back to the default paths (so it'll
|
||||
still compile on a different computer).
|
||||
|
||||
### How do I change which files are included in the mod deploy/zip?
|
||||
For custom files, you can [add/remove them in the build output](https://stackoverflow.com/a/10828462/262123).
|
||||
(If your project references another mod, make sure the reference is [_not_ marked 'copy
|
||||
local'](https://msdn.microsoft.com/en-us/library/t1zz5y8c(v=vs.100).aspx).)
|
||||
|
||||
To exclude a file the package copies by default, see `IgnoreModFilePatterns` under
|
||||
[_configure_](#configure).
|
||||
* For normal files, you can [add/remove them in the build output](https://stackoverflow.com/a/10828462/262123).
|
||||
* For assembly files (`*.dll`, `*.exe`, `*.pdb`, or `*.xml`), see the
|
||||
[`BundleExtraAssemblies` option](#configure).
|
||||
* To exclude a file which the package copies by default, see the [`IgnoreModFilePaths` or
|
||||
`IgnoreModFilePatterns` options](#configure).
|
||||
|
||||
### Can I use the package for non-mod projects?
|
||||
You can use the package in non-mod projects too (e.g. unit tests or framework DLLs). Just disable
|
||||
the mod-related package features (see [_configure_](#configure)):
|
||||
Yep, this works in unit tests and framework projects too. Just disable the mod-related package
|
||||
features (see [_configure_](#configure)):
|
||||
|
||||
```xml
|
||||
<EnableGameDebugging>false</EnableGameDebugging>
|
||||
|
@ -329,9 +394,9 @@ the mod-related package features (see [_configure_](#configure)):
|
|||
<EnableModZip>false</EnableModZip>
|
||||
```
|
||||
|
||||
If you need to copy the referenced DLLs into your build output, add this too:
|
||||
To copy referenced DLLs into your build output for unit tests, add this too:
|
||||
```xml
|
||||
<CopyModReferencesToBuildOutput>true</CopyModReferencesToBuildOutput>
|
||||
<BundleExtraAssemblies>All</BundleExtraAssemblies>
|
||||
```
|
||||
|
||||
## For SMAPI developers
|
||||
|
@ -347,11 +412,63 @@ The NuGet package is generated automatically in `StardewModdingAPI.ModBuildConfi
|
|||
when you compile it.
|
||||
|
||||
## Release notes
|
||||
## 3.2.2
|
||||
### 4.1.0
|
||||
Released 08 January 2023.
|
||||
|
||||
* Added `manifest.json` format validation on build (thanks to tylergibbs2!).
|
||||
* Fixed game DLLs not excluded from the release zip when they're referenced explicitly but `BundleExtraAssemblies` isn't set.
|
||||
|
||||
### 4.0.2
|
||||
Released 09 October 2022.
|
||||
|
||||
* Switched to the newer crossplatform `portable` debug symbols (thanks to lanturnalis!).
|
||||
* Fixed `BundleExtraAssemblies` option being partly case-sensitive.
|
||||
* Fixed `BundleExtraAssemblies` not applying `All` value to game assemblies.
|
||||
|
||||
### 4.0.1
|
||||
Released 14 April 2022.
|
||||
|
||||
* Added detection for Xbox app game folders.
|
||||
* Fixed "_conflicts between different versions of Microsoft.Win32.Registry_" warnings in recent SMAPI versions.
|
||||
* Internal refactoring.
|
||||
|
||||
### 4.0.0
|
||||
Released 30 November 2021.
|
||||
|
||||
* Updated for Stardew Valley 1.5.5 and SMAPI 3.13.0. (Older versions are no longer supported.)
|
||||
* Added `IgnoreModFilePaths` option to ignore literal paths.
|
||||
* Added `BundleExtraAssemblies` option to copy bundled DLLs into the mod zip/folder.
|
||||
* Removed the `GameExecutableName` and `GameFramework` options (since they now have the same value
|
||||
on all platforms).
|
||||
* Removed the `CopyModReferencesToBuildOutput` option (superseded by `BundleExtraAssemblies`).
|
||||
* Improved analyzer performance by enabling parallel execution.
|
||||
|
||||
**Migration guide for mod authors:**
|
||||
1. See [_migrate to 64-bit_](https://stardewvalleywiki.com/Modding:Migrate_to_64-bit_on_Windows) and
|
||||
[_migrate to Stardew Valley 1.5.5_](https://stardewvalleywiki.com/Modding:Migrate_to_Stardew_Valley_1.5.5).
|
||||
2. Possible changes in your `.csproj` or `.targets` files:
|
||||
* Replace `$(GameExecutableName)` with `Stardew Valley`.
|
||||
* Replace `$(GameFramework)` with `MonoGame` and remove any XNA Framework-specific logic.
|
||||
* Replace `<CopyModReferencesToBuildOutput>true</CopyModReferencesToBuildOutput>` with
|
||||
`<BundleExtraAssemblies>Game</BundleExtraAssemblies>`.
|
||||
* If you need to bundle extra DLLs besides your mod DLL, see the [`BundleExtraAssemblies`
|
||||
documentation](#configure).
|
||||
|
||||
### 3.3.0
|
||||
Released 30 March 2021.
|
||||
|
||||
* Added a build warning when the mod isn't compiled for `Any CPU`.
|
||||
* Added a `GameFramework` build property set to `MonoGame` or `Xna` based on the platform. This can
|
||||
be overridden to change which framework it references.
|
||||
* Added support for building mods against the 64-bit Linux version of the game on Windows.
|
||||
* The package now suppresses the misleading 'processor architecture mismatch' warnings.
|
||||
|
||||
### 3.2.2
|
||||
Released 23 September 2020.
|
||||
|
||||
* Reworked and streamlined how the package is compiled.
|
||||
* Added [SMAPI-ModTranslationClassBuilder](https://github.com/Pathoschild/SMAPI-ModTranslationClassBuilder) files to the ignore list.
|
||||
* Added [SMAPI-ModTranslationClassBuilder](https://github.com/Pathoschild/SMAPI-ModTranslationClassBuilder)
|
||||
files to the ignore list.
|
||||
|
||||
### 3.2.1
|
||||
Released 11 September 2020.
|
||||
|
@ -359,19 +476,19 @@ Released 11 September 2020.
|
|||
* Added more detailed logging.
|
||||
* Fixed _path's format is not supported_ error when using default `Mods` path in 3.2.
|
||||
|
||||
### 3.2
|
||||
### 3.2.0
|
||||
Released 07 September 2020.
|
||||
|
||||
* Added option to change `Mods` folder path.
|
||||
* Rewrote documentation to make it easier to read.
|
||||
|
||||
### 3.1
|
||||
### 3.1.0
|
||||
Released 01 February 2020.
|
||||
|
||||
* Added support for semantic versioning 2.0.
|
||||
* `0Harmony.dll` is now ignored if the mod references Harmony directly (it's bundled with SMAPI).
|
||||
|
||||
### 3.0
|
||||
### 3.0.0
|
||||
Released 26 November 2019.
|
||||
|
||||
* Updated for SMAPI 3.0 and Stardew Valley 1.4.
|
||||
|
@ -386,14 +503,14 @@ Released 26 November 2019.
|
|||
* Dropped support for older versions of SMAPI and Visual Studio.
|
||||
* Migrated package icon to NuGet's new format.
|
||||
|
||||
### 2.2
|
||||
### 2.2.0
|
||||
Released 28 October 2018.
|
||||
|
||||
* Added support for SMAPI 2.8+ (still compatible with earlier versions).
|
||||
* Added default game paths for 32-bit Windows.
|
||||
* Fixed valid manifests marked invalid in some cases.
|
||||
|
||||
### 2.1
|
||||
### 2.1.0
|
||||
Released 27 July 2018.
|
||||
|
||||
* Added support for Stardew Valley 1.3.
|
||||
|
@ -413,7 +530,7 @@ Released 11 October 2017.
|
|||
|
||||
* Fixed mod deploy failing to create subfolders if they don't already exist.
|
||||
|
||||
### 2.0
|
||||
### 2.0.0
|
||||
Released 11 October 2017.
|
||||
|
||||
* Added: mods are now copied into the `Mods` folder automatically (configurable).
|
||||
|
@ -431,7 +548,7 @@ Released 28 July 2017.
|
|||
* The manifest/i18n files in the project now take precedence over those in the build output if both
|
||||
are present.
|
||||
|
||||
### 1.7
|
||||
### 1.7.0
|
||||
Released 28 July 2017.
|
||||
|
||||
* Added option to create release zips on build.
|
||||
|
@ -448,19 +565,19 @@ Released 09 July 2017.
|
|||
|
||||
* Improved crossplatform game path detection.
|
||||
|
||||
### 1.6
|
||||
### 1.6.0
|
||||
Released 05 June 2017.
|
||||
|
||||
* Added support for deploying mod files into `Mods` automatically.
|
||||
* Added a build error if a game folder is found, but doesn't contain Stardew Valley or SMAPI.
|
||||
|
||||
### 1.5
|
||||
### 1.5.0
|
||||
Released 23 January 2017.
|
||||
|
||||
* Added support for setting a custom game path globally.
|
||||
* Added default GOG path on Mac.
|
||||
* Added default GOG path on macOS.
|
||||
|
||||
### 1.4
|
||||
### 1.4.0
|
||||
Released 11 January 2017.
|
||||
|
||||
* Fixed detection of non-default game paths on 32-bit Windows.
|
||||
|
@ -468,22 +585,22 @@ Released 11 January 2017.
|
|||
* Removed support for overriding the target platform (no longer needed since SMAPI crossplatforms
|
||||
mods automatically).
|
||||
|
||||
### 1.3
|
||||
### 1.3.0
|
||||
Released 31 December 2016.
|
||||
|
||||
* Added support for non-default game paths on Windows.
|
||||
|
||||
### 1.2
|
||||
### 1.2.0
|
||||
Released 24 October 2016.
|
||||
|
||||
* Exclude game binaries from mod build output.
|
||||
|
||||
### 1.1
|
||||
### 1.1.0
|
||||
Released 21 October 2016.
|
||||
|
||||
* Added support for overriding the target platform.
|
||||
|
||||
### 1.0
|
||||
### 1.0.0
|
||||
Released 21 October 2016.
|
||||
|
||||
* Initial release.
|
||||
|
|
|
@ -11,11 +11,12 @@ This document is about SMAPI itself; see also [mod build package](mod-package.md
|
|||
* [Configuration file](#configuration-file)
|
||||
* [Command-line arguments](#command-line-arguments)
|
||||
* [Compile flags](#compile-flags)
|
||||
* [For SMAPI developers](#for-smapi-developers)
|
||||
* [Compiling from source](#compiling-from-source)
|
||||
* [Debugging a local build](#debugging-a-local-build)
|
||||
* [Preparing a release](#preparing-a-release)
|
||||
* [Using a custom Harmony build](#using-a-custom-harmony-build)
|
||||
* [Compile from source code](#compile-from-source-code)
|
||||
* [Main project](#main-project)
|
||||
* [Custom Harmony build](#custom-harmony-build)
|
||||
* [Prepare a release](#prepare-a-release)
|
||||
* [On any platform](#on-any-platform)
|
||||
* [On Windows](#on-windows)
|
||||
* [Release notes](#release-notes)
|
||||
|
||||
## Customisation
|
||||
|
@ -32,24 +33,28 @@ argument | purpose
|
|||
`--uninstall` | Preselects the uninstall action, skipping the prompt asking what the user wants to do.
|
||||
`--game-path "path"` | Specifies the full path to the folder containing the Stardew Valley executable, skipping automatic detection and any prompt to choose a path. If the path is not valid, the installer displays an error.
|
||||
|
||||
SMAPI itself recognises two arguments **on Windows only**, but these are intended for internal use
|
||||
or testing and may change without warning. On Linux/Mac, see _environment variables_ below.
|
||||
SMAPI itself recognises five arguments, but these are meant for internal use or testing, and might
|
||||
change without warning. **On Linux/macOS**, command-line arguments won't work; see _environment
|
||||
variables_ below instead.
|
||||
|
||||
argument | purpose
|
||||
-------- | -------
|
||||
`--no-terminal` | SMAPI won't write anything to the console window. (Messages will still be written to the log file.)
|
||||
`--developer-mode`<br />`--developer-mode-off` | Enable or disable features intended for mod developers. Currently this only makes `TRACE`-level messages appear in the console.
|
||||
`--no-terminal` | SMAPI won't log anything to the console. On Linux/macOS only, this will also prevent the launch script from trying to open a terminal window. (Messages will still be written to the log file.)
|
||||
`--use-current-shell` | On Linux/macOS only, the launch script won't try to open a terminal window. All console output will be sent to the shell running the launch script.
|
||||
`--mods-path` | The path to search for mods, if not the standard `Mods` folder. This can be a path relative to the game folder (like `--mods-path "Mods (test)"`) or an absolute path.
|
||||
|
||||
### Environment variables
|
||||
The above SMAPI arguments don't work on Linux/Mac due to the way the game launcher works. You can
|
||||
set temporary environment variables instead. For example:
|
||||
The above SMAPI arguments may not work on Linux/macOS due to the way the game launcher works. You
|
||||
can set temporary environment variables instead. For example:
|
||||
> SMAPI_MODS_PATH="Mods (multiplayer)" /path/to/StardewValley
|
||||
|
||||
environment variable | purpose
|
||||
-------------------- | -------
|
||||
`SMAPI_NO_TERMINAL` | Equivalent to `--no-terminal` above.
|
||||
`SMAPI_DEVELOPER_MODE` | Equivalent to `--developer-mode` and `--developer-mode-off` above. The value must be `true` or `false`.
|
||||
`SMAPI_MODS_PATH` | Equivalent to `--mods-path` above.
|
||||
|
||||
`SMAPI_NO_TERMINAL` | Equivalent to `--no-terminal` above.
|
||||
`SMAPI_USE_CURRENT_SHELL` | Equivalent to `--use-current-shell` above.
|
||||
|
||||
### Compile flags
|
||||
SMAPI uses a small number of conditional compilation constants, which you can set by editing the
|
||||
|
@ -57,54 +62,114 @@ SMAPI uses a small number of conditional compilation constants, which you can se
|
|||
|
||||
flag | purpose
|
||||
---- | -------
|
||||
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`.
|
||||
`HARMONY_2` | Whether to enable experimental Harmony 2.0 support and rewrite existing Harmony 1._x_ mods for compatibility. Note that you need to replace `build/0Harmony.dll` with a Harmony 2.0 build (or switch to a package reference) to use this flag.
|
||||
`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.
|
||||
|
||||
## For SMAPI developers
|
||||
### Compiling from source
|
||||
## Compile from source code
|
||||
### Main project
|
||||
Using an official SMAPI release is recommended for most users, but you can compile from source
|
||||
directly if needed. There are no special steps (just open the project and compile), but SMAPI often
|
||||
uses the latest C# syntax. You may need the latest version of your IDE to compile it.
|
||||
directly if needed. Just open the project in an IDE like [Visual
|
||||
Studio](https://visualstudio.microsoft.com/vs/community/) or [Rider](https://www.jetbrains.com/rider/),
|
||||
and build the `SMAPI` project. The project will automatically adjust the build settings for your
|
||||
current OS and Stardew Valley install path.
|
||||
|
||||
SMAPI uses build configuration derived from the [crossplatform mod config](https://smapi.io/package/readme)
|
||||
to detect your current OS automatically and load the correct references. Compile output will be
|
||||
placed in a `bin` folder at the root of the Git repository.
|
||||
|
||||
### Debugging a local build
|
||||
Rebuilding the solution in debug mode will copy the SMAPI files into your game folder. Starting
|
||||
the `SMAPI` project with debugging from Visual Studio (on Mac or Windows) will launch SMAPI with
|
||||
the debugger attached, so you can intercept errors and step through the code being executed. That
|
||||
doesn't work in MonoDevelop on Linux, unfortunately.
|
||||
the `SMAPI` project with debugging from Visual Studio or Rider should launch SMAPI with the
|
||||
debugger attached, so you can intercept errors and step through the code being executed.
|
||||
|
||||
### Preparing a release
|
||||
To prepare a crossplatform SMAPI release, you'll need to compile it on two platforms. See
|
||||
[crossplatforming info](https://stardewvalleywiki.com/Modding:Modder_Guide/Test_and_Troubleshoot#Testing_on_all_platforms)
|
||||
on the wiki for the first-time setup.
|
||||
### Custom Harmony build
|
||||
SMAPI uses [a custom build of Harmony 2.2.2](https://github.com/Pathoschild/Harmony#readme), which
|
||||
is included in the `build` folder. To use a different build, just replace `0Harmony.dll` in that
|
||||
folder before compiling.
|
||||
|
||||
1. Update the version numbers in `build/common.targets`, `Constants`, and the `manifest.json` for
|
||||
bundled mods. Make sure you use a [semantic version](https://semver.org). Recommended format:
|
||||
## Prepare a release
|
||||
### On any platform
|
||||
**⚠ Ideally we'd have one set of instructions for all platforms. The instructions in this section
|
||||
will produce a fully functional release for all supported platforms, _except_ that the application
|
||||
icon for SMAPI on Windows will disappear due to [.NET runtime bug
|
||||
3828](https://github.com/dotnet/runtime/issues/3828). Until that's fixed, see the _[on
|
||||
Windows](#on-windows)_ section below to create a build that retains the icon.**
|
||||
|
||||
#### First-time setup
|
||||
1. On Windows only:
|
||||
1. [Install Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install).
|
||||
2. Run `sudo apt update` in WSL to update the package list.
|
||||
3. The rest of the instructions below should be run in WSL.
|
||||
2. Install the required software:
|
||||
1. Install the [.NET 5 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux-ubuntu).
|
||||
_For Ubuntu-based systems, you can run `lsb_release -a` to get the Ubuntu version number._
|
||||
2. [Install Steam](https://linuxconfig.org/how-to-install-steam-on-ubuntu-20-04-focal-fossa-linux).
|
||||
3. Launch `steam` and install the game like usual.
|
||||
4. Download and install your preferred IDE. For the [latest standalone Rider
|
||||
version](https://www.jetbrains.com/help/rider/Installation_guide.html#prerequisites):
|
||||
```sh
|
||||
wget "<download url here>" -O rider-install.tar.gz
|
||||
sudo tar -xzvf rider-install.tar.gz -C /opt
|
||||
ln -s "/opt/JetBrains Rider-<version>/bin/rider.sh"
|
||||
./rider.sh
|
||||
```
|
||||
3. Clone the SMAPI repo:
|
||||
```sh
|
||||
git clone https://github.com/Pathoschild/SMAPI.git
|
||||
```
|
||||
|
||||
### Launch the game
|
||||
1. Run these commands to start Steam:
|
||||
```sh
|
||||
export TERM=xterm
|
||||
steam
|
||||
```
|
||||
2. Launch the game through the Steam UI.
|
||||
|
||||
### Prepare the release
|
||||
1. Run `build/unix/prepare-install-package.sh VERSION_HERE` to create the release package in the
|
||||
root `bin` folder.
|
||||
|
||||
Make sure you use a [semantic version](https://semver.org). Recommended format:
|
||||
|
||||
build type | format | example
|
||||
:--------- | :----------------------- | :------
|
||||
dev build | `<version>-alpha.<date>` | `3.0.0-alpha.20171230`
|
||||
prerelease | `<version>-beta.<date>` | `3.0.0-beta.20171230`
|
||||
release | `<version>` | `3.0.0`
|
||||
dev build | `<version>-alpha.<date>` | `4.0.0-alpha.20251230`
|
||||
prerelease | `<version>-beta.<date>` | `4.0.0-beta.20251230`
|
||||
release | `<version>` | `4.0.0`
|
||||
|
||||
2. In Windows:
|
||||
1. Rebuild the solution with the _release_ solution configuration.
|
||||
2. Copy `bin/SMAPI installer` and `bin/SMAPI installer for developers` to Linux/Mac.
|
||||
### On Windows
|
||||
#### First-time setup
|
||||
1. Set up Windows Subsystem for Linux (WSL):
|
||||
1. [Install WSL](https://docs.microsoft.com/en-us/windows/wsl/install).
|
||||
2. Run `sudo apt update` in WSL to update the package list.
|
||||
3. The rest of the instructions below should be run in WSL.
|
||||
2. Install the required software:
|
||||
1. Install the [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0).
|
||||
2. Install [Stardew Valley](https://www.stardewvalley.net/).
|
||||
3. Clone the SMAPI repo:
|
||||
```sh
|
||||
git clone https://github.com/Pathoschild/SMAPI.git
|
||||
```
|
||||
|
||||
3. In Linux/Mac:
|
||||
1. Rebuild the solution with the _release_ solution configuration.
|
||||
2. Add the `windows-install.*` files from Windows to the `bin/SMAPI installer` and
|
||||
`bin/SMAPI installer for developers` folders compiled on Linux.
|
||||
3. Rename the folders to `SMAPI <version> installer` and `SMAPI <version> installer for developers`.
|
||||
4. Zip the two folders.
|
||||
### Prepare the release
|
||||
1. Run `build/windows/prepare-install-package.ps1 VERSION_HERE` in PowerShell to create the release
|
||||
package folders in the root `bin` folder.
|
||||
|
||||
### Custom Harmony build
|
||||
SMAPI uses [a custom build of Harmony](https://github.com/Pathoschild/Harmony#readme), which is
|
||||
included in the `build` folder. To use a different build, just replace `0Harmony.dll` in that
|
||||
folder before compiling.
|
||||
Make sure you use a [semantic version](https://semver.org). Recommended format:
|
||||
|
||||
build type | format | example
|
||||
:--------- | :----------------------- | :------
|
||||
dev build | `<version>-alpha.<date>` | `4.0.0-alpha.20251230`
|
||||
prerelease | `<version>-beta.<date>` | `4.0.0-beta.20251230`
|
||||
release | `<version>` | `4.0.0`
|
||||
|
||||
2. Launch WSL and run this script:
|
||||
```bash
|
||||
# edit to match the build created in steps 1
|
||||
# In WSL, `/mnt/c/example` accesses `C:\example` on the Windows filesystem.
|
||||
version="4.0.0"
|
||||
binFolder="/mnt/e/source/_Stardew/SMAPI/bin"
|
||||
build/windows/finalize-install-package.sh "$version" "$binFolder"
|
||||
```
|
||||
|
||||
Note: to prepare a test Windows-only build, you can pass `--windows-only` in the first step and
|
||||
skip the second one.
|
||||
|
||||
## Release notes
|
||||
See [release notes](../release-notes.md).
|
||||
|
|
|
@ -367,7 +367,7 @@ accordingly.
|
|||
Initial setup:
|
||||
|
||||
1. Create an Azure Blob storage account for uploaded files.
|
||||
2. Create an Azure App Services environment running the latest .NET Core on Linux or Windows.
|
||||
2. Create an Azure App Services environment running the latest .NET on Linux or Windows.
|
||||
3. Add these application settings in the new App Services environment:
|
||||
|
||||
property name | description
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
using System.IO;
|
||||
using StardewModdingAPI.Toolkit;
|
||||
using StardewModdingAPI.Toolkit.Framework.GameScanning;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
||||
namespace StardewModdingAPI.Installer.Framework
|
||||
{
|
||||
/// <summary>The installer context.</summary>
|
||||
internal class InstallerContext
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The underlying toolkit game scanner.</summary>
|
||||
private readonly GameScanner GameScanner = new();
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The current OS.</summary>
|
||||
public Platform Platform { get; }
|
||||
|
||||
/// <summary>The human-readable OS name and version.</summary>
|
||||
public string PlatformName { get; }
|
||||
|
||||
/// <summary>Whether the installer is running on Windows.</summary>
|
||||
public bool IsWindows => this.Platform == Platform.Windows;
|
||||
|
||||
/// <summary>Whether the installer is running on a Unix OS (including Linux or macOS).</summary>
|
||||
public bool IsUnix => !this.IsWindows;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public InstallerContext()
|
||||
{
|
||||
this.Platform = EnvironmentUtility.DetectPlatform();
|
||||
this.PlatformName = EnvironmentUtility.GetFriendlyPlatformName(this.Platform);
|
||||
}
|
||||
|
||||
/// <summary>Get the installer's version number.</summary>
|
||||
public ISemanticVersion GetInstallerVersion()
|
||||
{
|
||||
var raw = this.GetType().Assembly.GetName().Version!;
|
||||
return new SemanticVersion(raw);
|
||||
}
|
||||
|
||||
/// <summary>Get whether a folder seems to contain the game files.</summary>
|
||||
/// <param name="dir">The folder to check.</param>
|
||||
public bool LooksLikeGameFolder(DirectoryInfo dir)
|
||||
{
|
||||
return this.GameScanner.LooksLikeGameFolder(dir);
|
||||
}
|
||||
|
||||
/// <summary>Get whether a folder seems to contain the game, and which version it contains if so.</summary>
|
||||
/// <param name="dir">The folder to check.</param>
|
||||
public GameFolderType GetGameFolderType(DirectoryInfo dir)
|
||||
{
|
||||
return this.GameScanner.GetGameFolderType(dir);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using StardewModdingAPI.Toolkit.Framework;
|
||||
|
||||
namespace StardewModdingAPI.Installer.Framework
|
||||
{
|
||||
|
@ -44,17 +45,20 @@ namespace StardewModdingAPI.Installer.Framework
|
|||
/// <summary>The full path to the user's config overrides file.</summary>
|
||||
public string ApiUserConfigPath { get; }
|
||||
|
||||
/// <summary>The full path to the installed game DLL.</summary>
|
||||
public string GameDllPath { get; }
|
||||
|
||||
/// <summary>The full path to the installed SMAPI executable file.</summary>
|
||||
public string ExecutablePath { get; }
|
||||
public string UnixSmapiExecutablePath { get; }
|
||||
|
||||
/// <summary>The full path to the vanilla game launcher on Linux/Mac.</summary>
|
||||
public string UnixLauncherPath { get; }
|
||||
/// <summary>The full path to the vanilla game launch script on Linux/macOS.</summary>
|
||||
public string VanillaLaunchScriptPath { get; }
|
||||
|
||||
/// <summary>The full path to the installed SMAPI launcher on Linux/Mac before it's renamed.</summary>
|
||||
public string UnixSmapiLauncherPath { get; }
|
||||
/// <summary>The full path to the installed SMAPI launch script on Linux/macOS before it's renamed.</summary>
|
||||
public string NewLaunchScriptPath { get; }
|
||||
|
||||
/// <summary>The full path to the vanilla game launcher on Linux/Mac after SMAPI is installed.</summary>
|
||||
public string UnixBackupLauncherPath { get; }
|
||||
/// <summary>The full path to the backed up game launch script on Linux/macOS after SMAPI is installed.</summary>
|
||||
public string BackupLaunchScriptPath { get; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -63,19 +67,22 @@ namespace StardewModdingAPI.Installer.Framework
|
|||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="bundleDir">The directory path containing the files to copy into the game folder.</param>
|
||||
/// <param name="gameDir">The directory path for the installed game.</param>
|
||||
/// <param name="gameExecutableName">The name of the game's executable file for the current platform.</param>
|
||||
public InstallerPaths(DirectoryInfo bundleDir, DirectoryInfo gameDir, string gameExecutableName)
|
||||
public InstallerPaths(DirectoryInfo bundleDir, DirectoryInfo gameDir)
|
||||
{
|
||||
// base paths
|
||||
this.BundleDir = bundleDir;
|
||||
this.GameDir = gameDir;
|
||||
this.ModsDir = new DirectoryInfo(Path.Combine(gameDir.FullName, "Mods"));
|
||||
this.GameDllPath = Path.Combine(gameDir.FullName, Constants.GameDllName);
|
||||
|
||||
// launch scripts
|
||||
this.VanillaLaunchScriptPath = Path.Combine(gameDir.FullName, "StardewValley");
|
||||
this.NewLaunchScriptPath = Path.Combine(gameDir.FullName, "unix-launcher.sh");
|
||||
this.BackupLaunchScriptPath = Path.Combine(gameDir.FullName, "StardewValley-original");
|
||||
this.UnixSmapiExecutablePath = Path.Combine(gameDir.FullName, "StardewModdingAPI");
|
||||
|
||||
// internal files
|
||||
this.BundleApiUserConfigPath = Path.Combine(bundleDir.FullName, "smapi-internal", "config.user.json");
|
||||
|
||||
this.ExecutablePath = Path.Combine(gameDir.FullName, gameExecutableName);
|
||||
this.UnixLauncherPath = Path.Combine(gameDir.FullName, "StardewValley");
|
||||
this.UnixSmapiLauncherPath = Path.Combine(gameDir.FullName, "StardewModdingAPI");
|
||||
this.UnixBackupLauncherPath = Path.Combine(gameDir.FullName, "StardewValley-original");
|
||||
this.ApiConfigPath = Path.Combine(gameDir.FullName, "smapi-internal", "config.json");
|
||||
this.ApiUserConfigPath = Path.Combine(gameDir.FullName, "smapi-internal", "config.user.json");
|
||||
}
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Win32;
|
||||
using System.Reflection;
|
||||
using StardewModdingApi.Installer.Enums;
|
||||
using StardewModdingAPI.Installer.Framework;
|
||||
using StardewModdingAPI.Internal.ConsoleWriting;
|
||||
using StardewModdingAPI.Toolkit;
|
||||
using StardewModdingAPI.Toolkit.Framework;
|
||||
using StardewModdingAPI.Toolkit.Framework.GameScanning;
|
||||
using StardewModdingAPI.Toolkit.Framework.ModScanning;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
#if !SMAPI_FOR_WINDOWS
|
||||
using System.Diagnostics;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingApi.Installer
|
||||
{
|
||||
|
@ -25,35 +26,37 @@ namespace StardewModdingApi.Installer
|
|||
/// <summary>The absolute path to the directory containing the files to copy into the game folder.</summary>
|
||||
private readonly string BundlePath;
|
||||
|
||||
/// <summary>The <see cref="Environment.OSVersion"/> value that represents Windows 7.</summary>
|
||||
private readonly Version Windows7Version = new Version(6, 1);
|
||||
|
||||
/// <summary>The mod IDs which the installer should allow as bundled mods.</summary>
|
||||
private readonly string[] BundledModIDs = new[]
|
||||
{
|
||||
private readonly string[] BundledModIDs = {
|
||||
"SMAPI.SaveBackup",
|
||||
"SMAPI.ConsoleCommands"
|
||||
"SMAPI.ConsoleCommands",
|
||||
"SMAPI.ErrorHandler"
|
||||
};
|
||||
|
||||
/// <summary>Get the absolute file or folder paths to remove when uninstalling SMAPI.</summary>
|
||||
/// <param name="installDir">The folder for Stardew Valley and SMAPI.</param>
|
||||
/// <param name="modsDir">The folder for SMAPI mods.</param>
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These are valid file names.")]
|
||||
private IEnumerable<string> GetUninstallPaths(DirectoryInfo installDir, DirectoryInfo modsDir)
|
||||
{
|
||||
string GetInstallPath(string path) => Path.Combine(installDir.FullName, path);
|
||||
|
||||
// current files
|
||||
yield return GetInstallPath("libgdiplus.dylib"); // Linux/Mac only
|
||||
yield return GetInstallPath("StardewModdingAPI"); // Linux/Mac only
|
||||
yield return GetInstallPath("StardewModdingAPI"); // Linux/macOS only
|
||||
yield return GetInstallPath("StardewModdingAPI.deps.json");
|
||||
yield return GetInstallPath("StardewModdingAPI.dll");
|
||||
yield return GetInstallPath("StardewModdingAPI.exe");
|
||||
yield return GetInstallPath("StardewModdingAPI.exe.config");
|
||||
yield return GetInstallPath("StardewModdingAPI.exe.mdb"); // Linux/Mac only
|
||||
yield return GetInstallPath("StardewModdingAPI.exe.mdb"); // Linux/macOS only
|
||||
yield return GetInstallPath("StardewModdingAPI.pdb"); // Windows only
|
||||
yield return GetInstallPath("StardewModdingAPI.runtimeconfig.json");
|
||||
yield return GetInstallPath("StardewModdingAPI.xml");
|
||||
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
|
||||
yield return GetInstallPath(Path.Combine("Mods", "TrainerMod")); // *–2.0 (renamed to ConsoleCommands)
|
||||
yield return GetInstallPath("Mono.Cecil.Rocks.dll"); // 1.3–1.8
|
||||
|
@ -73,15 +76,14 @@ namespace StardewModdingApi.Installer
|
|||
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.dll"); // moved in 2.8
|
||||
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.pdb"); // moved in 2.8
|
||||
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.xml"); // moved in 2.8
|
||||
yield return GetInstallPath("System.Numerics.dll"); // moved in 2.8
|
||||
yield return GetInstallPath("System.Runtime.Caching.dll"); // moved in 2.8
|
||||
yield return GetInstallPath("System.ValueTuple.dll"); // moved in 2.8
|
||||
yield return GetInstallPath("StardewModdingAPI-x64.exe"); // before 3.13
|
||||
|
||||
if (modsDir.Exists)
|
||||
{
|
||||
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
|
||||
}
|
||||
|
@ -109,13 +111,13 @@ namespace StardewModdingApi.Installer
|
|||
/// 2. Ask the user whether to install or uninstall.
|
||||
///
|
||||
/// Uninstall logic:
|
||||
/// 1. On Linux/Mac: if a backup of the launcher exists, delete the launcher and restore the backup.
|
||||
/// 1. On Linux/macOS: if a backup of the launcher exists, delete the launcher and restore the backup.
|
||||
/// 2. Delete all files and folders in the game directory matching one of the values returned by <see cref="GetUninstallPaths"/>.
|
||||
///
|
||||
/// Install flow:
|
||||
/// 1. Run the uninstall flow.
|
||||
/// 2. Copy the SMAPI files from package/Windows or package/Mono into the game directory.
|
||||
/// 3. On Linux/Mac: back up the game launcher and replace it with the SMAPI launcher. (This isn't possible on Windows, so the user needs to configure it manually.)
|
||||
/// 3. On Linux/macOS: back up the game launcher and replace it with the SMAPI launcher. (This isn't possible on Windows, so the user needs to configure it manually.)
|
||||
/// 4. Create the 'Mods' directory.
|
||||
/// 5. Copy the bundled mods into the 'Mods' directory (deleting any existing versions).
|
||||
/// 6. Move any mods from app data into game's mods directory.
|
||||
|
@ -128,54 +130,30 @@ namespace StardewModdingApi.Installer
|
|||
/****
|
||||
** Get basic info & set window title
|
||||
****/
|
||||
ModToolkit toolkit = new ModToolkit();
|
||||
Platform platform = EnvironmentUtility.DetectPlatform();
|
||||
Console.Title = $"SMAPI {this.GetDisplayVersion(this.GetType().Assembly.GetName().Version)} installer on {platform} {EnvironmentUtility.GetFriendlyPlatformName(platform)}";
|
||||
ModToolkit toolkit = new();
|
||||
var context = new InstallerContext();
|
||||
Console.Title = $"SMAPI {context.GetInstallerVersion()} installer on {context.Platform} {context.PlatformName}";
|
||||
Console.WriteLine();
|
||||
|
||||
/****
|
||||
** Check if correct installer
|
||||
****/
|
||||
#if SMAPI_FOR_WINDOWS
|
||||
if (platform == Platform.Linux || platform == Platform.Mac)
|
||||
if (context.IsUnix)
|
||||
{
|
||||
this.PrintError($"This is the installer for Windows. Run the 'install on {platform}.{(platform == Platform.Linux ? "sh" : "command")}' file instead.");
|
||||
this.PrintError($"This is the installer for Windows. Run the 'install on {context.Platform}.{(context.Platform == Platform.Mac ? "command" : "sh")}' file instead.");
|
||||
Console.ReadLine();
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (platform == Platform.Windows)
|
||||
if (context.IsWindows)
|
||||
{
|
||||
this.PrintError($"This is the installer for Linux/Mac. Run the 'install on Windows.exe' file instead.");
|
||||
this.PrintError($"This is the installer for Linux/macOS. Run the 'install on Windows.exe' file instead.");
|
||||
Console.ReadLine();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/****
|
||||
** Check Windows dependencies
|
||||
****/
|
||||
if (platform == Platform.Windows)
|
||||
{
|
||||
// .NET Framework 4.5+
|
||||
if (!this.HasNetFramework45(platform))
|
||||
{
|
||||
this.PrintError(Environment.OSVersion.Version >= this.Windows7Version
|
||||
? "Please install the latest version of .NET Framework before installing SMAPI." // Windows 7+
|
||||
: "Please install .NET Framework 4.5 before installing SMAPI." // Windows Vista or earlier
|
||||
);
|
||||
this.PrintError("See the download page at https://www.microsoft.com/net/download/framework for details.");
|
||||
Console.ReadLine();
|
||||
return;
|
||||
}
|
||||
if (!this.HasXna(platform))
|
||||
{
|
||||
this.PrintError("You don't seem to have XNA Framework installed. Please run the game at least once before installing SMAPI, so it can perform its first-time setup.");
|
||||
Console.ReadLine();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
** read command-line arguments
|
||||
****/
|
||||
|
@ -190,7 +168,7 @@ namespace StardewModdingApi.Installer
|
|||
}
|
||||
|
||||
// get game path from CLI
|
||||
string gamePathArg = null;
|
||||
string? gamePathArg = null;
|
||||
{
|
||||
int pathIndex = Array.LastIndexOf(args, "--game-path") + 1;
|
||||
if (pathIndex >= 1 && args.Length >= pathIndex)
|
||||
|
@ -199,10 +177,10 @@ namespace StardewModdingApi.Installer
|
|||
|
||||
|
||||
/*********
|
||||
** Step 2: choose a theme (can't auto-detect on Linux/Mac)
|
||||
** Step 2: choose a theme (can't auto-detect on Linux/macOS)
|
||||
*********/
|
||||
MonitorColorScheme scheme = MonitorColorScheme.AutoDetect;
|
||||
if (platform == Platform.Linux || platform == Platform.Mac)
|
||||
if (context.IsUnix)
|
||||
{
|
||||
/****
|
||||
** print header
|
||||
|
@ -215,8 +193,8 @@ namespace StardewModdingApi.Installer
|
|||
** show theme selector
|
||||
****/
|
||||
// get theme writers
|
||||
var lightBackgroundWriter = new ColorfulConsoleWriter(platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.LightBackground));
|
||||
var darkBackgroundWriter = new ColorfulConsoleWriter(platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.DarkBackground));
|
||||
ColorfulConsoleWriter lightBackgroundWriter = new(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.LightBackground));
|
||||
ColorfulConsoleWriter darkBackgroundWriter = new(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.DarkBackground));
|
||||
|
||||
// print question
|
||||
this.PrintPlain("Which text looks more readable?");
|
||||
|
@ -228,7 +206,7 @@ namespace StardewModdingApi.Installer
|
|||
Console.WriteLine();
|
||||
|
||||
// handle choice
|
||||
string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" });
|
||||
string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" }, printLine: Console.WriteLine);
|
||||
switch (choice)
|
||||
{
|
||||
case "1":
|
||||
|
@ -263,8 +241,7 @@ namespace StardewModdingApi.Installer
|
|||
** collect details
|
||||
****/
|
||||
// get game path
|
||||
this.PrintInfo("Where is your game folder?");
|
||||
DirectoryInfo installDir = this.InteractivelyGetInstallPath(platform, toolkit, gamePathArg);
|
||||
DirectoryInfo? installDir = this.InteractivelyGetInstallPath(toolkit, context, gamePathArg);
|
||||
if (installDir == null)
|
||||
{
|
||||
this.PrintError("Failed finding your game path.");
|
||||
|
@ -273,21 +250,22 @@ namespace StardewModdingApi.Installer
|
|||
}
|
||||
|
||||
// get folders
|
||||
DirectoryInfo bundleDir = new DirectoryInfo(this.BundlePath);
|
||||
paths = new InstallerPaths(bundleDir, installDir, EnvironmentUtility.GetExecutableName(platform));
|
||||
DirectoryInfo bundleDir = new(this.BundlePath);
|
||||
paths = new InstallerPaths(bundleDir, installDir);
|
||||
}
|
||||
Console.Clear();
|
||||
|
||||
|
||||
/*********
|
||||
** Step 4: validate assumptions
|
||||
*********/
|
||||
if (!File.Exists(paths.ExecutablePath))
|
||||
// executable exists
|
||||
if (!File.Exists(paths.GameDllPath))
|
||||
{
|
||||
this.PrintError("The detected game install path doesn't contain a Stardew Valley executable.");
|
||||
Console.ReadLine();
|
||||
return;
|
||||
}
|
||||
Console.Clear();
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -359,11 +337,11 @@ namespace StardewModdingApi.Installer
|
|||
** Always uninstall old files
|
||||
****/
|
||||
// restore game launcher
|
||||
if (platform.IsMono() && File.Exists(paths.UnixBackupLauncherPath))
|
||||
if (context.IsUnix && File.Exists(paths.BackupLaunchScriptPath))
|
||||
{
|
||||
this.PrintDebug("Removing SMAPI launcher...");
|
||||
this.InteractivelyDelete(paths.UnixLauncherPath);
|
||||
File.Move(paths.UnixBackupLauncherPath, paths.UnixLauncherPath);
|
||||
this.InteractivelyDelete(paths.VanillaLaunchScriptPath);
|
||||
File.Move(paths.BackupLaunchScriptPath, paths.VanillaLaunchScriptPath);
|
||||
}
|
||||
|
||||
// remove old files
|
||||
|
@ -380,8 +358,8 @@ namespace StardewModdingApi.Installer
|
|||
// move global save data folder (changed in 3.2)
|
||||
{
|
||||
string dataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley");
|
||||
DirectoryInfo oldDir = new DirectoryInfo(Path.Combine(dataPath, "Saves", ".smapi"));
|
||||
DirectoryInfo newDir = new DirectoryInfo(Path.Combine(dataPath, ".smapi"));
|
||||
DirectoryInfo oldDir = new(Path.Combine(dataPath, "Saves", ".smapi"));
|
||||
DirectoryInfo newDir = new(Path.Combine(dataPath, ".smapi"));
|
||||
|
||||
if (oldDir.Exists)
|
||||
{
|
||||
|
@ -406,38 +384,46 @@ namespace StardewModdingApi.Installer
|
|||
}
|
||||
|
||||
// replace mod launcher (if possible)
|
||||
if (platform.IsMono())
|
||||
if (context.IsUnix)
|
||||
{
|
||||
this.PrintDebug("Safely replacing game launcher...");
|
||||
|
||||
// back up & remove current launcher
|
||||
if (File.Exists(paths.UnixLauncherPath))
|
||||
if (File.Exists(paths.VanillaLaunchScriptPath))
|
||||
{
|
||||
if (!File.Exists(paths.UnixBackupLauncherPath))
|
||||
File.Move(paths.UnixLauncherPath, paths.UnixBackupLauncherPath);
|
||||
if (!File.Exists(paths.BackupLaunchScriptPath))
|
||||
File.Move(paths.VanillaLaunchScriptPath, paths.BackupLaunchScriptPath);
|
||||
else
|
||||
this.InteractivelyDelete(paths.UnixLauncherPath);
|
||||
this.InteractivelyDelete(paths.VanillaLaunchScriptPath);
|
||||
}
|
||||
|
||||
// add new launcher
|
||||
File.Move(paths.UnixSmapiLauncherPath, paths.UnixLauncherPath);
|
||||
File.Move(paths.NewLaunchScriptPath, paths.VanillaLaunchScriptPath);
|
||||
|
||||
// mark file executable
|
||||
// mark files executable
|
||||
// (MSBuild doesn't keep permission flags for files zipped in a build task.)
|
||||
// (Note: exclude from Windows build because antivirus apps can flag the process start code as suspicious.)
|
||||
#if !SMAPI_FOR_WINDOWS
|
||||
new Process
|
||||
foreach (string path in new[] { paths.VanillaLaunchScriptPath, paths.UnixSmapiExecutablePath })
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
new Process
|
||||
{
|
||||
FileName = "chmod",
|
||||
Arguments = $"755 \"{paths.UnixLauncherPath}\"",
|
||||
CreateNoWindow = true
|
||||
}
|
||||
}.Start();
|
||||
#endif
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "chmod",
|
||||
Arguments = $"755 \"{path}\"",
|
||||
CreateNoWindow = true
|
||||
}
|
||||
}.Start();
|
||||
}
|
||||
}
|
||||
|
||||
// copy the game's deps.json file
|
||||
// (This is needed to resolve native DLLs like libSkiaSharp.)
|
||||
File.Copy(
|
||||
sourceFileName: Path.Combine(paths.GamePath, "Stardew Valley.deps.json"),
|
||||
destFileName: Path.Combine(paths.GamePath, "StardewModdingAPI.deps.json"),
|
||||
overwrite: true
|
||||
);
|
||||
|
||||
// create mods directory (if needed)
|
||||
if (!paths.ModsDir.Exists)
|
||||
{
|
||||
|
@ -446,13 +432,13 @@ namespace StardewModdingApi.Installer
|
|||
}
|
||||
|
||||
// add or replace bundled mods
|
||||
DirectoryInfo bundledModsDir = new DirectoryInfo(Path.Combine(paths.BundlePath, "Mods"));
|
||||
DirectoryInfo bundledModsDir = new(Path.Combine(paths.BundlePath, "Mods"));
|
||||
if (bundledModsDir.Exists && bundledModsDir.EnumerateDirectories().Any())
|
||||
{
|
||||
this.PrintDebug("Adding bundled mods...");
|
||||
|
||||
ModFolder[] targetMods = toolkit.GetModFolders(paths.ModsPath).ToArray();
|
||||
foreach (ModFolder sourceMod in toolkit.GetModFolders(bundledModsDir.FullName))
|
||||
ModFolder[] targetMods = toolkit.GetModFolders(paths.ModsPath, useCaseInsensitiveFilePaths: true).ToArray();
|
||||
foreach (ModFolder sourceMod in toolkit.GetModFolders(bundledModsDir.FullName, useCaseInsensitiveFilePaths: true))
|
||||
{
|
||||
// validate source mod
|
||||
if (sourceMod.Manifest == null)
|
||||
|
@ -467,8 +453,9 @@ namespace StardewModdingApi.Installer
|
|||
}
|
||||
|
||||
// find target folder
|
||||
ModFolder targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase) == true);
|
||||
DirectoryInfo defaultTargetFolder = new DirectoryInfo(Path.Combine(paths.ModsPath, sourceMod.Directory.Name));
|
||||
// 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;
|
||||
this.PrintDebug(targetFolder.FullName == defaultTargetFolder.FullName
|
||||
? $" adding {sourceMod.Manifest.Name}..."
|
||||
|
@ -493,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();
|
||||
|
@ -504,7 +493,7 @@ namespace StardewModdingApi.Installer
|
|||
/*********
|
||||
** Step 7: final instructions
|
||||
*********/
|
||||
if (platform == Platform.Windows)
|
||||
if (context.IsWindows)
|
||||
{
|
||||
if (action == ScriptAction.Install)
|
||||
{
|
||||
|
@ -531,16 +520,6 @@ namespace StardewModdingApi.Installer
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Get the display text for an assembly version.</summary>
|
||||
/// <param name="version">The assembly version.</param>
|
||||
private string GetDisplayVersion(Version version)
|
||||
{
|
||||
string str = $"{version.Major}.{version.Minor}";
|
||||
if (version.Build != 0)
|
||||
str += $".{version.Build}";
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>Get the display text for a color scheme.</summary>
|
||||
/// <param name="scheme">The color scheme.</param>
|
||||
private string GetDisplayText(MonitorColorScheme scheme)
|
||||
|
@ -560,58 +539,44 @@ namespace StardewModdingApi.Installer
|
|||
|
||||
/// <summary>Print a message without formatting.</summary>
|
||||
/// <param name="text">The text to print.</param>
|
||||
private void PrintPlain(string text) => Console.WriteLine(text);
|
||||
private void PrintPlain(string text)
|
||||
{
|
||||
Console.WriteLine(text);
|
||||
}
|
||||
|
||||
/// <summary>Print a debug message.</summary>
|
||||
/// <param name="text">The text to print.</param>
|
||||
private void PrintDebug(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Debug);
|
||||
private void PrintDebug(string text)
|
||||
{
|
||||
this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Debug);
|
||||
}
|
||||
|
||||
/// <summary>Print a debug message.</summary>
|
||||
/// <param name="text">The text to print.</param>
|
||||
private void PrintInfo(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Info);
|
||||
private void PrintInfo(string text)
|
||||
{
|
||||
this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Info);
|
||||
}
|
||||
|
||||
/// <summary>Print a warning message.</summary>
|
||||
/// <param name="text">The text to print.</param>
|
||||
private void PrintWarning(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Warn);
|
||||
private void PrintWarning(string text)
|
||||
{
|
||||
this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Warn);
|
||||
}
|
||||
|
||||
/// <summary>Print a warning message.</summary>
|
||||
/// <param name="text">The text to print.</param>
|
||||
private void PrintError(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Error);
|
||||
private void PrintError(string text)
|
||||
{
|
||||
this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Error);
|
||||
}
|
||||
|
||||
/// <summary>Print a success message.</summary>
|
||||
/// <param name="text">The text to print.</param>
|
||||
private void PrintSuccess(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Success);
|
||||
|
||||
/// <summary>Get whether the current system has .NET Framework 4.5 or later installed. This only applies on Windows.</summary>
|
||||
/// <param name="platform">The current platform.</param>
|
||||
/// <exception cref="NotSupportedException">The current platform is not Windows.</exception>
|
||||
private bool HasNetFramework45(Platform platform)
|
||||
private void PrintSuccess(string text)
|
||||
{
|
||||
switch (platform)
|
||||
{
|
||||
case Platform.Windows:
|
||||
using (RegistryKey versionKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"))
|
||||
return versionKey?.GetValue("Release") != null; // .NET Framework 4.5+
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("The installed .NET Framework version can only be checked on Windows.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Get whether the current system has XNA Framework installed. This only applies on Windows.</summary>
|
||||
/// <param name="platform">The current platform.</param>
|
||||
/// <exception cref="NotSupportedException">The current platform is not Windows.</exception>
|
||||
private bool HasXna(Platform platform)
|
||||
{
|
||||
switch (platform)
|
||||
{
|
||||
case Platform.Windows:
|
||||
using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\XNA\Framework"))
|
||||
return key != null; // XNA Framework 4.0+
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("The installed XNA Framework version can only be checked on Windows.");
|
||||
}
|
||||
this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Success);
|
||||
}
|
||||
|
||||
/// <summary>Interactively delete a file or folder path, and block until deletion completes.</summary>
|
||||
|
@ -622,7 +587,7 @@ namespace StardewModdingApi.Installer
|
|||
{
|
||||
try
|
||||
{
|
||||
FileUtilities.ForceDelete(Directory.Exists(path) ? new DirectoryInfo(path) : (FileSystemInfo)new FileInfo(path));
|
||||
FileUtilities.ForceDelete(Directory.Exists(path) ? new DirectoryInfo(path) : new FileInfo(path));
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -638,7 +603,7 @@ namespace StardewModdingApi.Installer
|
|||
/// <param name="source">The file or folder to copy.</param>
|
||||
/// <param name="targetFolder">The folder to copy into.</param>
|
||||
/// <param name="filter">A filter which matches directories and files to copy, or <c>null</c> to match all.</param>
|
||||
private void RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, Func<FileSystemInfo, bool> filter = null)
|
||||
private void RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, Func<FileSystemInfo, bool>? filter = null)
|
||||
{
|
||||
if (filter != null && !filter(source))
|
||||
return;
|
||||
|
@ -653,8 +618,8 @@ namespace StardewModdingApi.Installer
|
|||
break;
|
||||
|
||||
case DirectoryInfo sourceDir:
|
||||
DirectoryInfo targetSubfolder = new DirectoryInfo(Path.Combine(targetFolder.FullName, sourceDir.Name));
|
||||
foreach (var entry in sourceDir.EnumerateFileSystemInfos())
|
||||
DirectoryInfo targetSubfolder = new(Path.Combine(targetFolder.FullName, sourceDir.Name));
|
||||
foreach (FileSystemInfo entry in sourceDir.EnumerateFileSystemInfos())
|
||||
this.RecursiveCopy(entry, targetSubfolder, filter);
|
||||
break;
|
||||
|
||||
|
@ -664,22 +629,22 @@ namespace StardewModdingApi.Installer
|
|||
}
|
||||
|
||||
/// <summary>Interactively ask the user to choose a value.</summary>
|
||||
/// <param name="print">A callback which prints a message to the console.</param>
|
||||
/// <param name="printLine">A callback which prints a message to the console.</param>
|
||||
/// <param name="message">The message to print.</param>
|
||||
/// <param name="options">The allowed options (not case sensitive).</param>
|
||||
/// <param name="indent">The indentation to prefix to output.</param>
|
||||
private string InteractivelyChoose(string message, string[] options, string indent = "", Action<string> print = null)
|
||||
private string InteractivelyChoose(string message, string[] options, string indent = "", Action<string>? printLine = null)
|
||||
{
|
||||
print ??= this.PrintInfo;
|
||||
printLine ??= this.PrintInfo;
|
||||
|
||||
while (true)
|
||||
{
|
||||
print(indent + message);
|
||||
printLine(indent + message);
|
||||
Console.Write(indent);
|
||||
string input = Console.ReadLine()?.Trim().ToLowerInvariant();
|
||||
if (!options.Contains(input))
|
||||
string? input = Console.ReadLine()?.Trim().ToLowerInvariant();
|
||||
if (input == null || !options.Contains(input))
|
||||
{
|
||||
print($"{indent}That's not a valid option.");
|
||||
printLine($"{indent}That's not a valid option.");
|
||||
continue;
|
||||
}
|
||||
return input;
|
||||
|
@ -687,100 +652,166 @@ namespace StardewModdingApi.Installer
|
|||
}
|
||||
|
||||
/// <summary>Interactively locate the game install path to update.</summary>
|
||||
/// <param name="platform">The current platform.</param>
|
||||
/// <param name="toolkit">The mod toolkit.</param>
|
||||
/// <param name="context">The installer context.</param>
|
||||
/// <param name="specifiedPath">The path specified as a command-line argument (if any), which should override automatic path detection.</param>
|
||||
private DirectoryInfo InteractivelyGetInstallPath(Platform platform, ModToolkit toolkit, string specifiedPath)
|
||||
private DirectoryInfo? InteractivelyGetInstallPath(ModToolkit toolkit, InstallerContext context, string? specifiedPath)
|
||||
{
|
||||
// get executable name
|
||||
string executableFilename = EnvironmentUtility.GetExecutableName(platform);
|
||||
|
||||
// validate specified path
|
||||
// use specified path
|
||||
if (specifiedPath != null)
|
||||
{
|
||||
string errorPrefix = $"You specified --game-path \"{specifiedPath}\", but";
|
||||
|
||||
var dir = new DirectoryInfo(specifiedPath);
|
||||
if (!dir.Exists)
|
||||
{
|
||||
this.PrintError($"You specified --game-path \"{specifiedPath}\", but that folder doesn't exist.");
|
||||
this.PrintError($"{errorPrefix} that folder doesn't exist.");
|
||||
return null;
|
||||
}
|
||||
if (!dir.EnumerateFiles(executableFilename).Any())
|
||||
|
||||
switch (context.GetGameFolderType(dir))
|
||||
{
|
||||
this.PrintError($"You specified --game-path \"{specifiedPath}\", but that folder doesn't contain the Stardew Valley executable.");
|
||||
return null;
|
||||
case GameFolderType.Valid:
|
||||
return dir;
|
||||
|
||||
case GameFolderType.Legacy154OrEarlier:
|
||||
this.PrintWarning($"{errorPrefix} that directory seems to have Stardew Valley 1.5.4 or earlier.");
|
||||
this.PrintWarning("Please update your game to the latest version to use SMAPI.");
|
||||
return null;
|
||||
|
||||
case GameFolderType.LegacyCompatibilityBranch:
|
||||
this.PrintWarning($"{errorPrefix} that directory seems to have the Stardew Valley legacy 'compatibility' branch.");
|
||||
this.PrintWarning("Unfortunately SMAPI is only compatible with the modern version of the game.");
|
||||
this.PrintWarning("Please update your game to the main branch to use SMAPI.");
|
||||
return null;
|
||||
|
||||
case GameFolderType.NoGameFound:
|
||||
this.PrintWarning($"{errorPrefix} that directory doesn't contain a Stardew Valley executable.");
|
||||
return null;
|
||||
|
||||
default:
|
||||
this.PrintWarning($"{errorPrefix} that directory doesn't seem to contain a valid game install.");
|
||||
return null;
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
// get installed paths
|
||||
DirectoryInfo[] defaultPaths = toolkit.GetGameFolders().ToArray();
|
||||
// let user choose detected path
|
||||
DirectoryInfo[] defaultPaths = this.DetectGameFolders(toolkit, context).ToArray();
|
||||
if (defaultPaths.Any())
|
||||
{
|
||||
// only one path
|
||||
if (defaultPaths.Length == 1)
|
||||
return defaultPaths.First();
|
||||
|
||||
// let user choose path
|
||||
this.PrintInfo("Where do you want to add or remove SMAPI?");
|
||||
Console.WriteLine();
|
||||
this.PrintInfo("Found multiple copies of the game:");
|
||||
for (int i = 0; i < defaultPaths.Length; i++)
|
||||
this.PrintInfo($"[{i + 1}] {defaultPaths[i].FullName}");
|
||||
this.PrintInfo($"[{defaultPaths.Length + 1}] Enter a custom game path.");
|
||||
Console.WriteLine();
|
||||
|
||||
string[] validOptions = Enumerable.Range(1, defaultPaths.Length).Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray();
|
||||
string choice = this.InteractivelyChoose("Where do you want to add/remove SMAPI? Type the number next to your choice, then press enter.", validOptions);
|
||||
string[] validOptions = Enumerable.Range(1, defaultPaths.Length + 1).Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray();
|
||||
string choice = this.InteractivelyChoose("Type the number next to your choice, then press enter.", validOptions);
|
||||
int index = int.Parse(choice, CultureInfo.InvariantCulture) - 1;
|
||||
return defaultPaths[index];
|
||||
}
|
||||
|
||||
// ask user
|
||||
this.PrintInfo("Oops, couldn't find the game automatically.");
|
||||
if (index < defaultPaths.Length)
|
||||
return defaultPaths[index];
|
||||
}
|
||||
else
|
||||
this.PrintInfo("Oops, couldn't find the game automatically.");
|
||||
|
||||
// let user enter manual path
|
||||
while (true)
|
||||
{
|
||||
// get path from user
|
||||
this.PrintInfo($"Type the file path to the game directory (the one containing '{executableFilename}'), then press enter.");
|
||||
string path = Console.ReadLine()?.Trim();
|
||||
Console.WriteLine();
|
||||
this.PrintInfo($"Type the file path to the game directory (the one containing '{Constants.GameDllName}'), then press enter.");
|
||||
string? path = Console.ReadLine()?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
this.PrintInfo(" You must specify a directory path to continue.");
|
||||
this.PrintWarning("You must specify a directory path to continue.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// normalize path
|
||||
if (platform == Platform.Windows)
|
||||
path = path.Replace("\"", ""); // in Windows, quotes are used to escape spaces and aren't part of the file path
|
||||
if (platform == Platform.Linux || platform == Platform.Mac)
|
||||
path = path.Replace("\\ ", " "); // in Linux/Mac, spaces in paths may be escaped if copied from the command line
|
||||
path = context.IsWindows
|
||||
? path.Replace("\"", "") // in Windows, quotes are used to escape spaces and aren't part of the file path
|
||||
: path.Replace("\\ ", " "); // in Linux/macOS, spaces in paths may be escaped if copied from the command line
|
||||
if (path.StartsWith("~/"))
|
||||
{
|
||||
string home = Environment.GetEnvironmentVariable("HOME") ?? Environment.GetEnvironmentVariable("USERPROFILE");
|
||||
string home = Environment.GetEnvironmentVariable("HOME") ?? Environment.GetEnvironmentVariable("USERPROFILE")!;
|
||||
path = Path.Combine(home, path.Substring(2));
|
||||
}
|
||||
|
||||
// get directory
|
||||
if (File.Exists(path))
|
||||
path = Path.GetDirectoryName(path);
|
||||
DirectoryInfo directory = new DirectoryInfo(path);
|
||||
path = Path.GetDirectoryName(path)!;
|
||||
DirectoryInfo directory = new(path);
|
||||
|
||||
// validate path
|
||||
if (!directory.Exists)
|
||||
{
|
||||
this.PrintInfo(" That directory doesn't seem to exist.");
|
||||
continue;
|
||||
}
|
||||
if (!directory.EnumerateFiles(executableFilename).Any())
|
||||
{
|
||||
this.PrintInfo(" That directory doesn't contain a Stardew Valley executable.");
|
||||
this.PrintWarning("That directory doesn't seem to exist.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// looks OK
|
||||
this.PrintInfo(" OK!");
|
||||
return directory;
|
||||
switch (context.GetGameFolderType(directory))
|
||||
{
|
||||
case GameFolderType.Valid:
|
||||
this.PrintInfo(" OK!");
|
||||
return directory;
|
||||
|
||||
case GameFolderType.Legacy154OrEarlier:
|
||||
this.PrintWarning("That directory seems to have Stardew Valley 1.5.4 or earlier.");
|
||||
this.PrintWarning("Please update your game to the latest version to use SMAPI.");
|
||||
continue;
|
||||
|
||||
case GameFolderType.LegacyCompatibilityBranch:
|
||||
this.PrintWarning("That directory seems to have the Stardew Valley legacy 'compatibility' branch.");
|
||||
this.PrintWarning("Unfortunately SMAPI is only compatible with the modern version of the game.");
|
||||
this.PrintWarning("Please update your game to the main branch to use SMAPI.");
|
||||
continue;
|
||||
|
||||
case GameFolderType.NoGameFound:
|
||||
this.PrintWarning("That directory doesn't contain a Stardew Valley executable.");
|
||||
continue;
|
||||
|
||||
default:
|
||||
this.PrintWarning("That directory doesn't seem to contain a valid game install.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Interactively move mods out of the appdata directory.</summary>
|
||||
/// <summary>Get the possible game paths to update.</summary>
|
||||
/// <param name="toolkit">The mod toolkit.</param>
|
||||
/// <param name="context">The installer context.</param>
|
||||
private IEnumerable<DirectoryInfo> DetectGameFolders(ModToolkit toolkit, InstallerContext context)
|
||||
{
|
||||
HashSet<string> foundPaths = new HashSet<string>();
|
||||
|
||||
// game folder which contains the installer, if any
|
||||
{
|
||||
DirectoryInfo? curPath = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
|
||||
while (curPath?.Parent != null) // must be in a folder (not at the root)
|
||||
{
|
||||
if (context.LooksLikeGameFolder(curPath))
|
||||
{
|
||||
foundPaths.Add(curPath.FullName);
|
||||
yield return curPath;
|
||||
break;
|
||||
}
|
||||
|
||||
curPath = curPath.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
// game paths detected by toolkit
|
||||
foreach (DirectoryInfo dir in toolkit.GetGameFolders())
|
||||
{
|
||||
if (foundPaths.Add(dir.FullName))
|
||||
yield return dir;
|
||||
}
|
||||
}
|
||||
|
||||
#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>
|
||||
private void InteractivelyRemoveAppDataMods(DirectoryInfo properModsDir, DirectoryInfo packagedModsDir)
|
||||
|
@ -790,7 +821,7 @@ namespace StardewModdingApi.Installer
|
|||
|
||||
// get path
|
||||
string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley");
|
||||
DirectoryInfo modDir = new DirectoryInfo(Path.Combine(appDataPath, "Mods"));
|
||||
DirectoryInfo modDir = new(Path.Combine(appDataPath, "Mods"));
|
||||
|
||||
// check if migration needed
|
||||
if (!modDir.Exists)
|
||||
|
@ -803,7 +834,7 @@ namespace StardewModdingApi.Installer
|
|||
{
|
||||
// get type
|
||||
bool isDir = entry is DirectoryInfo;
|
||||
if (!isDir && !(entry is FileInfo))
|
||||
if (!isDir && entry is not FileInfo)
|
||||
continue; // should never happen
|
||||
|
||||
// delete packaged mods (newer version bundled into SMAPI)
|
||||
|
@ -840,7 +871,7 @@ namespace StardewModdingApi.Installer
|
|||
/// <summary>Move a filesystem entry to a new parent directory.</summary>
|
||||
/// <param name="entry">The filesystem entry to move.</param>
|
||||
/// <param name="newPath">The destination path.</param>
|
||||
/// <remarks>We can't use <see cref="FileInfo.MoveTo"/> or <see cref="DirectoryInfo.MoveTo"/>, because those don't work across partitions.</remarks>
|
||||
/// <remarks>We can't use <see cref="FileInfo.MoveTo(string)"/> or <see cref="DirectoryInfo.MoveTo"/>, because those don't work across partitions.</remarks>
|
||||
private void Move(FileSystemInfo entry, string newPath)
|
||||
{
|
||||
// file
|
||||
|
@ -862,20 +893,18 @@ 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>
|
||||
private bool ShouldCopy(FileSystemInfo entry)
|
||||
{
|
||||
switch (entry.Name)
|
||||
return entry.Name switch
|
||||
{
|
||||
case "mcs":
|
||||
return false; // ignore Mac symlink
|
||||
case "Mods":
|
||||
return false; // Mods folder handled separately
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
"mcs" => false, // ignore macOS symlink
|
||||
"Mods" => false, // Mods folder handled separately
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
namespace StardewModdingApi.Installer
|
||||
{
|
||||
|
@ -14,7 +15,7 @@ namespace StardewModdingApi.Installer
|
|||
*********/
|
||||
/// <summary>The absolute path of the installer folder.</summary>
|
||||
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute", Justification = "The assembly location is never null in this context.")]
|
||||
private static readonly string InstallerPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
private static readonly string InstallerPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
|
||||
|
||||
/// <summary>The absolute path of the folder containing the unzipped installer files.</summary>
|
||||
private static readonly string ExtractedBundlePath = Path.Combine(Path.GetTempPath(), $"SMAPI-installer-{Guid.NewGuid():N}");
|
||||
|
@ -30,8 +31,7 @@ namespace StardewModdingApi.Installer
|
|||
public static void Main(string[] args)
|
||||
{
|
||||
// find install bundle
|
||||
PlatformID platform = Environment.OSVersion.Platform;
|
||||
FileInfo zipFile = new FileInfo(Path.Combine(Program.InstallerPath, $"{(platform == PlatformID.Win32NT ? "windows" : "unix")}-install.dat"));
|
||||
FileInfo zipFile = new(Path.Combine(Program.InstallerPath, "install.dat"));
|
||||
if (!zipFile.Exists)
|
||||
{
|
||||
Console.WriteLine($"Oops! Some of the installer files are missing; try re-downloading the installer. (Missing file: {zipFile.FullName})");
|
||||
|
@ -40,7 +40,7 @@ namespace StardewModdingApi.Installer
|
|||
}
|
||||
|
||||
// unzip bundle into temp folder
|
||||
DirectoryInfo bundleDir = new DirectoryInfo(Program.ExtractedBundlePath);
|
||||
DirectoryInfo bundleDir = new(Program.ExtractedBundlePath);
|
||||
Console.WriteLine("Extracting install files...");
|
||||
ZipFile.ExtractToDirectory(zipFile.FullName, bundleDir.FullName);
|
||||
|
||||
|
@ -49,7 +49,15 @@ namespace StardewModdingApi.Installer
|
|||
|
||||
// launch installer
|
||||
var installer = new InteractiveInstaller(bundleDir.FullName);
|
||||
installer.Run(args);
|
||||
|
||||
try
|
||||
{
|
||||
installer.Run(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.PrintErrorAndExit($"The installer failed with an unexpected exception.\nIf you need help fixing this error, see https://smapi.io/help\n\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
/*********
|
||||
|
@ -58,14 +66,14 @@ namespace StardewModdingApi.Installer
|
|||
/// <summary>Method called when assembly resolution fails, which may return a manually resolved assembly.</summary>
|
||||
/// <param name="sender">The event sender.</param>
|
||||
/// <param name="e">The event arguments.</param>
|
||||
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e)
|
||||
private static Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssemblyName name = new AssemblyName(e.Name);
|
||||
AssemblyName name = new(e.Name);
|
||||
foreach (FileInfo dll in new DirectoryInfo(Program.InternalFilesPath).EnumerateFiles("*.dll"))
|
||||
{
|
||||
if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.OrdinalIgnoreCase))
|
||||
if (name.Name != null && name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.OrdinalIgnoreCase))
|
||||
return Assembly.LoadFrom(dll.FullName);
|
||||
}
|
||||
return null;
|
||||
|
@ -76,5 +84,19 @@ namespace StardewModdingApi.Installer
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Write an error directly to the console and exit.</summary>
|
||||
/// <param name="message">The error message to display.</param>
|
||||
private static void PrintErrorAndExit(string message)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(message);
|
||||
Console.ResetColor();
|
||||
|
||||
Console.WriteLine("Game has ended. Press any key to exit.");
|
||||
Thread.Sleep(100);
|
||||
Console.ReadKey();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
<PropertyGroup>
|
||||
<RootNamespace>StardewModdingAPI.Installer</RootNamespace>
|
||||
<Description>The SMAPI installer for players.</Description>
|
||||
<TargetFramework>net45</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -18,5 +17,4 @@
|
|||
|
||||
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
||||
<Import Project="..\..\build\common.targets" />
|
||||
<Import Project="..\..\build\prepare-install-package.targets" />
|
||||
</Project>
|
||||
|
|
|
@ -14,31 +14,34 @@
|
|||
SMAPI lets you run Stardew Valley with mods. Don't forget to download mods separately.
|
||||
|
||||
|
||||
Player's guide
|
||||
Automated install
|
||||
--------------------------------
|
||||
See https://stardewvalleywiki.com/Modding:Player_Guide for help installing SMAPI, adding mods, etc.
|
||||
|
||||
|
||||
Manual install
|
||||
--------------------------------
|
||||
THIS IS NOT RECOMMENDED FOR MOST PLAYERS. See instructions above instead.
|
||||
THIS IS NOT RECOMMENDED FOR MOST PLAYERS. See the instructions above instead.
|
||||
If you really want to install SMAPI manually, here's how.
|
||||
|
||||
1. Unzip "internal/windows-install.dat" (on Windows) or "internal/unix-install.dat" (on Linux/Mac).
|
||||
You can change '.dat' to '.zip', it's just a normal zip file renamed to prevent confusion.
|
||||
1. Unzip "internal/windows/install.dat" (on Windows) or "internal/unix/install.dat" (on Linux or
|
||||
macOS). You can change '.dat' to '.zip', it's just a normal zip file renamed to prevent
|
||||
confusion.
|
||||
|
||||
2. Copy the files from the folder you just unzipped into your game folder. The
|
||||
`StardewModdingAPI.exe` file should be right next to the game's executable.
|
||||
3.
|
||||
|
||||
3. Copy `Stardew Valley.deps.json` in the game folder, and rename the copy to
|
||||
`StardewModdingAPI.deps.json`.
|
||||
|
||||
4.
|
||||
- Windows only: if you use Steam, see the install guide above to enable achievements and
|
||||
overlay. Otherwise, just run StardewModdingAPI.exe in your game folder to play with mods.
|
||||
|
||||
- Linux/Mac only: rename the "StardewValley" file (no extension) to "StardewValley-original", and
|
||||
- Linux/macOS only: rename the "StardewValley" file (no extension) to "StardewValley-original", and
|
||||
"StardewModdingAPI" (no extension) to "StardewValley". Now just launch the game as usual to
|
||||
play with mods.
|
||||
|
||||
When installing on Linux or Mac:
|
||||
- Make sure Mono is installed (normally the installer checks for you). While it's not required,
|
||||
many mods won't work correctly without it. (Specifically, mods which load PNG images may crash or
|
||||
freeze the game.)
|
||||
When installing on Linux or macOS:
|
||||
- To configure the color scheme, edit the `smapi-internal/config.json` file and see instructions
|
||||
there for the 'ColorScheme' setting.
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "`dirname "$0"`"
|
||||
internal/linux/SMAPI.Installer
|
|
@ -0,0 +1,41 @@
|
|||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
SET installerDir="%~dp0"
|
||||
|
||||
REM make sure we're not running within a zip folder
|
||||
echo %installerDir% | findstr /C:"%TEMP%" 1>nul
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first.
|
||||
echo.
|
||||
pause
|
||||
exit
|
||||
)
|
||||
|
||||
REM make sure an antivirus hasn't deleted the installer DLL
|
||||
if not exist %installerDir%"internal\windows\SMAPI.Installer.dll" (
|
||||
echo Oops! SMAPI is missing one of its files. Your antivirus might have deleted it.
|
||||
echo Missing file: %installerDir%internal\windows\SMAPI.Installer.dll
|
||||
echo.
|
||||
pause
|
||||
exit
|
||||
)
|
||||
if not exist %installerDir%"internal\windows\SMAPI.Installer.exe" (
|
||||
echo Oops! SMAPI is missing one of its files. Your antivirus might have deleted it.
|
||||
echo Missing file: %installerDir%internal\windows\SMAPI.Installer.exe
|
||||
echo.
|
||||
pause
|
||||
exit
|
||||
)
|
||||
|
||||
REM start installer
|
||||
internal\windows\SMAPI.Installer.exe
|
||||
|
||||
REM keep window open if it failed
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo.
|
||||
echo Oops! The SMAPI installer seems to have failed. The error details may be shown above.
|
||||
echo.
|
||||
pause
|
||||
exit
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "`dirname "$0"`"
|
||||
|
||||
xattr -r -d com.apple.quarantine internal
|
||||
internal/macOS/SMAPI.Installer
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net5.0",
|
||||
"includedFrameworks": [
|
||||
{
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "5.0.0",
|
||||
"rollForward": "latestMinor"
|
||||
}
|
||||
],
|
||||
"configProperties": {
|
||||
// disable tiered runtime JIT: https://github.com/dotnet/runtime/blob/main/docs/design/features/tiered-compilation.md
|
||||
// This is disabled by the base game, and causes issues with Harmony patches.
|
||||
"System.Runtime.TieredCompilation": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Run the SMAPI installer through Mono on Linux or Mac.
|
||||
|
||||
# Move to script's directory
|
||||
cd "`dirname "$0"`"
|
||||
|
||||
# get cross-distro version of POSIX command
|
||||
COMMAND=""
|
||||
if command -v command >/dev/null 2>&1; then
|
||||
COMMAND="command -v"
|
||||
elif type type >/dev/null 2>&1; then
|
||||
COMMAND="type"
|
||||
fi
|
||||
|
||||
# if $TERM is not set to xterm, mono will bail out when attempting to write to the console.
|
||||
export TERM=xterm
|
||||
|
||||
# validate Mono & run installer
|
||||
if $COMMAND mono >/dev/null 2>&1; then
|
||||
mono internal/unix-install.exe
|
||||
else
|
||||
echo "Oops! Looks like Mono isn't installed. Please install Mono from https://mono-project.com, reboot, and run this installer again."
|
||||
read
|
||||
fi
|
|
@ -1,109 +1,164 @@
|
|||
#!/usr/bin/env bash
|
||||
# MonoKickstart Shell Script
|
||||
# Written by Ethan "flibitijibibo" Lee
|
||||
# Modified for SMAPI by various contributors
|
||||
|
||||
# Move to script's directory
|
||||
##########
|
||||
## Initial setup
|
||||
##########
|
||||
# move to script's directory
|
||||
cd "$(dirname "$0")" || exit $?
|
||||
|
||||
# Get the system architecture
|
||||
UNAME=$(uname)
|
||||
ARCH=$(uname -m)
|
||||
# Whether to avoid opening a separate terminal window, and avoid logging anything to the console.
|
||||
# This isn't recommended since you won't see errors, warnings, and update alerts.
|
||||
SKIP_TERMINAL=false
|
||||
|
||||
# MonoKickstart picks the right libfolder, so just execute the right binary.
|
||||
if [ "$UNAME" == "Darwin" ]; then
|
||||
# ... Except on OSX.
|
||||
export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:./osx/
|
||||
# Whether to avoid opening a separate terminal, but still send the usual log output to the console.
|
||||
USE_CURRENT_SHELL=false
|
||||
|
||||
# El Capitan is a total idiot and wipes this variable out, making the
|
||||
# Steam overlay disappear. This sidesteps "System Integrity Protection"
|
||||
# and resets the variable with Valve's own variable (they provided this
|
||||
# fix by the way, thanks Valve!). Note that you will need to update your
|
||||
# launch configuration to the script location, NOT just the app location
|
||||
# (i.e. Kick.app/Contents/MacOS/Kick, not just Kick.app).
|
||||
# -flibit
|
||||
if [ "$STEAM_DYLD_INSERT_LIBRARIES" != "" ] && [ "$DYLD_INSERT_LIBRARIES" == "" ]; then
|
||||
export DYLD_INSERT_LIBRARIES="$STEAM_DYLD_INSERT_LIBRARIES"
|
||||
fi
|
||||
|
||||
# this was here before
|
||||
ln -sf mcs.bin.osx mcs
|
||||
|
||||
# fix "DllNotFoundException: libgdiplus.dylib" errors when loading images in SMAPI
|
||||
if [ -f libgdiplus.dylib ]; then
|
||||
rm libgdiplus.dylib
|
||||
fi
|
||||
if [ -f /Library/Frameworks/Mono.framework/Versions/Current/lib/libgdiplus.dylib ]; then
|
||||
ln -s /Library/Frameworks/Mono.framework/Versions/Current/lib/libgdiplus.dylib libgdiplus.dylib
|
||||
fi
|
||||
|
||||
# launch SMAPI
|
||||
cp StardewValley.bin.osx StardewModdingAPI.bin.osx
|
||||
open -a Terminal ./StardewModdingAPI.bin.osx "$@"
|
||||
else
|
||||
# choose launcher
|
||||
LAUNCHER=""
|
||||
if [ "$ARCH" == "x86_64" ]; then
|
||||
ln -sf mcs.bin.x86_64 mcs
|
||||
cp StardewValley.bin.x86_64 StardewModdingAPI.bin.x86_64
|
||||
LAUNCHER="./StardewModdingAPI.bin.x86_64"
|
||||
else
|
||||
ln -sf mcs.bin.x86 mcs
|
||||
cp StardewValley.bin.x86 StardewModdingAPI.bin.x86
|
||||
LAUNCHER="./StardewModdingAPI.bin.x86"
|
||||
fi
|
||||
export LAUNCHER
|
||||
|
||||
# get cross-distro version of POSIX command
|
||||
COMMAND=""
|
||||
if command -v command 2>/dev/null; then
|
||||
COMMAND="command -v"
|
||||
elif type type 2>/dev/null; then
|
||||
COMMAND="type -p"
|
||||
fi
|
||||
|
||||
# select terminal (prefer xterm for best compatibility, then known supported terminals)
|
||||
for terminal in xterm gnome-terminal kitty terminator xfce4-terminal konsole terminal termite alacritty mate-terminal x-terminal-emulator; do
|
||||
if $COMMAND "$terminal" 2>/dev/null; then
|
||||
export LAUNCHTERM=$terminal
|
||||
break;
|
||||
fi
|
||||
done
|
||||
|
||||
# find the true shell behind x-terminal-emulator
|
||||
if [ "$LAUNCHTERM" = "x-terminal-emulator" ]; then
|
||||
export LAUNCHTERM="$(basename "$(readlink -f $(COMMAND x-terminal-emulator))")"
|
||||
fi
|
||||
|
||||
# run in selected terminal and account for quirks
|
||||
case $LAUNCHTERM in
|
||||
terminal|termite)
|
||||
# LAUNCHTERM consumes only one argument after -e
|
||||
# options containing space characters are unsupported
|
||||
exec $LAUNCHTERM -e "env TERM=xterm $LAUNCHER $@"
|
||||
;;
|
||||
xterm|konsole|alacritty)
|
||||
# LAUNCHTERM consumes all arguments after -e
|
||||
exec $LAUNCHTERM -e env TERM=xterm $LAUNCHER "$@"
|
||||
;;
|
||||
terminator|xfce4-terminal|mate-terminal)
|
||||
# LAUNCHTERM consumes all arguments after -x
|
||||
exec $LAUNCHTERM -x env TERM=xterm $LAUNCHER "$@"
|
||||
;;
|
||||
gnome-terminal)
|
||||
# LAUNCHTERM consumes all arguments after --
|
||||
exec $LAUNCHTERM -- env TERM=xterm $LAUNCHER "$@"
|
||||
;;
|
||||
kitty)
|
||||
# LAUNCHTERM consumes all trailing arguments
|
||||
exec $LAUNCHTERM env TERM=xterm $LAUNCHER "$@"
|
||||
;;
|
||||
*)
|
||||
# If we don't know the terminal, just try to run it in the current shell.
|
||||
env TERM=xterm $LAUNCHER "$@"
|
||||
# if THAT fails, launch with no output
|
||||
if [ $? -eq 127 ]; then
|
||||
exec $LAUNCHER --no-terminal "$@"
|
||||
fi
|
||||
esac
|
||||
##########
|
||||
## Read environment variables
|
||||
##########
|
||||
if [ "$SMAPI_NO_TERMINAL" == "true" ]; then
|
||||
SKIP_TERMINAL=true
|
||||
fi
|
||||
if [ "$SMAPI_USE_CURRENT_SHELL" == "true" ]; then
|
||||
USE_CURRENT_SHELL=true
|
||||
fi
|
||||
|
||||
|
||||
##########
|
||||
## Read command-line arguments
|
||||
##########
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--skip-terminal ) SKIP_TERMINAL=true; shift ;;
|
||||
--use-current-shell ) USE_CURRENT_SHELL=true; shift ;;
|
||||
-- ) shift; break ;;
|
||||
* ) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$SKIP_TERMINAL" == "true" ]; then
|
||||
USE_CURRENT_SHELL=true
|
||||
fi
|
||||
|
||||
|
||||
##########
|
||||
## Open terminal if needed
|
||||
##########
|
||||
# on macOS, make sure we're running in a Terminal
|
||||
# Besides letting the player see errors/warnings/alerts in the console, this is also needed because
|
||||
# Steam messes with the PATH.
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
if [ ! -t 1 ]; then # not open in Terminal (https://stackoverflow.com/q/911168/262123)
|
||||
# reopen in Terminal if needed
|
||||
# https://stackoverflow.com/a/29511052/262123
|
||||
if [ "$USE_CURRENT_SHELL" == "false" ]; then
|
||||
echo "Reopening in the Terminal app..."
|
||||
echo '#!/bin/sh' > /tmp/open-smapi-terminal.command
|
||||
echo "\"$0\" $@ --use-current-shell" >> /tmp/open-smapi-terminal.command
|
||||
chmod +x /tmp/open-smapi-terminal.command
|
||||
cat /tmp/open-smapi-terminal.command
|
||||
open -W /tmp/open-smapi-terminal.command
|
||||
rm /tmp/open-smapi-terminal.command
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
##########
|
||||
## Validate assumptions
|
||||
##########
|
||||
# script must be run from the game folder
|
||||
if [ ! -f "Stardew Valley.dll" ]; then
|
||||
printf "Oops! SMAPI must be placed in the Stardew Valley game folder.\nSee instructions: https://stardewvalleywiki.com/Modding:Player_Guide";
|
||||
read -r
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
##########
|
||||
## Launch SMAPI
|
||||
##########
|
||||
# macOS
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
./StardewModdingAPI "$@"
|
||||
|
||||
# Linux
|
||||
else
|
||||
# choose binary file to launch
|
||||
LAUNCH_FILE="./StardewModdingAPI"
|
||||
export LAUNCH_FILE
|
||||
|
||||
# run in terminal
|
||||
if [ "$USE_CURRENT_SHELL" == "false" ]; then
|
||||
# select terminal (prefer xterm for best compatibility, then known supported terminals)
|
||||
for terminal in xterm gnome-terminal kitty terminator xfce4-terminal konsole terminal termite alacritty mate-terminal x-terminal-emulator; do
|
||||
if command -v "$terminal" 2>/dev/null; then
|
||||
export TERMINAL_NAME=$terminal
|
||||
break;
|
||||
fi
|
||||
done
|
||||
|
||||
# find the true shell behind x-terminal-emulator
|
||||
if [ "$TERMINAL_NAME" = "x-terminal-emulator" ]; then
|
||||
TERMINAL_NAME="$(basename "$(readlink -f "$(command -v x-terminal-emulator)")")"
|
||||
export TERMINAL_NAME
|
||||
fi
|
||||
|
||||
# run in selected terminal and account for quirks
|
||||
TERMINAL_PATH="$(command -v "$TERMINAL_NAME")"
|
||||
export TERMINAL_PATH
|
||||
if [ -x "$TERMINAL_PATH" ]; then
|
||||
case $TERMINAL_NAME in
|
||||
terminal|termite)
|
||||
# consumes only one argument after -e
|
||||
# options containing space characters are unsupported
|
||||
exec "$TERMINAL_NAME" -e "env TERM=xterm $LAUNCH_FILE $@"
|
||||
;;
|
||||
|
||||
xterm|konsole|alacritty)
|
||||
# consumes all arguments after -e
|
||||
exec "$TERMINAL_NAME" -e env TERM=xterm $LAUNCH_FILE "$@"
|
||||
;;
|
||||
|
||||
terminator|xfce4-terminal|mate-terminal)
|
||||
# consumes all arguments after -x
|
||||
exec "$TERMINAL_NAME" -x env TERM=xterm $LAUNCH_FILE "$@"
|
||||
;;
|
||||
|
||||
gnome-terminal)
|
||||
# consumes all arguments after --
|
||||
exec "$TERMINAL_NAME" -- env TERM=xterm $LAUNCH_FILE "$@"
|
||||
;;
|
||||
|
||||
kitty)
|
||||
# consumes all trailing arguments
|
||||
exec "$TERMINAL_NAME" env TERM=xterm $LAUNCH_FILE "$@"
|
||||
;;
|
||||
|
||||
*)
|
||||
# If we don't know the terminal, just try to run it in the current shell.
|
||||
# If THAT fails, launch with no output.
|
||||
env TERM=xterm $LAUNCH_FILE "$@"
|
||||
if [ $? -eq 127 ]; then
|
||||
exec $LAUNCH_FILE --no-terminal "$@"
|
||||
fi
|
||||
esac
|
||||
|
||||
## terminal isn't executable; fallback to current shell or no terminal
|
||||
else
|
||||
echo "The '$TERMINAL_NAME' terminal isn't executable. SMAPI might be running in a sandbox or the system might be misconfigured? Falling back to current shell."
|
||||
env TERM=xterm $LAUNCH_FILE "$@"
|
||||
if [ $? -eq 127 ]; then
|
||||
exec $LAUNCH_FILE --no-terminal "$@"
|
||||
fi
|
||||
fi
|
||||
|
||||
# explicitly run without terminal
|
||||
elif [ "$SKIP_TERMINAL" == "true" ]; then
|
||||
exec $LAUNCH_FILE --no-terminal "$@"
|
||||
else
|
||||
exec $LAUNCH_FILE "$@"
|
||||
fi
|
||||
fi
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
@echo off
|
||||
echo %~dp0 | findstr /C:"%TEMP%" 1>nul
|
||||
if not errorlevel 1 (
|
||||
echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first.
|
||||
pause
|
||||
) else (
|
||||
start /WAIT /B internal/windows-install.exe
|
||||
)
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace StardewModdingAPI.Internal.Patching
|
||||
{
|
||||
/// <summary>Provides base implementation logic for <see cref="IPatcher"/> instances.</summary>
|
||||
internal abstract class BasePatcher : IPatcher
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <inheritdoc />
|
||||
public abstract void Apply(Harmony harmony, IMonitor monitor);
|
||||
|
||||
|
||||
/*********
|
||||
** Protected methods
|
||||
*********/
|
||||
/// <summary>Get a method and assert that it was found.</summary>
|
||||
/// <typeparam name="TTarget">The type containing the method.</typeparam>
|
||||
/// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
|
||||
protected ConstructorInfo RequireConstructor<TTarget>(params Type[] parameters)
|
||||
{
|
||||
return PatchHelper.RequireConstructor<TTarget>(parameters);
|
||||
}
|
||||
|
||||
/// <summary>Get a method and assert that it was found.</summary>
|
||||
/// <typeparam name="TTarget">The type containing the method.</typeparam>
|
||||
/// <param name="name">The method name.</param>
|
||||
/// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
|
||||
/// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param>
|
||||
protected MethodInfo RequireMethod<TTarget>(string name, Type[]? parameters = null, Type[]? generics = null)
|
||||
{
|
||||
return PatchHelper.RequireMethod<TTarget>(name, parameters, generics);
|
||||
}
|
||||
|
||||
/// <summary>Get a Harmony patch method on the current patcher instance.</summary>
|
||||
/// <param name="name">The method name.</param>
|
||||
/// <param name="priority">The patch priority to apply, usually specified using Harmony's <see cref="Priority"/> enum, or <c>null</c> to keep the default value.</param>
|
||||
protected HarmonyMethod GetHarmonyMethod(string name, int? priority = null)
|
||||
{
|
||||
HarmonyMethod method = new(
|
||||
AccessTools.Method(this.GetType(), name)
|
||||
?? throw new InvalidOperationException($"Can't find patcher method {PatchHelper.GetMethodString(this.GetType(), name)}.")
|
||||
);
|
||||
|
||||
if (priority.HasValue)
|
||||
method.priority = priority.Value;
|
||||
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace StardewModdingAPI.Internal.Patching
|
||||
{
|
||||
/// <summary>Simplifies applying <see cref="IPatcher"/> instances to the game.</summary>
|
||||
internal static class HarmonyPatcher
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Apply the given Harmony patchers.</summary>
|
||||
/// <param name="id">The mod ID applying the patchers.</param>
|
||||
/// <param name="monitor">The monitor with which to log any errors.</param>
|
||||
/// <param name="patchers">The patchers to apply.</param>
|
||||
public static Harmony Apply(string id, IMonitor monitor, params IPatcher[] patchers)
|
||||
{
|
||||
Harmony harmony = new(id);
|
||||
|
||||
foreach (IPatcher patcher in patchers)
|
||||
{
|
||||
try
|
||||
{
|
||||
patcher.Apply(harmony, monitor);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
monitor.Log($"Couldn't apply runtime patch '{patcher.GetType().Name}' to the game. Some SMAPI features may not work correctly. See log file for details.", LogLevel.Error);
|
||||
monitor.Log($"Technical details:\n{ex.GetLogSummary()}");
|
||||
}
|
||||
}
|
||||
|
||||
return harmony;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using HarmonyLib;
|
||||
|
||||
namespace StardewModdingAPI.Internal.Patching
|
||||
{
|
||||
/// <summary>A set of Harmony patches to apply.</summary>
|
||||
internal interface IPatcher
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Apply the Harmony patches for this instance.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
/// <param name="monitor">The monitor with which to log any errors.</param>
|
||||
public void Apply(Harmony harmony, IMonitor monitor);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace StardewModdingAPI.Internal.Patching
|
||||
{
|
||||
/// <summary>Provides utility methods for patching game code with Harmony.</summary>
|
||||
internal static class PatchHelper
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get a constructor and assert that it was found.</summary>
|
||||
/// <typeparam name="TTarget">The type containing the method.</typeparam>
|
||||
/// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
|
||||
/// <exception cref="InvalidOperationException">The type has no matching constructor.</exception>
|
||||
public static ConstructorInfo RequireConstructor<TTarget>(Type[]? parameters = null)
|
||||
{
|
||||
return
|
||||
AccessTools.Constructor(typeof(TTarget), parameters)
|
||||
?? throw new InvalidOperationException($"Can't find constructor {PatchHelper.GetMethodString(typeof(TTarget), null, parameters)} to patch.");
|
||||
}
|
||||
|
||||
/// <summary>Get a method and assert that it was found.</summary>
|
||||
/// <typeparam name="TTarget">The type containing the method.</typeparam>
|
||||
/// <param name="name">The method name.</param>
|
||||
/// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
|
||||
/// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param>
|
||||
/// <exception cref="InvalidOperationException">The type has no matching method.</exception>
|
||||
public static MethodInfo RequireMethod<TTarget>(string name, Type[]? parameters = null, Type[]? generics = null)
|
||||
{
|
||||
return
|
||||
AccessTools.Method(typeof(TTarget), name, parameters, generics)
|
||||
?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(typeof(TTarget), name, parameters, generics)} to patch.");
|
||||
}
|
||||
|
||||
/// <summary>Get a human-readable representation of a method target.</summary>
|
||||
/// <param name="type">The type containing the method.</param>
|
||||
/// <param name="name">The method name, or <c>null</c> for a constructor.</param>
|
||||
/// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
|
||||
/// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param>
|
||||
public static string GetMethodString(Type type, string? name, Type[]? parameters = null, Type[]? generics = null)
|
||||
{
|
||||
StringBuilder str = new();
|
||||
|
||||
// type
|
||||
str.Append(type.FullName);
|
||||
|
||||
// method name (if not constructor)
|
||||
if (name != null)
|
||||
{
|
||||
str.Append('.');
|
||||
str.Append(name);
|
||||
}
|
||||
|
||||
// generics
|
||||
if (generics?.Any() == true)
|
||||
{
|
||||
str.Append('<');
|
||||
str.Append(string.Join(", ", generics.Select(p => p.FullName)));
|
||||
str.Append('>');
|
||||
}
|
||||
|
||||
// parameters
|
||||
if (parameters?.Any() == true)
|
||||
{
|
||||
str.Append('(');
|
||||
str.Append(string.Join(", ", parameters.Select(p => p.FullName)));
|
||||
str.Append(')');
|
||||
}
|
||||
|
||||
return str.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' < '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<HasSharedItems>true</HasSharedItems>
|
||||
<SharedGUID>6c16e948-3e5c-47a7-bf4b-07a7469a87a5</SharedGUID>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Import_RootNamespace>SMAPI.Internal.Patching</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)BasePatcher.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HarmonyPatcher.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)IPatcher.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)PatchHelper.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>6c16e948-3e5c-47a7-bf4b-07a7469a87a5</ProjectGuid>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||
<PropertyGroup />
|
||||
<Import Project="SMAPI.Internal.Patching.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||
</Project>
|
|
@ -6,10 +6,26 @@ namespace StardewModdingAPI.Internal.ConsoleWriting
|
|||
/// <summary>The console color scheme options.</summary>
|
||||
internal class ColorSchemeConfig
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The default color scheme ID to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</summary>
|
||||
public MonitorColorScheme UseScheme { get; set; }
|
||||
public MonitorColorScheme UseScheme { get; }
|
||||
|
||||
/// <summary>The available console color schemes.</summary>
|
||||
public IDictionary<MonitorColorScheme, IDictionary<ConsoleLogLevel, ConsoleColor>> Schemes { get; set; }
|
||||
public IDictionary<MonitorColorScheme, IDictionary<ConsoleLogLevel, ConsoleColor>> Schemes { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="useScheme">The default color scheme ID to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
|
||||
/// <param name="schemes">The available console color schemes.</param>
|
||||
public ColorSchemeConfig(MonitorColorScheme useScheme, IDictionary<MonitorColorScheme, IDictionary<ConsoleLogLevel, ConsoleColor>> schemes)
|
||||
{
|
||||
this.UseScheme = useScheme;
|
||||
this.Schemes = schemes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
||||
namespace StardewModdingAPI.Internal.ConsoleWriting
|
||||
|
@ -11,10 +12,11 @@ namespace StardewModdingAPI.Internal.ConsoleWriting
|
|||
** Fields
|
||||
*********/
|
||||
/// <summary>The console text color for each log level.</summary>
|
||||
private readonly IDictionary<ConsoleLogLevel, ConsoleColor> Colors;
|
||||
private readonly IDictionary<ConsoleLogLevel, ConsoleColor>? Colors;
|
||||
|
||||
/// <summary>Whether the current console supports color formatting.</summary>
|
||||
private readonly bool SupportsColor;
|
||||
[MemberNotNullWhen(true, nameof(ColorfulConsoleWriter.Colors))]
|
||||
private bool SupportsColor { get; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -72,10 +74,9 @@ namespace StardewModdingAPI.Internal.ConsoleWriting
|
|||
/// <remarks>The colors here should be kept in sync with the SMAPI config file.</remarks>
|
||||
public static ColorSchemeConfig GetDefaultColorSchemeConfig(MonitorColorScheme useScheme)
|
||||
{
|
||||
return new ColorSchemeConfig
|
||||
{
|
||||
UseScheme = useScheme,
|
||||
Schemes = new Dictionary<MonitorColorScheme, IDictionary<ConsoleLogLevel, ConsoleColor>>
|
||||
return new ColorSchemeConfig(
|
||||
useScheme: useScheme,
|
||||
schemes: new Dictionary<MonitorColorScheme, IDictionary<ConsoleLogLevel, ConsoleColor>>
|
||||
{
|
||||
[MonitorColorScheme.DarkBackground] = new Dictionary<ConsoleLogLevel, ConsoleColor>
|
||||
{
|
||||
|
@ -98,7 +99,7 @@ namespace StardewModdingAPI.Internal.ConsoleWriting
|
|||
[ConsoleLogLevel.Success] = ConsoleColor.DarkGreen
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -129,12 +130,12 @@ namespace StardewModdingAPI.Internal.ConsoleWriting
|
|||
if (schemeID == MonitorColorScheme.AutoDetect)
|
||||
{
|
||||
schemeID = platform == Platform.Mac
|
||||
? MonitorColorScheme.LightBackground // MacOS doesn't provide console background color info, but it's usually white.
|
||||
? MonitorColorScheme.LightBackground // macOS doesn't provide console background color info, but it's usually white.
|
||||
: ColorfulConsoleWriter.IsDark(Console.BackgroundColor) ? MonitorColorScheme.DarkBackground : MonitorColorScheme.LightBackground;
|
||||
}
|
||||
|
||||
// get colors for scheme
|
||||
return colorConfig.Schemes.TryGetValue(schemeID, out IDictionary<ConsoleLogLevel, ConsoleColor> scheme)
|
||||
return colorConfig.Schemes.TryGetValue(schemeID, out IDictionary<ConsoleLogLevel, ConsoleColor>? scheme)
|
||||
? scheme
|
||||
: throw new NotSupportedException($"Unknown color scheme '{schemeID}'.");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace StardewModdingAPI.Internal
|
||||
{
|
||||
/// <summary>Provides extension methods for handling exceptions.</summary>
|
||||
internal static class ExceptionHelper
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get a string representation of an exception suitable for writing to the error log.</summary>
|
||||
/// <param name="exception">The error to summarize.</param>
|
||||
public static string GetLogSummary(this Exception? exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
string message;
|
||||
switch (exception)
|
||||
{
|
||||
case TypeLoadException ex:
|
||||
message = $"Failed loading type '{ex.TypeName}': {exception}";
|
||||
break;
|
||||
|
||||
case ReflectionTypeLoadException ex:
|
||||
string summary = ex.ToString();
|
||||
foreach (Exception? childEx in ex.LoaderExceptions)
|
||||
summary += $"\n\n{childEx?.GetLogSummary()}";
|
||||
message = summary;
|
||||
break;
|
||||
|
||||
default:
|
||||
message = exception?.ToString() ?? $"<null exception>\n{Environment.StackTrace}";
|
||||
break;
|
||||
}
|
||||
|
||||
return ExceptionHelper.SimplifyExtensionMessage(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed handling {exception?.GetType().FullName} (original message: {exception?.Message})", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Simplify common patterns in exception log messages that don't convey useful info.</summary>
|
||||
/// <param name="message">The log message to simplify.</param>
|
||||
public static string SimplifyExtensionMessage(string message)
|
||||
{
|
||||
// remove namespace for core exception types
|
||||
message = Regex.Replace(
|
||||
message,
|
||||
@"(?:StardewModdingAPI\.Framework\.Exceptions|Microsoft\.Xna\.Framework|System|System\.IO)\.([a-zA-Z]+Exception):",
|
||||
"$1:"
|
||||
);
|
||||
|
||||
// remove unneeded root build paths for SMAPI and Stardew Valley
|
||||
message = message
|
||||
.Replace(@"E:\source\_Stardew\SMAPI\src\", "")
|
||||
.Replace(@"C:\GitlabRunner\builds\Gq5qA5P4\0\ConcernedApe\", "");
|
||||
|
||||
// remove placeholder info in Linux/macOS stack traces
|
||||
return message
|
||||
.Replace(@"<filename unknown>:0", "");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,5 +14,6 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ConsoleLogLevel.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\IConsoleWriter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ExceptionHelper.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,6 +1,7 @@
|
|||
// <generated />
|
||||
using Microsoft.CodeAnalysis;
|
||||
// ReSharper disable All -- generated code
|
||||
using System;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
||||
{
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
// <generated />
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
// ReSharper disable All -- generated code
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
||||
{
|
||||
|
@ -51,17 +53,17 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents)
|
||||
{
|
||||
var projects = new HashSet<Project>();
|
||||
foreach (var document in documents)
|
||||
foreach (Document document in documents)
|
||||
{
|
||||
projects.Add(document.Project);
|
||||
}
|
||||
|
||||
var diagnostics = new List<Diagnostic>();
|
||||
foreach (var project in projects)
|
||||
foreach (Project project in projects)
|
||||
{
|
||||
var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer));
|
||||
CompilationWithAnalyzers compilationWithAnalyzers = project.GetCompilationAsync().Result!.WithAnalyzers(ImmutableArray.Create(analyzer));
|
||||
var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result;
|
||||
foreach (var diag in diags)
|
||||
foreach (Diagnostic diag in diags)
|
||||
{
|
||||
if (diag.Location == Location.None || diag.Location.IsInMetadata)
|
||||
{
|
||||
|
@ -71,8 +73,8 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
{
|
||||
for (int i = 0; i < documents.Length; i++)
|
||||
{
|
||||
var document = documents[i];
|
||||
var tree = document.GetSyntaxTreeAsync().Result;
|
||||
Document document = documents[i];
|
||||
SyntaxTree? tree = document.GetSyntaxTreeAsync().Result;
|
||||
if (tree == diag.Location.SourceTree)
|
||||
{
|
||||
diagnostics.Add(diag);
|
||||
|
@ -113,7 +115,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
throw new ArgumentException("Unsupported Language");
|
||||
}
|
||||
|
||||
var project = CreateProject(sources, language);
|
||||
Project project = CreateProject(sources, language);
|
||||
var documents = project.Documents.ToArray();
|
||||
|
||||
if (sources.Length != documents.Length)
|
||||
|
@ -124,17 +126,6 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
return documents;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a Document from a string through creating a project that contains it.
|
||||
/// </summary>
|
||||
/// <param name="source">Classes in the form of a string</param>
|
||||
/// <param name="language">The language the source code is in</param>
|
||||
/// <returns>A Document created from the source string</returns>
|
||||
protected static Document CreateDocument(string source, string language = LanguageNames.CSharp)
|
||||
{
|
||||
return CreateProject(new[] { source }, language).Documents.First();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a project using the inputted strings as sources.
|
||||
/// </summary>
|
||||
|
@ -146,9 +137,9 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
string fileNamePrefix = DefaultFilePathPrefix;
|
||||
string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt;
|
||||
|
||||
var projectId = ProjectId.CreateNewId(debugName: TestProjectName);
|
||||
ProjectId projectId = ProjectId.CreateNewId(debugName: TestProjectName);
|
||||
|
||||
var solution = new AdhocWorkspace()
|
||||
Solution solution = new AdhocWorkspace()
|
||||
.CurrentSolution
|
||||
.AddProject(projectId, TestProjectName, TestProjectName, language)
|
||||
.AddMetadataReference(projectId, DiagnosticVerifier.SelfReference)
|
||||
|
@ -158,14 +149,14 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
.AddMetadataReference(projectId, CodeAnalysisReference);
|
||||
|
||||
int count = 0;
|
||||
foreach (var source in sources)
|
||||
foreach (string source in sources)
|
||||
{
|
||||
var newFileName = fileNamePrefix + count + "." + fileExt;
|
||||
var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName);
|
||||
string newFileName = fileNamePrefix + count + "." + fileExt;
|
||||
DocumentId documentId = DocumentId.CreateNewId(projectId, debugName: newFileName);
|
||||
solution = solution.AddDocument(documentId, newFileName, SourceText.From(source));
|
||||
count++;
|
||||
}
|
||||
return solution.GetProject(projectId);
|
||||
return solution.GetProject(projectId)!;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// <generated />
|
||||
// ReSharper disable All -- generated code
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
@ -17,18 +19,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
/// <summary>
|
||||
/// Get the CSharp analyzer being tested - to be implemented in non-abstract class
|
||||
/// </summary>
|
||||
protected virtual DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class
|
||||
/// </summary>
|
||||
protected virtual DiagnosticAnalyzer GetBasicDiagnosticAnalyzer()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
protected abstract DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer();
|
||||
#endregion
|
||||
|
||||
#region Verifier wrappers
|
||||
|
@ -41,18 +32,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
/// <param name="expected"> DiagnosticResults that should appear after the analyzer is run on the source</param>
|
||||
protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected)
|
||||
{
|
||||
VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source
|
||||
/// Note: input a DiagnosticResult for each Diagnostic expected
|
||||
/// </summary>
|
||||
/// <param name="sources">An array of strings to create source documents from to run the analyzers on</param>
|
||||
/// <param name="expected">DiagnosticResults that should appear after the analyzer is run on the sources</param>
|
||||
protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected)
|
||||
{
|
||||
VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected);
|
||||
this.VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzer(), expected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -65,8 +45,8 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
/// <param name="expected">DiagnosticResults that should appear after the analyzer is run on the sources</param>
|
||||
private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expected)
|
||||
{
|
||||
var diagnostics = GetSortedDiagnostics(sources, language, analyzer);
|
||||
VerifyDiagnosticResults(diagnostics, analyzer, expected);
|
||||
var diagnostics = DiagnosticVerifier.GetSortedDiagnostics(sources, language, analyzer);
|
||||
DiagnosticVerifier.VerifyDiagnosticResults(diagnostics, analyzer, expected);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -86,7 +66,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
|
||||
if (expectedCount != actualCount)
|
||||
{
|
||||
string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE.";
|
||||
string diagnosticsOutput = actualResults.Any() ? DiagnosticVerifier.FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE.";
|
||||
|
||||
Assert.IsTrue(false,
|
||||
string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput));
|
||||
|
@ -103,12 +83,12 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
{
|
||||
Assert.IsTrue(false,
|
||||
string.Format("Expected:\nA project diagnostic with No location\nActual:\n{0}",
|
||||
FormatDiagnostics(analyzer, actual)));
|
||||
DiagnosticVerifier.FormatDiagnostics(analyzer, actual)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First());
|
||||
DiagnosticVerifier.VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First());
|
||||
var additionalLocations = actual.AdditionalLocations.ToArray();
|
||||
|
||||
if (additionalLocations.Length != expected.Locations.Length - 1)
|
||||
|
@ -116,12 +96,12 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
Assert.IsTrue(false,
|
||||
string.Format("Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n",
|
||||
expected.Locations.Length - 1, additionalLocations.Length,
|
||||
FormatDiagnostics(analyzer, actual)));
|
||||
DiagnosticVerifier.FormatDiagnostics(analyzer, actual)));
|
||||
}
|
||||
|
||||
for (int j = 0; j < additionalLocations.Length; ++j)
|
||||
{
|
||||
VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]);
|
||||
DiagnosticVerifier.VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,21 +109,21 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
{
|
||||
Assert.IsTrue(false,
|
||||
string.Format("Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
|
||||
expected.Id, actual.Id, FormatDiagnostics(analyzer, actual)));
|
||||
expected.Id, actual.Id, DiagnosticVerifier.FormatDiagnostics(analyzer, actual)));
|
||||
}
|
||||
|
||||
if (actual.Severity != expected.Severity)
|
||||
{
|
||||
Assert.IsTrue(false,
|
||||
string.Format("Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
|
||||
expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual)));
|
||||
expected.Severity, actual.Severity, DiagnosticVerifier.FormatDiagnostics(analyzer, actual)));
|
||||
}
|
||||
|
||||
if (actual.GetMessage() != expected.Message)
|
||||
{
|
||||
Assert.IsTrue(false,
|
||||
string.Format("Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
|
||||
expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual)));
|
||||
expected.Message, actual.GetMessage(), DiagnosticVerifier.FormatDiagnostics(analyzer, actual)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,7 +141,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
|
||||
Assert.IsTrue(actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")),
|
||||
string.Format("Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
|
||||
expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic)));
|
||||
expected.Path, actualSpan.Path, DiagnosticVerifier.FormatDiagnostics(analyzer, diagnostic)));
|
||||
|
||||
var actualLinePosition = actualSpan.StartLinePosition;
|
||||
|
||||
|
@ -172,7 +152,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
{
|
||||
Assert.IsTrue(false,
|
||||
string.Format("Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
|
||||
expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic)));
|
||||
expected.Line, actualLinePosition.Line + 1, DiagnosticVerifier.FormatDiagnostics(analyzer, diagnostic)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,7 +163,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
{
|
||||
Assert.IsTrue(false,
|
||||
string.Format("Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
|
||||
expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic)));
|
||||
expected.Column, actualLinePosition.Character + 1, DiagnosticVerifier.FormatDiagnostics(analyzer, diagnostic)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +181,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
var builder = new StringBuilder();
|
||||
for (int i = 0; i < diagnostics.Length; ++i)
|
||||
{
|
||||
builder.AppendLine("// " + diagnostics[i].ToString());
|
||||
builder.AppendLine("// " + diagnostics[i]);
|
||||
|
||||
var analyzerType = analyzer.GetType();
|
||||
var rules = analyzer.SupportedDiagnostics;
|
||||
|
@ -220,11 +200,10 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework
|
|||
Assert.IsTrue(location.IsInSource,
|
||||
$"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}\r\n");
|
||||
|
||||
string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt";
|
||||
var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition;
|
||||
|
||||
builder.AppendFormat("{0}({1}, {2}, {3}.{4})",
|
||||
resultMethodName,
|
||||
"GetCSharpResultAt",
|
||||
linePosition.Line + 1,
|
||||
linePosition.Character + 1,
|
||||
analyzerType.Name,
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
// ReSharper disable CheckNamespace -- matches Stardew Valley's code
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Netcode
|
||||
{
|
||||
/// <summary>A simplified version of Stardew Valley's <c>Netcode.NetCollection</c> for unit testing.</summary>
|
||||
public class NetCollection<T> : Collection<T>, IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable { }
|
||||
public class NetCollection<T> : Collection<T> { }
|
||||
}
|
||||
|
|
|
@ -7,10 +7,13 @@ namespace Netcode
|
|||
public class NetFieldBase<T, TSelf> where TSelf : NetFieldBase<T, TSelf>
|
||||
{
|
||||
/// <summary>The synchronised value.</summary>
|
||||
public T Value { get; set; }
|
||||
public T? Value { get; set; }
|
||||
|
||||
/// <summary>Implicitly convert a net field to the its type.</summary>
|
||||
/// <param name="field">The field to convert.</param>
|
||||
public static implicit operator T(NetFieldBase<T, TSelf> field) => field.Value;
|
||||
public static implicit operator T?(NetFieldBase<T, TSelf> field)
|
||||
{
|
||||
return field.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
// ReSharper disable CheckNamespace -- matches Stardew Valley's code
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Netcode
|
||||
{
|
||||
/// <summary>A simplified version of Stardew Valley's <c>Netcode.NetObjectList</c> for unit testing.</summary>
|
||||
public class NetList<T> : List<T>, IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable { }
|
||||
public class NetList<T> : List<T> { }
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// ReSharper disable CheckNamespace, InconsistentNaming -- matches Stardew Valley's code
|
||||
#pragma warning disable 649 // (never assigned) -- only used to test type conversions
|
||||
// ReSharper disable UnusedMember.Global -- used dynamically for unit tests
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StardewValley
|
||||
|
@ -8,6 +8,6 @@ namespace StardewValley
|
|||
internal class Farmer
|
||||
{
|
||||
/// <summary>A sample field which should be replaced with a different property.</summary>
|
||||
public readonly IDictionary<string, int[]> friendships;
|
||||
public readonly IDictionary<string, int[]> friendships = new Dictionary<string, int[]>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// ReSharper disable CheckNamespace, InconsistentNaming -- matches Stardew Valley's code
|
||||
// ReSharper disable UnusedMember.Global -- used dynamically for unit tests
|
||||
using Netcode;
|
||||
|
||||
namespace StardewValley
|
||||
|
@ -7,27 +8,27 @@ namespace StardewValley
|
|||
public class Item
|
||||
{
|
||||
/// <summary>A net int field with an equivalent non-net <c>Category</c> property.</summary>
|
||||
public readonly NetInt category = new NetInt { Value = 42 };
|
||||
public readonly NetInt category = new() { Value = 42 };
|
||||
|
||||
/// <summary>A generic net int field with no equivalent non-net property.</summary>
|
||||
public readonly NetInt netIntField = new NetInt { Value = 42 };
|
||||
public readonly NetInt netIntField = new() { Value = 42 };
|
||||
|
||||
/// <summary>A generic net ref field with no equivalent non-net property.</summary>
|
||||
public readonly NetRef<object> netRefField = new NetRef<object>();
|
||||
public readonly NetRef<object> netRefField = new();
|
||||
|
||||
/// <summary>A generic net int property with no equivalent non-net property.</summary>
|
||||
public NetInt netIntProperty = new NetInt { Value = 42 };
|
||||
public NetInt netIntProperty = new() { Value = 42 };
|
||||
|
||||
/// <summary>A generic net ref property with no equivalent non-net property.</summary>
|
||||
public NetRef<object> netRefProperty { get; } = new NetRef<object>();
|
||||
public NetRef<object> netRefProperty { get; } = new();
|
||||
|
||||
/// <summary>A sample net list.</summary>
|
||||
public readonly NetList<int> netList = new NetList<int>();
|
||||
public readonly NetList<int> netList = new();
|
||||
|
||||
/// <summary>A sample net object list.</summary>
|
||||
public readonly NetObjectList<int> netObjectList = new NetObjectList<int>();
|
||||
public readonly NetObjectList<int> netObjectList = new();
|
||||
|
||||
/// <summary>A sample net collection.</summary>
|
||||
public readonly NetCollection<int> netCollection = new NetCollection<int>();
|
||||
public readonly NetCollection<int> netCollection = new();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,6 @@ namespace StardewValley
|
|||
public class Object : Item
|
||||
{
|
||||
/// <summary>A net int field with an equivalent non-net property.</summary>
|
||||
public NetInt type = new NetInt { Value = 42 };
|
||||
public NetInt type = new() { Value = 42 };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,13 +87,13 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests
|
|||
[TestCase("SObject obj = null; if (obj.netRefProperty != null);", 24, "obj.netRefProperty", "NetRef", "object")]
|
||||
[TestCase("Item item = new Item(); object list = item.netList;", 38, "item.netList", "NetList", "object")] // ↓ NetList field converted to a non-interface type
|
||||
[TestCase("Item item = new Item(); object list = item.netCollection;", 38, "item.netCollection", "NetCollection", "object")]
|
||||
[TestCase("Item item = new Item(); int x = (int)item.netIntField;", 32, "item.netIntField", "NetInt", "int")] // ↓ explicit conversion to invalid type
|
||||
[TestCase("Item item = new Item(); int x = (int)item.netIntField;", 32, "item.netIntField", "NetFieldBase", "int")] // ↓ explicit conversion to invalid type
|
||||
[TestCase("Item item = new Item(); int x = item.netRefField as object;", 32, "item.netRefField", "NetRef", "object")]
|
||||
public void AvoidImplicitNetFieldComparisons_RaisesDiagnostic(string codeText, int column, string expression, string fromType, string toType)
|
||||
{
|
||||
// arrange
|
||||
string code = NetFieldAnalyzerTests.SampleProgram.Replace("{{test-code}}", codeText);
|
||||
DiagnosticResult expected = new DiagnosticResult
|
||||
DiagnosticResult expected = new()
|
||||
{
|
||||
Id = "AvoidImplicitNetFieldCast",
|
||||
Message = $"This implicitly converts '{expression}' from {fromType} to {toType}, but {fromType} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/package/avoid-implicit-net-field-cast for details.",
|
||||
|
@ -135,7 +135,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests
|
|||
{
|
||||
// arrange
|
||||
string code = NetFieldAnalyzerTests.SampleProgram.Replace("{{test-code}}", codeText);
|
||||
DiagnosticResult expected = new DiagnosticResult
|
||||
DiagnosticResult expected = new()
|
||||
{
|
||||
Id = "AvoidNetField",
|
||||
Message = $"'{expression}' is a {netType} field; consider using the {suggestedProperty} property instead. See https://smapi.io/package/avoid-net-field for details.",
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests
|
|||
{
|
||||
// arrange
|
||||
string code = ObsoleteFieldAnalyzerTests.SampleProgram.Replace("{{test-code}}", codeText);
|
||||
DiagnosticResult expected = new DiagnosticResult
|
||||
DiagnosticResult expected = new()
|
||||
{
|
||||
Id = "AvoidObsoleteField",
|
||||
Message = $"The '{oldName}' field is obsolete and should be replaced with '{newName}'. See https://smapi.io/package/avoid-obsolete-field for details.",
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.10.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -20,5 +16,4 @@
|
|||
</ItemGroup>
|
||||
|
||||
<Import Project="..\..\build\common.targets" />
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
## Release 2.1.0
|
||||
### New Rules
|
||||
Rule ID | Category | Severity | Notes
|
||||
------------------------- | ------------------ | -------- | ------------------------------------------------------------
|
||||
AvoidImplicitNetFieldCast | SMAPI.CommonErrors | Warning | See [documentation](https://smapi.io/package/code-warnings).
|
||||
AvoidNetField | SMAPI.CommonErrors | Warning | See [documentation](https://smapi.io/package/code-warnings).
|
||||
AvoidObsoleteField | SMAPI.CommonErrors | Warning | See [documentation](https://smapi.io/package/code-warnings).
|
|
@ -40,8 +40,8 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
|||
|
||||
// invalid
|
||||
fromExpression = null;
|
||||
fromType = default(TypeInfo);
|
||||
toType = default(TypeInfo);
|
||||
fromType = default;
|
||||
toType = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
|||
}
|
||||
|
||||
// conditional access
|
||||
if (node is ConditionalAccessExpressionSyntax conditionalAccess && conditionalAccess.WhenNotNull is MemberBindingExpressionSyntax conditionalBinding)
|
||||
if (node is ConditionalAccessExpressionSyntax { WhenNotNull: MemberBindingExpressionSyntax conditionalBinding } conditionalAccess)
|
||||
{
|
||||
declaringType = semanticModel.GetTypeInfo(conditionalAccess.Expression).Type;
|
||||
memberType = semanticModel.GetTypeInfo(node);
|
||||
|
@ -74,7 +74,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
|||
|
||||
// invalid
|
||||
declaringType = null;
|
||||
memberType = default(TypeInfo);
|
||||
memberType = default;
|
||||
memberName = null;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
|||
};
|
||||
|
||||
/// <summary>The diagnostic info for an implicit net field cast.</summary>
|
||||
private readonly DiagnosticDescriptor AvoidImplicitNetFieldCastRule = new DiagnosticDescriptor(
|
||||
private readonly DiagnosticDescriptor AvoidImplicitNetFieldCastRule = new(
|
||||
id: "AvoidImplicitNetFieldCast",
|
||||
title: "Netcode types shouldn't be implicitly converted",
|
||||
messageFormat: "This implicitly converts '{0}' from {1} to {2}, but {1} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/package/avoid-implicit-net-field-cast for details.",
|
||||
|
@ -143,7 +143,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
|||
);
|
||||
|
||||
/// <summary>The diagnostic info for an avoidable net field access.</summary>
|
||||
private readonly DiagnosticDescriptor AvoidNetFieldRule = new DiagnosticDescriptor(
|
||||
private readonly DiagnosticDescriptor AvoidNetFieldRule = new(
|
||||
id: "AvoidNetField",
|
||||
title: "Avoid Netcode types when possible",
|
||||
messageFormat: "'{0}' is a {1} field; consider using the {2} property instead. See https://smapi.io/package/avoid-net-field for details.",
|
||||
|
@ -174,6 +174,9 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
|||
/// <param name="context">The analysis context.</param>
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||
context.EnableConcurrentExecution();
|
||||
|
||||
context.RegisterSyntaxNodeAction(
|
||||
this.AnalyzeMemberAccess,
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
|
@ -224,10 +227,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
|||
|
||||
// warn: implicit conversion
|
||||
if (this.IsInvalidConversion(memberType.Type, memberType.ConvertedType))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(this.AvoidImplicitNetFieldCastRule, context.Node.GetLocation(), context.Node, memberType.Type.Name, memberType.ConvertedType));
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -312,7 +312,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
|||
return false;
|
||||
|
||||
// conversion to implemented interface is OK
|
||||
if (fromType.AllInterfaces.Contains(toType))
|
||||
if (fromType.AllInterfaces.Contains(toType, SymbolEqualityComparer.Default))
|
||||
return false;
|
||||
|
||||
// avoid any other conversions
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
|||
/// <summary>Describes the diagnostic rule covered by the analyzer.</summary>
|
||||
private readonly IDictionary<string, DiagnosticDescriptor> Rules = new Dictionary<string, DiagnosticDescriptor>
|
||||
{
|
||||
["AvoidObsoleteField"] = new DiagnosticDescriptor(
|
||||
["AvoidObsoleteField"] = new(
|
||||
id: "AvoidObsoleteField",
|
||||
title: "Reference to obsolete field",
|
||||
messageFormat: "The '{0}' field is obsolete and should be replaced with '{1}'. See https://smapi.io/package/avoid-obsolete-field for details.",
|
||||
|
@ -56,6 +56,9 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
|||
/// <param name="context">The analysis context.</param>
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||
context.EnableConcurrentExecution();
|
||||
|
||||
context.RegisterSyntaxNodeAction(
|
||||
this.AnalyzeObsoleteFields,
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
|
@ -74,7 +77,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
|||
try
|
||||
{
|
||||
// get reference info
|
||||
if (!AnalyzerUtilities.TryGetMemberInfo(context.Node, context.SemanticModel, out ITypeSymbol declaringType, out TypeInfo memberType, out string memberName))
|
||||
if (!AnalyzerUtilities.TryGetMemberInfo(context.Node, context.SemanticModel, out ITypeSymbol declaringType, out _, out string memberName))
|
||||
return;
|
||||
|
||||
// suggest replacement
|
||||
|
|
|
@ -9,8 +9,11 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.10.0" PrivateAssets="all" />
|
||||
<PackageReference Update="NETStandard.Library" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -7,7 +7,12 @@ using System.Reflection;
|
|||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
using Newtonsoft.Json;
|
||||
using StardewModdingAPI.ModBuildConfig.Framework;
|
||||
using StardewModdingAPI.Toolkit.Framework;
|
||||
using StardewModdingAPI.Toolkit.Serialization;
|
||||
using StardewModdingAPI.Toolkit.Serialization.Models;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
||||
namespace StardewModdingAPI.ModBuildConfig
|
||||
{
|
||||
|
@ -17,6 +22,10 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The name (without extension or path) of the current mod's DLL.</summary>
|
||||
[Required]
|
||||
public string ModDllName { get; set; }
|
||||
|
||||
/// <summary>The name of the mod folder.</summary>
|
||||
[Required]
|
||||
public string ModFolderName { get; set; }
|
||||
|
@ -45,9 +54,15 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
[Required]
|
||||
public bool EnableModZip { get; set; }
|
||||
|
||||
/// <summary>Custom comma-separated regex patterns matching files to ignore when deploying or zipping the mod.</summary>
|
||||
/// <summary>A comma-separated list of regex patterns matching files to ignore when deploying or zipping the mod.</summary>
|
||||
public string IgnoreModFilePatterns { get; set; }
|
||||
|
||||
/// <summary>A comma-separated list of relative file paths to ignore when deploying or zipping the mod.</summary>
|
||||
public string IgnoreModFilePaths { get; set; }
|
||||
|
||||
/// <summary>A comma-separated list of <see cref="ExtraAssemblyTypes"/> values which indicate which extra DLLs to bundle.</summary>
|
||||
public string BundleExtraAssemblies { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -64,16 +79,52 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
this.Log.LogMessage(MessageImportance.High, $"[mod build package] Handling build with options {string.Join(", ", properties)}");
|
||||
}
|
||||
|
||||
// skip if nothing to do
|
||||
// (This must be checked before the manifest validation, to allow cases like unit test projects.)
|
||||
if (!this.EnableModDeploy && !this.EnableModZip)
|
||||
return true; // nothing to do
|
||||
return true;
|
||||
|
||||
// validate the manifest file
|
||||
IManifest manifest;
|
||||
{
|
||||
try
|
||||
{
|
||||
string manifestPath = Path.Combine(this.ProjectDir, "manifest.json");
|
||||
if (!new JsonHelper().ReadJsonFileIfExists(manifestPath, out Manifest rawManifest))
|
||||
{
|
||||
this.Log.LogError("[mod build package] The mod's manifest.json file doesn't exist.");
|
||||
return false;
|
||||
}
|
||||
manifest = rawManifest;
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
{
|
||||
// log the inner exception, otherwise the message will be generic
|
||||
Exception exToShow = ex.InnerException ?? ex;
|
||||
this.Log.LogError($"[mod build package] The mod's manifest.json file isn't valid JSON: {exToShow.Message}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate manifest fields
|
||||
if (!ManifestValidator.TryValidateFields(manifest, out string error))
|
||||
{
|
||||
this.Log.LogError($"[mod build package] The mod's manifest.json file is invalid: {error}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// deploy files
|
||||
try
|
||||
{
|
||||
// parse extra DLLs to bundle
|
||||
ExtraAssemblyTypes bundleAssemblyTypes = this.GetExtraAssembliesToBundleOption();
|
||||
|
||||
// parse ignore patterns
|
||||
string[] ignoreFilePaths = this.GetCustomIgnoreFilePaths().ToArray();
|
||||
Regex[] ignoreFilePatterns = this.GetCustomIgnorePatterns().ToArray();
|
||||
|
||||
// get mod info
|
||||
ModFileManager package = new ModFileManager(this.ProjectDir, this.TargetDir, ignoreFilePatterns, validateRequiredModFiles: this.EnableModDeploy || this.EnableModZip);
|
||||
ModFileManager package = new(this.ProjectDir, this.TargetDir, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, this.ModDllName, validateRequiredModFiles: this.EnableModDeploy || this.EnableModZip);
|
||||
|
||||
// deploy mod files
|
||||
if (this.EnableModDeploy)
|
||||
|
@ -86,7 +137,7 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
// create release zip
|
||||
if (this.EnableModZip)
|
||||
{
|
||||
string zipName = this.EscapeInvalidFilenameCharacters($"{this.ModFolderName} {package.GetManifestVersion()}.zip");
|
||||
string zipName = this.EscapeInvalidFilenameCharacters($"{this.ModFolderName} {manifest.Version}.zip");
|
||||
string zipPath = Path.Combine(this.ModZipPath, zipName);
|
||||
|
||||
this.Log.LogMessage(MessageImportance.High, $"[mod build package] Generating the release zip at {zipPath}...");
|
||||
|
@ -134,6 +185,28 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>Parse the extra assembly types which should be bundled with the mod.</summary>
|
||||
private ExtraAssemblyTypes GetExtraAssembliesToBundleOption()
|
||||
{
|
||||
ExtraAssemblyTypes flags = ExtraAssemblyTypes.None;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this.BundleExtraAssemblies))
|
||||
{
|
||||
foreach (string raw in this.BundleExtraAssemblies.Split(','))
|
||||
{
|
||||
if (!Enum.TryParse(raw, out ExtraAssemblyTypes type))
|
||||
{
|
||||
this.Log.LogWarning($"[mod build package] Ignored invalid <{nameof(this.BundleExtraAssemblies)}> value '{raw}', expected one of '{string.Join("', '", Enum.GetNames(typeof(ExtraAssemblyTypes)))}'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
flags |= type;
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/// <summary>Get the custom ignore patterns provided by the user.</summary>
|
||||
private IEnumerable<Regex> GetCustomIgnorePatterns()
|
||||
{
|
||||
|
@ -157,6 +230,29 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>Get the custom relative file paths provided by the user to ignore.</summary>
|
||||
private IEnumerable<string> GetCustomIgnoreFilePaths()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(this.IgnoreModFilePaths))
|
||||
yield break;
|
||||
|
||||
foreach (string raw in this.IgnoreModFilePaths.Split(','))
|
||||
{
|
||||
string path;
|
||||
try
|
||||
{
|
||||
path = PathUtilities.NormalizePath(raw);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Log.LogWarning($"[mod build package] Ignored invalid <{nameof(this.IgnoreModFilePaths)}> path {raw}:\n{ex}");
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return path;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Copy the mod files into the game's mod folder.</summary>
|
||||
/// <param name="files">The files to include.</param>
|
||||
/// <param name="modFolderPath">The folder path to create with the mod files.</param>
|
||||
|
@ -167,8 +263,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);
|
||||
}
|
||||
|
@ -186,7 +281,7 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
// create zip file
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(zipPath)!);
|
||||
using Stream zipStream = new FileStream(zipPath, FileMode.Create, FileAccess.Write);
|
||||
using ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create);
|
||||
using ZipArchive archive = new(zipStream, ZipArchiveMode.Create);
|
||||
|
||||
foreach (var fileEntry in files)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
|
||||
namespace StardewModdingAPI.ModBuildConfig.Framework
|
||||
{
|
||||
/// <summary>An extra assembly type for the <see cref="DeployModTask.BundleExtraAssemblies"/> field.</summary>
|
||||
[Flags]
|
||||
internal enum ExtraAssemblyTypes
|
||||
{
|
||||
/// <summary>Don't include extra assemblies.</summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>Assembly files which are part of MonoGame, SMAPI, or Stardew Valley.</summary>
|
||||
Game = 1,
|
||||
|
||||
/// <summary>Assembly files whose names start with <c>Microsoft.*</c> or <c>System.*</c>.</summary>
|
||||
System = 2,
|
||||
|
||||
/// <summary>Assembly files which don't match any other category.</summary>
|
||||
ThirdParty = 4
|
||||
}
|
||||
}
|
|
@ -3,8 +3,6 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using StardewModdingAPI.Toolkit.Serialization;
|
||||
using StardewModdingAPI.Toolkit.Serialization.Models;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
||||
namespace StardewModdingAPI.ModBuildConfig.Framework
|
||||
|
@ -21,6 +19,45 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
|||
/// <summary>The files that are part of the package.</summary>
|
||||
private readonly IDictionary<string, FileInfo> Files;
|
||||
|
||||
/// <summary>The file extensions used by assembly files.</summary>
|
||||
private readonly ISet<string> AssemblyFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".dll",
|
||||
".exe",
|
||||
".pdb",
|
||||
".xml"
|
||||
};
|
||||
|
||||
/// <summary>The DLLs which match the <see cref="ExtraAssemblyTypes.Game"/> type.</summary>
|
||||
private readonly ISet<string> GameDllNames = new HashSet<string>
|
||||
{
|
||||
// SMAPI
|
||||
"0Harmony",
|
||||
"Mono.Cecil",
|
||||
"Mono.Cecil.Mdb",
|
||||
"Mono.Cecil.Pdb",
|
||||
"MonoMod.Common",
|
||||
"Newtonsoft.Json",
|
||||
"StardewModdingAPI",
|
||||
"SMAPI.Toolkit",
|
||||
"SMAPI.Toolkit.CoreInterfaces",
|
||||
"TMXTile",
|
||||
|
||||
// game + framework
|
||||
"BmFont",
|
||||
"FAudio-CS",
|
||||
"GalaxyCSharp",
|
||||
"GalaxyCSharpGlue",
|
||||
"Lidgren.Network",
|
||||
"MonoGame.Framework",
|
||||
"SkiaSharp",
|
||||
"Stardew Valley",
|
||||
"StardewValley.GameData",
|
||||
"Steamworks.NET",
|
||||
"TextCopy",
|
||||
"xTile"
|
||||
};
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -28,10 +65,13 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
|||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="projectDir">The folder containing the project files.</param>
|
||||
/// <param name="targetDir">The folder containing the build output.</param>
|
||||
/// <param name="ignoreFilePaths">The custom relative file paths provided by the user to ignore.</param>
|
||||
/// <param name="ignoreFilePatterns">Custom regex patterns matching files to ignore when deploying or zipping the mod.</param>
|
||||
/// <param name="bundleAssemblyTypes">The extra assembly types which should be bundled with the mod.</param>
|
||||
/// <param name="modDllName">The name (without extension or path) for the current mod's DLL.</param>
|
||||
/// <param name="validateRequiredModFiles">Whether to validate that required mod files like the manifest are present.</param>
|
||||
/// <exception cref="UserErrorException">The mod package isn't valid.</exception>
|
||||
public ModFileManager(string projectDir, string targetDir, Regex[] ignoreFilePatterns, bool validateRequiredModFiles)
|
||||
public ModFileManager(string projectDir, string targetDir, string[] ignoreFilePaths, Regex[] ignoreFilePatterns, ExtraAssemblyTypes bundleAssemblyTypes, string modDllName, bool validateRequiredModFiles)
|
||||
{
|
||||
this.Files = new Dictionary<string, FileInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
|
@ -47,7 +87,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
|||
string relativePath = entry.Item1;
|
||||
FileInfo file = entry.Item2;
|
||||
|
||||
if (!this.ShouldIgnore(file, relativePath, ignoreFilePatterns))
|
||||
if (!this.ShouldIgnore(file, relativePath, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, modDllName))
|
||||
this.Files[relativePath] = file;
|
||||
}
|
||||
|
||||
|
@ -71,16 +111,6 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
|||
return new Dictionary<string, FileInfo>(this.Files, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Get a semantic version from the mod manifest.</summary>
|
||||
/// <exception cref="UserErrorException">The manifest is missing or invalid.</exception>
|
||||
public string GetManifestVersion()
|
||||
{
|
||||
if (!this.Files.TryGetValue(this.ManifestFileName, out FileInfo manifestFile) || !new JsonHelper().ReadJsonFileIfExists(manifestFile.FullName, out Manifest manifest))
|
||||
throw new InvalidOperationException($"The mod does not have a {this.ManifestFileName} file."); // shouldn't happen since we validate in constructor
|
||||
|
||||
return manifest.Version.ToString();
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
|
@ -94,7 +124,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
|||
// project manifest
|
||||
bool hasProjectManifest = false;
|
||||
{
|
||||
FileInfo manifest = new FileInfo(Path.Combine(projectDir, this.ManifestFileName));
|
||||
FileInfo manifest = new(Path.Combine(projectDir, this.ManifestFileName));
|
||||
if (manifest.Exists)
|
||||
{
|
||||
yield return Tuple.Create(this.ManifestFileName, manifest);
|
||||
|
@ -104,7 +134,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
|||
|
||||
// project i18n files
|
||||
bool hasProjectTranslations = false;
|
||||
DirectoryInfo translationsFolder = new DirectoryInfo(Path.Combine(projectDir, "i18n"));
|
||||
DirectoryInfo translationsFolder = new(Path.Combine(projectDir, "i18n"));
|
||||
if (translationsFolder.Exists)
|
||||
{
|
||||
foreach (FileInfo file in translationsFolder.EnumerateFiles())
|
||||
|
@ -114,7 +144,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
|||
|
||||
// project assets folder
|
||||
bool hasAssetsFolder = false;
|
||||
DirectoryInfo assetsFolder = new DirectoryInfo(Path.Combine(projectDir, "assets"));
|
||||
DirectoryInfo assetsFolder = new(Path.Combine(projectDir, "assets"));
|
||||
if (assetsFolder.Exists)
|
||||
{
|
||||
foreach (FileInfo file in assetsFolder.EnumerateFiles("*", SearchOption.AllDirectories))
|
||||
|
@ -126,7 +156,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
|||
}
|
||||
|
||||
// build output
|
||||
DirectoryInfo buildFolder = new DirectoryInfo(targetDir);
|
||||
DirectoryInfo buildFolder = new(targetDir);
|
||||
foreach (FileInfo file in buildFolder.EnumerateFiles("*", SearchOption.AllDirectories))
|
||||
{
|
||||
// get path info
|
||||
|
@ -149,36 +179,83 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
|||
/// <summary>Get whether a build output file should be ignored.</summary>
|
||||
/// <param name="file">The file to check.</param>
|
||||
/// <param name="relativePath">The file's relative path in the package.</param>
|
||||
/// <param name="ignoreFilePaths">The custom relative file paths provided by the user to ignore.</param>
|
||||
/// <param name="ignoreFilePatterns">Custom regex patterns matching files to ignore when deploying or zipping the mod.</param>
|
||||
private bool ShouldIgnore(FileInfo file, string relativePath, Regex[] ignoreFilePatterns)
|
||||
/// <param name="bundleAssemblyTypes">The extra assembly types which should be bundled with the mod.</param>
|
||||
/// <param name="modDllName">The name (without extension or path) for the current mod's DLL.</param>
|
||||
private bool ShouldIgnore(FileInfo file, string relativePath, string[] ignoreFilePaths, Regex[] ignoreFilePatterns, ExtraAssemblyTypes bundleAssemblyTypes, string modDllName)
|
||||
{
|
||||
return
|
||||
// release zips
|
||||
this.EqualsInvariant(file.Extension, ".zip")
|
||||
// apply custom patterns
|
||||
if (ignoreFilePaths.Any(p => p == relativePath) || ignoreFilePatterns.Any(p => p.IsMatch(relativePath)))
|
||||
return true;
|
||||
|
||||
// Harmony (bundled into SMAPI)
|
||||
|| this.EqualsInvariant(file.Name, "0Harmony.dll")
|
||||
// ignore unneeded files
|
||||
{
|
||||
bool shouldIgnore =
|
||||
// release zips
|
||||
this.EqualsInvariant(file.Extension, ".zip")
|
||||
|
||||
// Json.NET (bundled into SMAPI)
|
||||
|| this.EqualsInvariant(file.Name, "Newtonsoft.Json.dll")
|
||||
|| this.EqualsInvariant(file.Name, "Newtonsoft.Json.pdb")
|
||||
|| this.EqualsInvariant(file.Name, "Newtonsoft.Json.xml")
|
||||
// *.deps.json (only SMAPI's top-level one is used)
|
||||
|| file.Name.EndsWith(".deps.json")
|
||||
|
||||
// mod translation class builder (not used at runtime)
|
||||
|| this.EqualsInvariant(file.Name, "Pathoschild.Stardew.ModTranslationClassBuilder.dll")
|
||||
|| this.EqualsInvariant(file.Name, "Pathoschild.Stardew.ModTranslationClassBuilder.pdb")
|
||||
|| this.EqualsInvariant(file.Name, "Pathoschild.Stardew.ModTranslationClassBuilder.xml")
|
||||
// code analysis files
|
||||
|| file.Name.EndsWith(".CodeAnalysisLog.xml", StringComparison.OrdinalIgnoreCase)
|
||||
|| file.Name.EndsWith(".lastcodeanalysissucceeded", StringComparison.OrdinalIgnoreCase)
|
||||
|
||||
// code analysis files
|
||||
|| file.Name.EndsWith(".CodeAnalysisLog.xml", StringComparison.OrdinalIgnoreCase)
|
||||
|| file.Name.EndsWith(".lastcodeanalysissucceeded", StringComparison.OrdinalIgnoreCase)
|
||||
// translation class builder (not used at runtime)
|
||||
|| (
|
||||
file.Name.StartsWith("Pathoschild.Stardew.ModTranslationClassBuilder")
|
||||
&& this.AssemblyFileExtensions.Contains(file.Extension)
|
||||
)
|
||||
|
||||
// OS metadata files
|
||||
|| this.EqualsInvariant(file.Name, ".DS_Store")
|
||||
|| this.EqualsInvariant(file.Name, "Thumbs.db")
|
||||
// OS metadata files
|
||||
|| this.EqualsInvariant(file.Name, ".DS_Store")
|
||||
|| this.EqualsInvariant(file.Name, "Thumbs.db");
|
||||
if (shouldIgnore)
|
||||
return true;
|
||||
}
|
||||
|
||||
// custom ignore patterns
|
||||
|| ignoreFilePatterns.Any(p => p.IsMatch(relativePath));
|
||||
// ignore by assembly type
|
||||
ExtraAssemblyTypes type = this.GetExtraAssemblyType(file, modDllName);
|
||||
switch (bundleAssemblyTypes)
|
||||
{
|
||||
// Only explicitly-referenced assemblies are in the build output. These should be added to the zip,
|
||||
// since it's possible the game won't load them (except game assemblies which will always be loaded
|
||||
// separately). If they're already loaded, SMAPI will just ignore them.
|
||||
case ExtraAssemblyTypes.None:
|
||||
if (type is ExtraAssemblyTypes.Game)
|
||||
return true;
|
||||
break;
|
||||
|
||||
// All assemblies are in the build output (due to how .NET builds references), but only those which
|
||||
// match the bundled type should be in the zip.
|
||||
default:
|
||||
if (type != ExtraAssemblyTypes.None && !bundleAssemblyTypes.HasFlag(type))
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Get the extra assembly type for a file, assuming that the user specified one or more extra types to bundle.</summary>
|
||||
/// <param name="file">The file to check.</param>
|
||||
/// <param name="modDllName">The name (without extension or path) for the current mod's DLL.</param>
|
||||
private ExtraAssemblyTypes GetExtraAssemblyType(FileInfo file, string modDllName)
|
||||
{
|
||||
string baseName = Path.GetFileNameWithoutExtension(file.Name);
|
||||
string extension = file.Extension;
|
||||
|
||||
if (baseName == modDllName || !this.AssemblyFileExtensions.Contains(extension))
|
||||
return ExtraAssemblyTypes.None;
|
||||
|
||||
if (this.GameDllNames.Contains(baseName))
|
||||
return ExtraAssemblyTypes.Game;
|
||||
|
||||
if (baseName.StartsWith("System.", StringComparison.OrdinalIgnoreCase) || baseName.StartsWith("Microsoft.", StringComparison.OrdinalIgnoreCase))
|
||||
return ExtraAssemblyTypes.System;
|
||||
|
||||
return ExtraAssemblyTypes.ThirdParty;
|
||||
}
|
||||
|
||||
/// <summary>Get whether a string is equal to another case-insensitively.</summary>
|
||||
|
|
|
@ -2,33 +2,35 @@
|
|||
<PropertyGroup>
|
||||
<!--build-->
|
||||
<RootNamespace>StardewModdingAPI.ModBuildConfig</RootNamespace>
|
||||
<TargetFramework>net45</TargetFramework>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
|
||||
|
||||
<!--NuGet package-->
|
||||
<PackageId>Pathoschild.Stardew.ModBuildConfig</PackageId>
|
||||
<Title>Build package for SMAPI mods</Title>
|
||||
<Version>3.2.2</Version>
|
||||
<Version>4.1.0</Version>
|
||||
<Authors>Pathoschild</Authors>
|
||||
<Description>Automates the build configuration for crossplatform Stardew Valley SMAPI mods. For SMAPI 3.0 or later.</Description>
|
||||
<Description>Automates the build configuration for crossplatform Stardew Valley SMAPI mods. For SMAPI 3.13.0 or later.</Description>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageIcon>images/icon.png</PackageIcon>
|
||||
<PackageProjectUrl>https://smapi.io/package/readme</PackageProjectUrl>
|
||||
<PackageReleaseNotes>
|
||||
- Reworked and streamlined how the package is compiled.
|
||||
- Added SMAPI-ModTranslationClassBuilder files to the ignore list.
|
||||
</PackageReleaseNotes>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
|
||||
<!--copy dependency DLLs to bin folder so we can include them in package -->
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Build" />
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
<Reference Include="Microsoft.Build.Utilities.v4.0" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.10" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
|
||||
<!--
|
||||
This is imported through Microsoft.Build.Utilities.Core. When installed by a mod, NuGet
|
||||
otherwise imports version 4.3.0 instead of 5.0.0, which conflicts with SMAPI's version.
|
||||
-->
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -49,5 +51,4 @@
|
|||
<None PackagePath="build/Pathoschild.Stardew.ModBuildConfig.targets" Include="build\smapi.targets" Pack="true" />
|
||||
<None PackagePath="images/icon.png" Include="assets\nuget-icon.png" Pack="true" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -8,12 +8,14 @@
|
|||
** Set build options
|
||||
**********************************************-->
|
||||
<PropertyGroup>
|
||||
<!-- include PDB file by default to enable line numbers in stack traces -->
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<!-- enable line numbers in stack traces -->
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
<!-- recognise XNA Framework DLLs in the GAC (only affects mods using new csproj format) -->
|
||||
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||
<!-- don't create the 'refs' folder (which isn't useful for mods) -->
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
|
||||
<!-- suppress processor architecture mismatch warning (mods should be compiled in 'Any CPU' so they work in both 32-bit and 64-bit mode) -->
|
||||
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||
|
||||
<!-- set default package options -->
|
||||
<ModFolderName Condition="'$(ModFolderName)' == ''">$(MSBuildProjectName)</ModFolderName>
|
||||
|
@ -23,7 +25,14 @@
|
|||
<EnableModZip Condition="'$(EnableModZip)' == ''">true</EnableModZip>
|
||||
<EnableHarmony Condition="'$(EnableHarmony)' == ''">false</EnableHarmony>
|
||||
<EnableGameDebugging Condition="'$(EnableGameDebugging)' == ''">true</EnableGameDebugging>
|
||||
<CopyModReferencesToBuildOutput Condition="'$(CopyModReferencesToBuildOutput)' == '' OR ('$(CopyModReferencesToBuildOutput)' != 'true' AND '$(CopyModReferencesToBuildOutput)' != 'false')">false</CopyModReferencesToBuildOutput>
|
||||
<BundleExtraAssemblies Condition="'$(BundleExtraAssemblies)' == ''"></BundleExtraAssemblies>
|
||||
|
||||
<!-- simplify conditions -->
|
||||
<_BundleExtraAssembliesForGame>$([System.Text.RegularExpressions.Regex]::IsMatch('$(BundleExtraAssemblies)', '\bGame|All\b', RegexOptions.IgnoreCase))</_BundleExtraAssembliesForGame>
|
||||
<_BundleExtraAssembliesForAny>$([System.Text.RegularExpressions.Regex]::IsMatch('$(BundleExtraAssemblies)', '\bGame|System|ThirdParty|All\b', RegexOptions.IgnoreCase))</_BundleExtraAssembliesForAny>
|
||||
|
||||
<!-- coppy referenced DLLs into build output -->
|
||||
<CopyLocalLockFileAssemblies Condition="$(_BundleExtraAssembliesForAny)">true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(OS)' == 'Windows_NT' AND '$(EnableGameDebugging)' == 'true'">
|
||||
|
@ -37,40 +46,36 @@
|
|||
<!--*********************************************
|
||||
** Add assembly references
|
||||
**********************************************-->
|
||||
<!-- common -->
|
||||
<ItemGroup>
|
||||
<Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="StardewModdingAPI" HintPath="$(GamePath)\StardewModdingAPI.exe" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="SMAPI.Toolkit.CoreInterfaces" HintPath="$(GamePath)\smapi-internal\SMAPI.Toolkit.CoreInterfaces.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="0Harmony" Condition="'$(EnableHarmony)' == 'true'" HintPath="$(GamePath)\smapi-internal\0Harmony.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
</ItemGroup>
|
||||
<!-- game -->
|
||||
<Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="$(_BundleExtraAssembliesForGame)" />
|
||||
<Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="$(_BundleExtraAssembliesForGame)" />
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="$(_BundleExtraAssembliesForGame)" />
|
||||
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="$(_BundleExtraAssembliesForGame)" />
|
||||
|
||||
<!-- Windows -->
|
||||
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
|
||||
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
</ItemGroup>
|
||||
<!-- SMAPI -->
|
||||
<Reference Include="StardewModdingAPI" HintPath="$(GamePath)\StardewModdingAPI.dll" Private="$(_BundleExtraAssembliesForGame)" />
|
||||
<Reference Include="SMAPI.Toolkit.CoreInterfaces" HintPath="$(GamePath)\smapi-internal\SMAPI.Toolkit.CoreInterfaces.dll" Private="$(_BundleExtraAssembliesForGame)" />
|
||||
|
||||
<!-- Linux/Mac -->
|
||||
<ItemGroup Condition="'$(OS)' != 'Windows_NT'">
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<!-- Harmony -->
|
||||
<Reference Include="0Harmony" Condition="'$(EnableHarmony)' == 'true'" HintPath="$(GamePath)\smapi-internal\0Harmony.dll" Private="$(_BundleExtraAssembliesForGame)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<!--*********************************************
|
||||
** Show friendly error for invalid OS or game path
|
||||
** Show validation messages
|
||||
**********************************************-->
|
||||
<Target Name="BeforeBuild">
|
||||
<!-- unknown OS type -->
|
||||
<Error Condition="'$(OS)' != 'OSX' AND '$(OS)' != 'Unix' AND '$(OS)' != 'Windows_NT'" Text="The mod build package doesn't recognise OS type '$(OS)'." />
|
||||
|
||||
<Error Condition="!Exists('$(GamePath)')" Text="The mod build package can't find your game folder. You can specify where to find it; see https://smapi.io/package/custom-game-path." />
|
||||
<Error Condition="!Exists('$(GamePath)\$(GameExecutableName).exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain the $(GameExecutableName) file. If this folder is invalid, delete it and the package will autodetect another game install path." />
|
||||
<Error Condition="!Exists('$(GamePath)\StardewModdingAPI.exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain SMAPI. You need to install SMAPI before building the mod." />
|
||||
<!-- invalid game path -->
|
||||
<Error Condition="!Exists('$(GamePath)')" Text="The mod build package can't find your game folder. You can specify where to find it; see https://smapi.io/package/custom-game-path." ContinueOnError="false" />
|
||||
<Error Condition="!Exists('$(GamePath)\Stardew Valley.dll')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain the Stardew Valley file. If this folder is invalid, delete it and the package will autodetect another game install path." ContinueOnError="false" />
|
||||
<Error Condition="!Exists('$(GamePath)\StardewModdingAPI.dll')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain SMAPI. You need to install SMAPI before building the mod." ContinueOnError="false" />
|
||||
|
||||
<!-- invalid target architecture (note: internal value is 'AnyCPU', value shown in Visual Studio is 'Any CPU') -->
|
||||
<Warning Condition="'$(Platform)' != 'AnyCPU'" Text="The target platform should be set to 'Any CPU' for compatibility with both 32-bit and 64-bit versions of Stardew Valley (currently set to '$(Platform)'). See https://smapi.io/package/wrong-processor-architecture for details." HelpLink="https://smapi.io/package/wrong-processor-architecture" />
|
||||
</Target>
|
||||
|
||||
|
||||
|
@ -79,6 +84,7 @@
|
|||
**********************************************-->
|
||||
<Target Name="AfterBuild">
|
||||
<DeployModTask
|
||||
ModDllName="$(TargetName)"
|
||||
ModFolderName="$(ModFolderName)"
|
||||
ModZipPath="$(ModZipPath)"
|
||||
|
||||
|
@ -89,6 +95,9 @@
|
|||
TargetDir="$(TargetDir)"
|
||||
GameModsDir="$(GameModsPath)"
|
||||
IgnoreModFilePatterns="$(IgnoreModFilePatterns)"
|
||||
IgnoreModFilePaths="$(IgnoreModFilePaths)"
|
||||
|
||||
BundleExtraAssemblies="$(BundleExtraAssemblies)"
|
||||
/>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
||||
|
@ -53,7 +53,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
|||
/// <param name="value">The parsed value.</param>
|
||||
/// <param name="required">Whether to show an error if the argument is missing.</param>
|
||||
/// <param name="oneOf">Require that the argument match one of the given values (case-insensitive).</param>
|
||||
public bool TryGet(int index, string name, out string value, bool required = true, string[] oneOf = null)
|
||||
public bool TryGet(int index, string name, [NotNullWhen(true)] out string? value, bool required = true, string[]? oneOf = null)
|
||||
{
|
||||
value = null;
|
||||
|
||||
|
@ -87,7 +87,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
|||
value = 0;
|
||||
|
||||
// get argument
|
||||
if (!this.TryGet(index, name, out string raw, required))
|
||||
if (!this.TryGet(index, name, out string? raw, required))
|
||||
return false;
|
||||
|
||||
// parse
|
||||
|
@ -107,38 +107,6 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Try to read a decimal argument.</summary>
|
||||
/// <param name="index">The argument index.</param>
|
||||
/// <param name="name">The argument name for error messages.</param>
|
||||
/// <param name="value">The parsed value.</param>
|
||||
/// <param name="required">Whether to show an error if the argument is missing.</param>
|
||||
/// <param name="min">The minimum value allowed.</param>
|
||||
/// <param name="max">The maximum value allowed.</param>
|
||||
public bool TryGetDecimal(int index, string name, out decimal value, bool required = true, decimal? min = null, decimal? max = null)
|
||||
{
|
||||
value = 0;
|
||||
|
||||
// get argument
|
||||
if (!this.TryGet(index, name, out string raw, required))
|
||||
return false;
|
||||
|
||||
// parse
|
||||
if (!decimal.TryParse(raw, NumberStyles.Number, CultureInfo.InvariantCulture, out value))
|
||||
{
|
||||
this.LogDecimalFormatError(index, name, min, max);
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate
|
||||
if ((min.HasValue && value < min) || (max.HasValue && value > max))
|
||||
{
|
||||
this.LogDecimalFormatError(index, name, min, max);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator that iterates through the collection.</summary>
|
||||
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
|
||||
public IEnumerator<string> GetEnumerator()
|
||||
|
@ -180,22 +148,5 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
|||
else
|
||||
this.LogError($"Argument {index} ({name}) must be an integer.");
|
||||
}
|
||||
|
||||
/// <summary>Print an error for an invalid decimal argument.</summary>
|
||||
/// <param name="index">The argument index.</param>
|
||||
/// <param name="name">The argument name for error messages.</param>
|
||||
/// <param name="min">The minimum value allowed.</param>
|
||||
/// <param name="max">The maximum value allowed.</param>
|
||||
private void LogDecimalFormatError(int index, string name, decimal? min, decimal? max)
|
||||
{
|
||||
if (min.HasValue && max.HasValue)
|
||||
this.LogError($"Argument {index} ({name}) must be a decimal between {min} and {max}.");
|
||||
else if (min.HasValue)
|
||||
this.LogError($"Argument {index} ({name}) must be a decimal and at least {min}.");
|
||||
else if (max.HasValue)
|
||||
this.LogError($"Argument {index} ({name}) must be a decimal and at most {max}.");
|
||||
else
|
||||
this.LogError($"Argument {index} ({name}) must be a decimal.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ using System.Linq;
|
|||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
||||
{
|
||||
/// <summary>The base implementation for a trainer command.</summary>
|
||||
internal abstract class TrainerCommand : ITrainerCommand
|
||||
/// <summary>The base implementation for a console command.</summary>
|
||||
internal abstract class ConsoleCommand : IConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
|
@ -50,7 +50,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
|||
/// <param name="description">The command description.</param>
|
||||
/// <param name="mayNeedInput">Whether the command may need to perform logic when the player presses a button.</param>
|
||||
/// <param name="mayNeedUpdate">Whether the command may need to perform logic when the game updates.</param>
|
||||
protected TrainerCommand(string name, string description, bool mayNeedInput = false, bool mayNeedUpdate = false)
|
||||
protected ConsoleCommand(string name, string description, bool mayNeedInput = false, bool mayNeedUpdate = false)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
|
@ -78,8 +78,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
|||
/// <param name="data">The data to display.</param>
|
||||
/// <param name="header">The table header.</param>
|
||||
/// <param name="getRow">Returns a set of fields for a data value.</param>
|
||||
/// <param name="rightAlign">Whether to right-align the data.</param>
|
||||
protected string GetTableString<T>(IEnumerable<T> data, string[] header, Func<T, string[]> getRow, bool rightAlign = false)
|
||||
protected string GetTableString<T>(IEnumerable<T> data, string[] header, Func<T, string[]> getRow)
|
||||
{
|
||||
// get table data
|
||||
int[] widths = header.Select(p => p.Length).ToArray();
|
||||
|
@ -101,14 +100,14 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
|||
List<string[]> lines = new List<string[]>(rows.Length + 2)
|
||||
{
|
||||
header,
|
||||
header.Select((value, i) => "".PadRight(widths[i], '-')).ToArray()
|
||||
header.Select((_, i) => "".PadRight(widths[i], '-')).ToArray()
|
||||
};
|
||||
lines.AddRange(rows);
|
||||
|
||||
return string.Join(
|
||||
Environment.NewLine,
|
||||
lines.Select(line => string.Join(" | ",
|
||||
line.Select((field, i) => rightAlign ? field.PadRight(widths[i], ' ') : field.PadLeft(widths[i], ' '))
|
||||
line.Select((field, i) => field.PadLeft(widths[i], ' '))
|
||||
))
|
||||
);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
||||
{
|
||||
/// <summary>A console command to register.</summary>
|
||||
internal interface ITrainerCommand
|
||||
internal interface IConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
||||
{
|
||||
/// <summary>A command which runs one of the game's save migrations.</summary>
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class ApplySaveFixCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public ApplySaveFixCommand()
|
||||
: base("apply_save_fix", "Apply one of the game's save migrations to the currently loaded save. WARNING: This may corrupt or make permanent changes to your save. DO NOT USE THIS unless you're absolutely sure.\n\nUsage: apply_save_fix list\nList all valid save IDs.\n\nUsage: apply_save_fix <fix ID>\nApply the named save fix.") { }
|
||||
|
||||
/// <summary>Handle the command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="command">The command name.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
|
||||
{
|
||||
// get fix ID
|
||||
if (!args.TryGet(0, "fix_id", out string? rawFixId, required: false))
|
||||
{
|
||||
monitor.Log("Invalid usage. Type 'help apply_save_fix' for details.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
rawFixId = rawFixId.Trim();
|
||||
|
||||
|
||||
// list mode
|
||||
if (rawFixId == "list")
|
||||
{
|
||||
monitor.Log("Valid save fix IDs:\n - " + string.Join("\n - ", this.GetSaveIds()), LogLevel.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
// validate fix ID
|
||||
if (!Enum.TryParse(rawFixId, ignoreCase: true, out SaveGame.SaveFixes fixId))
|
||||
{
|
||||
monitor.Log($"Invalid save ID '{rawFixId}'. Type 'help apply_save_fix' for details.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// apply
|
||||
monitor.Log("THIS MAY CAUSE PERMANENT CHANGES TO YOUR SAVE FILE. If you're not sure, exit your game without saving to avoid issues.", LogLevel.Warn);
|
||||
monitor.Log($"Trying to apply save fix ID: '{fixId}'.", LogLevel.Warn);
|
||||
try
|
||||
{
|
||||
Game1.applySaveFix(fixId);
|
||||
monitor.Log("Save fix applied.", LogLevel.Info);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
monitor.Log("Applying save fix failed. The save may be in an invalid state; you should exit your game now without saving to avoid issues.", LogLevel.Error);
|
||||
monitor.Log($"Technical details: {ex}", LogLevel.Debug);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Get the valid save fix IDs.</summary>
|
||||
private IEnumerable<string> GetSaveIds()
|
||||
{
|
||||
foreach (SaveGame.SaveFixes id in Enum.GetValues(typeof(SaveGame.SaveFixes)))
|
||||
{
|
||||
if (id == SaveGame.SaveFixes.MAX)
|
||||
continue;
|
||||
|
||||
yield return id.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
using StardewValley;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
||||
{
|
||||
/// <summary>A command which sends a debug command to the game.</summary>
|
||||
internal class DebugCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class DebugCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
|
|
@ -1,647 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.PerformanceMonitoring;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
||||
{
|
||||
/// <summary>A set of commands which displays or configures performance monitoring.</summary>
|
||||
internal class PerformanceCounterCommand : TrainerCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The name of the command.</summary>
|
||||
private const string CommandName = "performance";
|
||||
|
||||
/// <summary>The available commands.</summary>
|
||||
private enum SubCommand
|
||||
{
|
||||
Summary,
|
||||
Detail,
|
||||
Reset,
|
||||
Trigger,
|
||||
Enable,
|
||||
Disable,
|
||||
Help
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public PerformanceCounterCommand()
|
||||
: base(CommandName, PerformanceCounterCommand.GetDescription()) { }
|
||||
|
||||
/// <summary>Handle the command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="command">The command name.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
|
||||
{
|
||||
// parse args
|
||||
SubCommand subcommand = SubCommand.Summary;
|
||||
{
|
||||
if (args.TryGet(0, "command", out string subcommandStr, false) && !Enum.TryParse(subcommandStr, ignoreCase: true, out subcommand))
|
||||
{
|
||||
this.LogUsageError(monitor, $"Unknown command {subcommandStr}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// handle
|
||||
switch (subcommand)
|
||||
{
|
||||
case SubCommand.Summary:
|
||||
this.HandleSummarySubCommand(monitor, args);
|
||||
break;
|
||||
|
||||
case SubCommand.Detail:
|
||||
this.HandleDetailSubCommand(monitor, args);
|
||||
break;
|
||||
|
||||
case SubCommand.Reset:
|
||||
this.HandleResetSubCommand(monitor, args);
|
||||
break;
|
||||
|
||||
case SubCommand.Trigger:
|
||||
this.HandleTriggerSubCommand(monitor, args);
|
||||
break;
|
||||
|
||||
case SubCommand.Enable:
|
||||
SCore.PerformanceMonitor.EnableTracking = true;
|
||||
monitor.Log("Performance counter tracking is now enabled", LogLevel.Info);
|
||||
break;
|
||||
|
||||
case SubCommand.Disable:
|
||||
SCore.PerformanceMonitor.EnableTracking = false;
|
||||
monitor.Log("Performance counter tracking is now disabled", LogLevel.Info);
|
||||
break;
|
||||
|
||||
case SubCommand.Help:
|
||||
this.OutputHelp(monitor, args.TryGet(1, "command", out _) ? subcommand : null as SubCommand?);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.LogUsageError(monitor, $"Unknown command {subcommand}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Handles the summary sub command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
private void HandleSummarySubCommand(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
if (!this.AssertEnabled(monitor))
|
||||
return;
|
||||
|
||||
IEnumerable<PerformanceCounterCollection> data = SCore.PerformanceMonitor.GetCollections();
|
||||
|
||||
double? threshold = null;
|
||||
if (args.TryGetDecimal(1, "threshold", out decimal t, required: false))
|
||||
threshold = (double?)t;
|
||||
|
||||
TimeSpan interval = TimeSpan.FromSeconds(60);
|
||||
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.AppendLine($"Summary over the last {interval.TotalSeconds} seconds:");
|
||||
report.AppendLine(this.GetTableString(
|
||||
data: data,
|
||||
header: new[] { "Collection", "Avg Calls/s", "Avg Exec Time (Game)", "Avg Exec Time (Mods)", "Avg Exec Time (Game+Mods)", "Peak Exec Time" },
|
||||
getRow: item => new[]
|
||||
{
|
||||
item.Name,
|
||||
item.GetAverageCallsPerSecond().ToString(),
|
||||
this.FormatMilliseconds(item.GetGameAverageExecutionTime(interval), threshold),
|
||||
this.FormatMilliseconds(item.GetModsAverageExecutionTime(interval), threshold),
|
||||
this.FormatMilliseconds(item.GetAverageExecutionTime(interval), threshold),
|
||||
this.FormatMilliseconds(item.GetPeakExecutionTime(interval), threshold)
|
||||
},
|
||||
true
|
||||
));
|
||||
|
||||
monitor.Log(report.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
/// <summary>Handles the detail sub command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
private void HandleDetailSubCommand(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
if (!this.AssertEnabled(monitor))
|
||||
return;
|
||||
|
||||
// parse args
|
||||
double thresholdMilliseconds = 0;
|
||||
if (args.TryGetDecimal(1, "threshold", out decimal t, required: false))
|
||||
thresholdMilliseconds = (double)t;
|
||||
|
||||
// get collections
|
||||
var collections = SCore.PerformanceMonitor.GetCollections();
|
||||
|
||||
// render
|
||||
TimeSpan averageInterval = TimeSpan.FromSeconds(60);
|
||||
StringBuilder report = new StringBuilder($"Showing details for performance counters of {thresholdMilliseconds}+ milliseconds:\n\n");
|
||||
bool anyShown = false;
|
||||
foreach (PerformanceCounterCollection collection in collections)
|
||||
{
|
||||
KeyValuePair<string, PerformanceCounter>[] data = collection.PerformanceCounters
|
||||
.Where(p => p.Value.GetAverage(averageInterval) >= thresholdMilliseconds)
|
||||
.ToArray();
|
||||
|
||||
if (data.Any())
|
||||
{
|
||||
anyShown = true;
|
||||
report.AppendLine($"{collection.Name}:");
|
||||
report.AppendLine(this.GetTableString(
|
||||
data: data,
|
||||
header: new[] { "Mod", $"Avg Exec Time (last {(int)averageInterval.TotalSeconds}s)", "Last Exec Time", "Peak Exec Time", $"Peak Exec Time (last {(int)averageInterval.TotalSeconds}s)" },
|
||||
getRow: item => new[]
|
||||
{
|
||||
item.Key,
|
||||
this.FormatMilliseconds(item.Value.GetAverage(averageInterval), thresholdMilliseconds),
|
||||
this.FormatMilliseconds(item.Value.GetLastEntry()?.ElapsedMilliseconds),
|
||||
this.FormatMilliseconds(item.Value.GetPeak()?.ElapsedMilliseconds),
|
||||
this.FormatMilliseconds(item.Value.GetPeak(averageInterval)?.ElapsedMilliseconds)
|
||||
},
|
||||
true
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyShown)
|
||||
report.AppendLine("No performance counters found.");
|
||||
|
||||
monitor.Log(report.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
/// <summary>Handles the trigger sub command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
private void HandleTriggerSubCommand(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
if (!this.AssertEnabled(monitor))
|
||||
return;
|
||||
|
||||
if (args.TryGet(1, "mode", out string mode, false))
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case "list":
|
||||
this.OutputAlertTriggers(monitor);
|
||||
break;
|
||||
|
||||
case "collection":
|
||||
if (args.TryGet(2, "name", out string collectionName))
|
||||
{
|
||||
if (args.TryGetDecimal(3, "threshold", out decimal threshold))
|
||||
{
|
||||
if (!args.TryGet(4, "source", out string source, required: false))
|
||||
source = null;
|
||||
this.ConfigureAlertTrigger(monitor, collectionName, source, threshold);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "pause":
|
||||
SCore.PerformanceMonitor.PauseAlerts = true;
|
||||
monitor.Log("Alerts are now paused.", LogLevel.Info);
|
||||
break;
|
||||
|
||||
case "resume":
|
||||
SCore.PerformanceMonitor.PauseAlerts = false;
|
||||
monitor.Log("Alerts are now resumed.", LogLevel.Info);
|
||||
break;
|
||||
|
||||
case "dump":
|
||||
this.OutputAlertTriggers(monitor, true);
|
||||
break;
|
||||
|
||||
case "clear":
|
||||
this.ClearAlertTriggers(monitor);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.LogUsageError(monitor, $"Unknown mode {mode}. See '{CommandName} help trigger' for usage.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
this.OutputAlertTriggers(monitor);
|
||||
}
|
||||
|
||||
/// <summary>Sets up an an alert trigger.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="collectionName">The name of the collection.</param>
|
||||
/// <param name="sourceName">The name of the source, or null for all sources.</param>
|
||||
/// <param name="threshold">The trigger threshold, or 0 to remove.</param>
|
||||
private void ConfigureAlertTrigger(IMonitor monitor, string collectionName, string sourceName, decimal threshold)
|
||||
{
|
||||
foreach (PerformanceCounterCollection collection in SCore.PerformanceMonitor.GetCollections())
|
||||
{
|
||||
if (collection.Name.ToLowerInvariant().Equals(collectionName.ToLowerInvariant()))
|
||||
{
|
||||
if (sourceName == null)
|
||||
{
|
||||
if (threshold != 0)
|
||||
{
|
||||
collection.EnableAlerts = true;
|
||||
collection.AlertThresholdMilliseconds = (double)threshold;
|
||||
monitor.Log($"Set up alert triggering for '{collectionName}' with '{this.FormatMilliseconds((double?)threshold)}'", LogLevel.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
collection.EnableAlerts = false;
|
||||
monitor.Log($"Cleared alert triggering for '{collection}'.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var performanceCounter in collection.PerformanceCounters)
|
||||
{
|
||||
if (performanceCounter.Value.Source.ToLowerInvariant().Equals(sourceName.ToLowerInvariant()))
|
||||
{
|
||||
if (threshold != 0)
|
||||
{
|
||||
performanceCounter.Value.EnableAlerts = true;
|
||||
performanceCounter.Value.AlertThresholdMilliseconds = (double)threshold;
|
||||
monitor.Log($"Set up alert triggering for '{sourceName}' in collection '{collectionName}' with '{this.FormatMilliseconds((double?)threshold)}", LogLevel.Info);
|
||||
}
|
||||
else
|
||||
performanceCounter.Value.EnableAlerts = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
monitor.Log($"Could not find the source '{sourceName}' in collection '{collectionName}'", LogLevel.Warn);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
monitor.Log($"Could not find the collection '{collectionName}'", LogLevel.Warn);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Clears alert triggering for all collections.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
private void ClearAlertTriggers(IMonitor monitor)
|
||||
{
|
||||
int clearedTriggers = 0;
|
||||
foreach (PerformanceCounterCollection collection in SCore.PerformanceMonitor.GetCollections())
|
||||
{
|
||||
if (collection.EnableAlerts)
|
||||
{
|
||||
collection.EnableAlerts = false;
|
||||
clearedTriggers++;
|
||||
}
|
||||
|
||||
foreach (var performanceCounter in collection.PerformanceCounters)
|
||||
{
|
||||
if (performanceCounter.Value.EnableAlerts)
|
||||
{
|
||||
performanceCounter.Value.EnableAlerts = false;
|
||||
clearedTriggers++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
monitor.Log($"Cleared {clearedTriggers} alert triggers.", LogLevel.Info);
|
||||
}
|
||||
|
||||
/// <summary>Lists all configured alert triggers.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="asDump">True to dump the triggers as commands.</param>
|
||||
private void OutputAlertTriggers(IMonitor monitor, bool asDump = false)
|
||||
{
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.AppendLine("Configured triggers:");
|
||||
report.AppendLine();
|
||||
var collectionTriggers = new List<CollectionTrigger>();
|
||||
var sourceTriggers = new List<SourceTrigger>();
|
||||
|
||||
foreach (PerformanceCounterCollection collection in SCore.PerformanceMonitor.GetCollections())
|
||||
{
|
||||
if (collection.EnableAlerts)
|
||||
collectionTriggers.Add(new CollectionTrigger(collection.Name, collection.AlertThresholdMilliseconds));
|
||||
|
||||
sourceTriggers.AddRange(
|
||||
from counter in collection.PerformanceCounters
|
||||
where counter.Value.EnableAlerts
|
||||
select new SourceTrigger(collection.Name, counter.Value.Source, counter.Value.AlertThresholdMilliseconds)
|
||||
);
|
||||
}
|
||||
|
||||
if (collectionTriggers.Count > 0)
|
||||
{
|
||||
report.AppendLine("Collection Triggers:");
|
||||
report.AppendLine();
|
||||
|
||||
if (asDump)
|
||||
{
|
||||
foreach (var item in collectionTriggers)
|
||||
report.AppendLine($"{CommandName} trigger {item.CollectionName} {item.Threshold}");
|
||||
}
|
||||
else
|
||||
{
|
||||
report.AppendLine(this.GetTableString(
|
||||
data: collectionTriggers,
|
||||
header: new[] { "Collection", "Threshold" },
|
||||
getRow: item => new[] { item.CollectionName, this.FormatMilliseconds(item.Threshold) },
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
report.AppendLine();
|
||||
}
|
||||
else
|
||||
report.AppendLine("No collection triggers.");
|
||||
|
||||
if (sourceTriggers.Count > 0)
|
||||
{
|
||||
report.AppendLine("Source Triggers:");
|
||||
report.AppendLine();
|
||||
|
||||
if (asDump)
|
||||
{
|
||||
foreach (SourceTrigger item in sourceTriggers)
|
||||
report.AppendLine($"{CommandName} trigger {item.CollectionName} {item.Threshold} {item.SourceName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
report.AppendLine(this.GetTableString(
|
||||
data: sourceTriggers,
|
||||
header: new[] { "Collection", "Source", "Threshold" },
|
||||
getRow: item => new[] { item.CollectionName, item.SourceName, this.FormatMilliseconds(item.Threshold) },
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
report.AppendLine();
|
||||
}
|
||||
else
|
||||
report.AppendLine("No source triggers.");
|
||||
|
||||
monitor.Log(report.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
/// <summary>Handles the reset sub command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
private void HandleResetSubCommand(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
if (!this.AssertEnabled(monitor))
|
||||
return;
|
||||
|
||||
if (args.TryGet(1, "type", out string type, false, new[] { "category", "source" }))
|
||||
{
|
||||
args.TryGet(2, "name", out string name);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "category":
|
||||
SCore.PerformanceMonitor.ResetCollection(name);
|
||||
monitor.Log($"All performance counters for category {name} are now cleared.", LogLevel.Info);
|
||||
break;
|
||||
case "source":
|
||||
SCore.PerformanceMonitor.ResetSource(name);
|
||||
monitor.Log($"All performance counters for source {name} are now cleared.", LogLevel.Info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SCore.PerformanceMonitor.Reset();
|
||||
monitor.Log("All performance counters are now cleared.", LogLevel.Info);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Formats the given milliseconds value into a string format. Optionally
|
||||
/// allows a threshold to return "-" if the value is less than the threshold.</summary>
|
||||
/// <param name="milliseconds">The milliseconds to format. Returns "-" if null</param>
|
||||
/// <param name="thresholdMilliseconds">The threshold. Any value below this is returned as "-".</param>
|
||||
/// <returns>The formatted milliseconds.</returns>
|
||||
private string FormatMilliseconds(double? milliseconds, double? thresholdMilliseconds = null)
|
||||
{
|
||||
thresholdMilliseconds ??= 1;
|
||||
return milliseconds != null && milliseconds >= thresholdMilliseconds
|
||||
? ((double)milliseconds).ToString("F2")
|
||||
: "-";
|
||||
}
|
||||
|
||||
/// <summary>Shows detailed help for a specific sub command.</summary>
|
||||
/// <param name="monitor">The output monitor.</param>
|
||||
/// <param name="subcommand">The subcommand.</param>
|
||||
private void OutputHelp(IMonitor monitor, SubCommand? subcommand)
|
||||
{
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.AppendLine();
|
||||
|
||||
switch (subcommand)
|
||||
{
|
||||
case SubCommand.Detail:
|
||||
report.AppendLine($" {CommandName} detail <threshold>");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Displays details for a specific collection.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Arguments:");
|
||||
report.AppendLine(" <threshold> Optional. The threshold in milliseconds. Any average execution time below that");
|
||||
report.AppendLine(" threshold is not reported.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Examples:");
|
||||
report.AppendLine($"{CommandName} detail 5 Show counters exceeding an average of 5ms");
|
||||
break;
|
||||
|
||||
case SubCommand.Summary:
|
||||
report.AppendLine($"Usage: {CommandName} summary <threshold>");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Displays the performance counter summary.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Arguments:");
|
||||
report.AppendLine(" <threshold> Optional. Hides the actual execution time if it's below this threshold");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Examples:");
|
||||
report.AppendLine($"{CommandName} summary Show all events");
|
||||
report.AppendLine($"{CommandName} summary 5 Shows events exceeding an average of 5ms");
|
||||
break;
|
||||
|
||||
case SubCommand.Trigger:
|
||||
report.AppendLine($"Usage: {CommandName} trigger <mode>");
|
||||
report.AppendLine($"Usage: {CommandName} trigger collection <collectionName> <threshold>");
|
||||
report.AppendLine($"Usage: {CommandName} trigger collection <collectionName> <threshold> <sourceName>");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Manages alert triggers.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Arguments:");
|
||||
report.AppendLine(" <mode> Optional. Specifies if a specific source or a specific collection should be triggered.");
|
||||
report.AppendLine(" - list Lists current triggers");
|
||||
report.AppendLine(" - collection Sets up a trigger for a collection");
|
||||
report.AppendLine(" - clear Clears all trigger entries");
|
||||
report.AppendLine(" - pause Pauses triggering of alerts");
|
||||
report.AppendLine(" - resume Resumes triggering of alerts");
|
||||
report.AppendLine(" - dump Dumps all triggers as commands for copy and paste");
|
||||
report.AppendLine(" Defaults to 'list' if not specified.");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" <collectionName> Required if the mode 'collection' is specified.");
|
||||
report.AppendLine(" Specifies the name of the collection to be triggered. Must be an exact match.");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" <sourceName> Optional. Specifies the name of a specific source. Must be an exact match.");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" <threshold> Required if the mode 'collection' is specified.");
|
||||
report.AppendLine(" Specifies the threshold in milliseconds (fractions allowed).");
|
||||
report.AppendLine(" Specify '0' to remove the threshold.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Examples:");
|
||||
report.AppendLine();
|
||||
report.AppendLine($"{CommandName} trigger collection Display.Rendering 10");
|
||||
report.AppendLine(" Sets up an alert trigger which writes on the console if the execution time of all performance counters in");
|
||||
report.AppendLine(" the 'Display.Rendering' collection exceed 10 milliseconds.");
|
||||
report.AppendLine();
|
||||
report.AppendLine($"{CommandName} trigger collection Display.Rendering 5 Pathoschild.ChestsAnywhere");
|
||||
report.AppendLine(" Sets up an alert trigger to write on the console if the execution time of Pathoschild.ChestsAnywhere in");
|
||||
report.AppendLine(" the 'Display.Rendering' collection exceed 5 milliseconds.");
|
||||
report.AppendLine();
|
||||
report.AppendLine($"{CommandName} trigger collection Display.Rendering 0");
|
||||
report.AppendLine(" Removes the threshold previously defined from the collection. Note that source-specific thresholds are left intact.");
|
||||
report.AppendLine();
|
||||
report.AppendLine($"{CommandName} trigger clear");
|
||||
report.AppendLine(" Clears all previously setup alert triggers.");
|
||||
break;
|
||||
|
||||
case SubCommand.Reset:
|
||||
report.AppendLine($"Usage: {CommandName} reset <type> <name>");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Resets performance counters.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Arguments:");
|
||||
report.AppendLine(" <type> Optional. Specifies if a collection or source should be reset.");
|
||||
report.AppendLine(" If omitted, all performance counters are reset.");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" - source Clears performance counters for a specific source");
|
||||
report.AppendLine(" - collection Clears performance counters for a specific collection");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" <name> Required if a <type> is given. Specifies the name of either the collection");
|
||||
report.AppendLine(" or the source. The name must be an exact match.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Examples:");
|
||||
report.AppendLine($"{CommandName} reset Resets all performance counters");
|
||||
report.AppendLine($"{CommandName} reset source Pathoschild.ChestsAnywhere Resets all performance for the source named Pathoschild.ChestsAnywhere");
|
||||
report.AppendLine($"{CommandName} reset collection Display.Rendering Resets all performance for the collection named Display.Rendering");
|
||||
break;
|
||||
}
|
||||
|
||||
report.AppendLine();
|
||||
monitor.Log(report.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
/// <summary>Get the command description.</summary>
|
||||
private static string GetDescription()
|
||||
{
|
||||
StringBuilder report = new StringBuilder();
|
||||
|
||||
report.AppendLine("Displays or configures performance monitoring to diagnose issues. Performance monitoring is disabled by default.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("For example, the counter collection named 'Display.Rendered' contains one performance");
|
||||
report.AppendLine("counter when the game executes the 'Display.Rendered' event, and another counter for each mod which handles it.");
|
||||
report.AppendLine();
|
||||
report.AppendLine($"Usage: {CommandName} <command> <action>");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Commands:");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" summary Show a summary of collections.");
|
||||
report.AppendLine(" detail Show a summary for a given collection.");
|
||||
report.AppendLine(" reset Reset all performance counters.");
|
||||
report.AppendLine(" trigger Configure alert triggers.");
|
||||
report.AppendLine(" enable Enable performance counter recording.");
|
||||
report.AppendLine(" disable Disable performance counter recording.");
|
||||
report.AppendLine(" help Show verbose help for the available commands.");
|
||||
report.AppendLine();
|
||||
report.AppendLine($"To get help for a specific command, use '{CommandName} help <command>', for example:");
|
||||
report.AppendLine($"{CommandName} help summary");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Defaults to summary if no command is given.");
|
||||
report.AppendLine();
|
||||
|
||||
return report.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Log a warning if performance monitoring isn't enabled.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <returns>Returns whether performance monitoring is enabled.</returns>
|
||||
private bool AssertEnabled(IMonitor monitor)
|
||||
{
|
||||
if (!SCore.PerformanceMonitor.EnableTracking)
|
||||
{
|
||||
monitor.Log($"Performance monitoring is currently disabled; enter '{CommandName} enable' to enable it.", LogLevel.Warn);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private models
|
||||
*********/
|
||||
/// <summary>An alert trigger for a collection.</summary>
|
||||
private class CollectionTrigger
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The collection name.</summary>
|
||||
public string CollectionName { get; }
|
||||
|
||||
/// <summary>The trigger threshold.</summary>
|
||||
public double Threshold { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="collectionName">The collection name.</param>
|
||||
/// <param name="threshold">The trigger threshold.</param>
|
||||
public CollectionTrigger(string collectionName, double threshold)
|
||||
{
|
||||
this.CollectionName = collectionName;
|
||||
this.Threshold = threshold;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>An alert triggered for a source.</summary>
|
||||
private class SourceTrigger : CollectionTrigger
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The source name.</summary>
|
||||
public string SourceName { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="collectionName">The collection name.</param>
|
||||
/// <param name="sourceName">The source name.</param>
|
||||
/// <param name="threshold">The trigger threshold.</param>
|
||||
public SourceTrigger(string collectionName, string sourceName, double threshold)
|
||||
: base(collectionName, threshold)
|
||||
{
|
||||
this.SourceName = sourceName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Netcode;
|
||||
using StardewValley;
|
||||
using StardewValley.Network;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
||||
{
|
||||
/// <summary>A command which regenerates the game's bundles.</summary>
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class RegenerateBundlesCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public RegenerateBundlesCommand()
|
||||
: base("regenerate_bundles", $"Regenerate the game's community center bundle data. WARNING: this will reset all bundle progress, and may have unintended effects if you've already completed bundles. DO NOT USE THIS unless you're absolutely sure.\n\nUsage: regenerate_bundles confirm [<type>] [ignore_seed]\nRegenerate all bundles for this save. If the <type> is set to '{string.Join("' or '", Enum.GetNames(typeof(Game1.BundleType)))}', change the bundle type for the save. If an 'ignore_seed' option is included, remixed bundles are re-randomized without using the predetermined save seed.\n\nExample: regenerate_bundles remixed confirm") { }
|
||||
|
||||
/// <summary>Handle the command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="command">The command name.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
|
||||
{
|
||||
// get flags
|
||||
var bundleType = Game1.bundleType;
|
||||
bool confirmed = false;
|
||||
bool useSeed = true;
|
||||
foreach (string arg in args)
|
||||
{
|
||||
if (arg.Equals("confirm", StringComparison.OrdinalIgnoreCase))
|
||||
confirmed = true;
|
||||
else if (arg.Equals("ignore_seed", StringComparison.OrdinalIgnoreCase))
|
||||
useSeed = false;
|
||||
else if (Enum.TryParse(arg, ignoreCase: true, out Game1.BundleType type))
|
||||
bundleType = type;
|
||||
else
|
||||
{
|
||||
monitor.Log($"Invalid option '{arg}'. Type 'help {command}' for usage.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// require confirmation
|
||||
if (!confirmed)
|
||||
{
|
||||
monitor.Log($"WARNING: this may have unintended consequences (type 'help {command}' for details). Are you sure?", LogLevel.Warn);
|
||||
|
||||
string[] newArgs = args.Concat(new[] { "confirm" }).ToArray();
|
||||
monitor.Log($"To confirm, enter this command: '{command} {string.Join(" ", newArgs)}'.", LogLevel.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
// need a loaded save
|
||||
if (!Context.IsWorldReady)
|
||||
{
|
||||
monitor.Log("You need to load a save to use this command.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// get private fields
|
||||
IWorldState state = Game1.netWorldState.Value;
|
||||
var bundleData = state.GetType().GetField("_bundleData", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)?.GetValue(state) as IDictionary<string, string>
|
||||
?? throw new InvalidOperationException("Can't access '_bundleData' field on world state.");
|
||||
var netBundleData = state.GetType().GetField("netBundleData", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)?.GetValue(state) as NetStringDictionary<string, NetString>
|
||||
?? throw new InvalidOperationException("Can't access 'netBundleData' field on world state.");
|
||||
|
||||
// clear bundle data
|
||||
state.BundleData.Clear();
|
||||
state.Bundles.Clear();
|
||||
state.BundleRewards.Clear();
|
||||
bundleData.Clear();
|
||||
netBundleData.Clear();
|
||||
|
||||
// regenerate bundles
|
||||
var locale = LocalizedContentManager.CurrentLanguageCode;
|
||||
try
|
||||
{
|
||||
LocalizedContentManager.CurrentLanguageCode = LocalizedContentManager.LanguageCode.en; // the base bundle data needs to be unlocalized (the game will add localized names later)
|
||||
|
||||
Game1.bundleType = bundleType;
|
||||
Game1.GenerateBundles(bundleType, use_seed: useSeed);
|
||||
}
|
||||
finally
|
||||
{
|
||||
LocalizedContentManager.CurrentLanguageCode = locale;
|
||||
}
|
||||
|
||||
monitor.Log("Regenerated bundles and reset bundle progress.", LogLevel.Info);
|
||||
monitor.Log("This may have unintended effects if you've already completed any bundles. If you're not sure, exit your game without saving to cancel.", LogLevel.Warn);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
||||
{
|
||||
/// <summary>A command which shows the data files.</summary>
|
||||
internal class ShowDataFilesCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class ShowDataFilesCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
||||
{
|
||||
/// <summary>A command which shows the game files.</summary>
|
||||
internal class ShowGameFilesCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class ShowGameFilesCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -18,8 +20,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
/// <param name="args">The command arguments.</param>
|
||||
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
|
||||
{
|
||||
Process.Start(Constants.ExecutionPath);
|
||||
monitor.Log($"OK, opening {Constants.ExecutionPath}.", LogLevel.Info);
|
||||
Process.Start(Constants.GamePath);
|
||||
monitor.Log($"OK, opening {Constants.GamePath}.", LogLevel.Info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
||||
{
|
||||
/// <summary>A command which logs the keys being pressed for 30 seconds once enabled.</summary>
|
||||
internal class TestInputCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class TestInputCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -37,9 +39,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
public override void OnUpdated(IMonitor monitor)
|
||||
{
|
||||
// handle expiry
|
||||
if (this.ExpiryTicks == null)
|
||||
return;
|
||||
if (this.ExpiryTicks <= DateTime.UtcNow.Ticks)
|
||||
if (this.ExpiryTicks != null && this.ExpiryTicks <= DateTime.UtcNow.Ticks)
|
||||
{
|
||||
monitor.Log("No longer logging input.", LogLevel.Info);
|
||||
this.ExpiryTicks = null;
|
||||
|
|
|
@ -7,13 +7,13 @@ using Object = StardewValley.Object;
|
|||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which adds an item to the player inventory.</summary>
|
||||
internal class AddCommand : TrainerCommand
|
||||
internal class AddCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>Provides methods for searching and constructing items.</summary>
|
||||
private readonly ItemRepository Items = new ItemRepository();
|
||||
private readonly ItemRepository Items = new();
|
||||
|
||||
/// <summary>The type names recognized by this command.</summary>
|
||||
private readonly string[] ValidTypes = Enum.GetNames(typeof(ItemType)).Concat(new[] { "Name" }).ToArray();
|
||||
|
@ -40,7 +40,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
}
|
||||
|
||||
// read arguments
|
||||
if (!args.TryGet(0, "item type", out string type, oneOf: this.ValidTypes))
|
||||
if (!args.TryGet(0, "item type", out string? type, oneOf: this.ValidTypes))
|
||||
return;
|
||||
if (!args.TryGetInt(2, "count", out int count, min: 1, required: false))
|
||||
count = 1;
|
||||
|
@ -48,7 +48,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
quality = Object.lowQuality;
|
||||
|
||||
// find matching item
|
||||
SearchableItem match = Enum.TryParse(type, true, out ItemType itemType)
|
||||
SearchableItem? match = Enum.TryParse(type, true, out ItemType itemType)
|
||||
? this.FindItemByID(monitor, args, itemType)
|
||||
: this.FindItemByName(monitor, args);
|
||||
if (match == null)
|
||||
|
@ -76,14 +76,14 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
/// <param name="type">The item type.</param>
|
||||
private SearchableItem FindItemByID(IMonitor monitor, ArgumentParser args, ItemType type)
|
||||
private SearchableItem? FindItemByID(IMonitor monitor, ArgumentParser args, ItemType type)
|
||||
{
|
||||
// read arguments
|
||||
if (!args.TryGetInt(1, "item ID", out int id, min: 0))
|
||||
return null;
|
||||
|
||||
// find matching item
|
||||
SearchableItem item = this.Items.GetAll().FirstOrDefault(p => p.Type == type && p.ID == id);
|
||||
SearchableItem? item = this.Items.GetAll().FirstOrDefault(p => p.Type == type && p.ID == id);
|
||||
if (item == null)
|
||||
monitor.Log($"There's no {type} item with ID {id}.", LogLevel.Error);
|
||||
return item;
|
||||
|
@ -92,10 +92,10 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
/// <summary>Get a matching item by its name.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
private SearchableItem FindItemByName(IMonitor monitor, ArgumentParser args)
|
||||
private SearchableItem? FindItemByName(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
// read arguments
|
||||
if (!args.TryGet(1, "item name", out string name))
|
||||
if (!args.TryGet(1, "item name", out string? name))
|
||||
return null;
|
||||
|
||||
// find matching items
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which list item types.</summary>
|
||||
internal class ListItemTypesCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class ListItemTypesCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>Provides methods for searching and constructing items.</summary>
|
||||
private readonly ItemRepository Items = new ItemRepository();
|
||||
private readonly ItemRepository Items = new();
|
||||
|
||||
|
||||
/*********
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which list items available to spawn.</summary>
|
||||
internal class ListItemsCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class ListItemsCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>Provides methods for searching and constructing items.</summary>
|
||||
private readonly ItemRepository Items = new ItemRepository();
|
||||
private readonly ItemRepository Items = new();
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -59,15 +61,14 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
private IEnumerable<SearchableItem> GetItems(string[] searchWords)
|
||||
{
|
||||
// normalize search term
|
||||
searchWords = searchWords?.Where(word => !string.IsNullOrWhiteSpace(word)).ToArray();
|
||||
if (searchWords?.Any() == false)
|
||||
searchWords = null;
|
||||
searchWords = searchWords.Where(word => !string.IsNullOrWhiteSpace(word)).ToArray();
|
||||
bool getAll = !searchWords.Any();
|
||||
|
||||
// find matches
|
||||
return (
|
||||
from item in this.Items.GetAll()
|
||||
let term = $"{item.ID}|{item.Type}|{item.Name}|{item.DisplayName}"
|
||||
where searchWords == null || searchWords.All(word => term.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) != -1)
|
||||
where getAll || searchWords.All(word => term.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) != -1)
|
||||
select item
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Xna.Framework;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which edits the color of a player feature.</summary>
|
||||
internal class SetColorCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetColorCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -20,9 +22,9 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
|
||||
{
|
||||
// parse arguments
|
||||
if (!args.TryGet(0, "target", out string target, oneOf: new[] { "hair", "eyes", "pants" }))
|
||||
if (!args.TryGet(0, "target", out string? target, oneOf: new[] { "hair", "eyes", "pants" }))
|
||||
return;
|
||||
if (!args.TryGet(1, "color", out string rawColor))
|
||||
if (!args.TryGet(1, "color", out string? rawColor))
|
||||
return;
|
||||
|
||||
// parse color
|
||||
|
@ -61,7 +63,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
/// <param name="color">The color to set.</param>
|
||||
private bool TryParseColor(string input, out Color color)
|
||||
{
|
||||
string[] colorHexes = input.Split(new[] { ',' }, 3);
|
||||
string[] colorHexes = input.Split(',', 3);
|
||||
if (int.TryParse(colorHexes[0], out int r) && int.TryParse(colorHexes[1], out int g) && int.TryParse(colorHexes[2], out int b))
|
||||
{
|
||||
color = new Color(r, g, b);
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using StardewValley;
|
||||
using StardewValley.GameData;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which changes the player's farm type.</summary>
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetFarmTypeCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public SetFarmTypeCommand()
|
||||
: base("set_farm_type", "Sets the current player's farm type.\n\nUsage: set_farm_type <farm type>\n- farm type: the farm type to set. Enter `set_farm_type list` for a list of available farm types.") { }
|
||||
|
||||
/// <summary>Handle the command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="command">The command name.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
|
||||
{
|
||||
// validate
|
||||
if (!Context.IsWorldReady)
|
||||
{
|
||||
monitor.Log("You must load a save to use this command.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// parse arguments
|
||||
if (!args.TryGet(0, "farm type", out string? farmType))
|
||||
return;
|
||||
bool isVanillaId = int.TryParse(farmType, out int vanillaId) && vanillaId is (>= 0 and < Farm.layout_max);
|
||||
|
||||
// handle argument
|
||||
if (farmType == "list")
|
||||
this.HandleList(monitor);
|
||||
else if (isVanillaId)
|
||||
this.HandleVanillaFarmType(vanillaId, monitor);
|
||||
else
|
||||
this.HandleCustomFarmType(farmType, monitor);
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/****
|
||||
** Handlers
|
||||
****/
|
||||
/// <summary>Print a list of available farm types.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
private void HandleList(IMonitor monitor)
|
||||
{
|
||||
StringBuilder result = new();
|
||||
|
||||
// list vanilla types
|
||||
result.AppendLine("The farm type can be one of these vanilla types:");
|
||||
foreach (var type in this.GetVanillaFarmTypes())
|
||||
result.AppendLine($" - {type.Key} ({type.Value})");
|
||||
result.AppendLine();
|
||||
|
||||
// list custom types
|
||||
{
|
||||
var customTypes = this.GetCustomFarmTypes();
|
||||
if (customTypes.Any())
|
||||
{
|
||||
result.AppendLine("Or one of these custom farm types:");
|
||||
foreach (var type in customTypes.Values.OrderBy(p => p.ID))
|
||||
result.AppendLine($" - {type.ID} ({this.GetCustomName(type)})");
|
||||
}
|
||||
else
|
||||
result.AppendLine("Or a custom farm type (though none is loaded currently).");
|
||||
}
|
||||
|
||||
// print
|
||||
monitor.Log(result.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
/// <summary>Set a vanilla farm type.</summary>
|
||||
/// <param name="type">The farm type.</param>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
private void HandleVanillaFarmType(int type, IMonitor monitor)
|
||||
{
|
||||
if (Game1.whichFarm == type)
|
||||
{
|
||||
monitor.Log($"Your current farm is already set to {type} ({this.GetVanillaName(type)}).", LogLevel.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
this.SetFarmType(type, null);
|
||||
this.PrintSuccess(monitor, $"{type} ({this.GetVanillaName(type)}");
|
||||
}
|
||||
|
||||
/// <summary>Set a custom farm type.</summary>
|
||||
/// <param name="id">The farm type ID.</param>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
private void HandleCustomFarmType(string id, IMonitor monitor)
|
||||
{
|
||||
if (Game1.whichModFarm?.ID == id)
|
||||
{
|
||||
monitor.Log($"Your current farm is already set to {id} ({this.GetCustomName(Game1.whichModFarm)}).", LogLevel.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.GetCustomFarmTypes().TryGetValue(id, out ModFarmType? customFarmType))
|
||||
{
|
||||
monitor.Log($"Invalid farm type '{id}'. Enter `help set_farm_type` for more info.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.SetFarmType(Farm.mod_layout, customFarmType);
|
||||
this.PrintSuccess(monitor, $"{id} ({this.GetCustomName(customFarmType)})");
|
||||
}
|
||||
|
||||
/// <summary>Change the farm type.</summary>
|
||||
/// <param name="type">The farm type ID.</param>
|
||||
/// <param name="customFarmData">The custom farm type data, if applicable.</param>
|
||||
private void SetFarmType(int type, ModFarmType? customFarmData)
|
||||
{
|
||||
// set flags
|
||||
Game1.whichFarm = type;
|
||||
Game1.whichModFarm = customFarmData;
|
||||
|
||||
// update farm map
|
||||
Farm farm = Game1.getFarm();
|
||||
farm.mapPath.Value = $@"Maps\{Farm.getMapNameFromTypeInt(Game1.whichFarm)}";
|
||||
farm.reloadMap();
|
||||
farm.updateWarps();
|
||||
|
||||
// clear spouse area cache to avoid errors
|
||||
FieldInfo? cacheField = farm.GetType().GetField("_baseSpouseAreaTiles", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
if (cacheField == null)
|
||||
throw new InvalidOperationException("Failed to access '_baseSpouseAreaTiles' field to clear spouse area cache.");
|
||||
if (cacheField.GetValue(farm) is not IDictionary cache)
|
||||
throw new InvalidOperationException($"The farm's '_baseSpouseAreaTiles' field didn't match the expected {nameof(IDictionary)} type.");
|
||||
cache.Clear();
|
||||
}
|
||||
|
||||
private void PrintSuccess(IMonitor monitor, string label)
|
||||
{
|
||||
StringBuilder result = new();
|
||||
result.AppendLine($"Your current farm has been converted to {label}. Saving and reloading is recommended to make sure everything is updated for the change.");
|
||||
result.AppendLine();
|
||||
result.AppendLine("This doesn't move items that are out of bounds on the new map. If you need to clean up, you can...");
|
||||
result.AppendLine(" - temporarily switch back to the previous farm type;");
|
||||
result.AppendLine(" - or use a mod like Noclip Mode: https://www.nexusmods.com/stardewvalley/mods/3900 ;");
|
||||
result.AppendLine(" - or use the world_clear console command (enter `help world_clear` for details).");
|
||||
|
||||
monitor.Log(result.ToString(), LogLevel.Warn);
|
||||
}
|
||||
|
||||
/****
|
||||
** Vanilla farm types
|
||||
****/
|
||||
/// <summary>Get the display name for a vanilla farm type.</summary>
|
||||
/// <param name="type">The farm type.</param>
|
||||
private string GetVanillaName(int type)
|
||||
{
|
||||
string? translationKey = type switch
|
||||
{
|
||||
Farm.default_layout => "Character_FarmStandard",
|
||||
Farm.riverlands_layout => "Character_FarmFishing",
|
||||
Farm.forest_layout => "Character_FarmForaging",
|
||||
Farm.mountains_layout => "Character_FarmMining",
|
||||
Farm.combat_layout => "Character_FarmCombat",
|
||||
Farm.fourCorners_layout => "Character_FarmFourCorners",
|
||||
Farm.beach_layout => "Character_FarmBeach",
|
||||
_ => null
|
||||
};
|
||||
|
||||
return translationKey != null
|
||||
? Game1.content.LoadString(@$"Strings\UI:{translationKey}").Split('_')[0]
|
||||
: type.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Get the available vanilla farm types by ID.</summary>
|
||||
private IDictionary<int, string> GetVanillaFarmTypes()
|
||||
{
|
||||
IDictionary<int, string> farmTypes = new Dictionary<int, string>();
|
||||
|
||||
foreach (int id in Enumerable.Range(0, Farm.layout_max))
|
||||
farmTypes[id] = this.GetVanillaName(id);
|
||||
|
||||
return farmTypes;
|
||||
}
|
||||
|
||||
/****
|
||||
** Custom farm types
|
||||
****/
|
||||
/// <summary>Get the display name for a custom farm type.</summary>
|
||||
/// <param name="farmType">The custom farm type.</param>
|
||||
private string? GetCustomName(ModFarmType? farmType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(farmType?.TooltipStringPath))
|
||||
return farmType?.ID;
|
||||
|
||||
return Game1.content.LoadString(farmType.TooltipStringPath)?.Split('_')[0] ?? farmType.ID;
|
||||
}
|
||||
|
||||
/// <summary>Get the available custom farm types by ID.</summary>
|
||||
private IDictionary<string, ModFarmType> GetCustomFarmTypes()
|
||||
{
|
||||
IDictionary<string, ModFarmType> farmTypes = new Dictionary<string, ModFarmType>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (ModFarmType farmType in Game1.content.Load<List<ModFarmType>>("Data\\AdditionalFarms"))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(farmType.ID))
|
||||
continue;
|
||||
|
||||
farmTypes[farmType.ID] = farmType;
|
||||
}
|
||||
|
||||
return farmTypes;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,19 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which edits the player's current health.</summary>
|
||||
internal class SetHealthCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetHealthCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>Whether to keep the player's health at its maximum.</summary>
|
||||
private bool InfiniteHealth;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public SetHealthCommand()
|
||||
: base("player_sethealth", "Sets the player's health.\n\nUsage: player_sethealth [value]\n- value: an integer amount, or 'inf' for infinite health.", mayNeedUpdate: true) { }
|
||||
: base("player_sethealth", "Sets the player's health.\n\nUsage: player_sethealth [value]\n- value: an integer amount.") { }
|
||||
|
||||
/// <summary>Handle the command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
|
@ -29,36 +24,19 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
// no-argument mode
|
||||
if (!args.Any())
|
||||
{
|
||||
monitor.Log($"You currently have {(this.InfiniteHealth ? "infinite" : Game1.player.health.ToString())} health. Specify a value to change it.", LogLevel.Info);
|
||||
monitor.Log($"You currently have {Game1.player.health} health. Specify a value to change it.", LogLevel.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle
|
||||
string amountStr = args[0];
|
||||
if (amountStr == "inf")
|
||||
if (int.TryParse(amountStr, out int amount))
|
||||
{
|
||||
this.InfiniteHealth = true;
|
||||
monitor.Log("OK, you now have infinite health.", LogLevel.Info);
|
||||
Game1.player.health = amount;
|
||||
monitor.Log($"OK, you now have {Game1.player.health} health.", LogLevel.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.InfiniteHealth = false;
|
||||
if (int.TryParse(amountStr, out int amount))
|
||||
{
|
||||
Game1.player.health = amount;
|
||||
monitor.Log($"OK, you now have {Game1.player.health} health.", LogLevel.Info);
|
||||
}
|
||||
else
|
||||
this.LogArgumentNotInt(monitor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Perform any logic needed on update tick.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
public override void OnUpdated(IMonitor monitor)
|
||||
{
|
||||
if (this.InfiniteHealth && Context.IsWorldReady)
|
||||
Game1.player.health = Game1.player.maxHealth;
|
||||
this.LogArgumentNotInt(monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which edits the player's current immunity.</summary>
|
||||
internal class SetImmunityCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetImmunityCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which edits the player's maximum health.</summary>
|
||||
internal class SetMaxHealthCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetMaxHealthCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which edits the player's maximum stamina.</summary>
|
||||
internal class SetMaxStaminaCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetMaxStaminaCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which edits the player's current money.</summary>
|
||||
internal class SetMoneyCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetMoneyCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>Whether to keep the player's money at a set value.</summary>
|
||||
private bool InfiniteMoney;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public SetMoneyCommand()
|
||||
: base("player_setmoney", "Sets the player's money.\n\nUsage: player_setmoney <value>\n- value: an integer amount, or 'inf' for infinite money.", mayNeedUpdate: true) { }
|
||||
: base("player_setmoney", "Sets the player's money.\n\nUsage: player_setmoney <value>\n- value: an integer amount.") { }
|
||||
|
||||
/// <summary>Handle the command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
|
@ -29,36 +24,19 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
// validate
|
||||
if (!args.Any())
|
||||
{
|
||||
monitor.Log($"You currently have {(this.InfiniteMoney ? "infinite" : Game1.player.Money.ToString())} gold. Specify a value to change it.", LogLevel.Info);
|
||||
monitor.Log($"You currently have {Game1.player.Money} gold. Specify a value to change it.", LogLevel.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle
|
||||
string amountStr = args[0];
|
||||
if (amountStr == "inf")
|
||||
if (int.TryParse(amountStr, out int amount))
|
||||
{
|
||||
this.InfiniteMoney = true;
|
||||
monitor.Log("OK, you now have infinite money.", LogLevel.Info);
|
||||
Game1.player.Money = amount;
|
||||
monitor.Log($"OK, you now have {Game1.player.Money} gold.", LogLevel.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.InfiniteMoney = false;
|
||||
if (int.TryParse(amountStr, out int amount))
|
||||
{
|
||||
Game1.player.Money = amount;
|
||||
monitor.Log($"OK, you now have {Game1.player.Money} gold.", LogLevel.Info);
|
||||
}
|
||||
else
|
||||
this.LogArgumentNotInt(monitor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Perform any logic needed on update tick.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
public override void OnUpdated(IMonitor monitor)
|
||||
{
|
||||
if (this.InfiniteMoney && Context.IsWorldReady)
|
||||
Game1.player.Money = 999999;
|
||||
this.LogArgumentNotInt(monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which edits the player's name.</summary>
|
||||
internal class SetNameCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetNameCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -19,9 +21,9 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
|
||||
{
|
||||
// parse arguments
|
||||
if (!args.TryGet(0, "target", out string target, oneOf: new[] { "player", "farm" }))
|
||||
if (!args.TryGet(0, "target", out string? target, oneOf: new[] { "player", "farm" }))
|
||||
return;
|
||||
args.TryGet(1, "name", out string name, required: false);
|
||||
args.TryGet(1, "name", out string? name, required: false);
|
||||
|
||||
// handle
|
||||
switch (target)
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which edits the player's current stamina.</summary>
|
||||
internal class SetStaminaCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetStaminaCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>Whether to keep the player's stamina at its maximum.</summary>
|
||||
private bool InfiniteStamina;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public SetStaminaCommand()
|
||||
: base("player_setstamina", "Sets the player's stamina.\n\nUsage: player_setstamina [value]\n- value: an integer amount, or 'inf' for infinite stamina.", mayNeedUpdate: true) { }
|
||||
: base("player_setstamina", "Sets the player's stamina.\n\nUsage: player_setstamina [value]\n- value: an integer amount.") { }
|
||||
|
||||
/// <summary>Handle the command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
|
@ -29,36 +24,19 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
// validate
|
||||
if (!args.Any())
|
||||
{
|
||||
monitor.Log($"You currently have {(this.InfiniteStamina ? "infinite" : Game1.player.Stamina.ToString())} stamina. Specify a value to change it.", LogLevel.Info);
|
||||
monitor.Log($"You currently have {Game1.player.Stamina} stamina. Specify a value to change it.", LogLevel.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle
|
||||
string amountStr = args[0];
|
||||
if (amountStr == "inf")
|
||||
if (int.TryParse(amountStr, out int amount))
|
||||
{
|
||||
this.InfiniteStamina = true;
|
||||
monitor.Log("OK, you now have infinite stamina.", LogLevel.Info);
|
||||
Game1.player.Stamina = amount;
|
||||
monitor.Log($"OK, you now have {Game1.player.Stamina} stamina.", LogLevel.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.InfiniteStamina = false;
|
||||
if (int.TryParse(amountStr, out int amount))
|
||||
{
|
||||
Game1.player.Stamina = amount;
|
||||
monitor.Log($"OK, you now have {Game1.player.Stamina} stamina.", LogLevel.Info);
|
||||
}
|
||||
else
|
||||
this.LogArgumentNotInt(monitor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Perform any logic needed on update tick.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
public override void OnUpdated(IMonitor monitor)
|
||||
{
|
||||
if (this.InfiniteStamina && Context.IsWorldReady)
|
||||
Game1.player.stamina = Game1.player.MaxStamina;
|
||||
this.LogArgumentNotInt(monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||
{
|
||||
/// <summary>A command which edits a player style.</summary>
|
||||
internal class SetStyleCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetStyleCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -19,7 +21,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
|
||||
{
|
||||
// parse arguments
|
||||
if (!args.TryGet(0, "target", out string target, oneOf: new[] { "hair", "shirt", "acc", "skin", "shoe", "swim", "gender" }))
|
||||
if (!args.TryGet(0, "target", out string? target, oneOf: new[] { "hair", "shirt", "acc", "skin", "shoe", "swim", "gender" }))
|
||||
return;
|
||||
if (!args.TryGetInt(1, "style ID", out int styleID))
|
||||
return;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using StardewValley;
|
||||
using StardewValley.Locations;
|
||||
using StardewValley.Objects;
|
||||
|
@ -10,13 +11,14 @@ using SObject = StardewValley.Object;
|
|||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
||||
{
|
||||
/// <summary>A command which clears in-game objects.</summary>
|
||||
internal class ClearCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class ClearCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The valid types that can be cleared.</summary>
|
||||
private readonly string[] ValidTypes = { "crops", "debris", "fruit-trees", "grass", "trees", "everything" };
|
||||
private readonly string[] ValidTypes = { "crops", "debris", "fruit-trees", "furniture", "grass", "trees", "removable", "everything" };
|
||||
|
||||
/// <summary>The resource clump IDs to consider debris.</summary>
|
||||
private readonly int[] DebrisClumps = { ResourceClump.stumpIndex, ResourceClump.hollowLogIndex, ResourceClump.meteoriteIndex, ResourceClump.boulderIndex };
|
||||
|
@ -31,8 +33,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
name: "world_clear",
|
||||
description: "Clears in-game entities in a given location.\n\n"
|
||||
+ "Usage: world_clear <location> <object type>\n"
|
||||
+ "- location: the location name for which to clear objects (like Farm), or 'current' for the current location.\n"
|
||||
+ " - object type: the type of object clear. You can specify 'crops', 'debris' (stones/twigs/weeds and dead crops), 'grass', and 'trees' / 'fruit-trees'. You can also specify 'everything', which includes things not removed by the other types (like furniture or resource clumps)."
|
||||
+ " - location: the location name for which to clear objects (like Farm), or 'current' for the current location.\n"
|
||||
+ " - object type: the type of object clear. You can specify 'crops', 'debris' (stones/twigs/weeds and dead crops), 'furniture', 'grass', and 'trees' / 'fruit-trees'. You can also specify 'removable' (remove everything that can be removed or destroyed during normal gameplay) or 'everything' (remove everything including permanent bushes)."
|
||||
)
|
||||
{ }
|
||||
|
||||
|
@ -50,13 +52,13 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
}
|
||||
|
||||
// parse arguments
|
||||
if (!args.TryGet(0, "location", out string locationName, required: true))
|
||||
if (!args.TryGet(0, "location", out string? locationName, required: true))
|
||||
return;
|
||||
if (!args.TryGet(1, "object type", out string type, required: true, oneOf: this.ValidTypes))
|
||||
if (!args.TryGet(1, "object type", out string? type, required: true, oneOf: this.ValidTypes))
|
||||
return;
|
||||
|
||||
// get target location
|
||||
GameLocation location = Game1.locations.FirstOrDefault(p => p.Name != null && p.Name.Equals(locationName, StringComparison.OrdinalIgnoreCase));
|
||||
GameLocation? location = Game1.locations.FirstOrDefault(p => p.Name != null && p.Name.Equals(locationName, StringComparison.OrdinalIgnoreCase));
|
||||
if (location == null && locationName == "current")
|
||||
location = Game1.currentLocation;
|
||||
if (location == null)
|
||||
|
@ -93,11 +95,10 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
|
||||
removed +=
|
||||
this.RemoveObjects(location, obj =>
|
||||
!(obj is Chest)
|
||||
obj is not Chest
|
||||
&& (
|
||||
obj.Name == "Weeds"
|
||||
|| obj.Name == "Stone"
|
||||
|| (obj.ParentSheetIndex == 294 || obj.ParentSheetIndex == 295)
|
||||
obj.Name is "Weeds" or "Stone"
|
||||
|| obj.ParentSheetIndex is 294 or 295
|
||||
)
|
||||
)
|
||||
+ this.RemoveResourceClumps(location, clump => this.DebrisClumps.Contains(clump.parentSheetIndex.Value));
|
||||
|
@ -113,6 +114,13 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
break;
|
||||
}
|
||||
|
||||
case "furniture":
|
||||
{
|
||||
int removed = this.RemoveFurniture(location, _ => true);
|
||||
monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info);
|
||||
break;
|
||||
}
|
||||
|
||||
case "grass":
|
||||
{
|
||||
int removed = this.RemoveTerrainFeatures(location, feature => feature is Grass);
|
||||
|
@ -127,14 +135,16 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
break;
|
||||
}
|
||||
|
||||
case "removable":
|
||||
case "everything":
|
||||
{
|
||||
bool everything = type == "everything";
|
||||
int removed =
|
||||
this.RemoveFurniture(location, p => true)
|
||||
+ this.RemoveObjects(location, p => true)
|
||||
+ this.RemoveTerrainFeatures(location, p => true)
|
||||
+ this.RemoveLargeTerrainFeatures(location, p => true)
|
||||
+ this.RemoveResourceClumps(location, p => true);
|
||||
this.RemoveFurniture(location, _ => true)
|
||||
+ this.RemoveObjects(location, _ => true)
|
||||
+ this.RemoveTerrainFeatures(location, _ => true)
|
||||
+ this.RemoveLargeTerrainFeatures(location, p => everything || p is not Bush bush || bush.isDestroyable(location, p.currentTileLocation))
|
||||
+ this.RemoveResourceClumps(location, _ => true);
|
||||
monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info);
|
||||
break;
|
||||
}
|
||||
|
@ -157,11 +167,11 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
{
|
||||
int removed = 0;
|
||||
|
||||
foreach (var pair in location.Objects.Pairs.ToArray())
|
||||
foreach ((Vector2 tile, SObject? obj) in location.Objects.Pairs.ToArray())
|
||||
{
|
||||
if (shouldRemove(pair.Value))
|
||||
if (shouldRemove(obj))
|
||||
{
|
||||
location.Objects.Remove(pair.Key);
|
||||
location.Objects.Remove(tile);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
@ -177,11 +187,11 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
{
|
||||
int removed = 0;
|
||||
|
||||
foreach (var pair in location.terrainFeatures.Pairs.ToArray())
|
||||
foreach ((Vector2 tile, TerrainFeature? feature) in location.terrainFeatures.Pairs.ToArray())
|
||||
{
|
||||
if (shouldRemove(pair.Value))
|
||||
if (shouldRemove(feature))
|
||||
{
|
||||
location.terrainFeatures.Remove(pair.Key);
|
||||
location.terrainFeatures.Remove(tile);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
@ -217,18 +227,17 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
{
|
||||
int removed = 0;
|
||||
|
||||
// get resource clumps
|
||||
IList<ResourceClump> resourceClumps =
|
||||
(location as Farm)?.resourceClumps
|
||||
?? (IList<ResourceClump>)(location as Woods)?.stumps
|
||||
?? new List<ResourceClump>();
|
||||
|
||||
// remove matching clumps
|
||||
foreach (var clump in resourceClumps.ToArray())
|
||||
foreach (ResourceClump clump in location.resourceClumps.Where(shouldRemove).ToArray())
|
||||
{
|
||||
if (shouldRemove(clump))
|
||||
location.resourceClumps.Remove(clump);
|
||||
removed++;
|
||||
}
|
||||
|
||||
if (location is Woods woods)
|
||||
{
|
||||
foreach (ResourceClump clump in woods.stumps.Where(shouldRemove).ToArray())
|
||||
{
|
||||
resourceClumps.Remove(clump);
|
||||
woods.stumps.Remove(clump);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
@ -244,15 +253,12 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
{
|
||||
int removed = 0;
|
||||
|
||||
if (location is DecoratableLocation decoratableLocation)
|
||||
foreach (Furniture furniture in location.furniture.ToArray())
|
||||
{
|
||||
foreach (Furniture furniture in decoratableLocation.furniture.ToArray())
|
||||
if (shouldRemove(furniture))
|
||||
{
|
||||
if (shouldRemove(furniture))
|
||||
{
|
||||
decoratableLocation.furniture.Remove(furniture);
|
||||
removed++;
|
||||
}
|
||||
location.furniture.Remove(furniture);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewValley;
|
||||
using StardewValley.Locations;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
||||
{
|
||||
/// <summary>A command which moves the player to the next mine level.</summary>
|
||||
internal class DownMineLevelCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class DownMineLevelCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
|
|
@ -4,7 +4,7 @@ using StardewValley;
|
|||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
||||
{
|
||||
/// <summary>A command which freezes the current time.</summary>
|
||||
internal class FreezeTimeCommand : TrainerCommand
|
||||
internal class FreezeTimeCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
||||
{
|
||||
/// <summary>A command which immediately warps all NPCs to their scheduled positions. To hurry a single NPC, see <c>debug hurry npc-name</c> instead.</summary>
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class HurryAllCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public HurryAllCommand()
|
||||
: base(
|
||||
name: "hurry_all",
|
||||
description: "Immediately warps all NPCs to their scheduled positions. (To hurry a single NPC, use `debug hurry npc-name` instead.)\n\n"
|
||||
+ "Usage: hurry_all"
|
||||
)
|
||||
{ }
|
||||
|
||||
/// <summary>Handle the command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="command">The command name.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
|
||||
{
|
||||
// check context
|
||||
if (!Context.IsWorldReady)
|
||||
{
|
||||
monitor.Log("You need to load a save to use this command.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// hurry all NPCs
|
||||
foreach (NPC npc in Utility.getAllCharacters())
|
||||
{
|
||||
if (!npc.isVillager())
|
||||
continue;
|
||||
|
||||
monitor.Log($"Hurrying {npc.Name}...");
|
||||
try
|
||||
{
|
||||
npc.warpToPathControllerDestination();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
monitor.Log($"Failed hurrying {npc.Name}. Technical details:\n{ex}", LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
monitor.Log("Done!", LogLevel.Info);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Utilities;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
||||
{
|
||||
/// <summary>A command which sets the current day.</summary>
|
||||
internal class SetDayCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetDayCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
||||
{
|
||||
/// <summary>A command which moves the player to the given mine level.</summary>
|
||||
internal class SetMineLevelCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetMineLevelCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Utilities;
|
||||
using StardewValley;
|
||||
|
@ -5,7 +6,8 @@ using StardewValley;
|
|||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
||||
{
|
||||
/// <summary>A command which sets the current season.</summary>
|
||||
internal class SetSeasonCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetSeasonCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -35,7 +37,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
}
|
||||
|
||||
// parse arguments
|
||||
if (!args.TryGet(0, "season", out string season, oneOf: this.ValidSeasons))
|
||||
if (!args.TryGet(0, "season", out string? season, oneOf: this.ValidSeasons))
|
||||
return;
|
||||
|
||||
// handle
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
||||
{
|
||||
/// <summary>A command which sets the current time.</summary>
|
||||
internal class SetTimeCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetTimeCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -45,12 +47,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
/// <param name="time">The time of day.</param>
|
||||
private void SafelySetTime(int time)
|
||||
{
|
||||
// define conversion between game time and TimeSpan
|
||||
TimeSpan ToTimeSpan(int value) => new TimeSpan(0, value / 100, value % 100, 0);
|
||||
int FromTimeSpan(TimeSpan span) => (span.Hours * 100) + span.Minutes;
|
||||
|
||||
// transition to new time
|
||||
int intervals = (int)((ToTimeSpan(time) - ToTimeSpan(Game1.timeOfDay)).TotalMinutes / 10);
|
||||
int intervals = Utility.CalculateMinutesBetweenTimes(Game1.timeOfDay, time) / 10;
|
||||
if (intervals > 0)
|
||||
{
|
||||
for (int i = 0; i < intervals; i++)
|
||||
|
@ -60,10 +58,20 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
|||
{
|
||||
for (int i = 0; i > intervals; i--)
|
||||
{
|
||||
Game1.timeOfDay = FromTimeSpan(ToTimeSpan(Game1.timeOfDay).Subtract(TimeSpan.FromMinutes(20))); // offset 20 minutes so game updates to next interval
|
||||
Game1.timeOfDay = Utility.ModifyTime(Game1.timeOfDay, -20); // offset 20 mins so game updates to next interval
|
||||
Game1.performTenMinuteClockUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// reset ambient light
|
||||
// White is the default non-raining color. If it's raining or dark out, UpdateGameClock
|
||||
// below will update it automatically.
|
||||
Game1.outdoorLight = Color.White;
|
||||
Game1.ambientLight = Color.White;
|
||||
|
||||
// run clock update (to correct lighting, etc)
|
||||
Game1.gameTimeInterval = 0;
|
||||
Game1.UpdateGameClock(Game1.currentGameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Utilities;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
||||
{
|
||||
/// <summary>A command which sets the current year.</summary>
|
||||
internal class SetYearCommand : TrainerCommand
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")]
|
||||
internal class SetYearCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue