add duration+spellType args to SPELL_START events to make it easier to handle channeling spells for other players

add GetSpellDuration
This commit is contained in:
avitasia
2026-02-17 17:53:06 -08:00
parent ab8c0b9a9f
commit e92c222ac0
9 changed files with 10023 additions and 9940 deletions
+3 -1
View File
@@ -123,7 +123,7 @@ end);
```
### SPELL_START_SELF and SPELL_START_OTHER
Fire when a spell start packet is received. "Self" fires when the active player is the caster, "Other" fires for any other caster. Triggered by the server to notify that a spell with a cast time has begun.
Fire when a spell start packet is received. "Self" fires when the active player is the caster, "Other" fires for any other caster. Triggered by the server to notify that a spell with a cast time has begun. Note: SPELL_START_OTHER also fires for channeling spells cast by other players, as there is no separate SPELL_CHANNEL_START packet for other players — channeling and cast time spells are both bundled into SPELL_START. Use the `spellType` parameter to distinguish channeling spells.
These events are gated behind the `NP_EnableSpellStartEvents` CVar (default 0). Set it to `1` to enable.
@@ -134,6 +134,8 @@ Parameters:
4. string targetGuid - target guid like "0xF5300000000000A5" or "0x0000000000000000" if none
5. int castFlags
6. int castTime - cast time in milliseconds
7. int duration - channel duration in milliseconds (only set for channeling spells, 0 otherwise. See GetSpellDuration if you want it for other spell types)
8. int spellType - 0 = Normal, 1 = Channeling, 2 = Autorepeating
### SPELL_GO_SELF and SPELL_GO_OTHER
Fire when a spell go packet is received. "Self" fires when the active player is the caster, "Other" fires for any other caster. Triggered by the server to indicate a spell completed casting.
+1
View File
@@ -153,6 +153,7 @@ This includes functions for:
- Cooldown tracking (GetSpellIdCooldown, GetItemIdCooldown), including item metadata on cooldown detail tables
- Inventory helpers (GetTrinketCooldown, GetTrinkets, GetAmmo)
- Aura duration tracking and cancel helpers (GetPlayerAuraDuration, CancelPlayerAuraSlot, CancelPlayerAuraSpellId)
- Spell duration lookup (GetSpellDuration) - returns channel duration for channeling spells and the first aura effect duration for non-channeling spells
- Player movement state queries (PlayerIsMoving, PlayerIsRooted, PlayerIsSwimming)
- Talent helpers (LearnTalentRank)
- Spell lookups and utilities
+21
View File
@@ -21,6 +21,7 @@ For custom events, see [EVENTS.md](EVENTS.md). For installation, configuration,
- [GetSpellRecField](#getspellrecfieldspellid-fieldname-copy)
- [GetSpellModifiers](#getspellmodifiersspellid-modifiertype)
- [GetSpellPower](#getspellpowermode)
- [GetSpellDuration](#getspelldurationspellid-ignoremodifiers)
- [GetUnitData](#getunitdataunittoken-copy)
- [GetUnitField](#getunitfieldunittoken-fieldname-copy)
- [GetSpellIdForName](#getspellidfornamenspellname)
@@ -620,6 +621,26 @@ local physical, holy, fire, nature, frost, shadow, arcane = GetSpellPower("posit
local physical, holy, fire, nature, frost, shadow, arcane = GetSpellPower("negative")
```
#### GetSpellDuration(spellId, [ignoreModifiers])
Returns the duration of a spell in milliseconds. For channeling spells this is the channel duration, and for non-channeling spells I think this is always the duration of the first aura effect. By default, the active player's spell modifiers (e.g. talents that extend duration) are applied.
**Parameters:**
- `spellId` (number): The spell ID to look up.
- `ignoreModifiers` (number, optional): Pass `1` to ignore your spell modifiers (talents, etc.) and return the base duration. Defaults to `0` (apply modifiers).
**Returns:**
- `number` - Duration in milliseconds, or `0` if the spell has no duration.
- `nil` - If the spell ID is invalid.
**Examples:**
```lua
-- Get modified duration of a spell
local duration = GetSpellDuration(spellId)
-- Get base duration ignoring talent modifiers
local baseDuration = GetSpellDuration(spellId, 1)
```
#### 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.
+9933 -9935
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -1702,6 +1702,9 @@ namespace Nampower {
char getSpellPower[] = "GetSpellPower";
RegisterLuaFunction(getSpellPower, reinterpret_cast<uintptr_t *>(Script_GetSpellPower));
char getSpellDuration[] = "GetSpellDuration";
RegisterLuaFunction(getSpellDuration, reinterpret_cast<uintptr_t *>(Script_GetSpellDuration));
char learnTalentRank[] = "LearnTalentRank";
RegisterLuaFunction(learnTalentRank, reinterpret_cast<uintptr_t *>(Script_LearnTalentRank));
+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 = 37;
constexpr uint32_t MINOR_VERSION = 38;
constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000;
+31
View File
@@ -650,4 +650,35 @@ namespace Nampower {
return 7;
}
uint32_t Script_GetSpellDuration(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (!lua_isnumber(luaState, 1)) {
lua_error(luaState, "Usage: GetSpellDuration(spellId [, ignoreModifiers])");
return 0;
}
auto const spellId = uint32_t(lua_tonumber(luaState, 1));
auto const spellRec = game::GetSpellInfo(spellId);
if (!spellRec) {
lua_pushnil(luaState);
return 1;
}
bool ignoreModifiers = false;
if (lua_isnumber(luaState, 2)) {
ignoreModifiers = lua_tonumber(luaState, 2) != 0;
}
auto dur = game::GetSpellDuration(spellRec, ignoreModifiers);
if (dur > 0) {
lua_pushnumber(luaState, static_cast<uint32_t>(dur));
} else {
lua_pushnumber(luaState, 0);
}
return 1;
}
}
+2
View File
@@ -34,5 +34,7 @@ namespace Nampower {
uint32_t Script_GetSpellPower(uintptr_t *luaState);
uint32_t Script_GetSpellDuration(uintptr_t *luaState);
uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t *slot, uint32_t *type);
}
+28 -3
View File
@@ -13,6 +13,12 @@ namespace Nampower {
uint32_t lastCastResultTimeMs;
constexpr int MAX_ALLOWED_SPELL_TARGETS = 100;
enum SpellType : uint32_t {
SPELL_TYPE_NORMAL = 0,
SPELL_TYPE_CHANNELING = 1,
SPELL_TYPE_AUTOREPEATING = 2,
};
void SignalEventHook(hadesmem::PatchDetourBase *detour, game::Events eventId) {
auto const signalEvent = detour->GetTrampolineT<SignalEventT>();
@@ -772,11 +778,26 @@ namespace Nampower {
uint64_t targetGuid,
uint16_t castFlags,
uint32_t castTime) {
static char format[] = "%d%d%s%s%d%d";
static char format[] = "%d%d%s%s%d%d%d%d";
char *casterGuidStr = ConvertGuidToString(casterGuid);
char *targetGuidStr = ConvertGuidToString(targetGuid);
uint32_t duration = 0;
SpellType spellType = SPELL_TYPE_NORMAL;
auto spellRec = game::GetSpellInfo(spellId);
if (spellRec) {
if (spellRec->AttributesEx2 & game::SpellAttributesEx2::SPELL_ATTR_EX2_AUTO_REPEAT) {
spellType = SPELL_TYPE_AUTOREPEATING;
} else if (spellRec->AttributesEx & (game::SpellAttributesEx::SPELL_ATTR_EX_IS_CHANNELED | game::SpellAttributesEx::SPELL_ATTR_EX_IS_SELF_CHANNELED)) {
spellType = SPELL_TYPE_CHANNELING;
auto dur = game::GetSpellDuration(spellRec, casterGuid != game::ClntObjMgrGetActivePlayerGuid());
if (dur > 0) {
duration = static_cast<uint32_t>(dur);
}
}
}
auto event = game::SPELL_START_OTHER;
if (casterGuid == game::ClntObjMgrGetActivePlayerGuid()) {
event = game::SPELL_START_SELF;
@@ -789,7 +810,9 @@ namespace Nampower {
char *casterGuidParam,
char *targetGuidParam,
uint32_t castFlagsParam,
uint32_t castTimeParam)) Offsets::SignalEventParam)(
uint32_t castTimeParam,
uint32_t durationParam,
uint32_t spellTypeParam)) Offsets::SignalEventParam)(
event,
format,
itemId,
@@ -797,7 +820,9 @@ namespace Nampower {
casterGuidStr,
targetGuidStr,
castFlags,
castTime);
castTime,
duration,
spellType);
delete[] casterGuidStr;
delete[] targetGuidStr;