add fine grained control of UNIT_* events and new UNIT_*_GUID versions of key events

This commit is contained in:
avitasia
2026-02-20 08:44:08 -08:00
parent 5c50dfa25e
commit cbeafea450
11 changed files with 13552 additions and 12444 deletions
+2
View File
@@ -45,6 +45,8 @@ set(SOURCE_FILES
autoattack.cpp
player_scripts.hpp
player_scripts.cpp
unit.hpp
unit.cpp
)
add_library(${DLL_NAME} SHARED ${SOURCE_FILES})
+73
View File
@@ -1145,6 +1145,63 @@ namespace game {
};
enum Events : std::uint32_t {
UNIT_PET = 0u,
UNIT_PET_02 = 2u,
UNIT_HEALTH = 16u,
UNIT_MANA = 17u,
UNIT_RAGE = 18u,
UNIT_FOCUS = 19u,
UNIT_ENERGY = 20u,
UNIT_HAPPINESS = 21u,
UNIT_MAXHEALTH = 22u,
UNIT_MAXMANA = 23u,
UNIT_MAXRAGE = 24u,
UNIT_MAXFOCUS = 25u,
UNIT_MAXENERGY = 26u,
UNIT_MAXHAPPINESS = 27u,
UNIT_LEVEL = 28u,
UNIT_FACTION = 29u,
UNIT_DISPLAYPOWER = 30u,
UNIT_FLAGS = 40u,
UNIT_AURA = 41u,
UNIT_AURA_02 = 107u,
UNIT_ATTACK_SPEED_01 = 120u,
UNIT_ATTACK_SPEED_02 = 121u,
UNIT_RANGEDDAMAGE_01 = 122u,
UNIT_DAMAGE_01 = 128u,
UNIT_DAMAGE_02 = 129u,
UNIT_DAMAGE_03 = 130u,
UNIT_DAMAGE_04 = 131u,
UNIT_LOYALTY = 132u,
UNIT_PET_EXPERIENCE_01 = 135u,
UNIT_PET_EXPERIENCE_02 = 136u,
UNIT_DYNAMIC_FLAGS = 137u,
UNIT_PET_TRAINING_POINTS = 143u,
UNIT_STATS_01 = 144u,
UNIT_STATS_02 = 145u,
UNIT_STATS_03 = 146u,
UNIT_STATS_04 = 147u,
UNIT_STATS_05 = 148u,
UNIT_RESISTANCES_01 = 149u,
UNIT_RESISTANCES_02 = 150u,
UNIT_RESISTANCES_03 = 151u,
UNIT_RESISTANCES_04 = 152u,
UNIT_RESISTANCES_05 = 153u,
UNIT_RESISTANCES_06 = 154u,
UNIT_RESISTANCES_07 = 155u,
UNIT_ATTACK_POWER_01 = 159u,
UNIT_ATTACK_POWER_02 = 160u,
UNIT_ATTACK_POWER_03 = 161u,
UNIT_RANGED_ATTACK_POWER_01 = 162u,
UNIT_RANGED_ATTACK_POWER_02 = 163u,
UNIT_RANGED_ATTACK_POWER_03 = 164u,
UNIT_RANGEDDAMAGE_02 = 165u,
UNIT_RANGEDDAMAGE_03 = 166u,
UNIT_MANA1 = 167,
UNIT_MANA2 = 174,
UNIT_COMBAT = 182,
UNIT_NAME_UPDATE = 183,
UNIT_PORTRAIT_UPDATE = 184,
UNIT_MODEL_CHANGED = 185,
@@ -1562,6 +1619,22 @@ namespace game {
SPELL_MISS_SELF = 582,
SPELL_MISS_OTHER = 583,
UNIT_COMBAT_GUID = 584,
UNIT_NAME_UPDATE_GUID = 585,
UNIT_PORTRAIT_UPDATE_GUID = 586,
UNIT_MODEL_CHANGED_GUID = 587,
UNIT_INVENTORY_CHANGED_GUID = 588,
UNIT_PET_GUID = 589,
UNIT_HEALTH_GUID = 590,
UNIT_MANA_GUID = 591,
UNIT_RAGE_GUID = 592,
UNIT_ENERGY_GUID = 593,
UNIT_FLAGS_GUID = 594,
UNIT_AURA_GUID = 595,
UNIT_DYNAMIC_FLAGS_GUID = 596,
PLAYER_GUILD_UPDATE_GUID = 597,
};
enum TypeMask {
+99
View File
@@ -45,6 +45,7 @@
#include "auras.hpp"
#include "lua_refs.hpp"
#include "autoattack.hpp"
#include "unit.hpp"
#include <cstdint>
#include <memory>
@@ -157,6 +158,8 @@ namespace Nampower {
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT> > gCSimpleFrame_GetNameDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT> > gCastSpellByNameDetour;
std::unique_ptr<hadesmem::PatchDetour<SendUnitSignalT> > gSendUnitSignalDetour;
std::unique_ptr<hadesmem::PatchDetour<CGGameUI_ShowCombatFeedbackT> > gCGGameUI_ShowCombatFeedbackDetour;
// Flags for one-time initialization
std::once_flag loadFlag;
@@ -681,6 +684,24 @@ namespace Nampower {
} else if (strcmp(cvar, "NP_SpamProtectionEnabled") == 0) {
gUserSettings.spamProtectionEnabled = atoi(value) != 0;
DEBUG_LOG("Set NP_SpamProtectionEnabled to " << gUserSettings.spamProtectionEnabled);
} else if (strcmp(cvar, "NP_EnableUnitEventsPet") == 0) {
gUserSettings.enableUnitEventsPet = atoi(value) != 0;
DEBUG_LOG("Set NP_EnableUnitEventsPet to " << gUserSettings.enableUnitEventsPet);
} else if (strcmp(cvar, "NP_EnableUnitEventsParty") == 0) {
gUserSettings.enableUnitEventsParty = atoi(value) != 0;
DEBUG_LOG("Set NP_EnableUnitEventsParty to " << gUserSettings.enableUnitEventsParty);
} else if (strcmp(cvar, "NP_EnableUnitEventsRaid") == 0) {
gUserSettings.enableUnitEventsRaid = atoi(value) != 0;
DEBUG_LOG("Set NP_EnableUnitEventsRaid to " << gUserSettings.enableUnitEventsRaid);
} else if (strcmp(cvar, "NP_EnableUnitEventsMouseover") == 0) {
gUserSettings.enableUnitEventsMouseover = atoi(value) != 0;
DEBUG_LOG("Set NP_EnableUnitEventsMouseover to " << gUserSettings.enableUnitEventsMouseover);
} else if (strcmp(cvar, "NP_EnableUnitEventsGuid") == 0) {
gUserSettings.enableUnitEventsGuid = atoi(value) != 0;
DEBUG_LOG("Set NP_EnableUnitEventsGuid to " << gUserSettings.enableUnitEventsGuid);
} else if (strcmp(cvar, "NP_EnableUnitEventsGuidFiltering") == 0) {
gUserSettings.enableUnitEventsGuidFiltering = atoi(value) != 0;
DEBUG_LOG("Set NP_EnableUnitEventsGuidFiltering to " << gUserSettings.enableUnitEventsGuidFiltering);
} else if (strcmp(cvar, "NP_EnableAuraCastEvents") == 0) {
gUserSettings.enableAuraCastEvents = atoi(value) != 0;
DEBUG_LOG("Set NP_EnableAuraCastEvents to " << gUserSettings.enableAuraCastEvents);
@@ -1016,6 +1037,30 @@ namespace Nampower {
0, // unk2
0); // unk3
char NP_EnableUnitEventsPet[] = "NP_EnableUnitEventsPet";
CVarRegister(NP_EnableUnitEventsPet, nullptr, 0,
gUserSettings.enableUnitEventsPet ? defaultTrue : defaultFalse, nullptr, 1, 0, 0);
char NP_EnableUnitEventsParty[] = "NP_EnableUnitEventsParty";
CVarRegister(NP_EnableUnitEventsParty, nullptr, 0,
gUserSettings.enableUnitEventsParty ? defaultTrue : defaultFalse, nullptr, 1, 0, 0);
char NP_EnableUnitEventsRaid[] = "NP_EnableUnitEventsRaid";
CVarRegister(NP_EnableUnitEventsRaid, nullptr, 0,
gUserSettings.enableUnitEventsRaid ? defaultTrue : defaultFalse, nullptr, 1, 0, 0);
char NP_EnableUnitEventsMouseover[] = "NP_EnableUnitEventsMouseover";
CVarRegister(NP_EnableUnitEventsMouseover, nullptr, 0,
gUserSettings.enableUnitEventsMouseover ? defaultTrue : defaultFalse, nullptr, 1, 0, 0);
char NP_EnableUnitEventsGuid[] = "NP_EnableUnitEventsGuid";
CVarRegister(NP_EnableUnitEventsGuid, nullptr, 0,
gUserSettings.enableUnitEventsGuid ? defaultTrue : defaultFalse, nullptr, 1, 0, 0);
char NP_EnableUnitEventsGuidFiltering[] = "NP_EnableUnitEventsGuidFiltering";
CVarRegister(NP_EnableUnitEventsGuidFiltering, nullptr, 0,
gUserSettings.enableUnitEventsGuidFiltering ? defaultTrue : defaultFalse, nullptr, 1, 0, 0);
char NP_EnableAuraCastEvents[] = "NP_EnableAuraCastEvents";
CVarRegister(NP_EnableAuraCastEvents, // name
nullptr, // help
@@ -1211,6 +1256,14 @@ namespace Nampower {
loadUserVar("NP_DoubleCastToEndChannelEarly");
loadUserVar("NP_SpamProtectionEnabled");
loadUserVar("NP_EnableUnitEventsPet");
loadUserVar("NP_EnableUnitEventsParty");
loadUserVar("NP_EnableUnitEventsRaid");
loadUserVar("NP_EnableUnitEventsMouseover");
loadUserVar("NP_EnableUnitEventsGuid");
loadUserVar("NP_EnableUnitEventsGuidFiltering");
loadUserVar("NP_EnableAuraCastEvents");
loadUserVar("NP_EnableAutoAttackEvents");
loadUserVar("NP_EnableSpellStartEvents");
@@ -1405,6 +1458,10 @@ namespace Nampower {
&AttackRoundInfo_ReadPacketHook);
gCSimpleFrame_GetNameDetour = createHook<LuaScriptT>(process, Offsets::CSimpleFrame_GetName,
&CSimpleFrame_GetNameHook);
gSendUnitSignalDetour = createHook<SendUnitSignalT>(process, Offsets::SendUnitSignal,
&SendUnitSignalHook);
gCGGameUI_ShowCombatFeedbackDetour = createHook<CGGameUI_ShowCombatFeedbackT>(
process, Offsets::CGGameUI_ShowCombatFeedback, &CGGameUI_ShowCombatFeedbackHook);
gLoadScriptFunctionsDetour = createHook<LoadScriptFunctionsT>(process, Offsets::LoadScriptFunctions,
&LoadScriptFunctionsHook);
gCreateEventsDetour = createHook<FrameScript_CreateEventsT>(process, Offsets::FrameScript_CreateEvents,
@@ -1545,6 +1602,48 @@ namespace Nampower {
char SPELL_MISS_OTHER[] = "SPELL_MISS_OTHER";
addCustomEvent(game::SPELL_MISS_OTHER, SPELL_MISS_OTHER);
char UNIT_COMBAT_GUID[] = "UNIT_COMBAT_GUID";
addCustomEvent(game::UNIT_COMBAT_GUID, UNIT_COMBAT_GUID);
char UNIT_NAME_UPDATE_GUID[] = "UNIT_NAME_UPDATE_GUID";
addCustomEvent(game::UNIT_NAME_UPDATE_GUID, UNIT_NAME_UPDATE_GUID);
char UNIT_PORTRAIT_UPDATE_GUID[] = "UNIT_PORTRAIT_UPDATE_GUID";
addCustomEvent(game::UNIT_PORTRAIT_UPDATE_GUID, UNIT_PORTRAIT_UPDATE_GUID);
char UNIT_MODEL_CHANGED_GUID[] = "UNIT_MODEL_CHANGED_GUID";
addCustomEvent(game::UNIT_MODEL_CHANGED_GUID, UNIT_MODEL_CHANGED_GUID);
char UNIT_INVENTORY_CHANGED_GUID[] = "UNIT_INVENTORY_CHANGED_GUID";
addCustomEvent(game::UNIT_INVENTORY_CHANGED_GUID, UNIT_INVENTORY_CHANGED_GUID);
char UNIT_PET_GUID[] = "UNIT_PET_GUID";
addCustomEvent(game::UNIT_PET_GUID, UNIT_PET_GUID);
char UNIT_HEALTH_GUID[] = "UNIT_HEALTH_GUID";
addCustomEvent(game::UNIT_HEALTH_GUID, UNIT_HEALTH_GUID);
char UNIT_MANA_GUID[] = "UNIT_MANA_GUID";
addCustomEvent(game::UNIT_MANA_GUID, UNIT_MANA_GUID);
char UNIT_RAGE_GUID[] = "UNIT_RAGE_GUID";
addCustomEvent(game::UNIT_RAGE_GUID, UNIT_RAGE_GUID);
char UNIT_ENERGY_GUID[] = "UNIT_ENERGY_GUID";
addCustomEvent(game::UNIT_ENERGY_GUID, UNIT_ENERGY_GUID);
char UNIT_FLAGS_GUID[] = "UNIT_FLAGS_GUID";
addCustomEvent(game::UNIT_FLAGS_GUID, UNIT_FLAGS_GUID);
char UNIT_AURA_GUID[] = "UNIT_AURA_GUID";
addCustomEvent(game::UNIT_AURA_GUID, UNIT_AURA_GUID);
char UNIT_DYNAMIC_FLAGS_GUID[] = "UNIT_DYNAMIC_FLAGS_GUID";
addCustomEvent(game::UNIT_DYNAMIC_FLAGS_GUID, UNIT_DYNAMIC_FLAGS_GUID);
char PLAYER_GUILD_UPDATE_GUID[] = "PLAYER_GUILD_UPDATE_GUID";
addCustomEvent(game::PLAYER_GUILD_UPDATE_GUID, PLAYER_GUILD_UPDATE_GUID);
}
void FrameScript_CreateEventsHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t maxEventId) {
+5 -2
View File
@@ -25,8 +25,8 @@ namespace Nampower {
constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic
constexpr uint32_t MAJOR_VERSION = 2;
constexpr uint32_t MINOR_VERSION = 38;
constexpr uint32_t PATCH_VERSION = 1;
constexpr uint32_t MINOR_VERSION = 39;
constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000;
constexpr int32_t LUA_GLOBALSINDEX = -10001;
@@ -125,6 +125,9 @@ namespace Nampower {
using LuaScriptT = uint32_t (__fastcall *)(uintptr_t *luaState);
using GetGUIDFromNameT = std::uint64_t (__fastcall *)(const char *);
using GetUnitFromNameT = uintptr_t *(__fastcall *)(const char *);
using GetNamesFromGUIDT = char **(__fastcall *)(uint64_t *guid, int *numNamesReturn);
using SignalEventParamSingleStringT = int (__cdecl *)(uint32_t eventCode, const char *format, const char *str);
using SendUnitSignalT = void (__fastcall *)(uint64_t *guid, uint32_t eventCode);
using lua_gettableT = void (__fastcall *)(uintptr_t *luaState, int globalsIndex);
using lua_gettopT = int (__fastcall *)(uintptr_t *);
using lua_typeT = int (__fastcall *)(uintptr_t *, int);
+11
View File
@@ -38,6 +38,8 @@ enum class Offsets : std::uint32_t {
GetObjectPtr = 0x464870,
GetActivePlayer = 0x468550,
GetUnitFromName = 0x00515940,
GetNamesFromGUID = 0x00000000, // TODO: set correct offset
SendUnitSignal = 0x00515e50,
GetDurationObject = 0x00C0D828,
GetCastingTimeIndex = 0x2D,
Language = 0xC0E080,
@@ -89,10 +91,18 @@ enum class Offsets : std::uint32_t {
OnSpriteRightClick = 0x00492820,
CGGameUI_HandleObjectTrackChange = 0x00492890,
CGGameUI_Target = 0X00493540,
CGGameUI_ShowCombatFeedback = 0x00494600,
ScriptTargetUnit = 0X00489A40,
MouseoverGuidLow = 0x00B4E2C8,
MouseoverGuidHigh = 0x00B4E2CC,
NpcGuidLow = 0x00B4E2D0,
NpcGuidHigh = 0x00B4E2D4,
CGGameUI_GetPartyMember = 0X004E81A0,
CGGameUI_GetPartyMemberPet = 0X004E81D0,
CGGameUI_GetRaidMember = 0X00491940,
CGGameUI_GetRaidMemberPet = 0X00491960,
SpellVisualsHandleCastStart = 0X006EC220,
PlaySpellVisualHandler = 0X006E98D0,
@@ -191,6 +201,7 @@ enum class Offsets : std::uint32_t {
SpellVisualsInitialize = 0x006ec0e0,
SysMsgInitialize = 0x0044cd10,
UnitCombatParamFormat = 0x0084333c,
IntIntParamFormat = 0X00843342,
StringIntParamFormat = 0X00847FBC,
StringParamFormat = 0X0082E280,
+8
View File
@@ -29,6 +29,14 @@ struct UserSettings {
bool spamProtectionEnabled = true;
bool enableAuraCastEvents = false;
bool enableUnitEventsPet = true;
bool enableUnitEventsParty = true;
bool enableUnitEventsRaid = true;
bool enableUnitEventsMouseover = true;
bool enableUnitEventsGuid = true;
bool enableUnitEventsGuidFiltering = false;
bool enableAutoAttackEvents = false;
bool enableSpellStartEvents = false;
bool enableSpellGoEvents = false;
+278
View File
@@ -0,0 +1,278 @@
//
// Created by pmacc on 2/19/2026.
//
#include "unit.hpp"
#include "offsets.hpp"
#include "game.hpp"
#include "helper.hpp"
#include "logging.hpp"
namespace Nampower {
UnitNames GetUnitNames(uint64_t *guid) {
UnitNames names;
if (!guid || *guid == 0) return names;
names.guid = *guid;
uint64_t playerGuid = game::ClntObjMgrGetActivePlayerGuid();
if (playerGuid == 0) return names;
uintptr_t *playerObj = game::GetObjectPtr(playerGuid);
if (!playerObj) return names;
// player: compare guid directly against active player guid
if (*guid == playerGuid) {
names.player = true;
}
// pet: playerObj+0x110 is a pointer to the pet GUID; advance past null entry if present
// (mirrors: puVar2 = *(uint**)(uVar1+0x110); if (!*puVar2 && !puVar2[1]) puVar2+=2)
auto *petGuidPtr = *reinterpret_cast<uint64_t **>(reinterpret_cast<uint8_t *>(playerObj) + 0x110);
if (petGuidPtr) {
if (*petGuidPtr == 0) petGuidPtr++; // skip null slot (uint64_t++ == uint32_t+=2)
if (*petGuidPtr != 0 && *guid == *petGuidPtr) {
names.pet = true;
}
}
// target (locked target)
uint64_t targetGuid = *reinterpret_cast<uint64_t *>(Offsets::LockedTargetGuid);
if (*guid == targetGuid) {
names.target = true;
}
// party members (0-4, stored 1-indexed in struct)
auto const GetPartyMember = reinterpret_cast<CGGameUI_GetPartyMemberT>(Offsets::CGGameUI_GetPartyMember);
auto const GetPartyMemberPet = reinterpret_cast<CGGameUI_GetPartyMemberPetT>(
Offsets::CGGameUI_GetPartyMemberPet);
if (GetPartyMember && GetPartyMemberPet) {
for (uint32_t i = 0; i < 4; i++) {
if (*guid == GetPartyMember(i)) {
names.partyIndex = static_cast<int>(i) + 1;
break;
}
if (*guid == GetPartyMemberPet(i)) {
names.partyPetIndex = static_cast<int>(i) + 1;
break;
}
}
}
// raid members (0-39, stored 1-indexed in struct)
auto const GetRaidMember = reinterpret_cast<CGGameUI_GetRaidMemberT>(Offsets::CGGameUI_GetRaidMember);
auto const GetRaidMemberPet = reinterpret_cast<CGGameUI_GetRaidMemberPetT>(Offsets::CGGameUI_GetRaidMemberPet);
if (GetRaidMember && GetRaidMemberPet) {
for (uint32_t i = 0; i < 40; i++) {
if (*guid == GetRaidMember(i)) {
names.raidIndex = static_cast<int>(i) + 1;
break;
}
if (*guid == GetRaidMemberPet(i)) {
names.raidPetIndex = static_cast<int>(i) + 1;
break;
}
}
}
// npc (separate tracked GUID at 0x00B4E2D0)
uint64_t npcGuid = *reinterpret_cast<uint64_t *>(Offsets::NpcGuidLow);
if (npcGuid != 0 && *guid == npcGuid) {
names.npc = true;
}
// mouseover
uint64_t mouseoverGuid = *reinterpret_cast<uint64_t *>(Offsets::MouseoverGuidLow);
if (mouseoverGuid != 0 && *guid == mouseoverGuid) {
names.mouseover = true;
}
return names;
}
// Applies per-category filtering and invokes callback(name) for each passing name.
// Category mapping mirrors the original string-prefix logic:
void TriggerUnitEvents(const UnitNames &names, uint32_t eventCode) {
auto const SignalEventParam = reinterpret_cast<SignalEventParamSingleStringT>(Offsets::SignalEventParam);
auto *format = reinterpret_cast<char *>(Offsets::StringParamFormat);
if (names.player)
SignalEventParam(eventCode, format, UNIT_PLAYER); // always trigger for the current player
if (names.pet)
SignalEventParam(eventCode, format, UNIT_PET); // always trigger for your own pet
if (names.target)
SignalEventParam(eventCode, format, UNIT_TARGET); // always trigger for your target
if (names.npc)
SignalEventParam(eventCode, format, UNIT_NPC); // always trigger for interact npc
if (names.mouseover && gUserSettings.enableUnitEventsMouseover)
SignalEventParam(eventCode, format, UNIT_MOUSEOVER);
if (names.partyIndex > 0 && gUserSettings.enableUnitEventsParty)
SignalEventParam(eventCode, format, UNIT_PARTY[names.partyIndex - 1]);
if (names.partyPetIndex > 0 && gUserSettings.enableUnitEventsParty && gUserSettings.enableUnitEventsPet)
SignalEventParam(eventCode, format, UNIT_PARTYPET[names.partyPetIndex - 1]);
if (names.raidIndex > 0 && gUserSettings.enableUnitEventsRaid)
SignalEventParam(eventCode, format, UNIT_RAID[names.raidIndex - 1]);
if (names.raidPetIndex > 0 && gUserSettings.enableUnitEventsRaid && gUserSettings.enableUnitEventsPet)
SignalEventParam(eventCode, format, UNIT_RAIDPET[names.raidPetIndex - 1]);
if (names.guid != 0 && gUserSettings.enableUnitEventsGuid) {
// when filtering is enabled, suppress high-frequency guid events that cause spam in older addons:
// skip eventCodes below UNIT_COMBAT(182), and skip UNIT_NAME_UPDATE(183), UNIT_PORTRAIT_UPDATE(184),
// UNIT_INVENTORY_CHANGED(186), PLAYER_GUILD_UPDATE(345) — these cover UNIT_AURA/UNIT_HEALTH/UNIT_MANA etc.
bool filtered = gUserSettings.enableUnitEventsGuidFiltering &&
(eventCode < 182 || eventCode == 183 || eventCode == 184 ||
eventCode == 186 || eventCode == 345);
if (!filtered) {
char *guidStr = ConvertGuidToString(names.guid);
SignalEventParam(eventCode, format, guidStr);
delete[] guidStr;
}
}
// trigger the appropriate guid event for curated subset of the most useful unit events
if (names.guid != 0) {
uint32_t guidEventCode = 0;
switch (eventCode) {
case game::UNIT_PET:
case game::UNIT_PET_02:
guidEventCode = game::UNIT_PET_GUID;
break;
case game::UNIT_HEALTH:
guidEventCode = game::UNIT_HEALTH_GUID;
break;
case game::UNIT_MANA:
case game::UNIT_MANA1:
case game::UNIT_MANA2:
guidEventCode = game::UNIT_MANA_GUID;
break;
case game::UNIT_RAGE:
guidEventCode = game::UNIT_RAGE_GUID;
break;
case game::UNIT_ENERGY:
guidEventCode = game::UNIT_ENERGY_GUID;
break;
case game::UNIT_FLAGS:
guidEventCode = game::UNIT_FLAGS_GUID;
break;
case game::UNIT_AURA:
case game::UNIT_AURA_02:
guidEventCode = game::UNIT_AURA_GUID;
break;
case game::UNIT_DYNAMIC_FLAGS:
guidEventCode = game::UNIT_DYNAMIC_FLAGS_GUID;
break;
case game::UNIT_NAME_UPDATE:
guidEventCode = game::UNIT_NAME_UPDATE_GUID;
break;
case game::UNIT_PORTRAIT_UPDATE:
guidEventCode = game::UNIT_PORTRAIT_UPDATE_GUID;
break;
case game::UNIT_MODEL_CHANGED:
DEBUG_LOG("UNIT_MODEL_CHANGED hit in switch, guid=0x" << std::hex << names.guid << std::dec);
guidEventCode = game::UNIT_MODEL_CHANGED_GUID;
break;
case game::UNIT_INVENTORY_CHANGED:
guidEventCode = game::UNIT_INVENTORY_CHANGED_GUID;
break;
case game::PLAYER_GUILD_UPDATE:
guidEventCode = game::PLAYER_GUILD_UPDATE_GUID;
break;
default: break;
}
if (guidEventCode != 0) {
DEBUG_LOG(
"FireUnitGuidEvent guidEventCode=" << guidEventCode << " guid=0x" << std::hex << names.guid << std::
dec);
FireUnitGuidEvent(names, guidEventCode);
}
}
}
void FireUnitGuidEvent(const UnitNames &names, uint32_t guidEventCode) {
static constexpr char kGuidFormat[] = "%s%d%d%d%d%d%d";
auto const SignalGuidEvent = reinterpret_cast<SignalEventParamGuidT>(Offsets::SignalEventParam);
char *guidStr = ConvertGuidToString(names.guid);
SignalGuidEvent(guidEventCode, kGuidFormat,
guidStr,
names.player ? 1 : 0,
names.target ? 1 : 0,
names.mouseover ? 1 : 0,
names.pet ? 1 : 0,
names.partyIndex,
names.raidIndex);
delete[] guidStr;
}
void SendUnitSignalHook(hadesmem::PatchDetourBase *detour, uint64_t *guid, uint32_t eventCode) {
if (!guid || *guid == 0) return;
DEBUG_LOG("SendUnitSignal eventCode=" << eventCode << " guid=0x" << std::hex << *guid << std::dec);
auto names = GetUnitNames(guid);
TriggerUnitEvents(names, eventCode);
}
void CGGameUI_ShowCombatFeedbackHook(hadesmem::PatchDetourBase *detour, uint64_t *guid,
const char *action, int damage, int school, int hitInfo) {
if (!guid || *guid == 0) return;
auto names = GetUnitNames(guid);
// derive critIndicator string from hitInfo bitfield (mirrors WoW internal logic)
auto lo = static_cast<uint8_t>(hitInfo);
auto hi = static_cast<uint8_t>(hitInfo >> 8);
const char *crit = "";
if (damage > 0) {
if (lo & 0x80) crit = "CRITICAL";
else if (hi & 0x40) crit = "GLANCING";
else if (hi & 0x80) crit = "CRUSHING";
} else {
if (lo & 0x20) crit = "ABSORB";
else if (hi & 0x08) crit = "BLOCK";
else if (lo & 0x40) crit = "RESIST";
}
auto const SignalEventParam = reinterpret_cast<SignalEventParamUnitCombatT>(Offsets::SignalEventParam);
auto *format = reinterpret_cast<const char *>(Offsets::UnitCombatParamFormat);
if (names.player)
SignalEventParam(game::UNIT_COMBAT, format, UNIT_PLAYER, action, crit, damage, school);
if (names.pet && gUserSettings.enableUnitEventsPet)
SignalEventParam(game::UNIT_COMBAT, format, UNIT_PET, action, crit, damage, school);
if (names.target)
SignalEventParam(game::UNIT_COMBAT, format, UNIT_TARGET, action, crit, damage, school);
if (names.npc)
SignalEventParam(game::UNIT_COMBAT, format, UNIT_NPC, action, crit, damage, school);
if (names.mouseover && gUserSettings.enableUnitEventsMouseover)
SignalEventParam(game::UNIT_COMBAT, format, UNIT_MOUSEOVER, action, crit, damage, school);
if (names.partyIndex > 0 && gUserSettings.enableUnitEventsParty)
SignalEventParam(game::UNIT_COMBAT, format, UNIT_PARTY[names.partyIndex - 1], action, crit, damage, school);
if (names.partyPetIndex > 0 && gUserSettings.enableUnitEventsPet)
SignalEventParam(game::UNIT_COMBAT, format, UNIT_PARTYPET[names.partyPetIndex - 1], action, crit, damage,
school);
if (names.raidIndex > 0 && gUserSettings.enableUnitEventsRaid)
SignalEventParam(game::UNIT_COMBAT, format, UNIT_RAID[names.raidIndex - 1], action, crit, damage, school);
if (names.raidPetIndex > 0 && gUserSettings.enableUnitEventsRaid)
SignalEventParam(game::UNIT_COMBAT, format, UNIT_RAIDPET[names.raidPetIndex - 1], action, crit, damage,
school);
if (names.guid != 0) {
static constexpr char kCombatGuidFormat[] = "%s%s%d%d%d%d%d%d";
auto const SignalCombatGuid = reinterpret_cast<SignalEventParamUnitCombatGuidT>(Offsets::SignalEventParam);
char *guidStr = ConvertGuidToString(names.guid);
SignalCombatGuid(game::UNIT_COMBAT_GUID, kCombatGuidFormat,
guidStr,
action,
damage,
school,
hitInfo,
names.pet ? 1 : 0,
names.partyIndex,
names.raidIndex);
delete[] guidStr;
}
}
}
+78
View File
@@ -0,0 +1,78 @@
//
// Created by pmacc on 2/19/2026.
//
#pragma once
#include "main.hpp"
namespace Nampower {
constexpr const char *UNIT_PLAYER = "player";
constexpr const char *UNIT_PET = "pet";
constexpr const char *UNIT_TARGET = "target";
constexpr const char *UNIT_NPC = "npc";
constexpr const char *UNIT_MOUSEOVER = "mouseover";
constexpr const char *UNIT_PARTY[4] = {
"party1", "party2", "party3", "party4"
};
constexpr const char *UNIT_PARTYPET[4] = {
"partypet1", "partypet2", "partypet3", "partypet4"
};
constexpr const char *UNIT_RAID[40] = {
"raid1", "raid2", "raid3", "raid4", "raid5",
"raid6", "raid7", "raid8", "raid9", "raid10",
"raid11", "raid12", "raid13", "raid14", "raid15",
"raid16", "raid17", "raid18", "raid19", "raid20",
"raid21", "raid22", "raid23", "raid24", "raid25",
"raid26", "raid27", "raid28", "raid29", "raid30",
"raid31", "raid32", "raid33", "raid34", "raid35",
"raid36", "raid37", "raid38", "raid39", "raid40"
};
constexpr const char *UNIT_RAIDPET[40] = {
"raidpet1", "raidpet2", "raidpet3", "raidpet4", "raidpet5",
"raidpet6", "raidpet7", "raidpet8", "raidpet9", "raidpet10",
"raidpet11", "raidpet12", "raidpet13", "raidpet14", "raidpet15",
"raidpet16", "raidpet17", "raidpet18", "raidpet19", "raidpet20",
"raidpet21", "raidpet22", "raidpet23", "raidpet24", "raidpet25",
"raidpet26", "raidpet27", "raidpet28", "raidpet29", "raidpet30",
"raidpet31", "raidpet32", "raidpet33", "raidpet34", "raidpet35",
"raidpet36", "raidpet37", "raidpet38", "raidpet39", "raidpet40"
};
struct UnitNames {
uint64_t guid = 0;
bool player = false;
bool pet = false;
bool target = false;
bool npc = false;
bool mouseover = false;
int partyIndex = 0; // 1-4 (party has no 5th slot), 0 = none
int partyPetIndex = 0; // 1-4, 0 = none
int raidIndex = 0; // 1-40, 0 = none
int raidPetIndex = 0; // 1-40, 0 = none
bool any() const {
return player || pet || target || npc || mouseover ||
partyIndex > 0 || partyPetIndex > 0 ||
raidIndex > 0 || raidPetIndex > 0;
}
};
using CGGameUI_GetPartyMemberT = uint64_t (__fastcall *)(uint32_t index);
using CGGameUI_GetPartyMemberPetT = uint64_t (__fastcall *)(uint32_t index);
using CGGameUI_GetRaidMemberT = uint64_t (__fastcall *)(uint32_t index);
using CGGameUI_GetRaidMemberPetT = uint64_t (__fastcall *)(uint32_t index);
using CGGameUI_ShowCombatFeedbackT = void (__fastcall *)(uint64_t *guid, const char *action, int damage, int school, int hitInfo);
using SignalEventParamUnitCombatT = int (__cdecl *)(uint32_t eventCode, const char *format, const char *unit, const char *action, const char *critIndicator, int damage, int school);
using SignalEventParamGuidT = int (__cdecl *)(uint32_t eventCode, const char *format, const char *guid, int isPlayer, int isTarget, int isMouseover, int isPet, int partyIndex, int raidIndex);
using SignalEventParamUnitCombatGuidT = int (__cdecl *)(uint32_t eventCode, const char *format, const char *guid, const char *action, int damage, int school, int hitInfo, int isPet, int partyIndex, int raidIndex);
UnitNames GetUnitNames(uint64_t *guid);
void FireUnitGuidEvent(const UnitNames &names, uint32_t guidEventCode);
void TriggerUnitEvents(const UnitNames &names, uint32_t eventCode);
void SendUnitSignalHook(hadesmem::PatchDetourBase *detour, uint64_t *guid, uint32_t eventCode);
void CGGameUI_ShowCombatFeedbackHook(hadesmem::PatchDetourBase *detour, uint64_t *guid, const char *action, int damage, int school, int hitInfo);
}