Files
smack-a-brother/src/input.h
T
shweet b96defa1e6 Profile stats, first-run hints, tiered balance, a wave-driven whip, and more options
- Profile Stats: damage dealt, kills and wins (single player and multiplayer) and the most and least played weapons
  and trinkets, counted from running totals in the simulation so online rewinds correct them. Protocol 6.
- First-run control hints in the sandbox (and Chat in lobbies), each gone for good once used.
- The ready countdown and a timed round's last five seconds beep (new countdown sound), and those seconds show big.
  When time runs out, everyone short of the most health dies.
- Weapon balance tuned into tiers: Whip and Grenade Launcher on top, then melee, ranged, and utility (Vacuum,
  Grapple, Uzi) at the bottom.
- The whip's lash is a chain of links a wave rolls down: windup, loop, crack, then a floppy recoil.
- Muzzles come from Muzzle nulls in weapons.anm2 (read in at build time), for shots and the flash, which now follows
  the weapon.
- Settings: Quit, Scale (1x-4x or Free), FPS meter and limit beside Vsync, Mute on Unfocus, Damage Numbers, Rumble,
  Stick Deadzone, and an Online section (Host, Chat, Ping) shared with the Multiplayer page.
- The menu header lays out tabs and buttons by their names. About is sandbox-only. The score no longer shifts with
  the menu's tab.
- Smaller meters, 90 tips (all ending in !), and a shorter lobby welcome.
2026-11-10 18:32:31 -05:00

218 lines
7.6 KiB
C++

#pragma once
#include "common.h"
#include <vector>
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<PadEvent> script;
std::vector<SDL_Joystick*> 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);
};