Merge branch 'beta' into develop
This commit is contained in:
commit
919bbe94aa
Binary file not shown.
|
@ -7,7 +7,7 @@
|
|||
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||
|
||||
<!--set platform-->
|
||||
<DefineConstants Condition="$(OS) == 'Windows_NT'">$(DefineConstants);SMAPI_FOR_WINDOWS;SMAPI_FOR_XNA</DefineConstants>
|
||||
<DefineConstants Condition="$(OS) == 'Windows_NT'">$(DefineConstants);SMAPI_FOR_WINDOWS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--find game folder-->
|
||||
|
@ -27,20 +27,31 @@
|
|||
<TranslationFiles Include="$(TargetDir)\i18n\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFolder="$(GamePath)" />
|
||||
<!-- SMAPI -->
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFolder="$(GamePath)" Condition="$(OS) == 'Windows_NT'" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName)" DestinationFolder="$(GamePath)" Condition="$(OS) != 'Windows_NT'" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)" />
|
||||
<Copy SourceFiles="$(TargetDir)\SMAPI.config.json" DestinationFiles="$(GamePath)\smapi-internal\config.json" />
|
||||
<Copy SourceFiles="$(TargetDir)\SMAPI.metadata.json" DestinationFiles="$(GamePath)\smapi-internal\metadata.json" />
|
||||
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\TMXTile.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="@(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" />
|
||||
<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" />
|
||||
|
||||
<!-- .NET dependencies -->
|
||||
<Copy SourceFiles="$(TargetDir)\System.Configuration.ConfigurationManager.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\System.Management.dll" DestinationFolder="$(GamePath)\smapi-internal" Condition="$(OS) == 'Windows_NT'" />
|
||||
<Copy SourceFiles="$(TargetDir)\System.Runtime.Caching.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\System.Security.Permissions.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyDefaultMods" Condition="'$(MSBuildProjectName)' == 'SMAPI.Mods.ConsoleCommands' OR '$(MSBuildProjectName)' == 'SMAPI.Mods.ErrorHandler' OR '$(MSBuildProjectName)' == 'SMAPI.Mods.SaveBackup'">
|
||||
|
@ -54,13 +65,13 @@
|
|||
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)\i18n" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit' AND $(TargetFramework) == 'net452'" AfterTargets="PostBuildEvent">
|
||||
<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' AND $(TargetFramework) == 'net452'" AfterTargets="PostBuildEvent">
|
||||
<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" />
|
||||
|
|
|
@ -41,14 +41,4 @@
|
|||
</PropertyGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
|
||||
<!-- set game metadata -->
|
||||
<PropertyGroup>
|
||||
<!--standard executable name-->
|
||||
<GameExecutableName>Stardew Valley</GameExecutableName>
|
||||
<GameExecutableName Condition="$(OS) != 'Windows_NT'">StardewValley</GameExecutableName>
|
||||
|
||||
<!--Linux install on Windows (for 64-bit hack)-->
|
||||
<GameExecutableName Condition="$(OS) == 'Windows_NT' AND !Exists('$(GamePath)\$(GameExecutableName).exe') AND Exists('$(GamePath)\StardewValley.exe')">StardewValley</GameExecutableName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<OutRootPath>$(SolutionDir)\..\bin</OutRootPath>
|
||||
|
||||
<SmapiBin>$(BuildRootPath)\SMAPI\bin\$(Configuration)</SmapiBin>
|
||||
<ToolkitBin>$(BuildRootPath)\SMAPI.Toolkit\bin\$(Configuration)\net452</ToolkitBin>
|
||||
<ToolkitBin>$(BuildRootPath)\SMAPI.Toolkit\bin\$(Configuration)\net5.0</ToolkitBin>
|
||||
<ConsoleCommandsBin>$(BuildRootPath)\SMAPI.Mods.ConsoleCommands\bin\$(Configuration)</ConsoleCommandsBin>
|
||||
<ErrorHandlerBin>$(BuildRootPath)\SMAPI.Mods.ErrorHandler\bin\$(Configuration)</ErrorHandlerBin>
|
||||
<SaveBackupBin>$(BuildRootPath)\SMAPI.Mods.SaveBackup\bin\$(Configuration)</SaveBackupBin>
|
||||
|
@ -35,12 +35,16 @@
|
|||
<Copy SourceFiles="$(TargetDir)\assets\unix-install.sh" DestinationFiles="$(PackagePath)\install on Linux.sh" />
|
||||
<Copy SourceFiles="$(TargetDir)\assets\unix-install.sh" DestinationFiles="$(PackagePath)\install on macOS.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 SourceFiles="$(TargetDir)\assets\README.txt" DestinationFolder="$(PackagePath)" />
|
||||
<Copy SourceFiles="$(TargetDir)\assets\windows-exe-config.xml" DestinationFiles="$(PackagePath)\internal\$(PlatformName)\install.exe.config" Condition="$(PlatformName) == 'windows'" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(PackagePath)\internal\$(PlatformName)" />
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).runtimeconfig.json" DestinationFolder="$(PackagePath)\internal\$(PlatformName)" />
|
||||
|
||||
<!--copy bundle files-->
|
||||
<Copy SourceFiles="$(SmapiBin)\StardewModdingAPI.exe" DestinationFolder="$(PackagePath)\bundle" />
|
||||
<Copy SourceFiles="$(TargetDir)\assets\runtimeconfig.$(PlatformName).json" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI.runtimeconfig.json" />
|
||||
<Copy SourceFiles="$(SmapiBin)\StardewModdingAPI.dll" DestinationFolder="$(PackagePath)\bundle" />
|
||||
<Copy SourceFiles="$(SmapiBin)\StardewModdingAPI.exe" DestinationFolder="$(PackagePath)\bundle" Condition="$(PlatformName) == 'windows'" />
|
||||
<Copy SourceFiles="$(SmapiBin)\StardewModdingAPI" DestinationFolder="$(PackagePath)\bundle" Condition="$(PlatformName) != 'windows'" />
|
||||
<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" />
|
||||
|
@ -61,11 +65,16 @@
|
|||
<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="$(TargetDir)\assets\unix-launcher.sh" DestinationFolder="$(PackagePath)\bundle" />
|
||||
<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 .NET dependencies -->
|
||||
<Copy SourceFiles="$(SmapiBin)\System.Configuration.ConfigurationManager.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(SmapiBin)\System.Management.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" Condition="$(PlatformName) == 'windows'" />
|
||||
<Copy SourceFiles="$(SmapiBin)\System.Runtime.Caching.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(SmapiBin)\System.Security.Permissions.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
|
||||
<!--copy bundled mods-->
|
||||
<Copy SourceFiles="$(ConsoleCommandsBin)\ConsoleCommands.dll" DestinationFolder="$(PackagePath)\bundle\Mods\ConsoleCommands" />
|
||||
<Copy SourceFiles="$(ConsoleCommandsBin)\ConsoleCommands.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\ConsoleCommands" />
|
||||
|
@ -78,63 +87,33 @@
|
|||
<Copy SourceFiles="$(SaveBackupBin)\SaveBackup.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
|
||||
<Copy SourceFiles="$(SaveBackupBin)\manifest.json" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
|
||||
|
||||
<!-- fix errors on Linux/macOS (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/macOS permissions -->
|
||||
<Exec Condition="$(PlatformName) == 'unix'" Command="chmod 755 "$(PackagePath)\install on Linux.sh"" />
|
||||
<Exec Condition="$(PlatformName) == 'unix'" Command="chmod 755 "$(PackagePath)\install on macOS.command"" />
|
||||
<Exec Condition="$(PlatformName) == 'unix'" Command="chmod 755 "$(PackagePath)/install on Linux.sh"" />
|
||||
<Exec Condition="$(PlatformName) == 'unix'" Command="chmod 755 "$(PackagePath)/install on macOS.command"" />
|
||||
<Exec Condition="$(PlatformName) == 'unix'" Command="chmod 755 "$(PackagePath)/bundle/unix-launcher.sh"" />
|
||||
|
||||
<!-- 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" />
|
||||
<ZipDirectory SourceDirectory="$(PackageDevPath)\bundle" DestinationFile="$(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" />
|
||||
<ZipDirectory SourceDirectory="$(PackagePath)\bundle" DestinationFile="$(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">
|
||||
<UsingTask TaskName="ReplaceFileText" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.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" />
|
||||
|
|
|
@ -3,7 +3,25 @@
|
|||
# Release notes
|
||||
## Upcoming release
|
||||
* For players:
|
||||
* Updated for Stardew Valley 1.5.5.
|
||||
* Updated compatibility list.
|
||||
* 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).
|
||||
* 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.
|
||||
|
||||
* 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).
|
||||
|
||||
**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#Stardew_Valley_compatibility_branch).
|
||||
|
||||
Unfortunately **SMAPI only supports the main branch of the game**. There are formidable difficulties
|
||||
across all mods in supporting all three variations, the [Steam hardware stats](https://store.steampowered.com/hwsurvey)
|
||||
show that 99.69% of players have 64-bit, and 32-bit imposes significant restrictions on what mods
|
||||
can do.
|
||||
|
||||
* For the web UI:
|
||||
* Updated the JSON validator/schema for Content Patcher 1.24.0.
|
||||
|
|
|
@ -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/macOS, 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
|
||||
|
@ -129,23 +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/macOS or
|
||||
`Stardew Valley.exe` on Windows). This is auto-detected, and you should almost never change this.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GameFramework</code></td>
|
||||
<td>
|
||||
|
||||
The game framework for which the mod is being compiled (one of `Xna` or `MonoGame`). This is
|
||||
auto-detected based on the platform, and you should almost never change this.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</li>
|
||||
|
||||
|
@ -206,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>
|
||||
|
@ -222,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>
|
||||
|
@ -330,16 +378,15 @@ The configuration will check your custom path first, then fall back to the defau
|
|||
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>
|
||||
|
@ -347,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
|
||||
|
@ -366,13 +413,31 @@ when you compile it.
|
|||
|
||||
## Release notes
|
||||
## Upcoming release
|
||||
* 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 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.
|
||||
|
||||
|
@ -380,7 +445,8 @@ Released 30 March 2021.
|
|||
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.
|
||||
|
|
|
@ -57,7 +57,6 @@ SMAPI uses a small number of conditional compilation constants, which you can se
|
|||
flag | purpose
|
||||
---- | -------
|
||||
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled for Windows; if not set, the code assumes Linux/macOS. Set automatically in `common.targets`.
|
||||
`SMAPI_FOR_XNA` | Whether SMAPI is being compiled for XNA Framework; if not set, the code assumes MonoGame. Set automatically in `common.targets` with the same value as `SMAPI_FOR_WINDOWS`.
|
||||
|
||||
## For SMAPI developers
|
||||
### Compiling from source
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Win32;
|
||||
using StardewModdingAPI.Toolkit;
|
||||
using StardewModdingAPI.Toolkit.Framework.GameScanning;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
@ -13,9 +11,6 @@ namespace StardewModdingAPI.Installer.Framework
|
|||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The <see cref="Environment.OSVersion"/> value that represents Windows 7.</summary>
|
||||
private readonly Version Windows7Version = new Version(6, 1);
|
||||
|
||||
/// <summary>The underlying toolkit game scanner.</summary>
|
||||
private readonly GameScanner GameScanner = new GameScanner();
|
||||
|
||||
|
@ -29,9 +24,6 @@ namespace StardewModdingAPI.Installer.Framework
|
|||
/// <summary>The human-readable OS name and version.</summary>
|
||||
public string PlatformName { get; }
|
||||
|
||||
/// <summary>The name of the Stardew Valley executable.</summary>
|
||||
public string ExecutableName { get; }
|
||||
|
||||
/// <summary>Whether the installer is running on Windows.</summary>
|
||||
public bool IsWindows => this.Platform == Platform.Windows;
|
||||
|
||||
|
@ -47,7 +39,6 @@ namespace StardewModdingAPI.Installer.Framework
|
|||
{
|
||||
this.Platform = EnvironmentUtility.DetectPlatform();
|
||||
this.PlatformName = EnvironmentUtility.GetFriendlyPlatformName(this.Platform);
|
||||
this.ExecutableName = EnvironmentUtility.GetExecutableName(this.Platform);
|
||||
}
|
||||
|
||||
/// <summary>Get the installer's version number.</summary>
|
||||
|
@ -57,42 +48,6 @@ namespace StardewModdingAPI.Installer.Framework
|
|||
return new SemanticVersion(raw);
|
||||
}
|
||||
|
||||
/// <summary>Get whether the current system has .NET Framework 4.5 or later installed. This only applies on Windows.</summary>
|
||||
/// <exception cref="NotSupportedException">The current platform is not Windows.</exception>
|
||||
public bool HasNetFramework45()
|
||||
{
|
||||
switch (this.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>
|
||||
/// <exception cref="NotSupportedException">The current platform is not Windows.</exception>
|
||||
public bool HasXna()
|
||||
{
|
||||
switch (this.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.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether the current OS supports newer versions of .NET Framework.</summary>
|
||||
public bool CanInstallLatestNetFramework()
|
||||
{
|
||||
return Environment.OSVersion.Version >= this.Windows7Version; // Windows 7+
|
||||
}
|
||||
|
||||
/// <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)
|
||||
|
|
|
@ -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 executable file.</summary>
|
||||
public string ExecutablePath { get; private set; }
|
||||
/// <summary>The full path to the installed game DLL.</summary>
|
||||
public string GameDllPath { get; }
|
||||
|
||||
/// <summary>The full path to the vanilla game launcher on Linux/macOS.</summary>
|
||||
public string UnixLauncherPath { get; }
|
||||
/// <summary>The full path to the installed SMAPI executable file.</summary>
|
||||
public string UnixSmapiExecutablePath { get; }
|
||||
|
||||
/// <summary>The full path to the installed SMAPI launcher on Linux/macOS before it's renamed.</summary>
|
||||
public string UnixSmapiLauncherPath { 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 vanilla game launcher on Linux/macOS after SMAPI is installed.</summary>
|
||||
public string UnixBackupLauncherPath { 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 backed up game launch script on Linux/macOS after SMAPI is installed.</summary>
|
||||
public string BackupLaunchScriptPath { get; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -63,28 +67,24 @@ 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");
|
||||
}
|
||||
|
||||
/// <summary>Override the filename for the <see cref="ExecutablePath"/>.</summary>
|
||||
/// <param name="filename">the file name.</param>
|
||||
public void SetExecutableFileName(string filename)
|
||||
{
|
||||
this.ExecutablePath = Path.Combine(this.GamePath, filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,18 +39,19 @@ namespace StardewModdingApi.Installer
|
|||
string GetInstallPath(string path) => Path.Combine(installDir.FullName, path);
|
||||
|
||||
// current files
|
||||
yield return GetInstallPath("libgdiplus.dylib"); // Linux/macOS only
|
||||
yield return GetInstallPath("StardewModdingAPI"); // Linux/macOS only
|
||||
yield return GetInstallPath("StardewModdingAPI.dll");
|
||||
yield return GetInstallPath("StardewModdingAPI.exe");
|
||||
yield return GetInstallPath("StardewModdingAPI.exe.config");
|
||||
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("StardewModdingAPI-x64.exe"); // not normally added to game folder, but may be mistakenly added by a manual install
|
||||
yield return GetInstallPath("smapi-internal");
|
||||
yield return GetInstallPath("steam_appid.txt");
|
||||
|
||||
// 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
|
||||
|
@ -70,9 +71,7 @@ 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)
|
||||
{
|
||||
|
@ -149,30 +148,6 @@ namespace StardewModdingApi.Installer
|
|||
}
|
||||
#endif
|
||||
|
||||
/****
|
||||
** Check Windows dependencies
|
||||
****/
|
||||
if (context.IsWindows)
|
||||
{
|
||||
// .NET Framework 4.5+
|
||||
if (!context.HasNetFramework45())
|
||||
{
|
||||
this.PrintError(context.CanInstallLatestNetFramework()
|
||||
? "Please install the latest version of .NET Framework before installing SMAPI."
|
||||
: "Please install .NET Framework 4.5 before installing SMAPI."
|
||||
);
|
||||
this.PrintError("See the download page at https://www.microsoft.com/net/download/framework for details.");
|
||||
Console.ReadLine();
|
||||
return;
|
||||
}
|
||||
if (!context.HasXna())
|
||||
{
|
||||
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
|
||||
****/
|
||||
|
@ -270,51 +245,20 @@ namespace StardewModdingApi.Installer
|
|||
|
||||
// get folders
|
||||
DirectoryInfo bundleDir = new DirectoryInfo(this.BundlePath);
|
||||
paths = new InstallerPaths(bundleDir, installDir, context.ExecutableName);
|
||||
paths = new InstallerPaths(bundleDir, installDir);
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Step 4: validate assumptions
|
||||
*********/
|
||||
// not 64-bit on Windows
|
||||
if (context.Platform == Platform.Windows)
|
||||
{
|
||||
FileInfo linuxExecutable = new FileInfo(Path.Combine(paths.GamePath, "StardewValley.exe"));
|
||||
if (linuxExecutable.Exists && this.Is64Bit(linuxExecutable.FullName))
|
||||
{
|
||||
this.PrintError("Oops! The detected game install path seems to be unofficial 64-bit mode, which is no longer supported. You can update to Stardew Valley 1.5.5 or later instead. See https://stardewvalleywiki.com/Modding:Migrate_to_64-bit_on_Windows for more info.");
|
||||
Console.ReadLine();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// executable exists
|
||||
if (!File.Exists(paths.ExecutablePath))
|
||||
if (!File.Exists(paths.GameDllPath))
|
||||
{
|
||||
this.PrintError("The detected game install path doesn't contain a Stardew Valley executable.");
|
||||
Console.ReadLine();
|
||||
return;
|
||||
}
|
||||
|
||||
// not Stardew Valley 1.5.5+
|
||||
if (File.Exists(Path.Combine(paths.GamePath, "Stardew Valley.dll")))
|
||||
{
|
||||
this.PrintError("Oops! The detected game install path seems to be Stardew Valley 1.5.5 or later, but this version of SMAPI is only compatible up to Stardew Valley 1.5.4. Please check for a newer version of SMAPI: https://smapi.io.");
|
||||
Console.ReadLine();
|
||||
return;
|
||||
}
|
||||
|
||||
// game folder doesn't contain paths beyond the max limit
|
||||
{
|
||||
string[] tooLongPaths = PathUtilities.GetTooLongPaths(Path.Combine(paths.GamePath, "Mods")).ToArray();
|
||||
if (tooLongPaths.Any())
|
||||
{
|
||||
this.PrintError($"SMAPI can't install to the detected game folder, because some of its files exceed the maximum {context.Platform} path length.\nIf you need help fixing this error, see https://smapi.io/help\n\nAffected paths:\n {string.Join("\n ", tooLongPaths)}");
|
||||
Console.ReadLine();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Console.Clear();
|
||||
|
||||
|
||||
|
@ -387,11 +331,11 @@ namespace StardewModdingApi.Installer
|
|||
** Always uninstall old files
|
||||
****/
|
||||
// restore game launcher
|
||||
if (context.IsUnix && 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
|
||||
|
@ -439,30 +383,41 @@ namespace StardewModdingApi.Installer
|
|||
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.)
|
||||
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();
|
||||
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)
|
||||
{
|
||||
|
@ -527,7 +482,7 @@ namespace StardewModdingApi.Installer
|
|||
|
||||
|
||||
/*********
|
||||
** Step 6: final instructions
|
||||
** Step 7: final instructions
|
||||
*********/
|
||||
if (context.IsWindows)
|
||||
{
|
||||
|
@ -556,13 +511,6 @@ namespace StardewModdingApi.Installer
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Get whether an executable is 64-bit.</summary>
|
||||
/// <param name="executablePath">The absolute path to the executable file.</param>
|
||||
private bool Is64Bit(string executablePath)
|
||||
{
|
||||
return LowLevelEnvironmentUtility.Is64BitAssembly(executablePath);
|
||||
}
|
||||
|
||||
/// <summary>Get the display text for a color scheme.</summary>
|
||||
/// <param name="scheme">The color scheme.</param>
|
||||
private string GetDisplayText(MonitorColorScheme scheme)
|
||||
|
@ -725,7 +673,7 @@ namespace StardewModdingApi.Installer
|
|||
{
|
||||
// get path from user
|
||||
Console.WriteLine();
|
||||
this.PrintInfo($"Type the file path to the game directory (the one containing '{context.ExecutableName}'), then press enter.");
|
||||
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))
|
||||
{
|
||||
|
|
|
@ -31,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 FileInfo(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})");
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<RootNamespace>StardewModdingAPI.Installer</RootNamespace>
|
||||
<Description>The SMAPI installer for players.</Description>
|
||||
<TargetFramework>net452</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -24,7 +24,7 @@ Manual install
|
|||
THIS IS NOT RECOMMENDED FOR MOST PLAYERS. See 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
|
||||
1. Unzip "internal/windows/install.dat" (on Windows) or "internal/unix/install.dat" (on
|
||||
Linux/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
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net5.0",
|
||||
"includedFrameworks": [
|
||||
{
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "5.0.7"
|
||||
}
|
||||
],
|
||||
"configProperties": {
|
||||
"System.Runtime.TieredCompilation": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net5.0",
|
||||
"framework": {
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "5.0.0"
|
||||
},
|
||||
"configProperties": {
|
||||
"System.Runtime.TieredCompilation": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,14 @@
|
|||
#!/bin/bash
|
||||
# Run the SMAPI installer through Mono on Linux or macOS.
|
||||
|
||||
# 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"
|
||||
# make sure .NET 5 is installed
|
||||
if ! command -v dotnet >/dev/null 2>&1; then
|
||||
echo "Oops! You must have .NET 5 installed to use SMAPI: https://dotnet.microsoft.com/download";
|
||||
read
|
||||
exit 1
|
||||
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
|
||||
# run installer
|
||||
dotnet internal/unix/SMAPI.Installer.dll
|
||||
|
|
|
@ -1,51 +1,19 @@
|
|||
#!/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)
|
||||
|
||||
# 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/
|
||||
|
||||
# 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
|
||||
|
||||
# create bin file
|
||||
# Note: don't overwrite if it's identical, to avoid resetting permission flags
|
||||
if [ ! -x StardewModdingAPI.bin.osx ] || ! cmp StardewValley.bin.osx StardewModdingAPI.bin.osx >/dev/null 2>&1; then
|
||||
cp -p StardewValley.bin.osx StardewModdingAPI.bin.osx
|
||||
fi
|
||||
|
||||
# Make sure we're running in Terminal (so the user can see errors/warnings/update alerts).
|
||||
# Previously we would just use `open -a Terminal` to launch the .bin.osx file, but that
|
||||
# doesn't let us set environment variables.
|
||||
##########
|
||||
## 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 # https://stackoverflow.com/q/911168/262123
|
||||
# sanity check to make sure we don't have an infinite loop of opening windows
|
||||
SKIP_TERMINAL=false
|
||||
|
@ -68,21 +36,38 @@ if [ "$UNAME" == "Darwin" ]; then
|
|||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# launch SMAPI
|
||||
LC_ALL="C" ./StardewModdingAPI.bin.osx "$@"
|
||||
|
||||
##########
|
||||
## Validate assumptions
|
||||
##########
|
||||
# script must be run from the game folder
|
||||
if [ ! -f "Stardew Valley.dll" ]; then
|
||||
echo "Oops! SMAPI must be placed in the Stardew Valley game folder.\nSee instructions: https://stardewvalleywiki.com/Modding:Player_Guide";
|
||||
read
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# .NET 5 must be installed
|
||||
if ! command -v dotnet >/dev/null 2>&1; then
|
||||
echo "Oops! You must have .NET 5 installed to use SMAPI: https://dotnet.microsoft.com/download";
|
||||
read
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
##########
|
||||
## Launch SMAPI
|
||||
##########
|
||||
# macOS
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
dotnet StardewModdingAPI.dll "$@"
|
||||
|
||||
# Linux
|
||||
else
|
||||
# choose binary file to launch
|
||||
LAUNCH_FILE=""
|
||||
if [ "$ARCH" == "x86_64" ]; then
|
||||
ln -sf mcs.bin.x86_64 mcs
|
||||
cp StardewValley.bin.x86_64 StardewModdingAPI.bin.x86_64
|
||||
LAUNCH_FILE="./StardewModdingAPI.bin.x86_64"
|
||||
else
|
||||
ln -sf mcs.bin.x86 mcs
|
||||
cp StardewValley.bin.x86 StardewModdingAPI.bin.x86
|
||||
LAUNCH_FILE="./StardewModdingAPI.bin.x86"
|
||||
fi
|
||||
LAUNCH_FILE="./StardewModdingAPI"
|
||||
export LAUNCH_FILE
|
||||
|
||||
# select terminal (prefer xterm for best compatibility, then known supported terminals)
|
||||
|
@ -105,44 +90,44 @@ else
|
|||
terminal|termite)
|
||||
# consumes only one argument after -e
|
||||
# options containing space characters are unsupported
|
||||
exec $TERMINAL_NAME -e "env TERM=xterm LC_ALL=\"C\" $LAUNCH_FILE $@"
|
||||
exec $TERMINAL_NAME -e "env TERM=xterm $LAUNCH_FILE $@"
|
||||
;;
|
||||
|
||||
xterm|konsole|alacritty)
|
||||
# consumes all arguments after -e
|
||||
exec $TERMINAL_NAME -e env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@"
|
||||
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 LC_ALL="C" $LAUNCH_FILE "$@"
|
||||
exec $TERMINAL_NAME -x env TERM=xterm $LAUNCH_FILE "$@"
|
||||
;;
|
||||
|
||||
gnome-terminal)
|
||||
# consumes all arguments after --
|
||||
exec $TERMINAL_NAME -- env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@"
|
||||
exec $TERMINAL_NAME -- env TERM=xterm $LAUNCH_FILE "$@"
|
||||
;;
|
||||
|
||||
kitty)
|
||||
# consumes all trailing arguments
|
||||
exec $TERMINAL_NAME env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@"
|
||||
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 LC_ALL="C" $LAUNCH_FILE "$@"
|
||||
env TERM=xterm $LAUNCH_FILE "$@"
|
||||
if [ $? -eq 127 ]; then
|
||||
exec LC_ALL="C" $LAUNCH_FILE --no-terminal "$@"
|
||||
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 LC_ALL="C" $LAUNCH_FILE "$@"
|
||||
env TERM=xterm $LAUNCH_FILE "$@"
|
||||
if [ $? -eq 127 ]; then
|
||||
exec LC_ALL="C" $LAUNCH_FILE --no-terminal "$@"
|
||||
exec $LAUNCH_FILE --no-terminal "$@"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
@ -1,8 +1,49 @@
|
|||
@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
|
||||
|
||||
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 .NET 5 is installed
|
||||
WHERE dotnet /q
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo Oops! You must have .NET 5 ^(desktop x64^) installed to use SMAPI: https://dotnet.microsoft.com/download/dotnet/5.0/runtime
|
||||
echo.
|
||||
pause
|
||||
exit
|
||||
)
|
||||
dotnet --info | findstr /C:"Microsoft.WindowsDesktop.App 5." 1>nul
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo Oops! You must have .NET 5 ^(desktop x64^) installed to use SMAPI: https://dotnet.microsoft.com/download/dotnet/5.0/runtime
|
||||
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
|
||||
)
|
||||
|
||||
REM start installer
|
||||
dotnet internal\windows\SMAPI.Installer.dll
|
||||
|
||||
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
|
||||
)
|
||||
|
|
|
@ -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="17.0.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -20,5 +16,4 @@
|
|||
</ItemGroup>
|
||||
|
||||
<Import Project="..\..\build\common.targets" />
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
</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>
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Text.RegularExpressions;
|
|||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
using StardewModdingAPI.ModBuildConfig.Framework;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
||||
namespace StardewModdingAPI.ModBuildConfig
|
||||
{
|
||||
|
@ -17,6 +18,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 +50,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
|
||||
|
@ -69,11 +80,15 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
|
||||
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 ModFileManager(this.ProjectDir, this.TargetDir, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, this.ModDllName, validateRequiredModFiles: this.EnableModDeploy || this.EnableModZip);
|
||||
|
||||
// deploy mod files
|
||||
if (this.EnableModDeploy)
|
||||
|
@ -134,6 +149,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 +194,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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -21,6 +21,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 +67,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 +89,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;
|
||||
}
|
||||
|
||||
|
@ -149,36 +191,72 @@ 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));
|
||||
// check for bundled assembly types
|
||||
// When bundleAssemblyTypes is set, *all* dependencies are copied into the build output but only those which match the given assembly types should be bundled.
|
||||
if (bundleAssemblyTypes != ExtraAssemblyTypes.None)
|
||||
{
|
||||
var type = this.GetExtraAssemblyType(file, modDllName);
|
||||
if (type != ExtraAssemblyTypes.None && !bundleAssemblyTypes.HasFlag(type))
|
||||
return true;
|
||||
}
|
||||
|
||||
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,28 +2,28 @@
|
|||
<PropertyGroup>
|
||||
<!--build-->
|
||||
<RootNamespace>StardewModdingAPI.ModBuildConfig</RootNamespace>
|
||||
<TargetFramework>net452</TargetFramework>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
|
||||
<!--NuGet package-->
|
||||
<PackageId>Pathoschild.Stardew.ModBuildConfig</PackageId>
|
||||
<Title>Build package for SMAPI mods</Title>
|
||||
<Version>3.3.0</Version>
|
||||
<Version>4.0.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>
|
||||
<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="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
<DebugType>pdbonly</DebugType>
|
||||
<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>
|
||||
|
@ -26,10 +26,10 @@
|
|||
<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>
|
||||
|
||||
<GameFramework Condition="'$(GameFramework)' == '' AND '$(OS)' == 'Windows_NT'">Xna</GameFramework>
|
||||
<GameFramework Condition="'$(GameFramework)' == ''">MonoGame</GameFramework>
|
||||
<!-- coppy referenced DLLs into build output -->
|
||||
<CopyLocalLockFileAssemblies Condition="$(BundleExtraAssemblies.Contains('ThirdParty')) OR $(BundleExtraAssemblies.Contains('Game')) OR $(BundleExtraAssemblies.Contains('System')) OR $(BundleExtraAssemblies.Contains('All'))">true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(OS)' == 'Windows_NT' AND '$(EnableGameDebugging)' == 'true'">
|
||||
|
@ -43,37 +43,20 @@
|
|||
<!--*********************************************
|
||||
** 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="$(BundleExtraAssemblies.Contains('Game'))" />
|
||||
<Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
|
||||
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
|
||||
|
||||
<!-- Windows only -->
|
||||
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
|
||||
<Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
</ItemGroup>
|
||||
<!-- SMAPI -->
|
||||
<Reference Include="StardewModdingAPI" HintPath="$(GamePath)\StardewModdingAPI.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
|
||||
<Reference Include="SMAPI.Toolkit.CoreInterfaces" HintPath="$(GamePath)\smapi-internal\SMAPI.Toolkit.CoreInterfaces.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
|
||||
|
||||
<!-- Game framework -->
|
||||
<Choose>
|
||||
<When Condition="'$(GameFramework)' == 'Xna'">
|
||||
<ItemGroup>
|
||||
<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)" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<Otherwise>
|
||||
<ItemGroup>
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
</ItemGroup>
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
<!-- Harmony -->
|
||||
<Reference Include="0Harmony" Condition="'$(EnableHarmony)' == 'true'" HintPath="$(GamePath)\smapi-internal\0Harmony.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<!--*********************************************
|
||||
|
@ -85,8 +68,8 @@
|
|||
|
||||
<!-- 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)\$(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." ContinueOnError="false" />
|
||||
<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." 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" />
|
||||
|
@ -98,6 +81,7 @@
|
|||
**********************************************-->
|
||||
<Target Name="AfterBuild">
|
||||
<DeployModTask
|
||||
ModDllName="$(TargetName)"
|
||||
ModFolderName="$(ModFolderName)"
|
||||
ModZipPath="$(ModZipPath)"
|
||||
|
||||
|
@ -108,6 +92,9 @@
|
|||
TargetDir="$(TargetDir)"
|
||||
GameModsDir="$(GameModsPath)"
|
||||
IgnoreModFilePatterns="$(IgnoreModFilePatterns)"
|
||||
IgnoreModFilePaths="$(IgnoreModFilePaths)"
|
||||
|
||||
BundleExtraAssemblies="$(BundleExtraAssemblies)"
|
||||
/>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
@ -1,27 +1,23 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
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>
|
||||
internal class SetFarmTypeCommand : ConsoleCommand
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The vanilla farm type IDs.</summary>
|
||||
private static readonly ISet<int> VanillaFarmTypes = new HashSet<int>(
|
||||
Enumerable.Range(0, Farm.layout_max + 1)
|
||||
);
|
||||
|
||||
|
||||
/*********
|
||||
** 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: one of {string.Join(", ", SetFarmTypeCommand.VanillaFarmTypes.Select(id => $"{id} ({SetFarmTypeCommand.GetFarmLabel(id)})"))}.") { }
|
||||
: 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>
|
||||
|
@ -29,47 +25,141 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
/// <param name="args">The command arguments.</param>
|
||||
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
|
||||
{
|
||||
// validation checks
|
||||
// validate
|
||||
if (!Context.IsWorldReady)
|
||||
{
|
||||
monitor.Log("You must load a save to use this command.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// parse argument
|
||||
if (!args.TryGetInt(0, "farm type", out int farmType, min: 0, max: Farm.layout_max))
|
||||
// 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
|
||||
if (Game1.whichFarm == farmType)
|
||||
{
|
||||
monitor.Log($"Your current farm is already set to {farmType} ({SetFarmTypeCommand.GetFarmLabel(farmType)}).", LogLevel.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
this.SetFarmType(farmType);
|
||||
monitor.Log($"Your current farm has been converted to {farmType} ({SetFarmTypeCommand.GetFarmLabel(farmType)}).", LogLevel.Warn);
|
||||
monitor.Log("Saving and reloading is recommended to make sure everything is updated for the change.", LogLevel.Warn);
|
||||
// handle argument
|
||||
if (farmType == "list")
|
||||
this.HandleList(monitor);
|
||||
else if (isVanillaId)
|
||||
this.HandleVanillaFarmType(vanillaId, monitor);
|
||||
else
|
||||
this.HandleCustomFarmType(farmType, monitor);
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Change the farm type to the given value.</summary>
|
||||
/// <param name="type">The farm type ID.</param>
|
||||
private void SetFarmType(int type)
|
||||
/****
|
||||
** 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)
|
||||
{
|
||||
Game1.whichFarm = type;
|
||||
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();
|
||||
|
||||
// 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 static string GetFarmLabel(int type)
|
||||
private string GetVanillaName(int type)
|
||||
{
|
||||
string translationKey = type switch
|
||||
{
|
||||
|
@ -87,5 +177,45 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
|||
? 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<AssemblyName>ConsoleCommands</AssemblyName>
|
||||
<RootNamespace>StardewModdingAPI.Mods.ConsoleCommands</RootNamespace>
|
||||
<TargetFramework>net452</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -13,32 +13,12 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="False" />
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
|
||||
<Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="False" />
|
||||
<Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="False" />
|
||||
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Windows only -->
|
||||
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
|
||||
<Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Game framework -->
|
||||
<Choose>
|
||||
<When Condition="$(DefineConstants.Contains(SMAPI_FOR_XNA))">
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<Otherwise>
|
||||
<ItemGroup>
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="manifest.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -19,9 +19,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: Constants.GameFramework == GameFramework.Xna
|
||||
? this.RequireMethod<SpriteBatch>("InternalDraw")
|
||||
: this.RequireMethod<SpriteBatch>("CheckValid", new[] { typeof(Texture2D) }),
|
||||
original: this.RequireMethod<SpriteBatch>("CheckValid", new[] { typeof(Texture2D) }),
|
||||
postfix: this.GetHarmonyMethod(nameof(SpriteBatchPatcher.After_CheckValid))
|
||||
);
|
||||
}
|
||||
|
@ -30,13 +28,8 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
#if SMAPI_FOR_XNA
|
||||
/// <summary>The method to call after <see cref="SpriteBatch.InternalDraw"/>.</summary>
|
||||
/// <param name="texture">The texture to validate.</param>
|
||||
#else
|
||||
/// <summary>The method to call after <see cref="SpriteBatch.CheckValid"/>.</summary>
|
||||
/// <param name="texture">The texture to validate.</param>
|
||||
#endif
|
||||
private static void After_CheckValid(Texture2D texture)
|
||||
{
|
||||
if (texture?.IsDisposed == true)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<AssemblyName>ErrorHandler</AssemblyName>
|
||||
<RootNamespace>StardewModdingAPI.Mods.ErrorHandler</RootNamespace>
|
||||
<TargetFramework>net452</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -14,33 +14,12 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="False" />
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
|
||||
<Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="False" />
|
||||
<Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="False" />
|
||||
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Windows only -->
|
||||
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
|
||||
<Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Game framework -->
|
||||
<Choose>
|
||||
<When Condition="$(DefineConstants.Contains(SMAPI_FOR_XNA))">
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<Otherwise>
|
||||
<ItemGroup>
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="i18n\*.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="manifest.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<AssemblyName>SaveBackup</AssemblyName>
|
||||
<RootNamespace>StardewModdingAPI.Mods.SaveBackup</RootNamespace>
|
||||
<TargetFramework>net452</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="False" />
|
||||
<Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<AssemblyName>SMAPI.Tests</AssemblyName>
|
||||
<RootNamespace>SMAPI.Tests</RootNamespace>
|
||||
<TargetFramework>net452</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
@ -16,16 +16,14 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="$(GameExecutableName)">
|
||||
<HintPath>$(GamePath)\$(GameExecutableName).exe</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="True" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -182,18 +182,14 @@ namespace SMAPI.Tests.Utilities
|
|||
[TestCaseSource(nameof(PathUtilitiesTests.SamplePaths))]
|
||||
public void NormalizeAssetName(SamplePath path)
|
||||
{
|
||||
if (Path.IsPathRooted(path.OriginalPath) || path.OriginalPath.StartsWith("/") || path.OriginalPath.StartsWith("\\"))
|
||||
if (Path.IsPathRooted(path.OriginalPath) || path.OriginalPath.StartsWith('/') || path.OriginalPath.StartsWith('\\'))
|
||||
Assert.Ignore("Absolute paths can't be used as asset names.");
|
||||
|
||||
// act
|
||||
string normalized = PathUtilities.NormalizeAssetName(path.OriginalPath);
|
||||
|
||||
// assert
|
||||
#if SMAPI_FOR_WINDOWS
|
||||
Assert.AreEqual(path.NormalizedOnWindows, normalized);
|
||||
#else
|
||||
Assert.AreEqual(path.NormalizedOnUnix, normalized);
|
||||
#endif
|
||||
Assert.AreEqual(path.NormalizedOnUnix, normalized); // MonoGame uses the Linux format
|
||||
}
|
||||
|
||||
/****
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<RootNamespace>StardewModdingAPI</RootNamespace>
|
||||
<Description>Provides toolkit interfaces which are available to SMAPI mods.</Description>
|
||||
<TargetFrameworks>net452;netstandard2.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0; netstandard2.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
namespace StardewModdingAPI.Toolkit.Framework
|
||||
{
|
||||
/// <summary>Contains the SMAPI installer's constants and assumptions.</summary>
|
||||
internal static class Constants
|
||||
{
|
||||
/// <summary>The name of the game's main DLL, used to detect game folders.</summary>
|
||||
public const string GameDllName = "Stardew Valley.dll";
|
||||
}
|
||||
}
|
|
@ -56,10 +56,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
|
|||
{
|
||||
return
|
||||
dir.Exists
|
||||
&& (
|
||||
dir.EnumerateFiles("StardewValley.exe").Any()
|
||||
|| dir.EnumerateFiles("Stardew Valley.exe").Any()
|
||||
);
|
||||
&& dir.EnumerateFiles("Stardew Valley.dll").Any();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -80,15 +80,6 @@ namespace StardewModdingAPI.Toolkit.Framework
|
|||
return name;
|
||||
}
|
||||
|
||||
/// <summary>Get the name of the Stardew Valley executable.</summary>
|
||||
/// <param name="platform">The current platform.</param>
|
||||
public static string GetExecutableName(string platform)
|
||||
{
|
||||
return platform == nameof(Platform.Windows)
|
||||
? "Stardew Valley.exe"
|
||||
: "StardewValley.exe";
|
||||
}
|
||||
|
||||
/// <summary>Get whether an executable is 64-bit.</summary>
|
||||
/// <param name="path">The absolute path to the assembly file.</param>
|
||||
public static bool Is64BitAssembly(string path)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<RootNamespace>StardewModdingAPI.Toolkit</RootNamespace>
|
||||
<Description>A library which encapsulates mod-handling logic for mod managers and tools. Not intended for use by mods.</Description>
|
||||
<TargetFrameworks>net452;netstandard2.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0; netstandard2.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -12,8 +12,8 @@
|
|||
<PackageReference Include="HtmlAgilityPack" Version="1.11.33" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.0" />
|
||||
<PackageReference Include="System.Management" Version="4.5.0" Condition="'$(OS)' == 'Windows_NT'" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" Condition="'$(OS)' == 'Windows_NT' AND '$(TargetFramework)' == 'netstandard2.0'" />
|
||||
<PackageReference Include="System.Management" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -40,13 +40,6 @@ namespace StardewModdingAPI.Toolkit.Utilities
|
|||
return LowLevelEnvironmentUtility.GetFriendlyPlatformName(platform.ToString());
|
||||
}
|
||||
|
||||
/// <summary>Get the name of the Stardew Valley executable.</summary>
|
||||
/// <param name="platform">The current platform.</param>
|
||||
public static string GetExecutableName(Platform platform)
|
||||
{
|
||||
return LowLevelEnvironmentUtility.GetExecutableName(platform.ToString());
|
||||
}
|
||||
|
||||
/// <summary>Get whether an executable is 64-bit.</summary>
|
||||
/// <param name="path">The absolute path to the assembly file.</param>
|
||||
public static bool Is64BitAssembly(string path)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Utilities
|
||||
|
@ -42,5 +44,16 @@ namespace StardewModdingAPI.Toolkit.Utilities
|
|||
if (entry.Exists)
|
||||
throw new IOException($"Timed out trying to delete {entry.FullName}");
|
||||
}
|
||||
|
||||
/// <summary>Get the MD5 hash for a file.</summary>
|
||||
/// <param name="absolutePath">The absolute file path.</param>
|
||||
public static string GetFileHash(string absolutePath)
|
||||
{
|
||||
using FileStream stream = File.OpenRead(absolutePath);
|
||||
using MD5 md5 = MD5.Create();
|
||||
|
||||
byte[] hash = md5.ComputeHash(stream);
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -27,7 +26,7 @@ namespace StardewModdingAPI.Toolkit.Utilities
|
|||
public static readonly char PreferredPathSeparator = Path.DirectorySeparatorChar;
|
||||
|
||||
/// <summary>The preferred directory separator character in an asset key.</summary>
|
||||
public static readonly char PreferredAssetSeparator = PathUtilities.PreferredPathSeparator;
|
||||
public static readonly char PreferredAssetSeparator = '/';
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -88,14 +87,18 @@ namespace StardewModdingAPI.Toolkit.Utilities
|
|||
/// <summary>Get a directory or file path relative to a given source path. If no relative path is possible (e.g. the paths are on different drives), an absolute path is returned.</summary>
|
||||
/// <param name="sourceDir">The source folder path.</param>
|
||||
/// <param name="targetPath">The target folder or file path.</param>
|
||||
/// <remarks>
|
||||
///
|
||||
/// NOTE: this is a heuristic implementation that works in the cases SMAPI needs it for, but it doesn't handle all edge cases (e.g. case-sensitivity on Linux, or traversing between UNC paths on Windows). This should be replaced with the more comprehensive <c>Path.GetRelativePath</c> if the game ever migrates to .NET Core.
|
||||
///
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public static string GetRelativePath(string sourceDir, string targetPath)
|
||||
{
|
||||
#if NET5_0
|
||||
return Path.GetRelativePath(sourceDir, targetPath);
|
||||
#else
|
||||
// NOTE:
|
||||
// this is a heuristic implementation that works in the cases SMAPI needs it for, but it
|
||||
// doesn't handle all edge cases (e.g. case-sensitivity on Linux, or traversing between
|
||||
// UNC paths on Windows). SMAPI and mods will use the more robust .NET 5 version anyway
|
||||
// though, this is only for compatibility with the mod build package.
|
||||
|
||||
// convert to URIs
|
||||
Uri from = new Uri(sourceDir.TrimEnd(PathUtilities.PossiblePathSeparators) + "/");
|
||||
Uri to = new Uri(targetPath.TrimEnd(PathUtilities.PossiblePathSeparators) + "/");
|
||||
|
@ -123,6 +126,7 @@ namespace StardewModdingAPI.Toolkit.Utilities
|
|||
}
|
||||
|
||||
return relative;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain <c>../</c>).</summary>
|
||||
|
@ -145,32 +149,5 @@ namespace StardewModdingAPI.Toolkit.Utilities
|
|||
{
|
||||
return !Regex.IsMatch(str, "[^a-z0-9_.-]", RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Get the paths which exceed the OS length limit.</summary>
|
||||
/// <param name="rootPath">The root path to search.</param>
|
||||
internal static IEnumerable<string> GetTooLongPaths(string rootPath)
|
||||
{
|
||||
if (!Directory.Exists(rootPath))
|
||||
return new string[0];
|
||||
|
||||
return Directory
|
||||
.EnumerateFileSystemEntries(rootPath, "*.*", SearchOption.AllDirectories)
|
||||
.Where(PathUtilities.IsPathTooLong);
|
||||
}
|
||||
|
||||
/// <summary>Get whether a file or directory path exceeds the OS path length limit.</summary>
|
||||
/// <param name="path">The path to test.</param>
|
||||
internal static bool IsPathTooLong(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = Path.GetFullPath(path);
|
||||
return false;
|
||||
}
|
||||
catch (PathTooLongException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,6 +167,60 @@
|
|||
"~ | StatusReasonPhrase": "split-screen mode was added in Stardew Valley 1.5"
|
||||
},
|
||||
|
||||
/*********
|
||||
** Broke in SDV 1.5.5
|
||||
*********/
|
||||
"Animated Portrait Framework": {
|
||||
"ID": "akai.AnimatedPortrait",
|
||||
"~1.0.0 | Status": "AssumeBroken",
|
||||
"~1.0.0 | StatusReasonDetails": "requires the 'System.Windows.Forms' API, which isn't available in .NET 5"
|
||||
},
|
||||
"Audio Devices": {
|
||||
"ID": "maxvollmer.audiodevices",
|
||||
"~3.0.1 | Status": "AssumeBroken",
|
||||
"~3.0.1 | StatusReasonDetails": "fails to load due to an outdated implementation of the game's 'IAudioEngine' interface"
|
||||
},
|
||||
"Battery Warning": {
|
||||
"ID": "Husky110.BatteryWarningMod",
|
||||
"~1.0.4 | Status": "AssumeBroken",
|
||||
"~1.0.4 | StatusReasonDetails": "requires the 'System.Management' API, which is a different DLL in .NET 5"
|
||||
},
|
||||
"Junimo Studio": {
|
||||
"ID": "Becks723.JunimoStudio",
|
||||
"~2.0.1 | Status": "AssumeBroken",
|
||||
"~2.0.1 | StatusReasonDetails": "requires 'Microsoft.Xna.Framework.Audio.AudioCategory' which doesn't exist in MonoGame"
|
||||
},
|
||||
"Skip Intro": {
|
||||
"ID": "Pathoschild.SkipIntro",
|
||||
"~1.9.1 | Status": "AssumeBroken",
|
||||
"~1.9.1 | StatusReasonDetails": "causes freeze during game launch"
|
||||
},
|
||||
"Stardew Hack": {
|
||||
"ID": "bcmpinc.StardewHack",
|
||||
"~5.1.0 | Status": "AssumeBroken",
|
||||
"~5.1.0 | StatusReasonDetails": "runtime error when initializing due to an API change between .NET Framework and .NET 5"
|
||||
},
|
||||
"Stardew Valley Expanded": {
|
||||
"ID": "FlashShifter.SVECode",
|
||||
"~1.13.11 | Status": "AssumeBroken",
|
||||
"~1.13.11 | StatusReasonDetails": "fails to load due to an outdated implementation of the game's 'ICue' interface"
|
||||
},
|
||||
"Stardew Web": {
|
||||
"ID": "prism99.stardewweb",
|
||||
"~0.7.2 | Status": "AssumeBroken",
|
||||
"~0.7.2 | StatusReasonDetails": "requires the 'System.Drawing' API, which isn't available in .NET 5"
|
||||
},
|
||||
"Sundrop City": {
|
||||
"ID": "SundropTeam.SundropCity",
|
||||
"~0.4.1 | Status": "AssumeBroken",
|
||||
"~0.4.1 | StatusReasonDetails": "causes freeze during game launch"
|
||||
},
|
||||
"Video Player": {
|
||||
"ID": "aedenthorn.VideoPlayer",
|
||||
"~0.2.5 | Status": "AssumeBroken",
|
||||
"~0.2.5 | StatusReasonDetails": "requires an XNA Framework API that's not available in MonoGame and causes a crash to desktop"
|
||||
},
|
||||
|
||||
/*********
|
||||
** Broke in SMAPI 3.12.0
|
||||
*********/
|
||||
|
@ -205,11 +259,6 @@
|
|||
"~1.9.3 | Status": "AssumeBroken",
|
||||
"~1.9.3 | StatusReasonDetails": "fails to load with 'ReflectionTypeLoadException' error"
|
||||
},
|
||||
"Stardew Hack": {
|
||||
"ID": "bcmpinc.StardewHack",
|
||||
"~5.0.0 | Status": "AssumeBroken",
|
||||
"~5.0.0 | StatusReasonDetails": "causes Harmony patching errors for other mods"
|
||||
},
|
||||
"Tilled Soil Decay": {
|
||||
"ID": "bcmpinc.TilledSoilDecay",
|
||||
"~4.1.0 | Status": "AssumeBroken",
|
||||
|
@ -238,12 +287,6 @@
|
|||
/*********
|
||||
** Broke in SDV 1.5 (SMAPI mods)
|
||||
*********/
|
||||
"Audio Devices": {
|
||||
"ID": "maxvollmer.audiodevices",
|
||||
"~2.0.0 | Status": "AssumeBroken",
|
||||
"~2.0.0 | StatusReasonDetails": "causes crash to desktop when starting the game"
|
||||
},
|
||||
|
||||
"ChestEx": {
|
||||
"ID": "berkayylmao.ChestEx",
|
||||
"~1.3.4 | Status": "AssumeBroken",
|
||||
|
|
|
@ -40,15 +40,10 @@ namespace StardewModdingAPI
|
|||
internal static GamePlatform Platform { get; } = (GamePlatform)Enum.Parse(typeof(GamePlatform), LowLevelEnvironmentUtility.DetectPlatform());
|
||||
|
||||
/// <summary>The game framework running the game.</summary>
|
||||
internal static GameFramework GameFramework { get; } =
|
||||
#if SMAPI_FOR_XNA
|
||||
GameFramework.Xna;
|
||||
#else
|
||||
GameFramework.MonoGame;
|
||||
#endif
|
||||
internal static GameFramework GameFramework { get; } = GameFramework.MonoGame;
|
||||
|
||||
/// <summary>The game's assembly name.</summary>
|
||||
internal static string GameAssemblyName => EarlyConstants.Platform == GamePlatform.Windows ? "Stardew Valley" : "StardewValley";
|
||||
internal static string GameAssemblyName { get; } = "Stardew Valley";
|
||||
|
||||
/// <summary>The <see cref="Context.ScreenId"/> value which should appear in the SMAPI log, if any.</summary>
|
||||
internal static int? LogScreenId { get; set; }
|
||||
|
@ -70,10 +65,10 @@ namespace StardewModdingAPI
|
|||
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion(EarlyConstants.RawApiVersion);
|
||||
|
||||
/// <summary>The minimum supported version of Stardew Valley.</summary>
|
||||
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.4");
|
||||
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.5");
|
||||
|
||||
/// <summary>The maximum supported version of Stardew Valley.</summary>
|
||||
public static ISemanticVersion MaximumGameVersion { get; } = new GameVersion("1.5.4");
|
||||
public static ISemanticVersion MaximumGameVersion { get; } = null;
|
||||
|
||||
/// <summary>The target game platform.</summary>
|
||||
public static GamePlatform TargetPlatform { get; } = EarlyConstants.Platform;
|
||||
|
@ -240,18 +235,13 @@ namespace StardewModdingAPI
|
|||
// The game assembly can have one of three names depending how the mod was compiled:
|
||||
// - 'StardewValley': assembly name on Linux/macOS;
|
||||
// - 'Stardew Valley': assembly name on Windows;
|
||||
// - 'Netcode': an assembly that's separate on Windows only.
|
||||
resolver.AddWithExplicitNames(AssemblyDefinition.ReadAssembly(typeof(Game1).Assembly.Location), "StardewValley", "Stardew Valley"
|
||||
#if !SMAPI_FOR_WINDOWS
|
||||
, "Netcode"
|
||||
#endif
|
||||
);
|
||||
// - 'Netcode': an assembly that was separate on Windows only before Stardew Valley 1.5.5.
|
||||
resolver.AddWithExplicitNames(AssemblyDefinition.ReadAssembly(typeof(Game1).Assembly.Location), "StardewValley", "Stardew Valley", "Netcode");
|
||||
}
|
||||
|
||||
/// <summary>Get metadata for mapping assemblies to the current platform.</summary>
|
||||
/// <param name="targetPlatform">The target game platform.</param>
|
||||
/// <param name="framework">The game framework running the game.</param>
|
||||
internal static PlatformAssemblyMap GetAssemblyMap(Platform targetPlatform, GameFramework framework)
|
||||
internal static PlatformAssemblyMap GetAssemblyMap(Platform targetPlatform)
|
||||
{
|
||||
var removeAssemblyReferences = new List<string>();
|
||||
var targetAssemblies = new List<Assembly>();
|
||||
|
@ -260,61 +250,26 @@ namespace StardewModdingAPI
|
|||
removeAssemblyReferences.Add("StardewModdingAPI.Toolkit.CoreInterfaces");
|
||||
targetAssemblies.Add(typeof(StardewModdingAPI.IManifest).Assembly);
|
||||
|
||||
// get changes for platform
|
||||
if (Constants.Platform != Platform.Windows)
|
||||
// XNA Framework before Stardew Valley 1.5.5
|
||||
removeAssemblyReferences.AddRange(new[]
|
||||
{
|
||||
removeAssemblyReferences.AddRange(new[]
|
||||
{
|
||||
"Netcode",
|
||||
"Stardew Valley"
|
||||
});
|
||||
targetAssemblies.Add(
|
||||
typeof(StardewValley.Game1).Assembly // note: includes Netcode types on Linux/macOS
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
removeAssemblyReferences.Add(
|
||||
"StardewValley"
|
||||
);
|
||||
targetAssemblies.AddRange(new[]
|
||||
{
|
||||
typeof(Netcode.NetBool).Assembly,
|
||||
typeof(StardewValley.Game1).Assembly
|
||||
});
|
||||
}
|
||||
"Microsoft.Xna.Framework",
|
||||
"Microsoft.Xna.Framework.Game",
|
||||
"Microsoft.Xna.Framework.Graphics",
|
||||
"Microsoft.Xna.Framework.Xact"
|
||||
});
|
||||
targetAssemblies.Add(
|
||||
typeof(Microsoft.Xna.Framework.Vector2).Assembly
|
||||
);
|
||||
|
||||
// get changes for game framework
|
||||
switch (framework)
|
||||
{
|
||||
case GameFramework.MonoGame:
|
||||
removeAssemblyReferences.AddRange(new[]
|
||||
{
|
||||
"Microsoft.Xna.Framework",
|
||||
"Microsoft.Xna.Framework.Game",
|
||||
"Microsoft.Xna.Framework.Graphics",
|
||||
"Microsoft.Xna.Framework.Xact"
|
||||
});
|
||||
targetAssemblies.Add(
|
||||
typeof(Microsoft.Xna.Framework.Vector2).Assembly
|
||||
);
|
||||
break;
|
||||
// `Netcode.dll` merged into the game assembly in Stardew Valley 1.5.5
|
||||
removeAssemblyReferences.Add(
|
||||
"Netcode"
|
||||
);
|
||||
|
||||
case GameFramework.Xna:
|
||||
removeAssemblyReferences.Add(
|
||||
"MonoGame.Framework"
|
||||
);
|
||||
targetAssemblies.AddRange(new[]
|
||||
{
|
||||
typeof(Microsoft.Xna.Framework.Vector2).Assembly,
|
||||
typeof(Microsoft.Xna.Framework.Game).Assembly,
|
||||
typeof(Microsoft.Xna.Framework.Graphics.SpriteBatch).Assembly
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown game framework '{framework}'.");
|
||||
}
|
||||
// Stardew Valley reference
|
||||
removeAssemblyReferences.Add("StardewValley");
|
||||
targetAssemblies.Add(typeof(StardewValley.Game1).Assembly);
|
||||
|
||||
return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences.ToArray(), targetAssemblies.ToArray());
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
using StardewValley;
|
||||
using xTile;
|
||||
using xTile.Layers;
|
||||
using xTile.Tiles;
|
||||
|
@ -25,18 +26,17 @@ namespace StardewModdingAPI.Framework.Content
|
|||
: base(locale, assetName, data, getNormalizedPath, onDataReplaced) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Derived from <see cref="StardewValley.GameLocation.ApplyMapOverride"/> with a few changes:
|
||||
/// <remarks>Derived from <see cref="GameLocation.ApplyMapOverride(Map,string,Rectangle?,Rectangle?)"/> with a few changes:
|
||||
/// - can be applied directly to the maps when loading, before the location is created;
|
||||
/// - added support for source/target areas;
|
||||
/// - added support for patch modes (overlay, replace by layer, or fully replace);
|
||||
/// - added disambiguation if source has a modified version of the same tilesheet, instead of copying tiles into the target tilesheet;
|
||||
/// - changed to always overwrite tiles within the target area (to avoid edge cases where some tiles are only partly applied);
|
||||
/// - fixed copying tilesheets (avoid "The specified TileSheet was not created for use with this map" error);
|
||||
/// - fixed tilesheets not added at the end (via z_ prefix), which can cause crashes in game code which depends on hardcoded tilesheet indexes;
|
||||
/// - fixed issue where different tilesheets are linked by ID.
|
||||
/// </remarks>
|
||||
public void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null)
|
||||
public void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMapMode patchMode = PatchMapMode.Overlay)
|
||||
{
|
||||
var target = this.Data;
|
||||
Map target = this.Data;
|
||||
|
||||
// get areas
|
||||
{
|
||||
|
@ -84,10 +84,13 @@ namespace StardewModdingAPI.Framework.Content
|
|||
tilesheetMap[sourceSheet] = targetSheet;
|
||||
}
|
||||
|
||||
// get layer map
|
||||
IDictionary<Layer, Layer> layerMap = source.Layers.ToDictionary(p => p, p => target.GetLayer(p.Id));
|
||||
// get target layers
|
||||
IDictionary<Layer, Layer> sourceToTargetLayers = source.Layers.ToDictionary(p => p, p => target.GetLayer(p.Id));
|
||||
HashSet<Layer> orphanedTargetLayers = new HashSet<Layer>(target.Layers.Except(sourceToTargetLayers.Values));
|
||||
|
||||
// apply tiles
|
||||
bool replaceAll = patchMode == PatchMapMode.Replace;
|
||||
bool replaceByLayer = patchMode == PatchMapMode.ReplaceByLayer;
|
||||
for (int x = 0; x < sourceArea.Value.Width; x++)
|
||||
{
|
||||
for (int y = 0; y < sourceArea.Value.Height; y++)
|
||||
|
@ -96,47 +99,37 @@ namespace StardewModdingAPI.Framework.Content
|
|||
Point sourcePos = new Point(sourceArea.Value.X + x, sourceArea.Value.Y + y);
|
||||
Point targetPos = new Point(targetArea.Value.X + x, targetArea.Value.Y + y);
|
||||
|
||||
// replace tiles on target-only layers
|
||||
if (replaceAll)
|
||||
{
|
||||
foreach (Layer targetLayer in orphanedTargetLayers)
|
||||
targetLayer.Tiles[targetPos.X, targetPos.Y] = null;
|
||||
}
|
||||
|
||||
// merge layers
|
||||
foreach (Layer sourceLayer in source.Layers)
|
||||
{
|
||||
// get layer
|
||||
Layer targetLayer = layerMap[sourceLayer];
|
||||
Layer targetLayer = sourceToTargetLayers[sourceLayer];
|
||||
if (targetLayer == null)
|
||||
{
|
||||
target.AddLayer(targetLayer = new Layer(sourceLayer.Id, target, target.Layers[0].LayerSize, Layer.m_tileSize));
|
||||
layerMap[sourceLayer] = target.GetLayer(sourceLayer.Id);
|
||||
sourceToTargetLayers[sourceLayer] = target.GetLayer(sourceLayer.Id);
|
||||
}
|
||||
|
||||
// copy layer properties
|
||||
targetLayer.Properties.CopyFrom(sourceLayer.Properties);
|
||||
|
||||
// copy tiles
|
||||
// create new tile
|
||||
Tile sourceTile = sourceLayer.Tiles[sourcePos.X, sourcePos.Y];
|
||||
Tile targetTile;
|
||||
switch (sourceTile)
|
||||
{
|
||||
case StaticTile _:
|
||||
targetTile = new StaticTile(targetLayer, tilesheetMap[sourceTile.TileSheet], sourceTile.BlendMode, sourceTile.TileIndex);
|
||||
break;
|
||||
Tile newTile = sourceTile != null
|
||||
? this.CreateTile(sourceTile, targetLayer, tilesheetMap[sourceTile.TileSheet])
|
||||
: null;
|
||||
newTile?.Properties.CopyFrom(sourceTile.Properties);
|
||||
|
||||
case AnimatedTile animatedTile:
|
||||
{
|
||||
StaticTile[] tileFrames = new StaticTile[animatedTile.TileFrames.Length];
|
||||
for (int frame = 0; frame < animatedTile.TileFrames.Length; ++frame)
|
||||
{
|
||||
StaticTile frameTile = animatedTile.TileFrames[frame];
|
||||
tileFrames[frame] = new StaticTile(targetLayer, tilesheetMap[frameTile.TileSheet], frameTile.BlendMode, frameTile.TileIndex);
|
||||
}
|
||||
targetTile = new AnimatedTile(targetLayer, tileFrames, animatedTile.FrameInterval);
|
||||
}
|
||||
break;
|
||||
|
||||
default: // null or unhandled type
|
||||
targetTile = null;
|
||||
break;
|
||||
}
|
||||
targetTile?.Properties.CopyFrom(sourceTile.Properties);
|
||||
targetLayer.Tiles[targetPos.X, targetPos.Y] = targetTile;
|
||||
// replace tile
|
||||
if (newTile != null || replaceByLayer || replaceAll)
|
||||
targetLayer.Tiles[targetPos.X, targetPos.Y] = newTile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,6 +139,33 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Create a new tile for the target map.</summary>
|
||||
/// <param name="sourceTile">The source tile to copy.</param>
|
||||
/// <param name="targetLayer">The target layer.</param>
|
||||
/// <param name="targetSheet">The target tilesheet.</param>
|
||||
private Tile CreateTile(Tile sourceTile, Layer targetLayer, TileSheet targetSheet)
|
||||
{
|
||||
switch (sourceTile)
|
||||
{
|
||||
case StaticTile _:
|
||||
return new StaticTile(targetLayer, targetSheet, sourceTile.BlendMode, sourceTile.TileIndex);
|
||||
|
||||
case AnimatedTile animatedTile:
|
||||
{
|
||||
StaticTile[] tileFrames = new StaticTile[animatedTile.TileFrames.Length];
|
||||
for (int frame = 0; frame < animatedTile.TileFrames.Length; ++frame)
|
||||
{
|
||||
StaticTile frameTile = animatedTile.TileFrames[frame];
|
||||
tileFrames[frame] = new StaticTile(targetLayer, targetSheet, frameTile.BlendMode, frameTile.TileIndex);
|
||||
}
|
||||
|
||||
return new AnimatedTile(targetLayer, tileFrames, animatedTile.FrameInterval);
|
||||
}
|
||||
|
||||
default: // null or unhandled type
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/// <summary>Normalize a map tilesheet path for comparison. This value should *not* be used as the actual tilesheet path.</summary>
|
||||
/// <param name="path">The path to normalize.</param>
|
||||
private string NormalizeTilesheetPathForComparison(string path)
|
||||
|
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
using StardewValley;
|
||||
|
@ -18,9 +17,6 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <summary>The underlying asset cache.</summary>
|
||||
private readonly IDictionary<string, object> Cache;
|
||||
|
||||
/// <summary>Applies platform-specific asset key normalization so it's consistent with the underlying cache.</summary>
|
||||
private readonly Func<string, string> NormalizeAssetNameForPlatform;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
|
@ -48,17 +44,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <param name="reflection">Simplifies access to private game code.</param>
|
||||
public ContentCache(LocalizedContentManager contentManager, Reflector reflection)
|
||||
{
|
||||
// init
|
||||
this.Cache = reflection.GetField<Dictionary<string, object>>(contentManager, "loadedAssets").GetValue();
|
||||
|
||||
// get key normalization logic
|
||||
if (Constants.GameFramework == GameFramework.Xna)
|
||||
{
|
||||
IReflectedMethod method = reflection.GetMethod(typeof(TitleContainer), "GetCleanPath");
|
||||
this.NormalizeAssetNameForPlatform = path => method.Invoke<string>(path);
|
||||
}
|
||||
else
|
||||
this.NormalizeAssetNameForPlatform = key => key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load<T> logic
|
||||
}
|
||||
|
||||
/****
|
||||
|
@ -75,23 +61,24 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/****
|
||||
** Normalize
|
||||
****/
|
||||
/// <summary>Normalize path separators in a file path. For asset keys, see <see cref="NormalizeKey"/> instead.</summary>
|
||||
/// <summary>Normalize path separators in an asset name.</summary>
|
||||
/// <param name="path">The file path to normalize.</param>
|
||||
[Pure]
|
||||
public string NormalizePathSeparators(string path)
|
||||
{
|
||||
return PathUtilities.NormalizePath(path);
|
||||
return PathUtilities.NormalizeAssetName(path);
|
||||
}
|
||||
|
||||
/// <summary>Normalize a cache key so it's consistent with the underlying cache.</summary>
|
||||
/// <param name="key">The asset key.</param>
|
||||
/// <remarks>This is equivalent to <see cref="NormalizePathSeparators"/> with added file extension logic.</remarks>
|
||||
[Pure]
|
||||
public string NormalizeKey(string key)
|
||||
{
|
||||
key = this.NormalizePathSeparators(key);
|
||||
return key.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase)
|
||||
? key.Substring(0, key.Length - 4)
|
||||
: this.NormalizeAssetNameForPlatform(key);
|
||||
: key;
|
||||
}
|
||||
|
||||
/****
|
||||
|
|
|
@ -64,6 +64,9 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>An unmodified content manager which doesn't intercept assets, used to compare asset data.</summary>
|
||||
private readonly LocalizedContentManager VanillaContentManager;
|
||||
|
||||
/// <summary>The language enum values indexed by locale code.</summary>
|
||||
private Lazy<IDictionary<string, LocalizedContentManager.LanguageCode>> LocaleCodes;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
|
@ -133,6 +136,7 @@ namespace StardewModdingAPI.Framework
|
|||
this.ContentManagers.Add(contentManagerForAssetPropagation);
|
||||
this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory);
|
||||
this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, this.Monitor, reflection, aggressiveMemoryOptimizations);
|
||||
this.LocaleCodes = new Lazy<IDictionary<string, LocalizedContentManager.LanguageCode>>(this.GetLocaleCodes);
|
||||
}
|
||||
|
||||
/// <summary>Get a new content manager which handles reading files from the game content folder with support for interception.</summary>
|
||||
|
@ -195,6 +199,10 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>Perform any cleanup needed when the locale changes.</summary>
|
||||
public void OnLocaleChanged()
|
||||
{
|
||||
// rebuild locale cache (which may change due to custom mod languages)
|
||||
this.LocaleCodes = new Lazy<IDictionary<string, LocalizedContentManager.LanguageCode>>(this.GetLocaleCodes);
|
||||
|
||||
// reload affected content
|
||||
this.ContentManagerLock.InReadLock(() =>
|
||||
{
|
||||
foreach (IContentManager contentManager in this.ContentManagers)
|
||||
|
@ -408,6 +416,25 @@ namespace StardewModdingAPI.Framework
|
|||
return tilesheets ?? new TilesheetReference[0];
|
||||
}
|
||||
|
||||
/// <summary>Get the language enum which corresponds to a locale code (e.g. <see cref="LocalizedContentManager.LanguageCode.fr"/> given <c>fr-FR</c>).</summary>
|
||||
/// <param name="locale">The locale code to search. This must exactly match the language; no fallback is performed.</param>
|
||||
/// <param name="language">The matched language enum, if any.</param>
|
||||
/// <returns>Returns whether a valid language was found.</returns>
|
||||
public bool TryGetLanguageEnum(string locale, out LocalizedContentManager.LanguageCode language)
|
||||
{
|
||||
return this.LocaleCodes.Value.TryGetValue(locale, out language);
|
||||
}
|
||||
|
||||
/// <summary>Get the locale code which corresponds to a language enum (e.g. <c>fr-FR</c> given <see cref="LocalizedContentManager.LanguageCode.fr"/>).</summary>
|
||||
/// <param name="language">The language enum to search.</param>
|
||||
public string GetLocaleCode(LocalizedContentManager.LanguageCode language)
|
||||
{
|
||||
if (language == LocalizedContentManager.LanguageCode.mod && LocalizedContentManager.CurrentModLanguage == null)
|
||||
return null;
|
||||
|
||||
return Game1.content.LanguageCodeString(language);
|
||||
}
|
||||
|
||||
/// <summary>Dispose held resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -457,5 +484,19 @@ namespace StardewModdingAPI.Framework
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Get the language enums (like <see cref="LocalizedContentManager.LanguageCode.ja"/>) indexed by locale code (like <c>ja-JP</c>).</summary>
|
||||
private IDictionary<string, LocalizedContentManager.LanguageCode> GetLocaleCodes()
|
||||
{
|
||||
IDictionary<string, LocalizedContentManager.LanguageCode> map = new Dictionary<string, LocalizedContentManager.LanguageCode>();
|
||||
foreach (LocalizedContentManager.LanguageCode code in Enum.GetValues(typeof(LocalizedContentManager.LanguageCode)))
|
||||
{
|
||||
string locale = this.GetLocaleCode(code);
|
||||
if (locale != null)
|
||||
map[locale] = code;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,9 +38,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <summary>A callback to invoke when the content manager is being disposed.</summary>
|
||||
private readonly Action<BaseContentManager> OnDisposing;
|
||||
|
||||
/// <summary>The language enum values indexed by locale code.</summary>
|
||||
protected IDictionary<string, LanguageCode> LanguageCodes { get; }
|
||||
|
||||
/// <summary>A list of disposable assets.</summary>
|
||||
private readonly List<WeakReference<IDisposable>> Disposables = new List<WeakReference<IDisposable>>();
|
||||
|
||||
|
@ -92,7 +89,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations;
|
||||
|
||||
// get asset data
|
||||
this.LanguageCodes = this.GetKeyLocales().ToDictionary(p => p.Value, p => p.Key, StringComparer.OrdinalIgnoreCase);
|
||||
this.BaseDisposableReferences = reflection.GetField<List<IDisposable>>(this, "disposableAssets").GetValue();
|
||||
}
|
||||
|
||||
|
@ -292,7 +288,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
if (lastSepIndex >= 0)
|
||||
{
|
||||
string suffix = cacheKey.Substring(lastSepIndex + 1, cacheKey.Length - lastSepIndex - 1);
|
||||
if (this.LanguageCodes.ContainsKey(suffix))
|
||||
if (this.Coordinator.TryGetLanguageEnum(suffix, out _))
|
||||
{
|
||||
assetName = cacheKey.Substring(0, lastSepIndex);
|
||||
localeCode = cacheKey.Substring(lastSepIndex + 1, cacheKey.Length - lastSepIndex - 1);
|
||||
|
@ -311,17 +307,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <param name="language">The language to check.</param>
|
||||
protected abstract bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language);
|
||||
|
||||
/// <summary>Get the locale codes (like <c>ja-JP</c>) used in asset keys.</summary>
|
||||
private IDictionary<LanguageCode, string> GetKeyLocales()
|
||||
{
|
||||
// create locale => code map
|
||||
IDictionary<LanguageCode, string> map = new Dictionary<LanguageCode, string>();
|
||||
foreach (LanguageCode code in Enum.GetValues(typeof(LanguageCode)))
|
||||
map[code] = this.GetLocale(code);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>Get the asset name from a cache key.</summary>
|
||||
/// <param name="cacheKey">The input cache key.</param>
|
||||
private string GetAssetName(string cacheKey)
|
||||
|
|
|
@ -249,7 +249,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
|
||||
// extract language code
|
||||
int splitIndex = rawAsset.LastIndexOf('.');
|
||||
if (splitIndex != -1 && this.LanguageCodes.TryGetValue(rawAsset.Substring(splitIndex + 1), out language))
|
||||
if (splitIndex != -1 && this.Coordinator.TryGetLanguageEnum(rawAsset.Substring(splitIndex + 1), out language))
|
||||
{
|
||||
assetName = rawAsset.Substring(0, splitIndex);
|
||||
return true;
|
||||
|
|
|
@ -2,12 +2,12 @@ using System;
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BmFont;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using StardewModdingAPI.Framework.Exceptions;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Internal;
|
||||
using StardewModdingAPI.Toolkit.Serialization;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
using StardewValley;
|
||||
|
@ -130,6 +130,14 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
}
|
||||
break;
|
||||
|
||||
// unpacked Bitmap font
|
||||
case ".fnt":
|
||||
{
|
||||
string source = File.ReadAllText(file.FullName);
|
||||
asset = (T)(object)new XmlSource(source);
|
||||
}
|
||||
break;
|
||||
|
||||
// unpacked data
|
||||
case ".json":
|
||||
{
|
||||
|
@ -172,13 +180,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
break;
|
||||
|
||||
default:
|
||||
throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.json', '.png', '.tbin', or '.xnb'.");
|
||||
throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', or '.xnb'.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!(ex is SContentLoadException))
|
||||
{
|
||||
if (ex.GetInnermostException() is DllNotFoundException dllEx && dllEx.Message == "libgdiplus.dylib")
|
||||
throw GetContentError("couldn't find libgdiplus, which is needed to load mod images. Make sure Mono is installed and you're running the game through the normal launcher.");
|
||||
throw new SContentLoadException($"The content manager failed loading content asset '{assetName}' from {this.Name}.", ex);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
|
@ -157,11 +158,8 @@ namespace StardewModdingAPI.Framework.Input
|
|||
yield break;
|
||||
|
||||
// buttons
|
||||
foreach (var pair in this.ButtonStates)
|
||||
{
|
||||
if (pair.Value == ButtonState.Pressed && pair.Key.TryGetController(out Buttons button))
|
||||
yield return button.ToSButton();
|
||||
}
|
||||
foreach (Buttons button in this.GetPressedGamePadButtons())
|
||||
yield return button.ToSButton();
|
||||
|
||||
// triggers
|
||||
if (this.LeftTrigger > 0.2f)
|
||||
|
@ -201,7 +199,7 @@ namespace StardewModdingAPI.Framework.Input
|
|||
rightThumbStick: this.RightStickPos,
|
||||
leftTrigger: this.LeftTrigger,
|
||||
rightTrigger: this.RightTrigger,
|
||||
buttons: this.GetButtonBitmask() // MonoGame requires one bitmask here; don't specify multiple values
|
||||
buttons: this.GetPressedGamePadButtons().ToArray()
|
||||
);
|
||||
|
||||
return this.State.Value;
|
||||
|
@ -211,17 +209,14 @@ namespace StardewModdingAPI.Framework.Input
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Get a bitmask representing the pressed buttons.</summary>
|
||||
private Buttons GetButtonBitmask()
|
||||
/// <summary>Get the pressed gamepad buttons.</summary>
|
||||
private IEnumerable<Buttons> GetPressedGamePadButtons()
|
||||
{
|
||||
Buttons flag = 0;
|
||||
foreach (var pair in this.ButtonStates)
|
||||
{
|
||||
if (pair.Value == ButtonState.Pressed && pair.Key.TryGetController(out Buttons button))
|
||||
flag |= button;
|
||||
yield return button;
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ using System.Threading;
|
|||
using Microsoft.Xna.Framework.Graphics;
|
||||
using StardewModdingAPI.Framework.Events;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewValley;
|
||||
using StardewValley.Menus;
|
||||
|
||||
namespace StardewModdingAPI.Framework
|
||||
|
@ -150,11 +149,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="reflection">The reflection helper with which to access private fields.</param>
|
||||
public static bool IsOpen(this SpriteBatch spriteBatch, Reflector reflection)
|
||||
{
|
||||
string fieldName = Constants.GameFramework == GameFramework.Xna
|
||||
? "inBeginEndPair"
|
||||
: "_beginCalled";
|
||||
|
||||
return reflection.GetField<bool>(Game1.spriteBatch, fieldName).GetValue();
|
||||
return reflection.GetField<bool>(spriteBatch, "_beginCalled").GetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -250,36 +250,7 @@ namespace StardewModdingAPI.Framework.Logging
|
|||
/// <param name="exception">The exception details.</param>
|
||||
public void LogFatalLaunchError(Exception exception)
|
||||
{
|
||||
switch (exception)
|
||||
{
|
||||
// audio crash
|
||||
case InvalidOperationException ex when ex.Source == "Microsoft.Xna.Framework.Xact" && ex.StackTrace.Contains("Microsoft.Xna.Framework.Audio.AudioEngine..ctor"):
|
||||
this.Monitor.Log("The game couldn't load audio. Do you have speakers or headphones plugged in?", LogLevel.Error);
|
||||
this.Monitor.Log($"Technical details: {ex.GetLogSummary()}");
|
||||
break;
|
||||
|
||||
// missing content folder exception
|
||||
case FileNotFoundException ex when ex.Message == "Couldn't find file 'C:\\Program Files (x86)\\Steam\\SteamApps\\common\\Stardew Valley\\Content\\XACT\\FarmerSounds.xgs'.": // path in error is hardcoded regardless of install path
|
||||
this.Monitor.Log("The game can't find its Content\\XACT\\FarmerSounds.xgs file. You can usually fix this by resetting your content files (see https://smapi.io/troubleshoot#reset-content ), or by uninstalling and reinstalling the game.", LogLevel.Error);
|
||||
this.Monitor.Log($"Technical details: {ex.GetLogSummary()}");
|
||||
break;
|
||||
|
||||
// path too long exception
|
||||
case PathTooLongException _:
|
||||
{
|
||||
string[] affectedPaths = PathUtilities.GetTooLongPaths(Constants.ModsPath).ToArray();
|
||||
string message = affectedPaths.Any()
|
||||
? $"SMAPI can't launch because some of your mod files exceed the maximum path length on {Constants.Platform}.\nIf you need help fixing this error, see https://smapi.io/help\n\nAffected paths:\n {string.Join("\n ", affectedPaths)}"
|
||||
: $"The game failed to launch: {exception.GetLogSummary()}";
|
||||
this.MonitorForGame.Log(message, LogLevel.Error);
|
||||
}
|
||||
break;
|
||||
|
||||
// generic exception
|
||||
default:
|
||||
this.MonitorForGame.Log($"The game failed to launch: {exception.GetLogSummary()}", LogLevel.Error);
|
||||
break;
|
||||
}
|
||||
this.MonitorForGame.Log($"The game failed to launch: {exception.GetLogSummary()}", LogLevel.Error);
|
||||
}
|
||||
|
||||
/****
|
||||
|
@ -290,7 +261,7 @@ namespace StardewModdingAPI.Framework.Logging
|
|||
/// <param name="customSettings">The custom SMAPI settings.</param>
|
||||
public void LogIntro(string modsPath, IDictionary<string, object> customSettings)
|
||||
{
|
||||
// log platform & patches
|
||||
// log platform
|
||||
this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info);
|
||||
|
||||
// log basic info
|
||||
|
|
|
@ -53,16 +53,15 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="targetPlatform">The current game platform.</param>
|
||||
/// <param name="framework">The game framework running the game.</param>
|
||||
/// <param name="monitor">Encapsulates monitoring and logging.</param>
|
||||
/// <param name="paranoidMode">Whether to detect paranoid mode issues.</param>
|
||||
/// <param name="rewriteMods">Whether to rewrite mods for compatibility.</param>
|
||||
public AssemblyLoader(Platform targetPlatform, GameFramework framework, IMonitor monitor, bool paranoidMode, bool rewriteMods)
|
||||
public AssemblyLoader(Platform targetPlatform, IMonitor monitor, bool paranoidMode, bool rewriteMods)
|
||||
{
|
||||
this.Monitor = monitor;
|
||||
this.ParanoidMode = paranoidMode;
|
||||
this.RewriteMods = rewriteMods;
|
||||
this.AssemblyMap = this.TrackForDisposal(Constants.GetAssemblyMap(targetPlatform, framework));
|
||||
this.AssemblyMap = this.TrackForDisposal(Constants.GetAssemblyMap(targetPlatform));
|
||||
|
||||
// init resolver
|
||||
this.AssemblyDefinitionResolver = this.TrackForDisposal(new AssemblyDefinitionResolver());
|
||||
|
|
|
@ -4,7 +4,7 @@ using Microsoft.Xna.Framework.Graphics;
|
|||
|
||||
namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
||||
{
|
||||
/// <summary>Provides <see cref="SpriteBatch"/> method signatures that can be injected into mod code for compatibility between Linux/macOS or Windows.</summary>
|
||||
/// <summary>Provides <see cref="SpriteBatch"/> method signatures that can be injected into mod code for compatibility with mods written for XNA Framework before Stardew Valley 1.5.5.</summary>
|
||||
/// <remarks>This is public to support SMAPI rewriting and should not be referenced directly by mods.</remarks>
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")]
|
||||
[SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/macOS.")]
|
||||
|
@ -18,14 +18,6 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
public SpriteBatchFacade(GraphicsDevice graphicsDevice) : base(graphicsDevice) { }
|
||||
|
||||
|
||||
/****
|
||||
** MonoGame signatures
|
||||
****/
|
||||
public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix)
|
||||
{
|
||||
base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity);
|
||||
}
|
||||
|
||||
/****
|
||||
** XNA signatures
|
||||
****/
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
/// <summary>Construct an instance.</summary>
|
||||
public InterfaceProxyFactory()
|
||||
{
|
||||
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run);
|
||||
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run);
|
||||
this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies");
|
||||
}
|
||||
|
||||
|
|
|
@ -11,14 +11,10 @@ using System.Runtime.ExceptionServices;
|
|||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Xna.Framework;
|
||||
#if SMAPI_FOR_WINDOWS
|
||||
using Microsoft.Win32;
|
||||
#endif
|
||||
#if SMAPI_FOR_XNA
|
||||
using System.Windows.Forms;
|
||||
#endif
|
||||
using Newtonsoft.Json;
|
||||
using StardewModdingAPI.Enums;
|
||||
using StardewModdingAPI.Events;
|
||||
|
@ -224,10 +220,6 @@ namespace StardewModdingAPI.Framework
|
|||
this.Toolkit.JsonHelper.JsonSettings.Converters.Add(converter);
|
||||
|
||||
// add error handlers
|
||||
#if SMAPI_FOR_XNA
|
||||
Application.ThreadException += (sender, e) => this.Monitor.Log($"Critical thread exception: {e.Exception.GetLogSummary()}", LogLevel.Error);
|
||||
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
|
||||
#endif
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e) => this.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error);
|
||||
|
||||
// add more lenient assembly resolver
|
||||
|
@ -243,7 +235,7 @@ namespace StardewModdingAPI.Framework
|
|||
monitor: this.Monitor,
|
||||
reflection: this.Reflection,
|
||||
eventManager: this.EventManager,
|
||||
modHooks: new SModHooks(this.OnNewDayAfterFade),
|
||||
modHooks: new SModHooks(this.OnNewDayAfterFade, this.Monitor),
|
||||
multiplayer: this.Multiplayer,
|
||||
exitGameImmediately: this.ExitGameImmediately,
|
||||
|
||||
|
@ -657,13 +649,6 @@ namespace StardewModdingAPI.Framework
|
|||
this.Monitor.Log("Game loader done.");
|
||||
}
|
||||
|
||||
if (instance.NewDayTask?.Status == TaskStatus.Created)
|
||||
{
|
||||
this.Monitor.Log("New day task synchronizing...");
|
||||
instance.NewDayTask.RunSynchronously();
|
||||
this.Monitor.Log("New day task done.");
|
||||
}
|
||||
|
||||
// While a background task is in progress, the game may make changes to the game
|
||||
// state while mods are running their code. This is risky, because data changes can
|
||||
// conflict (e.g. collection changed during enumeration errors) and data may change
|
||||
|
@ -673,7 +658,7 @@ namespace StardewModdingAPI.Framework
|
|||
// a small chance that the task will finish after we defer but before the game checks,
|
||||
// which means technically events should be raised, but the effects of missing one
|
||||
// update tick are negligible and not worth the complications of bypassing Game1.Update.
|
||||
if (instance.NewDayTask != null || Game1.gameMode == Game1.loadingMode)
|
||||
if (Game1.gameMode == Game1.loadingMode)
|
||||
{
|
||||
events.UnvalidatedUpdateTicking.RaiseEmpty();
|
||||
runUpdate();
|
||||
|
@ -766,7 +751,7 @@ namespace StardewModdingAPI.Framework
|
|||
** Locale changed events
|
||||
*********/
|
||||
if (state.Locale.IsChanged)
|
||||
this.Monitor.Log($"Context: locale set to {state.Locale.New}.");
|
||||
this.Monitor.Log($"Context: locale set to {state.Locale.New} ({this.ContentCore.GetLocaleCode(state.Locale.New)}).");
|
||||
|
||||
/*********
|
||||
** Load / return-to-title events
|
||||
|
@ -776,7 +761,7 @@ namespace StardewModdingAPI.Framework
|
|||
else if (Context.IsWorldReady && Context.LoadStage != LoadStage.Ready)
|
||||
{
|
||||
// print context
|
||||
string context = $"Context: loaded save '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}, locale set to {this.ContentCore.Language}.";
|
||||
string context = $"Context: loaded save '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}, locale set to {this.ContentCore.GetLocale()}.";
|
||||
if (Context.IsMultiplayer)
|
||||
{
|
||||
int onlineCount = Game1.getOnlineFarmers().Count();
|
||||
|
@ -1304,9 +1289,6 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
// create client
|
||||
string url = this.Settings.WebApiBaseUrl;
|
||||
#if !SMAPI_FOR_WINDOWS
|
||||
url = url.Replace("https://", "http://"); // workaround for OpenSSL issues with the game's bundled Mono on Linux/macOS
|
||||
#endif
|
||||
WebApiClient client = new WebApiClient(url, Constants.ApiVersion);
|
||||
this.Monitor.Log("Checking for updates...");
|
||||
|
||||
|
@ -1491,7 +1473,7 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
// load mods
|
||||
IList<IModMetadata> skippedMods = new List<IModMetadata>();
|
||||
using (AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.Platform, Constants.GameFramework, this.Monitor, this.Settings.ParanoidWarnings, this.Settings.RewriteMods))
|
||||
using (AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.Platform, this.Monitor, this.Settings.ParanoidWarnings, this.Settings.RewriteMods))
|
||||
{
|
||||
// init
|
||||
HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase);
|
||||
|
@ -1701,9 +1683,8 @@ namespace StardewModdingAPI.Framework
|
|||
catch (Exception ex)
|
||||
{
|
||||
errorReasonPhrase = "its DLL couldn't be loaded.";
|
||||
// re-enable in Stardew Valley 1.5.5
|
||||
//if (ex is BadImageFormatException && !EnvironmentUtility.Is64BitAssembly(assemblyPath))
|
||||
// errorReasonPhrase = "it needs to be updated for 64-bit mode.";
|
||||
if (ex is BadImageFormatException && !EnvironmentUtility.Is64BitAssembly(assemblyPath))
|
||||
errorReasonPhrase = "it needs to be updated for 64-bit mode.";
|
||||
|
||||
errorDetails = $"Error: {ex.GetLogSummary()}";
|
||||
failReason = ModFailReason.LoadFailed;
|
||||
|
|
|
@ -252,7 +252,7 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>Replicate the game's draw logic with some changes for SMAPI.</summary>
|
||||
/// <param name="gameTime">A snapshot of the game timing state.</param>
|
||||
/// <param name="target_screen">The render target, if any.</param>
|
||||
/// <remarks>This implementation is identical to <see cref="Game1.Draw"/>, except for try..catch around menu draw code, private field references replaced by wrappers, and added events.</remarks>
|
||||
/// <remarks>This implementation is identical to <see cref="Game1._draw"/>, except for try..catch around menu draw code, private field references replaced by wrappers, and added events.</remarks>
|
||||
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "copied from game code as-is")]
|
||||
[SuppressMessage("ReSharper", "LocalVariableHidesMember", Justification = "copied from game code as-is")]
|
||||
|
@ -286,7 +286,7 @@ namespace StardewModdingAPI.Framework
|
|||
IClickableMenu menu = Game1.activeClickableMenu;
|
||||
if (menu != null)
|
||||
{
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
events.Rendering.RaiseEmpty();
|
||||
try
|
||||
{
|
||||
|
@ -304,7 +304,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
if (Game1.overlayMenu != null)
|
||||
{
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
Game1.overlayMenu.draw(Game1.spriteBatch);
|
||||
Game1.spriteBatch.End();
|
||||
}
|
||||
|
@ -315,7 +315,7 @@ namespace StardewModdingAPI.Framework
|
|||
if (Game1.activeClickableMenu != null && Game1.options.showMenuBackground && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet() && !this.takingMapScreenshot)
|
||||
{
|
||||
Game1.PushUIMode();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
|
||||
events.Rendering.RaiseEmpty();
|
||||
IClickableMenu curMenu = null;
|
||||
|
@ -346,11 +346,11 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
if (Game1.gameMode == 11)
|
||||
{
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
events.Rendering.RaiseEmpty();
|
||||
Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3685"), new Vector2(16f, 16f), Microsoft.Xna.Framework.Color.HotPink);
|
||||
Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3686"), new Vector2(16f, 32f), new Microsoft.Xna.Framework.Color(0, 255, 0));
|
||||
Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.parseText(Game1.errorMessage, Game1.dialogueFont, Game1.graphics.GraphicsDevice.Viewport.Width), new Vector2(16f, 48f), Microsoft.Xna.Framework.Color.White);
|
||||
Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3685"), new Vector2(16f, 16f), Color.HotPink);
|
||||
Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3686"), new Vector2(16f, 32f), new Color(0, 255, 0));
|
||||
Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.parseText(Game1.errorMessage, Game1.dialogueFont, Game1.graphics.GraphicsDevice.Viewport.Width), new Vector2(16f, 48f), Color.White);
|
||||
events.Rendered.RaiseEmpty();
|
||||
Game1.spriteBatch.End();
|
||||
return;
|
||||
|
@ -368,8 +368,8 @@ namespace StardewModdingAPI.Framework
|
|||
if (Game1.globalFade && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause))
|
||||
{
|
||||
Game1.PushUIMode();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.Black * ((Game1.gameMode == 0) ? (1f - Game1.fadeToBlackAlpha) : Game1.fadeToBlackAlpha));
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * ((Game1.gameMode == 0) ? (1f - Game1.fadeToBlackAlpha) : Game1.fadeToBlackAlpha));
|
||||
Game1.spriteBatch.End();
|
||||
Game1.PopUIMode();
|
||||
}
|
||||
|
@ -388,7 +388,7 @@ namespace StardewModdingAPI.Framework
|
|||
if (Game1.showingEndOfNightStuff)
|
||||
{
|
||||
Game1.PushUIMode();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
events.Rendering.RaiseEmpty();
|
||||
if (Game1.activeClickableMenu != null)
|
||||
{
|
||||
|
@ -417,16 +417,16 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
Game1.PushUIMode();
|
||||
base.GraphicsDevice.Clear(Game1.bgColor);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
events.Rendering.RaiseEmpty();
|
||||
string addOn = "";
|
||||
for (int i = 0; (double)i < gameTime.TotalGameTime.TotalMilliseconds % 999.0 / 333.0; i++)
|
||||
{
|
||||
addOn += ".";
|
||||
}
|
||||
string str = Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3688");
|
||||
string msg = str + addOn;
|
||||
string largestMessage = str + "... ";
|
||||
string text = Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3688");
|
||||
string msg = text + addOn;
|
||||
string largestMessage = text + "... ";
|
||||
int msgw = SpriteText.getWidthOfString(largestMessage);
|
||||
int msgh = 64;
|
||||
int msgx = 64;
|
||||
|
@ -442,7 +442,7 @@ namespace StardewModdingAPI.Framework
|
|||
byte batchOpens = 0; // used for rendering event
|
||||
if (Game1.gameMode == 0)
|
||||
{
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
if (++batchOpens == 1)
|
||||
events.Rendering.RaiseEmpty();
|
||||
}
|
||||
|
@ -456,7 +456,7 @@ namespace StardewModdingAPI.Framework
|
|||
if (Game1.drawLighting)
|
||||
{
|
||||
Game1.SetRenderTarget(Game1.lightmap);
|
||||
base.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.White * 0f);
|
||||
base.GraphicsDevice.Clear(Color.White * 0f);
|
||||
Matrix lighting_matrix = Matrix.Identity;
|
||||
if (this.useUnscaledLighting)
|
||||
{
|
||||
|
@ -465,13 +465,13 @@ namespace StardewModdingAPI.Framework
|
|||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, null, lighting_matrix);
|
||||
if (++batchOpens == 1)
|
||||
events.Rendering.RaiseEmpty();
|
||||
Microsoft.Xna.Framework.Color lighting = (Game1.currentLocation.Name.StartsWith("UndergroundMine") && Game1.currentLocation is MineShaft) ? (Game1.currentLocation as MineShaft).getLightingColor(gameTime) : ((Game1.ambientLight.Equals(Microsoft.Xna.Framework.Color.White) || (Game1.IsRainingHere() && (bool)Game1.currentLocation.isOutdoors)) ? Game1.outdoorLight : Game1.ambientLight);
|
||||
Color lighting = ((Game1.currentLocation.Name.StartsWith("UndergroundMine") && Game1.currentLocation is MineShaft) ? (Game1.currentLocation as MineShaft).getLightingColor(gameTime) : ((Game1.ambientLight.Equals(Color.White) || (Game1.IsRainingHere() && (bool)Game1.currentLocation.isOutdoors)) ? Game1.outdoorLight : Game1.ambientLight));
|
||||
float light_multiplier = 1f;
|
||||
if (Game1.player.hasBuff(26))
|
||||
{
|
||||
if (lighting == Microsoft.Xna.Framework.Color.White)
|
||||
if (lighting == Color.White)
|
||||
{
|
||||
lighting = new Microsoft.Xna.Framework.Color(0.75f, 0.75f, 0.75f);
|
||||
lighting = new Color(0.75f, 0.75f, 0.75f);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -504,12 +504,8 @@ namespace StardewModdingAPI.Framework
|
|||
Game1.spriteBatch.End();
|
||||
Game1.SetRenderTarget(target_screen);
|
||||
}
|
||||
if (Game1.bloomDay && Game1.bloom != null)
|
||||
{
|
||||
Game1.bloom.BeginDraw();
|
||||
}
|
||||
base.GraphicsDevice.Clear(Game1.bgColor);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
if (++batchOpens == 1)
|
||||
events.Rendering.RaiseEmpty();
|
||||
events.RenderingWorld.RaiseEmpty();
|
||||
|
@ -522,10 +518,10 @@ namespace StardewModdingAPI.Framework
|
|||
Game1.currentLocation.Map.GetLayer("Back").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, wrapAround: false, 4);
|
||||
Game1.currentLocation.drawWater(Game1.spriteBatch);
|
||||
Game1.spriteBatch.End();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
Game1.currentLocation.drawFloorDecorations(Game1.spriteBatch);
|
||||
Game1.spriteBatch.End();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
this._farmerShadows.Clear();
|
||||
if (Game1.currentLocation.currentEvent != null && !Game1.currentLocation.currentEvent.isFestival && Game1.currentLocation.currentEvent.farmerActors.Count > 0)
|
||||
{
|
||||
|
@ -555,17 +551,17 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
if (!k.swimming && !k.HideShadow && !k.IsInvisible && !this.checkCharacterTilesForShadowDrawFlag(k))
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, k.GetShadowOffset() + k.Position + new Vector2((float)(k.GetSpriteWidthForPositioning() * 4) / 2f, k.GetBoundingBox().Height + ((!k.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)k.yJumpOffset / 40f) * (float)k.scale), SpriteEffects.None, Math.Max(0f, (float)k.getStandingY() / 10000f) - 1E-06f);
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, k.GetShadowOffset() + k.Position + new Vector2((float)(k.GetSpriteWidthForPositioning() * 4) / 2f, k.GetBoundingBox().Height + ((!k.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)k.yJumpOffset / 40f) * (float)k.scale), SpriteEffects.None, Math.Max(0f, (float)k.getStandingY() / 10000f) - 1E-06f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (NPC m in Game1.CurrentEvent.actors)
|
||||
foreach (NPC l in Game1.CurrentEvent.actors)
|
||||
{
|
||||
if ((Game1.CurrentEvent == null || !Game1.CurrentEvent.ShouldHideCharacter(m)) && !m.swimming && !m.HideShadow && !this.checkCharacterTilesForShadowDrawFlag(m))
|
||||
if ((Game1.CurrentEvent == null || !Game1.CurrentEvent.ShouldHideCharacter(l)) && !l.swimming && !l.HideShadow && !this.checkCharacterTilesForShadowDrawFlag(l))
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, m.GetShadowOffset() + m.Position + new Vector2((float)(m.GetSpriteWidthForPositioning() * 4) / 2f, m.GetBoundingBox().Height + ((!m.IsMonster) ? ((m.Sprite.SpriteHeight <= 16) ? (-4) : 12) : 0))), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, 4f + (float)m.yJumpOffset / 40f) * (float)m.scale, SpriteEffects.None, Math.Max(0f, (float)m.getStandingY() / 10000f) - 1E-06f);
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, l.GetShadowOffset() + l.Position + new Vector2((float)(l.GetSpriteWidthForPositioning() * 4) / 2f, l.GetBoundingBox().Height + ((!l.IsMonster) ? ((l.Sprite.SpriteHeight <= 16) ? (-4) : 12) : 0))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, 4f + (float)l.yJumpOffset / 40f) * (float)l.scale, SpriteEffects.None, Math.Max(0f, (float)l.getStandingY() / 10000f) - 1E-06f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -573,7 +569,7 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
if (!Game1.multiplayer.isDisconnecting(f3.UniqueMultiplayerID) && !f3.swimming && !f3.isRidingHorse() && !f3.IsSitting() && (Game1.currentLocation == null || !this.checkCharacterTilesForShadowDrawFlag(f3)))
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(f3.GetShadowOffset() + f3.Position + new Vector2(32f, 24f)), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 4f - (((f3.running || f3.UsingTool) && f3.FarmerSprite.currentAnimationIndex > 1) ? ((float)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[f3.FarmerSprite.CurrentFrame]) * 0.5f) : 0f), SpriteEffects.None, 0f);
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(f3.GetShadowOffset() + f3.Position + new Vector2(32f, 24f)), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 4f - (((f3.running || f3.UsingTool) && f3.FarmerSprite.currentAnimationIndex > 1) ? ((float)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[f3.FarmerSprite.CurrentFrame]) * 0.5f) : 0f), SpriteEffects.None, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -581,26 +577,26 @@ namespace StardewModdingAPI.Framework
|
|||
building_layer.Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, wrapAround: false, 4);
|
||||
Game1.mapDisplayDevice.EndScene();
|
||||
Game1.spriteBatch.End();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
if (!Game1.currentLocation.shouldHideCharacters())
|
||||
{
|
||||
if (Game1.CurrentEvent == null)
|
||||
{
|
||||
foreach (NPC n in Game1.currentLocation.characters)
|
||||
foreach (NPC m in Game1.currentLocation.characters)
|
||||
{
|
||||
if (!n.swimming && !n.HideShadow && !n.isInvisible && this.checkCharacterTilesForShadowDrawFlag(n))
|
||||
if (!m.swimming && !m.HideShadow && !m.isInvisible && this.checkCharacterTilesForShadowDrawFlag(m))
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, n.GetShadowOffset() + n.Position + new Vector2((float)(n.GetSpriteWidthForPositioning() * 4) / 2f, n.GetBoundingBox().Height + ((!n.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)n.yJumpOffset / 40f) * (float)n.scale), SpriteEffects.None, Math.Max(0f, (float)n.getStandingY() / 10000f) - 1E-06f);
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, m.GetShadowOffset() + m.Position + new Vector2((float)(m.GetSpriteWidthForPositioning() * 4) / 2f, m.GetBoundingBox().Height + ((!m.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)m.yJumpOffset / 40f) * (float)m.scale), SpriteEffects.None, Math.Max(0f, (float)m.getStandingY() / 10000f) - 1E-06f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (NPC n2 in Game1.CurrentEvent.actors)
|
||||
foreach (NPC n in Game1.CurrentEvent.actors)
|
||||
{
|
||||
if ((Game1.CurrentEvent == null || !Game1.CurrentEvent.ShouldHideCharacter(n2)) && !n2.swimming && !n2.HideShadow && this.checkCharacterTilesForShadowDrawFlag(n2))
|
||||
if ((Game1.CurrentEvent == null || !Game1.CurrentEvent.ShouldHideCharacter(n)) && !n.swimming && !n.HideShadow && this.checkCharacterTilesForShadowDrawFlag(n))
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, n2.GetShadowOffset() + n2.Position + new Vector2((float)(n2.GetSpriteWidthForPositioning() * 4) / 2f, n2.GetBoundingBox().Height + ((!n2.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)n2.yJumpOffset / 40f) * (float)n2.scale), SpriteEffects.None, Math.Max(0f, (float)n2.getStandingY() / 10000f) - 1E-06f);
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, n.GetShadowOffset() + n.Position + new Vector2((float)(n.GetSpriteWidthForPositioning() * 4) / 2f, n.GetBoundingBox().Height + ((!n.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)n.yJumpOffset / 40f) * (float)n.scale), SpriteEffects.None, Math.Max(0f, (float)n.getStandingY() / 10000f) - 1E-06f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -609,7 +605,7 @@ namespace StardewModdingAPI.Framework
|
|||
float draw_layer = Math.Max(0.0001f, f4.getDrawLayer() + 0.00011f) - 0.0001f;
|
||||
if (!f4.swimming && !f4.isRidingHorse() && !f4.IsSitting() && Game1.currentLocation != null && this.checkCharacterTilesForShadowDrawFlag(f4))
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(f4.GetShadowOffset() + f4.Position + new Vector2(32f, 24f)), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 4f - (((f4.running || f4.UsingTool) && f4.FarmerSprite.currentAnimationIndex > 1) ? ((float)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[f4.FarmerSprite.CurrentFrame]) * 0.5f) : 0f), SpriteEffects.None, draw_layer);
|
||||
Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(f4.GetShadowOffset() + f4.Position + new Vector2(32f, 24f)), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 4f - (((f4.running || f4.UsingTool) && f4.FarmerSprite.currentAnimationIndex > 1) ? ((float)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[f4.FarmerSprite.CurrentFrame]) * 0.5f) : 0f), SpriteEffects.None, draw_layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -619,7 +615,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
if (Game1.player.currentUpgrade != null && Game1.player.currentUpgrade.daysLeftTillUpgradeDone <= 3 && Game1.currentLocation.Name.Equals("Farm"))
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.player.currentUpgrade.workerTexture, Game1.GlobalToLocal(Game1.viewport, Game1.player.currentUpgrade.positionOfCarpenter), Game1.player.currentUpgrade.getSourceRectangle(), Microsoft.Xna.Framework.Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, (Game1.player.currentUpgrade.positionOfCarpenter.Y + 48f) / 10000f);
|
||||
Game1.spriteBatch.Draw(Game1.player.currentUpgrade.workerTexture, Game1.GlobalToLocal(Game1.viewport, Game1.player.currentUpgrade.positionOfCarpenter), Game1.player.currentUpgrade.getSourceRectangle(), Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, (Game1.player.currentUpgrade.positionOfCarpenter.Y + 48f) / 10000f);
|
||||
}
|
||||
Game1.currentLocation.draw(Game1.spriteBatch);
|
||||
foreach (Vector2 tile_position in Game1.crabPotOverlayTiles.Keys)
|
||||
|
@ -646,14 +642,14 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
if (Game1.tvStation >= 0)
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.tvStationTexture, Game1.GlobalToLocal(Game1.viewport, new Vector2(400f, 160f)), new Microsoft.Xna.Framework.Rectangle(Game1.tvStation * 24, 0, 24, 15), Microsoft.Xna.Framework.Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, 1E-08f);
|
||||
Game1.spriteBatch.Draw(Game1.tvStationTexture, Game1.GlobalToLocal(Game1.viewport, new Vector2(400f, 160f)), new Microsoft.Xna.Framework.Rectangle(Game1.tvStation * 24, 0, 24, 15), Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, 1E-08f);
|
||||
}
|
||||
if (Game1.panMode)
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle((int)Math.Floor((double)(Game1.getOldMouseX() + Game1.viewport.X) / 64.0) * 64 - Game1.viewport.X, (int)Math.Floor((double)(Game1.getOldMouseY() + Game1.viewport.Y) / 64.0) * 64 - Game1.viewport.Y, 64, 64), Microsoft.Xna.Framework.Color.Lime * 0.75f);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle((int)Math.Floor((double)(Game1.getOldMouseX() + Game1.viewport.X) / 64.0) * 64 - Game1.viewport.X, (int)Math.Floor((double)(Game1.getOldMouseY() + Game1.viewport.Y) / 64.0) * 64 - Game1.viewport.Y, 64, 64), Color.Lime * 0.75f);
|
||||
foreach (Warp w in Game1.currentLocation.warps)
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(w.X * 64 - Game1.viewport.X, w.Y * 64 - Game1.viewport.Y, 64, 64), Microsoft.Xna.Framework.Color.Red * 0.75f);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(w.X * 64 - Game1.viewport.X, w.Y * 64 - Game1.viewport.Y, 64, 64), Color.Red * 0.75f);
|
||||
}
|
||||
}
|
||||
Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
|
||||
|
@ -661,7 +657,7 @@ namespace StardewModdingAPI.Framework
|
|||
Game1.mapDisplayDevice.EndScene();
|
||||
Game1.currentLocation.drawAboveFrontLayer(Game1.spriteBatch);
|
||||
Game1.spriteBatch.End();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
if (Game1.currentLocation.Map.GetLayer("AlwaysFront") != null)
|
||||
{
|
||||
Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
|
||||
|
@ -670,7 +666,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
if (Game1.toolHold > 400f && Game1.player.CurrentTool.UpgradeLevel >= 1 && Game1.player.canReleaseTool)
|
||||
{
|
||||
Microsoft.Xna.Framework.Color barColor = Microsoft.Xna.Framework.Color.White;
|
||||
Color barColor = Color.White;
|
||||
switch ((int)(Game1.toolHold / 600f) + 2)
|
||||
{
|
||||
case 1:
|
||||
|
@ -686,7 +682,7 @@ namespace StardewModdingAPI.Framework
|
|||
barColor = Tool.iridiumColor;
|
||||
break;
|
||||
}
|
||||
Game1.spriteBatch.Draw(Game1.littleEffect, new Microsoft.Xna.Framework.Rectangle((int)Game1.player.getLocalPosition(Game1.viewport).X - 2, (int)Game1.player.getLocalPosition(Game1.viewport).Y - ((!Game1.player.CurrentTool.Name.Equals("Watering Can")) ? 64 : 0) - 2, (int)(Game1.toolHold % 600f * 0.08f) + 4, 12), Microsoft.Xna.Framework.Color.Black);
|
||||
Game1.spriteBatch.Draw(Game1.littleEffect, new Microsoft.Xna.Framework.Rectangle((int)Game1.player.getLocalPosition(Game1.viewport).X - 2, (int)Game1.player.getLocalPosition(Game1.viewport).Y - ((!Game1.player.CurrentTool.Name.Equals("Watering Can")) ? 64 : 0) - 2, (int)(Game1.toolHold % 600f * 0.08f) + 4, 12), Color.Black);
|
||||
Game1.spriteBatch.Draw(Game1.littleEffect, new Microsoft.Xna.Framework.Rectangle((int)Game1.player.getLocalPosition(Game1.viewport).X, (int)Game1.player.getLocalPosition(Game1.viewport).Y - ((!Game1.player.CurrentTool.Name.Equals("Watering Can")) ? 64 : 0), (int)(Game1.toolHold % 600f * 0.08f), 8), barColor);
|
||||
}
|
||||
if (!Game1.IsFakedBlackScreen())
|
||||
|
@ -699,7 +695,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
if (Game1.currentLocation.LightLevel > 0f && Game1.timeOfDay < 2000)
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.Black * Game1.currentLocation.LightLevel);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * Game1.currentLocation.LightLevel);
|
||||
}
|
||||
if (Game1.screenGlow)
|
||||
{
|
||||
|
@ -711,51 +707,51 @@ namespace StardewModdingAPI.Framework
|
|||
Game1.player.CurrentTool.draw(Game1.spriteBatch);
|
||||
}
|
||||
Game1.spriteBatch.End();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
if (Game1.eventUp && Game1.currentLocation.currentEvent != null)
|
||||
{
|
||||
foreach (NPC l in Game1.currentLocation.currentEvent.actors)
|
||||
foreach (NPC n2 in Game1.currentLocation.currentEvent.actors)
|
||||
{
|
||||
if (l.isEmoting)
|
||||
if (n2.isEmoting)
|
||||
{
|
||||
Vector2 emotePosition = l.getLocalPosition(Game1.viewport);
|
||||
if (l.NeedsBirdieEmoteHack())
|
||||
Vector2 emotePosition = n2.getLocalPosition(Game1.viewport);
|
||||
if (n2.NeedsBirdieEmoteHack())
|
||||
{
|
||||
emotePosition.X += 64f;
|
||||
}
|
||||
emotePosition.Y -= 140f;
|
||||
if (l.Age == 2)
|
||||
if (n2.Age == 2)
|
||||
{
|
||||
emotePosition.Y += 32f;
|
||||
}
|
||||
else if (l.Gender == 1)
|
||||
else if (n2.Gender == 1)
|
||||
{
|
||||
emotePosition.Y += 10f;
|
||||
}
|
||||
Game1.spriteBatch.Draw(Game1.emoteSpriteSheet, emotePosition, new Microsoft.Xna.Framework.Rectangle(l.CurrentEmoteIndex * 16 % Game1.emoteSpriteSheet.Width, l.CurrentEmoteIndex * 16 / Game1.emoteSpriteSheet.Width * 16, 16, 16), Microsoft.Xna.Framework.Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, (float)l.getStandingY() / 10000f);
|
||||
Game1.spriteBatch.Draw(Game1.emoteSpriteSheet, emotePosition, new Microsoft.Xna.Framework.Rectangle(n2.CurrentEmoteIndex * 16 % Game1.emoteSpriteSheet.Width, n2.CurrentEmoteIndex * 16 / Game1.emoteSpriteSheet.Width * 16, 16, 16), Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, (float)n2.getStandingY() / 10000f);
|
||||
}
|
||||
}
|
||||
}
|
||||
Game1.spriteBatch.End();
|
||||
if (Game1.drawLighting && !Game1.IsFakedBlackScreen())
|
||||
{
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, this.lightingBlend, SamplerState.LinearClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, this.lightingBlend, SamplerState.LinearClamp);
|
||||
Viewport vp = base.GraphicsDevice.Viewport;
|
||||
vp.Bounds = (target_screen?.Bounds ?? base.GraphicsDevice.PresentationParameters.Bounds);
|
||||
vp.Bounds = target_screen?.Bounds ?? base.GraphicsDevice.PresentationParameters.Bounds;
|
||||
base.GraphicsDevice.Viewport = vp;
|
||||
float render_zoom = Game1.options.lightingQuality / 2;
|
||||
if (this.useUnscaledLighting)
|
||||
{
|
||||
render_zoom /= Game1.options.zoomLevel;
|
||||
}
|
||||
Game1.spriteBatch.Draw(Game1.lightmap, Vector2.Zero, Game1.lightmap.Bounds, Microsoft.Xna.Framework.Color.White, 0f, Vector2.Zero, render_zoom, SpriteEffects.None, 1f);
|
||||
Game1.spriteBatch.Draw(Game1.lightmap, Vector2.Zero, Game1.lightmap.Bounds, Color.White, 0f, Vector2.Zero, render_zoom, SpriteEffects.None, 1f);
|
||||
if (Game1.IsRainingHere() && (bool)Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert))
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.staminaRect, vp.Bounds, Microsoft.Xna.Framework.Color.OrangeRed * 0.45f);
|
||||
Game1.spriteBatch.Draw(Game1.staminaRect, vp.Bounds, Color.OrangeRed * 0.45f);
|
||||
}
|
||||
Game1.spriteBatch.End();
|
||||
}
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
events.RenderedWorld.RaiseEmpty();
|
||||
if (Game1.drawGrid)
|
||||
{
|
||||
|
@ -763,11 +759,11 @@ namespace StardewModdingAPI.Framework
|
|||
float startingY = -Game1.viewport.Y % 64;
|
||||
for (int x = startingX; x < Game1.graphics.GraphicsDevice.Viewport.Width; x += 64)
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(x, (int)startingY, 1, Game1.graphics.GraphicsDevice.Viewport.Height), Microsoft.Xna.Framework.Color.Red * 0.5f);
|
||||
Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(x, (int)startingY, 1, Game1.graphics.GraphicsDevice.Viewport.Height), Color.Red * 0.5f);
|
||||
}
|
||||
for (float y = startingY; y < (float)Game1.graphics.GraphicsDevice.Viewport.Height; y += 64f)
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(startingX, (int)y, Game1.graphics.GraphicsDevice.Viewport.Width, 1), Microsoft.Xna.Framework.Color.Red * 0.5f);
|
||||
Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(startingX, (int)y, Game1.graphics.GraphicsDevice.Viewport.Width, 1), Color.Red * 0.5f);
|
||||
}
|
||||
}
|
||||
if (Game1.ShouldShowOnscreenUsernames() && Game1.currentLocation != null)
|
||||
|
@ -780,14 +776,14 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
if (!Game1.eventUp && Game1.farmEvent == null && Game1.currentBillboard == 0 && Game1.gameMode == 3 && !this.takingMapScreenshot && Game1.isOutdoorMapSmallerThanViewport())
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, -Math.Min(Game1.viewport.X, GameRunner.MaxTextureSize), Game1.graphics.GraphicsDevice.Viewport.Height), Microsoft.Xna.Framework.Color.Black);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64, 0, Math.Min(GameRunner.MaxTextureSize, Game1.graphics.GraphicsDevice.Viewport.Width - (-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64)), Game1.graphics.GraphicsDevice.Viewport.Height), Microsoft.Xna.Framework.Color.Black);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, Game1.graphics.GraphicsDevice.Viewport.Width, -Math.Min(Game1.viewport.Y, GameRunner.MaxTextureSize)), Microsoft.Xna.Framework.Color.Black);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, -Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64, Game1.graphics.GraphicsDevice.Viewport.Width, Math.Min(GameRunner.MaxTextureSize, Game1.graphics.GraphicsDevice.Viewport.Height - (-Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64))), Microsoft.Xna.Framework.Color.Black);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, -Math.Min(Game1.viewport.X, GameRunner.MaxTextureSize), Game1.graphics.GraphicsDevice.Viewport.Height), Color.Black);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64, 0, Math.Min(GameRunner.MaxTextureSize, Game1.graphics.GraphicsDevice.Viewport.Width - (-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64)), Game1.graphics.GraphicsDevice.Viewport.Height), Color.Black);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, Game1.graphics.GraphicsDevice.Viewport.Width, -Math.Min(Game1.viewport.Y, GameRunner.MaxTextureSize)), Color.Black);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, -Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64, Game1.graphics.GraphicsDevice.Viewport.Width, Math.Min(GameRunner.MaxTextureSize, Game1.graphics.GraphicsDevice.Viewport.Height - (-Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64))), Color.Black);
|
||||
}
|
||||
Game1.spriteBatch.End();
|
||||
Game1.PushUIMode();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
if ((Game1.displayHUD || Game1.eventUp) && Game1.currentBillboard == 0 && Game1.gameMode == 3 && !Game1.freezeControls && !Game1.panMode && !Game1.HostPaused && !this.takingMapScreenshot)
|
||||
{
|
||||
events.RenderingHud.RaiseEmpty();
|
||||
|
@ -807,13 +803,13 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
Game1.spriteBatch.End();
|
||||
Game1.PopUIMode();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
}
|
||||
if (Game1.farmEvent != null)
|
||||
{
|
||||
Game1.farmEvent.draw(Game1.spriteBatch);
|
||||
Game1.spriteBatch.End();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
}
|
||||
Game1.PushUIMode();
|
||||
if (Game1.dialogueUp && !Game1.nameSelectUp && !Game1.messagePause && (Game1.activeClickableMenu == null || !(Game1.activeClickableMenu is DialogueBox)) && !this.takingMapScreenshot)
|
||||
|
@ -822,35 +818,35 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
if (Game1.progressBar && !this.takingMapScreenshot)
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle((Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Bottom - 128, Game1.dialogueWidth, 32), Microsoft.Xna.Framework.Color.LightGray);
|
||||
Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle((Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Bottom - 128, (int)(Game1.pauseAccumulator / Game1.pauseTime * (float)Game1.dialogueWidth), 32), Microsoft.Xna.Framework.Color.DimGray);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle((Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Bottom - 128, Game1.dialogueWidth, 32), Color.LightGray);
|
||||
Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle((Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Bottom - 128, (int)(Game1.pauseAccumulator / Game1.pauseTime * (float)Game1.dialogueWidth), 32), Color.DimGray);
|
||||
}
|
||||
Game1.spriteBatch.End();
|
||||
Game1.PopUIMode();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
if (Game1.eventUp && Game1.currentLocation != null && Game1.currentLocation.currentEvent != null)
|
||||
{
|
||||
Game1.currentLocation.currentEvent.drawAfterMap(Game1.spriteBatch);
|
||||
}
|
||||
if (!Game1.IsFakedBlackScreen() && Game1.IsRainingHere() && Game1.currentLocation != null && (bool)Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert))
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.staminaRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.Blue * 0.2f);
|
||||
Game1.spriteBatch.Draw(Game1.staminaRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Blue * 0.2f);
|
||||
}
|
||||
if ((Game1.fadeToBlack || Game1.globalFade) && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause) && !this.takingMapScreenshot)
|
||||
{
|
||||
Game1.spriteBatch.End();
|
||||
Game1.PushUIMode();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.Black * ((Game1.gameMode == 0) ? (1f - Game1.fadeToBlackAlpha) : Game1.fadeToBlackAlpha));
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * ((Game1.gameMode == 0) ? (1f - Game1.fadeToBlackAlpha) : Game1.fadeToBlackAlpha));
|
||||
Game1.spriteBatch.End();
|
||||
Game1.PopUIMode();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
}
|
||||
else if (Game1.flashAlpha > 0f && !this.takingMapScreenshot)
|
||||
{
|
||||
if (Game1.options.screenFlash)
|
||||
{
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.White * Math.Min(1f, Game1.flashAlpha));
|
||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.White * Math.Min(1f, Game1.flashAlpha));
|
||||
}
|
||||
Game1.flashAlpha -= 0.1f;
|
||||
}
|
||||
|
@ -866,14 +862,14 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
Game1.spriteBatch.End();
|
||||
Game1.PushUIMode();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
foreach (TemporaryAnimatedSprite uiOverlayTempSprite in Game1.uiOverlayTempSprites)
|
||||
{
|
||||
uiOverlayTempSprite.draw(Game1.spriteBatch, localPosition: true);
|
||||
}
|
||||
Game1.spriteBatch.End();
|
||||
Game1.PopUIMode();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
}
|
||||
if (Game1.debugMode)
|
||||
{
|
||||
|
@ -905,14 +901,14 @@ namespace StardewModdingAPI.Framework
|
|||
sb.Append(Game1.getMouseY() + Game1.viewport.Y);
|
||||
sb.Append(" debugOutput: ");
|
||||
sb.Append(Game1.debugOutput);
|
||||
Game1.spriteBatch.DrawString(Game1.smallFont, sb, new Vector2(base.GraphicsDevice.Viewport.GetTitleSafeArea().X, base.GraphicsDevice.Viewport.GetTitleSafeArea().Y + Game1.smallFont.LineSpacing * 8), Microsoft.Xna.Framework.Color.Red, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9999999f);
|
||||
Game1.spriteBatch.DrawString(Game1.smallFont, sb, new Vector2(base.GraphicsDevice.Viewport.GetTitleSafeArea().X, base.GraphicsDevice.Viewport.GetTitleSafeArea().Y + Game1.smallFont.LineSpacing * 8), Color.Red, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9999999f);
|
||||
}
|
||||
Game1.spriteBatch.End();
|
||||
Game1.PushUIMode();
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
|
||||
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
if (Game1.showKeyHelp && !this.takingMapScreenshot)
|
||||
{
|
||||
Game1.spriteBatch.DrawString(Game1.smallFont, Game1.keyHelpString, new Vector2(64f, (float)(Game1.viewport.Height - 64 - (Game1.dialogueUp ? (192 + (Game1.isQuestion ? (Game1.questionChoices.Count * 64) : 0)) : 0)) - Game1.smallFont.MeasureString(Game1.keyHelpString).Y), Microsoft.Xna.Framework.Color.LightGray, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9999999f);
|
||||
Game1.spriteBatch.DrawString(Game1.smallFont, Game1.keyHelpString, new Vector2(64f, (float)(Game1.viewport.Height - 64 - (Game1.dialogueUp ? (192 + (Game1.isQuestion ? (Game1.questionChoices.Count * 64) : 0)) : 0)) - Game1.smallFont.MeasureString(Game1.keyHelpString).Y), Color.LightGray, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9999999f);
|
||||
}
|
||||
if (Game1.activeClickableMenu != null && !this.takingMapScreenshot)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Framework
|
||||
|
@ -12,15 +13,20 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>A callback to invoke before <see cref="Game1.newDayAfterFade"/> runs.</summary>
|
||||
private readonly Action BeforeNewDayAfterFade;
|
||||
|
||||
/// <summary>Writes messages to the console.</summary>
|
||||
private readonly IMonitor Monitor;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="beforeNewDayAfterFade">A callback to invoke before <see cref="Game1.newDayAfterFade"/> runs.</param>
|
||||
public SModHooks(Action beforeNewDayAfterFade)
|
||||
/// <param name="monitor">Writes messages to the console.</param>
|
||||
public SModHooks(Action beforeNewDayAfterFade, IMonitor monitor)
|
||||
{
|
||||
this.BeforeNewDayAfterFade = beforeNewDayAfterFade;
|
||||
this.Monitor = monitor;
|
||||
}
|
||||
|
||||
/// <summary>A hook invoked when <see cref="Game1.newDayAfterFade"/> is called.</summary>
|
||||
|
@ -30,5 +36,27 @@ namespace StardewModdingAPI.Framework
|
|||
this.BeforeNewDayAfterFade?.Invoke();
|
||||
action();
|
||||
}
|
||||
|
||||
/// <summary>Start an asynchronous task for the game.</summary>
|
||||
/// <param name="task">The task to start.</param>
|
||||
/// <param name="id">A unique key which identifies the task.</param>
|
||||
public override Task StartTask(Task task, string id)
|
||||
{
|
||||
this.Monitor.Log($"Synchronizing '{id}' task...");
|
||||
task.RunSynchronously();
|
||||
this.Monitor.Log(" task complete.");
|
||||
return task;
|
||||
}
|
||||
|
||||
/// <summary>Start an asynchronous task for the game.</summary>
|
||||
/// <param name="task">The task to start.</param>
|
||||
/// <param name="id">A unique key which identifies the task.</param>
|
||||
public override Task<T> StartTask<T>(Task<T> task, string id)
|
||||
{
|
||||
this.Monitor.Log($"Synchronizing '{id}' task...");
|
||||
task.RunSynchronously();
|
||||
this.Monitor.Log(" task complete.");
|
||||
return task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>The game framework running the game.</summary>
|
||||
public enum GameFramework
|
||||
{
|
||||
/// <summary>The XNA Framework on Windows.</summary>
|
||||
/// <summary>The XNA Framework, previously used on Windows.</summary>
|
||||
[Obsolete("Stardew Valley no longer uses XNA Framework on any supported platform.")]
|
||||
Xna,
|
||||
|
||||
/// <summary>The MonoGame framework, usually on non-Windows platforms.</summary>
|
||||
/// <summary>The MonoGame framework.</summary>
|
||||
MonoGame
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace StardewModdingAPI
|
|||
/// <param name="source">The map from which to copy.</param>
|
||||
/// <param name="sourceArea">The tile area within the source map to copy, or <c>null</c> for the entire source map size. This must be within the bounds of the <paramref name="source"/> map.</param>
|
||||
/// <param name="targetArea">The tile area within the target map to overwrite, or <c>null</c> to patch the whole map. The original content within this area will be erased. This must be within the bounds of the existing map.</param>
|
||||
void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null);
|
||||
/// <param name="patchMode">Indicates how the map should be patched.</param>
|
||||
void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMapMode patchMode = PatchMapMode.Overlay);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -226,13 +226,8 @@ namespace StardewModdingAPI.Metadata
|
|||
** Buildings
|
||||
****/
|
||||
case "buildings\\houses": // Farm
|
||||
{
|
||||
var field = reflection.GetField<Texture2D>(typeof(Farm), nameof(Farm.houseTextures));
|
||||
field.SetValue(
|
||||
this.LoadAndDisposeIfNeeded(field.GetValue(), key)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
Farm.houseTextures = this.LoadAndDisposeIfNeeded(Farm.houseTextures, key);
|
||||
return true;
|
||||
|
||||
case "buildings\\houses_paintmask": // Farm
|
||||
{
|
||||
|
@ -447,10 +442,6 @@ namespace StardewModdingAPI.Metadata
|
|||
Game1.objectSpriteSheet = content.Load<Texture2D>(key);
|
||||
return true;
|
||||
|
||||
case "maps\\walls_and_floors": // Wallpaper
|
||||
Wallpaper.wallpaperTexture = content.Load<Texture2D>(key);
|
||||
return true;
|
||||
|
||||
/****
|
||||
** Content\Minigames
|
||||
****/
|
||||
|
|
|
@ -36,9 +36,6 @@ namespace StardewModdingAPI.Metadata
|
|||
// rewrite for crossplatform compatibility
|
||||
if (rewriteMods)
|
||||
{
|
||||
if (platformChanged)
|
||||
yield return new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchFacade));
|
||||
|
||||
// rewrite for Stardew Valley 1.5
|
||||
yield return new FieldReplaceRewriter(typeof(DecoratableLocation), "furniture", typeof(GameLocation), nameof(GameLocation.furniture));
|
||||
yield return new FieldReplaceRewriter(typeof(Farm), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps));
|
||||
|
@ -48,9 +45,10 @@ namespace StardewModdingAPI.Metadata
|
|||
yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies);
|
||||
yield return new HeuristicMethodRewriter(this.ValidateReferencesToAssemblies);
|
||||
|
||||
// rewrite for 64-bit mode
|
||||
// re-enable in Stardew Valley 1.5.5
|
||||
//yield return new ArchitectureAssemblyRewriter();
|
||||
// rewrite for Stardew Valley 1.5.5
|
||||
if (platformChanged)
|
||||
yield return new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchFacade));
|
||||
yield return new ArchitectureAssemblyRewriter();
|
||||
|
||||
// detect Harmony & rewrite for SMAPI 3.12 (Harmony 1.x => 2.0 update)
|
||||
yield return new HarmonyRewriter();
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>Indicates how a map should be patched.</summary>
|
||||
public enum PatchMapMode
|
||||
{
|
||||
/// <summary>Replace matching tiles. Target tiles missing in the source area are kept as-is.</summary>
|
||||
Overlay,
|
||||
|
||||
/// <summary>Replace all tiles on layers that exist in the source map.</summary>
|
||||
ReplaceByLayer,
|
||||
|
||||
/// <summary>Replace all tiles with the source map.</summary>
|
||||
Replace
|
||||
}
|
||||
}
|
|
@ -4,8 +4,8 @@ using System.Linq;
|
|||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Toolkit.Framework;
|
||||
using StardewModdingAPI.Toolkit.Serialization.Models;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ namespace StardewModdingAPI
|
|||
/// <param name="args">The command-line arguments.</param>
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.Title = $"SMAPI {EarlyConstants.RawApiVersion} - {Console.Title}";
|
||||
Console.Title = $"SMAPI {EarlyConstants.RawApiVersion}";
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -34,9 +34,10 @@ namespace StardewModdingAPI
|
|||
Program.AssertGamePresent();
|
||||
Program.AssertGameVersion();
|
||||
Program.AssertSmapiVersions();
|
||||
Program.AssertDepsJson();
|
||||
Program.Start(args);
|
||||
}
|
||||
catch (BadImageFormatException ex) when (ex.FileName == "StardewValley" || ex.FileName == "Stardew Valley") // don't use EarlyConstants.GameAssemblyName, since we want to check both possible names
|
||||
catch (BadImageFormatException ex) when (ex.FileName == EarlyConstants.GameAssemblyName)
|
||||
{
|
||||
Console.WriteLine($"SMAPI failed to initialize because your game's {ex.FileName}.exe seems to be invalid.\nThis may be a pirated version which modified the executable in an incompatible way; if so, you can try a different download or buy a legitimate version.\n\nTechnical details:\n{ex}");
|
||||
}
|
||||
|
@ -84,22 +85,10 @@ namespace StardewModdingAPI
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// unofficial 64-bit
|
||||
if (EarlyConstants.Platform == GamePlatform.Windows)
|
||||
{
|
||||
FileInfo linuxExecutable = new FileInfo(Path.Combine(EarlyConstants.ExecutionPath, "StardewValley.exe"));
|
||||
if (linuxExecutable.Exists && LowLevelEnvironmentUtility.Is64BitAssembly(linuxExecutable.FullName))
|
||||
Program.PrintErrorAndExit("Oops! You're running Stardew Valley in unofficial 64-bit mode, which is no longer supported. You can update to Stardew Valley 1.5.5 or later instead. See https://stardewvalleywiki.com/Modding:Migrate_to_64-bit_on_Windows for more info.");
|
||||
}
|
||||
|
||||
// file doesn't exist
|
||||
if (!File.Exists(Path.Combine(EarlyConstants.ExecutionPath, $"{EarlyConstants.GameAssemblyName}.exe")))
|
||||
Program.PrintErrorAndExit("Oops! SMAPI can't find the game. Make sure you're running StardewModdingAPI.exe in your game folder.");
|
||||
|
||||
// Stardew Valley 1.5.5+
|
||||
if (File.Exists(Path.Combine(EarlyConstants.ExecutionPath, "Stardew Valley.dll")))
|
||||
Program.PrintErrorAndExit("Oops! You're running Stardew Valley 1.5.5 or later, but this version of SMAPI is only compatible up to Stardew Valley 1.5.4. Please check for a newer version of SMAPI: https://smapi.io.");
|
||||
|
||||
// can't load file
|
||||
Program.PrintErrorAndExit(
|
||||
message: "Oops! SMAPI couldn't load the game executable. The technical details below may have more info.",
|
||||
|
@ -143,6 +132,20 @@ namespace StardewModdingAPI
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>Assert that SMAPI's <c>StardewModdingAPI.deps.json</c> matches <c>Stardew Valley.deps.json</c>, fixing it if necessary.</summary>
|
||||
/// <remarks>This is needed to resolve native DLLs like libSkiaSharp.</remarks>
|
||||
private static void AssertDepsJson()
|
||||
{
|
||||
string sourcePath = Path.Combine(Constants.ExecutionPath, "Stardew Valley.deps.json");
|
||||
string targetPath = Path.Combine(Constants.ExecutionPath, "StardewModdingAPI.deps.json");
|
||||
|
||||
if (!File.Exists(targetPath) || FileUtilities.GetFileHash(sourcePath) != FileUtilities.GetFileHash(targetPath))
|
||||
{
|
||||
File.Copy(sourcePath, targetPath, overwrite: true);
|
||||
Program.PrintErrorAndExit($"The '{Path.GetFileName(targetPath)}' file didn't match the game's version. SMAPI fixed it automatically, but you must restart SMAPI for the change to take effect.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Initialize SMAPI and launch the game.</summary>
|
||||
/// <param name="args">The command-line arguments.</param>
|
||||
/// <remarks>This method is separate from <see cref="Main"/> because that can't contain any references to assemblies loaded by <see cref="CurrentDomain_AssemblyResolve"/> (e.g. via <see cref="Constants"/>), or Mono will incorrectly show an assembly resolution error before assembly resolution is set up.</remarks>
|
||||
|
|
|
@ -76,8 +76,6 @@ copy all the settings, or you may cause bugs due to overridden changes in future
|
|||
|
||||
/**
|
||||
* The base URL for SMAPI's web API, used to perform update checks.
|
||||
* Note: the protocol will be changed to http:// on Linux/macOS due to OpenSSL issues with the
|
||||
* game's bundled Mono.
|
||||
*/
|
||||
"WebApiBaseUrl": "https://smapi.io/api/",
|
||||
|
||||
|
|
|
@ -3,13 +3,16 @@
|
|||
<AssemblyName>StardewModdingAPI</AssemblyName>
|
||||
<RootNamespace>StardewModdingAPI</RootNamespace>
|
||||
<Description>The modding API for Stardew Valley.</Description>
|
||||
<TargetFramework>net452</TargetFramework>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<OutputType>Exe</OutputType>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<LargeAddressAware Condition="'$(OS)' == 'Windows_NT'">true</LargeAddressAware>
|
||||
<ApplicationIcon>icon.ico</ApplicationIcon>
|
||||
|
||||
<!--copy dependency DLLs to bin folder so we can include them in installer bundle -->
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\..\build\common.targets" />
|
||||
|
@ -19,43 +22,22 @@
|
|||
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
|
||||
<PackageReference Include="MonoMod.Common" Version="21.6.21.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Platonymous.TMXTile" Version="1.5.8" />
|
||||
<PackageReference Include="Platonymous.TMXTile" Version="1.5.9" />
|
||||
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
|
||||
<PackageReference Include="System.Runtime.Caching" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="..\..\build\0Harmony.dll" Private="True" />
|
||||
<Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="False" />
|
||||
<Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="False" />
|
||||
<Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="False" />
|
||||
<Reference Include="System.Numerics" Private="True" />
|
||||
<Reference Include="System.Runtime.Caching" Private="True" />
|
||||
<Reference Include="BmFont" HintPath="$(GamePath)\BmFont.dll" Private="False" />
|
||||
<Reference Include="GalaxyCSharp" HintPath="$(GamePath)\GalaxyCSharp.dll" Private="False" />
|
||||
<Reference Include="Lidgren.Network" HintPath="$(GamePath)\Lidgren.Network.dll" Private="False" />
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
|
||||
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Windows only -->
|
||||
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
|
||||
<Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="False" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Game framework -->
|
||||
<Choose>
|
||||
<When Condition="$(DefineConstants.Contains(SMAPI_FOR_XNA))">
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<Otherwise>
|
||||
<ItemGroup>
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SMAPI.Toolkit.CoreInterfaces\SMAPI.Toolkit.CoreInterfaces.csproj" />
|
||||
<ProjectReference Include="..\SMAPI.Toolkit\SMAPI.Toolkit.csproj" />
|
||||
|
|
Loading…
Reference in New Issue