add GetSpellPower fn and SPELL_MISS events

This commit is contained in:
avitasia
2026-02-09 21:09:48 -08:00
parent 5ee9e492a4
commit d61c9ce0ed
11 changed files with 11963 additions and 11649 deletions
+69
View File
@@ -4,6 +4,28 @@ This document describes all custom events added by Nampower that you can registe
For custom Lua functions, see [SCRIPTS.md](SCRIPTS.md). For general usage information, see [README.md](README.md).
> **Note on examples:** The examples in this document use the **Ace2/Ace3 event format** where event parameters are passed directly as function arguments. In vanilla WoW's native event system, parameters are instead available as global variables `arg1`, `arg2`, `arg3`, etc.
>
> **Ace format (used in examples):**
> ```lua
> frame:RegisterEvent("SPELL_MISS_SELF", function(casterGuid, targetGuid, spellId, missInfo)
> -- args passed directly
> end)
> ```
>
> **Vanilla native format:**
> ```lua
> local frame = CreateFrame("Frame")
> frame:RegisterEvent("SPELL_MISS_SELF")
> frame:SetScript("OnEvent", function()
> -- use arg1, arg2, arg3, arg4 instead
> local casterGuid = arg1
> local targetGuid = arg2
> local spellId = arg3
> local missInfo = arg4
> end)
> ```
## Table of Contents
- [SPELL_QUEUE_EVENT](#spell_queue_event)
- [SPELL_CAST_EVENT](#spell_cast_event)
@@ -19,6 +41,7 @@ For custom Lua functions, see [SCRIPTS.md](SCRIPTS.md). For general usage inform
- [AUTO_ATTACK_SELF and AUTO_ATTACK_OTHER](#auto_attack_self-and-auto_attack_other)
- [SPELL_HEAL_BY_SELF, SPELL_HEAL_BY_OTHER, and SPELL_HEAL_ON_SELF](#spell_heal_by_self-spell_heal_by_other-and-spell_heal_on_self)
- [SPELL_ENERGIZE_BY_SELF, SPELL_ENERGIZE_BY_OTHER, and SPELL_ENERGIZE_ON_SELF](#spell_energize_by_self-spell_energize_by_other-and-spell_energize_on_self)
- [SPELL_MISS_SELF and SPELL_MISS_OTHER](#spell_miss_self-and-spell_miss_other)
- [UNIT_DIED](#unit_died)
---
@@ -482,6 +505,52 @@ frame:RegisterEvent("SPELL_ENERGIZE_BY_OTHER", onEnergize) -- Power others rest
frame:RegisterEvent("SPELL_ENERGIZE_ON_SELF", onEnergize) -- Power you receive
```
### SPELL_MISS_SELF and SPELL_MISS_OTHER
Fire when a spell miss or resist occurs. `SPELL_MISS_SELF` fires when the active player is the caster, `SPELL_MISS_OTHER` fires when someone else is the caster.
These events are triggered by `SMSG_SPELL_GO` (missed targets in spell go), `SMSG_SPELLLOGMISS` (miss, dodge, parry, etc.), `SMSG_PROCRESIST` (resist on proc effects), and `SMSG_SPELLORDAMAGE_IMMUNE` (immune).
Parameters:
1. string casterGuid - guid of the caster like "0xF5300000000000A5"
2. string targetGuid - guid of the target like "0xF5300000000000A5"
3. int spellId - the spell ID that missed
4. int missInfo - the type of miss (see SpellMissInfo below)
#### SpellMissInfo
```
SPELL_MISS_NONE = 0 -- No miss (shouldn't normally appear)
SPELL_MISS_MISS = 1 -- Miss
SPELL_MISS_RESIST = 2 -- Resist (also used for SMSG_PROCRESIST)
SPELL_MISS_DODGE = 3 -- Dodge
SPELL_MISS_PARRY = 4 -- Parry
SPELL_MISS_BLOCK = 5 -- Block
SPELL_MISS_EVADE = 6 -- Evade
SPELL_MISS_IMMUNE = 7 -- Immune
SPELL_MISS_IMMUNE2 = 8 -- Immune (variant)
SPELL_MISS_DEFLECT = 9 -- Deflect
SPELL_MISS_ABSORB = 10 -- Absorb
SPELL_MISS_REFLECT = 11 -- Reflect
```
Example:
```lua
local MISS_NAMES = {
[0] = "None", [1] = "Miss", [2] = "Resist", [3] = "Dodge",
[4] = "Parry", [5] = "Block", [6] = "Evade", [7] = "Immune",
[8] = "Immune", [9] = "Deflect", [10] = "Absorb", [11] = "Reflect",
}
local function onSpellMiss(casterGuid, targetGuid, spellId, missInfo)
local missName = MISS_NAMES[missInfo] or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(string.format(
"Spell %d on %s: %s", spellId, targetGuid, missName
))
end
frame:RegisterEvent("SPELL_MISS_SELF", onSpellMiss) -- Your spells that missed
frame:RegisterEvent("SPELL_MISS_OTHER", onSpellMiss) -- Others' spells that missed
```
### UNIT_DIED
Fires when a unit death is recorded in the combat log.
+31 -2
View File
@@ -1,8 +1,8 @@
# Nampower Custom Lua Functions
This document describes all custom Lua functions and events added by Nampower.
This document describes all custom Lua functions added by Nampower.
For installation, configuration, and general usage information, see the main [README.md](README.md).
For custom events, see [EVENTS.md](EVENTS.md). For installation, configuration, and general usage information, see the main [README.md](README.md).
## Table of Contents
- [Performance Optimization - Table References](#performance-optimization---table-references)
@@ -20,6 +20,7 @@ For installation, configuration, and general usage information, see the main [RE
- [GetSpellRec](#getspellrecspellid-copy)
- [GetSpellRecField](#getspellrecfieldspellid-fieldname-copy)
- [GetSpellModifiers](#getspellmodifiersspellid-modifiertype)
- [GetSpellPower](#getspellpowermode)
- [GetUnitData](#getunitdataunittoken-copy)
- [GetUnitField](#getunitfieldunittoken-fieldname-copy)
- [GetSpellIdForName](#getspellidfornamenspellname)
@@ -587,6 +588,34 @@ print("Flat damage bonus: " .. flatMod)
print("Percent damage bonus: " .. percentMod .. "%")
```
#### GetSpellPower([mode])
Returns the player's mod damage done values for all 7 spell schools from the player unit fields (`PLAYER_FIELD_MOD_DAMAGE_DONE_POS` / `PLAYER_FIELD_MOD_DAMAGE_DONE_NEG`).
**Parameters:**
- `mode` (string, optional): Which values to return. Defaults to `"net"`.
- `"net"` (default) - Returns positive minus negative for each school
- `"positive"` - Returns only the positive mod damage done values
- `"negative"` - Returns only the negative mod damage done values
**Returns:**
7 values in order: Physical, Holy, Fire, Nature, Frost, Shadow, Arcane
Returns `nil` if the player unit or player fields cannot be accessed.
**Examples:**
```lua
-- Get net spell power (default)
local physical, holy, fire, nature, frost, shadow, arcane = GetSpellPower()
print("Shadow spell power: " .. shadow)
print("Fire spell power: " .. fire)
-- Get only positive values
local physical, holy, fire, nature, frost, shadow, arcane = GetSpellPower("positive")
-- Get only negative values
local physical, holy, fire, nature, frost, shadow, arcane = GetSpellPower("negative")
```
#### GetUnitData(unitToken, [copy])
Returns a Lua table reference containing all unit fields for the specified unit. This provides access to low-level unit data like health, mana, stats, auras, resistances, and more.
+11668 -11644
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -1559,6 +1559,9 @@ namespace game {
BUFF_UPDATE_DURATION_SELF = 580,
DEBUFF_UPDATE_DURATION_SELF = 581,
SPELL_MISS_SELF = 582,
SPELL_MISS_OTHER = 583,
};
enum TypeMask {
+19
View File
@@ -133,6 +133,9 @@ namespace Nampower {
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gPlaySpellVisualHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gSpellHealingLogHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gSpellEnergizeLogHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gProcResistHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gSpellLogMissHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gSpellOrDamageImmuneHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<FastCallPacketHandlerT> > gSpellStartHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<FastCallPacketHandlerT> > gPeriodicAuraLogHandlerDetour;
@@ -1352,6 +1355,12 @@ namespace Nampower {
&SpellHealingLogHandlerHook);
gSpellEnergizeLogHandlerDetour = createHook<PacketHandlerT>(process, Offsets::SpellEnergizeLogHandler,
&SpellEnergizeLogHandlerHook);
gProcResistHandlerDetour = createHook<PacketHandlerT>(process, Offsets::ProcResistHandler,
&ProcResistHandlerHook);
gSpellLogMissHandlerDetour = createHook<PacketHandlerT>(process, Offsets::SpellLogMissHandler,
&SpellLogMissHandlerHook);
gSpellOrDamageImmuneHandlerDetour = createHook<PacketHandlerT>(process, Offsets::SpellOrDamageImmuneHandler,
&SpellOrDamageImmuneHandlerHook);
gSpellFailedOtherHandlerDetour = createHook<PacketHandlerT>(process, Offsets::SpellFailedOtherHandler,
&SpellFailedOtherHandlerHook);
gSpellFailedDetour = createHook<Spell_C_SpellFailedT>(process, Offsets::Spell_C_SpellFailed,
@@ -1526,6 +1535,12 @@ namespace Nampower {
char DEBUFF_UPDATE_DURATION_SELF[] = "DEBUFF_UPDATE_DURATION_SELF";
addCustomEvent(game::DEBUFF_UPDATE_DURATION_SELF, DEBUFF_UPDATE_DURATION_SELF);
char SPELL_MISS_SELF[] = "SPELL_MISS_SELF";
addCustomEvent(game::SPELL_MISS_SELF, SPELL_MISS_SELF);
char SPELL_MISS_OTHER[] = "SPELL_MISS_OTHER";
addCustomEvent(game::SPELL_MISS_OTHER, SPELL_MISS_OTHER);
}
void FrameScript_CreateEventsHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t maxEventId) {
@@ -1673,6 +1688,10 @@ namespace Nampower {
char getPlayerAuraDuration[] = "GetPlayerAuraDuration";
RegisterLuaFunction(getPlayerAuraDuration, reinterpret_cast<uintptr_t *>(Script_GetPlayerAuraDuration));
char getSpellPower[] = "GetSpellPower";
RegisterLuaFunction(getSpellPower, reinterpret_cast<uintptr_t *>(Script_GetSpellPower));
}
void load() {
+1 -1
View File
@@ -25,7 +25,7 @@ namespace Nampower {
constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic
constexpr uint32_t MAJOR_VERSION = 2;
constexpr uint32_t MINOR_VERSION = 30;
constexpr uint32_t MINOR_VERSION = 31;
constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000;
+3
View File
@@ -105,6 +105,9 @@ enum class Offsets : std::uint32_t {
SpellNonMeleeDmgLogHandler = 0X005E85E0,
SpellHealingLogHandler = 0x005E89C0,
SpellEnergizeLogHandler = 0x005E8A90,
ProcResistHandler = 0x006289A0,
SpellLogMissHandler = 0x005E7E00,
SpellOrDamageImmuneHandler = 0x005E8CA0,
Spell_C_GetCastTime = 0X006E3340,
Spell_C_CastSpell = 0x6E4B60,
+45
View File
@@ -572,5 +572,50 @@ namespace Nampower {
return 0;
}
uint32_t Script_GetSpellPower(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
auto playerUnit = game::GetObjectPtr(playerGuid);
if (!playerUnit) {
lua_pushnil(luaState);
return 1;
}
// Get player fields pointer at byte offset 0xe68
auto playerFields = *reinterpret_cast<uint8_t **>(reinterpret_cast<uint8_t *>(playerUnit) + 0xe68);
if (!playerFields) {
lua_pushnil(luaState);
return 1;
}
// PLAYER_FIELD_MOD_DAMAGE_DONE_POS at offset 0x0FD4, NEG at 0x0FF0 (7 uint32 fields each)
auto modDmgDonePos = reinterpret_cast<uint32_t *>(playerFields + 0x0FD4);
auto modDmgDoneNeg = reinterpret_cast<uint32_t *>(playerFields + 0x0FF0);
// Optional first arg: "net" (default), "positive", or "negative"
const char *mode = "net";
if (lua_isstring(luaState, 1)) {
mode = lua_tostring(luaState, 1);
}
// Return all 7 spell schools: Physical, Holy, Fire, Nature, Frost, Shadow, Arcane
if (_stricmp(mode, "positive") == 0) {
for (int i = 0; i < 7; i++) {
lua_pushnumber(luaState, modDmgDonePos[i]);
}
} else if (_stricmp(mode, "negative") == 0) {
for (int i = 0; i < 7; i++) {
lua_pushnumber(luaState, modDmgDoneNeg[i]);
}
} else {
for (int i = 0; i < 7; i++) {
lua_pushnumber(luaState, static_cast<int32_t>(modDmgDonePos[i]) - static_cast<int32_t>(modDmgDoneNeg[i]));
}
}
return 7;
}
}
+2
View File
@@ -30,5 +30,7 @@ namespace Nampower {
uint32_t Script_GetSpellModifiers(uintptr_t *luaState);
uint32_t Script_GetSpellPower(uintptr_t *luaState);
uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t *slot, uint32_t *type);
}
+116 -2
View File
@@ -844,6 +844,37 @@ namespace Nampower {
delete[] targetGuidStr;
}
void TriggerSpellMissEvent(uint64_t casterGuid,
uint64_t targetGuid,
uint32_t spellId,
uint32_t missInfo) {
static char format[] = "%s%s%d%d";
char *casterGuidStr = ConvertGuidToString(casterGuid);
char *targetGuidStr = ConvertGuidToString(targetGuid);
auto event = game::SPELL_MISS_OTHER;
if (casterGuid == game::ClntObjMgrGetActivePlayerGuid()) {
event = game::SPELL_MISS_SELF;
}
((int (__cdecl *)(int eventCode,
char *fmt,
char *casterGuidParam,
char *targetGuidParam,
uint32_t spellIdParam,
uint32_t missInfoParam)) Offsets::SignalEventParam)(
event,
format,
casterGuidStr,
targetGuidStr,
spellId,
missInfo);
delete[] casterGuidStr;
delete[] targetGuidStr;
}
void SpellGoHook(hadesmem::PatchDetourBase *detour, uint64_t *itemGUID, uint64_t *casterGUID,
uint32_t spellId, CDataStore *packet) {
auto const rpos = packet->m_read;
@@ -861,11 +892,13 @@ namespace Nampower {
packet->Get(targetHitGuids[i]);
}
uint64_t casterGuidValue = casterGUID ? *casterGUID : 0;
uint8_t numTargetsMissed;
packet->Get(numTargetsMissed);
for (int i = 0; i < numTargetsMissed; ++i) {
uint64_t missedTargetGuid;
packet->Get(missedTargetGuid); // not worth storing missed targets
packet->Get(missedTargetGuid);
uint8_t missCondition;
packet->Get(missCondition);
@@ -874,6 +907,8 @@ namespace Nampower {
uint8_t reflectResult;
packet->Get(reflectResult);
}
TriggerSpellMissEvent(casterGuidValue, missedTargetGuid, spellId, missCondition);
}
int16_t spellCastTargetMask;
@@ -884,7 +919,6 @@ namespace Nampower {
packet->GetPackedGuid(unitTargetGUID);
uint32_t itemId = 0;
uint64_t casterGuidValue = casterGUID ? *casterGUID : 0;
if (itemGUID && *itemGUID != 0 && *itemGUID != casterGuidValue) {
auto *item = reinterpret_cast<game::CGItem_C *>(game::ClntObjMgrObjectPtr(
game::TYPEMASK_ITEM, *itemGUID));
@@ -1346,4 +1380,84 @@ namespace Nampower {
return spellEnergizeLogHandler(opCode, packet);
}
int ProcResistHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet) {
auto const procResistHandler = detour->GetTrampolineT<PacketHandlerT>();
auto const rpos = packet->m_read;
uint64_t casterGuid;
packet->Get(casterGuid);
uint64_t targetGuid;
packet->Get(targetGuid);
uint32_t spellId;
packet->Get(spellId);
uint8_t isDebug;
packet->Get(isDebug);
packet->m_read = rpos;
// ProcResist is always SPELL_MISS_RESIST (2)
TriggerSpellMissEvent(casterGuid, targetGuid, spellId, 2);
return procResistHandler(opCode, packet);
}
int SpellLogMissHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet) {
auto const spellLogMissHandler = detour->GetTrampolineT<PacketHandlerT>();
auto const rpos = packet->m_read;
uint32_t spellId;
packet->Get(spellId);
uint64_t casterGuid;
packet->Get(casterGuid);
uint8_t unk;
packet->Get(unk);
uint32_t targetCount;
packet->Get(targetCount);
uint64_t targetGuid;
packet->Get(targetGuid);
uint8_t missCondition;
packet->Get(missCondition);
TriggerSpellMissEvent(casterGuid, targetGuid, spellId, missCondition);
packet->m_read = rpos;
return spellLogMissHandler(opCode, packet);
}
int SpellOrDamageImmuneHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet) {
auto const spellOrDamageImmuneHandler = detour->GetTrampolineT<PacketHandlerT>();
auto const rpos = packet->m_read;
uint64_t casterGuid;
packet->Get(casterGuid);
uint64_t targetGuid;
packet->Get(targetGuid);
uint32_t spellId;
packet->Get(spellId);
uint8_t unk;
packet->Get(unk);
packet->m_read = rpos;
// SPELL_MISS_IMMUNE = 7
TriggerSpellMissEvent(casterGuid, targetGuid, spellId, 7);
return spellOrDamageImmuneHandler(opCode, packet);
}
}
+6
View File
@@ -39,4 +39,10 @@ namespace Nampower {
int SpellHealingLogHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet);
int SpellEnergizeLogHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet);
int ProcResistHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet);
int SpellLogMissHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet);
int SpellOrDamageImmuneHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet);
}