- 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>
88 lines
4.7 KiB
Markdown
88 lines
4.7 KiB
Markdown
# Auto-attack (`src/mods/autoattack.cpp`)
|
|
|
|
Goal (user, 2026-10-15): once an enemy was targeted, by a left click or by
|
|
casting an art at it, the hero keeps attacking it with the weapon between
|
|
casts. Left click stays the normal attack; right click casts slot 0.
|
|
|
|
## What the game does (order log, 2026-10-15)
|
|
|
|
Orders logged in `cCreature::ReceiveOrder` (`0x52e400`) for the hero, with
|
|
the hero's fields per tick:
|
|
|
|
| Order | Caller | Fields |
|
|
|---|---|---|
|
|
| left click on an enemy: `cmd 7` | `0x617abb` | +0x18 target id, +0x1c / +0x20 its position |
|
|
| art: `cmd 0x105` | `0x6172f9` (`SendCreatureOrder`) | +0x10 target id, +0x18 / +0x1c ground point, +0x28 0x64, +0x2c 1, +0x30 art id |
|
|
| walk: `cmd 4` | `0x6172f9` | see docs/wasd.md |
|
|
| hit by an enemy: `cmd 5` | `0x4ea478` | +0x8 attacker id, +0x10 the hero, +0x2c 1.0f (every ~0.5 s) |
|
|
|
|
One left click is one swing: hero +0x150 goes 0x10000 (idle) -> 0x10003
|
|
(attack) -> 0x20003 -> 0x10000 within about a second; +0xfc 0 -> 4 ->
|
|
0x20004 -> 0 at the start. An art: 0x10001 -> 0x10008 -> 0x20008 -> 0 ->
|
|
0x10000. Low word of +0x150: current action (0 none, 1 art, 3 attack, 6
|
|
dying), high word its phase. `cCreature::IsAlive` (`0x548ef0`): +0xfc != 9,
|
|
+0x150 != 6, health +0x4d8 != 0.
|
|
|
|
Holding the left button on an enemy (measured): one attack order, then +0x150
|
|
stays 0x10003 for as long as the button is held (9 s in the test), no further
|
|
orders. At the end of each swing (after the hit, `0x4e6584`) the attack code
|
|
calls `cMouseCursor::IsButtonHeld` (`0x655ab0`, cursor word bit 8) at
|
|
`0x4e65c6` (attacker in esi, target creature in ebp; measured: the only
|
|
caller answered "held" during a held attack): held -> the next swing starts
|
|
right away, otherwise recovery (+0x152 = 2) and idle. (`0x4f665a`, a similar
|
|
check in another creature update, is not reached by the hero.) Re-sending
|
|
the attack order after the hero went idle took ~1.45 s per swing (0.36 s
|
|
wind-up, 0.64 s attack, 0.43 s recovery).
|
|
|
|
## The mod
|
|
|
|
- An attack order or an art order whose target is hostile
|
|
(`cCreature::IsHostileTo` `0x548dd0`, faction relation) sets the combat
|
|
target. (First version: "not an ally", which included NPCs: clicking a
|
|
trader made auto-attack talk to it again and again, reopening its window
|
|
with new stock.) any walk order (mouse, WASD, stop) clears it, as
|
|
does the target's death.
|
|
- Each engine tick, if the hero is idle (+0x150 low word 0), no mouse button
|
|
is held (engine +0x54 & 0x10), input is not blocked (0x4000) and no skill
|
|
key is held, it sends the attack order a left click sends. After sending it
|
|
waits up to 5 ticks for the swing to start.
|
|
- The end-of-swing button check (`call` at `0x4e65c6`) is redirected: it
|
|
also answers "held" when the hero hits its auto-attack target, the target
|
|
is alive and no skill key is held. Swings then chain like
|
|
with a held button; the tick only restarts the attack after casts.
|
|
- Self / ally arts pointed at an enemy lose their target
|
|
(`src/mods/arttarget.cpp`) and therefore do not start auto-attack.
|
|
|
|
## Next target, and auto-attack after arts (2026-10-16)
|
|
|
|
User requests: when the target dies, go on with the next enemy close by;
|
|
after a melee art always auto-attack, after a ranged art only with a ranged
|
|
weapon.
|
|
|
|
Measured with a temporary log (distance hero → target when the hero started
|
|
the swing or cast, battlemage with a staff):
|
|
|
|
- +0x150 low word: 1 walking (also walking up to a target), 3 weapon attack,
|
|
4 casting a spell. The "1 art" above was the walk up; 8 (seen after it on
|
|
2026-10-15) is presumably a combat move. Names in `game/engine/orders.h`.
|
|
- Weapon swings started at 117..149 after walking up from up to 1382: the
|
|
game's contact distance (`0x4dbaa0`, mean of both body radii by object
|
|
type; heroes 110 with a weapon, 80 without).
|
|
- Spells (Blitzschlag 0x21, art 5) started at once from 647..803: no walk-up.
|
|
Their cast limit, if any, is beyond that.
|
|
|
|
Rules (`autoattack.cpp`):
|
|
|
|
- Close range = `ContactDistance(hero, hero) + 100` (≈ 210) with a melee
|
|
weapon; 600 with a ranged one (TODO: measure a ranged hero). Ranged weapon =
|
|
weapon class (`0x546df0`) 6 throwing, 7 bow, 12 crossbow, 13 pistol.
|
|
- The target died: the nearest living hostile creature within close range
|
|
(`game::NearestHostile`) becomes the target, else auto-attack stops.
|
|
- An art order (not a buff / ally / corpse art) is watched until the hero
|
|
starts it (action 4 or 8). It is melee if the hero walked first, ranged if
|
|
it starts farther away than contact distance + 100, else as remembered for
|
|
that art (default melee). Melee: auto-attack its enemy target, or the
|
|
nearest enemy close by. Ranged: the same with a ranged weapon; with a melee
|
|
weapon only an enemy already in close range (the art's target if it is,
|
|
else the nearest), otherwise auto-attack stops. Until the art starts, no attack orders are sent.
|