Merge branch 'develop' into stable

This commit is contained in:
Jesse Plamondon-Willard 2018-11-19 13:48:19 -05:00
commit 593723b794
No known key found for this signature in database
GPG Key ID: 7D7C8097B62033CE
226 changed files with 7638 additions and 4353 deletions

View File

@ -3,7 +3,7 @@ root: true
##########
## General formatting
## documentation: http://editorconfig.org
## documentation: https://editorconfig.org
##########
[*]
indent_style = space

View File

@ -1,5 +1,5 @@
using System.Reflection;
[assembly: AssemblyProduct("SMAPI")]
[assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyVersion("2.8.1")]
[assembly: AssemblyFileVersion("2.8.1")]

View File

@ -9,13 +9,20 @@
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/GOG Games/Stardew Valley/game</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.local/share/Steam/steamapps/common/Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.steam/steam/steamapps/common/Stardew Valley</GamePath>
<!-- Mac paths -->
<GamePath Condition="!Exists('$(GamePath)')">/Applications/Stardew Valley.app/Contents/MacOS</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS</GamePath>
<!-- Windows paths -->
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GalaxyClient\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GOG Galaxy\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\Steam\steamapps\common\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GOG Galaxy\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)') AND '$(OS)' == 'Windows_NT'">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\GOG.com\Games\1453375253', 'PATH', null, RegistryView.Registry32))</GamePath>
<GamePath Condition="!Exists('$(GamePath)') AND '$(OS)' == 'Windows_NT'">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150', 'InstallLocation', null, RegistryView.Registry64, RegistryView.Registry32))</GamePath>
@ -31,10 +38,16 @@
<!-- add game references-->
<Choose>
<When Condition="'$(MSBuildProjectName)' == 'StardewModdingAPI' OR '$(MSBuildProjectName)' == 'StardewModdingAPI.Mods.ConsoleCommands' OR '$(MSBuildProjectName)' == 'StardewModdingAPI.Mods.SaveBackup' OR '$(MSBuildProjectName)' == 'StardewModdingAPI.Tests'">
<Choose>
<When Condition="$(OS) == 'Windows_NT'">
<ItemGroup>
<!--XNA framework-->
<!-- Windows -->
<ItemGroup Condition="$(OS) == 'Windows_NT'">
<Reference Include="Stardew Valley">
<HintPath>$(GamePath)\Stardew Valley.exe</HintPath>
<Private Condition="'$(MSBuildProjectName)' != 'StardewModdingAPI.Tests'">False</Private>
</Reference>
<Reference Include="Netcode">
<HintPath>$(GamePath)\Netcode.dll</HintPath>
<Private Condition="'$(MSBuildProjectName)' != 'StardewModdingAPI.Tests'">False</Private>
</Reference>
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
<Private>False</Private>
</Reference>
@ -47,35 +60,28 @@
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- game DLLs -->
<Reference Include="Netcode">
<HintPath>$(GamePath)\Netcode.dll</HintPath>
<Private Condition="'$(MSBuildProjectName)' != 'StardewModdingAPI.Tests'">False</Private>
</Reference>
<!-- Linux/Mac -->
<ItemGroup Condition="$(OS) != 'Windows_NT'">
<Reference Include="StardewValley">
<HintPath>$(GamePath)\StardewValley.exe</HintPath>
<Private Condition="'$(MSBuildProjectName)' != 'StardewModdingAPI.Tests'">False</Private>
</Reference>
<Reference Include="xTile, Version=2.0.4.0, Culture=neutral, processorArchitecture=x86">
<HintPath>$(GamePath)\xTile.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<!-- MonoGame -->
<Reference Include="MonoGame.Framework">
<HintPath>$(GamePath)\MonoGame.Framework.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
</ItemGroup>
<!-- game DLLs -->
<Reference Include="StardewValley">
<HintPath>$(GamePath)\StardewValley.exe</HintPath>
<!-- common -->
<ItemGroup>
<Reference Include="GalaxyCSharp">
<HintPath>$(GamePath)\GalaxyCSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lidgren.Network">
<HintPath>$(GamePath)\Lidgren.Network.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="xTile">
@ -83,8 +89,6 @@
<Private>False</Private>
</Reference>
</ItemGroup>
</Otherwise>
</Choose>
</When>
</Choose>
@ -99,14 +103,13 @@
</Target>
<Target Name="CopySMAPI" Condition="'$(MSBuildProjectName)' == 'StardewModdingAPI'">
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).config.json" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).metadata.json" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\0Harmony.dll" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\0Harmony.pdb" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).config.json" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).metadata.json" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\0Harmony.dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)\smapi-internal" />
</Target>
<Target Name="CopyDefaultMods" Condition="'$(MSBuildProjectName)' == 'StardewModdingAPI.Mods.ConsoleCommands' OR '$(MSBuildProjectName)' == 'StardewModdingAPI.Mods.SaveBackup'">
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" />
@ -114,12 +117,14 @@
<Copy SourceFiles="$(TargetDir)\manifest.json" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" />
</Target>
<Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'StardewModdingAPI.Toolkit' AND '$(Configuration)' == 'Debug' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
</Target>
<Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'StardewModdingAPI.Toolkit.CoreInterfaces' AND '$(Configuration)' == 'Debug' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
</Target>
<!-- launch SMAPI on debug -->

View File

@ -10,60 +10,113 @@
<CompiledRootPath>$(SolutionDir)\..\bin\$(Configuration)</CompiledRootPath>
<CompiledSmapiPath>$(CompiledRootPath)\SMAPI</CompiledSmapiPath>
<CompiledToolkitPath>$(CompiledRootPath)\SMAPI.Toolkit\net4.5</CompiledToolkitPath>
<PackagePath>$(SolutionDir)\..\bin\Packaged</PackagePath>
<PackageInternalPath>$(PackagePath)\internal</PackageInternalPath>
<PackagePath>$(SolutionDir)\..\bin\SMAPI installer</PackagePath>
<PackageDevPath>$(SolutionDir)\..\bin\SMAPI installer for developers</PackageDevPath>
<PlatformName>windows</PlatformName>
<PlatformName Condition="$(OS) != 'Windows_NT'">unix</PlatformName>
</PropertyGroup>
<ItemGroup>
<CompiledMods Include="$(SolutionDir)\..\bin\$(Configuration)\Mods\**\*.*" />
</ItemGroup>
<!-- reset package directory -->
<RemoveDir Directories="$(PackagePath)" />
<RemoveDir Directories="$(PackageDevPath)" />
<!-- copy installer files -->
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(PackagePath)\install on Windows.exe" />
<Copy SourceFiles="$(TargetDir)\readme.txt" DestinationFiles="$(PackagePath)\README.txt" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(TargetDir)\unix-launcher.sh" DestinationFiles="$(PackageInternalPath)\Mono\StardewModdingAPI" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(TargetDir)\unix-install.sh" DestinationFiles="$(PackagePath)\install.sh" />
<Copy SourceFiles="$(TargetDir)\unix-install.sh" DestinationFiles="$(PackagePath)\install on Linux.sh" />
<Copy SourceFiles="$(TargetDir)\unix-install.sh" DestinationFiles="$(PackagePath)\install on Mac.command" />
<Copy SourceFiles="$(TargetDir)\windows-install.bat" DestinationFiles="$(PackagePath)\install on Windows.bat" />
<Copy SourceFiles="$(TargetDir)\README.txt" DestinationFiles="$(PackagePath)\README.txt" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(PackagePath)\internal\$(PlatformName)-install.exe" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(TargetDir)\windows-exe-config.xml" DestinationFiles="$(PackagePath)\internal\$(PlatformName)-install.exe.config" />
<!-- copy SMAPI files for Mono -->
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(PackageInternalPath)\Mono\install.exe" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\0Harmony.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\0Harmony.pdb" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.pdb" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.xml" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.config.json" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.metadata.json" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Numerics.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Runtime.Caching.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\steam_appid.txt" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.pdb" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.xml" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.pdb" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.xml" DestinationFolder="$(PackageInternalPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="@(CompiledMods)" DestinationFolder="$(PackageInternalPath)\Mono\Mods\%(RecursiveDir)" />
<!--copy bundle files-->
<Copy SourceFiles="$(TargetDir)\unix-launcher.sh" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI" />
<Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe" DestinationFolder="$(PackagePath)\bundle" />
<Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.pdb" DestinationFolder="$(PackagePath)\bundle" />
<Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.xml" DestinationFolder="$(PackagePath)\bundle" />
<Copy SourceFiles="$(CompiledSmapiPath)\steam_appid.txt" DestinationFolder="$(PackagePath)\bundle" />
<Copy SourceFiles="$(CompiledSmapiPath)\0Harmony.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.config.json" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.metadata.json" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.pdb" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.pdb" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="@(CompiledMods)" DestinationFolder="$(PackagePath)\bundle\Mods\%(RecursiveDir)" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(TargetDir)\windows-exe-config.xml" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI.exe.config" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Numerics.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Runtime.Caching.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<!-- copy SMAPI files for Windows -->
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\0Harmony.dll" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\0Harmony.pdb" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.pdb" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.xml" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.config.json" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.metadata.json" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\steam_appid.txt" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.dll" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.pdb" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.xml" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.pdb" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.xml" DestinationFolder="$(PackageInternalPath)\Windows" />
<Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="@(CompiledMods)" DestinationFolder="$(PackageInternalPath)\Windows\Mods\%(RecursiveDir)" />
<!-- fix Linux/Mac permissions -->
<Exec Condition="$(OS) != 'Windows_NT'" Command="chmod 755 &quot;$(PackagePath)\install on Linux.sh&quot;" />
<Exec Condition="$(OS) != 'Windows_NT'" Command="chmod 755 &quot;$(PackagePath)\install on Mac.command&quot;" />
<!-- finalise 'for developers' installer -->
<ItemGroup>
<PackageFiles Include="$(PackagePath)\**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(PackageFiles)" DestinationFolder="$(PackageDevPath)\%(RecursiveDir)" />
<ZipDirectory FromDirPath="$(PackageDevPath)\bundle" ToFilePath="$(PackageDevPath)\internal\$(PlatformName)-install.dat" />
<RemoveDir Directories="$(PackageDevPath)\bundle" />
<!-- finalise normal installer -->
<ReplaceFileText FilePath="$(PackagePath)\bundle\smapi-internal\StardewModdingAPI.config.json" Search="&quot;DeveloperMode&quot;: true" Replace="&quot;DeveloperMode&quot;: false" />
<ZipDirectory FromDirPath="$(PackagePath)\bundle" ToFilePath="$(PackagePath)\internal\$(PlatformName)-install.dat" />
<RemoveDir Directories="$(PackagePath)\bundle" />
</Target>
<!-- Create a zip file with the contents of a given folder path. Derived from https://stackoverflow.com/a/38127938/262123. -->
<UsingTask TaskName="ZipDirectory" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
<ParameterGroup>
<FromDirPath ParameterType="System.String" Required="true" />
<ToFilePath ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.IO.Compression.FileSystem" />
<Using Namespace="System.IO.Compression" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try
{
ZipFile.CreateFromDirectory(FromDirPath, ToFilePath);
return true;
}
catch(Exception ex)
{
Log.LogErrorFromException(ex);
return false;
}
]]>
</Code>
</Task>
</UsingTask>
<!-- Replace text in a file based on a regex pattern. Derived from https://stackoverflow.com/a/22571621/262123. -->
<UsingTask TaskName="ReplaceFileText" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<FilePath ParameterType="System.String" Required="true" />
<Search ParameterType="System.String" Required="true" />
<Replace ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Core" />
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Text.RegularExpressions" />
<Code Type="Fragment" Language="cs">
<![CDATA[
File.WriteAllText(
FilePath,
Regex.Replace(File.ReadAllText(FilePath), Search, Replace)
);
]]>
</Code>
</Task>
</UsingTask>
</Project>

View File

@ -12,11 +12,12 @@
<RemoveDir Directories="$(PackagePath)" />
<Copy SourceFiles="$(ProjectDir)/package.nuspec" DestinationFolder="$(PackagePath)" />
<Copy SourceFiles="$(ProjectDir)/build/smapi.targets" DestinationFiles="$(PackagePath)/build/Pathoschild.Stardew.ModBuildConfig.targets" />
<Copy SourceFiles="$(TargetDir)/StardewModdingAPI.ModBuildConfig.dll" DestinationFiles="$(PackagePath)/build/StardewModdingAPI.ModBuildConfig.dll" />
<Copy SourceFiles="$(TargetDir)/StardewModdingAPI.Toolkit.dll" DestinationFiles="$(PackagePath)/build/StardewModdingAPI.Toolkit.dll" />
<Copy SourceFiles="$(TargetDir)/StardewModdingAPI.Toolkit.CoreInterfaces.dll" DestinationFiles="$(PackagePath)/build/StardewModdingAPI.Toolkit.CoreInterfaces.dll" />
<Copy SourceFiles="$(SolutionDir)/SMAPI.ModBuildConfig.Analyzer/bin/netstandard1.3/StardewModdingAPI.ModBuildConfig.Analyzer.dll" DestinationFiles="$(PackagePath)/analyzers/dotnet/cs/StardewModdingAPI.ModBuildConfig.Analyzer.dll" />
<Copy SourceFiles="$(SolutionDir)/SMAPI.ModBuildConfig.Analyzer/tools/install.ps1" DestinationFiles="$(PackagePath)/tools/install.ps1" />
<Copy SourceFiles="$(SolutionDir)/SMAPI.ModBuildConfig.Analyzer/tools/uninstall.ps1" DestinationFiles="$(PackagePath)/tools/uninstall.ps1" />
<Copy SourceFiles="$(TargetDir)/Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)/build" />
<Copy SourceFiles="$(TargetDir)/StardewModdingAPI.ModBuildConfig.dll" DestinationFolder="$(PackagePath)/build" />
<Copy SourceFiles="$(TargetDir)/StardewModdingAPI.Toolkit.dll" DestinationFolder="$(PackagePath)/build" />
<Copy SourceFiles="$(TargetDir)/StardewModdingAPI.Toolkit.CoreInterfaces.dll" DestinationFolder="$(PackagePath)/build" />
<Copy SourceFiles="$(SolutionDir)/SMAPI.ModBuildConfig.Analyzer/bin/netstandard1.3/StardewModdingAPI.ModBuildConfig.Analyzer.dll" DestinationFolder="$(PackagePath)/analyzers/dotnet/cs" />
<Copy SourceFiles="$(SolutionDir)/SMAPI.ModBuildConfig.Analyzer/tools/install.ps1" DestinationFolder="$(PackagePath)/tools" />
<Copy SourceFiles="$(SolutionDir)/SMAPI.ModBuildConfig.Analyzer/tools/uninstall.ps1" DestinationFolder="$(PackagePath)/tools" />
</Target>
</Project>

View File

