Key bindings layer: skills on 1-7, weapon sets on F1-F7
Raw WM_KEYDOWN/WM_KEYUP are handled in both window procedures (the game swaps 0x8110d0 and 0x8122a0 with SetWindowLong; 0x8110d0 is the in-game one). Bound keys are swallowed and post the original hotkey's key event to the kernel, so F-keys work and bindings are layout independent ([keys] in sacred_re.ini). Replaces the WASD hotkey remap and the digit swap; W/A/S/D and the digit row no longer reach the game. Skill keys still cast through the right mouse button. Enabled switches removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
9fb6c6b406
commit
cec2556fa3
11 files changed
+409
-279
No files matched your search
+58
-85
@@ -1,34 +1,22 @@
|
||||
// Skill hotkeys cast the skill, not only select it.
|
||||
// Skill keys cast the skill, not only select it.
|
||||
//
|
||||
// Digits 6-9 and 0 select one of the five combat-art slots on the taskbar
|
||||
// (cUI_Taskbar2::HandleEvent -> cUI_Taskbar2::SelectSkillSlot 0x6e17d0); the
|
||||
// selected art is then used with the right mouse button. With this mod a
|
||||
// digit also presses the right mouse button: a WM_RBUTTONDOWN is posted to the
|
||||
// game window, so the game handles it exactly like a real right-click at the
|
||||
// cursor (UI hit tests, target under the cursor, cEvent_creature 0x105). The
|
||||
// button is released when the digit is, so holding the digit is holding the
|
||||
// button.
|
||||
//
|
||||
// SwapDigits trades 1-5 (weapon sets) and 6-0 (combat-art slots), so the
|
||||
// skills sit on 1-5. Key events are rewritten where the WASD mod rewrites its
|
||||
// keys (Engine/UI manager ReceiveEvent, src/mods/wasd.cpp).
|
||||
//
|
||||
// sacred_re.ini:
|
||||
// [skillkeys]
|
||||
// Enabled=1
|
||||
// SwapDigits=1
|
||||
// The taskbar's combat-art slots are selected with a key (cUI_Taskbar2::
|
||||
// HandleEvent -> SelectSkillSlot 0x6e17d0); the selected art is then used with
|
||||
// the right mouse button. When a skill key (src/mods/input.cpp) selects a slot,
|
||||
// this mod also presses the right mouse button: a WM_RBUTTONDOWN is posted to
|
||||
// the game window, so the game handles it exactly like a real right-click at
|
||||
// the cursor (UI hit tests, target under the cursor, cEvent_creature 0x105).
|
||||
// The button is released with the key, so holding the key is holding the
|
||||
// button. Slots selected by clicking them with the mouse are not cast.
|
||||
|
||||
#include "mods/skillkeys.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "mods/skillkeys.h"
|
||||
#include "re/config.h"
|
||||
#include "re/hook.h"
|
||||
#include "re/log.h"
|
||||
#include "re/patch.h"
|
||||
|
||||
namespace mods::skillkeys {
|
||||
namespace {
|
||||
@@ -38,29 +26,7 @@ constexpr size_t kTaskbarSlotCount = 0x15c; // uint16: combat-art slots the her
|
||||
constexpr size_t kTaskbarSkillSlots = 0x178; // 5 slot controls (null: none)
|
||||
constexpr unsigned kSlots = 5;
|
||||
|
||||
bool Enabled() {
|
||||
static const bool enabled = re::ConfigInt("skillkeys", "Enabled", 1) != 0;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool SwapDigits() {
|
||||
static const bool swap = re::ConfigInt("skillkeys", "SwapDigits", 1) != 0;
|
||||
return swap;
|
||||
}
|
||||
|
||||
// Digit the player presses to select a slot: 6 7 8 9 0, or 1-5 when swapped.
|
||||
int SlotKey(unsigned slot) {
|
||||
if (SwapDigits()) {
|
||||
return '1' + static_cast<int>(slot);
|
||||
}
|
||||
return slot < 4 ? '6' + static_cast<int>(slot) : '0';
|
||||
}
|
||||
|
||||
bool KeyDown(int key) {
|
||||
return (GetAsyncKeyState(key) & 0x8000) != 0;
|
||||
}
|
||||
|
||||
// The game window, if it has the focus (it does while a hotkey is pressed).
|
||||
// The game window, if it has the focus (it does while a skill key is used).
|
||||
HWND GameWindow() {
|
||||
HWND window = GetForegroundWindow();
|
||||
DWORD process = 0;
|
||||
@@ -77,55 +43,62 @@ LPARAM CursorClientPos(HWND window) {
|
||||
return MAKELPARAM(cursor.x, cursor.y);
|
||||
}
|
||||
|
||||
std::atomic<bool> g_held{false};
|
||||
|
||||
// Presses the right mouse button and releases it once `key` is released.
|
||||
void PressRightButtonWhileHeld(int key) {
|
||||
HWND window = GameWindow();
|
||||
if (!window || g_held.exchange(true)) {
|
||||
return; // not focused, or already held (key repeat)
|
||||
}
|
||||
PostMessageW(window, WM_RBUTTONDOWN, MK_RBUTTON, CursorClientPos(window));
|
||||
std::thread([window, key] {
|
||||
while (KeyDown(key) && IsWindow(window)) {
|
||||
Sleep(10);
|
||||
}
|
||||
PostMessageW(window, WM_RBUTTONUP, 0, CursorClientPos(window));
|
||||
g_held = false;
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void Startup() {
|
||||
if (Enabled()) {
|
||||
re::Log("skillkeys: digits %s cast the skill in their slot", SwapDigits() ? "1-5" : "6-0");
|
||||
}
|
||||
}
|
||||
RE_STARTUP(SkillKeysStartup, Startup);
|
||||
// The key that is to cast slot `slot` when it gets selected, then the key
|
||||
// holding the right mouse button down. Set on the window thread, used on the
|
||||
// thread that handles UI events.
|
||||
struct State {
|
||||
std::mutex lock;
|
||||
int pendingKey = 0;
|
||||
unsigned pendingSlot = 0;
|
||||
bool keyDown = false;
|
||||
int heldKey = 0; // right button is down for this key
|
||||
HWND window = nullptr;
|
||||
};
|
||||
State g_state;
|
||||
|
||||
} // namespace
|
||||
|
||||
uint16_t RemapKey(uint16_t key) {
|
||||
if (!Enabled() || !SwapDigits() || key < '0' || key > '9') {
|
||||
return key;
|
||||
}
|
||||
static constexpr char kSwapped[] = "5678901234"; // indexed by key - '0'
|
||||
return static_cast<uint16_t>(kSwapped[key - '0']);
|
||||
void CastOnSelect(unsigned slot, int vk) {
|
||||
std::lock_guard guard(g_state.lock);
|
||||
g_state.pendingKey = vk;
|
||||
g_state.pendingSlot = slot;
|
||||
g_state.keyDown = true;
|
||||
}
|
||||
|
||||
// After a slot was selected: cast if its digit is what selected it (the slot
|
||||
// can also be clicked with the mouse).
|
||||
void KeyReleased(int vk) {
|
||||
std::lock_guard guard(g_state.lock);
|
||||
if (g_state.pendingKey == vk) {
|
||||
g_state.keyDown = false; // released before the slot was selected
|
||||
}
|
||||
if (g_state.heldKey == vk) {
|
||||
g_state.heldKey = 0;
|
||||
PostMessageW(g_state.window, WM_RBUTTONUP, 0, CursorClientPos(g_state.window));
|
||||
}
|
||||
}
|
||||
|
||||
// After a slot was selected: cast if a skill key asked for it.
|
||||
void OnSlotSelected(void* taskbar, unsigned slot) {
|
||||
if (!Enabled() || slot >= kSlots) {
|
||||
std::lock_guard guard(g_state.lock);
|
||||
if (!g_state.pendingKey || g_state.pendingSlot != slot) {
|
||||
return;
|
||||
}
|
||||
const int key = g_state.pendingKey;
|
||||
g_state.pendingKey = 0;
|
||||
const auto* bar = static_cast<const uint8_t*>(taskbar);
|
||||
if (slot >= *reinterpret_cast<const uint16_t*>(bar + kTaskbarSlotCount) ||
|
||||
if (slot >= kSlots || slot >= *reinterpret_cast<const uint16_t*>(bar + kTaskbarSlotCount) ||
|
||||
!reinterpret_cast<void* const*>(bar + kTaskbarSkillSlots)[slot]) {
|
||||
return; // empty slot: nothing was selected
|
||||
}
|
||||
const int key = SlotKey(slot);
|
||||
if (KeyDown(key)) {
|
||||
PressRightButtonWhileHeld(key);
|
||||
HWND window = GameWindow();
|
||||
if (!window || g_state.heldKey) {
|
||||
return;
|
||||
}
|
||||
PostMessageW(window, WM_RBUTTONDOWN, MK_RBUTTON, CursorClientPos(window));
|
||||
if (g_state.keyDown) {
|
||||
g_state.heldKey = key;
|
||||
g_state.window = window;
|
||||
} else {
|
||||
PostMessageW(window, WM_RBUTTONUP, 0, CursorClientPos(window));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +107,7 @@ void OnSlotSelected(void* taskbar, unsigned slot) {
|
||||
namespace game {
|
||||
|
||||
// cUI_Taskbar2::SelectSkillSlot (0x6e17d0): makes combat-art slot `slot` the
|
||||
// right mouse button's art (digits 6-0, clicks on the slot).
|
||||
// right mouse button's art (keys, clicks on the slot).
|
||||
void __fastcall UiTaskbar_SelectSkillSlot(void* self, void* edx, unsigned slot, int notify);
|
||||
RE_HOOK(0x006e17d0, UiTaskbar_SelectSkillSlot);
|
||||
void __fastcall UiTaskbar_SelectSkillSlot(void* self, void* edx, unsigned slot, int notify) {
|
||||
|
||||
Reference in new issue
Block a user