Auto-attack: next target, melee/ranged rule after arts; chain range = cast range

- autoattack: when the target dies, the nearest enemy within close range
  (contact distance + a step) takes over; after an art the hero auto-attacks
  if the art was melee (walked up / cast close by), or ranged with a ranged
  weapon, or ranged with an enemy already in melee range; measured action
  codes and weapon classes in game/engine/orders.h
- chainlightning: jumps reach Blitzschlag's cast range (20 ray segments of
  30, the art code's hit check) via the shared NearestHostile search
- guards: burning bone render hook had the wrong signature (device plus a
  4x4 matrix by value, ret 0x44) and corrupted the stack when fire arrows hit
- crash reports follow the ebp chain before the raw stack scan

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-16 09:16:47 +02:00
1 parent 5338527b49
commit 16c2844400
8 files changed
+275 -60

No files matched your search

+46 -2
View File
@@ -67,9 +67,20 @@ void OnCreatureOrder(CreatureOrderFn callback);
constexpr size_t kCreatureId = 0xc;
constexpr size_t kCreatureX = 0x1c;
constexpr size_t kCreatureY = 0x20;
// +0x150: low word the current action (0 none, 1 art, 3 attack, 8 ...), high
// word its phase; 0x10000 when standing idle.
// +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).
constexpr size_t kCreatureAction = 0x150;
enum CreatureAction : int32_t {
kActionIdle = 0,
kActionWalk = 1,
kActionAttack = 3,
kActionCastSpell = 4,
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.
@@ -96,4 +107,37 @@ inline int32_t CreatureField(const void* creature, size_t offset) {
return *reinterpret_cast<const int32_t*>(static_cast<const uint8_t*>(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<Fn>(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<Fn>(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