@ -1,4 +1,4 @@
**SMAPI** is an open-source modding API for [Stardew Valley](http://stardewvalley.net/) that lets
**SMAPI** is an open-source modding API for [Stardew Valley](https://stardewvalley.net/) that lets
you play the game with mods. It's safely installed alongside the game's executable, and doesn't
change any of your game files. It serves eight main purposes:

View File

@ -1,4 +1,74 @@
# Release notes
## 2.8.1
* Fixed installer error on Windows.
## 2.8
* For players:
* Reorganised SMAPI files:
* Moved most SMAPI files into a `smapi-internal` subfolder (so your game folder is less messy).
* Moved save backups into a `save-backups` subfolder (so they're easier to find).
* Simplified the installer files to avoid confusion.
* Added support for organising mods into subfolders.
* Added support for [ignoring mod folders](https://stardewvalleywiki.com/Modding:Player_Guide/Getting_Started#Install_mods).
* Update checks now work even for mods without update keys in most cases.
* SMAPI now prevents a crash caused by mods adding dialogue the game can't parse.
* SMAPI now recommends a compatible SMAPI version if you have an older game version.
* Improved various error messages to be more clear and intuitive.
* Improved compatibility with various Linux shells (thanks to lqdev!), and prefer xterm when available.
* Fixed transparency issues on Linux/Mac for some mod images.
* Fixed error when a mod manifest is corrupted.
* Fixed error when a mod adds an unnamed location.
* Fixed friendly error no longer shown when SMAPI isn't run from the game folder.
* Fixed some Windows install paths not detected.
* Fixed installer duplicating bundled mods if you moved them after the last install.
* Fixed installer allowing custom mods to be bundled with the install.
* Fixed some translation issues not shown as warnings.
* Fixed dependencies not correctly enforced if the dependency is installed but failed to load.
* Fixed some errors logged as SMAPI instead of the affected mod.
* Fixed crash log deleted immediately when you relaunch the game.
* Updated compatibility list.
* For the web UI:
* Added a [mod compatibility page](https://mods.smapi.io) and [privacy page](https://smapi.io/privacy).
* The log parser now has a separate filter for game messages.
* The log parser now shows content pack authors (thanks to danvolchek!).
* Tweaked log parser UI (thanks to danvolchek!).
* Fixed log parser instructions for Mac.
* For modders:
* Added [data API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Data) to store mod data in the save file or app data.
* Added [multiplayer API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Multiplayer) and [events](https://stardewvalleywiki.com/Modding:Modder_Guide/Apis/Events#Multiplayer_2) to send/receive messages and get connected player info.
* Added [verbose logging](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Logging#Verbose_logging) feature.
* Added `IContentPack.WriteJsonFile` method.
* Added IntelliSense documentation for the non-developers version of SMAPI.
* Added more events to the prototype `helper.Events` for SMAPI 3.0.
* Added `SkillType` enum constant.
* Improved content API:
* added support for overlaying image assets with semi-transparency;
* mods can now load PNGs even if the game is currently drawing.
* When comparing mod versions, SMAPI now assigns the lowest precedence to `-unofficial` (e.g. `1.0-beta > 1.0-unofficial`).
* Fixed content packs' `ReadJsonFile` allowing non-relative paths.
* Fixed content packs always failing to load if they declare a dependency on a SMAPI mod.
* Fixed trace logs not showing path for invalid mods.
* Fixed 'no update keys' warning not shown for mods with only invalid update keys.
* Fixed `Context.IsPlayerFree` being true while the player is mid-warp in multiplayer.
* Fixed update-check errors sometimes being overwritten with a generic message.
* Suppressed the game's 'added crickets' debug output.
* Updated dependencies (Harmony 1.0.9.1 → 1.2.0.1, Mono.Cecil 0.10 → 0.10.1).
* **Deprecations:**
* Non-string manifest versions are now deprecated and will stop working in SMAPI 3.0. Affected mods should use a string version, like `"Version": "1.0.0"`.
* `ISemanticVersion.Build` is now deprecated and will be removed in SMAPI 3.0. Affected mods should use `ISemanticVersion.PrereleaseTag` instead.
* **Breaking changes:**
* `helper.ModRegistry` now returns `IModInfo` instead of `IManifest` directly. This lets SMAPI return more metadata about mods. This doesn't affect any mods that didn't already break in Stardew Valley 1.3.32.
* Most SMAPI files have been moved into a `smapi-internal` subfolder. This won't affect compiled mod releases, but you'll need to update the build config NuGet package.
* For SMAPI developers:
* Added support for parallel stable/beta unofficial updates in update checks.
* Added a 'paranoid warnings' option which reports mods using potentially sensitive .NET APIs (like file or shell access) in the mod issues list.
* Adjusted `SaveBackup` mod to make it easier to account for custom mod subfolders in the installer.
* Installer no longer special-cases Omegasis' older `SaveBackup` mod (now named `AdvancedSaveBackup`).
* Fixed mod web API returning a concatenated name for mods with alternate names.
## 2.7
* For players:
* Updated for Stardew Valley 1.3.28.
@ -11,6 +81,7 @@
* Fixed `player_add` command not recognising return scepter.
* Fixed `player_add` command showing fish twice.
* Fixed some SMAPI logs not deleted when starting a new session.
* Updated compatibility list.
* For modders:
* Added support for `.json` data files in the content API (including Content Patcher).
@ -22,7 +93,6 @@
* All enums are now JSON-serialised by name instead of numeric value. (Previously only a few enums were serialised that way. JSON files which already have numeric enum values will still be parsed fine.)
* Fixed false compatibility error when constructing multidimensional arrays.
* Fixed `.ToSButton()` methods not being public.
* Updated compatibility list.
* For SMAPI developers:
* Dropped support for pre-SMAPI-2.6 update checks in the web API.
@ -609,7 +679,7 @@ For mod developers:
* The SMAPI log now always uses `\r\n` line endings to simplify crossplatform viewing.
* Fixed `SaveEvents.AfterLoad` being raised during the new-game intro before the player is initialised.
* Fixed SMAPI not recognising `Mod` instances that don't subclass `Mod` directly.
* Several obsolete APIs have been removed (see [deprecation guide](http://canimod.com/guides/updating-a-smapi-mod)),
* Several obsolete APIs have been removed (see [migration guides](https://stardewvalleywiki.com/Modding:Index#Migration_guides)),
and all _notice_-level deprecations have been increased to _info_.
* Removed the experimental `IConfigFile`.
@ -692,7 +762,7 @@ For players:
For developers:
* Deprecated `Version` in favour of `SemanticVersion`.
_This new implementation is [semver 2.0](http://semver.org/)-compliant, introduces `NewerThan(version)` and `OlderThan(version)` convenience methods, adds support for parsing a version string into a `SemanticVersion`, and fixes various bugs with the former implementation. This also replaces `Manifest` with `IManifest`._
_This new implementation is [semver 2.0](https://semver.org/)-compliant, introduces `NewerThan(version)` and `OlderThan(version)` convenience methods, adds support for parsing a version string into a `SemanticVersion`, and fixes various bugs with the former implementation. This also replaces `Manifest` with `IManifest`._
* Increased deprecation levels for `SObject`, `Extensions`, `LogWriter` (not `Log`), `SPlayer`, and `Mod.Entry(ModHelper)` (not `Mod.Entry(IModHelper)`).
## 1.4
@ -771,7 +841,7 @@ For mod developers:
* Added OS version to log.
* Added zoom-adjusted mouse position to mouse-changed event arguments.
* Added SMAPI code documentation.
* Switched to [semantic versioning](http://semver.org).
* Switched to [semantic versioning](https://semver.org).
* Fixed mod versions not shown correctly in the log.
* Fixed misspelled field in `manifest.json` schema.
* Fixed some events getting wrong data.

View File

@ -16,7 +16,7 @@ mods, this section isn't relevant to you; see the main README to use or create m
* [SMAPI web services](#smapi-web-services)
* [Overview](#overview)
* [Log parser](#log-parser)
* [Mods API](#mods-api)
* [Web API](#web-api)
* [Development](#development-2)
* [Local development](#local-development)
* [Deploying to Amazon Beanstalk](#deploying-to-amazon-beanstalk)
@ -29,7 +29,7 @@ Using an official SMAPI release is recommended for most users.
SMAPI uses some C# 7 code, so you'll need at least
[Visual Studio 2017](https://www.visualstudio.com/vs/community/) on Windows,
[MonoDevelop 7.0](http://www.monodevelop.com/) on Linux,
[MonoDevelop 7.0](https://www.monodevelop.com/) on Linux,
[Visual Studio 2017 for Mac](https://www.visualstudio.com/vs/visual-studio-mac/), or an equivalent
IDE to compile it. It uses build configuration derived from the
[crossplatform mod config](https://github.com/Pathoschild/Stardew.ModBuildConfig#readme) to detect
@ -44,70 +44,29 @@ executed. This doesn't work in MonoDevelop on Linux, unfortunately.
### Preparing a release
To prepare a crossplatform SMAPI release, you'll need to compile it on two platforms. See
[crossplatforming info](https://stardewvalleywiki.com/Modding:Creating_a_SMAPI_mod#Test_on_all_platforms)
[crossplatforming info](https://stardewvalleywiki.com/Modding:Modder_Guide/Test_and_Troubleshoot#Testing_on_all_platforms)
on the wiki for the first-time setup.
1. Update the version number in `GlobalAssemblyInfo.cs` and `Constants::Version`. Make sure you use a
[semantic version](http://semver.org). Recommended format:
[semantic version](https://semver.org). Recommended format:
build type | format | example
:--------- | :-------------------------------- | :------
dev build | `<version>-alpha.<timestamp>` | `2.0-alpha.20171230`
prerelease | `<version>-prerelease.<ID>` | `2.0-prerelease.2`
release | `<version>` | `2.0`
:--------- | :----------------------- | :------
dev build | `<version>-alpha.<date>` | `3.0-alpha.20171230`
prerelease | `<version>-beta.<count>` | `3.0-beta.2`
release | `<version>` | `3.0`
2. In Windows:
1. Rebuild the solution in _Release_ mode.
2. Rename `bin/Packaged` to `SMAPI <version>` (e.g. `SMAPI 2.0`).
2. Transfer the `SMAPI <version>` folder to Linux or Mac.
_This adds the installer executable and Windows files. We'll do the rest in Linux or Mac,
since we need to set Unix file permissions that Windows won't save._
1. Rebuild the solution in Release mode.
2. Copy `windows-install.*` from `bin/SMAPI installer` and `bin/SMAPI installer for developers` to
Linux/Mac.
2. In Linux or Mac:
1. Rebuild the solution in _Release_ mode.
2. Copy `bin/internal/Packaged/Mono` into the `SMAPI <version>` folder.
3. If you did everything right so far, you should have a folder like this:
```
SMAPI-2.x/
install.exe
readme.txt
internal/
Mono/
Mods/*
Mono.Cecil.dll
Newtonsoft.Json.dll
StardewModdingAPI
StardewModdingAPI.config.json
StardewModdingAPI.Internal.dll
StardewModdingAPI.metadata.json
StardewModdingAPI.exe
StardewModdingAPI.pdb
StardewModdingAPI.xml
steam_appid.txt
System.Numerics.dll
System.Runtime.Caching.dll
System.ValueTuple.dll
Windows/
Mods/*
Mono.Cecil.dll
Newtonsoft.Json.dll
StardewModdingAPI.config.json
StardewModdingAPI.Internal.dll
StardewModdingAPI.metadata.json
StardewModdingAPI.exe
StardewModdingAPI.pdb
StardewModdingAPI.xml
System.ValueTuple.dll
steam_appid.txt
```
4. Open a terminal in the `SMAPI <version>` folder and run `chmod 755 internal/Mono/StardewModdingAPI`.
5. Copy & paste the `SMAPI <version>` folder as `SMAPI <version> for developers`.
6. In the `SMAPI <version>` folder...
* edit `internal/Mono/StardewModdingAPI.config.json` and
`internal/Windows/StardewModdingAPI.config.json` to disable developer mode;
* delete `internal/Windows/StardewModdingAPI.xml`.
7. Compress the two folders into `SMAPI <version>.zip` and `SMAPI <version> for developers.zip`.
3. In Linux/Mac:
1. Rebuild the solution in Release mode.
2. Add the `windows-install.*` files to the `bin/SMAPI installer` and
`bin/SMAPI installer for developers` folders.
3. Rename the folders to `SMAPI <version> installer` and `SMAPI <version> installer for developers`.
4. Zip the two folders.
## Customisation
### Configuration file

View File

@ -8,8 +8,8 @@ namespace StardewModdingAPI.Installer.Framework
/*********
** Accessors
*********/
/// <summary>The directory containing the installer files for the current platform.</summary>
public DirectoryInfo PackageDir { get; }
/// <summary>The directory path containing the files to copy into the game folder.</summary>
public DirectoryInfo BundleDir { get; }
/// <summary>The directory containing the installed game.</summary>
public DirectoryInfo GameDir { get; }
@ -17,8 +17,8 @@ namespace StardewModdingAPI.Installer.Framework
/// <summary>The directory into which to install mods.</summary>
public DirectoryInfo ModsDir { get; }
/// <summary>The full path to the directory containing the installer files for the current platform.</summary>
public string PackagePath => this.PackageDir.FullName;
/// <summary>The full path to directory path containing the files to copy into the game folder.</summary>
public string BundlePath => this.BundleDir.FullName;
/// <summary>The full path to the directory containing the installed game.</summary>
public string GamePath => this.GameDir.FullName;
@ -26,6 +26,9 @@ namespace StardewModdingAPI.Installer.Framework
/// <summary>The full path to the directory into which to install mods.</summary>
public string ModsPath => this.ModsDir.FullName;
/// <summary>The full path to SMAPI's internal configuration file.</summary>
public string ApiConfigPath { get; }
/// <summary>The full path to the installed SMAPI executable file.</summary>
public string ExecutablePath { get; }
@ -43,12 +46,12 @@ namespace StardewModdingAPI.Installer.Framework
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="packageDir">The directory path containing the installer files for the current platform.</param>
/// <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 packageDir, DirectoryInfo gameDir, string gameExecutableName)
public InstallerPaths(DirectoryInfo bundleDir, DirectoryInfo gameDir, string gameExecutableName)
{
this.PackageDir = packageDir;
this.BundleDir = bundleDir;
this.GameDir = gameDir;
this.ModsDir = new DirectoryInfo(Path.Combine(gameDir.FullName, "Mods"));
@ -56,6 +59,7 @@ namespace StardewModdingAPI.Installer.Framework
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", "StardewModdingAPI.config.json");
}
}
}

View File

@ -1,15 +1,18 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Win32;
using StardewModdingApi.Installer.Enums;
using StardewModdingAPI.Installer.Framework;
using StardewModdingAPI.Internal;
using StardewModdingAPI.Internal.ConsoleWriting;
using StardewModdingAPI.Toolkit;
using StardewModdingAPI.Toolkit.Framework.ModScanning;
using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingApi.Installer
{
@ -19,18 +22,19 @@ namespace StardewModdingApi.Installer
/*********
** Properties
*********/
/// <summary>The name of the installer file in the package.</summary>
private readonly string InstallerFileName = "install.exe";
/// <summary>Mod files which shouldn't be deleted when deploying bundled mods (mod folder name => file names).</summary>
private readonly IDictionary<string, HashSet<string>> ProtectBundledFiles = new Dictionary<string, HashSet<string>>(StringComparer.InvariantCultureIgnoreCase)
{
["SaveBackup"] = new HashSet<string>(new[] { "backups", "config.json" }, StringComparer.InvariantCultureIgnoreCase)
};
/// <summary>The absolute path to the directory containing the files to copy into the game folder.</summary>
private readonly string BundlePath;
/// <summary>The <see cref="Environment.OSVersion"/> value that represents Windows 7.</summary>
private readonly Version Windows7Version = new Version(6, 1);
/// <summary>The mod IDs which the installer should allow as bundled mods.</summary>
private readonly string[] BundledModIDs = new[]
{
"SMAPI.SaveBackup",
"SMAPI.ConsoleCommands"
};
/// <summary>The default file paths where Stardew Valley can be installed.</summary>
/// <param name="platform">The target platform.</param>
/// <remarks>Derived from the crossplatform mod config: https://github.com/Pathoschild/Stardew.ModBuildConfig. </remarks>
@ -58,8 +62,12 @@ namespace StardewModdingApi.Installer
case Platform.Windows:
{
// Windows
yield return @"C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley";
yield return @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley";
foreach (string programFiles in new[] { @"C:\Program Files", @"C:\Program Files (x86)" })
{
yield return $@"{programFiles}\GalaxyClient\Games\Stardew Valley";
yield return $@"{programFiles}\GOG Galaxy\Games\Stardew Valley";
yield return $@"{programFiles}\Steam\steamapps\common\Stardew Valley";
}
// Windows registry
IDictionary<string, string> registryKeys = new Dictionary<string, string>
@ -93,33 +101,15 @@ namespace StardewModdingApi.Installer
{
string GetInstallPath(string path) => Path.Combine(installDir.FullName, path);
// common
yield return GetInstallPath("0Harmony.dll");
yield return GetInstallPath("0Harmony.pdb");
yield return GetInstallPath("Mono.Cecil.dll");
yield return GetInstallPath("Newtonsoft.Json.dll");
// current files
yield return GetInstallPath("libgdiplus.dylib"); // Linux/Mac only
yield return GetInstallPath("StardewModdingAPI"); // Linux/Mac only
yield return GetInstallPath("StardewModdingAPI.exe");
yield return GetInstallPath("StardewModdingAPI.config.json");
yield return GetInstallPath("StardewModdingAPI.metadata.json");
yield return GetInstallPath("StardewModdingAPI.Toolkit.dll");
yield return GetInstallPath("StardewModdingAPI.Toolkit.pdb");
yield return GetInstallPath("StardewModdingAPI.Toolkit.xml");
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.dll");
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.pdb");
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.xml");
yield return GetInstallPath("StardewModdingAPI.exe.config");
yield return GetInstallPath("StardewModdingAPI.exe.mdb"); // Linux/Mac only
yield return GetInstallPath("StardewModdingAPI.pdb"); // Windows only
yield return GetInstallPath("StardewModdingAPI.xml");
yield return GetInstallPath("System.ValueTuple.dll");
yield return GetInstallPath("steam_appid.txt");
// Linux/Mac only
yield return GetInstallPath("libgdiplus.dylib");
yield return GetInstallPath("StardewModdingAPI");
yield return GetInstallPath("StardewModdingAPI.exe.mdb");
yield return GetInstallPath("System.Numerics.dll");
yield return GetInstallPath("System.Runtime.Caching.dll");
// Windows only
yield return GetInstallPath("StardewModdingAPI.pdb");
yield return GetInstallPath("smapi-internal");
// obsolete
yield return GetInstallPath(Path.Combine("Mods", ".cache")); // 1.3-1.4
@ -127,6 +117,26 @@ namespace StardewModdingApi.Installer
yield return GetInstallPath("Mono.Cecil.Rocks.dll"); // 1.31.8
yield return GetInstallPath("StardewModdingAPI-settings.json"); // 1.0-1.4
yield return GetInstallPath("StardewModdingAPI.AssemblyRewriters.dll"); // 1.3-2.5.5
yield return GetInstallPath("0Harmony.dll"); // moved in 2.8
yield return GetInstallPath("0Harmony.pdb"); // moved in 2.8
yield return GetInstallPath("Mono.Cecil.dll"); // moved in 2.8
yield return GetInstallPath("Newtonsoft.Json.dll"); // moved in 2.8
yield return GetInstallPath("StardewModdingAPI.config.json"); // moved in 2.8
yield return GetInstallPath("StardewModdingAPI.crash.marker"); // moved in 2.8
yield return GetInstallPath("StardewModdingAPI.metadata.json"); // moved in 2.8
yield return GetInstallPath("StardewModdingAPI.update.marker"); // moved in 2.8
yield return GetInstallPath("StardewModdingAPI.Toolkit.dll"); // moved in 2.8
yield return GetInstallPath("StardewModdingAPI.Toolkit.pdb"); // moved in 2.8
yield return GetInstallPath("StardewModdingAPI.Toolkit.xml"); // moved in 2.8
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("StardewModdingAPI.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("steam_appid.txt"); // moved in 2.8
if (modsDir.Exists)
{
foreach (DirectoryInfo modDir in modsDir.EnumerateDirectories())
@ -143,8 +153,10 @@ namespace StardewModdingApi.Installer
** Public methods
*********/
/// <summary>Construct an instance.</summary>
public InteractiveInstaller()
/// <param name="bundlePath">The absolute path to the directory containing the files to copy into the game folder.</param>
public InteractiveInstaller(string bundlePath)
{
this.BundlePath = bundlePath;
this.ConsoleWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform(), MonitorColorScheme.AutoDetect);
}
@ -319,10 +331,8 @@ namespace StardewModdingApi.Installer
}
// get folders
DirectoryInfo packageDir = platform.IsMono()
? new DirectoryInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)) // installer runs from internal folder on Mono
: new DirectoryInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "internal", "Windows"));
paths = new InstallerPaths(packageDir, installDir, EnvironmentUtility.GetExecutableName(platform));
DirectoryInfo bundleDir = new DirectoryInfo(this.BundlePath);
paths = new InstallerPaths(bundleDir, installDir, EnvironmentUtility.GetExecutableName(platform));
}
Console.Clear();
@ -330,24 +340,12 @@ namespace StardewModdingApi.Installer
/*********
** Step 4: validate assumptions
*********/
{
if (!paths.PackageDir.Exists)
{
this.PrintError(platform == Platform.Windows && paths.PackagePath.Contains(Path.GetTempPath()) && paths.PackagePath.Contains(".zip")
? "The installer is missing some files. It looks like you're running the installer from inside the downloaded zip; make sure you unzip the downloaded file first, then run the installer from the unzipped folder."
: $"The 'internal/{paths.PackageDir.Name}' package folder is missing (should be at {paths.PackagePath})."
);
Console.ReadLine();
return;
}
if (!File.Exists(paths.ExecutablePath))
{
this.PrintError("The detected game install path doesn't contain a Stardew Valley executable.");
Console.ReadLine();
return;
}
}
/*********
@ -438,20 +436,18 @@ namespace StardewModdingApi.Installer
{
// copy SMAPI files to game dir
this.PrintDebug("Adding SMAPI files...");
foreach (FileInfo sourceFile in paths.PackageDir.EnumerateFiles().Where(this.ShouldCopyFile))
foreach (FileSystemInfo sourceEntry in paths.BundleDir.EnumerateFileSystemInfos().Where(this.ShouldCopy))
{
if (sourceFile.Name == this.InstallerFileName)
continue;
string targetPath = Path.Combine(paths.GameDir.FullName, sourceFile.Name);
this.InteractivelyDelete(targetPath);
sourceFile.CopyTo(targetPath);
this.InteractivelyDelete(Path.Combine(paths.GameDir.FullName, sourceEntry.Name));
this.RecursiveCopy(sourceEntry, paths.GameDir);
}
// replace mod launcher (if possible)
if (platform.IsMono())
{
this.PrintDebug("Safely replacing game launcher...");
// back up & remove current launcher
if (File.Exists(paths.UnixLauncherPath))
{
if (!File.Exists(paths.UnixBackupLauncherPath))
@ -460,7 +456,20 @@ namespace StardewModdingApi.Installer
this.InteractivelyDelete(paths.UnixLauncherPath);
}
// add new launcher
File.Move(paths.UnixSmapiLauncherPath, paths.UnixLauncherPath);
// mark file executable
// (MSBuild doesn't keep permission flags for files zipped in a build task.)
new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "chmod",
Arguments = $"755 \"{paths.UnixLauncherPath}\"",
CreateNoWindow = true
}
}.Start();
}
// create mods directory (if needed)
@ -471,60 +480,56 @@ namespace StardewModdingApi.Installer
}
// add or replace bundled mods
DirectoryInfo packagedModsDir = new DirectoryInfo(Path.Combine(paths.PackageDir.FullName, "Mods"));
if (packagedModsDir.Exists && packagedModsDir.EnumerateDirectories().Any())
DirectoryInfo bundledModsDir = new DirectoryInfo(Path.Combine(paths.BundlePath, "Mods"));
if (bundledModsDir.Exists && bundledModsDir.EnumerateDirectories().Any())
{
this.PrintDebug("Adding bundled mods...");
// special case: rename Omegasis' SaveBackup mod
ModToolkit toolkit = new ModToolkit();
ModFolder[] targetMods = toolkit.GetModFolders(paths.ModsPath).ToArray();
foreach (ModFolder sourceMod in toolkit.GetModFolders(bundledModsDir.FullName))
{
DirectoryInfo oldFolder = new DirectoryInfo(Path.Combine(paths.ModsDir.FullName, "SaveBackup"));
DirectoryInfo newFolder = new DirectoryInfo(Path.Combine(paths.ModsDir.FullName, "AdvancedSaveBackup"));
FileInfo manifest = new FileInfo(Path.Combine(oldFolder.FullName, "manifest.json"));
if (manifest.Exists && !newFolder.Exists && File.ReadLines(manifest.FullName).Any(p => p.IndexOf("Omegasis", StringComparison.InvariantCultureIgnoreCase) != -1))
// validate source mod
if (sourceMod.Manifest == null)
{
this.PrintDebug($" moving {oldFolder.Name} to {newFolder.Name}...");
this.Move(oldFolder, newFolder.FullName);
this.PrintWarning($" ignored invalid bundled mod {sourceMod.DisplayName}: {sourceMod.ManifestParseError}");
continue;
}
if (!this.BundledModIDs.Contains(sourceMod.Manifest.UniqueID))
{
this.PrintWarning($" ignored unknown '{sourceMod.DisplayName}' mod in the installer folder. To add mods, put them here instead: {paths.ModsPath}");
continue;
}
// add bundled mods
foreach (DirectoryInfo sourceDir in packagedModsDir.EnumerateDirectories())
{
this.PrintDebug($" adding {sourceDir.Name}...");
// find target folder
ModFolder targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase) == true);
DirectoryInfo defaultTargetFolder = new DirectoryInfo(Path.Combine(paths.ModsPath, sourceMod.Directory.Name));
DirectoryInfo targetFolder = targetMod?.Directory ?? defaultTargetFolder;
this.PrintDebug(targetFolder.FullName == defaultTargetFolder.FullName
? $" adding {sourceMod.Manifest.Name}..."
: $" adding {sourceMod.Manifest.Name} to {Path.Combine(paths.ModsDir.Name, PathUtilities.GetRelativePath(paths.ModsPath, targetFolder.FullName))}..."
);
// init/clear target dir
DirectoryInfo targetDir = new DirectoryInfo(Path.Combine(paths.ModsDir.FullName, sourceDir.Name));
if (targetDir.Exists)
{
this.ProtectBundledFiles.TryGetValue(targetDir.Name, out HashSet<string> protectedFiles);
foreach (FileSystemInfo entry in targetDir.EnumerateFileSystemInfos())
{
if (protectedFiles == null || !protectedFiles.Contains(entry.Name))
this.InteractivelyDelete(entry.FullName);
}
}
else
targetDir.Create();
// remove existing folder
if (targetFolder.Exists)
this.InteractivelyDelete(targetFolder.FullName);
// copy files
foreach (FileInfo sourceFile in sourceDir.EnumerateFiles().Where(this.ShouldCopyFile))
sourceFile.CopyTo(Path.Combine(targetDir.FullName, sourceFile.Name));
this.RecursiveCopy(sourceMod.Directory, paths.ModsDir, filter: this.ShouldCopy);
}
}
// set SMAPI's color scheme if defined
if (scheme != MonitorColorScheme.AutoDetect)
{
string configPath = Path.Combine(paths.GamePath, "StardewModdingAPI.config.json");
string text = File
.ReadAllText(configPath)
.ReadAllText(paths.ApiConfigPath)
.Replace(@"""ColorScheme"": ""AutoDetect""", $@"""ColorScheme"": ""{scheme}""");
File.WriteAllText(configPath, text);
}
File.WriteAllText(paths.ApiConfigPath, text);
}
// remove obsolete appdata mods
this.InteractivelyRemoveAppDataMods(paths.ModsDir, packagedModsDir);
this.InteractivelyRemoveAppDataMods(paths.ModsDir, bundledModsDir);
}
}
Console.WriteLine();
@ -690,6 +695,35 @@ namespace StardewModdingApi.Installer
}
}
/// <summary>Recursively copy a directory or file.</summary>
/// <param name="source">The file or folder to copy.</param>
/// <param name="targetFolder">The folder to copy into.</param>
/// <param name="filter">A filter which matches directories and files to copy, or <c>null</c> to match all.</param>
private void RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, Func<FileSystemInfo, bool> filter = null)
{
if (filter != null && !filter(source))
return;
if (!targetFolder.Exists)
targetFolder.Create();
switch (source)
{
case FileInfo sourceFile:
sourceFile.CopyTo(Path.Combine(targetFolder.FullName, sourceFile.Name));
break;
case DirectoryInfo sourceDir:
DirectoryInfo targetSubfolder = new DirectoryInfo(Path.Combine(targetFolder.FullName, sourceDir.Name));
foreach (var entry in sourceDir.EnumerateFileSystemInfos())
this.RecursiveCopy(entry, targetSubfolder, filter);
break;
default:
throw new NotSupportedException($"Unknown filesystem info type '{source.GetType().FullName}'.");
}
}
/// <summary>Delete a file or folder regardless of file permissions, and block until deletion completes.</summary>
/// <param name="entry">The file or folder to reset.</param>
/// <remarks>This method is mirred from <c>FileUtilities.ForceDelete</c> in the toolkit.</remarks>
@ -871,7 +905,7 @@ namespace StardewModdingApi.Installer
this.PrintDebug(" Support for mods here was dropped in SMAPI 1.0 (it was never officially supported).");
// move mods if no conflicts (else warn)
foreach (FileSystemInfo entry in modDir.EnumerateFileSystemInfos().Where(this.ShouldCopyFile))
foreach (FileSystemInfo entry in modDir.EnumerateFileSystemInfos().Where(this.ShouldCopy))
{
// get type
bool isDir = entry is DirectoryInfo;
@ -928,22 +962,26 @@ namespace StardewModdingApi.Installer
Directory.CreateDirectory(newPath);
DirectoryInfo directory = (DirectoryInfo)entry;
foreach (FileSystemInfo child in directory.EnumerateFileSystemInfos().Where(this.ShouldCopyFile))
foreach (FileSystemInfo child in directory.EnumerateFileSystemInfos().Where(this.ShouldCopy))
this.Move(child, Path.Combine(newPath, child.Name));
directory.Delete(recursive: true);
}
}
/// <summary>Get whether a file should be copied when moving a folder.</summary>
/// <param name="file">The file info.</param>
private bool ShouldCopyFile(FileSystemInfo file)
/// <summary>Get whether a file or folder should be copied from the installer files.</summary>
/// <param name="entry">The file or folder info.</param>
private bool ShouldCopy(FileSystemInfo entry)
{
// ignore Mac symlink
if (file is FileInfo && file.Name == "mcs")
return false;
switch (entry.Name)
{
case "mcs":
return false; // ignore Mac symlink
case "Mods":
return false; // Mods folder handled separately
default:
return true;
}
}
}
}

View File

@ -1,8 +1,29 @@
namespace StardewModdingApi.Installer
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using StardewModdingAPI.Internal;
using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingApi.Installer
{
/// <summary>The entry point for SMAPI's install and uninstall console app.</summary>
internal class Program
{
/*********
** Properties
*********/
/// <summary>The absolute path of the installer folder.</summary>
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute", Justification = "The assembly location is never null in this context.")]
private static readonly string InstallerPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
/// <summary>The absolute path of the folder containing the unzipped installer files.</summary>
private static readonly string ExtractedBundlePath = Path.Combine(Path.GetTempPath(), $"SMAPI-installer-{Guid.NewGuid():N}");
/// <summary>The absolute path for referenced assemblies.</summary>
private static readonly string InternalFilesPath = Path.Combine(Program.ExtractedBundlePath, "smapi-internal");
/*********
** Public methods
*********/
@ -10,8 +31,52 @@
/// <param name="args">The command line arguments.</param>
public static void Main(string[] args)
{
var installer = new InteractiveInstaller();
// find install bundle
PlatformID platform = Environment.OSVersion.Platform;
FileInfo zipFile = new FileInfo(Path.Combine(Program.InstallerPath, $"{(platform == PlatformID.Win32NT ? "windows" : "unix")}-install.dat"));
if (!zipFile.Exists)
{
Console.WriteLine($"Oops! Some of the installer files are missing; try redownloading the installer. (Missing file: {zipFile.FullName})");
Console.ReadLine();
return;
}
// unzip bundle into temp folder
DirectoryInfo bundleDir = new DirectoryInfo(Program.ExtractedBundlePath);
Console.WriteLine("Extracting install files...");
ZipFile.ExtractToDirectory(zipFile.FullName, bundleDir.FullName);
// set up assembly resolution
AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve;
// launch installer
var installer = new InteractiveInstaller(bundleDir.FullName);
installer.Run(args);
}
/*********
** Private methods
*********/
/// <summary>Method called when assembly resolution fails, which may return a manually resolved assembly.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e)
{
try
{
AssemblyName name = new AssemblyName(e.Name);
foreach (FileInfo dll in new DirectoryInfo(Program.InternalFilesPath).EnumerateFiles("*.dll"))
{
if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.InvariantCultureIgnoreCase))
return Assembly.LoadFrom(dll.FullName);
}
return null;
}
catch (Exception ex)
{
Console.WriteLine($"Error resolving assembly: {ex}");
return null;
}
}
}
}

View File

@ -16,7 +16,7 @@ SMAPI lets you run Stardew Valley with mods. Don't forget to download mods separ
Player's guide
--------------------------------
See https://stardewvalleywiki.com/Modding:Player_Guide
See https://stardewvalleywiki.com/Modding:Player_Guide for help installing SMAPI, adding mods, etc.
Manual install
@ -24,15 +24,21 @@ 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. Download the latest version of SMAPI: https://github.com/Pathoschild/SMAPI/releases
2. Unzip the .zip file somewhere (not in your game folder).
3. Copy the files from the "internal/Windows" folder (on Windows) or "internal/Mono" folder (on
Linux/Mac) into your game folder. The `StardewModdingAPI.exe` file should be right next to the
game's executable.
4.
1. Unzip "internal/windows-install.dat" (on Windows) or "internal/unix-install.dat" (on Linux/Mac).
You can change '.dat' to '.zip', it's just a normal zip file renamed to prevent confusion.
2. Copy the files from the folder you just unzipped into your game folder. The
`StardewModdingAPI.exe` file should be right next to the game's executable.
3.
- Windows only: if you use Steam, see the install guide above to enable achievements and
overlay. Otherwise, just run StardewModdingAPI.exe in your game folder to play with mods.
- Linux/Mac only: rename the "StardewValley" file (no extension) to "StardewValley-original", and
"StardewModdingAPI" (no extension) to "StardewValley". Now just launch the game as usual to
play with mods.
When installing on Linux or Mac:
- Make sure Mono is installed (normally the installer checks for you). While it's not required,
many mods won't work correctly without it. (Specifically, mods which load PNG images may crash or
freeze the game.)
- To configure the color scheme, edit the `smapi-internal/StardewModdingAPI.config.json` file and
see instructions there for the 'ColorScheme' setting.

View File

@ -34,6 +34,8 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\build\GlobalAssemblyInfo.cs">
@ -46,11 +48,17 @@
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="readme.txt">
<Content Include="README.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="windows-exe-config.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="windows-install.bat">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="unix-install.sh">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@ -58,6 +66,12 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StardewModdingAPI.Toolkit\StardewModdingAPI.Toolkit.csproj">
<Project>{ea5cfd2e-9453-4d29-b80f-8e0ea23f4ac6}</Project>
<Name>StardewModdingAPI.Toolkit</Name>
</ProjectReference>
</ItemGroup>
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\common.targets" />

View File

@ -14,8 +14,8 @@ fi
# validate Mono & run installer
if $COMMAND mono >/dev/null 2>&1; then
mono internal/Mono/install.exe
mono internal/unix-install.exe
else
echo "Oops! Looks like Mono isn't installed. Please install Mono from http://mono-project.com, reboot, and run this installer again."
echo "Oops! Looks like Mono isn't installed. Please install Mono from https://mono-project.com, reboot, and run this installer again."
read
fi

View File

@ -62,27 +62,27 @@ else
fi
# open SMAPI in terminal
if $COMMAND x-terminal-emulator 2>/dev/null; then
if $COMMAND xterm 2>/dev/null; then
xterm -e "$LAUNCHER"
elif $COMMAND x-terminal-emulator 2>/dev/null; then
# Terminator converts -e to -x when used through x-terminal-emulator for some reason (per
# `man terminator`), which causes an "unable to find shell" error. If x-terminal-emulator
# is mapped to Terminator, invoke it directly instead.
if [[ "$(readlink -e $(which x-terminal-emulator))" == *"/terminator" ]]; then
terminator -e "$LAUNCHER"
terminator -e "sh -c 'TERM=xterm $LAUNCHER'"
else
x-terminal-emulator -e "$LAUNCHER"
x-terminal-emulator -e "sh -c 'TERM=xterm $LAUNCHER'"
fi
elif $COMMAND xterm 2>/dev/null; then
xterm -e "$LAUNCHER"
elif $COMMAND xfce4-terminal 2>/dev/null; then
xfce4-terminal -e "env TERM=xterm; $LAUNCHER"
xfce4-terminal -e "sh -c 'TERM=xterm $LAUNCHER'"
elif $COMMAND gnome-terminal 2>/dev/null; then
gnome-terminal -e "env TERM=xterm; $LAUNCHER"
gnome-terminal -e "sh -c 'TERM=xterm $LAUNCHER'"
elif $COMMAND konsole 2>/dev/null; then
konsole -p Environment=TERM=xterm -e "$LAUNCHER"
elif $COMMAND terminal 2>/dev/null; then
terminal -e "$LAUNCHER"
terminal -e "sh -c 'TERM=xterm $LAUNCHER'"
else
$LAUNCHER
sh -c 'TERM=xterm $LAUNCHER'
fi
# some Linux users get error 127 (command not found) from the above block, even though

View File

@ -0,0 +1,5 @@
<configuration>
<runtime>
<loadFromRemoteSources enabled="true"/>
</runtime>
</configuration>

View File

@ -0,0 +1 @@
START /WAIT /B internal/windows-install.exe

View File

@ -6,9 +6,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.8.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.2" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
</ItemGroup>
<ItemGroup>

View File

@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Script.Serialization;
using StardewModdingAPI.Toolkit;
using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Toolkit.Serialisation.Models;
namespace StardewModdingAPI.ModBuildConfig.Framework
{
@ -107,41 +107,10 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
/// <exception cref="UserErrorException">The manifest is missing or invalid.</exception>
public string GetManifestVersion()
{
// get manifest file
if (!this.Files.TryGetValue(this.ManifestFileName, out FileInfo manifestFile))
if (!this.Files.TryGetValue(this.ManifestFileName, out FileInfo manifestFile) || !new JsonHelper().ReadJsonFileIfExists(manifestFile.FullName, out Manifest manifest))
throw new InvalidOperationException($"The mod does not have a {this.ManifestFileName} file."); // shouldn't happen since we validate in constructor
// read content
string json = File.ReadAllText(manifestFile.FullName);
if (string.IsNullOrWhiteSpace(json))
throw new UserErrorException("The mod's manifest must not be empty.");
// parse JSON
IDictionary<string, object> data;
try
{
data = this.Parse(json);
}
catch (Exception ex)
{
throw new UserErrorException($"The mod's manifest couldn't be parsed. It doesn't seem to be valid JSON.\n{ex}");
}
// get version field
object versionObj = data.ContainsKey("Version") ? data["Version"] : null;
if (versionObj == null)
throw new UserErrorException("The mod's manifest must have a version field.");
// get version string
if (versionObj is IDictionary<string, object> versionFields) // SMAPI 1.x
{
int major = versionFields.ContainsKey("MajorVersion") ? (int)versionFields["MajorVersion"] : 0;
int minor = versionFields.ContainsKey("MinorVersion") ? (int)versionFields["MinorVersion"] : 0;
int patch = versionFields.ContainsKey("PatchVersion") ? (int)versionFields["PatchVersion"] : 0;
string tag = versionFields.ContainsKey("Build") ? (string)versionFields["Build"] : null;
return new SemanticVersion(major, minor, patch, tag).ToString();
}
return new SemanticVersion(versionObj.ToString()).ToString(); // SMAPI 2.0+
return manifest.Version.ToString();
}
@ -174,24 +143,6 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|| ignoreFilePatterns.Any(p => p.IsMatch(relativePath));
}
/// <summary>Get a case-insensitive dictionary matching the given JSON.</summary>
/// <param name="json">The JSON to parse.</param>
private IDictionary<string, object> Parse(string json)
{
IDictionary<string, object> MakeCaseInsensitive(IDictionary<string, object> dict)
{
foreach (var field in dict.ToArray())
{
if (field.Value is IDictionary<string, object> value)
dict[field.Key] = MakeCaseInsensitive(value);
}
return new Dictionary<string, object>(dict, StringComparer.InvariantCultureIgnoreCase);
}
IDictionary<string, object> data = (IDictionary<string, object>)new JavaScriptSerializer().DeserializeObject(json);
return MakeCaseInsensitive(data);
}
/// <summary>Get whether a string is equal to another case-insensitively.</summary>
/// <param name="str">The string value.</param>
/// <param name="other">The string to compare with.</param>

View File

@ -2,5 +2,5 @@ using System.Reflection;
[assembly: AssemblyTitle("SMAPI.ModBuildConfig")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyVersion("2.1.0.0")]
[assembly: AssemblyFileVersion("2.1.0.0")]
[assembly: AssemblyVersion("2.2.0")]
[assembly: AssemblyFileVersion("2.2.0")]

View File

@ -56,6 +56,10 @@
<Content Include="assets\nuget-icon.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StardewModdingAPI.Toolkit.CoreInterfaces\StardewModdingAPI.Toolkit.CoreInterfaces.csproj">
<Project>{d5cfd923-37f1-4bc3-9be8-e506e202ac28}</Project>
<Name>StardewModdingAPI.Toolkit.CoreInterfaces</Name>
</ProjectReference>
<ProjectReference Include="..\StardewModdingAPI.Toolkit\StardewModdingAPI.Toolkit.csproj">
<Project>{ea5cfd2e-9453-4d29-b80f-8e0ea23f4ac6}</Project>
<Name>StardewModdingAPI.Toolkit</Name>

View File

@ -42,6 +42,10 @@
<When Condition="$(OS) == 'Windows_NT'">
<PropertyGroup>
<!-- default paths -->
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GalaxyClient\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GOG Galaxy\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\Steam\steamapps\common\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GOG Galaxy\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley</GamePath>
@ -97,7 +101,8 @@
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
</Reference>
<Reference Include="StardewModdingAPI.Toolkit.CoreInterfaces">
<HintPath>$(GamePath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath>
<HintPath>$(GamePath)\smapi-internal\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath>
<HintPath Condition="!Exists('$(GamePath)\smapi-internal')">$(GamePath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath>
<Private>false</Private>
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
</Reference>
@ -136,7 +141,8 @@
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
</Reference>
<Reference Include="StardewModdingAPI.Toolkit.CoreInterfaces">
<HintPath>$(GamePath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath>
<HintPath>$(GamePath)\smapi-internal\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath>
<HintPath Condition="!Exists('$(GamePath)\smapi-internal')">$(GamePath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath>
<Private>false</Private>
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
</Reference>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>Pathoschild.Stardew.ModBuildConfig</id>
<version>2.1.0</version>
<version>2.2</version>
<title>Build package for SMAPI mods</title>
<authors>Pathoschild</authors>
<owners>Pathoschild</owners>
@ -12,13 +12,10 @@
<iconUrl>https://raw.githubusercontent.com/Pathoschild/SMAPI/develop/src/SMAPI.ModBuildConfig/assets/nuget-icon.png</iconUrl>
<description>Automates the build configuration for crossplatform Stardew Valley SMAPI mods. For Stardew Valley 1.3 or later.</description>
<releaseNotes>
2.1:
- Added support for Stardew Valley 1.3.
- Added support for non-mod projects.
- Added C# analyzers to warn about implicit conversions of Netcode fields in Stardew Valley 1.3.
- Added option to ignore files by regex pattern.
- Added reference to new SMAPI DLL.
- Fixed some game paths not detected by NuGet package.
2.2:
- Added support for SMAPI 2.8+ (still compatible with earlier versions).
- Added default game paths for 32-bit Windows.
- Fixed valid manifests marked invalid in some cases.
</releaseNotes>
</metadata>
</package>

View File

@ -36,10 +36,6 @@
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Xml" />
</ItemGroup>
@ -89,7 +85,6 @@
<None Include="manifest.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\common.targets" />

View File

@ -1,9 +1,9 @@
{
"Name": "Console Commands",
"Author": "SMAPI",
"Version": "2.7.0",
"Version": "2.8.0",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
"MinimumApiVersion": "2.7.0"
"MinimumApiVersion": "2.8.1"
}

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net45" />
</packages>

View File

@ -1,9 +0,0 @@
namespace StardewModdingAPI.Mods.SaveBackup.Framework
{
/// <summary>The mod configuration.</summary>
internal class ModConfig
{
/// <summary>The number of backups to keep.</summary>
public int BackupsToKeep { get; set; } = 10;
}
}

View File

@ -4,7 +4,6 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using StardewModdingAPI.Mods.SaveBackup.Framework;
using StardewValley;
namespace StardewModdingAPI.Mods.SaveBackup
@ -15,6 +14,12 @@ namespace StardewModdingAPI.Mods.SaveBackup
/*********
** Properties
*********/
/// <summary>The number of backups to keep.</summary>
private readonly int BackupsToKeep = 10;
/// <summary>The absolute path to the folder in which to store save backups.</summary>
private readonly string BackupFolder = Path.Combine(Constants.ExecutionPath, "save-backups");
/// <summary>The name of the save archive to create.</summary>
private readonly string FileName = $"{DateTime.UtcNow:yyyy-MM-dd} - SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version}.zip";
@ -28,15 +33,13 @@ namespace StardewModdingAPI.Mods.SaveBackup
{
try
{
ModConfig config = this.Helper.ReadConfig<ModConfig>();
// init backup folder
DirectoryInfo backupFolder = new DirectoryInfo(Path.Combine(this.Helper.DirectoryPath, "backups"));
DirectoryInfo backupFolder = new DirectoryInfo(this.BackupFolder);
backupFolder.Create();
// back up saves
this.CreateBackup(backupFolder);
this.PruneBackups(backupFolder, config.BackupsToKeep);
this.PruneBackups(backupFolder, this.BackupsToKeep);
}
catch (Exception ex)
{

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@ -36,7 +36,6 @@
<Compile Include="..\..\build\GlobalAssemblyInfo.cs">
<Link>Properties\GlobalAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Framework\ModConfig.cs" />
<Compile Include="ModEntry.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View File

@ -1,9 +1,9 @@
{
"Name": "Save Backup",
"Author": "SMAPI",
"Version": "2.7.0",
"Version": "2.8.0",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
"MinimumApiVersion": "2.7.0"
"MinimumApiVersion": "2.8.1"
}

View File

@ -145,7 +145,7 @@ namespace StardewModdingAPI.Tests.Core
this.SetupMetadataForValidation(mock, new ModDataRecordVersionedFields
{
Status = ModStatus.AssumeBroken,
AlternativeUrl = "http://example.org"
AlternativeUrl = "https://example.org"
});
// act
@ -513,6 +513,7 @@ namespace StardewModdingAPI.Tests.Core
mod.Setup(p => p.Status).Returns(ModMetadataStatus.Found);
mod.Setup(p => p.DisplayName).Returns(manifest.UniqueID);
mod.Setup(p => p.Manifest).Returns(manifest);
mod.Setup(p => p.HasID(It.IsAny<string>())).Returns((string id) => manifest.UniqueID == id);
if (allowStatusChange)
{
mod

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\NUnit.3.10.1\build\NUnit.props" Condition="Exists('..\packages\NUnit.3.10.1\build\NUnit.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -31,29 +30,17 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.8.0.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.8.3\lib\net45\Moq.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=3.10.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.10.1\lib\net45\nunit.framework.dll</HintPath>
</Reference>
<PackageReference Include="Castle.Core" Version="4.3.1" />
<PackageReference Include="Moq" Version="4.10.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.1" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.1\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\build\GlobalAssemblyInfo.cs">
@ -69,7 +56,6 @@
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SMAPI\StardewModdingAPI.csproj">
@ -90,10 +76,4 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\common.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\NUnit.3.10.1\build\NUnit.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\NUnit.3.10.1\build\NUnit.props'))" />
</Target>
</Project>

View File

@ -127,6 +127,7 @@ namespace StardewModdingAPI.Tests.Utilities
[TestCase("1.0-beta.1", "1.0-beta.2", ExpectedResult = -1)]
[TestCase("1.0-beta.2", "1.0-beta.10", ExpectedResult = -1)]
[TestCase("1.0-beta-2", "1.0-beta-10", ExpectedResult = -1)]
[TestCase("1.0-unofficial.1", "1.0-beta.1", ExpectedResult = -1)] // special case: 'unofficial' has lower priority than official releases
// more than
[TestCase("0.5.8", "0.5.7", ExpectedResult = 1)]

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Castle.Core" version="4.3.1" targetFramework="net45" />
<package id="Moq" version="4.8.3" targetFramework="net45" />
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net45" />
<package id="NUnit" version="3.10.1" targetFramework="net45" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.1" targetFramework="net45" />
<package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net45" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net45" />
</packages>

View File

@ -67,15 +67,22 @@ namespace StardewModdingAPI.Web.Controllers
IndexVersionModel stableVersionModel = stableVersion != null
? new IndexVersionModel(stableVersion.Version.ToString(), stableVersion.Release.Body, stableVersion.Asset.DownloadUrl, stableVersionForDevs?.Asset.DownloadUrl)
: new IndexVersionModel("unknown", "", "https://github.com/Pathoschild/SMAPI/releases", null); // just in case something goes wrong)
IndexVersionModel betaVersionModel = betaVersion != null && this.SiteConfig.EnableSmapiBeta
IndexVersionModel betaVersionModel = betaVersion != null && this.SiteConfig.BetaEnabled
? new IndexVersionModel(betaVersion.Version.ToString(), betaVersion.Release.Body, betaVersion.Asset.DownloadUrl, betaVersionForDevs?.Asset.DownloadUrl)
: null;
// render view
var model = new IndexModel(stableVersionModel, betaVersionModel);
var model = new IndexModel(stableVersionModel, betaVersionModel, this.SiteConfig.BetaBlurb);
return this.View(model);
}
/// <summary>Display the index page.</summary>
[HttpGet("/privacy")]
public ViewResult Privacy()
{
return this.View();
}
/*********
** Private methods

View File

@ -12,6 +12,7 @@ using StardewModdingAPI.Toolkit;
using StardewModdingAPI.Toolkit.Framework.Clients.WebApi;
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
using StardewModdingAPI.Web.Framework.Clients.Chucklefish;
using StardewModdingAPI.Web.Framework.Clients.GitHub;
using StardewModdingAPI.Web.Framework.Clients.Nexus;
@ -29,7 +30,7 @@ namespace StardewModdingAPI.Web.Controllers
** Properties
*********/
/// <summary>The mod repositories which provide mod metadata.</summary>
private readonly IDictionary<string, IModRepository> Repositories;
private readonly IDictionary<ModRepositoryKey, IModRepository> Repositories;
/// <summary>The cache in which to store mod metadata.</summary>
private readonly IMemoryCache Cache;
@ -46,8 +47,8 @@ namespace StardewModdingAPI.Web.Controllers
/// <summary>The internal mod metadata list.</summary>
private readonly ModDatabase ModDatabase;
/// <summary>The web URL for the wiki compatibility list.</summary>
private readonly string WikiCompatibilityPageUrl;
/// <summary>The web URL for the compatibility list.</summary>
private readonly string CompatibilityPageUrl;
/*********
@ -64,7 +65,7 @@ namespace StardewModdingAPI.Web.Controllers
{
this.ModDatabase = new ModToolkit().GetModDatabase(Path.Combine(environment.WebRootPath, "StardewModdingAPI.metadata.json"));
ModUpdateCheckConfig config = configProvider.Value;
this.WikiCompatibilityPageUrl = config.WikiCompatibilityPageUrl;
this.CompatibilityPageUrl = config.CompatibilityPageUrl;
this.Cache = cache;
this.SuccessCacheMinutes = config.SuccessCacheMinutes;
@ -73,11 +74,11 @@ namespace StardewModdingAPI.Web.Controllers
this.Repositories =
new IModRepository[]
{
new ChucklefishRepository(config.ChucklefishKey, chucklefish),
new GitHubRepository(config.GitHubKey, github),
new NexusRepository(config.NexusKey, nexus)
new ChucklefishRepository(chucklefish),
new GitHubRepository(github),
new NexusRepository(nexus)
}
.ToDictionary(p => p.VendorKey, StringComparer.CurrentCultureIgnoreCase);
.ToDictionary(p => p.VendorKey);
}
/// <summary>Fetch version metadata for the given mods.</summary>
@ -89,7 +90,7 @@ namespace StardewModdingAPI.Web.Controllers
return new ModEntryModel[0];
// fetch wiki data
WikiCompatibilityEntry[] wikiData = await this.GetWikiDataAsync();
WikiModEntry[] wikiData = await this.GetWikiDataAsync();
IDictionary<string, ModEntryModel> mods = new Dictionary<string, ModEntryModel>(StringComparer.CurrentCultureIgnoreCase);
foreach (ModSearchEntryModel mod in model.Mods)
{
@ -113,17 +114,12 @@ namespace StardewModdingAPI.Web.Controllers
/// <param name="wikiData">The wiki data.</param>
/// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
/// <returns>Returns the mod data if found, else <c>null</c>.</returns>
private async Task<ModEntryModel> GetModData(ModSearchEntryModel search, WikiCompatibilityEntry[] wikiData, bool includeExtendedMetadata)
private async Task<ModEntryModel> GetModData(ModSearchEntryModel search, WikiModEntry[] wikiData, bool includeExtendedMetadata)
{
// resolve update keys
var updateKeys = new HashSet<string>(search.UpdateKeys ?? new string[0], StringComparer.InvariantCultureIgnoreCase);
// crossreference data
ModDataRecord record = this.ModDatabase.Get(search.ID);
if (record?.Fields != null)
{
string defaultUpdateKey = record.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey && p.IsDefault)?.Value;
if (!string.IsNullOrWhiteSpace(defaultUpdateKey))
updateKeys.Add(defaultUpdateKey);
}
WikiModEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(search.ID.Trim(), StringComparer.InvariantCultureIgnoreCase));
string[] updateKeys = this.GetUpdateKeys(search.UpdateKeys, record, wikiEntry).ToArray();
// get latest versions
ModEntryModel result = new ModEntryModel { ID = search.ID };
@ -166,9 +162,25 @@ namespace StardewModdingAPI.Web.Controllers
}
// get unofficial version
WikiCompatibilityEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(result.ID.Trim(), StringComparer.InvariantCultureIgnoreCase));
if (wikiEntry?.UnofficialVersion != null && this.IsNewer(wikiEntry.UnofficialVersion, result.Main?.Version) && this.IsNewer(wikiEntry.UnofficialVersion, result.Optional?.Version))
result.Unofficial = new ModEntryVersionModel(wikiEntry.UnofficialVersion, this.WikiCompatibilityPageUrl);
if (wikiEntry?.Compatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, result.Main?.Version) && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, result.Optional?.Version))
result.Unofficial = new ModEntryVersionModel(wikiEntry.Compatibility.UnofficialVersion, $"{this.CompatibilityPageUrl}/#{wikiEntry.Anchor}");
// get unofficial version for beta
if (wikiEntry?.HasBetaInfo == true)
{
result.HasBetaInfo = true;
if (wikiEntry.BetaCompatibility.Status == WikiCompatibilityStatus.Unofficial)
{
if (wikiEntry.BetaCompatibility.UnofficialVersion != null)
{
result.UnofficialForBeta = (wikiEntry.BetaCompatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, result.Main?.Version) && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, result.Optional?.Version))
? new ModEntryVersionModel(wikiEntry.BetaCompatibility.UnofficialVersion, $"{this.CompatibilityPageUrl}/#{wikiEntry.Anchor}")
: null;
}
else
result.UnofficialForBeta = result.Unofficial;
}
}
// fallback to preview if latest is invalid
if (result.Main == null && result.Optional != null)
@ -195,28 +207,6 @@ namespace StardewModdingAPI.Web.Controllers
return result;
}
/// <summary>Parse a namespaced mod ID.</summary>
/// <param name="raw">The raw mod ID to parse.</param>
/// <param name="vendorKey">The parsed vendor key.</param>
/// <param name="modID">The parsed mod ID.</param>
/// <returns>Returns whether the value could be parsed.</returns>
private bool TryParseModKey(string raw, out string vendorKey, out string modID)
{
// split parts
string[] parts = raw?.Split(':');
if (parts == null || parts.Length != 2)
{
vendorKey = null;
modID = null;
return false;
}
// parse
vendorKey = parts[0].Trim();
modID = parts[1].Trim();
return true;
}
/// <summary>Get whether a <paramref name="current"/> version is newer than an <paramref name="other"/> version.</summary>
/// <param name="current">The current version.</param>
/// <param name="other">The other version.</param>
@ -226,21 +216,21 @@ namespace StardewModdingAPI.Web.Controllers
}
/// <summary>Get mod data from the wiki compatibility list.</summary>
private async Task<WikiCompatibilityEntry[]> GetWikiDataAsync()
private async Task<WikiModEntry[]> GetWikiDataAsync()
{
ModToolkit toolkit = new ModToolkit();
return await this.Cache.GetOrCreateAsync($"_wiki", async entry =>
return await this.Cache.GetOrCreateAsync("_wiki", async entry =>
{
try
{
WikiCompatibilityEntry[] entries = await toolkit.GetWikiCompatibilityListAsync();
WikiModEntry[] entries = (await toolkit.GetWikiCompatibilityListAsync()).Mods;
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.SuccessCacheMinutes);
return entries;
}
catch
{
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.ErrorCacheMinutes);
return new WikiCompatibilityEntry[0];
return new WikiModEntry[0];
}
});
}
@ -250,18 +240,19 @@ namespace StardewModdingAPI.Web.Controllers
private async Task<ModInfoModel> GetInfoForUpdateKeyAsync(string updateKey)
{
// parse update key
if (!this.TryParseModKey(updateKey, out string vendorKey, out string modID))
UpdateKey parsed = UpdateKey.Parse(updateKey);
if (!parsed.LooksValid)
return new ModInfoModel($"The update key '{updateKey}' isn't in a valid format. It should contain the site key and mod ID like 'Nexus:541'.");
// get matching repository
if (!this.Repositories.TryGetValue(vendorKey, out IModRepository repository))
return new ModInfoModel($"There's no mod site with key '{vendorKey}'. Expected one of [{string.Join(", ", this.Repositories.Keys)}].");
if (!this.Repositories.TryGetValue(parsed.Repository, out IModRepository repository))
return new ModInfoModel($"There's no mod site with key '{parsed.Repository}'. Expected one of [{string.Join(", ", this.Repositories.Keys)}].");
// fetch mod info
return await this.Cache.GetOrCreateAsync($"{repository.VendorKey}:{modID}".ToLower(), async entry =>
return await this.Cache.GetOrCreateAsync($"{repository.VendorKey}:{parsed.ID}".ToLower(), async entry =>
{
ModInfoModel result = await repository.GetModInfoAsync(modID);
if (result.Error != null)
ModInfoModel result = await repository.GetModInfoAsync(parsed.ID);
if (result.Error == null)
{
if (result.Version == null)
result.Error = $"The update key '{updateKey}' matches a mod with no version number.";
@ -273,11 +264,42 @@ namespace StardewModdingAPI.Web.Controllers
});
}
/// <summary>Get the requested API version.</summary>
private ISemanticVersion GetApiVersion()
/// <summary>Get update keys based on the available mod metadata, while maintaining the precedence order.</summary>
/// <param name="specifiedKeys">The specified update keys.</param>
/// <param name="record">The mod's entry in SMAPI's internal database.</param>
/// <param name="entry">The mod's entry in the wiki list.</param>
public IEnumerable<string> GetUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry)
{
string actualVersion = (string)this.RouteData.Values["version"];
return new SemanticVersion(actualVersion);
IEnumerable<string> GetRaw()
{
// specified update keys
if (specifiedKeys != null)
{
foreach (string key in specifiedKeys)
yield return key?.Trim();
}
// default update key
string defaultKey = record?.GetDefaultUpdateKey();
if (defaultKey != null)
yield return defaultKey;
// wiki metadata
if (entry != null)
{
if (entry.NexusID.HasValue)
yield return $"Nexus:{entry.NexusID}";
if (entry.ChucklefishID.HasValue)
yield return $"Chucklefish:{entry.ChucklefishID}";
}
}
HashSet<string> seen = new HashSet<string>(StringComparer.InvariantCulture);
foreach (string key in GetRaw())
{
if (!string.IsNullOrWhiteSpace(key) && seen.Add(key))
yield return key;
}
}
}
}

View File

@ -0,0 +1,74 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using StardewModdingAPI.Toolkit;
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
using StardewModdingAPI.Web.Framework.ConfigModels;
using StardewModdingAPI.Web.ViewModels;
namespace StardewModdingAPI.Web.Controllers
{
/// <summary>Provides user-friendly info about SMAPI mods.</summary>
internal class ModsController : Controller
{
/*********
** Properties
*********/
/// <summary>The cache in which to store mod metadata.</summary>
private readonly IMemoryCache Cache;
/// <summary>The number of minutes successful update checks should be cached before refetching them.</summary>
private readonly int SuccessCacheMinutes;
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="cache">The cache in which to store mod metadata.</param>
/// <param name="configProvider">The config settings for mod update checks.</param>
public ModsController(IMemoryCache cache, IOptions<ModUpdateCheckConfig> configProvider)
{
ModUpdateCheckConfig config = configProvider.Value;
this.Cache = cache;
this.SuccessCacheMinutes = config.SuccessCacheMinutes;
}
/// <summary>Display information for all mods.</summary>
[HttpGet]
[Route("mods")]
public async Task<ViewResult> Index()
{
return this.View("Index", await this.FetchDataAsync());
}
/*********
** Private methods
*********/
/// <summary>Asynchronously fetch mod metadata from the wiki.</summary>
public async Task<ModListModel> FetchDataAsync()
{
return await this.Cache.GetOrCreateAsync($"{nameof(ModsController)}_mod_list", async entry =>
{
WikiModList data = await new ModToolkit().GetWikiCompatibilityListAsync();
ModListModel model = new ModListModel(
stableVersion: data.StableVersion,
betaVersion: data.BetaVersion,
mods: data
.Mods
.Select(mod => new ModModel(mod))
.OrderBy(p => Regex.Replace(p.Name.ToLower(), "[^a-z0-9]", "")) // ignore case, spaces, and special characters when sorting
);
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.SuccessCacheMinutes);
return model;
});
}
}
}

View File

@ -1,5 +1,4 @@
using Newtonsoft.Json;
using StardewModdingAPI.Toolkit;
namespace StardewModdingAPI.Web.Framework.Clients.Nexus
{

View File

@ -16,16 +16,7 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
/// <remarks>Derived from SMAPI's SemanticVersion implementation.</remarks>
public string SemanticVersionRegex { get; set; }
/// <summary>The repository key for the Chucklefish mod site.</summary>
public string ChucklefishKey { get; set; }
/// <summary>The repository key for Nexus Mods.</summary>
public string GitHubKey { get; set; }
/// <summary>The repository key for Nexus Mods.</summary>
public string NexusKey { get; set; }
/// <summary>The web URL for the wiki compatibility list.</summary>
public string WikiCompatibilityPageUrl { get; set; }
public string CompatibilityPageUrl { get; set; }
}
}

View File

@ -12,7 +12,13 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
/// <summary>The root URL for the log parser.</summary>
public string LogParserUrl { get; set; }
/// <summary>The root URL for the mod list.</summary>
public string ModListUrl { get; set; }
/// <summary>Whether to show SMAPI beta versions on the main page, if any.</summary>
public bool EnableSmapiBeta { get; set; }
public bool BetaEnabled { get; set; }
/// <summary>A short sentence shown under the beta download button, if any.</summary>
public string BetaBlurb { get; set; }
}
}

View File

@ -70,6 +70,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
// parse log messages
LogModInfo smapiMod = new LogModInfo { Name = "SMAPI", Author = "Pathoschild", Description = "" };
LogModInfo gameMod = new LogModInfo { Name = "game", Author = "", Description = "" };
IDictionary<string, LogModInfo> mods = new Dictionary<string, LogModInfo>();
bool inModList = false;
bool inContentPackList = false;
@ -78,10 +79,23 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
// collect stats
if (message.Level == LogLevel.Error)
{
if (message.Mod == "SMAPI")
switch (message.Mod)
{
case "SMAPI":
smapiMod.Errors++;
else if (mods.ContainsKey(message.Mod))
break;
case "game":
gameMod.Errors++;
break;
default:
{
if (mods.ContainsKey(message.Mod))
mods[message.Mod].Errors++;
break;
}
}
}
// collect SMAPI metadata
@ -151,7 +165,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
}
// finalise log
log.Mods = new[] { smapiMod }.Concat(mods.Values.OrderBy(p => p.Name)).ToArray();
gameMod.Version = log.GameVersion;
log.Mods = new[] { gameMod, smapiMod }.Concat(mods.Values.OrderBy(p => p.Name)).ToArray();
return log;
}
catch (LogParseException ex)

View File

@ -1,6 +1,6 @@
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using StardewModdingAPI.Toolkit.Framework.Clients.WebApi;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
namespace StardewModdingAPI.Web.Framework.ModRepositories
{
@ -10,7 +10,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories
** Accessors
*********/
/// <summary>The unique key for this vendor.</summary>
public string VendorKey { get; }
public ModRepositoryKey VendorKey { get; }
/*********
@ -29,7 +29,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories
*********/
/// <summary>Construct an instance.</summary>
/// <param name="vendorKey">The unique key for this vendor.</param>
protected RepositoryBase(string vendorKey)
protected RepositoryBase(ModRepositoryKey vendorKey)
{
this.VendorKey = vendorKey;
}

View File

@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
using StardewModdingAPI.Toolkit.Framework.Clients.WebApi;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
using StardewModdingAPI.Web.Framework.Clients.Chucklefish;
namespace StardewModdingAPI.Web.Framework.ModRepositories
@ -19,10 +19,9 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="vendorKey">The unique key for this vendor.</param>
/// <param name="client">The underlying HTTP client.</param>
public ChucklefishRepository(string vendorKey, IChucklefishClient client)
: base(vendorKey)
public ChucklefishRepository(IChucklefishClient client)
: base(ModRepositoryKey.Chucklefish)
{
this.Client = client;
}

View File

@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
using StardewModdingAPI.Toolkit.Framework.Clients.WebApi;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
using StardewModdingAPI.Web.Framework.Clients.GitHub;
namespace StardewModdingAPI.Web.Framework.ModRepositories
@ -19,10 +19,9 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="vendorKey">The unique key for this vendor.</param>
/// <param name="client">The underlying GitHub API client.</param>
public GitHubRepository(string vendorKey, IGitHubClient client)
: base(vendorKey)
public GitHubRepository(IGitHubClient client)
: base(ModRepositoryKey.GitHub)
{
this.Client = client;
}

View File

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
namespace StardewModdingAPI.Web.Framework.ModRepositories
{
@ -10,7 +11,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories
** Accessors
*********/
/// <summary>The unique key for this vendor.</summary>
string VendorKey { get; }
ModRepositoryKey VendorKey { get; }
/*********

View File

@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
using StardewModdingAPI.Toolkit.Framework.Clients.WebApi;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
using StardewModdingAPI.Web.Framework.Clients.Nexus;
namespace StardewModdingAPI.Web.Framework.ModRepositories
@ -19,10 +19,9 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="vendorKey">The unique key for this vendor.</param>
/// <param name="client">The underlying Nexus Mods API client.</param>
public NexusRepository(string vendorKey, INexusClient client)
: base(vendorKey)
public NexusRepository(INexusClient client)
: base(ModRepositoryKey.Nexus)
{
this.Client = client;
}

View File

@ -10,10 +10,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.8.4" />
<PackageReference Include="Markdig" Version="0.15.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.1" />
<PackageReference Include="HtmlAgilityPack" Version="1.8.9" />
<PackageReference Include="Markdig" Version="0.15.4" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Rewrite" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
@ -27,6 +27,12 @@
<ProjectReference Include="..\StardewModdingAPI.Toolkit\StardewModdingAPI.Toolkit.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="Views\Index\Privacy.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Views\Mods\Index.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="wwwroot\StardewModdingAPI.metadata.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View File

@ -147,14 +147,14 @@ namespace StardewModdingAPI.Web
redirects.Add(new ConditionalRewriteSubdomainRule(
shouldRewrite: req =>
req.Host.Host != "localhost"
&& (req.Host.Host.StartsWith("api.") || req.Host.Host.StartsWith("log."))
&& (req.Host.Host.StartsWith("api.") || req.Host.Host.StartsWith("log.") || req.Host.Host.StartsWith("mods."))
&& !req.Path.StartsWithSegments("/content")
&& !req.Path.StartsWithSegments("/favicon.ico")
));
// shortcut redirects
redirects.Add(new RedirectToUrlRule(@"^/buildmsg(?:/?(.*))$", "https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md#$1"));
redirects.Add(new RedirectToUrlRule(@"^/compat\.?$", "https://stardewvalleywiki.com/Modding:SMAPI_compatibility"));
redirects.Add(new RedirectToUrlRule(@"^/compat\.?$", "https://mods.smapi.io"));
redirects.Add(new RedirectToUrlRule(@"^/docs\.?$", "https://stardewvalleywiki.com/Modding:Index"));
redirects.Add(new RedirectToUrlRule(@"^/install\.?$", "https://stardewvalleywiki.com/Modding:Player_Guide/Getting_Started#Install_SMAPI"));

View File

@ -12,6 +12,9 @@ namespace StardewModdingAPI.Web.ViewModels
/// <summary>The latest prerelease SMAPI version (if newer than <see cref="StableVersion"/>).</summary>
public IndexVersionModel BetaVersion { get; set; }
/// <summary>A short sentence shown under the beta download button, if any.</summary>
public string BetaBlurb { get; set; }
/*********
** Public methods
@ -22,10 +25,12 @@ namespace StardewModdingAPI.Web.ViewModels
/// <summary>Construct an instance.</summary>
/// <param name="stableVersion">The latest stable SMAPI version.</param>
/// <param name="betaVersion">The latest prerelease SMAPI version (if newer than <paramref name="stableVersion"/>).</param>
internal IndexModel(IndexVersionModel stableVersion, IndexVersionModel betaVersion)
/// <param name="betaBlurb">A short sentence shown under the beta download button, if any.</param>
internal IndexModel(IndexVersionModel stableVersion, IndexVersionModel betaVersion, string betaBlurb)
{
this.StableVersion = stableVersion;
this.BetaVersion = betaVersion;
this.BetaBlurb = betaBlurb;
}
}
}

View File

@ -0,0 +1,40 @@
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
namespace StardewModdingAPI.Web.ViewModels
{
/// <summary>Metadata about a mod's compatibility with the latest versions of SMAPI and Stardew Valley.</summary>
public class ModCompatibilityModel
{
/*********
** Accessors
*********/
/// <summary>The compatibility status, as a string like <c>"Broken"</c>.</summary>
public string Status { get; set; }
/// <summary>The human-readable summary, as an HTML block.</summary>
public string Summary { get; set; }
/// <summary>The game or SMAPI version which broke this mod (if applicable).</summary>
public string BrokeIn { get; set; }
/// <summary>A link to the unofficial version which fixes compatibility, if any.</summary>
public ModLinkModel UnofficialVersion { get; set; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="info">The mod metadata.</param>
public ModCompatibilityModel(WikiCompatibilityInfo info)
{
this.Status = info.Status.ToString();
this.Status = this.Status.Substring(0, 1).ToLower() + this.Status.Substring(1);
this.Summary = info.Summary;
this.BrokeIn = info.BrokeIn;
if (info.UnofficialVersion != null)
this.UnofficialVersion = new ModLinkModel(info.UnofficialUrl, info.UnofficialVersion.ToString());
}
}
}

View File

@ -0,0 +1,28 @@
namespace StardewModdingAPI.Web.ViewModels
{
/// <summary>Metadata about a link.</summary>
public class ModLinkModel
{
/*********
** Accessors
*********/
/// <summary>The URL of the linked page.</summary>
public string Url { get; set; }
/// <summary>The suggested link text.</summary>
public string Text { get; set; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="url">The URL of the linked page.</param>
/// <param name="text">The suggested link text.</param>
public ModLinkModel(string url, string text)
{
this.Url = url;
this.Text = text;
}
}
}

View File

@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
namespace StardewModdingAPI.Web.ViewModels
{
/// <summary>Metadata for the mod list page.</summary>
public class ModListModel
{
/*********
** Accessors
*********/
/// <summary>The current stable version of the game.</summary>
public string StableVersion { get; set; }
/// <summary>The current beta version of the game (if any).</summary>
public string BetaVersion { get; set; }
/// <summary>The mods to display.</summary>
public ModModel[] Mods { get; set; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="stableVersion">The current stable version of the game.</param>
/// <param name="betaVersion">The current beta version of the game (if any).</param>
/// <param name="mods">The mods to display.</param>
public ModListModel(string stableVersion, string betaVersion, IEnumerable<ModModel> mods)
{
this.StableVersion = stableVersion;
this.BetaVersion = betaVersion;
this.Mods = mods.ToArray();
}
}
}

View File

@ -0,0 +1,110 @@
using System.Collections.Generic;
using System.Linq;
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
namespace StardewModdingAPI.Web.ViewModels
{
/// <summary>Metadata about a mod.</summary>
public class ModModel
{
/*********
** Accessors
*********/
/// <summary>The mod name.</summary>
public string Name { get; set; }
/// <summary>The mod's alternative names, if any.</summary>
public string AlternateNames { get; set; }
/// <summary>The mod author's name.</summary>
public string Author { get; set; }
/// <summary>The mod author's alternative names, if any.</summary>
public string AlternateAuthors { get; set; }
/// <summary>The URL to the mod's source code, if any.</summary>
public string SourceUrl { get; set; }
/// <summary>The compatibility status for the stable version of the game.</summary>
public ModCompatibilityModel Compatibility { get; set; }
/// <summary>The compatibility status for the beta version of the game.</summary>
public ModCompatibilityModel BetaCompatibility { get; set; }
/// <summary>Links to the available mod pages.</summary>
public ModLinkModel[] ModPages { get; set; }
/// <summary>The human-readable warnings for players about this mod.</summary>
public string[] Warnings { get; set; }
/// <summary>A unique identifier for the mod that can be used in an anchor URL.</summary>
public string Slug { get; set; }
/// <summary>The sites where the mod can be downloaded.</summary>
public string[] ModPageSites => this.ModPages.Select(p => p.Text).ToArray();
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="entry">The mod metadata.</param>
public ModModel(WikiModEntry entry)
{
// basic info
this.Name = entry.Name.FirstOrDefault();
this.AlternateNames = string.Join(", ", entry.Name.Skip(1).ToArray());
this.Author = entry.Author.FirstOrDefault();
this.AlternateAuthors = string.Join(", ", entry.Author.Skip(1).ToArray());
this.SourceUrl = this.GetSourceUrl(entry);
this.Compatibility = new ModCompatibilityModel(entry.Compatibility);
this.BetaCompatibility = entry.BetaCompatibility != null ? new ModCompatibilityModel(entry.BetaCompatibility) : null;
this.ModPages = this.GetModPageUrls(entry).ToArray();
this.Warnings = entry.Warnings;
this.Slug = entry.Anchor;
}
/*********
** Private methods
*********/
/// <summary>Get the web URL for the mod's source code repository, if any.</summary>
/// <param name="entry">The mod metadata.</param>
private string GetSourceUrl(WikiModEntry entry)
{
if (!string.IsNullOrWhiteSpace(entry.GitHubRepo))
return $"https://github.com/{entry.GitHubRepo}";
if (!string.IsNullOrWhiteSpace(entry.CustomSourceUrl))
return entry.CustomSourceUrl;
return null;
}
/// <summary>Get the web URLs for the mod pages, if any.</summary>
/// <param name="entry">The mod metadata.</param>
private IEnumerable<ModLinkModel> GetModPageUrls(WikiModEntry entry)
{
bool anyFound = false;
// normal mod pages
if (entry.NexusID.HasValue)
{
anyFound = true;
yield return new ModLinkModel($"https://www.nexusmods.com/stardewvalley/mods/{entry.NexusID}", "Nexus");
}
if (entry.ChucklefishID.HasValue)
{
anyFound = true;
yield return new ModLinkModel($"https://community.playstarbound.com/resources/{entry.ChucklefishID}", "Chucklefish");
}
// fallback
if (!anyFound && !string.IsNullOrWhiteSpace(entry.CustomUrl))
{
anyFound = true;
yield return new ModLinkModel(entry.CustomUrl, "custom");
}
if (!anyFound && !string.IsNullOrWhiteSpace(entry.GitHubRepo))
yield return new ModLinkModel($"https://github.com/{entry.GitHubRepo}/releases", "GitHub");
}
}
}

View File

@ -1,10 +1,13 @@
@using Microsoft.Extensions.Options
@using StardewModdingAPI.Web.Framework.ConfigModels
@inject IOptions<SiteConfig> SiteConfig
@model StardewModdingAPI.Web.ViewModels.IndexModel
@{
ViewData["Title"] = "SMAPI";
}
@model StardewModdingAPI.Web.ViewModels.IndexModel
@section Head {
<link rel="stylesheet" href="~/Content/css/index.css?r=20180615" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js" crossorigin="anonymous"></script>
<script src="~/Content/js/index.js?r=20180615"></script>
}
@ -26,20 +29,29 @@
@if (Model.BetaVersion != null)
{
<div class="cta-dropdown secondary-cta-dropdown">
<a href="@Model.BetaVersion.DownloadUrl" class="secondary-cta download">Download SMAPI @Model.BetaVersion.Version<br/><small>for Stardew Valley 1.3 beta</small></a><br/>
<a href="@Model.BetaVersion.DownloadUrl" class="secondary-cta download">
Download SMAPI @Model.BetaVersion.Version
@if (!string.IsNullOrWhiteSpace(Model.BetaBlurb))
{
<br /><small>@Model.BetaBlurb</small>
}
</a><br />
<div class="dropdown-content">
<a href="https://www.nexusmods.com/stardewvalley/mods/2400"><img src="Content/images/nexus-icon.png" /> Download from Nexus</a>
<a href="@Model.BetaVersion.DownloadUrl"><img src="Content/images/direct-download-icon.png" /> Direct download</a>
</div>
</div><br />
}
<a href="https://stardewvalleywiki.com/Modding:Player_Guide" class="secondary-cta">Player guide</a><br />
<div><a href="https://stardewvalleywiki.com/Modding:Player_Guide" class="secondary-cta">Player guide</a></div>
<div class="sublinks">
<a href="https://github.com/Pathoschild/SMAPI">source code</a> | <a href="@(new UriBuilder(SiteConfig.Value.RootUrl) { Path = "privacy" }.Uri)">privacy</a>
</div>
<img id="pufferchick" src="Content/images/pufferchick.png" />
</div>
<h2 id="help">Get help</h2>
<ul>
<li><a href="https://stardewvalleywiki.com/Modding:SMAPI_compatibility">Mod compatibility list</a></li>
<li><a href="@SiteConfig.Value.ModListUrl">Mod compatibility list</a></li>
<li>Get help <a href="https://stardewvalleywiki.com/Modding:Community#Discord">on Discord</a> or <a href="https://community.playstarbound.com/threads/smapi-stardew-modding-api.108375/">in the forums</a></li>
</ul>
@ -49,7 +61,7 @@
<div class="github-description">
@Html.Raw(Markdig.Markdown.ToHtml(Model.StableVersion.Description))
</div>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="https://stardewvalleywiki.com/Modding:SMAPI_compatibility">mod compatibility list</a> for more info.</p>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@SiteConfig.Value.ModListUrl">mod compatibility list</a> for more info.</p>
}
else
{
@ -58,13 +70,13 @@ else
<div class="github-description">
@Html.Raw(Markdig.Markdown.ToHtml(Model.StableVersion.Description))
</div>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="https://stardewvalleywiki.com/Modding:SMAPI_compatibility">mod compatibility list</a> for more info.</p>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@SiteConfig.Value.ModListUrl">mod compatibility list</a> for more info.</p>
<h3>SMAPI @Model.BetaVersion.Version?</h3>
<div class="github-description">
@Html.Raw(Markdig.Markdown.ToHtml(Model.BetaVersion.Description))
</div>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="https://stardewvalleywiki.com/Modding:SMAPI_compatibility">mod compatibility list</a> for more info.</p>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@SiteConfig.Value.ModListUrl">mod compatibility list</a> for more info.</p>
}
<h2 id="donate">Donate to support SMAPI ♥</h2>
@ -87,13 +99,13 @@ else
<p>
Special thanks to
AbroadKew,
acerbicon,
<a href="https://www.nexusmods.com/stardewvalley/users/31393530">ChefRude</a>,
cheesysteak,
hawkfalcon,
jwdred,
KNakamura,
Kono Tyran,
<a href="https://www.nexusmods.com/users/12252523">Karmylla</a>,
Pucklynn,
Robby LaFarge,
and a few anonymous users for their ongoing support; you're awesome! 🏅

View File

@ -0,0 +1,43 @@
@using Microsoft.Extensions.Options
@using StardewModdingAPI.Web.Framework.ConfigModels
@inject IOptions<SiteConfig> SiteConfig
@{
ViewData["Title"] = "SMAPI privacy notes";
}
@section Head {
<link rel="stylesheet" href="~/Content/css/privacy.css" />
}
&larr; <a href="@SiteConfig.Value.RootUrl">back to SMAPI page</a>
<p>SMAPI is an <a href="https://github.com/Pathoschild/SMAPI">open-source</a> and non-profit project. Your privacy is important, so this page explains what information SMAPI uses and transmits. <strong>This page is informational only, it's not a legal document.</strong></p>
<h2>Principles</h2>
<ol>
<li>SMAPI collects the minimum information needed to enable its features (see below).</li>
<li>SMAPI does not collect telemetry, analytics, etc.</li>
<li>SMAPI will never sell your information.</li>
</ol>
<h2>Data collected and transmitted</h2>
<h3 id="web-logging">Web logging</h3>
<p>This website and SMAPI's web API are hosted by Amazon Web Services. Their servers may automatically collect diagnostics like your IP address, but this information is not visible to SMAPI's web application or developers. For more information, see the <a href="https://aws.amazon.com/privacy/">Amazon Privacy Notice</a>.</p>
<h3>Update checks</h3>
<p>SMAPI notifies you when there's a new version of SMAPI or your mods available. To do so, it sends your SMAPI and mod versions to its web API. No personal information is stored by the web application, but see <em><a href="#web-logging">web logging</a></em>.</p>
<p>You can disable update checks, and no information will be transmitted to the web API. To do so:</p>
<ol>
<li><a href="https://stardewvalleywiki.com/Modding:Game_folder">find your game folder</a>;</li>
<li>open the <code>smapi-internal/StardewModdingAPI.config.json</code> file in a text editor;</li>
<li>change <code>"CheckForUpdates": true</code> to <code>"CheckForUpdates": false</code>.</li>
</ol>
<h3>Log parser</h3>
<p>The <a href="https://log.smapi.io/">log parser page</a> lets you store a log file for analysis and sharing. The log data is stored indefinitely in an obfuscated form as unlisted pastes in <a href="https://pastebin.com/">Pastebin</a>. No personal information is stored by the log parser beyond what you choose to upload, but see <em><a href="#web-logging">web logging</a></em> and the <a href="https://pastebin.com/doc_privacy_statement">Pastebin Privacy Statement</a>.</p>
<h3>Multiplayer sync</h3>
<p>As part of its multiplayer API, SMAPI transmits basic context to players you connect to (mainly your OS, SMAPI version, game version, and installed mods). This is used to enable multiplayer features like inter-mod messages, compatibility checks, etc. Although this information is normally hidden from players, it may be visible due to mods or configuration changes.</p>
<h3>Custom mods</h3>
<p><strong>Mods may collect and transmit any information. Mods (except those provided as part of the SMAPI download) are not covered by this page. Install third-party mods at your own risk.</strong></p>

View File

@ -18,8 +18,8 @@
<meta name="robots" content="noindex" />
}
<link rel="stylesheet" href="~/Content/css/log-parser.css?r=20180627" />
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js" crossorigin="anonymous"></script>
<script src="~/Content/js/log-parser.js?r=20180627"></script>
<script>
$(function() {
@ -84,7 +84,7 @@ else if (Model.ParsedLog?.IsValid == true)
On Mac:
<ol>
<li>Open the Finder app.</li>
<li>Click <em>Go</em> at the top, then <em>Enter Location</em>.</li>
<li>Click <em>Go</em> at the top, then <em>Go to Folder</em>.</li>
<li>Enter this exact text: <pre>~/.config/StardewValley/ErrorLogs</pre></li>
<li>The log file is <code>SMAPI-crash.txt</code> if it exists, otherwise <code>SMAPI-latest.txt</code>.</li>
</ol>
@ -145,14 +145,14 @@ else if (Model.ParsedLog?.IsValid == true)
@if (!Model.ShowRaw)
{
<span class="notice txt"><i>click any mod to filter</i></span>
<span class="notice btn txt" v-on:click="showAllMods" v-show="stats.modsHidden > 0">show all</span>
<span class="notice btn txt" v-on:click="hideAllMods" v-show="stats.modsShown > 0 && stats.modsHidden > 0">hide all</span>
<span class="notice btn txt" v-on:click="showAllMods" v-bind:class="{ invisible: !anyModsHidden }">show all</span>
<span class="notice btn txt" v-on:click="hideAllMods" v-bind:class="{ invisible: !anyModsShown || !anyModsHidden }">hide all</span>
}
</caption>
@foreach (var mod in Model.ParsedLog.Mods.Where(p => p.ContentPackFor == null))
{
<tr v-on:click="toggleMod('@Model.GetSlug(mod.Name)')" class="mod-entry" v-bind:class="{ hidden: !showMods['@Model.GetSlug(mod.Name)'] }">
<td><input type="checkbox" v-bind:checked="showMods['@Model.GetSlug(mod.Name)']" v-show="anyModsHidden" /></td>
<td><input type="checkbox" v-bind:checked="showMods['@Model.GetSlug(mod.Name)']" v-bind:class="{ invisible: !anyModsHidden }" /></td>
<td v-pre>
<strong>@mod.Name</strong> @mod.Version
@if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[] contentPackList))
@ -165,7 +165,18 @@ else if (Model.ParsedLog?.IsValid == true)
</div>
}
</td>
<td v-pre>@mod.Author</td>
<td v-pre>
@mod.Author
@if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out contentPackList))
{
<div class="content-packs">
@foreach (var contentPack in contentPackList)
{
<text>+ @contentPack.Author</text><br />
}
</div>
}
</td>
@if (mod.Errors == 0)
{
<td v-pre class="color-green">no errors</td>

View File

@ -0,0 +1,94 @@
@using Newtonsoft.Json
@model StardewModdingAPI.Web.ViewModels.ModListModel
@{
ViewData["Title"] = "SMAPI mod compatibility";
}
@section Head {
<link rel="stylesheet" href="~/Content/css/mods.css?r=20181109" />
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/tablesorter@2.31.0/dist/js/jquery.tablesorter.combined.min.js" crossorigin="anonymous"></script>
<script src="~/Content/js/mods.js?r=20181109"></script>
<script>
$(function() {
var data = @Json.Serialize(Model.Mods, new JsonSerializerSettings { Formatting = Formatting.None });
smapi.modList(data);
});
</script>
}
<div id="intro">
<p>This page lists all known SMAPI mods, whether they're compatible with the latest versions of Stardew Valley and SMAPI, and how to fix broken mods if possible. The list is updated every few days. (You can help <a href="https://stardewvalleywiki.com/Modding:SMAPI_compatibility">edit this list</a>!)</p>
<p>If a mod doesn't work after following the instructions below, check <a href="https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting">the troubleshooting guide</a> or <a href="https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting#Ask_for_help">ask for help</a>.</p>
@if (Model.BetaVersion != null)
{
<p id="beta-blurb"><strong>Note:</strong> "SDV beta only" means Stardew Valley @Model.BetaVersion-beta; if you didn't opt in to the beta, you have the stable version and can ignore that line. If a mod doesn't have a "SDV beta only" line, the compatibility applies to both versions of the game.</p>
}
</div>
<div id="app">
<div id="options">
<div>
<label for="search-box">Search: </label>
<input type="text" id="search-box" v-model="search" v-on:input="applyFilters" />
</div>
<div id="filter-area">
<input type="checkbox" id="show-advanced" v-model="showAdvanced" />
<label for="show-advanced">show detailed options</label>
<div id="filters" v-show="showAdvanced">
<div v-for="(filterGroup, key) in filters">
{{key}}: <span v-for="filter in filterGroup" v-bind:class="{ active: filter.value }"><input type="checkbox" v-bind:id="filter.id" v-model="filter.value" v-on:change="applyFilters" /> <label v-bind:for="filter.id">{{filter.label}}</label></span>
</div>
</div>
</div>
</div>
<div id="mod-count" v-show="showAdvanced">{{visibleCount}} mods shown.</div>
<table class="wikitable" id="mod-list">
<thead>
<tr>
<th>mod name</th>
<th>links</th>
<th>author</th>
<th>compatibility</th>
<th v-show="showAdvanced">broke in</th>
<th v-show="showAdvanced">code</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr v-for="mod in mods" :key="mod.Name" v-bind:id="mod.Slug" :key="mod.Slug" v-bind:data-status="mod.BetaCompatibility != null ? mod.BetaCompatibility.Status : mod.Compatibility.Status" v-show="mod.Visible">
<td>
{{mod.Name}}
<small class="mod-alt-names" v-if="mod.AlternateNames">(aka {{mod.AlternateNames}})</small>
</td>
<td class="mod-page-links">
<span v-for="(link, i) in mod.ModPages">
<a v-bind:href="link.Url">{{link.Text}}</a>{{i < mod.ModPages.length - 1 ? ', ' : ''}}
</span>
</td>
<td>
{{mod.Author}}
<small class="mod-alt-authors" v-if="mod.AlternateAuthors">(aka {{mod.AlternateAuthors}})</small>
</td>
<td>
<div v-html="mod.Compatibility.Summary"></div>
<div v-if="mod.BetaCompatibility">
<strong v-if="mod.BetaCompatibility">SDV beta only:</strong>
<span v-html="mod.BetaCompatibility.Summary"></span>
</div>
<div v-for="(warning, i) in mod.Warnings">⚠ {{warning}}</div>
</td>
<td class="mod-broke-in" v-html="mod.BetaCompatibility ? mod.BetaCompatibility.BrokeIn : mod.Compatibility.BrokeIn" v-show="showAdvanced"></td>
<td v-show="showAdvanced">
<span v-if="mod.SourceUrl"><a v-bind:href="mod.SourceUrl">source</a></span>
<span v-else class="mod-closed-source">no source</span>
</td>
<td>
<small><a v-bind:href="'#' + mod.Slug">#</a></small>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -16,6 +16,7 @@
<h4>SMAPI</h4>
<ul>
<li><a href="@SiteConfig.Value.RootUrl">About SMAPI</a></li>
<li><a href="@SiteConfig.Value.ModListUrl">Mod compatibility</a></li>
<li><a href="@SiteConfig.Value.LogParserUrl">Log parser</a></li>
<li><a href="https://stardewvalleywiki.com/Modding:Index">Docs</a></li>
</ul>

View File

@ -19,8 +19,10 @@
"Site": {
"RootUrl": "http://localhost:59482/",
"ModListUrl": "http://localhost:59482/mods/",
"LogParserUrl": "http://localhost:59482/log/",
"EnableSmapiBeta": false
"BetaEnabled": false,
"BetaBlurb": null
},
"ApiClients": {

View File

@ -16,8 +16,10 @@
"Site": {
"RootUrl": null, // see top note
"ModListUrl": null, // see top note
"LogParserUrl": null, // see top note
"EnableSmapiBeta": null // see top note
"BetaEnabled": null, // see top note
"BetaBlurb": null // see top note
},
"ApiClients": {
@ -46,11 +48,6 @@
"SuccessCacheMinutes": 60,
"ErrorCacheMinutes": 5,
"SemanticVersionRegex": "^(?>(?<major>0|[1-9]\\d*))\\.(?>(?<minor>0|[1-9]\\d*))(?>(?:\\.(?<patch>0|[1-9]\\d*))?)(?:-(?<prerelease>(?>[a-z0-9]+[\\-\\.]?)+))?$",
"ChucklefishKey": "Chucklefish",
"GitHubKey": "GitHub",
"NexusKey": "Nexus",
"WikiCompatibilityPageUrl": "https://smapi.io/compat"
"CompatibilityPageUrl": "https://mods.smapi.io"
}
}

View File

@ -93,6 +93,11 @@ h1 {
display: block;
}
.sublinks {
font-size: 0.9em;
margin-bottom: 1em;
}
/*********
** Subsections
*********/

View File

@ -63,6 +63,10 @@ table#metadata, table#mods {
box-shadow: 1px 1px 1px 1px #dddddd;
}
.invisible {
visibility: hidden;
}
#mods {
min-width: 400px;
}

View File

@ -0,0 +1,135 @@
/*********
** Intro
*********/
#content {
max-width: calc(100% - 2em); /* allow for wider table if room available */
}
#intro {
width: 50em;
}
#beta-blurb {
margin-bottom: 2em;
padding: 1em;
border: 3px solid darkgreen;
}
table.wikitable {
background-color:#f8f9fa;
color:#222;
border:1px solid #a2a9b1;
border-collapse:collapse
}
table.wikitable > tr > th,
table.wikitable > tr > td,
table.wikitable > * > tr > th,
table.wikitable > * > tr > td {
border:1px solid #a2a9b1;
padding:0.2em 0.4em
}
table.wikitable > tr > th,
table.wikitable > * > tr > th {
background-color:#eaecf0;
}
table.wikitable > caption {
font-weight:bold
}
#options {
margin-bottom: 1em;
}
#options #filter-area {
opacity: 0.7;
}
#options #filters {
margin-left: 2em;
padding-left: 0.5em;
border-left: 2px solid gray;
}
#options #filters span {
padding: 2px;
margin: 2px;
display: inline-block;
border-radius: 3px;
color: #000;
border-color: #880000;
background-color: #fcc;
font-size: 0.9em;
}
#options #filters span.active {
background: #cfc;
}
#mod-count {
font-size: 0.8em;
opacity: 0.5;
}
#mod-list {
font-size: 0.9em;
}
#mod-list th.header {
background-repeat: no-repeat;
background-position: center right;
cursor: pointer;
background-image: url(data:image/gif;base64,R0lGODlhFQAJAIABAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjAxODAxMTc0MDcyMDY4MTE4OEM2REYyN0ExMDhBNDJFIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjdCNTAyODcwMEY4NjExRTBBMzkyQzAyM0E1RDk3RDc3IiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjdCNTAyODZGMEY4NjExRTBBMzkyQzAyM0E1RDk3RDc3IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzUgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDE4MDExNzQwNzIwNjgxMTg4QzZERjI3QTEwOEE0MkUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDE4MDExNzQwNzIwNjgxMTg4QzZERjI3QTEwOEE0MkUiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4B//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAAAh+QQBAAABACwAAAAAFQAJAAACF4yPgMsJ2mJ4VDKKrd4GVz5lYPeMiVUAADs=);
padding-right: 1.5em;
}
#mod-list th.headerSortUp {
background-image: url(data:image/gif;base64,R0lGODlhFQAEAIABAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjAzODAxMTc0MDcyMDY4MTE4OEM2REYyN0ExMDhBNDJFIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjdCNTAyODc0MEY4NjExRTBBMzkyQzAyM0E1RDk3RDc3IiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjdCNTAyODczMEY4NjExRTBBMzkyQzAyM0E1RDk3RDc3IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzUgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDM4MDExNzQwNzIwNjgxMTg4QzZERjI3QTEwOEE0MkUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDM4MDExNzQwNzIwNjgxMTg4QzZERjI3QTEwOEE0MkUiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4B//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAAAh+QQBAAABACwAAAAAFQAEAAACDYwfoAvoz9qbZ9FrJC0AOw==);
}
#mod-list th.headerSortDown {
background-image: url(data:image/gif;base64,R0lGODlhFQAEAIABAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjAyODAxMTc0MDcyMDY4MTE4OEM2REYyN0ExMDhBNDJFIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjhFNzNGQjI3MEY4NjExRTBBMzkyQzAyM0E1RDk3RDc3IiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjhFNzNGQjI2MEY4NjExRTBBMzkyQzAyM0E1RDk3RDc3IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzUgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDI4MDExNzQwNzIwNjgxMTg4QzZERjI3QTEwOEE0MkUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDI4MDExNzQwNzIwNjgxMTg4QzZERjI3QTEwOEE0MkUiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4B//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAAAh+QQBAAABACwAAAAAFQAEAAACDYyPAcmtsJyDVDKKWQEAOw==);
}
#mod-list .mod-page-links,
#mod-list .mod-broke-in {
font-size: 0.9em;
}
#mod-list .mod-alt-authors,
#mod-list .mod-alt-names {
font-size: 0.8em;
}
#mod-list .mod-alt-authors,
#mod-list .mod-alt-names {
display: block;
}
#mod-list tr[data-status="ok"],
#mod-list tr[data-status="optional"] {
background: #BFB;
}
#mod-list tr[data-status="workaround"],
#mod-list tr[data-status="unofficial"] {
background: #FFFEC6;
}
#mod-list tr[data-status="broken"] {
background: #FBB;
}
#mod-list tr[data-status="obsolete"],
#mod-list tr[data-status="abandoned"] {
background: #BBB;
opacity: 0.7;
}
#mod-list .mod-closed-source {
color: red;
font-size: 0.8em;
opacity: 0.5;
}

View File

@ -0,0 +1,3 @@
h3 {
border: 0;
}

View File

@ -0,0 +1,205 @@
/* globals $ */
var smapi = smapi || {};
var app;
smapi.modList = function (mods) {
// init data
var data = {
mods: mods,
visibleCount: mods.length,
showAdvanced: false,
filters: {
source: {
open: {
label: "open",
id: "show-open-source",
value: true
},
closed: {
label: "closed",
id: "show-closed-source",
value: true
}
},
status: {
ok: {
label: "ok",
id: "show-status-ok",
value: true
},
optional: {
label: "optional",
id: "show-status-optional",
value: true
},
unofficial: {
label: "unofficial",
id: "show-status-unofficial",
value: true
},
workaround: {
label: "workaround",
id: "show-status-workaround",
value: true
},
broken: {
label: "broken",
id: "show-status-broken",
value: true
},
abandoned: {
label: "abandoned",
id: "show-status-abandoned",
value: true
},
obsolete: {
label: "obsolete",
id: "show-status-obsolete",
value: true
}
},
download: {
chucklefish: {
label: "Chucklefish",
id: "show-chucklefish",
value: true
},
nexus: {
label: "Nexus",
id: "show-nexus",
value: true
},
custom: {
label: "custom",
id: "show-custom",
value: true
}
}
},
search: ""
};
for (var i = 0; i < data.mods.length; i++) {
var mod = mods[i];
// set initial visibility
mod.Visible = true;
// concatenate searchable text
mod.SearchableText = [mod.Name, mod.AlternateNames, mod.Author, mod.AlternateAuthors, mod.Compatibility.Summary, mod.BrokeIn];
if (mod.Compatibility.UnofficialVersion)
mod.SearchableText.push(mod.Compatibility.UnofficialVersion);
if (mod.BetaCompatibility) {
mod.SearchableText.push(mod.BetaCompatibility.Summary);
if (mod.BetaCompatibility.UnofficialVersion)
mod.SearchableText.push(mod.BetaCompatibility.UnofficialVersion);
}
for (var p = 0; p < mod.ModPages; p++)
mod.SearchableField.push(mod.ModPages[p].Text);
mod.SearchableText = mod.SearchableText.join(" ").toLowerCase();
}
// init app
app = new Vue({
el: "#app",
data: data,
mounted: function() {
// enable table sorting
$("#mod-list").tablesorter({
cssHeader: "header",
cssAsc: "headerSortUp",
cssDesc: "headerSortDown"
});
// put focus in textbox for quick search
if (!location.hash)
$("#search-box").focus();
// jump to anchor (since table is added after page load)
if (location.hash) {
var row = $(location.hash).get(0);
if (row)
row.scrollIntoView();
}
},
methods: {
/**
* Update the visibility of all mods based on the current search text and filters.
*/
applyFilters: function () {
// get search terms
var words = data.search.toLowerCase().split(" ");
// apply criteria
data.visibleCount = data.mods.length;
for (var i = 0; i < data.mods.length; i++) {
var mod = data.mods[i];
mod.Visible = true;
// check filters
if (!this.matchesFilters(mod)) {
mod.Visible = false;
data.visibleCount--;
continue;
}
// check search terms (all search words should match)
if (words.length) {
for (var w = 0; w < words.length; w++) {
if (mod.SearchableText.indexOf(words[w]) === -1) {
mod.Visible = false;
data.visibleCount--;
break;
}
}
}
}
},
/**
* Get whether a mod matches the current filters.
* @param {object} mod The mod to check.
* @returns {bool} Whether the mod matches the filters.
*/
matchesFilters: function(mod) {
var filters = data.filters;
// check source
if (!filters.source.open.value && mod.SourceUrl)
return false;
if (!filters.source.closed.value && !mod.SourceUrl)
return false;
// check status
var status = (mod.BetaCompatibility || mod.Compatibility).Status;
if (filters.status[status] && !filters.status[status].value)
return false;
// check download sites
var ignoreSites = [];
if (!filters.download.chucklefish.value)
ignoreSites.push("Chucklefish");
if (!filters.download.nexus.value)
ignoreSites.push("Nexus");
if (!filters.download.custom.value)
ignoreSites.push("custom");
if (ignoreSites.length) {
var anyLeft = false;
for (var i = 0; i < mod.ModPageSites.length; i++) {
if (ignoreSites.indexOf(mod.ModPageSites[i]) === -1) {
anyLeft = true;
break;
}
}
if (!anyLeft)
return false;
}
return true;
}
}
});
};

File diff suppressed because it is too large Load Diff

View File

@ -33,7 +33,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Internal", "Internal", "{82
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{EB35A917-67B9-4EFA-8DFC-4FB49B3949BB}"
ProjectSection(SolutionItems) = preProject
..\docs\CONTRIBUTING.md = ..\docs\CONTRIBUTING.md
..\docs\mod-build-config.md = ..\docs\mod-build-config.md
..\docs\README.md = ..\docs\README.md
..\docs\release-notes.md = ..\docs\release-notes.md
@ -65,6 +64,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StardewModdingAPI.Toolkit",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StardewModdingAPI.Toolkit.CoreInterfaces", "StardewModdingAPI.Toolkit.CoreInterfaces\StardewModdingAPI.Toolkit.CoreInterfaces.csproj", "{D5CFD923-37F1-4BC3-9BE8-E506E202AC28}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{4B1CEB70-F756-4A57-AAE8-8CD78C475F25}"
ProjectSection(SolutionItems) = preProject
..\.github\CONTRIBUTING.md = ..\.github\CONTRIBUTING.md
..\.github\SUPPORT.md = ..\.github\SUPPORT.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{F4453AB6-D7D6-447F-A973-956CC777968F}"
ProjectSection(SolutionItems) = preProject
..\.github\ISSUE_TEMPLATE\bug_report.md = ..\.github\ISSUE_TEMPLATE\bug_report.md
..\.github\ISSUE_TEMPLATE\feature_request.md = ..\.github\ISSUE_TEMPLATE\feature_request.md
..\.github\ISSUE_TEMPLATE\general.md = ..\.github\ISSUE_TEMPLATE\general.md
EndProjectSection
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
SMAPI.Internal\SMAPI.Internal.projitems*{443ddf81-6aaf-420a-a610-3459f37e5575}*SharedItemsImports = 4
@ -130,6 +142,8 @@ Global
{EB35A917-67B9-4EFA-8DFC-4FB49B3949BB} = {86C452BE-D2D8-45B4-B63F-E329EB06CEDA}
{09CF91E5-5BAB-4650-A200-E5EA9A633046} = {86C452BE-D2D8-45B4-B63F-E329EB06CEDA}
{0CF97929-B0D0-4D73-B7BF-4FF7191035F9} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
{4B1CEB70-F756-4A57-AAE8-8CD78C475F25} = {86C452BE-D2D8-45B4-B63F-E329EB06CEDA}
{F4453AB6-D7D6-447F-A973-956CC777968F} = {4B1CEB70-F756-4A57-AAE8-8CD78C475F25}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {70143042-A862-47A8-A677-7C819DDC90DC}

View File

@ -29,10 +29,10 @@ namespace StardewModdingAPI
** Public
****/
/// <summary>SMAPI's current semantic version.</summary>
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.7.0");
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.8.1");
/// <summary>The minimum supported version of Stardew Valley.</summary>
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.28");
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.32");
/// <summary>The maximum supported version of Stardew Valley.</summary>
public static ISemanticVersion MaximumGameVersion { get; } = null;
@ -64,11 +64,14 @@ namespace StardewModdingAPI
/// <summary>The URL of the SMAPI home page.</summary>
internal const string HomePageUrl = "https://smapi.io";
/// <summary>The absolute path to the folder containing SMAPI's internal files.</summary>
internal static readonly string InternalFilesPath = Program.DllSearchPath;
/// <summary>The file path for the SMAPI configuration file.</summary>
internal static string ApiConfigPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.config.json");
internal static string ApiConfigPath => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.config.json");
/// <summary>The file path for the SMAPI metadata file.</summary>
internal static string ApiMetadataPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.metadata.json");
internal static string ApiMetadataPath => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.metadata.json");
/// <summary>The filename prefix used for all SMAPI logs.</summary>
internal static string LogNamePrefix { get; } = "SMAPI-";
@ -79,14 +82,14 @@ namespace StardewModdingAPI
/// <summary>The filename extension for SMAPI log files.</summary>
internal static string LogExtension { get; } = "txt";
/// <summary>A copy of the log leading up to the previous fatal crash, if any.</summary>
/// <summary>The file path for the log containing the previous fatal crash, if any.</summary>
internal static string FatalCrashLog => Path.Combine(Constants.LogDir, "SMAPI-crash.txt");
/// <summary>The file path which stores a fatal crash message for the next run.</summary>
internal static string FatalCrashMarker => Path.Combine(Constants.ExecutionPath, "StardewModdingAPI.crash.marker");
internal static string FatalCrashMarker => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.crash.marker");
/// <summary>The file path which stores the detected update version for the next run.</summary>
internal static string UpdateMarker => Path.Combine(Constants.ExecutionPath, "StardewModdingAPI.update.marker");
internal static string UpdateMarker => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.update.marker");
/// <summary>The full path to the folder containing mods.</summary>
internal static string DefaultModsPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods");
@ -104,6 +107,26 @@ namespace StardewModdingAPI
/*********
** Internal methods
*********/
/// <summary>Get the SMAPI version to recommend for an older game version, if any.</summary>
/// <param name="version">The game version to search.</param>
/// <returns>Returns the compatible SMAPI version, or <c>null</c> if none was found.</returns>
internal static ISemanticVersion GetCompatibleApiVersion(ISemanticVersion version)
{
switch (version.ToString())
{
case "1.3.28":
return new SemanticVersion(2, 7, 0);
case "1.2.30":
case "1.2.31":
case "1.2.32":
case "1.2.33":
return new SemanticVersion(2, 5, 5);
}
return null;
}
/// <summary>Get metadata for mapping assemblies to the current platform.</summary>
/// <param name="targetPlatform">The target game platform.</param>
internal static PlatformAssemblyMap GetAssemblyMap(Platform targetPlatform)

View File

@ -17,7 +17,7 @@ namespace StardewModdingAPI
public static bool IsWorldReady { get; internal set; }
/// <summary>Whether <see cref="IsWorldReady"/> is true and the player is free to act in the world (no menu is displayed, no cutscene is in progress, etc).</summary>
public static bool IsPlayerFree => Context.IsWorldReady && Game1.activeClickableMenu == null && !Game1.dialogueUp && (!Game1.eventUp || Game1.isFestival());
public static bool IsPlayerFree => Context.IsWorldReady && Game1.currentLocation != null && Game1.activeClickableMenu == null && !Game1.dialogueUp && (!Game1.eventUp || Game1.isFestival());
/// <summary>Whether <see cref="IsPlayerFree"/> is true and the player is free to move (e.g. not using a tool).</summary>
public static bool CanPlayerMove => Context.IsPlayerFree && Game1.player.CanMove;

View File

@ -0,0 +1,26 @@
using StardewValley;
namespace StardewModdingAPI.Enums
{
/// <summary>The player skill types.</summary>
public enum SkillType
{
/// <summary>The combat skill.</summary>
Combat = Farmer.combatSkill,
/// <summary>The farming skill.</summary>
Farming = Farmer.farmingSkill,
/// <summary>The fishing skill.</summary>
Fishing = Farmer.fishingSkill,
/// <summary>The foraging skill.</summary>
Foraging = Farmer.foragingSkill,
/// <summary>The mining skill.</summary>
Mining = Farmer.miningSkill,
/// <summary>The luck skill.</summary>
Luck = Farmer.luckSkill
}
}

View File

@ -7,7 +7,7 @@ using StardewValley.Buildings;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for a <see cref="IWorldEvents.BuildingListChanged"/> event.</summary>
public class WorldBuildingListChangedEventArgs : EventArgs
public class BuildingListChangedEventArgs : EventArgs
{
/*********
** Accessors
@ -29,7 +29,7 @@ namespace StardewModdingAPI.Events
/// <param name="location">The location which changed.</param>
/// <param name="added">The buildings added to the location.</param>
/// <param name="removed">The buildings removed from the location.</param>
public WorldBuildingListChangedEventArgs(GameLocation location, IEnumerable<Building> added, IEnumerable<Building> removed)
public BuildingListChangedEventArgs(GameLocation location, IEnumerable<Building> added, IEnumerable<Building> removed)
{
this.Location = location;
this.Added = added.ToArray();

View File

@ -4,7 +4,7 @@ using StardewModdingAPI.Framework.Input;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments when a button is pressed.</summary>
public class InputButtonPressedEventArgs : EventArgs
public class ButtonPressedEventArgs : EventArgs
{
/*********
** Properties
@ -30,7 +30,7 @@ namespace StardewModdingAPI.Events
/// <param name="button">The button on the controller, keyboard, or mouse.</param>
/// <param name="cursor">The cursor position.</param>
/// <param name="inputState">The game's current input state.</param>
internal InputButtonPressedEventArgs(SButton button, ICursorPosition cursor, SInputState inputState)
internal ButtonPressedEventArgs(SButton button, ICursorPosition cursor, SInputState inputState)
{
this.Button = button;
this.Cursor = cursor;

View File

@ -4,7 +4,7 @@ using StardewModdingAPI.Framework.Input;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments when a button is released.</summary>
public class InputButtonReleasedEventArgs : EventArgs
public class ButtonReleasedEventArgs : EventArgs
{
/*********
** Properties
@ -30,7 +30,7 @@ namespace StardewModdingAPI.Events
/// <param name="button">The button on the controller, keyboard, or mouse.</param>
/// <param name="cursor">The cursor position.</param>
/// <param name="inputState">The game's current input state.</param>
internal InputButtonReleasedEventArgs(SButton button, ICursorPosition cursor, SInputState inputState)
internal ButtonReleasedEventArgs(SButton button, ICursorPosition cursor, SInputState inputState)
{
this.Button = button;
this.Cursor = cursor;

View File

@ -19,8 +19,8 @@ namespace StardewModdingAPI.Events
/// <summary>Raised after the content language changes.</summary>
public static event EventHandler<EventArgsValueChanged<string>> AfterLocaleChanged
{
add => ContentEvents.EventManager.Content_LocaleChanged.Add(value);
remove => ContentEvents.EventManager.Content_LocaleChanged.Remove(value);
add => ContentEvents.EventManager.Legacy_LocaleChanged.Add(value);
remove => ContentEvents.EventManager.Legacy_LocaleChanged.Remove(value);
}

View File

@ -20,57 +20,57 @@ namespace StardewModdingAPI.Events
/// <summary>Raised when the <see cref="KeyboardState"/> changes. That happens when the player presses or releases a key.</summary>
public static event EventHandler<EventArgsKeyboardStateChanged> KeyboardChanged
{
add => ControlEvents.EventManager.Legacy_Control_KeyboardChanged.Add(value);
remove => ControlEvents.EventManager.Legacy_Control_KeyboardChanged.Remove(value);
add => ControlEvents.EventManager.Legacy_KeyboardChanged.Add(value);
remove => ControlEvents.EventManager.Legacy_KeyboardChanged.Remove(value);
}
/// <summary>Raised after the player presses a keyboard key.</summary>
public static event EventHandler<EventArgsKeyPressed> KeyPressed
{
add => ControlEvents.EventManager.Legacy_Control_KeyPressed.Add(value);
remove => ControlEvents.EventManager.Legacy_Control_KeyPressed.Remove(value);
add => ControlEvents.EventManager.Legacy_KeyPressed.Add(value);
remove => ControlEvents.EventManager.Legacy_KeyPressed.Remove(value);
}
/// <summary>Raised after the player releases a keyboard key.</summary>
public static event EventHandler<EventArgsKeyPressed> KeyReleased
{
add => ControlEvents.EventManager.Legacy_Control_KeyReleased.Add(value);
remove => ControlEvents.EventManager.Legacy_Control_KeyReleased.Remove(value);
add => ControlEvents.EventManager.Legacy_KeyReleased.Add(value);
remove => ControlEvents.EventManager.Legacy_KeyReleased.Remove(value);
}
/// <summary>Raised when the <see cref="MouseState"/> changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button.</summary>
public static event EventHandler<EventArgsMouseStateChanged> MouseChanged
{
add => ControlEvents.EventManager.Legacy_Control_MouseChanged.Add(value);
remove => ControlEvents.EventManager.Legacy_Control_MouseChanged.Remove(value);
add => ControlEvents.EventManager.Legacy_MouseChanged.Add(value);
remove => ControlEvents.EventManager.Legacy_MouseChanged.Remove(value);
}
/// <summary>The player pressed a controller button. This event isn't raised for trigger buttons.</summary>
public static event EventHandler<EventArgsControllerButtonPressed> ControllerButtonPressed
{
add => ControlEvents.EventManager.Legacy_Control_ControllerButtonPressed.Add(value);
remove => ControlEvents.EventManager.Legacy_Control_ControllerButtonPressed.Remove(value);
add => ControlEvents.EventManager.Legacy_ControllerButtonPressed.Add(value);
remove => ControlEvents.EventManager.Legacy_ControllerButtonPressed.Remove(value);
}
/// <summary>The player released a controller button. This event isn't raised for trigger buttons.</summary>
public static event EventHandler<EventArgsControllerButtonReleased> ControllerButtonReleased
{
add => ControlEvents.EventManager.Legacy_Control_ControllerButtonReleased.Add(value);
remove => ControlEvents.EventManager.Legacy_Control_ControllerButtonReleased.Remove(value);
add => ControlEvents.EventManager.Legacy_ControllerButtonReleased.Add(value);
remove => ControlEvents.EventManager.Legacy_ControllerButtonReleased.Remove(value);
}
/// <summary>The player pressed a controller trigger button.</summary>
public static event EventHandler<EventArgsControllerTriggerPressed> ControllerTriggerPressed
{
add => ControlEvents.EventManager.Legacy_Control_ControllerTriggerPressed.Add(value);
remove => ControlEvents.EventManager.Legacy_Control_ControllerTriggerPressed.Remove(value);
add => ControlEvents.EventManager.Legacy_ControllerTriggerPressed.Add(value);
remove => ControlEvents.EventManager.Legacy_ControllerTriggerPressed.Remove(value);
}
/// <summary>The player released a controller trigger button.</summary>
public static event EventHandler<EventArgsControllerTriggerReleased> ControllerTriggerReleased
{
add => ControlEvents.EventManager.Legacy_Control_ControllerTriggerReleased.Add(value);
remove => ControlEvents.EventManager.Legacy_Control_ControllerTriggerReleased.Remove(value);
add => ControlEvents.EventManager.Legacy_ControllerTriggerReleased.Add(value);
remove => ControlEvents.EventManager.Legacy_ControllerTriggerReleased.Remove(value);
}

View File

@ -3,7 +3,7 @@ using System;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments when the in-game cursor is moved.</summary>
public class InputCursorMovedEventArgs : EventArgs
public class CursorMovedEventArgs : EventArgs
{
/*********
** Accessors
@ -21,7 +21,7 @@ namespace StardewModdingAPI.Events
/// <summary>Construct an instance.</summary>
/// <param name="oldPosition">The previous cursor position.</param>
/// <param name="newPosition">The new cursor position.</param>
public InputCursorMovedEventArgs(ICursorPosition oldPosition, ICursorPosition newPosition)
public CursorMovedEventArgs(ICursorPosition oldPosition, ICursorPosition newPosition)
{
this.OldPosition = oldPosition;
this.NewPosition = newPosition;

View File

@ -2,6 +2,6 @@ using System;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for an <see cref="IGameLoopEvents.Launched"/> event.</summary>
public class GameLoopLaunchedEventArgs : EventArgs { }
/// <summary>Event arguments for an <see cref="IGameLoopEvents.DayEnding"/> event.</summary>
public class DayEndingEventArgs : EventArgs { }
}

View File

@ -0,0 +1,7 @@
using System;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for an <see cref="IGameLoopEvents.DayStarted"/> event.</summary>
public class DayStartedEventArgs : EventArgs { }
}

View File

@ -6,7 +6,7 @@ using StardewValley;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for a <see cref="IWorldEvents.DebrisListChanged"/> event.</summary>
public class WorldDebrisListChangedEventArgs : EventArgs
public class DebrisListChangedEventArgs : EventArgs
{
/*********
** Accessors
@ -28,7 +28,7 @@ namespace StardewModdingAPI.Events
/// <param name="location">The location which changed.</param>
/// <param name="added">The debris added to the location.</param>
/// <param name="removed">The debris removed from the location.</param>
public WorldDebrisListChangedEventArgs(GameLocation location, IEnumerable<Debris> added, IEnumerable<Debris> removed)
public DebrisListChangedEventArgs(GameLocation location, IEnumerable<Debris> added, IEnumerable<Debris> removed)
{
this.Location = location;
this.Added = added.ToArray();

View File

@ -30,7 +30,7 @@ namespace StardewModdingAPI.Events
/// <summary>Construct an instance.</summary>
/// <param name="inventory">The player's inventory.</param>
/// <param name="changedItems">The inventory changes.</param>
public EventArgsInventoryChanged(IList<Item> inventory, List<ItemStackChange> changedItems)
public EventArgsInventoryChanged(IList<Item> inventory, ItemStackChange[] changedItems)
{
this.Inventory = inventory;
this.Added = changedItems.Where(n => n.ChangeType == ChangeType.Added).ToList();

View File

@ -1,4 +1,5 @@
using System;
using StardewModdingAPI.Enums;
namespace StardewModdingAPI.Events
{
@ -18,22 +19,22 @@ namespace StardewModdingAPI.Events
public enum LevelType
{
/// <summary>The combat skill.</summary>
Combat,
Combat = SkillType.Combat,
/// <summary>The farming skill.</summary>
Farming,
Farming = SkillType.Farming,
/// <summary>The fishing skill.</summary>
Fishing,
Fishing = SkillType.Fishing,
/// <summary>The foraging skill.</summary>
Foraging,
Foraging = SkillType.Foraging,
/// <summary>The mining skill.</summary>
Mining,
Mining = SkillType.Mining,
/// <summary>The luck skill.</summary>
Luck
Luck = SkillType.Luck
}

View File

@ -19,57 +19,57 @@ namespace StardewModdingAPI.Events
/// <summary>Raised when the game updates its state (≈60 times per second).</summary>
public static event EventHandler UpdateTick
{
add => GameEvents.EventManager.Game_UpdateTick.Add(value);
remove => GameEvents.EventManager.Game_UpdateTick.Remove(value);
add => GameEvents.EventManager.Legacy_UpdateTick.Add(value);
remove => GameEvents.EventManager.Legacy_UpdateTick.Remove(value);
}
/// <summary>Raised every other tick (≈30 times per second).</summary>
public static event EventHandler SecondUpdateTick
{
add => GameEvents.EventManager.Game_SecondUpdateTick.Add(value);
remove => GameEvents.EventManager.Game_SecondUpdateTick.Remove(value);
add => GameEvents.EventManager.Legacy_SecondUpdateTick.Add(value);
remove => GameEvents.EventManager.Legacy_SecondUpdateTick.Remove(value);
}
/// <summary>Raised every fourth tick (≈15 times per second).</summary>
public static event EventHandler FourthUpdateTick
{
add => GameEvents.EventManager.Game_FourthUpdateTick.Add(value);
remove => GameEvents.EventManager.Game_FourthUpdateTick.Remove(value);
add => GameEvents.EventManager.Legacy_FourthUpdateTick.Add(value);
remove => GameEvents.EventManager.Legacy_FourthUpdateTick.Remove(value);
}
/// <summary>Raised every eighth tick (≈8 times per second).</summary>
public static event EventHandler EighthUpdateTick
{
add => GameEvents.EventManager.Game_EighthUpdateTick.Add(value);
remove => GameEvents.EventManager.Game_EighthUpdateTick.Remove(value);
add => GameEvents.EventManager.Legacy_EighthUpdateTick.Add(value);
remove => GameEvents.EventManager.Legacy_EighthUpdateTick.Remove(value);
}
/// <summary>Raised every 15th tick (≈4 times per second).</summary>
public static event EventHandler QuarterSecondTick
{
add => GameEvents.EventManager.Game_QuarterSecondTick.Add(value);
remove => GameEvents.EventManager.Game_QuarterSecondTick.Remove(value);
add => GameEvents.EventManager.Legacy_QuarterSecondTick.Add(value);
remove => GameEvents.EventManager.Legacy_QuarterSecondTick.Remove(value);
}
/// <summary>Raised every 30th tick (≈twice per second).</summary>
public static event EventHandler HalfSecondTick
{
add => GameEvents.EventManager.Game_HalfSecondTick.Add(value);
remove => GameEvents.EventManager.Game_HalfSecondTick.Remove(value);
add => GameEvents.EventManager.Legacy_HalfSecondTick.Add(value);
remove => GameEvents.EventManager.Legacy_HalfSecondTick.Remove(value);
}
/// <summary>Raised every 60th tick (≈once per second).</summary>
public static event EventHandler OneSecondTick
{
add => GameEvents.EventManager.Game_OneSecondTick.Add(value);
remove => GameEvents.EventManager.Game_OneSecondTick.Remove(value);
add => GameEvents.EventManager.Legacy_OneSecondTick.Add(value);
remove => GameEvents.EventManager.Legacy_OneSecondTick.Remove(value);
}
/// <summary>Raised once after the game initialises and all <see cref="IMod.Entry"/> methods have been called.</summary>
public static event EventHandler FirstUpdateTick
{
add => GameEvents.EventManager.Game_FirstUpdateTick.Add(value);
remove => GameEvents.EventManager.Game_FirstUpdateTick.Remove(value);
add => GameEvents.EventManager.Legacy_FirstUpdateTick.Add(value);
remove => GameEvents.EventManager.Legacy_FirstUpdateTick.Remove(value);
}

View File

@ -0,0 +1,7 @@
using System;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for an <see cref="IGameLoopEvents.GameLaunched"/> event.</summary>
public class GameLaunchedEventArgs : EventArgs { }
}

View File

@ -19,8 +19,8 @@ namespace StardewModdingAPI.Events
/// <summary>Raised after the game window is resized.</summary>
public static event EventHandler Resize
{
add => GraphicsEvents.EventManager.Graphics_Resize.Add(value);
remove => GraphicsEvents.EventManager.Graphics_Resize.Remove(value);
add => GraphicsEvents.EventManager.Legacy_Resize.Add(value);
remove => GraphicsEvents.EventManager.Legacy_Resize.Remove(value);
}
/****
@ -29,15 +29,15 @@ namespace StardewModdingAPI.Events
/// <summary>Raised before drawing the world to the screen.</summary>
public static event EventHandler OnPreRenderEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPreRenderEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPreRenderEvent.Remove(value);
add => GraphicsEvents.EventManager.Legacy_OnPreRenderEvent.Add(value);
remove => GraphicsEvents.EventManager.Legacy_OnPreRenderEvent.Remove(value);
}
/// <summary>Raised after drawing the world to the screen.</summary>
public static event EventHandler OnPostRenderEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPostRenderEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPostRenderEvent.Remove(value);
add => GraphicsEvents.EventManager.Legacy_OnPostRenderEvent.Add(value);
remove => GraphicsEvents.EventManager.Legacy_OnPostRenderEvent.Remove(value);
}
/****
@ -46,15 +46,15 @@ namespace StardewModdingAPI.Events
/// <summary>Raised before drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.)</summary>
public static event EventHandler OnPreRenderHudEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPreRenderHudEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPreRenderHudEvent.Remove(value);
add => GraphicsEvents.EventManager.Legacy_OnPreRenderHudEvent.Add(value);
remove => GraphicsEvents.EventManager.Legacy_OnPreRenderHudEvent.Remove(value);
}
/// <summary>Raised after drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.)</summary>
public static event EventHandler OnPostRenderHudEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPostRenderHudEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPostRenderHudEvent.Remove(value);
add => GraphicsEvents.EventManager.Legacy_OnPostRenderHudEvent.Add(value);
remove => GraphicsEvents.EventManager.Legacy_OnPostRenderHudEvent.Remove(value);
}
/****
@ -63,15 +63,15 @@ namespace StardewModdingAPI.Events
/// <summary>Raised before drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen.</summary>
public static event EventHandler OnPreRenderGuiEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPreRenderGuiEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPreRenderGuiEvent.Remove(value);
add => GraphicsEvents.EventManager.Legacy_OnPreRenderGuiEvent.Add(value);
remove => GraphicsEvents.EventManager.Legacy_OnPreRenderGuiEvent.Remove(value);
}
/// <summary>Raised after drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen.</summary>
public static event EventHandler OnPostRenderGuiEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPostRenderGuiEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPostRenderGuiEvent.Remove(value);
add => GraphicsEvents.EventManager.Legacy_OnPostRenderGuiEvent.Add(value);
remove => GraphicsEvents.EventManager.Legacy_OnPostRenderGuiEvent.Remove(value);
}

View File

@ -0,0 +1,39 @@
using System;
using StardewValley;
namespace StardewModdingAPI.Events
{
/// <summary>Events related to UI and drawing to the screen.</summary>
public interface IDisplayEvents
{
/// <summary>Raised after a game menu is opened, closed, or replaced.</summary>
event EventHandler<MenuChangedEventArgs> MenuChanged;
/// <summary>Raised before the game draws anything to the screen in a draw tick, as soon as the sprite batch is opened. The sprite batch may be closed and reopened multiple times after this event is called, but it's only raised once per draw tick. This event isn't useful for drawing to the screen, since the game will draw over it.</summary>
event EventHandler<RenderingEventArgs> Rendering;
/// <summary>Raised after the game draws to the sprite patch in a draw tick, just before the final sprite batch is rendered to the screen. Since the game may open/close the sprite batch multiple times in a draw tick, the sprite batch may not contain everything being drawn and some things may already be rendered to the screen. Content drawn to the sprite batch at this point will be drawn over all vanilla content (including menus, HUD, and cursor).</summary>
event EventHandler<RenderedEventArgs> Rendered;
/// <summary>Raised before the game world is drawn to the screen. This event isn't useful for drawing to the screen, since the game will draw over it.</summary>
event EventHandler<RenderingWorldEventArgs> RenderingWorld;
/// <summary>Raised after the game world is drawn to the sprite patch, before it's rendered to the screen. Content drawn to the sprite batch at this point will be drawn over the world, but under any active menu, HUD elements, or cursor.</summary>
event EventHandler<RenderedWorldEventArgs> RenderedWorld;
/// <summary>When a menu is open (<see cref="Game1.activeClickableMenu"/> isn't null), raised before that menu is drawn to the screen. This includes the game's internal menus like the title screen. Content drawn to the sprite batch at this point will appear under the menu.</summary>
event EventHandler<RenderingActiveMenuEventArgs> RenderingActiveMenu;
/// <summary>When a menu is open (<see cref="Game1.activeClickableMenu"/> isn't null), raised after that menu is drawn to the sprite batch but before it's rendered to the screen. Content drawn to the sprite batch at this point will appear over the menu and menu cursor.</summary>
event EventHandler<RenderedActiveMenuEventArgs> RenderedActiveMenu;
/// <summary>Raised before drawing the HUD (item toolbar, clock, etc) to the screen. The vanilla HUD may be hidden at this point (e.g. because a menu is open). Content drawn to the sprite batch at this point will appear under the HUD.</summary>
event EventHandler<RenderingHudEventArgs> RenderingHud;
/// <summary>Raised after drawing the HUD (item toolbar, clock, etc) to the sprite batch, but before it's rendered to the screen. The vanilla HUD may be hidden at this point (e.g. because a menu is open). Content drawn to the sprite batch at this point will appear over the HUD.</summary>
event EventHandler<RenderedHudEventArgs> RenderedHud;
/// <summary>Raised after the game window is resized.</summary>
event EventHandler<WindowResizedEventArgs> WindowResized;
}
}

View File

@ -6,12 +6,39 @@ namespace StardewModdingAPI.Events
public interface IGameLoopEvents
{
/// <summary>Raised after the game is launched, right before the first update tick. This happens once per game session (unrelated to loading saves). All mods are loaded and initialised at this point, so this is a good time to set up mod integrations.</summary>
event EventHandler<GameLoopLaunchedEventArgs> Launched;
event EventHandler<GameLaunchedEventArgs> GameLaunched;
/// <summary>Raised before the game performs its overall update tick (≈60 times per second).</summary>
event EventHandler<GameLoopUpdatingEventArgs> Updating;
/// <summary>Raised before the game state is updated (≈60 times per second).</summary>
event EventHandler<UpdateTickingEventArgs> UpdateTicking;
/// <summary>Raised after the game performs its overall update tick (≈60 times per second).</summary>
event EventHandler<GameLoopUpdatedEventArgs> Updated;
/// <summary>Raised after the game state is updated (≈60 times per second).</summary>
event EventHandler<UpdateTickedEventArgs> UpdateTicked;
/// <summary>Raised before the game creates a new save file.</summary>
event EventHandler<SaveCreatingEventArgs> SaveCreating;
/// <summary>Raised after the game finishes creating the save file.</summary>
event EventHandler<SaveCreatedEventArgs> SaveCreated;
/// <summary>Raised before the game begins writes data to the save file (except the initial save creation).</summary>
event EventHandler<SavingEventArgs> Saving;
/// <summary>Raised after the game finishes writing data to the save file (except the initial save creation).</summary>
event EventHandler<SavedEventArgs> Saved;
/// <summary>Raised after the player loads a save slot.</summary>
event EventHandler<SaveLoadedEventArgs> SaveLoaded;
/// <summary>Raised after the game begins a new day (including when the player loads a save).</summary>
event EventHandler<DayStartedEventArgs> DayStarted;
/// <summary>Raised before the game ends the current day. This happens before it starts setting up the next day and before <see cref="Saving"/>.</summary>
event EventHandler<DayEndingEventArgs> DayEnding;
/// <summary>Raised after the in-game clock time changes.</summary>
event EventHandler<TimeChangedEventArgs> TimeChanged;
/// <summary>Raised after the game returns to the title screen.</summary>
event EventHandler<ReturnedToTitleEventArgs> ReturnedToTitle;
}
}

View File

@ -6,15 +6,15 @@ namespace StardewModdingAPI.Events
public interface IInputEvents
{
/// <summary>Raised after the player presses a button on the keyboard, controller, or mouse.</summary>
event EventHandler<InputButtonPressedEventArgs> ButtonPressed;
event EventHandler<ButtonPressedEventArgs> ButtonPressed;
/// <summary>Raised after the player releases a button on the keyboard, controller, or mouse.</summary>
event EventHandler<InputButtonReleasedEventArgs> ButtonReleased;
event EventHandler<ButtonReleasedEventArgs> ButtonReleased;
/// <summary>Raised after the player moves the in-game cursor.</summary>
event EventHandler<InputCursorMovedEventArgs> CursorMoved;
event EventHandler<CursorMovedEventArgs> CursorMoved;
/// <summary>Raised after the player scrolls the mouse wheel.</summary>
event EventHandler<InputMouseWheelScrolledEventArgs> MouseWheelScrolled;
event EventHandler<MouseWheelScrolledEventArgs> MouseWheelScrolled;
}
}

View File

@ -3,13 +3,25 @@ namespace StardewModdingAPI.Events
/// <summary>Manages access to events raised by SMAPI.</summary>
public interface IModEvents
{
/// <summary>Events related to UI and drawing to the screen.</summary>
IDisplayEvents Display { get; }
/// <summary>Events linked to the game's update loop. The update loop runs roughly ≈60 times/second to run game logic like state changes, action handling, etc. These can be useful, but you should consider more semantic events like <see cref="Input"/> if possible.</summary>
IGameLoopEvents GameLoop { get; }
/// <summary>Events raised when the player provides input using a controller, keyboard, or mouse.</summary>
IInputEvents Input { get; }
/// <summary>Events raised for multiplayer messages and connections.</summary>
IMultiplayerEvents Multiplayer { get; }
/// <summary>Events raised when the player data changes.</summary>
IPlayerEvents Player { get; }
/// <summary>Events raised when something changes in the world.</summary>
IWorldEvents World { get; }
/// <summary>Events serving specialised edge cases that shouldn't be used by most mods.</summary>
ISpecialisedEvents Specialised { get; }
}
}

View File

@ -0,0 +1,17 @@
using System;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised for multiplayer messages and connections.</summary>
public interface IMultiplayerEvents
{
/// <summary>Raised after the mod context for a peer is received. This happens before the game approves the connection, so the player doesn't yet exist in the game. This is the earliest point where messages can be sent to the peer via SMAPI.</summary>
event EventHandler<PeerContextReceivedEventArgs> PeerContextReceived;
/// <summary>Raised after a mod message is received over the network.</summary>
event EventHandler<ModMessageReceivedEventArgs> ModMessageReceived;
/// <summary>Raised after the connection with a peer is severed.</summary>
event EventHandler<PeerDisconnectedEventArgs> PeerDisconnected;
}
}

View File

@ -0,0 +1,17 @@
using System;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when the player data changes.</summary>
public interface IPlayerEvents
{
/// <summary>Raised after items are added or removed to a player's inventory. NOTE: this event is currently only raised for the current player.</summary>
event EventHandler<InventoryChangedEventArgs> InventoryChanged;
/// <summary>Raised after a player skill level changes. This happens as soon as they level up, not when the game notifies the player after their character goes to bed. NOTE: this event is currently only raised for the current player.</summary>
event EventHandler<LevelChangedEventArgs> LevelChanged;
/// <summary>Raised after a player warps to a new location. NOTE: this event is currently only raised for the current player.</summary>
event EventHandler<WarpedEventArgs> Warped;
}
}

View File

@ -0,0 +1,14 @@
using System;
namespace StardewModdingAPI.Events
{
/// <summary>Events serving specialised edge cases that shouldn't be used by most mods.</summary>
public interface ISpecialisedEvents
{
/// <summary>Raised before the game state is updated (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this event will trigger a stability warning in the SMAPI console.</summary>
event EventHandler<UnvalidatedUpdateTickingEventArgs> UnvalidatedUpdateTicking;
/// <summary>Raised after the game state is updated (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this event will trigger a stability warning in the SMAPI console.</summary>
event EventHandler<UnvalidatedUpdateTickedEventArgs> UnvalidatedUpdateTicked;
}
}

View File

@ -6,24 +6,24 @@ namespace StardewModdingAPI.Events
public interface IWorldEvents
{
/// <summary>Raised after a game location is added or removed.</summary>
event EventHandler<WorldLocationListChangedEventArgs> LocationListChanged;
event EventHandler<LocationListChangedEventArgs> LocationListChanged;
/// <summary>Raised after buildings are added or removed in a location.</summary>
event EventHandler<WorldBuildingListChangedEventArgs> BuildingListChanged;
event EventHandler<BuildingListChangedEventArgs> BuildingListChanged;
/// <summary>Raised after debris are added or removed in a location.</summary>
event EventHandler<WorldDebrisListChangedEventArgs> DebrisListChanged;
event EventHandler<DebrisListChangedEventArgs> DebrisListChanged;
/// <summary>Raised after large terrain features (like bushes) are added or removed in a location.</summary>
event EventHandler<WorldLargeTerrainFeatureListChangedEventArgs> LargeTerrainFeatureListChanged;
event EventHandler<LargeTerrainFeatureListChangedEventArgs> LargeTerrainFeatureListChanged;
/// <summary>Raised after NPCs are added or removed in a location.</summary>
event EventHandler<WorldNpcListChangedEventArgs> NpcListChanged;
event EventHandler<NpcListChangedEventArgs> NpcListChanged;
/// <summary>Raised after objects are added or removed in a location.</summary>
event EventHandler<WorldObjectListChangedEventArgs> ObjectListChanged;
event EventHandler<ObjectListChangedEventArgs> ObjectListChanged;
/// <summary>Raised after terrain features (like floors and trees) are added or removed in a location.</summary>
event EventHandler<WorldTerrainFeatureListChangedEventArgs> TerrainFeatureListChanged;
event EventHandler<TerrainFeatureListChangedEventArgs> TerrainFeatureListChanged;
}
}

View File

@ -19,15 +19,15 @@ namespace StardewModdingAPI.Events
/// <summary>Raised when the player presses a button on the keyboard, controller, or mouse.</summary>
public static event EventHandler<EventArgsInput> ButtonPressed
{
add => InputEvents.EventManager.Legacy_Input_ButtonPressed.Add(value);
remove => InputEvents.EventManager.Legacy_Input_ButtonPressed.Remove(value);
add => InputEvents.EventManager.Legacy_ButtonPressed.Add(value);
remove => InputEvents.EventManager.Legacy_ButtonPressed.Remove(value);
}
/// <summary>Raised when the player releases a keyboard key on the keyboard, controller, or mouse.</summary>
public static event EventHandler<EventArgsInput> ButtonReleased
{
add => InputEvents.EventManager.Legacy_Input_ButtonReleased.Add(value);
remove => InputEvents.EventManager.Legacy_Input_ButtonReleased.Remove(value);
add => InputEvents.EventManager.Legacy_ButtonReleased.Add(value);
remove => InputEvents.EventManager.Legacy_ButtonReleased.Remove(value);
}

View File

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StardewValley;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for an <see cref="IPlayerEvents.InventoryChanged"/> event.</summary>
public class InventoryChangedEventArgs : EventArgs
{
/*********
** Accessors
*********/
/// <summary>The player whose inventory changed.</summary>
public Farmer Player { get; }
/// <summary>The added items.</summary>
public IEnumerable<Item> Added { get; }
/// <summary>The removed items.</summary>
public IEnumerable<Item> Removed { get; }
/// <summary>The items whose stack sizes changed, with the relative change.</summary>
public IEnumerable<ItemStackSizeChange> QuantityChanged { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="player">The player whose inventory changed.</param>
/// <param name="changedItems">The inventory changes.</param>
public InventoryChangedEventArgs(Farmer player, ItemStackChange[] changedItems)
{
this.Player = player;
this.Added = changedItems
.Where(n => n.ChangeType == ChangeType.Added)
.Select(p => p.Item)
.ToArray();
this.Removed = changedItems
.Where(n => n.ChangeType == ChangeType.Removed)
.Select(p => p.Item)
.ToArray();
this.QuantityChanged = changedItems
.Where(n => n.ChangeType == ChangeType.StackChange)
.Select(change => new ItemStackSizeChange(
item: change.Item,
oldSize: change.Item.Stack - change.StackChange,
newSize: change.Item.Stack
))
.ToArray();
}
}
}

View File

@ -0,0 +1,35 @@
using StardewValley;
namespace StardewModdingAPI.Events
{
/// <summary>An inventory item stack size change.</summary>
public class ItemStackSizeChange
{
/*********
** Accessors
*********/
/// <summary>The item whose stack size changed.</summary>
public Item Item { get; }
/// <summary>The previous stack size.</summary>
public int OldSize { get; }
/// <summary>The new stack size.</summary>
public int NewSize { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="item">The item whose stack size changed.</param>
/// <param name="oldSize">The previous stack size.</param>
/// <param name="newSize">The new stack size.</param>
public ItemStackSizeChange(Item item, int oldSize, int newSize)
{
this.Item = item;
this.OldSize = oldSize;
this.NewSize = newSize;
}
}
}

View File

@ -7,7 +7,7 @@ using StardewValley.TerrainFeatures;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for a <see cref="IWorldEvents.LargeTerrainFeatureListChanged"/> event.</summary>
public class WorldLargeTerrainFeatureListChangedEventArgs : EventArgs
public class LargeTerrainFeatureListChangedEventArgs : EventArgs
{
/*********
** Accessors
@ -29,7 +29,7 @@ namespace StardewModdingAPI.Events
/// <param name="location">The location which changed.</param>
/// <param name="added">The large terrain features added to the location.</param>
/// <param name="removed">The large terrain features removed from the location.</param>
public WorldLargeTerrainFeatureListChangedEventArgs(GameLocation location, IEnumerable<LargeTerrainFeature> added, IEnumerable<LargeTerrainFeature> removed)
public LargeTerrainFeatureListChangedEventArgs(GameLocation location, IEnumerable<LargeTerrainFeature> added, IEnumerable<LargeTerrainFeature> removed)
{
this.Location = location;
this.Added = added.ToArray();

View File

@ -0,0 +1,42 @@
using System;
using StardewModdingAPI.Enums;
using StardewValley;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for a <see cref="IPlayerEvents.LevelChanged"/> event.</summary>
public class LevelChangedEventArgs : EventArgs
{
/*********
** Accessors
*********/
/// <summary>The player whose skill level changed.</summary>
public Farmer Player { get; }
/// <summary>The skill whose level changed.</summary>
public SkillType Skill { get; }
/// <summary>The previous skill level.</summary>
public int OldLevel { get; }
/// <summary>The new skill level.</summary>
public int NewLevel { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="player">The player whose skill level changed.</param>
/// <param name="skill">The skill whose level changed.</param>
/// <param name="oldLevel">The previous skill level.</param>
/// <param name="newLevel">The new skill level.</param>
public LevelChangedEventArgs(Farmer player, SkillType skill, int oldLevel, int newLevel)
{
this.Player = player;
this.Skill = skill;
this.OldLevel = oldLevel;
this.NewLevel = newLevel;
}
}
}

View File

@ -19,22 +19,22 @@ namespace StardewModdingAPI.Events
/// <summary>Raised after a game location is added or removed.</summary>
public static event EventHandler<EventArgsLocationsChanged> LocationsChanged
{
add => LocationEvents.EventManager.Legacy_Location_LocationsChanged.Add(value);
remove => LocationEvents.EventManager.Legacy_Location_LocationsChanged.Remove(value);
add => LocationEvents.EventManager.Legacy_LocationsChanged.Add(value);
remove => LocationEvents.EventManager.Legacy_LocationsChanged.Remove(value);
}
/// <summary>Raised after buildings are added or removed in a location.</summary>
public static event EventHandler<EventArgsLocationBuildingsChanged> BuildingsChanged
{
add => LocationEvents.EventManager.Legacy_Location_BuildingsChanged.Add(value);
remove => LocationEvents.EventManager.Legacy_Location_BuildingsChanged.Remove(value);
add => LocationEvents.EventManager.Legacy_BuildingsChanged.Add(value);
remove => LocationEvents.EventManager.Legacy_BuildingsChanged.Remove(value);
}
/// <summary>Raised after objects are added or removed in a location.</summary>
public static event EventHandler<EventArgsLocationObjectsChanged> ObjectsChanged
{
add => LocationEvents.EventManager.Legacy_Location_ObjectsChanged.Add(value);
remove => LocationEvents.EventManager.Legacy_Location_ObjectsChanged.Remove(value);
add => LocationEvents.EventManager.Legacy_ObjectsChanged.Add(value);
remove => LocationEvents.EventManager.Legacy_ObjectsChanged.Remove(value);
}

View File

@ -6,7 +6,7 @@ using StardewValley;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for a <see cref="IWorldEvents.LocationListChanged"/> event.</summary>
public class WorldLocationListChangedEventArgs : EventArgs
public class LocationListChangedEventArgs : EventArgs
{
/*********
** Accessors
@ -24,7 +24,7 @@ namespace StardewModdingAPI.Events
/// <summary>Construct an instance.</summary>
/// <param name="added">The added locations.</param>
/// <param name="removed">The removed locations.</param>
public WorldLocationListChangedEventArgs(IEnumerable<GameLocation> added, IEnumerable<GameLocation> removed)
public LocationListChangedEventArgs(IEnumerable<GameLocation> added, IEnumerable<GameLocation> removed)
{
this.Added = added.ToArray();
this.Removed = removed.ToArray();

View File

@ -0,0 +1,31 @@
using System;
using StardewValley.Menus;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for an <see cref="IDisplayEvents.MenuChanged"/> event.</summary>
public class MenuChangedEventArgs : EventArgs
{
/*********
** Accessors
*********/
/// <summary>The previous menu.</summary>
public IClickableMenu OldMenu { get; }
/// <summary>The current menu.</summary>
public IClickableMenu NewMenu { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="oldMenu">The previous menu.</param>
/// <param name="newMenu">The current menu.</param>
public MenuChangedEventArgs(IClickableMenu oldMenu, IClickableMenu newMenu)
{
this.OldMenu = oldMenu;
this.NewMenu = newMenu;
}
}
}

Some files were not shown because too many files have changed in this diff Show More