Auto-attack: bow shots chain at full speed, arts start by their cooldown; hang watchdog
- The shot end's button check (0x4def52) answers "held" for the auto-attack target, like the melee swing end. - An art order counts as started when the art's cooldown rises, so bow arts and arts ordered mid-swing hand over to auto-attack. - re/crash.cpp: a watchdog writes sacred_re_hang.dmp when the engine tick stops for 10 s while the game is in front. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
432cac2ccf
commit
184c7058fd
6 files changed
+125
-13
No files matched your search
+38
-11
@@ -58,6 +58,8 @@ struct CastWatch {
|
||||
uint32_t target = 0; // an enemy, or 0
|
||||
bool walked = false;
|
||||
DWORD since = 0;
|
||||
float cooldown = 0.0f; // the art's remaining cooldown when ordered
|
||||
int32_t action = 0; // the hero's action when ordered (and while it lasts)
|
||||
};
|
||||
CastWatch g_cast;
|
||||
std::unordered_map<uint16_t, bool> g_artRanged;
|
||||
@@ -118,17 +120,24 @@ void WatchCast(void* hero) {
|
||||
g_cast = {};
|
||||
return;
|
||||
}
|
||||
switch (game::CurrentAction(hero)) {
|
||||
case game::kActionWalk:
|
||||
g_cast.walked = true;
|
||||
break;
|
||||
case game::kActionCastSpell:
|
||||
case game::kActionCombatMove:
|
||||
// Started: the art's cooldown began (spells, combat moves and bow arts
|
||||
// alike), or for an art without one, the hero went into a new action
|
||||
// other than walking (arts ordered during an attack or a cast were taken
|
||||
// as started by that one, measured 2026-10-18).
|
||||
const int32_t action = game::CurrentAction(hero);
|
||||
const game::ArtCooldown cooldown = game::GetArtCooldown(hero, g_cast.art);
|
||||
if (cooldown.remaining > g_cast.cooldown) {
|
||||
OnCastStarted(hero);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
return;
|
||||
}
|
||||
g_cast.cooldown = cooldown.remaining; // regenerating meanwhile
|
||||
if (action == game::kActionWalk) {
|
||||
g_cast.walked = true;
|
||||
} else if (action != g_cast.action && action != game::kActionIdle && cooldown.total <= 0.0f) {
|
||||
OnCastStarted(hero);
|
||||
return;
|
||||
}
|
||||
g_cast.action = action;
|
||||
}
|
||||
|
||||
void OnTick(uint8_t* engine) {
|
||||
@@ -233,8 +242,26 @@ char __fastcall AttackHeld(void* cursor, void*) {
|
||||
return target && game::IsAlive(target) ? 1 : 0;
|
||||
}
|
||||
|
||||
// A shot with a projectile weapon (action 5) checks the button the same way
|
||||
// before the next shot (call at 0x4def52; shooter in esi, target creature in
|
||||
// edi): held -> the next shot at a hostile target follows right away
|
||||
// (measured: a held button fired for 2 s without new orders, auto-attack
|
||||
// orders one shot per 0.67 s). The check after it (0x4def89) retargets to the
|
||||
// creature under the cursor and is left alone.
|
||||
constexpr uint32_t kShotEndButtonCheck = 0x004def52;
|
||||
|
||||
__declspec(naked) void ShotContinuesThunk() {
|
||||
__asm {
|
||||
push edi
|
||||
push esi
|
||||
call SwingContinues
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
void Startup() {
|
||||
re::RedirectCall(kSwingEndButtonCheck, kIsButtonHeld, reinterpret_cast<const void*>(&SwingContinuesThunk));
|
||||
re::RedirectCall(kShotEndButtonCheck, kIsButtonHeld, reinterpret_cast<const void*>(&ShotContinuesThunk));
|
||||
re::RedirectCall(kSwingButtonCheck, kIsButtonHeld, reinterpret_cast<const void*>(&AttackHeld));
|
||||
}
|
||||
RE_STARTUP(AutoAttackStartup, Startup);
|
||||
@@ -265,7 +292,8 @@ void OnOrder(void* creature, const game::CreatureOrder& order, void*) {
|
||||
break;
|
||||
}
|
||||
g_cast = {static_cast<uint16_t>(order.art), IsEnemy(hero, order.target) ? order.target : 0u, false,
|
||||
GetTickCount()};
|
||||
GetTickCount(), game::GetArtCooldown(hero, static_cast<uint16_t>(order.art)).remaining,
|
||||
game::CurrentAction(hero)};
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -278,4 +306,3 @@ struct OrderRegistration {
|
||||
} // namespace
|
||||
|
||||
} // namespace mods::autoattack
|
||||
|
||||
Reference in new issue
Block a user