Merge branch 'develop' into stable
This commit is contained in:
commit
0da5dab893
|
@ -2,5 +2,5 @@ using System.Reflection;
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: AssemblyVersion("2.3.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.3.0.0")]
|
||||
[assembly: AssemblyVersion("2.4.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.4.0.0")]
|
||||
|
|
|
@ -1,4 +1,29 @@
|
|||
# Release notes
|
||||
## 2.4
|
||||
* For players:
|
||||
* Fixed visual map glitch in rare cases.
|
||||
* Fixed error parsing JSON files which have curly quotes.
|
||||
* Fixed error parsing some JSON files generated on another system.
|
||||
* Fixed error parsing some JSON files after mods reload core assemblies, which is no longer allowed.
|
||||
* Fixed intermittent errors (e.g. 'collection has been modified') with some mods when loading a save.
|
||||
* Fixed compatibility with Linux Terminator terminal.
|
||||
|
||||
* For the [log parser][]:
|
||||
* Fixed error parsing logs with zero installed mods.
|
||||
|
||||
* For modders:
|
||||
* Added `SaveEvents.BeforeCreate` and `AfterCreate` events.
|
||||
* Added `SButton` `IsActionButton()` and `IsUseToolButton()` extensions.
|
||||
* Improved JSON parse errors to provide more useful info for troubleshooting.
|
||||
* Fixed events being raised while the game is loading a save file.
|
||||
* Fixed input events not recognising controller input as an action or use-tool button.
|
||||
* Fixed input events setting the same `IsActionButton` and `IsUseToolButton` values for all buttons pressed in an update tick.
|
||||
* Fixed semantic versions ignoring `-0` as a prerelease tag.
|
||||
* Updated Json.NET to 11.0.1-beta3 (needed to avoid a parser edge case).
|
||||
|
||||
* For SMAPI developers:
|
||||
* Overhauled input handling to support future input events.
|
||||
|
||||
## 2.3
|
||||
* For players:
|
||||
* Added a user-friendly [download page](https://smapi.io).
|
||||
|
|
|
@ -190,9 +190,7 @@ namespace StardewModdingAPI.Common
|
|||
private string GetNormalisedTag(string tag)
|
||||
{
|
||||
tag = tag?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(tag) || tag == "0") // '0' from incorrect examples in old SMAPI documentation
|
||||
return null;
|
||||
return tag;
|
||||
return !string.IsNullOrWhiteSpace(tag) ? tag : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,8 +122,8 @@
|
|||
<Error Condition="'$(OS)' != 'OSX' AND '$(OS)' != 'Unix' AND '$(OS)' != 'Windows_NT'" Text="The mod build package doesn't recognise OS type '$(OS)'." />
|
||||
|
||||
<Error Condition="!Exists('$(GamePath)')" Text="The mod build package can't find your game folder. You can specify where to find it; see details at https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md#game-path." />
|
||||
<Error Condition="'$(OS)' == 'Windows_NT' AND !Exists('$(GamePath)\Stardew Valley.exe')" Text="The mod build package found a a game folder at $(GamePath), but it doesn't contain the Stardew Valley.exe file. If this folder is invalid, delete it and the package will autodetect another game install path." />
|
||||
<Error Condition="'$(OS)' != 'Windows_NT' AND !Exists('$(GamePath)\StardewValley.exe')" Text="The mod build package found a a game folder at $(GamePath), but it doesn't contain the StardewValley.exe file. If this folder is invalid, delete it and the package will autodetect another game install path." />
|
||||
<Error Condition="'$(OS)' == 'Windows_NT' AND !Exists('$(GamePath)\Stardew Valley.exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain the Stardew Valley.exe file. If this folder is invalid, delete it and the package will autodetect another game install path." />
|
||||
<Error Condition="'$(OS)' != 'Windows_NT' AND !Exists('$(GamePath)\StardewValley.exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain the StardewValley.exe file. If this folder is invalid, delete it and the package will autodetect another game install path." />
|
||||
<Error Condition="!Exists('$(GamePath)\StardewModdingAPI.exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain SMAPI. You need to install SMAPI before building the mod." />
|
||||
</Target>
|
||||
|
||||
|
|
|
@ -36,8 +36,8 @@
|
|||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.11.0.1-beta3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
{
|
||||
"Name": "Console Commands",
|
||||
"Author": "SMAPI",
|
||||
"Version": {
|
||||
"MajorVersion": 2,
|
||||
"MinorVersion": 3,
|
||||
"PatchVersion": 0,
|
||||
"Build": null
|
||||
},
|
||||
"Version": "2.4.0",
|
||||
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
||||
"UniqueID": "SMAPI.ConsoleCommands",
|
||||
"EntryDll": "ConsoleCommands.dll"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="11.0.1-beta3" targetFramework="net45" />
|
||||
</packages>
|
|
@ -36,8 +36,8 @@
|
|||
<Reference Include="Moq, Version=4.7.142.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Moq.4.7.142\lib\net45\Moq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.11.0.1-beta3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace StardewModdingAPI.Tests.Utilities
|
|||
[TestCase(3000, 4000, 5000, null, ExpectedResult = "3000.4000.5000")]
|
||||
[TestCase(1, 2, 3, "", ExpectedResult = "1.2.3")]
|
||||
[TestCase(1, 2, 3, " ", ExpectedResult = "1.2.3")]
|
||||
[TestCase(1, 2, 3, "0", ExpectedResult = "1.2.3-0")]
|
||||
[TestCase(1, 2, 3, "some-tag.4", ExpectedResult = "1.2.3-some-tag.4")]
|
||||
[TestCase(1, 2, 3, "some-tag.4 ", ExpectedResult = "1.2.3-some-tag.4")]
|
||||
public string Constructor_FromParts(int major, int minor, int patch, string tag)
|
||||
|
@ -270,6 +271,22 @@ namespace StardewModdingAPI.Tests.Utilities
|
|||
Assert.IsTrue(version.IsOlderThan(new SemanticVersion("1.2.30")), "The game version should be considered older than the later semantic versions.");
|
||||
}
|
||||
|
||||
/****
|
||||
** LegacyManifestVersion
|
||||
****/
|
||||
[Test(Description = "Assert that the LegacyManifestVersion subclass correctly parses legacy manifest versions.")]
|
||||
[TestCase(1, 0, 0, null, ExpectedResult = "1.0")]
|
||||
[TestCase(3000, 4000, 5000, null, ExpectedResult = "3000.4000.5000")]
|
||||
[TestCase(1, 2, 3, "", ExpectedResult = "1.2.3")]
|
||||
[TestCase(1, 2, 3, " ", ExpectedResult = "1.2.3")]
|
||||
[TestCase(1, 2, 3, "0", ExpectedResult = "1.2.3")] // special case: drop '0' tag for legacy manifest versions
|
||||
[TestCase(1, 2, 3, "some-tag.4", ExpectedResult = "1.2.3-some-tag.4")]
|
||||
[TestCase(1, 2, 3, "some-tag.4 ", ExpectedResult = "1.2.3-some-tag.4")]
|
||||
public string LegacyManifestVersion(int major, int minor, int patch, string tag)
|
||||
{
|
||||
return new LegacyManifestVersion(major, minor, patch, tag).ToString();
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
<packages>
|
||||
<package id="Castle.Core" version="4.2.1" targetFramework="net45" />
|
||||
<package id="Moq" version="4.7.142" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="11.0.1-beta3" targetFramework="net45" />
|
||||
<package id="NUnit" version="3.8.1" targetFramework="net45" />
|
||||
</packages>
|
|
@ -62,7 +62,7 @@
|
|||
|
||||
<h2>For mod creators</h2>
|
||||
<ul>
|
||||
<li><a href="@Model.DevDownloadUrl">SMAPI 2.2 for developers</a> (includes <a href="https://docs.microsoft.com/en-us/visualstudio/ide/using-intellisense">intellisense</a> and full console output)</li>
|
||||
<li><a href="@Model.DevDownloadUrl">SMAPI @Model.LatestVersion for developers</a> (includes <a href="https://docs.microsoft.com/en-us/visualstudio/ide/using-intellisense">intellisense</a> and full console output)</li>
|
||||
<li><a href="https://stardewvalleywiki.com/Modding:Index">Modding documentation</a></li>
|
||||
<li>Need help? Come <a href="https://stardewvalleywiki.com/Modding:Community#Discord">chat on Discord</a>.</li>
|
||||
</ul>
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
}
|
||||
@model StardewModdingAPI.Web.ViewModels.LogParserModel
|
||||
@section Head {
|
||||
<link rel="stylesheet" href="~/Content/css/log-parser.css?r=20171202" />
|
||||
<link rel="stylesheet" href="~/Content/css/log-parser.css?r=20180101" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" crossorigin="anonymous"></script>
|
||||
<script src="~/Content/js/log-parser.js?r=20171202"></script>
|
||||
<script src="~/Content/js/log-parser.js?r=20180101"></script>
|
||||
<style type="text/css" id="modflags"></style>
|
||||
<script>
|
||||
$(function() {
|
||||
|
|
|
@ -175,7 +175,7 @@ smapi.logParser = function(sectionUrl, pasteID) {
|
|||
|
||||
}
|
||||
var dataInfo = regexInfo.exec(data) || regexInfo.exec(data) || regexInfo.exec(data),
|
||||
dataMods = regexMods.exec(data) || regexMods.exec(data) || regexMods.exec(data),
|
||||
dataMods = regexMods.exec(data) || regexMods.exec(data) || regexMods.exec(data) || [""],
|
||||
dataDate = regexDate.exec(data) || regexDate.exec(data) || regexDate.exec(data),
|
||||
dataPath = regexPath.exec(data) || regexPath.exec(data) || regexPath.exec(data),
|
||||
match;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/StaticQualifier/STATIC_MEMBERS_QUALIFY_MEMBERS/@EntryValue">Field, Property, Event, Method</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue">Field, Property, Event, Method</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseVarWhenEvident</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||
|
@ -13,7 +14,12 @@
|
|||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||
</wpf:ResourceDictionary>
|
|
@ -29,7 +29,7 @@ namespace StardewModdingAPI
|
|||
** Public
|
||||
****/
|
||||
/// <summary>SMAPI's current semantic version.</summary>
|
||||
public static ISemanticVersion ApiVersion { get; } = new SemanticVersion("2.3");
|
||||
public static ISemanticVersion ApiVersion { get; } = new SemanticVersion("2.4.0");
|
||||
|
||||
/// <summary>The minimum supported version of Stardew Valley.</summary>
|
||||
public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.30");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using StardewModdingAPI.Framework;
|
||||
|
||||
namespace StardewModdingAPI.Events
|
||||
|
@ -9,6 +9,12 @@ namespace StardewModdingAPI.Events
|
|||
/*********
|
||||
** Events
|
||||
*********/
|
||||
/// <summary>Raised before the game creates the save file.</summary>
|
||||
public static event EventHandler BeforeCreate;
|
||||
|
||||
/// <summary>Raised after the game finishes creating the save file.</summary>
|
||||
public static event EventHandler AfterCreate;
|
||||
|
||||
/// <summary>Raised before the game begins writes data to the save file.</summary>
|
||||
public static event EventHandler BeforeSave;
|
||||
|
||||
|
@ -25,6 +31,20 @@ namespace StardewModdingAPI.Events
|
|||
/*********
|
||||
** Internal methods
|
||||
*********/
|
||||
/// <summary>Raise a <see cref="BeforeCreate"/> event.</summary>
|
||||
/// <param name="monitor">Encapsulates monitoring and logging.</param>
|
||||
internal static void InvokeBeforeCreate(IMonitor monitor)
|
||||
{
|
||||
monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.BeforeCreate)}", SaveEvents.BeforeCreate?.GetInvocationList(), null, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>Raise a <see cref="AfterCreate"/> event.</summary>
|
||||
/// <param name="monitor">Encapsulates monitoring and logging.</param>
|
||||
internal static void InvokeAfterCreated(IMonitor monitor)
|
||||
{
|
||||
monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.AfterCreate)}", SaveEvents.AfterCreate?.GetInvocationList(), null, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>Raise a <see cref="BeforeSave"/> event.</summary>
|
||||
/// <param name="monitor">Encapsulates monitoring and logging.</param>
|
||||
internal static void InvokeBeforeSave(IMonitor monitor)
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Input
|
||||
{
|
||||
/// <summary>A summary of input changes during an update frame.</summary>
|
||||
internal class InputState
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The underlying controller state.</summary>
|
||||
public GamePadState ControllerState { get; }
|
||||
|
||||
/// <summary>The underlying keyboard state.</summary>
|
||||
public KeyboardState KeyboardState { get; }
|
||||
|
||||
/// <summary>The underlying mouse state.</summary>
|
||||
public MouseState MouseState { get; }
|
||||
|
||||
/// <summary>The mouse position on the screen adjusted for the zoom level.</summary>
|
||||
public Point MousePosition { get; }
|
||||
|
||||
/// <summary>The buttons which were pressed, held, or released.</summary>
|
||||
public IDictionary<SButton, InputStatus> ActiveButtons { get; } = new Dictionary<SButton, InputStatus>();
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an empty instance.</summary>
|
||||
public InputState() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="previousState">The previous input state.</param>
|
||||
/// <param name="controllerState">The current controller state.</param>
|
||||
/// <param name="keyboardState">The current keyboard state.</param>
|
||||
/// <param name="mouseState">The current mouse state.</param>
|
||||
public InputState(InputState previousState, GamePadState controllerState, KeyboardState keyboardState, MouseState mouseState)
|
||||
{
|
||||
// init properties
|
||||
this.ControllerState = controllerState;
|
||||
this.KeyboardState = keyboardState;
|
||||
this.MouseState = mouseState;
|
||||
this.MousePosition = new Point((int)(mouseState.X * (1.0 / Game1.options.zoomLevel)), (int)(mouseState.Y * (1.0 / Game1.options.zoomLevel))); // derived from Game1::getMouseX
|
||||
|
||||
// get button states
|
||||
SButton[] down = InputState.GetPressedButtons(keyboardState, mouseState, controllerState).ToArray();
|
||||
foreach (SButton button in down)
|
||||
this.ActiveButtons[button] = this.GetStatus(previousState.GetStatus(button), isDown: true);
|
||||
foreach (KeyValuePair<SButton, InputStatus> prev in previousState.ActiveButtons)
|
||||
{
|
||||
if (prev.Value.IsDown() && !this.ActiveButtons.ContainsKey(prev.Key))
|
||||
this.ActiveButtons[prev.Key] = InputStatus.Released;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Get the status of a button.</summary>
|
||||
/// <param name="button">The button to check.</param>
|
||||
public InputStatus GetStatus(SButton button)
|
||||
{
|
||||
return this.ActiveButtons.TryGetValue(button, out InputStatus status) ? status : InputStatus.None;
|
||||
}
|
||||
|
||||
/// <summary>Get whether a given button was pressed or held.</summary>
|
||||
/// <param name="button">The button to check.</param>
|
||||
public bool IsDown(SButton button)
|
||||
{
|
||||
return this.GetStatus(button).IsDown();
|
||||
}
|
||||
|
||||
/// <summary>Get the current input state.</summary>
|
||||
/// <param name="previousState">The previous input state.</param>
|
||||
public static InputState GetState(InputState previousState)
|
||||
{
|
||||
GamePadState controllerState = GamePad.GetState(PlayerIndex.One);
|
||||
KeyboardState keyboardState = Keyboard.GetState();
|
||||
MouseState mouseState = Mouse.GetState();
|
||||
|
||||
return new InputState(previousState, controllerState, keyboardState, mouseState);
|
||||
}
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Get the status of a button.</summary>
|
||||
/// <param name="oldStatus">The previous button status.</param>
|
||||
/// <param name="isDown">Whether the button is currently down.</param>
|
||||
public InputStatus GetStatus(InputStatus oldStatus, bool isDown)
|
||||
{
|
||||
if (isDown && oldStatus.IsDown())
|
||||
return InputStatus.Held;
|
||||
if (isDown)
|
||||
return InputStatus.Pressed;
|
||||
return InputStatus.Released;
|
||||
}
|
||||
|
||||
/// <summary>Get the buttons pressed in the given stats.</summary>
|
||||
/// <param name="keyboard">The keyboard state.</param>
|
||||
/// <param name="mouse">The mouse state.</param>
|
||||
/// <param name="controller">The controller state.</param>
|
||||
private static IEnumerable<SButton> GetPressedButtons(KeyboardState keyboard, MouseState mouse, GamePadState controller)
|
||||
{
|
||||
// keyboard
|
||||
foreach (Keys key in keyboard.GetPressedKeys())
|
||||
yield return key.ToSButton();
|
||||
|
||||
// mouse
|
||||
if (mouse.LeftButton == ButtonState.Pressed)
|
||||
yield return SButton.MouseLeft;
|
||||
if (mouse.RightButton == ButtonState.Pressed)
|
||||
yield return SButton.MouseRight;
|
||||
if (mouse.MiddleButton == ButtonState.Pressed)
|
||||
yield return SButton.MouseMiddle;
|
||||
if (mouse.XButton1 == ButtonState.Pressed)
|
||||
yield return SButton.MouseX1;
|
||||
if (mouse.XButton2 == ButtonState.Pressed)
|
||||
yield return SButton.MouseX2;
|
||||
|
||||
// controller
|
||||
if (controller.IsConnected)
|
||||
{
|
||||
if (controller.Buttons.A == ButtonState.Pressed)
|
||||
yield return SButton.ControllerA;
|
||||
if (controller.Buttons.B == ButtonState.Pressed)
|
||||
yield return SButton.ControllerB;
|
||||
if (controller.Buttons.Back == ButtonState.Pressed)
|
||||
yield return SButton.ControllerBack;
|
||||
if (controller.Buttons.BigButton == ButtonState.Pressed)
|
||||
yield return SButton.BigButton;
|
||||
if (controller.Buttons.LeftShoulder == ButtonState.Pressed)
|
||||
yield return SButton.LeftShoulder;
|
||||
if (controller.Buttons.LeftStick == ButtonState.Pressed)
|
||||
yield return SButton.LeftStick;
|
||||
if (controller.Buttons.RightShoulder == ButtonState.Pressed)
|
||||
yield return SButton.RightShoulder;
|
||||
if (controller.Buttons.RightStick == ButtonState.Pressed)
|
||||
yield return SButton.RightStick;
|
||||
if (controller.Buttons.Start == ButtonState.Pressed)
|
||||
yield return SButton.ControllerStart;
|
||||
if (controller.Buttons.X == ButtonState.Pressed)
|
||||
yield return SButton.ControllerX;
|
||||
if (controller.Buttons.Y == ButtonState.Pressed)
|
||||
yield return SButton.ControllerY;
|
||||
if (controller.DPad.Up == ButtonState.Pressed)
|
||||
yield return SButton.DPadUp;
|
||||
if (controller.DPad.Down == ButtonState.Pressed)
|
||||
yield return SButton.DPadDown;
|
||||
if (controller.DPad.Left == ButtonState.Pressed)
|
||||
yield return SButton.DPadLeft;
|
||||
if (controller.DPad.Right == ButtonState.Pressed)
|
||||
yield return SButton.DPadRight;
|
||||
if (controller.Triggers.Left > 0.2f)
|
||||
yield return SButton.LeftTrigger;
|
||||
if (controller.Triggers.Right > 0.2f)
|
||||
yield return SButton.RightTrigger;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
namespace StardewModdingAPI.Framework.Input
|
||||
{
|
||||
/// <summary>The input status for a button during an update frame.</summary>
|
||||
internal enum InputStatus
|
||||
{
|
||||
/// <summary>The button was neither pressed, held, nor released.</summary>
|
||||
None,
|
||||
|
||||
/// <summary>The button was pressed in this frame.</summary>
|
||||
Pressed,
|
||||
|
||||
/// <summary>The button has been held since the last frame.</summary>
|
||||
Held,
|
||||
|
||||
/// <summary>The button was released in this frame.</summary>
|
||||
Released
|
||||
}
|
||||
|
||||
/// <summary>Extension methods for <see cref="InputStatus"/>.</summary>
|
||||
internal static class InputStatusExtensions
|
||||
{
|
||||
/// <summary>Whether the button was pressed or held.</summary>
|
||||
/// <param name="status">The button status.</param>
|
||||
public static bool IsDown(this InputStatus status)
|
||||
{
|
||||
return status == InputStatus.Held || status == InputStatus.Pressed;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace StardewModdingAPI.Framework
|
||||
{
|
||||
/// <summary>An implementation of <see cref="ISemanticVersion"/> that hamdles the legacy <see cref="IManifest"/> version format.</summary>
|
||||
internal class LegacyManifestVersion : SemanticVersion
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="majorVersion">The major version incremented for major API changes.</param>
|
||||
/// <param name="minorVersion">The minor version incremented for backwards-compatible changes.</param>
|
||||
/// <param name="patchVersion">The patch version for backwards-compatible bug fixes.</param>
|
||||
/// <param name="build">An optional build tag.</param>
|
||||
[JsonConstructor]
|
||||
public LegacyManifestVersion(int majorVersion, int minorVersion, int patchVersion, string build = null)
|
||||
: base(
|
||||
majorVersion,
|
||||
minorVersion,
|
||||
patchVersion,
|
||||
build != "0" ? build : null // '0' from incorrect examples in old SMAPI documentation
|
||||
)
|
||||
{ }
|
||||
}
|
||||
}
|
|
@ -162,7 +162,11 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
|
||||
// skip if already visited
|
||||
if (visitedAssemblyNames.Contains(assembly.Name.Name))
|
||||
{
|
||||
yield return new AssemblyParseResult(file, null, AssemblyLoadStatus.AlreadyLoaded);
|
||||
yield break;
|
||||
}
|
||||
|
||||
visitedAssemblyNames.Add(assembly.Name.Name);
|
||||
|
||||
// yield referenced assemblies
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using StardewModdingAPI.Framework.Serialisation;
|
||||
using StardewModdingAPI.Framework.Serialisation.SmapiConverters;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Models
|
||||
{
|
||||
|
@ -20,18 +20,18 @@ namespace StardewModdingAPI.Framework.Models
|
|||
public string Author { get; set; }
|
||||
|
||||
/// <summary>The mod version.</summary>
|
||||
[JsonConverter(typeof(SFieldConverter))]
|
||||
[JsonConverter(typeof(SemanticVersionConverter))]
|
||||
public ISemanticVersion Version { get; set; }
|
||||
|
||||
/// <summary>The minimum SMAPI version required by this mod, if any.</summary>
|
||||
[JsonConverter(typeof(SFieldConverter))]
|
||||
[JsonConverter(typeof(SemanticVersionConverter))]
|
||||
public ISemanticVersion MinimumApiVersion { get; set; }
|
||||
|
||||
/// <summary>The name of the DLL in the directory that has the <see cref="IMod.Entry"/> method.</summary>
|
||||
public string EntryDll { get; set; }
|
||||
|
||||
/// <summary>The other mods that must be loaded before this mod.</summary>
|
||||
[JsonConverter(typeof(SFieldConverter))]
|
||||
[JsonConverter(typeof(ManifestDependencyArrayConverter))]
|
||||
public IManifestDependency[] Dependencies { get; set; }
|
||||
|
||||
/// <summary>The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</summary>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using StardewModdingAPI.Framework.Serialisation;
|
||||
using StardewModdingAPI.Framework.Serialisation.SmapiConverters;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Models
|
||||
{
|
||||
|
@ -12,7 +12,7 @@ namespace StardewModdingAPI.Framework.Models
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The unique mod identifier.</summary>
|
||||
[JsonConverter(typeof(SFieldConverter))]
|
||||
[JsonConverter(typeof(ModDataIdConverter))]
|
||||
public ModDataID ID { get; set; }
|
||||
|
||||
/// <summary>A value to inject into <see cref="IManifest.UpdateKeys"/> field if it's not already set.</summary>
|
||||
|
@ -22,7 +22,7 @@ namespace StardewModdingAPI.Framework.Models
|
|||
public string AlternativeUrl { get; set; }
|
||||
|
||||
/// <summary>The compatibility of given mod versions (if any).</summary>
|
||||
[JsonConverter(typeof(SFieldConverter))]
|
||||
[JsonConverter(typeof(ModCompatibilityArrayConverter))]
|
||||
public ModCompatibility[] Compatibility { get; set; } = new ModCompatibility[0];
|
||||
|
||||
/// <summary>Map local versions to a semantic version for update checks.</summary>
|
||||
|
|
|
@ -10,6 +10,7 @@ using Microsoft.Xna.Framework;
|
|||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using StardewModdingAPI.Events;
|
||||
using StardewModdingAPI.Framework.Input;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Framework.Utilities;
|
||||
using StardewValley;
|
||||
|
@ -50,23 +51,14 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>Whether the game is saving and SMAPI has already raised <see cref="SaveEvents.BeforeSave"/>.</summary>
|
||||
private bool IsBetweenSaveEvents;
|
||||
|
||||
/// <summary>Whether the game is creating the save file and SMAPI has already raised <see cref="SaveEvents.BeforeCreate"/>.</summary>
|
||||
private bool IsBetweenCreateEvents;
|
||||
|
||||
/****
|
||||
** Game state
|
||||
****/
|
||||
/// <summary>A record of the buttons pressed as of the previous tick.</summary>
|
||||
private SButton[] PreviousPressedButtons = new SButton[0];
|
||||
|
||||
/// <summary>A record of the keyboard state (i.e. the up/down state for each button) as of the previous tick.</summary>
|
||||
private KeyboardState PreviousKeyState;
|
||||
|
||||
/// <summary>A record of the controller state (i.e. the up/down state for each button) as of the previous tick.</summary>
|
||||
private GamePadState PreviousControllerState;
|
||||
|
||||
/// <summary>A record of the mouse state (i.e. the cursor position, scroll amount, and the up/down state for each button) as of the previous tick.</summary>
|
||||
private MouseState PreviousMouseState;
|
||||
|
||||
/// <summary>The previous mouse position on the screen adjusted for the zoom level.</summary>
|
||||
private Point PreviousMousePosition;
|
||||
/// <summary>The player input as of the previous tick.</summary>
|
||||
private InputState PreviousInput = new InputState();
|
||||
|
||||
/// <summary>The window size value at last check.</summary>
|
||||
private Point PreviousWindowSize;
|
||||
|
@ -240,6 +232,13 @@ namespace StardewModdingAPI.Framework
|
|||
return;
|
||||
}
|
||||
|
||||
// game is asynchronously loading a save, block mod events to avoid conflicts
|
||||
if (Game1.gameMode == Game1.loadingMode)
|
||||
{
|
||||
base.Update(gameTime);
|
||||
return;
|
||||
}
|
||||
|
||||
/*********
|
||||
** Save events + suppress events during save
|
||||
*********/
|
||||
|
@ -250,6 +249,14 @@ namespace StardewModdingAPI.Framework
|
|||
// opened (since the save hasn't started yet), but all other events should be suppressed.
|
||||
if (Context.IsSaving)
|
||||
{
|
||||
// raise before-create
|
||||
if (!Context.IsWorldReady && !this.IsBetweenCreateEvents)
|
||||
{
|
||||
this.IsBetweenCreateEvents = true;
|
||||
this.Monitor.Log("Context: before save creation.", LogLevel.Trace);
|
||||
SaveEvents.InvokeBeforeCreate(this.Monitor);
|
||||
}
|
||||
|
||||
// raise before-save
|
||||
if (Context.IsWorldReady && !this.IsBetweenSaveEvents)
|
||||
{
|
||||
|
@ -262,6 +269,13 @@ namespace StardewModdingAPI.Framework
|
|||
base.Update(gameTime);
|
||||
return;
|
||||
}
|
||||
if (this.IsBetweenCreateEvents)
|
||||
{
|
||||
// raise after-create
|
||||
this.IsBetweenCreateEvents = false;
|
||||
this.Monitor.Log($"Context: after save creation, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
|
||||
SaveEvents.InvokeAfterCreated(this.Monitor);
|
||||
}
|
||||
if (this.IsBetweenSaveEvents)
|
||||
{
|
||||
// raise after-save
|
||||
|
@ -348,34 +362,17 @@ namespace StardewModdingAPI.Framework
|
|||
*********/
|
||||
if (Game1.game1.IsActive)
|
||||
{
|
||||
// get latest state
|
||||
KeyboardState keyState;
|
||||
GamePadState controllerState;
|
||||
MouseState mouseState;
|
||||
Point mousePosition;
|
||||
// get input state
|
||||
InputState inputState;
|
||||
try
|
||||
{
|
||||
keyState = Keyboard.GetState();
|
||||
controllerState = GamePad.GetState(PlayerIndex.One);
|
||||
mouseState = Mouse.GetState();
|
||||
mousePosition = new Point(Game1.getMouseX(), Game1.getMouseY());
|
||||
inputState = InputState.GetState(this.PreviousInput);
|
||||
}
|
||||
catch (InvalidOperationException) // GetState() may crash for some players if window doesn't have focus but game1.IsActive == true
|
||||
{
|
||||
keyState = this.PreviousKeyState;
|
||||
controllerState = this.PreviousControllerState;
|
||||
mouseState = this.PreviousMouseState;
|
||||
mousePosition = this.PreviousMousePosition;
|
||||
inputState = this.PreviousInput;
|
||||
}
|
||||
|
||||
// analyse state
|
||||
SButton[] currentlyPressedKeys = this.GetPressedButtons(keyState, mouseState, controllerState).ToArray();
|
||||
SButton[] previousPressedKeys = this.PreviousPressedButtons;
|
||||
SButton[] framePressedKeys = currentlyPressedKeys.Except(previousPressedKeys).ToArray();
|
||||
SButton[] frameReleasedKeys = previousPressedKeys.Except(currentlyPressedKeys).ToArray();
|
||||
bool isUseToolButton = Game1.options.useToolButton.Any(p => framePressedKeys.Contains(p.ToSButton()));
|
||||
bool isActionButton = !isUseToolButton && Game1.options.actionButton.Any(p => framePressedKeys.Contains(p.ToSButton()));
|
||||
|
||||
// get cursor position
|
||||
ICursorPosition cursor;
|
||||
{
|
||||
|
@ -388,10 +385,15 @@ namespace StardewModdingAPI.Framework
|
|||
cursor = new CursorPosition(screenPixels, tile, grabTile);
|
||||
}
|
||||
|
||||
// raise button pressed
|
||||
foreach (SButton button in framePressedKeys)
|
||||
// raise input events
|
||||
foreach (var pair in inputState.ActiveButtons)
|
||||
{
|
||||
InputEvents.InvokeButtonPressed(this.Monitor, button, cursor, isActionButton, isUseToolButton);
|
||||
SButton button = pair.Key;
|
||||
InputStatus status = pair.Value;
|
||||
|
||||
if (status == InputStatus.Pressed)
|
||||
{
|
||||
InputEvents.InvokeButtonPressed(this.Monitor, button, cursor, button.IsActionButton(), button.IsUseToolButton());
|
||||
|
||||
// legacy events
|
||||
if (button.TryGetKeyboard(out Keys key))
|
||||
|
@ -402,18 +404,14 @@ namespace StardewModdingAPI.Framework
|
|||
else if (button.TryGetController(out Buttons controllerButton))
|
||||
{
|
||||
if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger)
|
||||
ControlEvents.InvokeTriggerPressed(this.Monitor, controllerButton, controllerButton == Buttons.LeftTrigger ? controllerState.Triggers.Left : controllerState.Triggers.Right);
|
||||
ControlEvents.InvokeTriggerPressed(this.Monitor, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.ControllerState.Triggers.Left : inputState.ControllerState.Triggers.Right);
|
||||
else
|
||||
ControlEvents.InvokeButtonPressed(this.Monitor, controllerButton);
|
||||
}
|
||||
}
|
||||
|
||||
// raise button released
|
||||
foreach (SButton button in frameReleasedKeys)
|
||||
else if (status == InputStatus.Released)
|
||||
{
|
||||
bool wasUseToolButton = (from opt in Game1.options.useToolButton let optButton = opt.ToSButton() where optButton == button && framePressedKeys.Contains(optButton) select optButton).Any();
|
||||
bool wasActionButton = !wasUseToolButton && (from opt in Game1.options.actionButton let optButton = opt.ToSButton() where optButton == button && framePressedKeys.Contains(optButton) select optButton).Any();
|
||||
InputEvents.InvokeButtonReleased(this.Monitor, button, cursor, wasActionButton, wasUseToolButton);
|
||||
InputEvents.InvokeButtonReleased(this.Monitor, button, cursor, button.IsActionButton(), button.IsUseToolButton());
|
||||
|
||||
// legacy events
|
||||
if (button.TryGetKeyboard(out Keys key))
|
||||
|
@ -424,24 +422,21 @@ namespace StardewModdingAPI.Framework
|
|||
else if (button.TryGetController(out Buttons controllerButton))
|
||||
{
|
||||
if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger)
|
||||
ControlEvents.InvokeTriggerReleased(this.Monitor, controllerButton, controllerButton == Buttons.LeftTrigger ? controllerState.Triggers.Left : controllerState.Triggers.Right);
|
||||
ControlEvents.InvokeTriggerReleased(this.Monitor, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.ControllerState.Triggers.Left : inputState.ControllerState.Triggers.Right);
|
||||
else
|
||||
ControlEvents.InvokeButtonReleased(this.Monitor, controllerButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// raise legacy state-changed events
|
||||
if (keyState != this.PreviousKeyState)
|
||||
ControlEvents.InvokeKeyboardChanged(this.Monitor, this.PreviousKeyState, keyState);
|
||||
if (mouseState != this.PreviousMouseState)
|
||||
ControlEvents.InvokeMouseChanged(this.Monitor, this.PreviousMouseState, mouseState, this.PreviousMousePosition, mousePosition);
|
||||
if (inputState.KeyboardState != this.PreviousInput.KeyboardState)
|
||||
ControlEvents.InvokeKeyboardChanged(this.Monitor, this.PreviousInput.KeyboardState, inputState.KeyboardState);
|
||||
if (inputState.MouseState != this.PreviousInput.MouseState)
|
||||
ControlEvents.InvokeMouseChanged(this.Monitor, this.PreviousInput.MouseState, inputState.MouseState, this.PreviousInput.MousePosition, inputState.MousePosition);
|
||||
|
||||
// track state
|
||||
this.PreviousMouseState = mouseState;
|
||||
this.PreviousMousePosition = mousePosition;
|
||||
this.PreviousKeyState = keyState;
|
||||
this.PreviousControllerState = controllerState;
|
||||
this.PreviousPressedButtons = currentlyPressedKeys;
|
||||
this.PreviousInput = inputState;
|
||||
}
|
||||
|
||||
/*********
|
||||
|
@ -1304,67 +1299,7 @@ namespace StardewModdingAPI.Framework
|
|||
this.PreviousSaveID = 0;
|
||||
}
|
||||
|
||||
/// <summary>Get the buttons pressed in the given stats.</summary>
|
||||
/// <param name="keyboard">The keyboard state.</param>
|
||||
/// <param name="mouse">The mouse state.</param>
|
||||
/// <param name="controller">The controller state.</param>
|
||||
private IEnumerable<SButton> GetPressedButtons(KeyboardState keyboard, MouseState mouse, GamePadState controller)
|
||||
{
|
||||
// keyboard
|
||||
foreach (Keys key in keyboard.GetPressedKeys())
|
||||
yield return key.ToSButton();
|
||||
|
||||
// mouse
|
||||
if (mouse.LeftButton == ButtonState.Pressed)
|
||||
yield return SButton.MouseLeft;
|
||||
if (mouse.RightButton == ButtonState.Pressed)
|
||||
yield return SButton.MouseRight;
|
||||
if (mouse.MiddleButton == ButtonState.Pressed)
|
||||
yield return SButton.MouseMiddle;
|
||||
if (mouse.XButton1 == ButtonState.Pressed)
|
||||
yield return SButton.MouseX1;
|
||||
if (mouse.XButton2 == ButtonState.Pressed)
|
||||
yield return SButton.MouseX2;
|
||||
|
||||
// controller
|
||||
if (controller.IsConnected)
|
||||
{
|
||||
if (controller.Buttons.A == ButtonState.Pressed)
|
||||
yield return SButton.ControllerA;
|
||||
if (controller.Buttons.B == ButtonState.Pressed)
|
||||
yield return SButton.ControllerB;
|
||||
if (controller.Buttons.Back == ButtonState.Pressed)
|
||||
yield return SButton.ControllerBack;
|
||||
if (controller.Buttons.BigButton == ButtonState.Pressed)
|
||||
yield return SButton.BigButton;
|
||||
if (controller.Buttons.LeftShoulder == ButtonState.Pressed)
|
||||
yield return SButton.LeftShoulder;
|
||||
if (controller.Buttons.LeftStick == ButtonState.Pressed)
|
||||
yield return SButton.LeftStick;
|
||||
if (controller.Buttons.RightShoulder == ButtonState.Pressed)
|
||||
yield return SButton.RightShoulder;
|
||||
if (controller.Buttons.RightStick == ButtonState.Pressed)
|
||||
yield return SButton.RightStick;
|
||||
if (controller.Buttons.Start == ButtonState.Pressed)
|
||||
yield return SButton.ControllerStart;
|
||||
if (controller.Buttons.X == ButtonState.Pressed)
|
||||
yield return SButton.ControllerX;
|
||||
if (controller.Buttons.Y == ButtonState.Pressed)
|
||||
yield return SButton.ControllerY;
|
||||
if (controller.DPad.Up == ButtonState.Pressed)
|
||||
yield return SButton.DPadUp;
|
||||
if (controller.DPad.Down == ButtonState.Pressed)
|
||||
yield return SButton.DPadDown;
|
||||
if (controller.DPad.Left == ButtonState.Pressed)
|
||||
yield return SButton.DPadLeft;
|
||||
if (controller.DPad.Right == ButtonState.Pressed)
|
||||
yield return SButton.DPadRight;
|
||||
if (controller.Triggers.Left > 0.2f)
|
||||
yield return SButton.LeftTrigger;
|
||||
if (controller.Triggers.Right > 0.2f)
|
||||
yield return SButton.RightTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Get the player inventory changes between two states.</summary>
|
||||
/// <param name="current">The player's current inventory.</param>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StardewModdingAPI.Framework.Exceptions;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Serialisation.CrossplatformConverters
|
||||
{
|
||||
/// <summary>Handles deserialisation of <see cref="Color"/> for crossplatform compatibility.</summary>
|
||||
/// <remarks>
|
||||
/// - Linux/Mac format: { "B": 76, "G": 51, "R": 25, "A": 102 }
|
||||
/// - Windows format: "26, 51, 76, 102"
|
||||
/// </remarks>
|
||||
internal class ColorConverter : SimpleReadOnlyConverter<Color>
|
||||
{
|
||||
/*********
|
||||
** Protected methods
|
||||
*********/
|
||||
/// <summary>Read a JSON object.</summary>
|
||||
/// <param name="obj">The JSON object to read.</param>
|
||||
/// <param name="path">The path to the current JSON node.</param>
|
||||
protected override Color ReadObject(JObject obj, string path)
|
||||
{
|
||||
int r = obj.Value<int>(nameof(Color.R));
|
||||
int g = obj.Value<int>(nameof(Color.G));
|
||||
int b = obj.Value<int>(nameof(Color.B));
|
||||
int a = obj.Value<int>(nameof(Color.A));
|
||||
return new Color(r, g, b, a);
|
||||
}
|
||||
|
||||
/// <summary>Read a JSON string.</summary>
|
||||
/// <param name="str">The JSON string value.</param>
|
||||
/// <param name="path">The path to the current JSON node.</param>
|
||||
protected override Color ReadString(string str, string path)
|
||||
{
|
||||
string[] parts = str.Split(',');
|
||||
if (parts.Length != 4)
|
||||
throw new SParseException($"Can't parse {typeof(Color).Name} from invalid value '{str}' (path: {path}).");
|
||||
|
||||
int r = Convert.ToInt32(parts[0]);
|
||||
int g = Convert.ToInt32(parts[1]);
|
||||
int b = Convert.ToInt32(parts[2]);
|
||||
int a = Convert.ToInt32(parts[3]);
|
||||
return new Color(r, g, b, a);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StardewModdingAPI.Framework.Exceptions;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Serialisation.CrossplatformConverters
|
||||
{
|
||||
/// <summary>Handles deserialisation of <see cref="PointConverter"/> for crossplatform compatibility.</summary>
|
||||
/// <remarks>
|
||||
/// - Linux/Mac format: { "X": 1, "Y": 2 }
|
||||
/// - Windows format: "1, 2"
|
||||
/// </remarks>
|
||||
internal class PointConverter : SimpleReadOnlyConverter<Point>
|
||||
{
|
||||
/*********
|
||||
** Protected methods
|
||||
*********/
|
||||
/// <summary>Read a JSON object.</summary>
|
||||
/// <param name="obj">The JSON object to read.</param>
|
||||
/// <param name="path">The path to the current JSON node.</param>
|
||||
protected override Point ReadObject(JObject obj, string path)
|
||||
{
|
||||
int x = obj.Value<int>(nameof(Point.X));
|
||||
int y = obj.Value<int>(nameof(Point.Y));
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
/// <summary>Read a JSON string.</summary>
|
||||
/// <param name="str">The JSON string value.</param>
|
||||
/// <param name="path">The path to the current JSON node.</param>
|
||||
protected override Point ReadString(string str, string path)
|
||||
{
|
||||
string[] parts = str.Split(',');
|
||||
if (parts.Length != 2)
|
||||
throw new SParseException($"Can't parse {typeof(Point).Name} from invalid value '{str}' (path: {path}).");
|
||||
|
||||
int x = Convert.ToInt32(parts[0]);
|
||||
int y = Convert.ToInt32(parts[1]);
|
||||
return new Point(x, y);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StardewModdingAPI.Framework.Exceptions;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Serialisation.CrossplatformConverters
|
||||
{
|
||||
/// <summary>Handles deserialisation of <see cref="Rectangle"/> for crossplatform compatibility.</summary>
|
||||
/// <remarks>
|
||||
/// - Linux/Mac format: { "X": 1, "Y": 2, "Width": 3, "Height": 4 }
|
||||
/// - Windows format: "{X:1 Y:2 Width:3 Height:4}"
|
||||
/// </remarks>
|
||||
internal class RectangleConverter : SimpleReadOnlyConverter<Rectangle>
|
||||
{
|
||||
/*********
|
||||
** Protected methods
|
||||
*********/
|
||||
/// <summary>Read a JSON object.</summary>
|
||||
/// <param name="obj">The JSON object to read.</param>
|
||||
/// <param name="path">The path to the current JSON node.</param>
|
||||
protected override Rectangle ReadObject(JObject obj, string path)
|
||||
{
|
||||
int x = obj.Value<int>(nameof(Rectangle.X));
|
||||
int y = obj.Value<int>(nameof(Rectangle.Y));
|
||||
int width = obj.Value<int>(nameof(Rectangle.Width));
|
||||
int height = obj.Value<int>(nameof(Rectangle.Height));
|
||||
return new Rectangle(x, y, width, height);
|
||||
}
|
||||
|
||||
/// <summary>Read a JSON string.</summary>
|
||||
/// <param name="str">The JSON string value.</param>
|
||||
/// <param name="path">The path to the current JSON node.</param>
|
||||
protected override Rectangle ReadString(string str, string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return Rectangle.Empty;
|
||||
|
||||
var match = Regex.Match(str, @"^\{X:(?<x>\d+) Y:(?<y>\d+) Width:(?<width>\d+) Height:(?<height>\d+)\}$");
|
||||
if (!match.Success)
|
||||
throw new SParseException($"Can't parse {typeof(Rectangle).Name} from invalid value '{str}' (path: {path}).");
|
||||
|
||||
int x = Convert.ToInt32(match.Groups["x"].Value);
|
||||
int y = Convert.ToInt32(match.Groups["y"].Value);
|
||||
int width = Convert.ToInt32(match.Groups["width"].Value);
|
||||
int height = Convert.ToInt32(match.Groups["height"].Value);
|
||||
|
||||
return new Rectangle(x, y, width, height);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Newtonsoft.Json;
|
||||
using StardewModdingAPI.Framework.Serialisation.CrossplatformConverters;
|
||||
using StardewModdingAPI.Framework.Serialisation.SmapiConverters;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Serialisation
|
||||
{
|
||||
|
@ -19,9 +21,15 @@ namespace StardewModdingAPI.Framework.Serialisation
|
|||
ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded
|
||||
Converters = new List<JsonConverter>
|
||||
{
|
||||
// enums
|
||||
new StringEnumConverter<Buttons>(),
|
||||
new StringEnumConverter<Keys>(),
|
||||
new StringEnumConverter<SButton>()
|
||||
new StringEnumConverter<SButton>(),
|
||||
|
||||
// crossplatform compatibility
|
||||
new ColorConverter(),
|
||||
new PointConverter(),
|
||||
new RectangleConverter()
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -55,18 +63,20 @@ namespace StardewModdingAPI.Framework.Serialisation
|
|||
// deserialise model
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings);
|
||||
return this.Deserialise<TModel>(json);
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
string message = $"The file at {fullPath} doesn't seem to be valid JSON.";
|
||||
string error = $"Can't parse JSON file at {fullPath}.";
|
||||
|
||||
string text = File.ReadAllText(fullPath);
|
||||
if (text.Contains("“") || text.Contains("”"))
|
||||
message += " Found curly quotes in the text; note that only straight quotes are allowed in JSON.";
|
||||
|
||||
message += $"\nTechnical details: {ex.Message}";
|
||||
throw new JsonReaderException(message);
|
||||
if (ex is JsonReaderException)
|
||||
{
|
||||
error += " This doesn't seem to be valid JSON.";
|
||||
if (json.Contains("“") || json.Contains("”"))
|
||||
error += " Found curly quotes in the text; note that only straight quotes are allowed in JSON.";
|
||||
}
|
||||
error += $"\nTechnical details: {ex.Message}";
|
||||
throw new JsonReaderException(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,5 +103,34 @@ namespace StardewModdingAPI.Framework.Serialisation
|
|||
string json = JsonConvert.SerializeObject(model, this.JsonSettings);
|
||||
File.WriteAllText(fullPath, json);
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Deserialize JSON text if possible.</summary>
|
||||
/// <typeparam name="TModel">The model type.</typeparam>
|
||||
/// <param name="json">The raw JSON text.</param>
|
||||
private TModel Deserialise<TModel>(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings);
|
||||
}
|
||||
catch (JsonReaderException)
|
||||
{
|
||||
// try replacing curly quotes
|
||||
if (json.Contains("“") || json.Contains("”"))
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<TModel>(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings);
|
||||
}
|
||||
catch { /* rethrow original error */ }
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StardewModdingAPI.Framework.Exceptions;
|
||||
using StardewModdingAPI.Framework.Models;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Serialisation
|
||||
{
|
||||
/// <summary>Overrides how SMAPI reads and writes <see cref="ISemanticVersion"/> and <see cref="IManifestDependency"/> fields.</summary>
|
||||
internal class SFieldConverter : JsonConverter
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether this converter can write JSON.</summary>
|
||||
public override bool CanWrite => false;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get whether this instance can convert the specified object type.</summary>
|
||||
/// <param name="objectType">The object type.</param>
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return
|
||||
objectType == typeof(ISemanticVersion)
|
||||
|| objectType == typeof(IManifestDependency[])
|
||||
|| objectType == typeof(ModDataID)
|
||||
|| objectType == typeof(ModCompatibility[]);
|
||||
}
|
||||
|
||||
/// <summary>Reads the JSON representation of the object.</summary>
|
||||
/// <param name="reader">The JSON reader.</param>
|
||||
/// <param name="objectType">The object type.</param>
|
||||
/// <param name="existingValue">The object being read.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
// semantic version
|
||||
if (objectType == typeof(ISemanticVersion))
|
||||
{
|
||||
JToken token = JToken.Load(reader);
|
||||
switch (token.Type)
|
||||
{
|
||||
case JTokenType.Object:
|
||||
{
|
||||
JObject obj = (JObject)token;
|
||||
int major = obj.Value<int>(nameof(ISemanticVersion.MajorVersion));
|
||||
int minor = obj.Value<int>(nameof(ISemanticVersion.MinorVersion));
|
||||
int patch = obj.Value<int>(nameof(ISemanticVersion.PatchVersion));
|
||||
string build = obj.Value<string>(nameof(ISemanticVersion.Build));
|
||||
return new SemanticVersion(major, minor, patch, build);
|
||||
}
|
||||
|
||||
case JTokenType.String:
|
||||
{
|
||||
string str = token.Value<string>();
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return null;
|
||||
if (!SemanticVersion.TryParse(str, out ISemanticVersion version))
|
||||
throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta.");
|
||||
return version;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new SParseException($"Can't parse semantic version from {token.Type}, must be an object or string.");
|
||||
}
|
||||
}
|
||||
|
||||
// manifest dependencies
|
||||
if (objectType == typeof(IManifestDependency[]))
|
||||
{
|
||||
List<IManifestDependency> result = new List<IManifestDependency>();
|
||||
foreach (JObject obj in JArray.Load(reader).Children<JObject>())
|
||||
{
|
||||
string uniqueID = obj.Value<string>(nameof(IManifestDependency.UniqueID));
|
||||
string minVersion = obj.Value<string>(nameof(IManifestDependency.MinimumVersion));
|
||||
bool required = obj.Value<bool?>(nameof(IManifestDependency.IsRequired)) ?? true;
|
||||
result.Add(new ManifestDependency(uniqueID, minVersion, required));
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
// mod data ID
|
||||
if (objectType == typeof(ModDataID))
|
||||
{
|
||||
JToken token = JToken.Load(reader);
|
||||
return new ModDataID(token.Value<string>());
|
||||
}
|
||||
|
||||
// mod compatibility records
|
||||
if (objectType == typeof(ModCompatibility[]))
|
||||
{
|
||||
List<ModCompatibility> result = new List<ModCompatibility>();
|
||||
foreach (JProperty property in JObject.Load(reader).Properties())
|
||||
{
|
||||
string range = property.Name;
|
||||
ModStatus status = (ModStatus)Enum.Parse(typeof(ModStatus), property.Value.Value<string>(nameof(ModCompatibility.Status)));
|
||||
string reasonPhrase = property.Value.Value<string>(nameof(ModCompatibility.ReasonPhrase));
|
||||
|
||||
result.Add(new ModCompatibility(range, status, reasonPhrase));
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
// unknown
|
||||
throw new NotSupportedException($"Unknown type '{objectType?.FullName}'.");
|
||||
}
|
||||
|
||||
/// <summary>Writes the JSON representation of the object.</summary>
|
||||
/// <param name="writer">The JSON writer.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
throw new InvalidOperationException("This converter does not write JSON.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StardewModdingAPI.Framework.Exceptions;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Serialisation
|
||||
{
|
||||
/// <summary>The base implementation for simplified converters which deserialise <typeparamref name="T"/> without overriding serialisation.</summary>
|
||||
/// <typeparam name="T">The type to deserialise.</typeparam>
|
||||
internal abstract class SimpleReadOnlyConverter<T> : JsonConverter
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether this converter can write JSON.</summary>
|
||||
public override bool CanWrite => false;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get whether this instance can convert the specified object type.</summary>
|
||||
/// <param name="objectType">The object type.</param>
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(T);
|
||||
}
|
||||
|
||||
/// <summary>Writes the JSON representation of the object.</summary>
|
||||
/// <param name="writer">The JSON writer.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
throw new InvalidOperationException("This converter does not write JSON.");
|
||||
}
|
||||
|
||||
/// <summary>Reads the JSON representation of the object.</summary>
|
||||
/// <param name="reader">The JSON reader.</param>
|
||||
/// <param name="objectType">The object type.</param>
|
||||
/// <param name="existingValue">The object being read.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
string path = reader.Path;
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.StartObject:
|
||||
return this.ReadObject(JObject.Load(reader), path);
|
||||
case JsonToken.String:
|
||||
return this.ReadString(JToken.Load(reader).Value<string>(), path);
|
||||
default:
|
||||
throw new SParseException($"Can't parse {typeof(T).Name} from {reader.TokenType} node (path: {reader.Path}).");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Protected methods
|
||||
*********/
|
||||
/// <summary>Read a JSON object.</summary>
|
||||
/// <param name="obj">The JSON object to read.</param>
|
||||
/// <param name="path">The path to the current JSON node.</param>
|
||||
protected virtual T ReadObject(JObject obj, string path)
|
||||
{
|
||||
throw new SParseException($"Can't parse {typeof(T).Name} from object node (path: {path}).");
|
||||
}
|
||||
|
||||
/// <summary>Read a JSON string.</summary>
|
||||
/// <param name="str">The JSON string value.</param>
|
||||
/// <param name="path">The path to the current JSON node.</param>
|
||||
protected virtual T ReadString(string str, string path)
|
||||
{
|
||||
throw new SParseException($"Can't parse {typeof(T).Name} from string node (path: {path}).");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StardewModdingAPI.Framework.Models;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters
|
||||
{
|
||||
/// <summary>Handles deserialisation of <see cref="IManifestDependency"/> arrays.</summary>
|
||||
internal class ManifestDependencyArrayConverter : JsonConverter
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether this converter can write JSON.</summary>
|
||||
public override bool CanWrite => false;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get whether this instance can convert the specified object type.</summary>
|
||||
/// <param name="objectType">The object type.</param>
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(IManifestDependency[]);
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Protected methods
|
||||
*********/
|
||||
/// <summary>Read the JSON representation of the object.</summary>
|
||||
/// <param name="reader">The JSON reader.</param>
|
||||
/// <param name="objectType">The object type.</param>
|
||||
/// <param name="existingValue">The object being read.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
List<IManifestDependency> result = new List<IManifestDependency>();
|
||||
foreach (JObject obj in JArray.Load(reader).Children<JObject>())
|
||||
{
|
||||
string uniqueID = obj.Value<string>(nameof(IManifestDependency.UniqueID));
|
||||
string minVersion = obj.Value<string>(nameof(IManifestDependency.MinimumVersion));
|
||||
bool required = obj.Value<bool?>(nameof(IManifestDependency.IsRequired)) ?? true;
|
||||
result.Add(new ManifestDependency(uniqueID, minVersion, required));
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>Writes the JSON representation of the object.</summary>
|
||||
/// <param name="writer">The JSON writer.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
throw new InvalidOperationException("This converter does not write JSON.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StardewModdingAPI.Framework.Models;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters
|
||||
{
|
||||
/// <summary>Handles deserialisation of <see cref="ModCompatibility"/> arrays.</summary>
|
||||
internal class ModCompatibilityArrayConverter : JsonConverter
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether this converter can write JSON.</summary>
|
||||
public override bool CanWrite => false;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get whether this instance can convert the specified object type.</summary>
|
||||
/// <param name="objectType">The object type.</param>
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(ModCompatibility[]);
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Protected methods
|
||||
*********/
|
||||
/// <summary>Read the JSON representation of the object.</summary>
|
||||
/// <param name="reader">The JSON reader.</param>
|
||||
/// <param name="objectType">The object type.</param>
|
||||
/// <param name="existingValue">The object being read.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
List<ModCompatibility> result = new List<ModCompatibility>();
|
||||
foreach (JProperty property in JObject.Load(reader).Properties())
|
||||
{
|
||||
string range = property.Name;
|
||||
ModStatus status = (ModStatus)Enum.Parse(typeof(ModStatus), property.Value.Value<string>(nameof(ModCompatibility.Status)));
|
||||
string reasonPhrase = property.Value.Value<string>(nameof(ModCompatibility.ReasonPhrase));
|
||||
|
||||
result.Add(new ModCompatibility(range, status, reasonPhrase));
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>Writes the JSON representation of the object.</summary>
|
||||
/// <param name="writer">The JSON writer.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
throw new InvalidOperationException("This converter does not write JSON.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using StardewModdingAPI.Framework.Models;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters
|
||||
{
|
||||
/// <summary>Handles deserialisation of <see cref="ModDataID"/>.</summary>
|
||||
internal class ModDataIdConverter : SimpleReadOnlyConverter<ModDataID>
|
||||
{
|
||||
/*********
|
||||
** Protected methods
|
||||
*********/
|
||||
/// <summary>Read a JSON string.</summary>
|
||||
/// <param name="str">The JSON string value.</param>
|
||||
/// <param name="path">The path to the current JSON node.</param>
|
||||
protected override ModDataID ReadString(string str, string path)
|
||||
{
|
||||
return new ModDataID(str);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using StardewModdingAPI.Framework.Exceptions;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters
|
||||
{
|
||||
/// <summary>Handles deserialisation of <see cref="SemanticVersion"/>.</summary>
|
||||
internal class SemanticVersionConverter : SimpleReadOnlyConverter<ISemanticVersion>
|
||||
{
|
||||
/*********
|
||||
** Protected methods
|
||||
*********/
|
||||
/// <summary>Read a JSON object.</summary>
|
||||
/// <param name="obj">The JSON object to read.</param>
|
||||
/// <param name="path">The path to the current JSON node.</param>
|
||||
protected override ISemanticVersion ReadObject(JObject obj, string path)
|
||||
{
|
||||
int major = obj.Value<int>(nameof(ISemanticVersion.MajorVersion));
|
||||
int minor = obj.Value<int>(nameof(ISemanticVersion.MinorVersion));
|
||||
int patch = obj.Value<int>(nameof(ISemanticVersion.PatchVersion));
|
||||
string build = obj.Value<string>(nameof(ISemanticVersion.Build));
|
||||
return new LegacyManifestVersion(major, minor, patch, build);
|
||||
}
|
||||
|
||||
/// <summary>Read a JSON string.</summary>
|
||||
/// <param name="str">The JSON string value.</param>
|
||||
/// <param name="path">The path to the current JSON node.</param>
|
||||
protected override ISemanticVersion ReadString(string str, string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return null;
|
||||
if (!SemanticVersion.TryParse(str, out ISemanticVersion version))
|
||||
throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta (path: {path}).");
|
||||
return version;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Serialisation
|
||||
namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters
|
||||
{
|
||||
/// <summary>A variant of <see cref="StringEnumConverter"/> which only converts a specified enum.</summary>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using StardewValley;
|
||||
|
||||
|
@ -683,5 +684,19 @@ namespace StardewModdingAPI
|
|||
button = default(InputButton);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Get whether the given button is equivalent to <see cref="Options.useToolButton"/>.</summary>
|
||||
/// <param name="input">The button.</param>
|
||||
public static bool IsUseToolButton(this SButton input)
|
||||
{
|
||||
return input == SButton.ControllerX || Game1.options.useToolButton.Any(p => p.ToSButton() == input);
|
||||
}
|
||||
|
||||
/// <summary>Get whether the given button is equivalent to <see cref="Options.actionButton"/>.</summary>
|
||||
/// <param name="input">The button.</param>
|
||||
public static bool IsActionButton(this SButton input)
|
||||
{
|
||||
return input == SButton.ControllerA || Game1.options.actionButton.Any(p => p.ToSButton() == input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,8 +65,8 @@
|
|||
<HintPath>..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.11.0.1-beta3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
|
@ -86,6 +86,9 @@
|
|||
<Link>Properties\GlobalAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Framework\Content\ContentCache.cs" />
|
||||
<Compile Include="Framework\Input\InputState.cs" />
|
||||
<Compile Include="Framework\Input\InputStatus.cs" />
|
||||
<Compile Include="Framework\LegacyManifestVersion.cs" />
|
||||
<Compile Include="Framework\Models\ModCompatibility.cs" />
|
||||
<Compile Include="Framework\ModLoading\Finders\EventFinder.cs" />
|
||||
<Compile Include="Framework\ModLoading\Finders\FieldFinder.cs" />
|
||||
|
@ -107,6 +110,14 @@
|
|||
<Compile Include="Framework\Exceptions\SAssemblyLoadFailedException.cs" />
|
||||
<Compile Include="Framework\ModLoading\AssemblyLoadStatus.cs" />
|
||||
<Compile Include="Framework\Reflection\InterfaceProxyBuilder.cs" />
|
||||
<Compile Include="Framework\Serialisation\SmapiConverters\ModCompatibilityArrayConverter.cs" />
|
||||
<Compile Include="Framework\Serialisation\SmapiConverters\ManifestDependencyArrayConverter.cs" />
|
||||
<Compile Include="Framework\Serialisation\SmapiConverters\ModDataIdConverter.cs" />
|
||||
<Compile Include="Framework\Serialisation\SmapiConverters\SemanticVersionConverter.cs" />
|
||||
<Compile Include="Framework\Serialisation\SimpleReadOnlyConverter.cs" />
|
||||
<Compile Include="Framework\Serialisation\CrossplatformConverters\RectangleConverter.cs" />
|
||||
<Compile Include="Framework\Serialisation\CrossplatformConverters\ColorConverter.cs" />
|
||||
<Compile Include="Framework\Serialisation\CrossplatformConverters\PointConverter.cs" />
|
||||
<Compile Include="Framework\Utilities\ContextHash.cs" />
|
||||
<Compile Include="IReflectedField.cs" />
|
||||
<Compile Include="IReflectedMethod.cs" />
|
||||
|
@ -174,8 +185,7 @@
|
|||
<Compile Include="Framework\SContentManager.cs" />
|
||||
<Compile Include="Framework\Exceptions\SParseException.cs" />
|
||||
<Compile Include="Framework\Serialisation\JsonHelper.cs" />
|
||||
<Compile Include="Framework\Serialisation\StringEnumConverter.cs" />
|
||||
<Compile Include="Framework\Serialisation\SFieldConverter.cs" />
|
||||
<Compile Include="Framework\Serialisation\SmapiConverters\StringEnumConverter.cs" />
|
||||
<Compile Include="IAssetEditor.cs" />
|
||||
<Compile Include="IAssetInfo.cs" />
|
||||
<Compile Include="IAssetLoader.cs" />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Mono.Cecil" version="0.9.6.4" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="11.0.1-beta3" targetFramework="net45" />
|
||||
</packages>
|
|
@ -63,7 +63,14 @@ else
|
|||
|
||||
# open SMAPI in terminal
|
||||
if $COMMAND x-terminal-emulator 2>/dev/null; then
|
||||
# Terminator converts -e to -x when used through x-terminal-emulator for some reason (per
|
||||
# `man terminator`), which causes an "unable to find shell" error. If x-terminal-emulator
|
||||
# is mapped to Terminator, invoke it directly instead.
|
||||
if [[ "$(readlink -e $(which x-terminal-emulator))" == *"/terminator" ]]; then
|
||||
terminator -e "$LAUNCHER"
|
||||
else
|
||||
x-terminal-emulator -e "$LAUNCHER"
|
||||
fi
|
||||
elif $COMMAND xfce4-terminal 2>/dev/null; then
|
||||
xfce4-terminal -e "$LAUNCHER"
|
||||
elif $COMMAND gnome-terminal 2>/dev/null; then
|
||||
|
|
Loading…
Reference in New Issue