using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; namespace StardewModdingAPI.Framework.Input { /// Manages controller state. internal class GamePadStateBuilder : IInputStateBuilder { /********* ** Fields *********/ /// The maximum direction to ignore for the left thumbstick. private const float LeftThumbstickDeadZone = 0.2f; /// The maximum direction to ignore for the right thumbstick. private const float RightThumbstickDeadZone = 0.9f; /// The underlying controller state. private GamePadState? State; /// The current button states. private readonly IDictionary? ButtonStates; /// The left trigger value. private float LeftTrigger; /// The right trigger value. private float RightTrigger; /// The left thumbstick position. private Vector2 LeftStickPos; /// The left thumbstick position. private Vector2 RightStickPos; /********* ** Accessors *********/ /// Whether the gamepad is currently connected. [MemberNotNullWhen(true, nameof(GamePadStateBuilder.ButtonStates))] public bool IsConnected { get; } /********* ** Public methods *********/ /// Construct an instance. /// The initial state. public GamePadStateBuilder(GamePadState state) { this.State = state; this.IsConnected = state.IsConnected; if (!this.IsConnected) return; GamePadDPad pad = state.DPad; GamePadButtons buttons = state.Buttons; GamePadTriggers triggers = state.Triggers; GamePadThumbSticks sticks = state.ThumbSticks; this.ButtonStates = new Dictionary { [SButton.DPadUp] = pad.Up, [SButton.DPadDown] = pad.Down, [SButton.DPadLeft] = pad.Left, [SButton.DPadRight] = pad.Right, [SButton.ControllerA] = buttons.A, [SButton.ControllerB] = buttons.B, [SButton.ControllerX] = buttons.X, [SButton.ControllerY] = buttons.Y, [SButton.LeftStick] = buttons.LeftStick, [SButton.RightStick] = buttons.RightStick, [SButton.LeftShoulder] = buttons.LeftShoulder, [SButton.RightShoulder] = buttons.RightShoulder, [SButton.ControllerBack] = buttons.Back, [SButton.ControllerStart] = buttons.Start, [SButton.BigButton] = buttons.BigButton }; this.LeftTrigger = triggers.Left; this.RightTrigger = triggers.Right; this.LeftStickPos = sticks.Left; this.RightStickPos = sticks.Right; } /// public GamePadStateBuilder OverrideButtons(IDictionary overrides) { if (!this.IsConnected) return this; foreach (var pair in overrides) { bool changed = true; bool isDown = pair.Value.IsDown(); switch (pair.Key) { // left thumbstick case SButton.LeftThumbstickUp: this.LeftStickPos.Y = isDown ? 1 : 0; break; case SButton.LeftThumbstickDown: this.LeftStickPos.Y = isDown ? -1 : 0; break; case SButton.LeftThumbstickLeft: this.LeftStickPos.X = isDown ? -1 : 0; break; case SButton.LeftThumbstickRight: this.LeftStickPos.X = isDown ? 1 : 0; break; // right thumbstick case SButton.RightThumbstickUp: this.RightStickPos.Y = isDown ? 1 : 0; break; case SButton.RightThumbstickDown: this.RightStickPos.Y = isDown ? -1 : 0; break; case SButton.RightThumbstickLeft: this.RightStickPos.X = isDown ? -1 : 0; break; case SButton.RightThumbstickRight: this.RightStickPos.X = isDown ? 1 : 0; break; // triggers case SButton.LeftTrigger: this.LeftTrigger = isDown ? 1 : 0; break; case SButton.RightTrigger: this.RightTrigger = isDown ? 1 : 0; break; // buttons default: if (this.ButtonStates.ContainsKey(pair.Key)) this.ButtonStates[pair.Key] = isDown ? ButtonState.Pressed : ButtonState.Released; else changed = false; break; } if (changed) this.State = null; } return this; } /// public IEnumerable GetPressedButtons() { if (!this.IsConnected) yield break; // buttons foreach (Buttons button in this.GetPressedGamePadButtons()) yield return button.ToSButton(); // triggers if (this.LeftTrigger > 0.2f) yield return SButton.LeftTrigger; if (this.RightTrigger > 0.2f) yield return SButton.RightTrigger; // left thumbstick direction if (this.LeftStickPos.Y > GamePadStateBuilder.LeftThumbstickDeadZone) yield return SButton.LeftThumbstickUp; if (this.LeftStickPos.Y < -GamePadStateBuilder.LeftThumbstickDeadZone) yield return SButton.LeftThumbstickDown; if (this.LeftStickPos.X > GamePadStateBuilder.LeftThumbstickDeadZone) yield return SButton.LeftThumbstickRight; if (this.LeftStickPos.X < -GamePadStateBuilder.LeftThumbstickDeadZone) yield return SButton.LeftThumbstickLeft; // right thumbstick direction if (this.RightStickPos.Length() > GamePadStateBuilder.RightThumbstickDeadZone) { if (this.RightStickPos.Y > 0) yield return SButton.RightThumbstickUp; if (this.RightStickPos.Y < 0) yield return SButton.RightThumbstickDown; if (this.RightStickPos.X > 0) yield return SButton.RightThumbstickRight; if (this.RightStickPos.X < 0) yield return SButton.RightThumbstickLeft; } } /// public GamePadState GetState() { this.State ??= new GamePadState( leftThumbStick: this.LeftStickPos, rightThumbStick: this.RightStickPos, leftTrigger: this.LeftTrigger, rightTrigger: this.RightTrigger, buttons: this.GetPressedGamePadButtons().ToArray() ); return this.State.Value; } /********* ** Private methods *********/ /// Get the pressed gamepad buttons. private IEnumerable GetPressedGamePadButtons() { if (!this.IsConnected) yield break; foreach (var pair in this.ButtonStates) { if (pair.Value == ButtonState.Pressed && pair.Key.TryGetController(out Buttons button)) yield return button; } } } }