#pragma once #include "common.h" #include class Fighter; struct Game; struct SDL_Gamepad; struct SDL_Joystick; constexpr int KEY_COUNT = 512; constexpr int TYPED_LENGTH = 64; enum Control { CONTROL_LEFT, CONTROL_RIGHT, CONTROL_UP, CONTROL_DOWN, CONTROL_JUMP, CONTROL_ATTACK, CONTROL_CHARGE, CONTROL_LEVEL_DOWN, CONTROL_LEVEL_UP, CONTROL_READY, CONTROL_NEXT_WEAPON, CONTROL_PREVIOUS_WEAPON, CONTROL_MENU, CONTROL_CHAT, CONTROL_COUNT }; // What each control is called in the Controls settings, and its key in settings.txt. constexpr const char* CONTROL_NAMES[CONTROL_COUNT] = { "Move Left", "Move Right", "Up", "Down", "Jump", "Attack", "Charge", "Level Down", "Level Up", "Ready", "Next Weapon", "Previous Weapon", "Menu", "Chat"}; constexpr const char* CONTROL_KEYS[CONTROL_COUNT] = { "left", "right", "up", "down", "jump", "attack", "charge", "level_down", "level_up", "ready", "next_weapon", "previous_weapon", "menu", "chat"}; // One tick of a fighter's intent, from the keyboard, a gamepad or a bot. Buttons holds a bit per Control held this // tick, cursor is the world point it aims at, slot is a weapon slot to switch to (-1 for none), and edit a change to // its loadout or settings made from the menu this tick (see edit_encode, -1 for none). struct Controls { Vec2 cursor; Uint16 buttons = 0; int edit = -1; int slot = -1; }; constexpr int EDIT_QUEUE = 16; enum MouseButton { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE, MOUSE_COUNT }; // Which half of a binding: the keyboard side or the gamepad side. enum BindingSide { SIDE_KEY, SIDE_PAD }; // A control's two bindings: one on the keyboard side (a key's scancode, or past the keys a mouse button or wheel // direction) and one on the gamepad (a button, or past the buttons a stick or trigger direction). -1 is unbound. struct Binding { int key = -1; int pad = -1; int& side_get(BindingSide side) { return side == SIDE_PAD ? pad : key; } }; constexpr int PAD_MAX = 4; constexpr int DEVICE_NONE = -1; constexpr int DEVICE_KEYBOARD = 0; constexpr int DEVICE_COUNT = PAD_MAX + 1; constexpr int PAD_AXES = 6; // Menu navigation directions, as bits. enum Nav { NAV_UP = 1, NAV_DOWN = 2, NAV_LEFT = 4, NAV_RIGHT = 8 }; // One step of the test gamepad script: at time seconds, pad's button (or axis) index goes to value. struct PadEvent { float time = 0; float value = 0; int index = 0; int pad = 0; bool isAxis = false; }; // One way of playing: the keyboard and mouse, or a gamepad. Previous is what its controls were last tick (for // presses), stick the last way its right stick leaned, pick a Choice pick and ready a Ready click waiting for the next // tick, presses the gamepad buttons pressed since the last poll (for menus) and taps those pressed since the last // tick (held for it, however quick). Nav is the menu directions pressed since the last poll (D-pad, left stick or // arrow keys, repeating while held), held the D-pad and left stick directions held, and repeat when they next repeat. // Down and axes are the gamepad's buttons held and sticks and triggers (-1 to 1) as of the last poll, which bindings // read so a replayed tick sees the gamepad as it was. struct Device { SDL_Gamepad* pad = nullptr; Vec2 stick = {1, 0}; Uint64 repeat = 0; float axes[PAD_AXES] = {}; Uint32 down = 0; Uint32 presses = 0; Uint32 taps = 0; int held = 0; int nav = 0; // Edits waiting to go out, one per tick, oldest first. int edits[EDIT_QUEUE] = {}; int editCount = 0; bool previous[CONTROL_COUNT] = {}; bool isAiming = false; bool isClaimed = false; bool isReady = false; }; // Per-frame keyboard, mouse and gamepad state. Edges persist until a tick consumes them with tick_end. Menus read it // directly, and gameplay only sees it turned into Controls through the bindings, per device: the keyboard and mouse // (which also take any gamepad no one has claimed), or a claimed gamepad. Mouse is in canvas pixels. Widgets drawn // every frame use the per-frame parts instead: whether the left button was clicked since the last poll, and while a // text field has the keyboard, the text typed, backspaces and whether Enter or Escape ended it, and scroll, the wheel // turned since the last poll (wheel is the same for ticks, kept until one ends), whether the mouse moved, and whether // Enter was pressed outside typing (confirming in menus). Play request is the menu's Play button, waiting for the next // tick to start the game. Clicks, keys and buttons come from // press events too, so one let go quicker than a frame or tick still counts. Typing hides every key from the game. F // toggles fullscreen whenever nothing is being typed. A gamepad's right stick aims while it's in use (for the // keyboard's device, until the mouse moves). Capture listens for the next input on one side of a control's binding // (Escape cancels), which then doesn't count as pressed. RPG_TEST_PADS names a script of virtual gamepad presses for // testing ("seconds pad button|axis index value" a line). class Input { public: Binding bindings[CONTROL_COUNT]; Device devices[DEVICE_COUNT]; // How far a right stick must lean before it aims (see Settings::stickDeadzone). float aimDeadzone = 0.3f; Vec2 mouse; char typed[TYPED_LENGTH] = {}; float scroll = 0; float wheel = 0; int captureControl = -1; int erases = 0; int slotPressed = -1; // A loadout picked with Shift and a number key, kept until the settings take it (they update once a frame). int loadoutPressed = -1; bool isBindingChanged = false; BindingSide captureSide = SIDE_KEY; bool isClicked = false; bool isConfirmed = false; bool isMouseMoved = false; bool isPlayRequested = false; bool isRightClicked = false; bool isQuit = false; bool isSubmitted = false; // Whether the text being typed was given up on (Escape) rather than entered. bool isCancelled = false; Input(); void poll(Game& game); void tick_end(); void bindings_reset(); Controls controls_get(int device, Vec2 camera, Fighter& fighter, bool isMenuOpen); void edit_send(int device, int edit); Vec2 aim_get(int device, Vec2 camera, Vec2 origin); bool is_down(int scancode); bool is_pressed(int scancode); bool is_control_pressed(Control control); bool is_key_control_pressed(Control control); bool is_pad_pressed(int device, int button); Uint32 pad_presses_get(int device); bool is_pad_tapped(int button); int nav_get(int device); bool is_mouse_down(MouseButton button); bool is_mouse_pressed(MouseButton button); bool is_mouse_released(MouseButton button); void binding_name_get(int binding, BindingSide side, char* text, int size); void control_name_get(Control control, char* text, int size); void rumble_play(int device, float strength, Uint32 milliseconds); bool is_control_down(int device, Control control); private: std::vector script; std::vector virtualPads; bool buttons[MOUSE_COUNT] = {}; bool buttonsPrevious[MOUSE_COUNT] = {}; bool keys[KEY_COUNT] = {}; bool keysPrevious[KEY_COUNT] = {}; bool keysTapped[KEY_COUNT] = {}; bool presses[MOUSE_COUNT] = {}; bool pressesUnticked[MOUSE_COUNT] = {}; bool isSwallowed = false; bool wasTyping = false; bool is_key_binding_down(int binding); bool is_pad_binding_down(Device& device, int binding); void capture_take(int binding); void script_load(); void script_play(Game& game); };