using System.Collections.Generic; 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 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. public bool IsConnected { get; private set; } /********* ** Public methods *********/ /// Construct an instance. /// The initial state, or null to get the latest state. public GamePadStateBuilder(GamePadState? state = null) { this.Reset(state); } /// Reset the tracked state. /// The state from which to reset, or null to get the latest state. public GamePadStateBuilder Reset(GamePadState? state = null) { this.State = state ??= GamePad.GetState(PlayerIndex.One); this.IsConnected = state.Value.IsConnected; if (!this.IsConnected) return this; GamePadDPad pad = state.Value.DPad; GamePadButtons buttons = state.Value.Buttons; GamePadTriggers triggers = state.Value.Triggers; GamePadThumbSticks sticks = state.Value.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; return this; } /// Override the states for a set of buttons. /// The button state overrides. 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; } /// Get the currently pressed buttons. public IEnumerable GetPressedButtons() { if (!this.IsConnected) yield break; // buttons foreach (var pair in this.ButtonStates) { if (pair.Value == ButtonState.Pressed && pair.Key.TryGetController(out Buttons button)) 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; } } /// Get the equivalent state. public GamePadState GetState() { this.State ??= new GamePadState( leftThumbStick: this.LeftStickPos, rightThumbStick: this.RightStickPos, leftTrigger: this.LeftTrigger, rightTrigger: this.RightTrigger, buttons: this.GetButtonBitmask() // MonoGame requires one bitmask here; don't specify multiple values ); return this.State.Value; } /********* ** Private methods *********/ /// Get a bitmask representing the pressed buttons. private Buttons GetButtonBitmask() { Buttons flag = 0; foreach (var pair in this.ButtonStates) { if (pair.Value == ButtonState.Pressed && pair.Key.TryGetController(out Buttons button)) flag |= button; } return flag; } } }