#pragma once // Creature orders (cEvent_creature) and the engine fields around them // (docs/wasd.md, docs/auto-attack.md). #include #include #include "game/engine/objects.h" namespace game { constexpr uint32_t kEventCreatureVtable = 0x0088e95c; // cEngine fields. constexpr size_t kEngineFlags = 0x54; // 0x10 mouse button held (hold-to-move), 0x4000 input blocked constexpr size_t kEngineControlledId = 0x13c; // object id of the controlled creature // Order commands. constexpr uint32_t kOrderWalk = 4; constexpr uint32_t kOrderAttack = 7; // left click on a creature: one attack constexpr uint32_t kOrderUseArt = 0x105; // cEvent_creature (0x44 bytes). The meaning of +0x10..+0x20 depends on the // command: // walk (4): +0x14 2 = ground point at +0x18 / +0x1c (0: stop), // +0x20 1 = re-aim while a mouse button is held // attack (7): +0x18 target object id, +0x1c / +0x20 its position // use art (105): +0x10 target object id (0: ground / self), +0x18 / +0x1c // ground point, +0x30 art id // hit (5): +8 sender, +0xc / +0x10 target, +0x1c attacker id (the // damage code credits it: life leech, ...; aura hits leave // it 0), +0x20 four u16 damage slots (physical, ...), // +0x30 float damage factor struct CreatureOrder { uint32_t vtable = kEventCreatureVtable; uint32_t command = 0; uint32_t unknown08[2] = {}; uint32_t target = 0; // +0x10 uint32_t groundTarget = 0; // +0x14 int32_t a = 0; // +0x18 int32_t b = 0; // +0x1c int32_t c = 0; // +0x20 uint32_t unknown24[3] = {}; uint32_t art = 0; // +0x30 uint32_t unknown34[4] = {}; }; static_assert(sizeof(CreatureOrder) == 0x44); static_assert(offsetof(CreatureOrder, art) == 0x30); // cEngine::SendCreatureOrder (0x6170d0): snaps ground targets, then // creature->vf18 (cCreature::ReceiveOrder 0x52e400). inline void SendCreatureOrder(uint8_t* engine, void* creature, CreatureOrder& order) { using Fn = void(__fastcall*)(void* engine, void* edx, void* creature, void* event); reinterpret_cast(0x006170d0)(engine, nullptr, creature, &order); } inline void* ControlledCreature(uint8_t* engine) { return GetCreature(*reinterpret_cast(engine + kEngineControlledId)); } // Called before cCreature::ReceiveOrder (0x52e400) handles an order (any // creature; `caller` is the code that sent it). Register at startup. using CreatureOrderFn = void (*)(void* creature, const CreatureOrder& order, void* caller); void OnCreatureOrder(CreatureOrderFn callback); // Creature fields. constexpr size_t kCreatureId = 0xc; constexpr size_t kCreatureX = 0x1c; constexpr size_t kCreatureY = 0x20; // +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), // 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). constexpr size_t kCreatureAction = 0x150; enum CreatureAction : int32_t { kActionIdle = 0, kActionWalk = 1, kActionAttack = 3, kActionCastSpell = 4, kActionShoot = 5, kActionDying = 6, kActionCombatMove = 8, }; inline int32_t CurrentAction(const void* creature); // cCreature::IsAlive (0x548ef0): not dying (+0x150 == 6), not dead (+0xfc == // 9), health (+0x4d8) left. inline bool IsAlive(void* creature) { using Fn = char(__fastcall*)(void*, void*); return reinterpret_cast(0x00548ef0)(creature, nullptr) != 0; } // cCreature::IsHostileTo (0x548dd0, (creature)): `other` is alive and its // faction is hostile to this creature's (0x4226f0 / 0x423450). NPCs such as // traders are not hostile (and not allies either). inline bool IsHostileTo(void* creature, void* other) { using Fn = char(__fastcall*)(void*, void*, void*); return other && reinterpret_cast(0x00548dd0)(creature, nullptr, other) != 0; } // cCreature::DistanceTo (0x536f20, (object id)). inline uint32_t DistanceTo(void* creature, uint32_t id) { using Fn = uint32_t(__fastcall*)(void*, void*, uint32_t); return reinterpret_cast(0x00536f20)(creature, nullptr, id); } inline int32_t CreatureField(const void* creature, size_t offset) { return *reinterpret_cast(static_cast(creature) + offset); } inline int32_t CurrentAction(const void* creature) { return CreatureField(creature, kCreatureAction) & 0xffff; } // 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 // (measured: heroes started swings at 117..149). inline int ContactDistance(void* creature, void* other) { using Fn = int(__fastcall*)(void*, void*, void*); return reinterpret_cast(0x004dbaa0)(creature, nullptr, other); } // The creature's weapon class (0x546df0, (0)): from the weapons in its hands // (+0x1d4 / +0x1d8, item type +0x1e), in the order of the motion sets: // 0 bare hands, 1 one-handed, 2 two-handed, 3 two-handed axe, 4 staff, // 5 dagger, 6 throwing, 7 bow, 8 two weapons, 9 / 10 blades, 11 whip, // 12 crossbow, 13 pistol. inline int WeaponClass(void* creature) { using Fn = short(__fastcall*)(void*, void*, int); return reinterpret_cast(0x00546df0)(creature, nullptr, 0); } inline bool HasRangedWeapon(void* creature) { const int c = WeaponClass(creature); return c == 6 || c == 7 || c == 12 || c == 13; } // The nearest living creature hostile to `attacker` within `maxDistance` of // `from` (a creature), other than `except` and other player heroes; one not // in `avoid` if there is any. 0 if none. uint32_t NearestHostile(void* attacker, void* from, double maxDistance, uint32_t except = 0, const uint32_t* avoid = nullptr, size_t avoidCount = 0); } // namespace game