add auto attack events

This commit is contained in:
avitasia
2026-01-11 12:28:31 -08:00
parent d55dc6e693
commit a0c321b192
11 changed files with 12467 additions and 12028 deletions
+86 -2
View File
@@ -10,6 +10,7 @@ For custom Lua functions, see [SCRIPTS.md](SCRIPTS.md). For general usage inform
- [SPELL_DAMAGE_EVENT_SELF and SPELL_DAMAGE_EVENT_OTHER](#spell_damage_event_self-and-spell_damage_event_other)
- [Buff/Debuff Events](#buffdebuff-events)
- [AURA_CAST_ON_SELF and AURA_CAST_ON_OTHER](#aura_cast_on_self-and-aura_cast_on_other)
- [AUTO_ATTACK_SELF and AUTO_ATTACK_OTHER](#auto_attack_self-and-auto_attack_other)
- [UNIT_DIED](#unit_died)
---
@@ -172,9 +173,8 @@ These events are primarily intended for basic tracking of aura applications when
#### Multi-Target Behavior
These events fire separately for each target affected by the spell:
- **Self-targeting spells** (e.g., buffs with TARGET_UNIT_CASTER) trigger once on the caster
- **Single-target spells** (e.g., healing/damage spells) trigger once on the primary target
- **Single-target spells** (e.g., spells that target specific units) trigger once on the primary target
- **AOE spells** (e.g., area buffs/debuffs) trigger once for each affected target based on the spell's implicit targeting rules
- For AOE enemy spells, the event fires on all targets except the caster (if the caster is in the area)
Each event fires once per qualifying spell effect per target. A spell with multiple aura-applying effects will trigger multiple events for the same target.
@@ -191,6 +191,90 @@ Parameters:
8. int durationMs - spell duration in milliseconds (includes client modifiers if you are the caster)
9. int auraCapStatus - bitfield: 1 = buff bar full, 2 = debuff bar full (3 means both)
### AUTO_ATTACK_SELF and AUTO_ATTACK_OTHER
Fire when auto attack damage is processed. "Self" fires when the active player is the target of the attack; "Other" fires when the active player is the attacker or a different unit is the target.
These events are gated behind the `NP_EnableAutoAttackEvents` CVar (default 0). Set it to `1` to enable.
These events provide detailed information about auto attack rounds including damage, hit type (critical, glancing, crushing), victim state (dodged, parried, blocked), and mitigation amounts.
Parameters:
1. string attackerGuid - attacker guid like "0xF5300000000000A5"
2. string targetGuid - target guid like "0xF5300000000000A5"
3. int totalDamage - total damage dealt by the attack
4. int hitInfo - bitfield containing hit flags (see below)
5. int victimState - state of the victim after the attack (see below)
6. int subDamageCount - number of damage components (usually 1, can be up to 3 for main-hand + off-hand + ranged)
7. int blockedAmount - amount of damage blocked
8. int totalAbsorb - total damage absorbed across all sub-damage components
9. int totalResist - total damage resisted across all sub-damage components
#### HitInfo Flags
Bitfield values that can be combined:
```
HITINFO_NORMALSWING = 0 (0x0)
HITINFO_UNK0 = 1 (0x1)
HITINFO_AFFECTS_VICTIM = 2 (0x2)
HITINFO_LEFTSWING = 4 (0x4) -- Off-hand attack
HITINFO_UNK3 = 8 (0x8)
HITINFO_MISS = 16 (0x10)
HITINFO_ABSORB = 32 (0x20)
HITINFO_RESIST = 64 (0x40)
HITINFO_CRITICALHIT = 128 (0x80)
HITINFO_UNK8 = 256 (0x100)
HITINFO_UNK9 = 8192 (0x2000)
HITINFO_GLANCING = 16384 (0x4000)
HITINFO_CRUSHING = 32768 (0x8000)
HITINFO_NOACTION = 65536 (0x10000)
HITINFO_SWINGNOHITSOUND = 524288 (0x80000)
```
#### Victim States
```
VICTIMSTATE_UNAFFECTED = 0 -- Seen with HITINFO_MISS
VICTIMSTATE_NORMAL = 1
VICTIMSTATE_DODGE = 2
VICTIMSTATE_PARRY = 3
VICTIMSTATE_INTERRUPT = 4
VICTIMSTATE_BLOCKS = 5
VICTIMSTATE_EVADES = 6
VICTIMSTATE_IS_IMMUNE = 7
VICTIMSTATE_DEFLECTS = 8
```
Example:
```
local HITINFO_CRITICALHIT = 128 -- 0x80
local HITINFO_GLANCING = 16384 -- 0x4000
local HITINFO_CRUSHING = 32768 -- 0x8000
local function onAutoAttack(attackerGuid, targetGuid, totalDamage, hitInfo, victimState, subDamageCount, blockedAmount, totalAbsorb, totalResist)
local isCrit = bit.band(hitInfo, HITINFO_CRITICALHIT) ~= 0
local isGlancing = bit.band(hitInfo, HITINFO_GLANCING) ~= 0
local isCrushing = bit.band(hitInfo, HITINFO_CRUSHING) ~= 0
local hitType = "Normal"
if isCrit then hitType = "Critical"
elseif isGlancing then hitType = "Glancing"
elseif isCrushing then hitType = "Crushing"
end
local victimStateNames = {
[0] = "Unaffected", [1] = "Normal", [2] = "Dodged", [3] = "Parried",
[4] = "Interrupted", [5] = "Blocked", [6] = "Evaded", [7] = "Immune", [8] = "Deflected"
}
DEFAULT_CHAT_FRAME:AddMessage(string.format(
"%s hit for %d (%s) - State: %s, Absorbed: %d, Resisted: %d, Blocked: %d",
hitType, totalDamage, attackerGuid, victimStateNames[victimState] or "Unknown",
totalAbsorb, totalResist, blockedAmount
))
end
frame:RegisterEvent("AUTO_ATTACK_SELF", onAutoAttack)
frame:RegisterEvent("AUTO_ATTACK_OTHER", onAutoAttack)
```
### UNIT_DIED
Fires when a unit death is recorded in the combat log.
+3
View File
@@ -97,6 +97,8 @@ SET NP_TargetingQueueWindowMs "1000"
- `NP_EnableAuraCastEvents` - Whether to enable AURA_CAST_ON_SELF and AURA_CAST_ON_OTHER events. 0 to disable, 1 to enable. Default is 0.
- `NP_EnableAutoAttackEvents` - Whether to enable AUTO_ATTACK_SELF and AUTO_ATTACK_OTHER events. 0 to disable, 1 to enable. Default is 0.
- `NP_ChannelLatencyReductionPercentage` - The percentage of your latency to subtract from the end of a channel duration to optimize cast time while hopefully not losing any ticks (more explanation below). Default is 75.
- `NP_NameplateDistance` - The distance in yards to display nameplates. Defaults to whatever was set by the game or vanilla tweaks.
@@ -147,6 +149,7 @@ Available events:
- SPELL_DAMAGE_EVENT_SELF and SPELL_DAMAGE_EVENT_OTHER - Combat damage events
- Buff/Debuff Events (BUFF_ADDED_SELF, BUFF_REMOVED_SELF, etc.)
- AURA_CAST_ON_SELF and AURA_CAST_ON_OTHER - Aura application events (fires once per aura effect per target; "Self" fires when the aura lands on the active player, including self-cast with no explicit target; includes aura metadata + amplitude/misc + aura cap bitfield for buff/debuff slots). Handles single-target and multi-target (AOE) spells. Some auras don't have spell effects and won't trigger this; use BUFF/DEBUFF gains for those. Set `NP_EnableAuraCastEvents=1` to enable. See EVENTS.md for details on multi-target behavior and known limitations.
- AUTO_ATTACK_SELF and AUTO_ATTACK_OTHER - Auto attack round events (fires when auto attack hits are processed; includes damage, hit info, victim state, absorb/resist/block amounts). Set `NP_EnableAutoAttackEvents=1` to enable. See EVENTS.md for details.
- UNIT_DIED - Fires when a unit dies
## Bug Reporting
+12171 -12024
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -41,6 +41,8 @@ set(SOURCE_FILES
auras.cpp
lua_refs.hpp
lua_refs.cpp
autoattack.hpp
autoattack.cpp
)
add_library(${DLL_NAME} SHARED ${SOURCE_FILES})
+114
View File
@@ -0,0 +1,114 @@
//
// Created for autoattack hook
//
#include "autoattack.hpp"
#include "helper.hpp"
#include "offsets.hpp"
#include "logging.hpp"
namespace Nampower {
void TriggerAutoAttackEvent(uint64_t attackerGuid, uint64_t targetGuid, uint32_t totalDamage,
uint32_t hitInfo, uint32_t victimState, uint8_t subDamageCount,
uint32_t blockedAmount, uint32_t totalAbsorb, uint32_t totalResist) {
static char format[] = "%s%s%d%d%d%d%d%d%d";
auto const activePlayerGuid = game::ClntObjMgrGetActivePlayerGuid();
auto eventToTrigger = game::AUTO_ATTACK_OTHER;
if (attackerGuid == activePlayerGuid) {
eventToTrigger = game::AUTO_ATTACK_SELF;
}
char *attackerGuidStr = ConvertGuidToString(attackerGuid);
char *targetGuidStr = ConvertGuidToString(targetGuid);
reinterpret_cast<int (__cdecl *)(int eventCode,
char *fmt,
char *attackerGuidParam,
char *targetGuidParam,
uint32_t totalDamageParam,
uint32_t hitInfoParam,
uint32_t victimStateParam,
uint32_t subDamageCountParam,
uint32_t blockedAmountParam,
uint32_t totalAbsorbParam,
uint32_t totalResistParam)>(Offsets::SignalEventParam)(
eventToTrigger,
format,
attackerGuidStr,
targetGuidStr,
totalDamage,
hitInfo,
victimState,
static_cast<uint32_t>(subDamageCount),
blockedAmount,
totalAbsorb,
totalResist);
delete[] attackerGuidStr;
delete[] targetGuidStr;
}
void AttackRoundInfo_ReadPacketHook(hadesmem::PatchDetourBase *detour, void *thisptr, void *dummy_edx,
CDataStore *param_2) {
auto const attackRoundInfoReadPacket = detour->GetTrampolineT<AttackRoundInfo_ReadPacketT>();
if (gUserSettings.enableAutoAttackEvents) {
// Save the read position to restore it later
auto const rpos = param_2->m_read;
// Parse the packet data
uint32_t hitInfo;
param_2->Get(hitInfo);
uint64_t attackerGuid;
param_2->GetPackedGuid(attackerGuid);
uint64_t targetGuid;
param_2->GetPackedGuid(targetGuid);
uint32_t totalDamage;
param_2->Get(totalDamage);
uint8_t subDamageCount;
param_2->Get(subDamageCount);
uint32_t totalAbsorb = 0;
uint32_t totalResist = 0;
// Read sub damage info
SubDamageInfo subDamageArray[MAX_ATTACK];
for (uint8_t i = 0; i < subDamageCount && i < MAX_ATTACK; i++) {
param_2->Get(subDamageArray[i].damageSchool);
param_2->Get(subDamageArray[i].coefficient);
param_2->Get(subDamageArray[i].damage);
param_2->Get(subDamageArray[i].absorb);
param_2->Get(subDamageArray[i].resist);
totalAbsorb += subDamageArray[i].absorb;
totalResist += subDamageArray[i].resist;
}
uint32_t victimState;
param_2->Get(victimState);
uint32_t unused1;
param_2->Get(unused1);
uint32_t unused2;
param_2->Get(unused2);
uint32_t blockedAmount;
param_2->Get(blockedAmount);
// Reset read pointer for the original function
param_2->m_read = rpos;
// Trigger auto attack event
TriggerAutoAttackEvent(attackerGuid, targetGuid, totalDamage, hitInfo, victimState,
subDamageCount, blockedAmount, totalAbsorb, totalResist);
}
// Call original function
attackRoundInfoReadPacket(thisptr, dummy_edx, param_2);
}
}
+28
View File
@@ -0,0 +1,28 @@
//
// Created for autoattack hook
//
#pragma once
#include "game.hpp"
#include <Windows.h>
#include "main.hpp"
#include "cdatastore.hpp"
#define MAX_ATTACK 3
namespace Nampower {
struct SubDamageInfo {
uint32_t damageSchool;
float coefficient;
uint32_t damage;
uint32_t absorb;
int32_t resist;
};
void TriggerAutoAttackEvent(uint64_t attackerGuid, uint64_t targetGuid, uint32_t totalDamage,
uint32_t hitInfo, uint32_t victimState, uint8_t subDamageCount,
uint32_t blockedAmount, uint32_t totalAbsorb, uint32_t totalResist);
void AttackRoundInfo_ReadPacketHook(hadesmem::PatchDetourBase *detour, void *thisptr, void *dummy_edx, CDataStore *param_2);
}
+33
View File
@@ -777,6 +777,36 @@ namespace game {
SPELL_FAILED_TOO_CLOSE = 118
};
enum HitInfo {
HITINFO_NORMALSWING = 0,
HITINFO_UNK0 = 0x1,
HITINFO_AFFECTS_VICTIM = 0x2,
HITINFO_LEFTSWING = 0x4,
HITINFO_UNK3 = 0x8,
HITINFO_MISS = 0x10,
HITINFO_ABSORB = 0x20,
HITINFO_RESIST = 0x40,
HITINFO_CRITICALHIT = 0x80,
HITINFO_UNK8 = 0x100,
HITINFO_UNK9 = 0x2000,
HITINFO_GLANCING = 0x4000,
HITINFO_CRUSHING = 0x8000,
HITINFO_NOACTION = 0x10000,
HITINFO_SWINGNOHITSOUND = 0x80000
};
enum VictimState {
VICTIMSTATE_UNAFFECTED = 0, // seen in relation with HITINFO_MISS
VICTIMSTATE_NORMAL = 1,
VICTIMSTATE_DODGE = 2,
VICTIMSTATE_PARRY = 3,
VICTIMSTATE_INTERRUPT = 4,
VICTIMSTATE_BLOCKS = 5,
VICTIMSTATE_EVADES = 6,
VICTIMSTATE_IS_IMMUNE = 7,
VICTIMSTATE_DEFLECTS = 8
};
enum SpellAttributes {
SPELL_ATTR_UNK0 = 0x1,
SPELL_ATTR_RANGED = 0x2,
@@ -1445,6 +1475,9 @@ namespace game {
AURA_CAST_ON_SELF = 560,
AURA_CAST_ON_OTHER = 561,
AUTO_ATTACK_SELF = 562,
AUTO_ATTACK_OTHER = 563,
};
enum TypeMask {
+24
View File
@@ -43,6 +43,7 @@
#include "item_scripts.hpp"
#include "auras.hpp"
#include "lua_refs.hpp"
#include "autoattack.hpp"
#include <cstdint>
#include <memory>
@@ -136,6 +137,7 @@ namespace Nampower {
std::unique_ptr<hadesmem::PatchDetour<FastCallPacketHandlerT> > gSpellNonMeleeDmgLogHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<CGPlayer_C_OnAttackIconPressedT> > gCGPlayer_C_OnAttackIconPressedDetour;
std::unique_ptr<hadesmem::PatchDetour<AttackRoundInfo_ReadPacketT> > gAttackRoundInfo_ReadPacketDetour;
std::unique_ptr<hadesmem::PatchDetour<CGActionBar_UseActionT> > gCGActionBar_UseActionDetour;
std::unique_ptr<hadesmem::PatchDetour<CGUnit_C_OnAuraRemovedT> > gCGUnit_C_OnAuraRemovedDetour;
std::unique_ptr<hadesmem::PatchDetour<CGUnit_C_OnAuraAddedT> > gCGUnit_C_OnAuraAddedDetour;
@@ -672,6 +674,9 @@ namespace Nampower {
} else if (strcmp(cvar, "NP_EnableAuraCastEvents") == 0) {
gUserSettings.enableAuraCastEvents = atoi(value) != 0;
DEBUG_LOG("Set NP_EnableAuraCastEvents to " << gUserSettings.enableAuraCastEvents);
} else if (strcmp(cvar, "NP_EnableAutoAttackEvents") == 0) {
gUserSettings.enableAutoAttackEvents = atoi(value) != 0;
DEBUG_LOG("Set NP_EnableAutoAttackEvents to " << gUserSettings.enableAutoAttackEvents);
} else if (strcmp(cvar, "NP_PreventMountingWhenBuffCapped") == 0) {
gUserSettings.preventMountingWhenBuffCapped = atoi(value) != 0;
DEBUG_LOG("Set NP_PreventMountingWhenBuffCapped to " << gUserSettings.preventMountingWhenBuffCapped);
@@ -986,6 +991,16 @@ namespace Nampower {
0, // unk2
0); // unk3
char NP_EnableAutoAttackEvents[] = "NP_EnableAutoAttackEvents";
CVarRegister(NP_EnableAutoAttackEvents, // name
nullptr, // help
0, // unk1
gUserSettings.enableAutoAttackEvents ? defaultTrue : defaultFalse, // default value address
nullptr, // callback
1, // category
0, // unk2
0); // unk3
char NP_PreventMountingWhenBuffCapped[] = "NP_PreventMountingWhenBuffCapped";
CVarRegister(NP_PreventMountingWhenBuffCapped, // name
nullptr, // help
@@ -1122,6 +1137,7 @@ namespace Nampower {
loadUserVar("NP_SpamProtectionEnabled");
loadUserVar("NP_EnableAuraCastEvents");
loadUserVar("NP_EnableAutoAttackEvents");
loadUserVar("NP_PreventMountingWhenBuffCapped");
loadUserVar("NP_MinBufferTimeMs");
@@ -1290,6 +1306,8 @@ namespace Nampower {
&CGUnit_C_OnAuraAddedStackHook);
gUnitCombatLogUnitDeadDetour = createHook<UnitCombatLogUnitDeadT>(process, Offsets::UnitCombatLogUnitDead,
&UnitCombatLogUnitDeadHook);
gAttackRoundInfo_ReadPacketDetour = createHook<AttackRoundInfo_ReadPacketT>(process, Offsets::AttackRoundInfo_ReadPacket,
&AttackRoundInfo_ReadPacketHook);
gLoadScriptFunctionsDetour = createHook<LoadScriptFunctionsT>(process, Offsets::LoadScriptFunctions,
&LoadScriptFunctionsHook);
gCreateEventsDetour = createHook<FrameScript_CreateEventsT>(process, Offsets::FrameScript_CreateEvents,
@@ -1364,6 +1382,12 @@ namespace Nampower {
char AURA_CAST_ON_OTHER[] = "AURA_CAST_ON_OTHER";
addCustomEvent(game::AURA_CAST_ON_OTHER, AURA_CAST_ON_OTHER);
char AUTO_ATTACK_SELF[] = "AUTO_ATTACK_SELF";
addCustomEvent(game::AUTO_ATTACK_SELF, AUTO_ATTACK_SELF);
char AUTO_ATTACK_OTHER[] = "AUTO_ATTACK_OTHER";
addCustomEvent(game::AUTO_ATTACK_OTHER, AUTO_ATTACK_OTHER);
}
void FrameScript_CreateEventsHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t maxEventId) {
+3 -2
View File
@@ -35,8 +35,8 @@ namespace Nampower {
constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic
constexpr uint32_t MAJOR_VERSION = 2;
constexpr uint32_t MINOR_VERSION = 23;
constexpr uint32_t PATCH_VERSION = 5;
constexpr uint32_t MINOR_VERSION = 24;
constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000;
constexpr int32_t LUA_GLOBALSINDEX = -10001;
@@ -111,6 +111,7 @@ namespace Nampower {
using Spell_C_GetSpellModifiersT = void (__fastcall *)(const game::SpellRec *spellRec, int *returnVal, game::SpellModOp modOp);
using CGPlayer_C_OnAttackIconPressedT = int (__fastcall *)(uintptr_t *this_ptr, void *dummy_edx, uint64_t guid);
using AttackRoundInfo_ReadPacketT = void (__fastcall *)(void *thisptr, void *dummy_edx, CDataStore *param_2);
using CGActionBar_UseActionT = void (__fastcall *)(uint32_t param_1, int param_2, int param_3);
using CGCharacterInfo_UseItemT = void (__fastcall *)(uintptr_t *this_ptr, void *dummy_edx, uint32_t itemSlot, uint64_t *targetGuid);
+2
View File
@@ -205,6 +205,8 @@ enum class Offsets : std::uint32_t {
CGPlayer_C_OnAttackIconPressed = 0X006131A0,
AttackRoundInfo_ReadPacket = 0x00625c60,
CGCharacterInfo_UseItem = 0x004c7970,
CGItem_C_Use = 0x005D8D00,
+1
View File
@@ -29,6 +29,7 @@ struct UserSettings {
bool spamProtectionEnabled = true;
bool enableAuraCastEvents = false;
bool enableAutoAttackEvents = false;
bool preventMountingWhenBuffCapped = true;
uint32_t spellQueueWindowMs = 500;