From d296e2850b32a4a2b6bcaf157b78e75b3ac91d2d Mon Sep 17 00:00:00 2001 From: Torhan Bartel Date: Sun, 18 Oct 2026 01:41:13 +0200 Subject: [PATCH] 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) --- docs/wasd.md | 15 +++++++++++ src/mods/skillbar.cpp | 13 ++++++++++ src/mods/wasd.cpp | 59 ++++++++++++++++++++++++++++++++++--------- src/mods/wasd.h | 4 +++ 4 files changed, 79 insertions(+), 12 deletions(-) diff --git a/docs/wasd.md b/docs/wasd.md index dda76ff..ba74dbd 100644 --- a/docs/wasd.md +++ b/docs/wasd.md @@ -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` (0x737ae0, thiscall (renderer), ret 4, returns at once while hidden) drew in 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. diff --git a/src/mods/skillbar.cpp b/src/mods/skillbar.cpp index 740ae6f..244625d 100644 --- a/src/mods/skillbar.cpp +++ b/src/mods/skillbar.cpp @@ -36,6 +36,7 @@ #include "game/engine/objects.h" #include "game/ui/text.h" #include "mods/skillslots.h" +#include "mods/wasd.h" #include "re/hook.h" #include "re/log.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. void __fastcall UiTaskbar_RenderArtSlots(void* self, void* edx, void* renderer); RE_HOOK(0x006e31e0, UiTaskbar_RenderArtSlots); diff --git a/src/mods/wasd.cpp b/src/mods/wasd.cpp index 4e091c4..bc1ccb0 100644 --- a/src/mods/wasd.cpp +++ b/src/mods/wasd.cpp @@ -15,6 +15,7 @@ #include "mods/wasd.h" +#include #include #include @@ -69,19 +70,39 @@ bool IsMovementKey(int vk) { return (vk == 'W' || vk == 'A' || vk == 'S' || vk == 'D'); } -// Called once per engine tick (10 Hz), after the game's own input handling. -void Update(uint8_t* engine) { - // World axes on screen: (-1,-1) up, (1,1) down, (-1,1) left, (1,-1) right. - int dx = 0; - int dy = 0; +namespace { + +// The held movement keys as a screen direction in world axes: (-1,-1) up, +// (1,1) down, (-1,1) left, (1,-1) right; 0,0 when none (or the keys belong to +// 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(engine + kEngineFlags); - const bool mouseMoving = (flags & 0x10) != 0; - if (!mouseMoving && (flags & 0x4000) == 0 && !mods::input::TypingIntoTextField()) { + if ((flags & 0x10) == 0 && (flags & 0x4000) == 0 && !mods::input::TypingIntoTextField()) { if (KeyDown('W')) { dx -= 1; dy -= 1; } if (KeyDown('S')) { dx += 1; dy += 1; } if (KeyDown('A')) { 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(dx * dx + dy * dy)); + const int x = game::CreatureField(creature, game::kCreatureX) + static_cast(dx / length * kTargetDistance); + const int y = game::CreatureField(creature, game::kCreatureY) + static_cast(dy / length * kTargetDistance); + SendWalk(engine, creature, x, y); +} + +std::atomic 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(engine + kEngineFlags) & 0x10) != 0; if (dx == 0 && dy == 0) { if (g_move.moving && !mouseMoving) { if (void* creature = ControlledCreature(engine)) { @@ -95,16 +116,30 @@ void Update(uint8_t* engine) { if (!creature) { return; } - const double length = std::sqrt(static_cast(dx * dx + dy * dy)); - const int x = game::CreatureField(creature, game::kCreatureX) + static_cast(dx / length * kTargetDistance); - const int y = game::CreatureField(creature, game::kCreatureY) + static_cast(dy / length * kTargetDistance); - SendWalk(engine, creature, x, y); + Walk(engine, creature, dx, dy); 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 { struct Registration { - Registration() { game::OnEngineTick(&Update); } + Registration() { + game::OnEngineTick(&Update); + } } g_registration; } // namespace diff --git a/src/mods/wasd.h b/src/mods/wasd.h index d663921..6950aee 100644 --- a/src/mods/wasd.h +++ b/src/mods/wasd.h @@ -5,4 +5,8 @@ namespace mods::wasd { // Whether `vk` is one of the movement keys W/A/S/D. 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