WASD: direction movement steers the walk path directly

The keys rewrite the hero's path every cCreature::Update: one segment
along the held direction, the facing set to it, sliding along blocked
cells. Starting from idle runs the game's own Think steps at once
(StartPendingWalk, WalkAction) instead of waiting up to 110 ms, so
weapon switches no longer stop the walk. Stops wait 50 ms after the
keys are released, and a stop in progress is taken back when keys are
held again. Names for movement, stats and text-key functions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-18 03:18:51 +02:00
1 parent d296e2850b
commit 84c50d8c33
6 files changed
+422 -104

No files matched your search

+84
View File
@@ -67,8 +67,55 @@ void OnCreatureOrder(CreatureOrderFn callback);
constexpr size_t kCreatureId = 0xc;
constexpr size_t kCreatureX = 0x1c;
constexpr size_t kCreatureY = 0x20;
// The walk path (measured 2026-10-18): waypoints {x, y} at +0x2ed + i * 8,
// the current segment's start index at +0x36d, the count at +0x371, and the
// step's sub-unit remainder (x, y, 1/1024 units) at +0x37d / +0x381.
// cCreature::Update (0x594110, thiscall (u8), ret 4; about 40 times per second
// on the engine thread) moves the creature its step length along the
// segments into g_creatureNewPosition (0xad2368, {u16, x, y, u8 level}),
// writes the reached point back as the segment start, and commits it with
// cObjectManager::Place (0x5fcd10), which refuses blocked spots (the
// creature then stays where it was). At the last waypoint the walk ends.
constexpr size_t kCreaturePath = 0x2ed;
constexpr size_t kPathPointSize = 8;
constexpr size_t kCreaturePathIndex = 0x36d;
constexpr size_t kCreaturePathCount = 0x371;
// Facing: a float unit vector in world axes ({0.707, -0.707} facing right on
// screen). Walking turns it towards the path gradually (measured 2026-10-18:
// about 300 ms for a reversal, swinging through the side).
constexpr size_t kCreatureFacing = 0x70;
// +0xfc: the pending action (u16, the order received but not yet started;
// 2 a walk), +0xfe its phase, +0x100..+0x13f its parameters (walk target at
// +0x104 / +0x108). cCreature::Think (vf04, 0x537430, thiscall (float),
// ret 4; about every 110 ms per creature) starts it and then runs the current
// action's handler.
constexpr size_t kCreaturePendingAction = 0xfc;
constexpr uint16_t kPendingWalk = 2;
// cCreature::StartPendingWalk (0x4ede70, thiscall (), ret): the Think step
// for a pending walk: unless a status effect (+0x1f4) holds the creature or
// the target is over 2000 units away, the pending parameters become the
// action's (+0x154), action 1, phase 0 unless already walking.
inline void StartPendingWalk(void* creature) {
using Fn = void(__fastcall*)(void*, void*);
reinterpret_cast<Fn>(0x004ede70)(creature, nullptr);
}
// cCreature::WalkAction (0x4f45b0, thiscall (), ret): the Think step of the
// walk action; phase 0 plans the path, sets the facing, starts the walk
// animation and goes to phase 1.
inline void WalkAction(void* creature) {
using Fn = void(__fastcall*)(void*, void*);
reinterpret_cast<Fn>(0x004f45b0)(creature, nullptr);
}
// +0x150: low word the current action, high word its phase; 0x10000 when
// standing idle. Measured on the hero: 1 walking (also up to a target),
// 2 stopping (2026-10-18: the target-less walk order sets the walk's phase
// to 0x64 at 0x52f03a, WalkAction then switches to 2 with the stop
// animation at 0x4f6f8b, idle about 110 ms later at 0x546513),
// 3 weapon attack, 4 casting a spell (2026-10-16), 6 dying, 8 an art seen
// after walking up (2026-10-15, presumably a combat move), 5 a shot with a
// projectile weapon (bow, 2026-10-18).
@@ -76,6 +123,7 @@ constexpr size_t kCreatureAction = 0x150;
enum CreatureAction : int32_t {
kActionIdle = 0,
kActionWalk = 1,
kActionStop = 2,
kActionAttack = 3,
kActionCastSpell = 4,
kActionShoot = 5,
@@ -83,6 +131,8 @@ enum CreatureAction : int32_t {
kActionCombatMove = 8,
};
inline int32_t CurrentAction(const void* creature);
constexpr size_t kCreatureActionPhase = 0x152; // u16
constexpr uint16_t kPhaseStopping = 0x64; // walk: stop requested
// cCreature::IsAlive (0x548ef0): not dying (+0x150 == 6), not dead (+0xfc ==
// 9), health (+0x4d8) left.
@@ -113,6 +163,40 @@ inline int32_t CurrentAction(const void* creature) {
return CreatureField(creature, kCreatureAction) & 0xffff;
}
// A map position as objects keep it (object +0x18): area, x, y, level. In
// world units, or in map cells (world / kMapCellSize, truncated; 0x6225c0
// converts an object's position).
struct MapPosition {
uint16_t area = 0;
int32_t x = 0;
int32_t y = 0;
uint8_t level = 0;
};
static_assert(offsetof(MapPosition, x) == 4 && offsetof(MapPosition, level) == 0xc);
constexpr size_t kObjectPosition = 0x18;
constexpr size_t kObjectLevel = 0x24;
constexpr float kMapCellSize = 53.66563f; // 0x88f318 / 0x890044
// The map cell of world point (x, y) on the creature's area and level.
inline MapPosition CellAt(const void* creature, int x, int y) {
const auto* c = static_cast<const uint8_t*>(creature);
MapPosition cell;
cell.area = *reinterpret_cast<const uint16_t*>(c + kObjectPosition);
cell.x = static_cast<int32_t>(x / kMapCellSize);
cell.y = static_cast<int32_t>(y / kMapCellSize);
cell.level = c[kObjectLevel];
return cell;
}
// cCreature::IsCellWalkable (0x4f7b20, thiscall (cell*), ret 4): whether the
// creature may enter map cell `cell` (map sector lookups and its walk area
// +0x2c; reads only). The step test cCreature::CanStepTo (0x593c50) refuses a
// step into another cell that is not walkable.
inline bool IsCellWalkable(void* creature, MapPosition& cell) {
using Fn = char(__fastcall*)(void*, void*, MapPosition*);
return reinterpret_cast<Fn>(0x004f7b20)(creature, nullptr, &cell) != 0;
}
// The distance at which two creatures touch (0x4dbaa0 on one, (the other)):
// the mean of their body radii by object type (heroes 110 with a weapon, 80
// without; monsters 70..480). A melee attacker swings from about there