mirror of
https://github.com/Awakenchan/LanternEngine.git
synced 2026-11-13 02:31:24 -10:00
641 lines
24 KiB
C++
641 lines
24 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#include "bindings.h"
|
|
#include "input.h"
|
|
#include "menu_pointer.h"
|
|
#include "pad.h"
|
|
#include <SDL3/SDL.h>
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
|
|
namespace {
|
|
using namespace PadBits;
|
|
|
|
constexpr BindingInfo kActions[kBindCount] = {
|
|
{"move_forward", "Move Forward", "Left stick up.", 0, "W", 0},
|
|
{"move_back", "Move Back", "Left stick down.", 0, "S", 0},
|
|
{"move_left", "Move Left", "Left stick left.", 0, "A", 0},
|
|
{"move_right", "Move Right", "Left stick right.", 0, "D", 0},
|
|
{"walk", "Walk", "Hold with the movement keys to walk.", 0, "Left Alt", 0},
|
|
{"roll", "Dodge / Sprint", "Circle. Tap to dodge, hold to sprint.", kCircle, "Space", 0},
|
|
{"lock_on", "Lock On", "R3. Lock on to a target, or reset the camera.", kR3, "Q", 0},
|
|
|
|
{"attack", "Attack", "R1. With the strong attack modifier held, R2.", kR1, nullptr, 1},
|
|
{"strong_attack", "Strong Attack", "R2. Hold to charge.", kR2, "V", 0},
|
|
{"strong_modifier", "Strong Attack Modifier", "Held with Attack, attacks strong (R2).", 0, "Left Shift", 0},
|
|
{"transform", "Transform", "L1. Transform the trick weapon.", kL1, "C", 2},
|
|
{"firearm", "Firearm / Parry", "L2. Shoot the left-hand weapon.", kL2, "X", 3},
|
|
{"blood_vial", "Blood Vial", "Triangle. Restore HP with a Blood Vial.", kTriangle, "R", 0},
|
|
{"use_item", "Use Item", "Square. Use the selected quick item.", kSquare, "F", 0},
|
|
|
|
{"interact", "Interact", "Cross. Talk, examine, pick up, open.", kCross, "E", 0},
|
|
{"switch_item", "Switch Item", "D-pad down. Cycle the quick items.", kDown, "Down", 0},
|
|
{"switch_right", "Switch Right Weapon", "D-pad right.", kRight, "Right", 0},
|
|
{"switch_left", "Switch Left Weapon", "D-pad left.", kLeft, "Left", 0},
|
|
{"dpad_up", "D-pad Up", "D-pad up.", kUp, "Up", 0},
|
|
{"gestures", "Gestures", "The touchpad's left side.", kTouchPad, "G", 0},
|
|
{"personal_effects", "Personal Effects", "The touchpad's right side.", kTouchPad, "T", 0},
|
|
|
|
{"look_up", "Look Up", "Right stick up, for the camera without the mouse.", 0, "I", 0},
|
|
{"look_down", "Look Down", "Right stick down.", 0, "K", 0},
|
|
{"look_left", "Look Left", "Right stick left.", 0, "J", 0},
|
|
{"look_right", "Look Right", "Right stick right.", 0, "L", 0},
|
|
{"menu", "Menu", "Options. Open the menu.", kOptions, "Escape", 0},
|
|
{"confirm", "Menu Confirm", "OK, in menus.", 0, "Return", 0},
|
|
{"back", "Menu Back", "Return, in menus.", 0, "Backspace", 0},
|
|
|
|
{"l3", "Jump / Left Stick Press", "L3. Jumps when Jump on its own key is on.", kL3, "Left Ctrl", 0},
|
|
{"dodge", "Dodge", "A tap of Circle, also while the sprint key is held.", 0, nullptr, 0},
|
|
{"backstep", "Backstep", "Steps back at once, also while moving or sprinting.", 0, nullptr, 0},
|
|
{"sprint", "Sprint", "Sprints at once while held, and never dodges.", 0, nullptr, 0},
|
|
{"debug_menu", "Debug Menu", "Opens and closes the developers' debug menu (the Debug Menu plugin).", 0, "`", 0},
|
|
};
|
|
|
|
struct OldName {
|
|
const char* name;
|
|
int action;
|
|
};
|
|
constexpr OldName kOldNames[] = {
|
|
{"dpad_down", kBindSwitchItem}, {"dpad_left", kBindSwitchLeft}, {"dpad_right", kBindSwitchRight}};
|
|
|
|
enum Modifier : std::uint8_t { kCtrl = 1, kShift = 2, kAlt = 4 };
|
|
|
|
constexpr int kMouseInputs = 7;
|
|
|
|
struct Input {
|
|
std::uint16_t code = 0;
|
|
bool mouse = false;
|
|
std::uint8_t modifiers = 0;
|
|
bool None() const { return code == 0; }
|
|
bool operator==(const Input&) const = default;
|
|
};
|
|
|
|
constexpr int kKeySlot = 0, kMouseSlot = 1, kFirstExtra = 2, kSlots = 5;
|
|
struct Binding {
|
|
Input slot[kSlots];
|
|
};
|
|
|
|
std::mutex g_lock;
|
|
Binding g_bindings[kBindCount];
|
|
std::atomic<std::uint64_t> g_serial{1};
|
|
std::atomic<bool> g_dodge_down{false}, g_backstep_down{false}, g_sprint_down{false};
|
|
|
|
void Changed() { g_serial.fetch_add(1, std::memory_order_relaxed); }
|
|
|
|
const char* KeyName(int scancode) {
|
|
const char* name = SDL_GetScancodeName(static_cast<SDL_Scancode>(scancode));
|
|
return name && *name ? name : nullptr;
|
|
}
|
|
|
|
std::string ModifierText(std::uint8_t modifiers) {
|
|
std::string text;
|
|
if (modifiers & kCtrl) text += "Ctrl+";
|
|
if (modifiers & kShift) text += "Shift+";
|
|
if (modifiers & kAlt) text += "Alt+";
|
|
return text;
|
|
}
|
|
|
|
std::string IniText(const Input& in) {
|
|
if (in.mouse) {
|
|
const std::string name = in.code == 6 ? "WheelUp" : in.code == 7 ? "WheelDown" : "Mouse" + std::to_string(in.code);
|
|
return ModifierText(in.modifiers) + name;
|
|
}
|
|
const char* name = KeyName(in.code);
|
|
return name ? ModifierText(in.modifiers) + name : std::string();
|
|
}
|
|
|
|
std::string ScreenText(const Input& in) {
|
|
static constexpr const char* kMouseNames[kMouseInputs + 1] = {
|
|
nullptr, "Left Button", "Middle Button", "Right Button", "Button 4", "Button 5", "Wheel Up", "Wheel Down"};
|
|
const char* name = nullptr;
|
|
if (in.mouse) {
|
|
name = in.code <= kMouseInputs ? kMouseNames[in.code] : nullptr;
|
|
} else {
|
|
name = KeyName(in.code);
|
|
if (name && std::strcmp(name, "`") == 0) name = "` (backtick)";
|
|
}
|
|
return name ? ModifierText(in.modifiers) + name : std::string();
|
|
}
|
|
|
|
std::string Trimmed(const std::string& text) {
|
|
const auto keep = [](char c) { return c != ' ' && c != '\t' && c != '"'; };
|
|
std::size_t first = 0, last = text.size();
|
|
while (first < last && !keep(text[first])) ++first;
|
|
while (last > first && !keep(text[last - 1])) --last;
|
|
return text.substr(first, last - first);
|
|
}
|
|
|
|
Input ParseInput(std::string text) {
|
|
Input in;
|
|
static constexpr struct {
|
|
const char* prefix;
|
|
std::uint8_t modifier;
|
|
} kPrefixes[] = {{"Ctrl+", kCtrl}, {"Control+", kCtrl}, {"Shift+", kShift}, {"Alt+", kAlt}};
|
|
for (bool found = true; found;) {
|
|
found = false;
|
|
for (const auto& p : kPrefixes) {
|
|
const std::size_t n = std::strlen(p.prefix);
|
|
if (text.size() > n && SDL_strncasecmp(text.c_str(), p.prefix, n) == 0) {
|
|
in.modifiers |= p.modifier;
|
|
text = Trimmed(text.substr(n));
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
if (text.size() == 6 && text.compare(0, 5, "Mouse") == 0 && text[5] >= '1' && text[5] <= '5') {
|
|
in.mouse = true;
|
|
in.code = std::uint16_t(text[5] - '0');
|
|
} else if (text == "WheelUp" || text == "WheelDown") {
|
|
in.mouse = true;
|
|
in.code = text == "WheelUp" ? 6 : 7;
|
|
} else {
|
|
const SDL_Scancode sc = SDL_GetScancodeFromName(text.c_str());
|
|
in.code = sc > SDL_SCANCODE_UNKNOWN ? std::uint16_t(sc) : 0;
|
|
}
|
|
return in;
|
|
}
|
|
|
|
int ActionNamed(const char* name) {
|
|
for (int a = 0; a < kBindCount; ++a) {
|
|
if (std::strcmp(name, kActions[a].key) == 0) return a;
|
|
}
|
|
for (const OldName& old : kOldNames) {
|
|
if (std::strcmp(name, old.name) == 0) return old.action;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool Valid(int action) { return action >= 0 && action < kBindCount; }
|
|
|
|
int SlotFor(const Binding& b, const Input& in) {
|
|
if (!in.mouse && b.slot[kKeySlot].None()) return kKeySlot;
|
|
if (in.mouse && !in.modifiers && b.slot[kMouseSlot].None()) return kMouseSlot;
|
|
for (int s = kFirstExtra; s < kSlots; ++s) {
|
|
if (b.slot[s].None()) return s;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
std::atomic<std::uint8_t> g_keyboard_modifiers{0};
|
|
std::atomic<bool> g_walk_on_mouse{false};
|
|
|
|
constexpr float kWalkPush = 0.6f;
|
|
|
|
constexpr int kOpposites[][2] = {
|
|
{kBindMoveF, kBindMoveB}, {kBindMoveL, kBindMoveR}, {kBindLookU, kBindLookD}, {kBindLookL, kBindLookR}};
|
|
|
|
template <typename IsDown>
|
|
void HeldOn(bool mouse, IsDown down, std::uint8_t modifiers, bool held[kBindCount]) {
|
|
struct Candidate {
|
|
int action;
|
|
Input input;
|
|
};
|
|
Candidate found[kBindCount * kSlots];
|
|
int count = 0;
|
|
{
|
|
std::scoped_lock lock(g_lock);
|
|
for (int a = 0; a < kBindCount; ++a) {
|
|
for (const Input& in : g_bindings[a].slot) {
|
|
if (!in.None() && in.mouse == mouse && down(in.code) && (in.modifiers & ~modifiers) == 0)
|
|
found[count++] = {a, in};
|
|
}
|
|
}
|
|
}
|
|
for (int a = 0; a < kBindCount; ++a) held[a] = false;
|
|
for (int i = 0; i < count; ++i) {
|
|
|
|
bool outranked = false;
|
|
for (int j = 0; j < count && !outranked; ++j) {
|
|
const std::uint8_t mine = found[i].input.modifiers, theirs = found[j].input.modifiers;
|
|
outranked = found[j].input.code == found[i].input.code && theirs != mine && (theirs & mine) == mine;
|
|
}
|
|
if (!outranked) held[found[i].action] = true;
|
|
}
|
|
}
|
|
|
|
std::uint8_t ModifiersDown(const bool* keys, int count) {
|
|
const auto down = [&](SDL_Scancode sc) { return sc < count && keys[sc]; };
|
|
std::uint8_t m = 0;
|
|
if (down(SDL_SCANCODE_LCTRL) || down(SDL_SCANCODE_RCTRL)) m |= kCtrl;
|
|
if (down(SDL_SCANCODE_LSHIFT) || down(SDL_SCANCODE_RSHIFT)) m |= kShift;
|
|
if (down(SDL_SCANCODE_LALT) || down(SDL_SCANCODE_RALT)) m |= kAlt;
|
|
return m;
|
|
}
|
|
|
|
std::uint8_t ModifiersOf(SDL_Keymod mods) {
|
|
std::uint8_t m = 0;
|
|
if (mods & SDL_KMOD_CTRL) m |= kCtrl;
|
|
if (mods & SDL_KMOD_SHIFT) m |= kShift;
|
|
if (mods & SDL_KMOD_ALT) m |= kAlt;
|
|
return m;
|
|
}
|
|
|
|
bool IsModifierKey(int sc) {
|
|
return sc == SDL_SCANCODE_LCTRL || sc == SDL_SCANCODE_RCTRL || sc == SDL_SCANCODE_LSHIFT ||
|
|
sc == SDL_SCANCODE_RSHIFT || sc == SDL_SCANCODE_LALT || sc == SDL_SCANCODE_RALT;
|
|
}
|
|
|
|
using Clock = std::chrono::steady_clock;
|
|
std::int64_t NowMs() {
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
std::atomic<int> g_capturing{-1};
|
|
std::atomic<std::int64_t> g_capture_began{0};
|
|
std::atomic<int> g_capture_modifier{0};
|
|
std::atomic<int> g_capture_done{-1};
|
|
|
|
std::atomic<bool> g_after_capture{false};
|
|
std::atomic<std::int64_t> g_after_capture_since{0};
|
|
constexpr std::int64_t kAfterCaptureMaxMs = 1500;
|
|
|
|
constexpr std::int64_t kCaptureClickMs = 300;
|
|
std::atomic<std::int64_t> g_clear_pressed{0};
|
|
constexpr std::int64_t kClearKeyMs = 250;
|
|
|
|
void TakeFromOthers(int action, const Input& in) {
|
|
for (int b = 0; b < kBindCount; ++b) {
|
|
if (b == action) continue;
|
|
for (Input& slot : g_bindings[b].slot) {
|
|
if (slot == in) {
|
|
slot = {};
|
|
host_log("keys: %s gives up %s", kActions[b].key, IniText(in).c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void EndCapture(int action, const char* outcome) {
|
|
Changed();
|
|
g_capture_modifier.store(0, std::memory_order_relaxed);
|
|
g_capturing.store(-1, std::memory_order_relaxed);
|
|
g_after_capture_since.store(NowMs(), std::memory_order_relaxed);
|
|
g_after_capture.store(true, std::memory_order_relaxed);
|
|
g_capture_done.store(action, std::memory_order_relaxed);
|
|
char now[160];
|
|
host_binding_describe(action, now, sizeof now);
|
|
host_log("keys: %s %s, now %s", kActions[action].key, outcome, now);
|
|
}
|
|
|
|
void BindKey(int action, int scancode, std::uint8_t modifiers) {
|
|
const Input in{std::uint16_t(scancode), false, modifiers};
|
|
{
|
|
std::scoped_lock lock(g_lock);
|
|
TakeFromOthers(action, in);
|
|
g_bindings[action].slot[kKeySlot] = in;
|
|
}
|
|
EndCapture(action, "rebound");
|
|
}
|
|
}
|
|
|
|
const BindingInfo& host_binding_info(int action) { return kActions[Valid(action) ? action : 0]; }
|
|
|
|
std::uint64_t host_bindings_serial() { return g_serial.load(std::memory_order_relaxed); }
|
|
|
|
void host_bindings_load_defaults() {
|
|
{
|
|
std::scoped_lock lock(g_lock);
|
|
for (int a = 0; a < kBindCount; ++a) {
|
|
Binding& b = g_bindings[a];
|
|
b = {};
|
|
if (const char* key = kActions[a].default_key)
|
|
b.slot[kKeySlot].code = std::uint16_t(SDL_GetScancodeFromName(key));
|
|
if (const int mouse = kActions[a].default_mouse) b.slot[kMouseSlot] = {std::uint16_t(mouse), true, 0};
|
|
}
|
|
}
|
|
Changed();
|
|
}
|
|
|
|
bool host_bindings_parse(const char* name, const char* value) {
|
|
const int action = ActionNamed(name);
|
|
if (action < 0) return false;
|
|
const std::string text = value;
|
|
Binding b;
|
|
for (std::size_t at = 0; at <= text.size();) {
|
|
std::size_t comma = text.find(',', at);
|
|
if (comma == std::string::npos) comma = text.size();
|
|
const std::string token = Trimmed(text.substr(at, comma - at));
|
|
at = comma + 1;
|
|
if (token.empty() || token == "none") continue;
|
|
const Input in = ParseInput(token);
|
|
if (in.None()) {
|
|
host_log("keys: %s=%s: \"%s\" is not a key or mouse input", kActions[action].key, value, token.c_str());
|
|
continue;
|
|
}
|
|
const int slot = SlotFor(b, in);
|
|
if (slot < 0) {
|
|
host_log("keys: %s=%s: more than %d inputs, \"%s\" left out", kActions[action].key, value, kSlots,
|
|
token.c_str());
|
|
continue;
|
|
}
|
|
b.slot[slot] = in;
|
|
}
|
|
{
|
|
std::scoped_lock lock(g_lock);
|
|
g_bindings[action] = b;
|
|
}
|
|
Changed();
|
|
return true;
|
|
}
|
|
|
|
std::string host_binding_ini_value(int action) {
|
|
if (!Valid(action)) return "none";
|
|
Binding b;
|
|
{
|
|
std::scoped_lock lock(g_lock);
|
|
b = g_bindings[action];
|
|
}
|
|
std::string value;
|
|
for (const Input& in : b.slot) {
|
|
const std::string text = in.None() ? std::string() : IniText(in);
|
|
if (text.empty()) continue;
|
|
if (!value.empty()) value += ", ";
|
|
value += text;
|
|
}
|
|
return value.empty() ? "none" : value;
|
|
}
|
|
|
|
int host_binding_key(int action) {
|
|
if (!Valid(action)) return 0;
|
|
std::scoped_lock lock(g_lock);
|
|
return g_bindings[action].slot[kKeySlot].code;
|
|
}
|
|
|
|
int host_binding_mouse(int action) {
|
|
if (!Valid(action)) return 0;
|
|
std::scoped_lock lock(g_lock);
|
|
return g_bindings[action].slot[kMouseSlot].code;
|
|
}
|
|
|
|
void host_binding_describe(int action, char* out, std::size_t size) {
|
|
if (!size) return;
|
|
std::string text;
|
|
if (Valid(action)) {
|
|
Binding b;
|
|
{
|
|
std::scoped_lock lock(g_lock);
|
|
b = g_bindings[action];
|
|
}
|
|
for (const Input& in : b.slot) {
|
|
const std::string one = in.None() ? std::string() : ScreenText(in);
|
|
if (one.empty()) continue;
|
|
if (!text.empty()) text += " / ";
|
|
text += one;
|
|
}
|
|
}
|
|
std::snprintf(out, size, "%s", text.empty() ? "Unbound" : text.c_str());
|
|
}
|
|
|
|
void host_binding_clear(int action) {
|
|
if (!Valid(action)) return;
|
|
{
|
|
std::scoped_lock lock(g_lock);
|
|
g_bindings[action] = {};
|
|
}
|
|
Changed();
|
|
host_log("keys: %s cleared", kActions[action].key);
|
|
}
|
|
|
|
namespace {
|
|
bool g_toggles[kBindCount] = {};
|
|
std::atomic<bool> g_toggled[kBindCount] = {};
|
|
|
|
bool LoadToggles() {
|
|
const char* env = std::getenv("BB_TOGGLE_KEYS");
|
|
if (!env) return false;
|
|
bool any = false;
|
|
std::string name;
|
|
for (const char* p = env;; ++p) {
|
|
if (*p && *p != ',') {
|
|
if (*p != ' ') name += *p;
|
|
continue;
|
|
}
|
|
const int action = ActionNamed(name.c_str());
|
|
if (action >= 0 && action != kBindMenu && action != kBindConfirm && action != kBindBack &&
|
|
action != kBindDebugMenu) {
|
|
g_toggles[action] = any = true;
|
|
}
|
|
name.clear();
|
|
if (!*p) break;
|
|
}
|
|
return any;
|
|
}
|
|
|
|
void ApplyToggles(bool was[kBindCount], bool held[kBindCount]) {
|
|
static const bool any = LoadToggles();
|
|
if (!any) return;
|
|
const bool menu = menu_pointer_in_menu();
|
|
for (int a = 0; a < kBindCount; ++a) {
|
|
if (!g_toggles[a]) continue;
|
|
if (held[a] && !was[a] && !menu) g_toggled[a].store(!g_toggled[a].load(std::memory_order_relaxed), std::memory_order_relaxed);
|
|
was[a] = held[a];
|
|
held[a] = !menu && g_toggled[a].load(std::memory_order_relaxed);
|
|
}
|
|
}
|
|
}
|
|
|
|
void host_bindings_keys_held(const bool* keys, int count, bool held[kBindCount]) {
|
|
const std::uint8_t modifiers = ModifiersDown(keys, count);
|
|
g_keyboard_modifiers.store(modifiers, std::memory_order_relaxed);
|
|
HeldOn(false, [&](int sc) { return sc < count && keys[sc]; }, modifiers, held);
|
|
static bool was[kBindCount] = {};
|
|
ApplyToggles(was, held);
|
|
|
|
for (const auto& pair : kOpposites) {
|
|
if (held[pair[0]] && held[pair[1]]) held[pair[0]] = held[pair[1]] = false;
|
|
}
|
|
held[kBindWalk] = held[kBindWalk] || g_walk_on_mouse.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
void host_bindings_mouse_held(std::uint32_t inputs, bool held[kBindCount]) {
|
|
HeldOn(true, [&](int m) { return m >= 1 && m <= kMouseInputs && (inputs >> (m - 1) & 1); },
|
|
g_keyboard_modifiers.load(std::memory_order_relaxed), held);
|
|
static bool was[kBindCount] = {};
|
|
ApplyToggles(was, held);
|
|
g_walk_on_mouse.store(held[kBindWalk], std::memory_order_relaxed);
|
|
}
|
|
|
|
bool host_bindings_dodge(std::uint64_t now_ms, bool menu, PadState& pad) {
|
|
constexpr std::uint64_t kShortestStepMs = 20, kLongestStepMs = 67, kSprintTailMs = 150;
|
|
constexpr int kGaps = 8;
|
|
static bool was_dodge = false, was_backstep = false, from_sprint = false, still = false;
|
|
static std::uint64_t began = 0, step_ms = kLongestStepMs, last_read = 0, last_sprint = 0;
|
|
static std::uint64_t gaps[kGaps] = {};
|
|
static int gap_at = 0;
|
|
if (last_read && now_ms > last_read) {
|
|
gaps[gap_at] = now_ms - last_read;
|
|
gap_at = (gap_at + 1) % kGaps;
|
|
}
|
|
last_read = now_ms;
|
|
const bool dodge = g_dodge_down.exchange(false, std::memory_order_relaxed);
|
|
const bool backstep = g_backstep_down.exchange(false, std::memory_order_relaxed);
|
|
const bool sprint = g_sprint_down.exchange(false, std::memory_order_relaxed);
|
|
const bool dodge_pressed = dodge && !was_dodge, backstep_pressed = backstep && !was_backstep;
|
|
was_dodge = dodge;
|
|
was_backstep = backstep;
|
|
if (menu) {
|
|
began = 0;
|
|
last_sprint = 0;
|
|
return false;
|
|
}
|
|
if (sprint) {
|
|
last_sprint = std::max<std::uint64_t>(now_ms, 1);
|
|
pad.buttons |= kCircle;
|
|
}
|
|
const bool at_once = last_sprint && now_ms - last_sprint < kSprintTailMs;
|
|
if ((dodge_pressed || backstep_pressed) && !began) {
|
|
began = std::max<std::uint64_t>(now_ms, 1);
|
|
from_sprint = (pad.buttons & kCircle) != 0;
|
|
still = backstep_pressed;
|
|
const std::uint64_t frame = *std::max_element(gaps, gaps + kGaps);
|
|
step_ms = std::clamp<std::uint64_t>(frame + frame / 4, kShortestStepMs, kLongestStepMs);
|
|
}
|
|
if (!began) return at_once;
|
|
const std::uint64_t step = (now_ms - began) / step_ms + (from_sprint ? 0 : 1);
|
|
if (step > 2) {
|
|
began = 0;
|
|
return at_once;
|
|
}
|
|
if (step == 1) pad.buttons |= kCircle;
|
|
else pad.buttons &= ~std::uint32_t(kCircle);
|
|
if (still) pad.lx = pad.ly = kStickRest;
|
|
return at_once && step == 0;
|
|
}
|
|
|
|
void host_bindings_apply(const bool held[kBindCount], bool strong, PadState& pad) {
|
|
const bool menu = menu_pointer_in_menu();
|
|
if (held[kBindDodge]) g_dodge_down.store(true, std::memory_order_relaxed);
|
|
if (held[kBindBackstep]) g_backstep_down.store(true, std::memory_order_relaxed);
|
|
if (held[kBindSprint]) g_sprint_down.store(true, std::memory_order_relaxed);
|
|
for (int a = 0; a < kBindCount; ++a) {
|
|
if (!held[a]) continue;
|
|
if ((a == kBindConfirm || a == kBindBack) && !menu) continue;
|
|
std::uint32_t bit = kActions[a].button;
|
|
if (a == kBindAttack && strong) bit = kR2;
|
|
if (a == kBindConfirm) bit = menu_confirm_button();
|
|
if (a == kBindBack) bit = menu_back_button();
|
|
pad.buttons |= bit;
|
|
if (bit == kL2) pad.l2 = 255;
|
|
if (bit == kR2) pad.r2 = 255;
|
|
switch (a) {
|
|
case kBindLookU: pad.ry = kStickLow; break;
|
|
case kBindLookD: pad.ry = kStickHigh; break;
|
|
case kBindLookL: pad.rx = kStickLow; break;
|
|
case kBindLookR: pad.rx = kStickHigh; break;
|
|
case kBindGestures:
|
|
case kBindEffects:
|
|
|
|
if (pad.touch_count < 2) {
|
|
PadState::Finger& f = pad.touch[pad.touch_count++];
|
|
f.x = std::uint16_t(a == kBindGestures ? kTouchWidth / 4 : kTouchWidth * 3 / 4);
|
|
f.y = std::uint16_t(kTouchHeight / 2);
|
|
f.id = 200;
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
const int x = int(held[kBindMoveR]) - int(held[kBindMoveL]);
|
|
const int y = int(held[kBindMoveB]) - int(held[kBindMoveF]);
|
|
if (x || y) {
|
|
int reach = kStickHigh - kStickRest;
|
|
if (held[kBindWalk]) reach = int(std::lround(reach * kWalkPush / (x && y ? std::sqrt(2.0f) : 1.0f)));
|
|
if (x) pad.lx = std::uint8_t(kStickRest + x * reach);
|
|
if (y) pad.ly = std::uint8_t(kStickRest + y * reach);
|
|
}
|
|
}
|
|
|
|
void host_bind_capture_begin(int action) {
|
|
if (!Valid(action)) return;
|
|
g_capture_modifier.store(0, std::memory_order_relaxed);
|
|
g_capture_began.store(NowMs(), std::memory_order_relaxed);
|
|
g_capturing.store(action, std::memory_order_relaxed);
|
|
host_log("keys: rebinding %s: a key, a mouse button or the wheel (Escape keeps it, Delete unbinds it)",
|
|
kActions[action].key);
|
|
}
|
|
|
|
int host_bind_capturing() { return g_capturing.load(std::memory_order_relaxed); }
|
|
|
|
bool host_bind_capture_key(int scancode, unsigned sdl_mods) {
|
|
const int action = g_capturing.load(std::memory_order_relaxed);
|
|
if (action < 0) return false;
|
|
if (scancode == SDL_SCANCODE_ESCAPE) {
|
|
EndCapture(action, "unchanged");
|
|
} else if (scancode == SDL_SCANCODE_DELETE) {
|
|
{
|
|
std::scoped_lock lock(g_lock);
|
|
g_bindings[action] = {};
|
|
}
|
|
EndCapture(action, "unbound");
|
|
} else if (IsModifierKey(scancode)) {
|
|
g_capture_modifier.store(scancode, std::memory_order_relaxed);
|
|
} else {
|
|
BindKey(action, scancode, ModifiersOf(SDL_Keymod(sdl_mods)));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool host_bind_capture_key_up(int scancode) {
|
|
const int action = g_capturing.load(std::memory_order_relaxed);
|
|
if (action < 0 || !scancode || g_capture_modifier.load(std::memory_order_relaxed) != scancode) return false;
|
|
BindKey(action, scancode, 0);
|
|
return true;
|
|
}
|
|
|
|
bool host_bind_capture_mouse(int input) {
|
|
const int action = g_capturing.load(std::memory_order_relaxed);
|
|
if (action < 0 || input < 1 || input > kMouseInputs) return false;
|
|
const std::int64_t since = NowMs() - g_capture_began.load(std::memory_order_relaxed);
|
|
if (since < kCaptureClickMs) {
|
|
host_log("keys: a mouse press %lld ms into the capture is part of the click that began it",
|
|
static_cast<long long>(since));
|
|
return true;
|
|
}
|
|
const Input in{std::uint16_t(input), true, ModifiersOf(SDL_GetModState())};
|
|
{
|
|
std::scoped_lock lock(g_lock);
|
|
TakeFromOthers(action, in);
|
|
Binding& b = g_bindings[action];
|
|
if (!in.modifiers) {
|
|
b.slot[kMouseSlot] = in;
|
|
} else {
|
|
|
|
int slot = -1;
|
|
for (int s = kFirstExtra; s < kSlots && slot < 0; ++s) {
|
|
if (b.slot[s] == in) slot = s;
|
|
}
|
|
for (int s = kFirstExtra; s < kSlots && slot < 0; ++s) {
|
|
if (b.slot[s].None()) slot = s;
|
|
}
|
|
b.slot[slot < 0 ? kSlots - 1 : slot] = in;
|
|
}
|
|
}
|
|
EndCapture(action, "rebound");
|
|
return true;
|
|
}
|
|
|
|
int host_bind_capture_take_done() { return g_capture_done.exchange(-1); }
|
|
|
|
bool host_bind_capture_blocking() {
|
|
return g_capturing.load(std::memory_order_relaxed) >= 0 || g_after_capture.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
void host_bind_capture_note_held(bool any_down) {
|
|
if (g_after_capture.load(std::memory_order_relaxed) &&
|
|
(!any_down || NowMs() - g_after_capture_since.load(std::memory_order_relaxed) > kAfterCaptureMaxMs))
|
|
g_after_capture.store(false, std::memory_order_relaxed);
|
|
}
|
|
|
|
void host_bind_note_clear_key() { g_clear_pressed.store(NowMs(), std::memory_order_relaxed); }
|
|
|
|
bool host_bind_take_clear_key() {
|
|
const std::int64_t at = g_clear_pressed.exchange(0);
|
|
return at && NowMs() - at < kClearKeyMs;
|
|
}
|