Merge branch 'develop' of https://github.com/Pathoschild/SMAPI into harmony2
Conflicts: src/SMAPI.Installer/SMAPI.Installer.csproj src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj src/SMAPI.Toolkit/SMAPI.Toolkit.csproj src/SMAPI/SMAPI.csproj
This commit is contained in:
commit
db61312dc6
|
@ -30,8 +30,8 @@ _ReSharper*/
|
|||
# sensitive files
|
||||
appsettings.Development.json
|
||||
|
||||
# AWS generated files
|
||||
src/SMAPI.Web.LegacyRedirects/aws-beanstalk-tools-defaults.json
|
||||
# generated build files
|
||||
build/0Harmony.*
|
||||
|
||||
# Azure generated files
|
||||
src/SMAPI.Web/Properties/PublishProfiles/*.pubxml
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<Version>3.5.0</Version>
|
||||
<Product>SMAPI</Product>
|
||||
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
← [README](README.md)
|
||||
|
||||
# Release notes
|
||||
## Upcoming released
|
||||
## Upcoming release + 1
|
||||
* For modders:
|
||||
* Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
|
||||
* Added `harmony_summary` console command which lists all current Harmony patches, optionally with a search filter.
|
||||
|
||||
## Upcoming release
|
||||
* For players:
|
||||
* Mod warnings are now listed alphabetically.
|
||||
* MacOS files starting with `._` are now ignored and can no longer cause skipped mods.
|
||||
|
@ -17,11 +22,10 @@
|
|||
* Internal changes to improve performance and reliability.
|
||||
|
||||
* For modders:
|
||||
* Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
|
||||
* Added [event priorities](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Custom_priority) (thanks to spacechase0!).
|
||||
* Added [update subkeys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks#Update_subkeys).
|
||||
* Added `Multiplayer.PeerConnected` event.
|
||||
* Added ability to override update keys from the compatibility list.
|
||||
* Added `harmony_summary` console command which lists all current Harmony patches, optionally with a search filter.
|
||||
* Harmony mods which use the `[HarmonyPatch(type)]` attribute now work crossplatform. Previously SMAPI couldn't rewrite types in custom attributes for compatibility.
|
||||
* Improved mod rewriting for compatibility:
|
||||
* Fixed rewriting types in custom attributes.
|
||||
|
@ -29,8 +33,10 @@
|
|||
* Fixed `helper.Reflection` blocking access to game methods/properties that were extended by SMAPI.
|
||||
* Fixed asset propagation for Gil's portraits.
|
||||
* Fixed `.pdb` files ignored for error stack traces for mods rewritten by SMAPI.
|
||||
* Fixed `ModMessageReceived` event handlers not tracked for performance monitoring.
|
||||
|
||||
* For SMAPI developers:
|
||||
* Added support for bundling a custom Harmony build for upcoming use.
|
||||
* Eliminated MongoDB storage in the web services, which complicated the code unnecessarily. The app still uses an abstract interface for storage, so we can wrap a distributed cache in the future if needed.
|
||||
* Overhauled update checks to simplify individual clients, centralize common logic, and enable upcoming features.
|
||||
* Merged the separate legacy redirects app on AWS into the main app on Azure.
|
||||
|
|
|
@ -15,6 +15,7 @@ This document is about SMAPI itself; see also [mod build package](mod-package.md
|
|||
* [Compiling from source](#compiling-from-source)
|
||||
* [Debugging a local build](#debugging-a-local-build)
|
||||
* [Preparing a release](#preparing-a-release)
|
||||
* [Using a custom Harmony build](#using-a-custom-harmony-build)
|
||||
* [Release notes](#release-notes)
|
||||
|
||||
## Customisation
|
||||
|
@ -57,24 +58,22 @@ SMAPI uses a small number of conditional compilation constants, which you can se
|
|||
flag | purpose
|
||||
---- | -------
|
||||
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`.
|
||||
`HARMONY_2` | Whether to enable experimental Harmony 2.0 support. Existing Harmony 1._x_ mods will be rewritten automatically for compatibility.
|
||||
|
||||
## For SMAPI developers
|
||||
### Compiling from source
|
||||
Using an official SMAPI release is recommended for most users.
|
||||
Using an official SMAPI release is recommended for most users, but you can compile from source
|
||||
directly if needed. There are no special steps (just open the project and compile), but SMAPI often
|
||||
uses the latest C# syntax. You may need the latest version of your IDE to compile it.
|
||||
|
||||
SMAPI often uses the latest C# syntax. You may need the latest version of
|
||||
[Visual Studio](https://www.visualstudio.com/vs/community/) on Windows,
|
||||
[MonoDevelop](https://www.monodevelop.com/) on Linux,
|
||||
[Visual Studio 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://smapi.io/package/readme) to detect your current OS automatically
|
||||
and load the correct references. Compile output will be placed in a `bin` folder at the root of the
|
||||
git repository.
|
||||
SMAPI uses build configuration derived from the [crossplatform mod config](https://smapi.io/package/readme)
|
||||
to detect your current OS automatically and load the correct references. Compile output will be
|
||||
placed in a `bin` folder at the root of the Git repository.
|
||||
|
||||
### Debugging a local build
|
||||
Rebuilding the solution in debug mode will copy the SMAPI files into your game folder. Starting
|
||||
the `SMAPI` project with debugging from Visual Studio (on Mac or Windows) will launch SMAPI with
|
||||
the debugger attached, so you can intercept errors and step through the code being executed. This
|
||||
the debugger attached, so you can intercept errors and step through the code being executed. That
|
||||
doesn't work in MonoDevelop on Linux, unfortunately.
|
||||
|
||||
### Preparing a release
|
||||
|
@ -87,9 +86,9 @@ on the wiki for the first-time setup.
|
|||
|
||||
build type | format | example
|
||||
:--------- | :----------------------- | :------
|
||||
dev build | `<version>-alpha.<date>` | `3.0-alpha.20171230`
|
||||
prerelease | `<version>-beta.<count>` | `3.0-beta.2`
|
||||
release | `<version>` | `3.0`
|
||||
dev build | `<version>-alpha.<date>` | `3.0.0-alpha.20171230`
|
||||
prerelease | `<version>-beta.<count>` | `3.0.0-beta.2`
|
||||
release | `<version>` | `3.0.0`
|
||||
|
||||
2. In Windows:
|
||||
1. Rebuild the solution in Release mode.
|
||||
|
@ -103,5 +102,14 @@ on the wiki for the first-time setup.
|
|||
3. Rename the folders to `SMAPI <version> installer` and `SMAPI <version> installer for developers`.
|
||||
4. Zip the two folders.
|
||||
|
||||
### Using a custom Harmony build
|
||||
The official SMAPI releases include [a custom build of Harmony](https://github.com/Pathoschild/Harmony),
|
||||
but compiling from source will use the official build. To use a custom build, put `0Harmony.dll` in
|
||||
the `build` folder and it'll be referenced automatically.
|
||||
|
||||
Note that Harmony merges its dependencies into `0Harmony.dll` when compiled in release mode. To use
|
||||
a debug build of Harmony, you'll need to manually copy those dependencies into your game's
|
||||
`smapi-internal` folder.
|
||||
|
||||
## Release notes
|
||||
See [release notes](../release-notes.md).
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>SMAPI.Installer</AssemblyName>
|
||||
<RootNamespace>StardewModdingAPI.Installer</RootNamespace>
|
||||
<Description>The SMAPI installer for players.</Description>
|
||||
<TargetFramework>net45</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
|
@ -16,13 +13,10 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="assets\*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="assets\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
||||
<Import Project="..\..\build\common.targets" />
|
||||
<Import Project="..\..\build\prepare-install-package.targets" />
|
||||
|
||||
</Project>
|
|
@ -1,11 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>SMAPI.ModBuildConfig.Analyzer</AssemblyName>
|
||||
<RootNamespace>StardewModdingAPI.ModBuildConfig.Analyzer</RootNamespace>
|
||||
<Version>3.0.0</Version>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<OutputPath>bin</OutputPath>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
@ -19,5 +16,4 @@
|
|||
<ItemGroup>
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,24 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>SMAPI.ModBuildConfig</AssemblyName>
|
||||
<RootNamespace>StardewModdingAPI.ModBuildConfig</RootNamespace>
|
||||
<Version>3.0.0</Version>
|
||||
<Version>3.1.0</Version>
|
||||
<TargetFramework>net45</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SMAPI.Toolkit\SMAPI.Toolkit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\build\find-game-folder.targets" Link="build\find-game-folder.targets" />
|
||||
<None Include="..\..\docs\technical\mod-package.md" Link="mod-build-config.md" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Build" />
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
|
@ -28,19 +16,16 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\docs\technical\mod-package.md">
|
||||
<Link>mod-package.md</Link>
|
||||
</None>
|
||||
<ProjectReference Include="..\SMAPI.Toolkit\SMAPI.Toolkit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="assets\nuget-icon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\..\build\find-game-folder.targets" Link="build\find-game-folder.targets" />
|
||||
<None Include="..\..\docs\technical\mod-package.md" Link="mod-package.md" />
|
||||
<None Update="assets\nuget-icon.png" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
||||
<Import Project="..\..\build\common.targets" />
|
||||
<Import Project="..\..\build\prepare-nuget-package.targets" />
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -38,58 +38,26 @@
|
|||
**********************************************-->
|
||||
<!-- common -->
|
||||
<ItemGroup>
|
||||
<Reference Include="$(GameExecutableName)">
|
||||
<HintPath>$(GamePath)\$(GameExecutableName).exe</HintPath>
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="StardewValley.GameData">
|
||||
<HintPath>$(GamePath)\StardewValley.GameData.dll</HintPath>
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="StardewModdingAPI">
|
||||
<HintPath>$(GamePath)\StardewModdingAPI.exe</HintPath>
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="SMAPI.Toolkit.CoreInterfaces">
|
||||
<HintPath>$(GamePath)\smapi-internal\SMAPI.Toolkit.CoreInterfaces.dll</HintPath>
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="xTile">
|
||||
<HintPath>$(GamePath)\xTile.dll</HintPath>
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="0Harmony" Condition="'$(EnableHarmony)' == 'true'">
|
||||
<HintPath>$(GamePath)\smapi-internal\0Harmony.dll</HintPath>
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="StardewModdingAPI" HintPath="$(GamePath)\StardewModdingAPI.exe" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="SMAPI.Toolkit.CoreInterfaces" HintPath="$(GamePath)\smapi-internal\SMAPI.Toolkit.CoreInterfaces.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="0Harmony" Condition="'$(EnableHarmony)' == 'true'" HintPath="$(GamePath)\smapi-internal\0Harmony.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Windows -->
|
||||
<ItemGroup Condition="$(OS) == 'Windows_NT'">
|
||||
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="Netcode">
|
||||
<HintPath>$(GamePath)\Netcode.dll</HintPath>
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
<Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Linux/Mac -->
|
||||
<ItemGroup Condition="$(OS) != 'Windows_NT'">
|
||||
<Reference Include="MonoGame.Framework">
|
||||
<HintPath>$(GamePath)\MonoGame.Framework.dll</HintPath>
|
||||
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||
</Reference>
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="$(CopyModReferencesToBuildOutput)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
<AssemblyName>ConsoleCommands</AssemblyName>
|
||||
<RootNamespace>StardewModdingAPI.Mods.ConsoleCommands</RootNamespace>
|
||||
<TargetFramework>net45</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>SaveBackup</AssemblyName>
|
||||
<RootNamespace>StardewModdingAPI.Mods.SaveBackup</RootNamespace>
|
||||
<TargetFramework>net45</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<OutputPath></OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
@ -29,9 +26,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="manifest.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="manifest.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>SMAPI.Toolkit.CoreInterfaces</AssemblyName>
|
||||
<RootNamespace>StardewModdingAPI</RootNamespace>
|
||||
<Description>Provides toolkit interfaces which are available to SMAPI mods.</Description>
|
||||
<LangVersion>8</LangVersion>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\SMAPI.Toolkit.CoreInterfaces.xml</DocumentationFile>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PlatformTarget Condition="'$(TargetFramework)' == 'net4.5'">x86</PlatformTarget>
|
||||
<TargetFramework>net45</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\..\build\common.targets" />
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
|
||||
|
@ -11,8 +10,6 @@ using StardewModdingAPI.Toolkit.Framework.ModData;
|
|||
using StardewModdingAPI.Toolkit.Framework.ModScanning;
|
||||
using StardewModdingAPI.Toolkit.Serialization;
|
||||
|
||||
[assembly: InternalsVisibleTo("StardewModdingAPI")]
|
||||
[assembly: InternalsVisibleTo("SMAPI.Web")]
|
||||
namespace StardewModdingAPI.Toolkit
|
||||
{
|
||||
/// <summary>A convenience wrapper for the various tools.</summary>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("StardewModdingAPI")]
|
||||
[assembly: InternalsVisibleTo("SMAPI.Web")]
|
|
@ -1,11 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>SMAPI.Toolkit</AssemblyName>
|
||||
<RootNamespace>StardewModdingAPI.Toolkit</RootNamespace>
|
||||
<Description>A library which encapsulates mod-handling logic for mod managers and tools. Not intended for use by mods.</Description>
|
||||
<LangVersion>8</LangVersion>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\SMAPI.Toolkit.xml</DocumentationFile>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PlatformTarget Condition="'$(TargetFramework)' == 'net4.5'">x86</PlatformTarget>
|
||||
<RootNamespace>StardewModdingAPI.Toolkit</RootNamespace>
|
||||
<TargetFramework>net45</TargetFramework>
|
||||
|
@ -24,5 +21,4 @@
|
|||
</ItemGroup>
|
||||
|
||||
<Import Project="..\..\build\common.targets" />
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -7,8 +7,11 @@ using StardewModdingAPI.Framework;
|
|||
using StardewModdingAPI.Framework.ModLoading;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
using StardewValley;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>Contains SMAPI's constants and assumptions.</summary>
|
||||
|
@ -202,7 +205,11 @@ namespace StardewModdingAPI
|
|||
{
|
||||
typeof(StardewValley.Game1).Assembly, // note: includes Netcode types on Linux/Mac
|
||||
typeof(Microsoft.Xna.Framework.Vector2).Assembly,
|
||||
#if HARMONY_2
|
||||
typeof(HarmonyLib.Harmony).Assembly,
|
||||
#else
|
||||
typeof(Harmony.HarmonyInstance).Assembly,
|
||||
#endif
|
||||
typeof(Mono.Cecil.MethodDefinition).Assembly,
|
||||
typeof(StardewModdingAPI.IManifest).Assembly,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
namespace StardewModdingAPI.Events
|
||||
{
|
||||
/// <summary>The event priorities for method handlers.</summary>
|
||||
public enum EventPriority
|
||||
{
|
||||
/// <summary>Low priority.</summary>
|
||||
Low = -1000,
|
||||
|
||||
/// <summary>The default priority.</summary>
|
||||
Normal = 0,
|
||||
|
||||
/// <summary>High priority.</summary>
|
||||
High = 1000
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
|
||||
namespace StardewModdingAPI.Events
|
||||
{
|
||||
/// <summary>An attribute which specifies the priority for an event handler.</summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class EventPriorityAttribute : Attribute
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The event handler priority, relative to other handlers across all mods registered for this event.</summary>
|
||||
internal EventPriority Priority { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="priority">The event handler priority, relative to other handlers across all mods registered for this event. Higher-priority handlers are notified before lower-priority handlers.</param>
|
||||
public EventPriorityAttribute(EventPriority priority)
|
||||
{
|
||||
this.Priority = priority;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
#if HARMONY_2
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -166,3 +167,4 @@ namespace StardewModdingAPI.Framework.Commands
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -177,15 +177,14 @@ namespace StardewModdingAPI.Framework.Events
|
|||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="monitor">Writes messages to the log.</param>
|
||||
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
|
||||
/// <param name="performanceMonitor">Tracks performance metrics.</param>
|
||||
public EventManager(IMonitor monitor, ModRegistry modRegistry, PerformanceMonitor performanceMonitor)
|
||||
public EventManager(ModRegistry modRegistry, PerformanceMonitor performanceMonitor)
|
||||
{
|
||||
// create shortcut initializers
|
||||
ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName, bool isPerformanceCritical = false)
|
||||
{
|
||||
return new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", monitor, modRegistry, performanceMonitor, isPerformanceCritical);
|
||||
return new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", modRegistry, performanceMonitor, isPerformanceCritical);
|
||||
}
|
||||
|
||||
// init events (new)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using StardewModdingAPI.Events;
|
||||
using StardewModdingAPI.Framework.PerformanceMonitoring;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Events
|
||||
|
@ -12,24 +14,24 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The underlying event.</summary>
|
||||
private event EventHandler<TEventArgs> Event;
|
||||
|
||||
/// <summary>Writes messages to the log.</summary>
|
||||
private readonly IMonitor Monitor;
|
||||
|
||||
/// <summary>The mod registry with which to identify mods.</summary>
|
||||
protected readonly ModRegistry ModRegistry;
|
||||
|
||||
/// <summary>The display names for the mods which added each delegate.</summary>
|
||||
private readonly IDictionary<EventHandler<TEventArgs>, IModMetadata> SourceMods = new Dictionary<EventHandler<TEventArgs>, IModMetadata>();
|
||||
|
||||
/// <summary>The cached invocation list.</summary>
|
||||
private EventHandler<TEventArgs>[] CachedInvocationList;
|
||||
|
||||
/// <summary>Tracks performance metrics.</summary>
|
||||
private readonly PerformanceMonitor PerformanceMonitor;
|
||||
|
||||
/// <summary>The underlying event handlers.</summary>
|
||||
private readonly List<ManagedEventHandler<TEventArgs>> Handlers = new List<ManagedEventHandler<TEventArgs>>();
|
||||
|
||||
/// <summary>A cached snapshot of <see cref="Handlers"/>, or <c>null</c> to rebuild it next raise.</summary>
|
||||
private ManagedEventHandler<TEventArgs>[] CachedHandlers = new ManagedEventHandler<TEventArgs>[0];
|
||||
|
||||
/// <summary>The total number of event handlers registered for this events, regardless of whether they're still registered.</summary>
|
||||
private int RegistrationIndex;
|
||||
|
||||
/// <summary>Whether new handlers were added since the last raise.</summary>
|
||||
private bool HasNewHandlers;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
|
@ -46,14 +48,12 @@ namespace StardewModdingAPI.Framework.Events
|
|||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="eventName">A human-readable name for the event.</param>
|
||||
/// <param name="monitor">Writes messages to the log.</param>
|
||||
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
|
||||
/// <param name="performanceMonitor">Tracks performance metrics.</param>
|
||||
/// <param name="isPerformanceCritical">Whether the event is typically called at least once per second.</param>
|
||||
public ManagedEvent(string eventName, IMonitor monitor, ModRegistry modRegistry, PerformanceMonitor performanceMonitor, bool isPerformanceCritical = false)
|
||||
public ManagedEvent(string eventName, ModRegistry modRegistry, PerformanceMonitor performanceMonitor, bool isPerformanceCritical = false)
|
||||
{
|
||||
this.EventName = eventName;
|
||||
this.Monitor = monitor;
|
||||
this.ModRegistry = modRegistry;
|
||||
this.PerformanceMonitor = performanceMonitor;
|
||||
this.IsPerformanceCritical = isPerformanceCritical;
|
||||
|
@ -62,14 +62,7 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Get whether anything is listening to the event.</summary>
|
||||
public bool HasListeners()
|
||||
{
|
||||
return this.CachedInvocationList?.Length > 0;
|
||||
}
|
||||
|
||||
/// <summary>Add an event handler.</summary>
|
||||
/// <param name="handler">The event handler.</param>
|
||||
public void Add(EventHandler<TEventArgs> handler)
|
||||
{
|
||||
this.Add(handler, this.ModRegistry.GetFromStack());
|
||||
return this.Handlers.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>Add an event handler.</summary>
|
||||
|
@ -77,33 +70,62 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <param name="mod">The mod which added the event handler.</param>
|
||||
public void Add(EventHandler<TEventArgs> handler, IModMetadata mod)
|
||||
{
|
||||
this.Event += handler;
|
||||
this.AddTracking(mod, handler, this.Event?.GetInvocationList().Cast<EventHandler<TEventArgs>>());
|
||||
EventPriority priority = handler.Method.GetCustomAttribute<EventPriorityAttribute>()?.Priority ?? EventPriority.Normal;
|
||||
var managedHandler = new ManagedEventHandler<TEventArgs>(handler, this.RegistrationIndex++, priority, mod);
|
||||
|
||||
this.Handlers.Add(managedHandler);
|
||||
this.CachedHandlers = null;
|
||||
this.HasNewHandlers = true;
|
||||
}
|
||||
|
||||
/// <summary>Remove an event handler.</summary>
|
||||
/// <param name="handler">The event handler.</param>
|
||||
public void Remove(EventHandler<TEventArgs> handler)
|
||||
{
|
||||
this.Event -= handler;
|
||||
this.RemoveTracking(handler, this.Event?.GetInvocationList().Cast<EventHandler<TEventArgs>>());
|
||||
// match C# events: if a handler is listed multiple times, remove the last one added
|
||||
for (int i = this.Handlers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (this.Handlers[i].Handler != handler)
|
||||
continue;
|
||||
|
||||
this.Handlers.RemoveAt(i);
|
||||
this.CachedHandlers = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Raise the event and notify all handlers.</summary>
|
||||
/// <param name="args">The event arguments to pass.</param>
|
||||
public void Raise(TEventArgs args)
|
||||
/// <param name="match">A lambda which returns true if the event should be raised for the given mod.</param>
|
||||
public void Raise(TEventArgs args, Func<IModMetadata, bool> match = null)
|
||||
{
|
||||
if (this.Event == null)
|
||||
// skip if no handlers
|
||||
if (this.Handlers.Count == 0)
|
||||
return;
|
||||
|
||||
// update cached data
|
||||
// (This is debounced here to avoid repeatedly sorting when handlers are added/removed,
|
||||
// and keeping a separate cached list allows changes during enumeration.)
|
||||
if (this.CachedHandlers == null)
|
||||
{
|
||||
if (this.HasNewHandlers && this.Handlers.Any(p => p.Priority != EventPriority.Normal))
|
||||
this.Handlers.Sort();
|
||||
|
||||
this.CachedHandlers = this.Handlers.ToArray();
|
||||
this.HasNewHandlers = false;
|
||||
}
|
||||
|
||||
// raise event
|
||||
this.PerformanceMonitor.Track(this.EventName, () =>
|
||||
{
|
||||
foreach (EventHandler<TEventArgs> handler in this.CachedInvocationList)
|
||||
foreach (ManagedEventHandler<TEventArgs> handler in this.CachedHandlers)
|
||||
{
|
||||
if (match != null && !match(handler.SourceMod))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
this.PerformanceMonitor.Track(this.EventName, this.GetModNameForPerformanceCounters(handler), () => handler.Invoke(null, args));
|
||||
this.PerformanceMonitor.Track(this.EventName, this.GetModNameForPerformanceCounters(handler), () => handler.Handler.Invoke(null, args));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -113,86 +135,27 @@ namespace StardewModdingAPI.Framework.Events
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>Raise the event and notify all handlers.</summary>
|
||||
/// <param name="args">The event arguments to pass.</param>
|
||||
/// <param name="match">A lambda which returns true if the event should be raised for the given mod.</param>
|
||||
public void RaiseForMods(TEventArgs args, Func<IModMetadata, bool> match)
|
||||
{
|
||||
if (this.Event == null)
|
||||
return;
|
||||
|
||||
foreach (EventHandler<TEventArgs> handler in this.CachedInvocationList)
|
||||
{
|
||||
if (match(this.GetSourceMod(handler)))
|
||||
{
|
||||
try
|
||||
{
|
||||
handler.Invoke(null, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(handler, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Get the mod name for a given event handler to display in performance monitoring reports.</summary>
|
||||
/// <param name="handler">The event handler.</param>
|
||||
private string GetModNameForPerformanceCounters(EventHandler<TEventArgs> handler)
|
||||
private string GetModNameForPerformanceCounters(ManagedEventHandler<TEventArgs> handler)
|
||||
{
|
||||
IModMetadata mod = this.GetSourceMod(handler);
|
||||
if (mod == null)
|
||||
return Constants.GamePerformanceCounterName;
|
||||
IModMetadata mod = handler.SourceMod;
|
||||
|
||||
return mod.HasManifest()
|
||||
? mod.Manifest.UniqueID
|
||||
: mod.DisplayName;
|
||||
}
|
||||
|
||||
/// <summary>Track an event handler.</summary>
|
||||
/// <param name="mod">The mod which added the handler.</param>
|
||||
/// <param name="handler">The event handler.</param>
|
||||
/// <param name="invocationList">The updated event invocation list.</param>
|
||||
protected void AddTracking(IModMetadata mod, EventHandler<TEventArgs> handler, IEnumerable<EventHandler<TEventArgs>> invocationList)
|
||||
{
|
||||
this.SourceMods[handler] = mod;
|
||||
this.CachedInvocationList = invocationList?.ToArray() ?? new EventHandler<TEventArgs>[0];
|
||||
}
|
||||
|
||||
/// <summary>Remove tracking for an event handler.</summary>
|
||||
/// <param name="handler">The event handler.</param>
|
||||
/// <param name="invocationList">The updated event invocation list.</param>
|
||||
protected void RemoveTracking(EventHandler<TEventArgs> handler, IEnumerable<EventHandler<TEventArgs>> invocationList)
|
||||
{
|
||||
this.CachedInvocationList = invocationList?.ToArray() ?? new EventHandler<TEventArgs>[0];
|
||||
if (!this.CachedInvocationList.Contains(handler)) // don't remove if there's still a reference to the removed handler (e.g. it was added twice and removed once)
|
||||
this.SourceMods.Remove(handler);
|
||||
}
|
||||
|
||||
/// <summary>Get the mod which registered the given event handler, if available.</summary>
|
||||
/// <param name="handler">The event handler.</param>
|
||||
protected IModMetadata GetSourceMod(EventHandler<TEventArgs> handler)
|
||||
{
|
||||
return this.SourceMods.TryGetValue(handler, out IModMetadata mod)
|
||||
? mod
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>Log an exception from an event handler.</summary>
|
||||
/// <param name="handler">The event handler instance.</param>
|
||||
/// <param name="ex">The exception that was raised.</param>
|
||||
protected void LogError(EventHandler<TEventArgs> handler, Exception ex)
|
||||
protected void LogError(ManagedEventHandler<TEventArgs> handler, Exception ex)
|
||||
{
|
||||
IModMetadata mod = this.GetSourceMod(handler);
|
||||
if (mod != null)
|
||||
mod.LogAsMod($"This mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error);
|
||||
else
|
||||
this.Monitor.Log($"A mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error);
|
||||
handler.SourceMod.LogAsMod($"This mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using StardewModdingAPI.Events;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Events
|
||||
{
|
||||
/// <summary>An event handler wrapper which tracks metadata about an event handler.</summary>
|
||||
/// <typeparam name="TEventArgs">The event arguments type.</typeparam>
|
||||
internal class ManagedEventHandler<TEventArgs> : IComparable
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The event handler method.</summary>
|
||||
public EventHandler<TEventArgs> Handler { get; }
|
||||
|
||||
/// <summary>The order in which the event handler was registered, relative to other handlers for this event.</summary>
|
||||
public int RegistrationOrder { get; }
|
||||
|
||||
/// <summary>The event handler priority, relative to other handlers for this event.</summary>
|
||||
public EventPriority Priority { get; }
|
||||
|
||||
/// <summary>The mod which registered the handler.</summary>
|
||||
public IModMetadata SourceMod { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="handler">The event handler method.</param>
|
||||
/// <param name="registrationOrder">The order in which the event handler was registered, relative to other handlers for this event.</param>
|
||||
/// <param name="priority">The event handler priority, relative to other handlers for this event.</param>
|
||||
/// <param name="sourceMod">The mod which registered the handler.</param>
|
||||
public ManagedEventHandler(EventHandler<TEventArgs> handler, int registrationOrder, EventPriority priority, IModMetadata sourceMod)
|
||||
{
|
||||
this.Handler = handler;
|
||||
this.RegistrationOrder = registrationOrder;
|
||||
this.Priority = priority;
|
||||
this.SourceMod = sourceMod;
|
||||
}
|
||||
|
||||
/// <summary>Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.</summary>
|
||||
/// <param name="obj">An object to compare with this instance.</param>
|
||||
/// <exception cref="T:System.ArgumentException"><paramref name="obj" /> is not the same type as this instance.</exception>
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (!(obj is ManagedEventHandler<TEventArgs> other))
|
||||
throw new ArgumentException("Can't compare to an unrelated object type.");
|
||||
|
||||
int priorityCompare = this.Priority.CompareTo(other.Priority);
|
||||
return priorityCompare != 0
|
||||
? priorityCompare
|
||||
: this.RegistrationOrder.CompareTo(other.RegistrationOrder);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,70 +13,70 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Raised after a game menu is opened, closed, or replaced.</summary>
|
||||
public event EventHandler<MenuChangedEventArgs> MenuChanged
|
||||
{
|
||||
add => this.EventManager.MenuChanged.Add(value);
|
||||
add => this.EventManager.MenuChanged.Add(value, this.Mod);
|
||||
remove => this.EventManager.MenuChanged.Remove(value);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public event EventHandler<RenderingEventArgs> Rendering
|
||||
{
|
||||
add => this.EventManager.Rendering.Add(value);
|
||||
add => this.EventManager.Rendering.Add(value, this.Mod);
|
||||
remove => this.EventManager.Rendering.Remove(value);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public event EventHandler<RenderedEventArgs> Rendered
|
||||
{
|
||||
add => this.EventManager.Rendered.Add(value);
|
||||
add => this.EventManager.Rendered.Add(value, this.Mod);
|
||||
remove => this.EventManager.Rendered.Remove(value);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public event EventHandler<RenderingWorldEventArgs> RenderingWorld
|
||||
{
|
||||
add => this.EventManager.RenderingWorld.Add(value);
|
||||
add => this.EventManager.RenderingWorld.Add(value, this.Mod);
|
||||
remove => this.EventManager.RenderingWorld.Remove(value);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public event EventHandler<RenderedWorldEventArgs> RenderedWorld
|
||||
{
|
||||
add => this.EventManager.RenderedWorld.Add(value);
|
||||
add => this.EventManager.RenderedWorld.Add(value, this.Mod);
|
||||
remove => this.EventManager.RenderedWorld.Remove(value);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public event EventHandler<RenderingActiveMenuEventArgs> RenderingActiveMenu
|
||||
{
|
||||
add => this.EventManager.RenderingActiveMenu.Add(value);
|
||||
add => this.EventManager.RenderingActiveMenu.Add(value, this.Mod);
|
||||
remove => this.EventManager.RenderingActiveMenu.Remove(value);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public event EventHandler<RenderedActiveMenuEventArgs> RenderedActiveMenu
|
||||
{
|
||||
add => this.EventManager.RenderedActiveMenu.Add(value);
|
||||
add => this.EventManager.RenderedActiveMenu.Add(value, this.Mod);
|
||||
remove => this.EventManager.RenderedActiveMenu.Remove(value);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public event EventHandler<RenderingHudEventArgs> RenderingHud
|
||||
{
|
||||
add => this.EventManager.RenderingHud.Add(value);
|
||||
add => this.EventManager.RenderingHud.Add(value, this.Mod);
|
||||
remove => this.EventManager.RenderingHud.Remove(value);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public event EventHandler<RenderedHudEventArgs> RenderedHud
|
||||
{
|
||||
add => this.EventManager.RenderedHud.Add(value);
|
||||
add => this.EventManager.RenderedHud.Add(value, this.Mod);
|
||||
remove => this.EventManager.RenderedHud.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after the game window is resized.</summary>
|
||||
public event EventHandler<WindowResizedEventArgs> WindowResized
|
||||
{
|
||||
add => this.EventManager.WindowResized.Add(value);
|
||||
add => this.EventManager.WindowResized.Add(value, this.Mod);
|
||||
remove => this.EventManager.WindowResized.Remove(value);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,84 +12,84 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Raised after the game is launched, right before the first update tick.</summary>
|
||||
public event EventHandler<GameLaunchedEventArgs> GameLaunched
|
||||
{
|
||||
add => this.EventManager.GameLaunched.Add(value);
|
||||
add => this.EventManager.GameLaunched.Add(value, this.Mod);
|
||||
remove => this.EventManager.GameLaunched.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised before the game performs its overall update tick (≈60 times per second).</summary>
|
||||
public event EventHandler<UpdateTickingEventArgs> UpdateTicking
|
||||
{
|
||||
add => this.EventManager.UpdateTicking.Add(value);
|
||||
add => this.EventManager.UpdateTicking.Add(value, this.Mod);
|
||||
remove => this.EventManager.UpdateTicking.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after the game performs its overall update tick (≈60 times per second).</summary>
|
||||
public event EventHandler<UpdateTickedEventArgs> UpdateTicked
|
||||
{
|
||||
add => this.EventManager.UpdateTicked.Add(value);
|
||||
add => this.EventManager.UpdateTicked.Add(value, this.Mod);
|
||||
remove => this.EventManager.UpdateTicked.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised once per second before the game state is updated.</summary>
|
||||
public event EventHandler<OneSecondUpdateTickingEventArgs> OneSecondUpdateTicking
|
||||
{
|
||||
add => this.EventManager.OneSecondUpdateTicking.Add(value);
|
||||
add => this.EventManager.OneSecondUpdateTicking.Add(value, this.Mod);
|
||||
remove => this.EventManager.OneSecondUpdateTicking.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised once per second after the game state is updated.</summary>
|
||||
public event EventHandler<OneSecondUpdateTickedEventArgs> OneSecondUpdateTicked
|
||||
{
|
||||
add => this.EventManager.OneSecondUpdateTicked.Add(value);
|
||||
add => this.EventManager.OneSecondUpdateTicked.Add(value, this.Mod);
|
||||
remove => this.EventManager.OneSecondUpdateTicked.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised before the game creates a new save file.</summary>
|
||||
public event EventHandler<SaveCreatingEventArgs> SaveCreating
|
||||
{
|
||||
add => this.EventManager.SaveCreating.Add(value);
|
||||
add => this.EventManager.SaveCreating.Add(value, this.Mod);
|
||||
remove => this.EventManager.SaveCreating.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after the game finishes creating the save file.</summary>
|
||||
public event EventHandler<SaveCreatedEventArgs> SaveCreated
|
||||
{
|
||||
add => this.EventManager.SaveCreated.Add(value);
|
||||
add => this.EventManager.SaveCreated.Add(value, this.Mod);
|
||||
remove => this.EventManager.SaveCreated.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised before the game begins writes data to the save file.</summary>
|
||||
public event EventHandler<SavingEventArgs> Saving
|
||||
{
|
||||
add => this.EventManager.Saving.Add(value);
|
||||
add => this.EventManager.Saving.Add(value, this.Mod);
|
||||
remove => this.EventManager.Saving.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after the game finishes writing data to the save file.</summary>
|
||||
public event EventHandler<SavedEventArgs> Saved
|
||||
{
|
||||
add => this.EventManager.Saved.Add(value);
|
||||
add => this.EventManager.Saved.Add(value, this.Mod);
|
||||
remove => this.EventManager.Saved.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after the player loads a save slot and the world is initialized.</summary>
|
||||
public event EventHandler<SaveLoadedEventArgs> SaveLoaded
|
||||
{
|
||||
add => this.EventManager.SaveLoaded.Add(value);
|
||||
add => this.EventManager.SaveLoaded.Add(value, this.Mod);
|
||||
remove => this.EventManager.SaveLoaded.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after the game begins a new day (including when the player loads a save).</summary>
|
||||
public event EventHandler<DayStartedEventArgs> DayStarted
|
||||
{
|
||||
add => this.EventManager.DayStarted.Add(value);
|
||||
add => this.EventManager.DayStarted.Add(value, this.Mod);
|
||||
remove => this.EventManager.DayStarted.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised before the game ends the current day. This happens before it starts setting up the next day and before <see cref="IGameLoopEvents.Saving"/>.</summary>
|
||||
public event EventHandler<DayEndingEventArgs> DayEnding
|
||||
{
|
||||
add => this.EventManager.DayEnding.Add(value);
|
||||
add => this.EventManager.DayEnding.Add(value, this.Mod);
|
||||
remove => this.EventManager.DayEnding.Remove(value);
|
||||
}
|
||||
|
||||
|
@ -97,14 +97,14 @@ namespace StardewModdingAPI.Framework.Events
|
|||
public event EventHandler<TimeChangedEventArgs> TimeChanged
|
||||
{
|
||||
|
||||
add => this.EventManager.TimeChanged.Add(value);
|
||||
add => this.EventManager.TimeChanged.Add(value, this.Mod);
|
||||
remove => this.EventManager.TimeChanged.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after the game returns to the title screen.</summary>
|
||||
public event EventHandler<ReturnedToTitleEventArgs> ReturnedToTitle
|
||||
{
|
||||
add => this.EventManager.ReturnedToTitle.Add(value);
|
||||
add => this.EventManager.ReturnedToTitle.Add(value, this.Mod);
|
||||
remove => this.EventManager.ReturnedToTitle.Remove(value);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,28 +12,28 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Raised after the player presses a button on the keyboard, controller, or mouse.</summary>
|
||||
public event EventHandler<ButtonPressedEventArgs> ButtonPressed
|
||||
{
|
||||
add => this.EventManager.ButtonPressed.Add(value);
|
||||
add => this.EventManager.ButtonPressed.Add(value, this.Mod);
|
||||
remove => this.EventManager.ButtonPressed.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after the player releases a button on the keyboard, controller, or mouse.</summary>
|
||||
public event EventHandler<ButtonReleasedEventArgs> ButtonReleased
|
||||
{
|
||||
add => this.EventManager.ButtonReleased.Add(value);
|
||||
add => this.EventManager.ButtonReleased.Add(value, this.Mod);
|
||||
remove => this.EventManager.ButtonReleased.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after the player moves the in-game cursor.</summary>
|
||||
public event EventHandler<CursorMovedEventArgs> CursorMoved
|
||||
{
|
||||
add => this.EventManager.CursorMoved.Add(value);
|
||||
add => this.EventManager.CursorMoved.Add(value, this.Mod);
|
||||
remove => this.EventManager.CursorMoved.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after the player scrolls the mouse wheel.</summary>
|
||||
public event EventHandler<MouseWheelScrolledEventArgs> MouseWheelScrolled
|
||||
{
|
||||
add => this.EventManager.MouseWheelScrolled.Add(value);
|
||||
add => this.EventManager.MouseWheelScrolled.Add(value, this.Mod);
|
||||
remove => this.EventManager.MouseWheelScrolled.Remove(value);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,28 +12,28 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Raised after the mod context for a peer is received. This happens before the game approves the connection (<see cref="IMultiplayerEvents.PeerConnected"/>), 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>
|
||||
public event EventHandler<PeerContextReceivedEventArgs> PeerContextReceived
|
||||
{
|
||||
add => this.EventManager.PeerContextReceived.Add(value);
|
||||
add => this.EventManager.PeerContextReceived.Add(value, this.Mod);
|
||||
remove => this.EventManager.PeerContextReceived.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after a peer connection is approved by the game.</summary>
|
||||
public event EventHandler<PeerConnectedEventArgs> PeerConnected
|
||||
{
|
||||
add => this.EventManager.PeerConnected.Add(value);
|
||||
add => this.EventManager.PeerConnected.Add(value, this.Mod);
|
||||
remove => this.EventManager.PeerConnected.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after a mod message is received over the network.</summary>
|
||||
public event EventHandler<ModMessageReceivedEventArgs> ModMessageReceived
|
||||
{
|
||||
add => this.EventManager.ModMessageReceived.Add(value);
|
||||
add => this.EventManager.ModMessageReceived.Add(value, this.Mod);
|
||||
remove => this.EventManager.ModMessageReceived.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after the connection with a peer is severed.</summary>
|
||||
public event EventHandler<PeerDisconnectedEventArgs> PeerDisconnected
|
||||
{
|
||||
add => this.EventManager.PeerDisconnected.Add(value);
|
||||
add => this.EventManager.PeerDisconnected.Add(value, this.Mod);
|
||||
remove => this.EventManager.PeerDisconnected.Remove(value);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,21 +12,21 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Raised after items are added or removed to a player's inventory. NOTE: this event is currently only raised for the local player.</summary>
|
||||
public event EventHandler<InventoryChangedEventArgs> InventoryChanged
|
||||
{
|
||||
add => this.EventManager.InventoryChanged.Add(value);
|
||||
add => this.EventManager.InventoryChanged.Add(value, this.Mod);
|
||||
remove => this.EventManager.InventoryChanged.Remove(value);
|
||||
}
|
||||
|
||||
/// <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 local player.</summary>
|
||||
public event EventHandler<LevelChangedEventArgs> LevelChanged
|
||||
{
|
||||
add => this.EventManager.LevelChanged.Add(value);
|
||||
add => this.EventManager.LevelChanged.Add(value, this.Mod);
|
||||
remove => this.EventManager.LevelChanged.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after a player warps to a new location. NOTE: this event is currently only raised for the local player.</summary>
|
||||
public event EventHandler<WarpedEventArgs> Warped
|
||||
{
|
||||
add => this.EventManager.Warped.Add(value);
|
||||
add => this.EventManager.Warped.Add(value, this.Mod);
|
||||
remove => this.EventManager.Warped.Remove(value);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,21 +12,21 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Raised when the low-level stage in the game's loading process has changed. This is an advanced event for mods which need to run code at specific points in the loading process. The available stages or when they happen might change without warning in future versions (e.g. due to changes in the game's load process), so mods using this event are more likely to break or have bugs. Most mods should use <see cref="IGameLoopEvents"/> instead.</summary>
|
||||
public event EventHandler<LoadStageChangedEventArgs> LoadStageChanged
|
||||
{
|
||||
add => this.EventManager.LoadStageChanged.Add(value);
|
||||
add => this.EventManager.LoadStageChanged.Add(value, this.Mod);
|
||||
remove => this.EventManager.LoadStageChanged.Remove(value);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public event EventHandler<UnvalidatedUpdateTickingEventArgs> UnvalidatedUpdateTicking
|
||||
{
|
||||
add => this.EventManager.UnvalidatedUpdateTicking.Add(value);
|
||||
add => this.EventManager.UnvalidatedUpdateTicking.Add(value, this.Mod);
|
||||
remove => this.EventManager.UnvalidatedUpdateTicking.Remove(value);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public event EventHandler<UnvalidatedUpdateTickedEventArgs> UnvalidatedUpdateTicked
|
||||
{
|
||||
add => this.EventManager.UnvalidatedUpdateTicked.Add(value);
|
||||
add => this.EventManager.UnvalidatedUpdateTicked.Add(value, this.Mod);
|
||||
remove => this.EventManager.UnvalidatedUpdateTicked.Remove(value);
|
||||
}
|
||||
|
||||
|
|
|
@ -40,28 +40,28 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Raised after NPCs are added or removed in a location.</summary>
|
||||
public event EventHandler<NpcListChangedEventArgs> NpcListChanged
|
||||
{
|
||||
add => this.EventManager.NpcListChanged.Add(value);
|
||||
add => this.EventManager.NpcListChanged.Add(value, this.Mod);
|
||||
remove => this.EventManager.NpcListChanged.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after objects are added or removed in a location.</summary>
|
||||
public event EventHandler<ObjectListChangedEventArgs> ObjectListChanged
|
||||
{
|
||||
add => this.EventManager.ObjectListChanged.Add(value);
|
||||
add => this.EventManager.ObjectListChanged.Add(value, this.Mod);
|
||||
remove => this.EventManager.ObjectListChanged.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after items are added or removed from a chest.</summary>
|
||||
public event EventHandler<ChestInventoryChangedEventArgs> ChestInventoryChanged
|
||||
{
|
||||
add => this.EventManager.ChestInventoryChanged.Add(value);
|
||||
add => this.EventManager.ChestInventoryChanged.Add(value, this.Mod);
|
||||
remove => this.EventManager.ChestInventoryChanged.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after terrain features (like floors and trees) are added or removed in a location.</summary>
|
||||
public event EventHandler<TerrainFeatureListChangedEventArgs> TerrainFeatureListChanged
|
||||
{
|
||||
add => this.EventManager.TerrainFeatureListChanged.Add(value);
|
||||
add => this.EventManager.TerrainFeatureListChanged.Add(value, this.Mod);
|
||||
remove => this.EventManager.TerrainFeatureListChanged.Remove(value);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#if HARMONY_2
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -40,3 +41,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#if HARMONY_2
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -82,3 +83,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
||||
{
|
||||
public class HarmonyInstanceMethods
|
||||
{
|
||||
public static MethodInfo Patch(
|
||||
#if HARMONY_2
|
||||
Harmony instance,
|
||||
#else
|
||||
HarmonyInstance instance,
|
||||
#endif
|
||||
MethodBase original,
|
||||
HarmonyMethod prefix = null,
|
||||
HarmonyMethod postfix = null,
|
||||
|
@ -15,16 +23,32 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
HarmonyMethod finalizer = null)
|
||||
{
|
||||
if (Constants.HarmonyEnabled)
|
||||
#if HARMONY_2
|
||||
return instance.Patch(original, prefix, postfix, transpiler, finalizer);
|
||||
#else
|
||||
return instance.Patch(original, prefix, postfix, transpiler);
|
||||
#endif
|
||||
else
|
||||
return null;
|
||||
}
|
||||
public static void PatchAll(Harmony instance)
|
||||
public static void PatchAll(
|
||||
#if HARMONY_2
|
||||
Harmony instance
|
||||
#else
|
||||
HarmonyInstance instance
|
||||
#endif
|
||||
)
|
||||
{
|
||||
if (Constants.HarmonyEnabled)
|
||||
instance.PatchAll();
|
||||
}
|
||||
public static void PatchAllToAssembly(Harmony instance, Assembly assembly)
|
||||
public static void PatchAllToAssembly(
|
||||
#if HARMONY_2
|
||||
Harmony instance,
|
||||
#else
|
||||
HarmonyInstance instance,
|
||||
#endif
|
||||
Assembly assembly)
|
||||
{
|
||||
if (Constants.HarmonyEnabled)
|
||||
instance.PatchAll(assembly);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#if HARMONY_2
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
|
@ -43,3 +44,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#if HARMONY_2
|
||||
using System;
|
||||
using HarmonyLib;
|
||||
using Mono.Cecil;
|
||||
|
@ -125,3 +126,4 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
using System;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingAPI.Framework.Patching
|
||||
{
|
||||
|
@ -27,7 +31,11 @@ namespace StardewModdingAPI.Framework.Patching
|
|||
/// <param name="patches">The patches to apply.</param>
|
||||
public void Apply(params IHarmonyPatch[] patches)
|
||||
{
|
||||
#if HARMONY_2
|
||||
Harmony harmony = new Harmony("SMAPI");
|
||||
#else
|
||||
HarmonyInstance harmony = HarmonyInstance.Create("SMAPI");
|
||||
#endif
|
||||
foreach (IHarmonyPatch patch in patches)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingAPI.Framework.Patching
|
||||
{
|
||||
|
@ -10,6 +14,10 @@ namespace StardewModdingAPI.Framework.Patching
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
#if HARMONY_2
|
||||
void Apply(Harmony harmony);
|
||||
#else
|
||||
void Apply(HarmonyInstance harmony);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
#if !HARMONY_2
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Patching
|
||||
{
|
||||
/// <summary>Provides generic methods for implementing Harmony patches.</summary>
|
||||
internal class PatchHelper
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The interception keys currently being intercepted.</summary>
|
||||
private static readonly HashSet<string> InterceptingKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Track a method that will be intercepted.</summary>
|
||||
/// <param name="key">The intercept key.</param>
|
||||
/// <returns>Returns false if the method was already marked for interception, else true.</returns>
|
||||
public static bool StartIntercept(string key)
|
||||
{
|
||||
return PatchHelper.InterceptingKeys.Add(key);
|
||||
}
|
||||
|
||||
/// <summary>Track a method as no longer being intercepted.</summary>
|
||||
/// <param name="key">The intercept key.</param>
|
||||
public static void StopIntercept(string key)
|
||||
{
|
||||
PatchHelper.InterceptingKeys.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -175,7 +175,7 @@ namespace StardewModdingAPI.Framework
|
|||
this.MonitorForGame = this.GetSecondaryMonitor("game");
|
||||
|
||||
SCore.PerformanceMonitor = new PerformanceMonitor(this.Monitor);
|
||||
this.EventManager = new EventManager(this.Monitor, this.ModRegistry, SCore.PerformanceMonitor);
|
||||
this.EventManager = new EventManager(this.ModRegistry, SCore.PerformanceMonitor);
|
||||
SCore.PerformanceMonitor.InitializePerformanceCounterCollections(this.EventManager);
|
||||
|
||||
SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry);
|
||||
|
@ -489,7 +489,9 @@ namespace StardewModdingAPI.Framework
|
|||
this.Monitor.Log("Type 'help' for help, or 'help <cmd>' for a command's usage", LogLevel.Info);
|
||||
this.GameInstance.CommandManager
|
||||
.Add(new HelpCommand(this.GameInstance.CommandManager), this.Monitor)
|
||||
#if HARMONY_2
|
||||
.Add(new HarmonySummaryCommand(), this.Monitor)
|
||||
#endif
|
||||
.Add(new ReloadI18nCommand(this.ReloadTranslations), this.Monitor);
|
||||
|
||||
// update window titles
|
||||
|
@ -538,7 +540,9 @@ namespace StardewModdingAPI.Framework
|
|||
this.Monitor.Log("Type 'help' for help, or 'help <cmd>' for a command's usage", LogLevel.Info);
|
||||
this.GameInstance.CommandManager
|
||||
.Add(new HelpCommand(this.GameInstance.CommandManager), this.Monitor)
|
||||
#if HARMONY_2
|
||||
.Add(new HarmonySummaryCommand(), this.Monitor)
|
||||
#endif
|
||||
.Add(new ReloadI18nCommand(this.ReloadTranslations), this.Monitor);
|
||||
|
||||
// start handling command line input
|
||||
|
@ -1020,7 +1024,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
catch (SAssemblyLoadFailedException ex)
|
||||
{
|
||||
errorReasonPhrase = $"it DLL couldn't be loaded: {ex.Message}";
|
||||
errorReasonPhrase = $"its DLL couldn't be loaded: {ex.Message}";
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -304,7 +304,7 @@ namespace StardewModdingAPI.Framework
|
|||
modIDs.Remove(message.FromModID); // don't send a broadcast back to the sender
|
||||
|
||||
// raise events
|
||||
this.Events.ModMessageReceived.RaiseForMods(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID));
|
||||
this.Events.ModMessageReceived.Raise(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID));
|
||||
}
|
||||
|
||||
/// <summary>A callback invoked when custom content is removed from the save data to avoid a crash.</summary>
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using StardewModdingAPI.Events;
|
||||
|
@ -118,15 +122,23 @@ namespace StardewModdingAPI.Metadata
|
|||
yield return new FieldToPropertyRewriter(typeof(Game1), "activeClickableMenu");
|
||||
yield return new FieldToPropertyRewriter(typeof(Game1), "stats");
|
||||
|
||||
#if HARMONY_2
|
||||
// rewrite for SMAPI 3.6 (Harmony 1.x => 2.0 update)
|
||||
yield return new Harmony1AssemblyRewriter();
|
||||
#endif
|
||||
|
||||
// MonoMod fix
|
||||
if (!Constants.HarmonyEnabled)
|
||||
{
|
||||
#if HARMONY_2
|
||||
yield return new MethodToAnotherStaticMethodRewriter(typeof(Harmony), (method) => method.Name == "Patch", typeof(HarmonyInstanceMethods), "Patch");
|
||||
yield return new MethodToAnotherStaticMethodRewriter(typeof(Harmony), (method) => method.Name == "PatchAll" && method.Parameters.Count == 0, typeof(HarmonyInstanceMethods), "PatchAll");
|
||||
yield return new MethodToAnotherStaticMethodRewriter(typeof(Harmony), (method) => method.Name == "PatchAll" && method.Parameters.Count == 1, typeof(HarmonyInstanceMethods), "PatchAllToAssembly");
|
||||
#else
|
||||
yield return new MethodToAnotherStaticMethodRewriter(typeof(HarmonyInstance), (method) => method.Name == "Patch", typeof(HarmonyInstanceMethods), "Patch");
|
||||
yield return new MethodToAnotherStaticMethodRewriter(typeof(HarmonyInstance), (method) => method.Name == "PatchAll" && method.Parameters.Count == 0, typeof(HarmonyInstanceMethods), "PatchAll");
|
||||
yield return new MethodToAnotherStaticMethodRewriter(typeof(HarmonyInstance), (method) => method.Name == "PatchAll" && method.Parameters.Count == 1, typeof(HarmonyInstanceMethods), "PatchAllToAssembly");
|
||||
#endif
|
||||
}
|
||||
|
||||
/****
|
||||
|
@ -139,7 +151,11 @@ namespace StardewModdingAPI.Metadata
|
|||
/****
|
||||
** detect code which may impact game stability
|
||||
****/
|
||||
#if HARMONY_2
|
||||
yield return new TypeFinder(typeof(HarmonyLib.Harmony).FullName, InstructionHandleResult.DetectedGamePatch);
|
||||
#else
|
||||
yield return new TypeFinder(typeof(Harmony.HarmonyInstance).FullName, InstructionHandleResult.DetectedGamePatch);
|
||||
#endif
|
||||
yield return new TypeFinder("System.Runtime.CompilerServices.CallSite", InstructionHandleResult.DetectedDynamic);
|
||||
yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.serializer), InstructionHandleResult.DetectedSaveSerializer);
|
||||
yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.farmerSerializer), InstructionHandleResult.DetectedSaveSerializer);
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewValley;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework;
|
||||
#else
|
||||
using System.Reflection;
|
||||
using Harmony;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingAPI.Patches
|
||||
{
|
||||
|
@ -47,6 +52,7 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
#if HARMONY_2
|
||||
public void Apply(Harmony harmony)
|
||||
{
|
||||
harmony.Patch(
|
||||
|
@ -58,11 +64,24 @@ namespace StardewModdingAPI.Patches
|
|||
finalizer: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Finalize_NPC_CurrentDialogue))
|
||||
);
|
||||
}
|
||||
|
||||
#else
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Before_Dialogue_Constructor))
|
||||
);
|
||||
harmony.Patch(
|
||||
original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod,
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Before_NPC_CurrentDialogue))
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
#if HARMONY_2
|
||||
/// <summary>The method to call after the Dialogue constructor.</summary>
|
||||
/// <param name="__instance">The instance being patched.</param>
|
||||
/// <param name="masterDialogue">The dialogue being parsed.</param>
|
||||
|
@ -102,5 +121,74 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
|
||||
/// <summary>The method to call instead of the Dialogue constructor.</summary>
|
||||
/// <param name="__instance">The instance being patched.</param>
|
||||
/// <param name="masterDialogue">The dialogue being parsed.</param>
|
||||
/// <param name="speaker">The NPC for which the dialogue is being parsed.</param>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
private static bool Before_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker)
|
||||
{
|
||||
// get private members
|
||||
bool nameArraysTranslated = DialogueErrorPatch.Reflection.GetField<bool>(typeof(Dialogue), "nameArraysTranslated").GetValue();
|
||||
IReflectedMethod translateArraysOfStrings = DialogueErrorPatch.Reflection.GetMethod(typeof(Dialogue), "TranslateArraysOfStrings");
|
||||
IReflectedMethod parseDialogueString = DialogueErrorPatch.Reflection.GetMethod(__instance, "parseDialogueString");
|
||||
IReflectedMethod checkForSpecialDialogueAttributes = DialogueErrorPatch.Reflection.GetMethod(__instance, "checkForSpecialDialogueAttributes");
|
||||
IReflectedField<List<string>> dialogues = DialogueErrorPatch.Reflection.GetField<List<string>>(__instance, "dialogues");
|
||||
|
||||
// replicate base constructor
|
||||
if (dialogues.GetValue() == null)
|
||||
dialogues.SetValue(new List<string>());
|
||||
|
||||
// duplicate code with try..catch
|
||||
try
|
||||
{
|
||||
if (!nameArraysTranslated)
|
||||
translateArraysOfStrings.Invoke();
|
||||
__instance.speaker = speaker;
|
||||
parseDialogueString.Invoke(masterDialogue);
|
||||
checkForSpecialDialogueAttributes.Invoke();
|
||||
}
|
||||
catch (Exception baseEx) when (baseEx.InnerException is TargetInvocationException invocationEx && invocationEx.InnerException is Exception ex)
|
||||
{
|
||||
string name = !string.IsNullOrWhiteSpace(speaker?.Name) ? speaker.Name : null;
|
||||
DialogueErrorPatch.MonitorForGame.Log($"Failed parsing dialogue string{(name != null ? $" for {name}" : "")}:\n{masterDialogue}\n{ex}", LogLevel.Error);
|
||||
|
||||
parseDialogueString.Invoke("...");
|
||||
checkForSpecialDialogueAttributes.Invoke();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>The method to call instead of <see cref="NPC.CurrentDialogue"/>.</summary>
|
||||
/// <param name="__instance">The instance being patched.</param>
|
||||
/// <param name="__result">The return value of the original method.</param>
|
||||
/// <param name="__originalMethod">The method being wrapped.</param>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, MethodInfo __originalMethod)
|
||||
{
|
||||
const string key = nameof(Before_NPC_CurrentDialogue);
|
||||
if (!PatchHelper.StartIntercept(key))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
__result = (Stack<Dialogue>)__originalMethod.Invoke(__instance, new object[0]);
|
||||
return false;
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
DialogueErrorPatch.MonitorForGame.Log($"Failed loading current dialogue for NPC {__instance.Name}:\n{ex.InnerException ?? ex}", LogLevel.Error);
|
||||
__result = new Stack<Dialogue>();
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
PatchHelper.StopIntercept(key);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#if HARMONY_2
|
||||
using System;
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using System.Reflection;
|
||||
using Harmony;
|
||||
#endif
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewValley;
|
||||
|
||||
|
@ -38,6 +43,7 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
#if HARMONY_2
|
||||
public void Apply(Harmony harmony)
|
||||
{
|
||||
harmony.Patch(
|
||||
|
@ -45,11 +51,21 @@ namespace StardewModdingAPI.Patches
|
|||
finalizer: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Finalize_GameLocation_CheckEventPrecondition))
|
||||
);
|
||||
}
|
||||
#else
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(GameLocation), "checkEventPrecondition"),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Before_GameLocation_CheckEventPrecondition))
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
#if HARMONY_2
|
||||
/// <summary>The method to call instead of the GameLocation.CheckEventPrecondition.</summary>
|
||||
/// <param name="__result">The return value of the original method.</param>
|
||||
/// <param name="precondition">The precondition to be parsed.</param>
|
||||
|
@ -65,5 +81,35 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
/// <summary>The method to call instead of the GameLocation.CheckEventPrecondition.</summary>
|
||||
/// <param name="__instance">The instance being patched.</param>
|
||||
/// <param name="__result">The return value of the original method.</param>
|
||||
/// <param name="precondition">The precondition to be parsed.</param>
|
||||
/// <param name="__originalMethod">The method being wrapped.</param>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod)
|
||||
{
|
||||
const string key = nameof(Before_GameLocation_CheckEventPrecondition);
|
||||
if (!PatchHelper.StartIntercept(key))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
__result = (int)__originalMethod.Invoke(__instance, new object[] { precondition });
|
||||
return false;
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
__result = -1;
|
||||
EventErrorPatch.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
PatchHelper.StopIntercept(key);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,11 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
using Microsoft.Xna.Framework;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewValley;
|
||||
|
@ -47,7 +51,13 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
public void Apply(Harmony harmony)
|
||||
public void Apply(
|
||||
#if HARMONY_2
|
||||
Harmony harmony
|
||||
#else
|
||||
HarmonyInstance harmony
|
||||
#endif
|
||||
)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.DeclaredConstructor(typeof(JunimoHarvester), new System.Type[] { typeof(Vector2), typeof(JunimoHut), typeof(int), typeof(Color?)}),
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
using StardewModdingAPI.Enums;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
|
@ -47,7 +51,11 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
#if HARMONY_2
|
||||
public void Apply(Harmony harmony)
|
||||
#else
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
#endif
|
||||
{
|
||||
// detect CreatedBasicInfo
|
||||
harmony.Patch(
|
||||
|
|
|
@ -2,7 +2,11 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
using StardewModdingAPI.Framework.Exceptions;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewValley;
|
||||
|
@ -49,7 +53,11 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
#if HARMONY_2
|
||||
public void Apply(Harmony harmony)
|
||||
#else
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
#endif
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)),
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewValley;
|
||||
using StardewValley.Characters;
|
||||
|
@ -42,7 +46,13 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
public void Apply(Harmony harmony)
|
||||
public void Apply(
|
||||
#if HARMONY_2
|
||||
Harmony harmony
|
||||
#else
|
||||
HarmonyInstance harmony
|
||||
#endif
|
||||
)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Property(typeof(Game1), "currentLocation").SetMethod,
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewValley;
|
||||
using StardewValley.Menus;
|
||||
using SObject = StardewValley.Object;
|
||||
#if HARMONY_2
|
||||
using System;
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using System.Reflection;
|
||||
using Harmony;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingAPI.Patches
|
||||
{
|
||||
|
@ -27,7 +32,11 @@ namespace StardewModdingAPI.Patches
|
|||
*********/
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
#if HARMONY_2
|
||||
public void Apply(Harmony harmony)
|
||||
#else
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
#endif
|
||||
{
|
||||
// object.getDescription
|
||||
harmony.Patch(
|
||||
|
@ -38,7 +47,11 @@ namespace StardewModdingAPI.Patches
|
|||
// object.getDisplayName
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(SObject), "loadDisplayName"),
|
||||
#if HARMONY_2
|
||||
finalizer: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Finalize_Object_loadDisplayName))
|
||||
#else
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_Object_loadDisplayName))
|
||||
#endif
|
||||
);
|
||||
|
||||
// IClickableMenu.drawToolTip
|
||||
|
@ -68,6 +81,7 @@ namespace StardewModdingAPI.Patches
|
|||
return true;
|
||||
}
|
||||
|
||||
#if HARMONY_2
|
||||
/// <summary>The method to call after <see cref="StardewValley.Object.loadDisplayName"/>.</summary>
|
||||
/// <param name="__result">The patched method's return value.</param>
|
||||
/// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
|
||||
|
@ -82,6 +96,38 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
return __exception;
|
||||
}
|
||||
#else
|
||||
/// <summary>The method to call instead of <see cref="StardewValley.Object.loadDisplayName"/>.</summary>
|
||||
/// <param name="__instance">The instance being patched.</param>
|
||||
/// <param name="__result">The patched method's return value.</param>
|
||||
/// <param name="__originalMethod">The method being wrapped.</param>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
private static bool Before_Object_loadDisplayName(SObject __instance, ref string __result, MethodInfo __originalMethod)
|
||||
{
|
||||
const string key = nameof(Before_Object_loadDisplayName);
|
||||
if (!PatchHelper.StartIntercept(key))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
__result = (string)__originalMethod.Invoke(__instance, new object[0]);
|
||||
return false;
|
||||
}
|
||||
catch (TargetInvocationException ex) when (ex.InnerException is KeyNotFoundException)
|
||||
{
|
||||
__result = "???";
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
PatchHelper.StopIntercept(key);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>The method to call instead of <see cref="IClickableMenu.drawToolTip"/>.</summary>
|
||||
/// <param name="hoveredItem">The item for which to draw a tooltip.</param>
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewValley;
|
||||
|
@ -28,16 +32,21 @@ namespace StardewModdingAPI.Patches
|
|||
OnAppPausePatch.Monitor = monitor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
#if HARMONY_2
|
||||
public void Apply(Harmony harmony)
|
||||
{
|
||||
harmony.Patch(AccessTools.Method(typeof(Game1), nameof(Game1.OnAppPause)),
|
||||
finalizer:new HarmonyMethod(AccessTools.Method(this.GetType(), nameof(OnAppPausePatch.Game_OnAppPauseFinalizer))));
|
||||
}
|
||||
|
||||
#else
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
{
|
||||
harmony.Patch(AccessTools.Method(typeof(Game1), nameof(Game1.OnAppPause)),
|
||||
new HarmonyMethod(AccessTools.Method(this.GetType(), nameof(OnAppPausePatch.Game_OnAppPausePrefix))));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
|
@ -45,6 +54,7 @@ namespace StardewModdingAPI.Patches
|
|||
/// <summary>The method to call instead of <see cref="StardewValley.Game1.OnAppPause"/>.</summary>
|
||||
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")]
|
||||
#if HARMONY_2
|
||||
private static Exception Game_OnAppPauseFinalizer(Exception __exception)
|
||||
{
|
||||
if (__exception != null)
|
||||
|
@ -53,6 +63,26 @@ namespace StardewModdingAPI.Patches
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#else
|
||||
private static bool Game_OnAppPausePrefix(Game1 __instance, MethodInfo __originalMethod)
|
||||
{
|
||||
const string key = nameof(OnAppPausePatch.Game_OnAppPausePrefix);
|
||||
if (!PatchHelper.StartIntercept(key))
|
||||
return true;
|
||||
try
|
||||
{
|
||||
__originalMethod.Invoke(__instance, new object[] { });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnAppPausePatch.Monitor.Log($"Failed during OnAppPause method :\n{ex.InnerException ?? ex}", LogLevel.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PatchHelper.StopIntercept(key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.Events;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
|
@ -39,6 +43,7 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
#if HARMONY_2
|
||||
public void Apply(Harmony harmony)
|
||||
{
|
||||
MethodInfo makeFullBackup = AccessTools.Method(typeof(Game1), nameof(Game1.MakeFullBackup));
|
||||
|
@ -50,14 +55,25 @@ namespace StardewModdingAPI.Patches
|
|||
harmony.Patch(makeFullBackup, new HarmonyMethod(prefix), finalizer: new HarmonyMethod(finalizer));
|
||||
harmony.Patch(saveWholeBackup, new HarmonyMethod(prefix), finalizer: new HarmonyMethod(finalizer));
|
||||
}
|
||||
#else
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
{
|
||||
MethodInfo makeFullBackup = AccessTools.Method(typeof(Game1), nameof(Game1.MakeFullBackup));
|
||||
MethodInfo saveWholeBackup = AccessTools.Method(typeof(Game1), nameof(Game1.saveWholeBackup));
|
||||
|
||||
MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(SaveBackupPatch.GameSave_Prefix));
|
||||
|
||||
harmony.Patch(makeFullBackup, new HarmonyMethod(prefix));
|
||||
harmony.Patch(saveWholeBackup, new HarmonyMethod(prefix));
|
||||
}
|
||||
#endif
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>The method to call instead of <see cref="StardewValley.Object.getDescription"/>.</summary>
|
||||
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")]
|
||||
#if HARMONY_2
|
||||
private static bool GameSave_Prefix()
|
||||
{
|
||||
SaveBackupPatch.Events.Saving.RaiseEmpty();
|
||||
|
@ -75,5 +91,29 @@ namespace StardewModdingAPI.Patches
|
|||
SaveBackupPatch.Events.Saved.RaiseEmpty();
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
private static bool GameSave_Prefix(MethodInfo __originalMethod)
|
||||
{
|
||||
const string key = nameof(SaveBackupPatch.GameSave_Prefix);
|
||||
if (!PatchHelper.StartIntercept(key))
|
||||
return true;
|
||||
SaveBackupPatch.Events.Saving.RaiseEmpty();
|
||||
try
|
||||
{
|
||||
__originalMethod.Invoke(null, new object[] { });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SaveBackupPatch.Monitor.Log($"Failed to save the game :\n{ex.InnerException ?? ex}", LogLevel.Error);
|
||||
Game1.addHUDMessage(new HUDMessage("An error occurs during save the game.Check the error log for details.", HUDMessage.error_type));
|
||||
}
|
||||
finally
|
||||
{
|
||||
PatchHelper.StopIntercept(key);
|
||||
}
|
||||
SaveBackupPatch.Events.Saved.RaiseEmpty();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,11 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
using Microsoft.AppCenter.Crashes;
|
||||
using Microsoft.Xna.Framework;
|
||||
using StardewModdingAPI.Framework;
|
||||
|
@ -42,6 +46,7 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
#if HARMONY_2
|
||||
public void Apply(Harmony harmony)
|
||||
{
|
||||
harmony.Patch(
|
||||
|
@ -53,7 +58,19 @@ namespace StardewModdingAPI.Patches
|
|||
finalizer: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.SaveGameMenu_UpdateFinalizer))
|
||||
);
|
||||
}
|
||||
|
||||
#else
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(SaveGame), "HandleLoadError"),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.Prefix))
|
||||
);
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(SaveGameMenu), "update"),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.SaveGameMenu_UpdatePrefix))
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
|
@ -156,6 +173,7 @@ namespace StardewModdingAPI.Patches
|
|||
/// <summary>The method to call instead of <see cref="StardewValley.Menus.SaveGameMenu.update"/>.</summary>
|
||||
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")]
|
||||
#if HARMONY_2
|
||||
private static Exception SaveGameMenu_UpdateFinalizer(SaveGameMenu __instance, Exception __exception)
|
||||
{
|
||||
if(__exception != null) {
|
||||
|
@ -165,5 +183,28 @@ namespace StardewModdingAPI.Patches
|
|||
}
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
private static bool SaveGameMenu_UpdatePrefix(SaveGameMenu __instance, GameTime time, MethodInfo __originalMethod)
|
||||
{
|
||||
const string key = nameof(SaveGamePatch.SaveGameMenu_UpdatePrefix);
|
||||
if (!PatchHelper.StartIntercept(key))
|
||||
return true;
|
||||
try
|
||||
{
|
||||
__originalMethod.Invoke(__instance, new object[] {time});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SaveGamePatch.Monitor.Log($"Failed during SaveGameMenu.update method :\n{ex.InnerException ?? ex}", LogLevel.Error);
|
||||
__instance.complete();
|
||||
Game1.addHUDMessage(new HUDMessage("An error occurs during save the game.Check the error log for details.", HUDMessage.error_type));
|
||||
}
|
||||
finally
|
||||
{
|
||||
PatchHelper.StopIntercept(key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewValley;
|
||||
#if HARMONY_2
|
||||
using System;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework;
|
||||
#else
|
||||
using System.Reflection;
|
||||
using Harmony;
|
||||
#endif
|
||||
|
||||
namespace StardewModdingAPI.Patches
|
||||
{
|
||||
|
@ -40,11 +45,19 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
#if HARMONY_2
|
||||
public void Apply(Harmony harmony)
|
||||
#else
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
#endif
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(NPC), "parseMasterSchedule"),
|
||||
#if HARMONY_2
|
||||
finalizer: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Finalize_NPC_parseMasterSchedule))
|
||||
#else
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Before_NPC_parseMasterSchedule))
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -52,6 +65,7 @@ namespace StardewModdingAPI.Patches
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
#if HARMONY_2
|
||||
/// <summary>The method to call instead of <see cref="NPC.parseMasterSchedule"/>.</summary>
|
||||
/// <param name="rawData">The raw schedule data to parse.</param>
|
||||
/// <param name="__instance">The instance being patched.</param>
|
||||
|
@ -68,5 +82,35 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
/// <summary>The method to call instead of <see cref="NPC.parseMasterSchedule"/>.</summary>
|
||||
/// <param name="rawData">The raw schedule data to parse.</param>
|
||||
/// <param name="__instance">The instance being patched.</param>
|
||||
/// <param name="__result">The patched method's return value.</param>
|
||||
/// <param name="__originalMethod">The method being wrapped.</param>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
private static bool Before_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary<int, SchedulePathDescription> __result, MethodInfo __originalMethod)
|
||||
{
|
||||
const string key = nameof(Before_NPC_parseMasterSchedule);
|
||||
if (!PatchHelper.StartIntercept(key))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
__result = (Dictionary<int, SchedulePathDescription>)__originalMethod.Invoke(__instance, new object[] { rawData });
|
||||
return false;
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
ScheduleErrorPatch.MonitorForGame.Log($"Failed parsing schedule for NPC {__instance.Name}:\n{rawData}\n{ex.InnerException ?? ex}", LogLevel.Error);
|
||||
__result = new Dictionary<int, SchedulePathDescription>();
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
PatchHelper.StopIntercept(key);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewValley.Characters;
|
||||
|
@ -41,7 +45,13 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
public void Apply(Harmony harmony)
|
||||
public void Apply(
|
||||
#if HARMONY_2
|
||||
Harmony harmony
|
||||
#else
|
||||
HarmonyInstance harmony
|
||||
#endif
|
||||
)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(SpriteFont), "MeasureString", new System.Type[] { typeof(string)}),
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
#if HARMONY_2
|
||||
using HarmonyLib;
|
||||
#else
|
||||
using Harmony;
|
||||
#endif
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
|
||||
namespace StardewModdingAPI.Patches
|
||||
|
@ -41,6 +45,7 @@ namespace StardewModdingAPI.Patches
|
|||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
#if HARMONY_2
|
||||
public void Apply(Harmony harmony)
|
||||
{
|
||||
harmony.Patch(
|
||||
|
@ -48,7 +53,15 @@ namespace StardewModdingAPI.Patches
|
|||
finalizer: new HarmonyMethod(this.GetType(), nameof(ThreadSilenceExitPatch.ThreadStart_Finalizer))
|
||||
);
|
||||
}
|
||||
|
||||
#else
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(Type.GetType("System.Threading.ThreadHelper"), "ThreadStart_Context"),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(ThreadSilenceExitPatch.ThreadStart_Finalizer))
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
|
@ -56,6 +69,7 @@ namespace StardewModdingAPI.Patches
|
|||
/// <summary>The method to call instead of <see cref="System.Threading.ThreadHelper.ThreadStart_Context"/>.</summary>
|
||||
/// <param name="state">The thread context.</param>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
#if HARMONY_2
|
||||
private static Exception ThreadStart_Finalizer(Exception __exception)
|
||||
{
|
||||
if (__exception != null) {
|
||||
|
@ -63,5 +77,28 @@ namespace StardewModdingAPI.Patches
|
|||
}
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
private static bool ThreadStart_Finalizer(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
object _start = state.GetType().GetField("_start", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(state);
|
||||
if (_start is ThreadStart)
|
||||
{
|
||||
((ThreadStart)_start)();
|
||||
}
|
||||
else
|
||||
{
|
||||
object _startArg = state.GetType().GetField("_startArg", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(state);
|
||||
((ParameterizedThreadStart)_start)(_startArg);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Monitor.Log($"Thread failed:\n{ex.InnerException ?? ex}", LogLevel.Error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
@ -36,7 +36,7 @@
|
|||
<OutputPath>bin\Release\</OutputPath>
|
||||
<!-- <DefineConstants>TRACE;DEBUG;ANDROID_TARGET_GOOGLE</DefineConstants>-->
|
||||
<!-- <DefineConstants>TRACE;DEBUG;ANDROID_TARGET_SAMSUNG</DefineConstants>-->
|
||||
<DefineConstants>TRACE;DEBUG;ANDROID_TARGET_GOOGLE</DefineConstants>
|
||||
<DefineConstants>TRACE;DEBUG;ANDROID_TARGET_GOOGLE;HARMONY_1</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
|
@ -143,6 +143,8 @@
|
|||
<Compile Include="Events\DayEndingEventArgs.cs" />
|
||||
<Compile Include="Events\DayStartedEventArgs.cs" />
|
||||
<Compile Include="Events\DebrisListChangedEventArgs.cs" />
|
||||
<Compile Include="Events\EventPriority.cs" />
|
||||
<Compile Include="Events\EventPriorityAttribute.cs" />
|
||||
<Compile Include="Events\GameLaunchedEventArgs.cs" />
|
||||
<Compile Include="Events\IDisplayEvents.cs" />
|
||||
<Compile Include="Events\IGameLoopEvents.cs" />
|
||||
|
@ -217,6 +219,7 @@
|
|||
<Compile Include="Framework\Events\EventManager.cs" />
|
||||
<Compile Include="Framework\Events\IManagedEvent.cs" />
|
||||
<Compile Include="Framework\Events\ManagedEvent.cs" />
|
||||
<Compile Include="Framework\Events\ManagedEventHandler.cs" />
|
||||
<Compile Include="Framework\Events\ModDisplayEvents.cs" />
|
||||
<Compile Include="Framework\Events\ModEvents.cs" />
|
||||
<Compile Include="Framework\Events\ModEventsBase.cs" />
|
||||
|
@ -325,6 +328,7 @@
|
|||
<Compile Include="Framework\Networking\RemoteContextModModel.cs" />
|
||||
<Compile Include="Framework\Patching\GamePatcher.cs" />
|
||||
<Compile Include="Framework\Patching\IHarmonyPatch.cs" />
|
||||
<Compile Include="Framework\Patching\PatchHelper.cs" />
|
||||
<Compile Include="Framework\PerformanceMonitoring\AlertContext.cs" />
|
||||
<Compile Include="Framework\PerformanceMonitoring\AlertEntry.cs" />
|
||||
<Compile Include="Framework\PerformanceMonitoring\PeakEntry.cs" />
|
||||
|
|
Loading…
Reference in New Issue