Skip to content

Commit b496568

Browse files
committed
Add C_Spell.GetSpellEffectMechanics(spellID)
Returns the spell's three per-effect SpellMechanic ids (Spell.dbc EffectMechanic[3], +0x13C) as a 1-based array, or nil for an invalid spell. Complements GetSpellMechanicByID, which reads only the spell-level Mechanic field (+0x14): vanilla stores some mechanics on an effect instead -- e.g. Rake's bleed is effect-level (spell-level 0, EffectMechanic[2]=15), so GetSpellMechanicByID misses it while this returns {0,15,0}. Add OFF_SPELL_RECORD_EFFECT_MECHANIC offset, API.md section + TOC entry, and README index entry.
1 parent a37c590 commit b496568

4 files changed

Lines changed: 119 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
5858
| [NewItems](docs/API.md#newitems) | `C_NewItems.ClearAll`, `C_NewItems.IsNewItem`, `C_NewItems.RemoveNewItem` |
5959
| [PlayerInfo](docs/API.md#playerinfo) | `C_PlayerInfo.CanUseItem`, `C_PlayerInfo.GetClass`, `C_PlayerInfo.GetName`, `C_PlayerInfo.GetRace`, `C_PlayerInfo.GetSex`, `C_PlayerInfo.GUIDIsCreature`, `C_PlayerInfo.GUIDIsGameObject`, `C_PlayerInfo.GUIDIsPet`, `C_PlayerInfo.GUIDIsPlayer`, `C_PlayerInfo.IsConnected` |
6060
| [Quest](docs/API.md#quest) | `C_QuestLog.GetNumQuestObjectives`, `C_QuestLog.GetQuestDetails`, `C_QuestLog.GetHeaderIndexForQuest`, `C_QuestLog.GetLogIndexForQuestID`, `C_QuestLog.GetQuestIDForLogIndex`, `C_QuestLog.GetTitleForQuestID`, `C_QuestLog.IsOnQuest`, `C_QuestLog.IsQuestDataCachedByID`, `C_QuestLog.IsUnitOnQuest`, `C_QuestLog.RequestLoadQuestByID`, `GetQuestLogLeaderBoardID` |
61-
| [Spell](docs/API.md#spell) | `C_Spell.CancelSpellByID`, `C_Spell.CastAtCursor`, `C_Spell.CastAtUnit`, `C_Spell.CastingInfo`, `C_Spell.ChannelInfo`, `C_Spell.DoesSpellExist`, `C_Spell.GetSchoolString`, `C_Spell.GetSpellCooldown`, `C_Spell.GetSpellDescription`, `C_Spell.GetSpellInfo`, `C_Spell.GetSpellLevelInfo`, `C_Spell.GetSpellLink`, `C_Spell.GetSpellMechanicByID`, `C_Spell.GetSpellName`, `C_Spell.GetSpellPowerCost`, `C_Spell.GetSpellRadius`, `C_Spell.GetSpellReagents`, `C_Spell.GetSpellRequiredTargetLevel`, `C_Spell.GetSpellSubtext`, `C_Spell.GetSpellTexture`, `C_Spell.IsAutoAttackSpell`, `C_Spell.IsCurrentSpell`, `C_Spell.IsRangedAutoAttackSpell`, `C_Spell.IsSelfBuff`, `C_Spell.IsSpellHarmful`, `C_Spell.IsSpellHelpful`, `C_Spell.IsSpellInRange`, `C_Spell.IsSpellPassive`, `C_Spell.IsSpellUsable`, `C_Spell.SpellHasRange`, `C_Spell.UnitCastingInfo`, `C_Spell.UnitChannelInfo`, `CanDualWield`, `CancelSpellByName`, `CastSpellNoToggle`, `GetCraftSpellID`, `GetSpellBonusDamage`, `GetSpellBonusHealing`, `GetSpellInfo`, `GetSpellLink`, `GetSpellRadius`, `GetSpellRequiredTargetLevel`, `GetSpellSchool`, `IsHarmfulSpell`, `IsHelpfulSpell`, `IsPassiveSpell`, `IsPlayerSpell`, `IsSpellKnown`, `IsUsableSpell`, `SpellHasRange` |
61+
| [Spell](docs/API.md#spell) | `C_Spell.CancelSpellByID`, `C_Spell.CastAtCursor`, `C_Spell.CastAtUnit`, `C_Spell.CastingInfo`, `C_Spell.ChannelInfo`, `C_Spell.DoesSpellExist`, `C_Spell.GetSchoolString`, `C_Spell.GetSpellCooldown`, `C_Spell.GetSpellDescription`, `C_Spell.GetSpellEffectMechanics`, `C_Spell.GetSpellInfo`, `C_Spell.GetSpellLevelInfo`, `C_Spell.GetSpellLink`, `C_Spell.GetSpellMechanicByID`, `C_Spell.GetSpellName`, `C_Spell.GetSpellPowerCost`, `C_Spell.GetSpellRadius`, `C_Spell.GetSpellReagents`, `C_Spell.GetSpellRequiredTargetLevel`, `C_Spell.GetSpellSubtext`, `C_Spell.GetSpellTexture`, `C_Spell.IsAutoAttackSpell`, `C_Spell.IsCurrentSpell`, `C_Spell.IsRangedAutoAttackSpell`, `C_Spell.IsSelfBuff`, `C_Spell.IsSpellHarmful`, `C_Spell.IsSpellHelpful`, `C_Spell.IsSpellInRange`, `C_Spell.IsSpellPassive`, `C_Spell.IsSpellUsable`, `C_Spell.SpellHasRange`, `C_Spell.UnitCastingInfo`, `C_Spell.UnitChannelInfo`, `CanDualWield`, `CancelSpellByName`, `CastSpellNoToggle`, `GetCraftSpellID`, `GetSpellBonusDamage`, `GetSpellBonusHealing`, `GetSpellInfo`, `GetSpellLink`, `GetSpellRadius`, `GetSpellRequiredTargetLevel`, `GetSpellSchool`, `IsHarmfulSpell`, `IsHelpfulSpell`, `IsPassiveSpell`, `IsPlayerSpell`, `IsSpellKnown`, `IsUsableSpell`, `SpellHasRange` |
6262
| [SpellBook](docs/API.md#spellbook) | `C_SpellBook.GetCurrentLevelSpells`, `C_SpellBook.GetSkillLineName`, `C_SpellBook.GetSkillLineRank`, `C_SpellBook.GetSpellLevelLearned`, `C_SpellBook.GetSpellSkillLine`, `C_SpellBook.IsAutoAttackSpellBookItem`, `C_SpellBook.IsRangedAutoAttackSpellBookItem`, `FindSpellBookSlotByID` |
6363
| [State](docs/API.md#state) | `CancelShapeshiftForm`, `Dismount`, `GetMirrorTimerInfo`, `GetMirrorTimerProgress`, `GetShapeshiftFormID`, `IsAssistingRitual`, `IsFalling`, `IsInGroup`, `IsInRaid`, `IsLoggedIn`, `IsMounted`, `IsStealthed`, `IsSwimming` |
6464
| [System](docs/API.md#system) | `CopyToClipboard`, `GetPhysicalScreenSize` |

docs/API.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ build instructions.
402402
- [`C_Spell.GetSpellLink(spellID)`](#c_spellgetspelllinkspellid)
403403
- [`C_Spell.GetSpellDescription(spellID)`](#c_spellgetspelldescriptionspellid)
404404
- [`C_Spell.GetSpellMechanicByID(spellID)`](#c_spellgetspellmechanicbyidspellid)
405+
- [`C_Spell.GetSpellEffectMechanics(spellID)`](#c_spellgetspelleffectmechanicsspellid)
405406
- [`C_Spell.GetSpellRadius(spellID)` / `GetSpellRadius(slot, bookType)`](#c_spellgetspellradiusspellid--getspellradiusslot-booktype)
406407
- [`C_Spell.GetSpellPowerCost(spellIdentifier)`](#c_spellgetspellpowercostspellidentifier)
407408
- [`C_Spell.GetSpellReagents(spellID)`](#c_spellgetspellreagentsspellid)
@@ -9639,6 +9640,41 @@ column is exactly what this function returns):
96399640
> `14` (incapacitated) in 1.12 — the `30` ("sapped") value used by some
96409641
> addon tables is a later-expansion addition and has no row here.
96419642

9643+
### `C_Spell.GetSpellEffectMechanics(spellID)`
9644+
9645+
Returns the spell's three per-effect `SpellMechanic` ids (`Spell.dbc`
9646+
`EffectMechanic[3]`) as a 1-based array table, or `nil` for an invalid /
9647+
out-of-range spell ID. Each entry uses the same numbering as
9648+
[`GetSpellMechanicByID`](#c_spellgetspellmechanicbyidspellid) (`0` = that
9649+
effect carries no mechanic).
9650+
9651+
```lua
9652+
C_Spell.GetSpellEffectMechanics(1822) -- Rake → { 0, 15, 0 }
9653+
C_Spell.GetSpellEffectMechanics(703) -- Garrote → { 0, 0, 0 }
9654+
C_Spell.GetSpellEffectMechanics(133) -- Fireball → { 0, 0, 0 }
9655+
```
9656+
9657+
Complements [`GetSpellMechanicByID`](#c_spellgetspellmechanicbyidspellid),
9658+
which reads only the **spell-level** `Mechanic` field (`+0x14`). Vanilla
9659+
frequently stores a spell's mechanic on an **effect** instead — periodic
9660+
damage such as bleeds is the common case. Garrote/Rupture/Rend/Rip tag
9661+
`bleeding` (15) at the spell level, but **Rake** has spell-level `0` and
9662+
`EffectMechanic[2] = 15`, so effect-mechanic-aware callers (e.g. bleed
9663+
classification for immunity tracking) need this array:
9664+
9665+
```lua
9666+
local function IsBleed(spellID)
9667+
if C_Spell.GetSpellMechanicByID(spellID) == 15 then return true end
9668+
local em = C_Spell.GetSpellEffectMechanics(spellID)
9669+
if em then for i = 1, 3 do if em[i] == 15 then return true end end end
9670+
return false
9671+
end
9672+
```
9673+
9674+
Reads `Spell.dbc` directly (`EffectMechanic[3]` at `+0x13C`), so it covers
9675+
every spell the client knows — not just the player's spellbook — with no
9676+
caching or network round-trip.
9677+
96429678
### `C_Spell.GetSpellRadius(spellID)` / `GetSpellRadius(slot, bookType)`
96439679

96449680
Returns `(baseRadius, modifiedRadius)` — the spell's AOE radius in yards,

src/Offsets.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5338,6 +5338,12 @@ enum Offsets {
53385338
// (form ID for shapeshift, channel index for periodic, etc.).
53395339
OFF_SPELL_RECORD_EFFECT_APPLY_AURA_NAME = 0x16C, // int32[3]
53405340
OFF_SPELL_RECORD_EFFECT_MISC_VALUE = 0x1A8, // int32[3]
5341+
// Per-effect SpellMechanic ids (EffectMechanic[3]). One int32[3] (0xC)
5342+
// after EffectBasePoints (0x130); cross-checked against EffectRadiusIndex
5343+
// (0x160) and EffectApplyAuraName (0x16C). Vanilla often stores a spell's
5344+
// mechanic on an effect rather than the spell-level Mechanic (0x14), so
5345+
// bleed / DoT detection needs this array in addition to that field.
5346+
OFF_SPELL_RECORD_EFFECT_MECHANIC = 0x13C, // int32[3]
53415347
SPELL_RECORD_EFFECT_COUNT = 3,
53425348

53435349
// SPELL_EFFECT_* id array (`Effect[3]`) — five arrays before

src/spell/EffectMechanic.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// This file is part of ClassicAPI.
2+
//
3+
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
4+
// of the GNU General Public License as published by the Free Software Foundation, either
5+
// version 3 of the License, or (at your option) any later version.
6+
//
7+
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
8+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9+
// PURPOSE. See the GNU General Public License for more details.
10+
//
11+
// You should have received a copy of the GNU General Public License along with
12+
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
13+
14+
// `C_Spell.GetSpellEffectMechanics(spellID)` -> { m1, m2, m3 }
15+
//
16+
// Returns a spell's three per-effect mechanic ids (Spell.dbc
17+
// `EffectMechanic[3]`) as a 1-based array table, or nil for an invalid /
18+
// out-of-range spell ID. Each entry is a standard WoW SpellMechanic id
19+
// (0 = that effect carries no mechanic); the numbering matches
20+
// C_Spell.GetSpellMechanicByID (1 = Charm, 7 = Root, 12 = Stun,
21+
// 15 = Bleed, 17 = Polymorph, ...).
22+
//
23+
// Complements GetSpellMechanicByID, which reads only the SPELL-level
24+
// `Mechanic` field (+0x14). Vanilla frequently stores the meaningful
25+
// mechanic on an *effect* rather than the spell — periodic damage such
26+
// as bleeds is the common case — so effect-mechanic-aware callers (e.g.
27+
// bleed classification for immunity tracking) need this array too:
28+
//
29+
// local em = C_Spell.GetSpellEffectMechanics(spellID)
30+
// if em then for i = 1, 3 do if em[i] == 15 then --[[ bleed ]] end end end
31+
//
32+
// Reads Spell.dbc directly, so it covers every spell the client knows —
33+
// not just the player's spellbook — with no caching or network round-trip
34+
// (Spell.dbc is resident from boot).
35+
36+
#include "Game.h"
37+
#include "Offsets.h"
38+
#include "spell/Arg.h"
39+
#include "spell/Lookup.h"
40+
41+
#include <cstdint>
42+
43+
namespace Spell::EffectMechanic {
44+
45+
namespace {
46+
47+
int __fastcall Script_GetSpellEffectMechanics(void *L) {
48+
const int spellID = Spell::Arg::ResolveSpellID(L, 1);
49+
const uint8_t *record = Spell::Lookup::RecordForID(spellID);
50+
if (record == nullptr)
51+
return 0; // nil for invalid / out-of-range spell IDs
52+
53+
auto *mechanics = reinterpret_cast<const uint32_t *>(
54+
record + Offsets::OFF_SPELL_RECORD_EFFECT_MECHANIC);
55+
56+
// Fixed 1-based array of all three effects (0 = effect has no mechanic),
57+
// matching the raw EffectMechanic[3] layout so callers can index by slot.
58+
Game::Lua::NewTable(L);
59+
for (int i = 0; i < Offsets::SPELL_RECORD_EFFECT_COUNT; ++i) {
60+
Game::Lua::PushNumber(L, static_cast<double>(i + 1)); // key
61+
Game::Lua::PushNumber(L, static_cast<double>(mechanics[i])); // value
62+
Game::Lua::SetTable(L, -3);
63+
}
64+
return 1;
65+
}
66+
67+
} // namespace
68+
69+
static void RegisterLuaFunctions() {
70+
Game::Lua::RegisterTableFunction("C_Spell", "GetSpellEffectMechanics",
71+
&Script_GetSpellEffectMechanics);
72+
}
73+
74+
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
75+
76+
} // namespace Spell::EffectMechanic

0 commit comments

Comments
 (0)