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:
1 parent
5338527b49
commit
16c2844400
8 files changed
+275
-60
No files matched your search
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "game/engine/objects.h"
|
||||
#include "re/hook.h"
|
||||
|
||||
namespace game {
|
||||
@@ -33,4 +34,33 @@ void __fastcall Creature_ReceiveOrder(void* self, void* edx, CreatureOrder* orde
|
||||
Creature_ReceiveOrder_original(self, edx, order);
|
||||
}
|
||||
|
||||
uint32_t NearestHostile(void* attacker, void* from, double maxDistance, uint32_t except, const uint32_t* avoid,
|
||||
size_t avoidCount) {
|
||||
uint32_t best = 0;
|
||||
uint32_t bestDistance = 0;
|
||||
bool bestFresh = false;
|
||||
const int count = NearbyCreatureCount();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const uint32_t id = NearbyCreatureId(i);
|
||||
if (id == except || IsPlayerHeroId(id) || !IsHostileTo(attacker, GetCreature(id))) {
|
||||
continue;
|
||||
}
|
||||
const uint32_t distance = DistanceTo(from, id);
|
||||
if (distance > maxDistance) {
|
||||
continue;
|
||||
}
|
||||
bool fresh = true;
|
||||
for (size_t k = 0; k < avoidCount; ++k) {
|
||||
fresh = fresh && avoid[k] != id;
|
||||
}
|
||||
if (best && ((bestFresh && !fresh) || (bestFresh == fresh && distance >= bestDistance))) {
|
||||
continue;
|
||||
}
|
||||
best = id;
|
||||
bestDistance = distance;
|
||||
bestFresh = fresh;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
@@ -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
|
||||
Reference in new issue
Block a user