Skill hotkeys cast the skill; skills on 1-5, weapon sets on 6-0
A digit that selects a combat-art slot now also presses the right mouse button (posted WM_RBUTTONDOWN, released with the key), so the game casts the art at the cursor like a right-click. SwapDigits trades the digit rows; the key rewrite shares the WASD remap in the engine/UI receivers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b32b39f7cb
commit
aa59680fb1
4 files changed
+165
-3
No files matched your search
@@ -34,6 +34,7 @@ add_library(sacred_re SHARED
|
|||||||
src/mods/widescreen.cpp
|
src/mods/widescreen.cpp
|
||||||
src/mods/hud.cpp
|
src/mods/hud.cpp
|
||||||
src/mods/wasd.cpp
|
src/mods/wasd.cpp
|
||||||
|
src/mods/skillkeys.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(sacred_re PRIVATE src)
|
target_include_directories(sacred_re PRIVATE src)
|
||||||
target_compile_options(sacred_re PRIVATE ${SACRED_RE_CXX_FLAGS})
|
target_compile_options(sacred_re PRIVATE ${SACRED_RE_CXX_FLAGS})
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
// Skill hotkeys 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
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#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 {
|
||||||
|
|
||||||
|
// cUI_Taskbar2 fields.
|
||||||
|
constexpr size_t kTaskbarSlotCount = 0x15c; // uint16: combat-art slots the hero has
|
||||||
|
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).
|
||||||
|
HWND GameWindow() {
|
||||||
|
HWND window = GetForegroundWindow();
|
||||||
|
DWORD process = 0;
|
||||||
|
if (!window || !GetWindowThreadProcessId(window, &process) || process != GetCurrentProcessId()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
LPARAM CursorClientPos(HWND window) {
|
||||||
|
POINT cursor = {};
|
||||||
|
GetCursorPos(&cursor);
|
||||||
|
ScreenToClient(window, &cursor);
|
||||||
|
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);
|
||||||
|
|
||||||
|
} // 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']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// After a slot was selected: cast if its digit is what selected it (the slot
|
||||||
|
// can also be clicked with the mouse).
|
||||||
|
void OnSlotSelected(void* taskbar, unsigned slot) {
|
||||||
|
if (!Enabled() || slot >= kSlots) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* bar = static_cast<const uint8_t*>(taskbar);
|
||||||
|
if (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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mods::skillkeys
|
||||||
|
|
||||||
|
namespace game {
|
||||||
|
|
||||||
|
// cUI_Taskbar2::SelectSkillSlot (0x6e17d0): makes combat-art slot `slot` the
|
||||||
|
// right mouse button's art (digits 6-0, 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) {
|
||||||
|
UiTaskbar_SelectSkillSlot_original(self, edx, slot, notify);
|
||||||
|
if (self) {
|
||||||
|
mods::skillkeys::OnSlotSelected(self, slot & 0xffff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace mods::skillkeys {
|
||||||
|
|
||||||
|
// The key the game should see for `key` from a key event: with SwapDigits,
|
||||||
|
// 1-5 and 6-0 trade places (skills on 1-5, weapon sets on 6-0).
|
||||||
|
uint16_t RemapKey(uint16_t key);
|
||||||
|
|
||||||
|
} // namespace mods::skillkeys
|
||||||
+6
-3
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "mods/skillkeys.h"
|
||||||
#include "re/config.h"
|
#include "re/config.h"
|
||||||
#include "re/hook.h"
|
#include "re/hook.h"
|
||||||
#include "re/log.h"
|
#include "re/log.h"
|
||||||
@@ -135,9 +136,11 @@ bool IsMovementKey(int upper) {
|
|||||||
return upper == 'W' || upper == 'A' || upper == 'S' || upper == 'D';
|
return upper == 'W' || upper == 'A' || upper == 'S' || upper == 'D';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the key the game should see instead of `key` (0: none).
|
// Returns the key the game should see instead of `key` (0: none). Also
|
||||||
|
// applies the skill hotkeys' digit swap (src/mods/skillkeys.cpp).
|
||||||
uint16_t RemapKey(uint16_t key) {
|
uint16_t RemapKey(uint16_t key) {
|
||||||
if (key >= 0x80) {
|
key = mods::skillkeys::RemapKey(key);
|
||||||
|
if (key >= 0x80 || !Enabled()) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
const int upper = toupper(key);
|
const int upper = toupper(key);
|
||||||
@@ -159,7 +162,7 @@ thread_local int g_keyEventDepth = 0;
|
|||||||
class KeyEventRemap {
|
class KeyEventRemap {
|
||||||
public:
|
public:
|
||||||
explicit KeyEventRemap(void* event) {
|
explicit KeyEventRemap(void* event) {
|
||||||
if (g_keyEventDepth++ > 0 || !Enabled() || !event ||
|
if (g_keyEventDepth++ > 0 || !event ||
|
||||||
*static_cast<uint32_t*>(event) != kEventKeyVtable) {
|
*static_cast<uint32_t*>(event) != kEventKeyVtable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user