WASD: resume the walk right after a weapon set switch (untested in-game)
Switching between weapon classes resets the hero's animation state inside cUI_Taskbar2::SelectWeaponSet, which dropped the walk for a tick. The hook sends the WASD walk again at once. Checkpoint before the movement rework. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
184c7058fd
commit
d296e2850b
4 files changed
+79
-12
No files matched your search
@@ -79,3 +79,18 @@ Now (`src/mods/input.cpp`, `FocusedTextField`): keys pass to the game while the
|
|||||||
focused control is a writable edit control that `cUI_EditControl::Render`
|
focused control is a writable edit control that `cUI_EditControl::Render`
|
||||||
(0x737ae0, thiscall (renderer), ret 4, returns at once while hidden) drew in
|
(0x737ae0, thiscall (renderer), ret 4, returns at once while hidden) drew in
|
||||||
the last 250 ms. The render hook only notes the focused control.
|
the last 250 ms. The render hook only notes the focused control.
|
||||||
|
|
||||||
|
## Weapon set switch while walking (2026-10-18)
|
||||||
|
|
||||||
|
Request: the hero stops for a moment when switching weapon sets (F-keys)
|
||||||
|
while walking with WASD.
|
||||||
|
|
||||||
|
Measured (state per tick + orders): `cUI_Taskbar2::SelectWeaponSet`
|
||||||
|
(`0x6e1440`, ret 8) sends order 0x114 (set, items) and 0x107; between melee
|
||||||
|
sets the walk goes on, between a melee and a bow set the hero drops to idle
|
||||||
|
for one tick: the weapon class changed, and the switch resets the hero's
|
||||||
|
animation state (`0x54e540(1, 0)`). The next WASD tick restarts the walk.
|
||||||
|
|
||||||
|
Now: `SelectWeaponSet` is hooked (`skillbar.cpp`) and calls
|
||||||
|
`mods::wasd::ResumeWalk`, which sends the walk for the held keys at once.
|
||||||
|
Not yet confirmed in-game.
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "game/engine/objects.h"
|
#include "game/engine/objects.h"
|
||||||
#include "game/ui/text.h"
|
#include "game/ui/text.h"
|
||||||
#include "mods/skillslots.h"
|
#include "mods/skillslots.h"
|
||||||
|
#include "mods/wasd.h"
|
||||||
#include "re/hook.h"
|
#include "re/hook.h"
|
||||||
#include "re/log.h"
|
#include "re/log.h"
|
||||||
#include "re/patch.h"
|
#include "re/patch.h"
|
||||||
@@ -307,6 +308,18 @@ void __fastcall UiTaskbar_SelectSkillSlot(void* self, void* edx, uint32_t slot,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cUI_Taskbar2::SelectWeaponSet (0x6e1440, ret 8): sends the hero the
|
||||||
|
// weapon set order (0x114) and, when the weapon class changed, resets its
|
||||||
|
// animation state (0x54e540), which drops a walk for a tick (measured
|
||||||
|
// 2026-10-18: action walk -> idle -> walk on switches between melee and bow
|
||||||
|
// sets, none between melee sets). A WASD walk goes on at once.
|
||||||
|
void __fastcall UiTaskbar_SelectWeaponSet(void* self, void* edx, uint32_t set, int notify);
|
||||||
|
RE_HOOK(0x006e1440, UiTaskbar_SelectWeaponSet);
|
||||||
|
void __fastcall UiTaskbar_SelectWeaponSet(void* self, void* edx, uint32_t set, int notify) {
|
||||||
|
UiTaskbar_SelectWeaponSet_original(self, edx, set, notify);
|
||||||
|
mods::wasd::ResumeWalk();
|
||||||
|
}
|
||||||
|
|
||||||
// cUI_Taskbar2::RenderArtSlots (0x6e31e0): then the cooldown labels.
|
// cUI_Taskbar2::RenderArtSlots (0x6e31e0): then the cooldown labels.
|
||||||
void __fastcall UiTaskbar_RenderArtSlots(void* self, void* edx, void* renderer);
|
void __fastcall UiTaskbar_RenderArtSlots(void* self, void* edx, void* renderer);
|
||||||
RE_HOOK(0x006e31e0, UiTaskbar_RenderArtSlots);
|
RE_HOOK(0x006e31e0, UiTaskbar_RenderArtSlots);
|
||||||
|
|||||||
+47
-12
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
#include "mods/wasd.h"
|
#include "mods/wasd.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
@@ -69,19 +70,39 @@ bool IsMovementKey(int vk) {
|
|||||||
return (vk == 'W' || vk == 'A' || vk == 'S' || vk == 'D');
|
return (vk == 'W' || vk == 'A' || vk == 'S' || vk == 'D');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called once per engine tick (10 Hz), after the game's own input handling.
|
namespace {
|
||||||
void Update(uint8_t* engine) {
|
|
||||||
// World axes on screen: (-1,-1) up, (1,1) down, (-1,1) left, (1,-1) right.
|
// The held movement keys as a screen direction in world axes: (-1,-1) up,
|
||||||
int dx = 0;
|
// (1,1) down, (-1,1) left, (1,-1) right; 0,0 when none (or the keys belong to
|
||||||
int dy = 0;
|
// the mouse, blocked input or a text field).
|
||||||
|
void HeldDirection(uint8_t* engine, int& dx, int& dy) {
|
||||||
|
dx = 0;
|
||||||
|
dy = 0;
|
||||||
const uint32_t flags = *reinterpret_cast<uint32_t*>(engine + kEngineFlags);
|
const uint32_t flags = *reinterpret_cast<uint32_t*>(engine + kEngineFlags);
|
||||||
const bool mouseMoving = (flags & 0x10) != 0;
|
if ((flags & 0x10) == 0 && (flags & 0x4000) == 0 && !mods::input::TypingIntoTextField()) {
|
||||||
if (!mouseMoving && (flags & 0x4000) == 0 && !mods::input::TypingIntoTextField()) {
|
|
||||||
if (KeyDown('W')) { dx -= 1; dy -= 1; }
|
if (KeyDown('W')) { dx -= 1; dy -= 1; }
|
||||||
if (KeyDown('S')) { dx += 1; dy += 1; }
|
if (KeyDown('S')) { dx += 1; dy += 1; }
|
||||||
if (KeyDown('A')) { dx -= 1; dy += 1; }
|
if (KeyDown('A')) { dx -= 1; dy += 1; }
|
||||||
if (KeyDown('D')) { dx += 1; dy -= 1; }
|
if (KeyDown('D')) { dx += 1; dy -= 1; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Walk(uint8_t* engine, void* creature, int dx, int dy) {
|
||||||
|
const double length = std::sqrt(static_cast<double>(dx * dx + dy * dy));
|
||||||
|
const int x = game::CreatureField(creature, game::kCreatureX) + static_cast<int>(dx / length * kTargetDistance);
|
||||||
|
const int y = game::CreatureField(creature, game::kCreatureY) + static_cast<int>(dy / length * kTargetDistance);
|
||||||
|
SendWalk(engine, creature, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::atomic<uint8_t*> g_engine{nullptr};
|
||||||
|
|
||||||
|
// Called once per engine tick (10 Hz), after the game's own input handling.
|
||||||
|
void Update(uint8_t* engine) {
|
||||||
|
g_engine = engine;
|
||||||
|
int dx;
|
||||||
|
int dy;
|
||||||
|
HeldDirection(engine, dx, dy);
|
||||||
|
const bool mouseMoving = (*reinterpret_cast<uint32_t*>(engine + kEngineFlags) & 0x10) != 0;
|
||||||
if (dx == 0 && dy == 0) {
|
if (dx == 0 && dy == 0) {
|
||||||
if (g_move.moving && !mouseMoving) {
|
if (g_move.moving && !mouseMoving) {
|
||||||
if (void* creature = ControlledCreature(engine)) {
|
if (void* creature = ControlledCreature(engine)) {
|
||||||
@@ -95,16 +116,30 @@ void Update(uint8_t* engine) {
|
|||||||
if (!creature) {
|
if (!creature) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const double length = std::sqrt(static_cast<double>(dx * dx + dy * dy));
|
Walk(engine, creature, dx, dy);
|
||||||
const int x = game::CreatureField(creature, game::kCreatureX) + static_cast<int>(dx / length * kTargetDistance);
|
|
||||||
const int y = game::CreatureField(creature, game::kCreatureY) + static_cast<int>(dy / length * kTargetDistance);
|
|
||||||
SendWalk(engine, creature, x, y);
|
|
||||||
g_move.moving = true;
|
g_move.moving = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void ResumeWalk() {
|
||||||
|
uint8_t* engine = g_engine.load();
|
||||||
|
void* creature = engine && g_move.moving ? ControlledCreature(engine) : nullptr;
|
||||||
|
int dx;
|
||||||
|
int dy;
|
||||||
|
if (creature) {
|
||||||
|
HeldDirection(engine, dx, dy);
|
||||||
|
if (dx != 0 || dy != 0) {
|
||||||
|
Walk(engine, creature, dx, dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
struct Registration {
|
struct Registration {
|
||||||
Registration() { game::OnEngineTick(&Update); }
|
Registration() {
|
||||||
|
game::OnEngineTick(&Update);
|
||||||
|
}
|
||||||
} g_registration;
|
} g_registration;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,8 @@ namespace mods::wasd {
|
|||||||
// Whether `vk` is one of the movement keys W/A/S/D.
|
// Whether `vk` is one of the movement keys W/A/S/D.
|
||||||
bool IsMovementKey(int vk);
|
bool IsMovementKey(int vk);
|
||||||
|
|
||||||
|
// Sends the walk for the held movement keys now, after something stopped the
|
||||||
|
// hero mid-walk (a weapon set switch).
|
||||||
|
void ResumeWalk();
|
||||||
|
|
||||||
} // namespace mods::wasd
|
} // namespace mods::wasd
|
||||||
Reference in new issue
Block a user