diff options
Diffstat (limited to 'src/SMAPI/Framework')
18 files changed, 744 insertions, 277 deletions
diff --git a/src/SMAPI/Framework/Input/InputState.cs b/src/SMAPI/Framework/Input/InputState.cs new file mode 100644 index 00000000..8b0108ae --- /dev/null +++ b/src/SMAPI/Framework/Input/InputState.cs @@ -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; + } + } + } +} diff --git a/src/SMAPI/Framework/Input/InputStatus.cs b/src/SMAPI/Framework/Input/InputStatus.cs new file mode 100644 index 00000000..99b0006c --- /dev/null +++ b/src/SMAPI/Framework/Input/InputStatus.cs @@ -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; + } + } +} diff --git a/src/SMAPI/Framework/LegacyManifestVersion.cs b/src/SMAPI/Framework/LegacyManifestVersion.cs new file mode 100644 index 00000000..454b9137 --- /dev/null +++ b/src/SMAPI/Framework/LegacyManifestVersion.cs @@ -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 + ) + { } + } +} diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 1e3c4a05..3a7b214a 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -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 diff --git a/src/SMAPI/Framework/Models/Manifest.cs b/src/SMAPI/Framework/Models/Manifest.cs index b85787e5..f9762406 100644 --- a/src/SMAPI/Framework/Models/Manifest.cs +++ b/src/SMAPI/Framework/Models/Manifest.cs @@ -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> diff --git a/src/SMAPI/Framework/Models/ModDataRecord.cs b/src/SMAPI/Framework/Models/ModDataRecord.cs index c6a12188..580acb70 100644 --- a/src/SMAPI/Framework/Models/ModDataRecord.cs +++ b/src/SMAPI/Framework/Models/ModDataRecord.cs @@ -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> diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 0a614f17..e82ee778 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -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,60 +385,58 @@ 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; - // legacy events - if (button.TryGetKeyboard(out Keys key)) + if (status == InputStatus.Pressed) { - if (key != Keys.None) - ControlEvents.InvokeKeyPressed(this.Monitor, key); - } - 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); - else - ControlEvents.InvokeButtonPressed(this.Monitor, controllerButton); - } - } - - // raise button released - foreach (SButton button in frameReleasedKeys) - { - 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.InvokeButtonPressed(this.Monitor, button, cursor, button.IsActionButton(), button.IsUseToolButton()); - // legacy events - if (button.TryGetKeyboard(out Keys key)) - { - if (key != Keys.None) - ControlEvents.InvokeKeyReleased(this.Monitor, key); + // legacy events + if (button.TryGetKeyboard(out Keys key)) + { + if (key != Keys.None) + ControlEvents.InvokeKeyPressed(this.Monitor, key); + } + else if (button.TryGetController(out Buttons controllerButton)) + { + if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger) + ControlEvents.InvokeTriggerPressed(this.Monitor, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.ControllerState.Triggers.Left : inputState.ControllerState.Triggers.Right); + else + ControlEvents.InvokeButtonPressed(this.Monitor, controllerButton); + } } - else if (button.TryGetController(out Buttons controllerButton)) + else if (status == InputStatus.Released) { - if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger) - ControlEvents.InvokeTriggerReleased(this.Monitor, controllerButton, controllerButton == Buttons.LeftTrigger ? controllerState.Triggers.Left : controllerState.Triggers.Right); - else - ControlEvents.InvokeButtonReleased(this.Monitor, controllerButton); + InputEvents.InvokeButtonReleased(this.Monitor, button, cursor, button.IsActionButton(), button.IsUseToolButton()); + + // legacy events + if (button.TryGetKeyboard(out Keys key)) + { + if (key != Keys.None) + ControlEvents.InvokeKeyReleased(this.Monitor, key); + } + else if (button.TryGetController(out Buttons controllerButton)) + { + if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger) + 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> diff --git a/src/SMAPI/Framework/Serialisation/CrossplatformConverters/ColorConverter.cs b/src/SMAPI/Framework/Serialisation/CrossplatformConverters/ColorConverter.cs new file mode 100644 index 00000000..f4a2a26e --- /dev/null +++ b/src/SMAPI/Framework/Serialisation/CrossplatformConverters/ColorConverter.cs @@ -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); + } + } +} diff --git a/src/SMAPI/Framework/Serialisation/CrossplatformConverters/PointConverter.cs b/src/SMAPI/Framework/Serialisation/CrossplatformConverters/PointConverter.cs new file mode 100644 index 00000000..84c70989 --- /dev/null +++ b/src/SMAPI/Framework/Serialisation/CrossplatformConverters/PointConverter.cs @@ -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 |
