Skip to content

Commit f559eb4

Browse files
committed
docs(api): move UNIT_SPELLCAST_* events into the Events section, fix TOC link
The TOC entry pointed at #unit_spellcast_-events-player, but the heading generates #unit_spellcast_-events (no -player suffix) — broken link. Move both the TOC entry and the content block out of the Spell section and into the Events section where the other event docs live, and correct the anchor.
1 parent 2de7176 commit f559eb4

1 file changed

Lines changed: 126 additions & 126 deletions

File tree

docs/API.md

Lines changed: 126 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ build instructions.
131131
- [`UNIT_FACTION` event (fire-coverage fix)](#unit_faction-event-fire-coverage-fix)
132132
- [`UPDATE_MOUSEOVER_UNIT` event (loss-fire fix)](#update_mouseover_unit-event-loss-fire-fix)
133133
- [`UPDATE_SHAPESHIFT_FORM` event](#update_shapeshift_form-event)
134+
- [`UNIT_SPELLCAST_*` events](#unit_spellcast_-events)
134135

135136
- [Expansion](#expansion)
136137
- [`GetClassicExpansionLevel()`](#getclassicexpansionlevel)
@@ -461,7 +462,6 @@ build instructions.
461462
- [`C_Spell.CancelSpellByID(spellID)` / `CancelSpellByName(name)`](#c_spellcancelspellbyidspellid--cancelspellbynamename)
462463
- [`C_Spell.UnitCastingInfo(unit)` / `C_Spell.CastingInfo()`](#c_spellunitcastinginfounit--c_spellcastinginfo)
463464
- [`C_Spell.UnitChannelInfo(unit)` / `C_Spell.ChannelInfo()`](#c_spellunitchannelinfounit--c_spellchannelinfo)
464-
- [`UNIT_SPELLCAST_*` events (player)](#unit_spellcast_-events-player)
465465
- [`C_Spell.GetSpellLevelInfo(spellID)`](#c_spellgetspelllevelinfospellid)
466466
- [`GetSpellRequiredTargetLevel(spellID)`](#getspellrequiredtargetlevelspellid)
467467

@@ -3244,6 +3244,131 @@ The cached "last form" sentinel uses `-1` for "player descriptor not
32443244
yet resolvable" so transient resolution failures during early login
32453245
don't get misread as leaving a form.
32463246

3247+
### `UNIT_SPELLCAST_*` events
3248+
3249+
Backport of the TBC+ cast/channel events to 1.12, for the **local player and
3250+
other units**. Ported cast-bar / rotation addons (anything written against
3251+
the modern signature) register these instead of vanilla's arg-less
3252+
`SPELLCAST_*` events and read `unit, castGUID, spellID` directly. Thirteen
3253+
events are provided; six also fire for non-player units:
3254+
3255+
| Event | Fires when | Units | Args |
3256+
|-------|-----------|-------|------|
3257+
| `UNIT_SPELLCAST_SENT` | `CMSG_CAST_SPELL` leaves the client (earliest point) | player | `unit, target, castGUID, spellID, spellName, rank` |
3258+
| `UNIT_SPELLCAST_START` | a cast-time spell begins | all | `unit, castGUID, spellID, spellName, rank` |
3259+
| `UNIT_SPELLCAST_STOP` | a cast-time spell ends (any reason) | all | same |
3260+
| `UNIT_SPELLCAST_DELAYED` | pushback extends the cast | player | same |
3261+
| `UNIT_SPELLCAST_SUCCEEDED` | the spell goes off (`SMSG_SPELL_GO`) — incl. instants | all | same |
3262+
| `UNIT_SPELLCAST_INTERRUPTED` | a started **cast** is interrupted (kick, movement, LoS) — never for channels | all | same |
3263+
| `UNIT_SPELLCAST_FAILED` | a cast is rejected before it starts (range, mana, cooldown) with an error shown | player | same |
3264+
| `UNIT_SPELLCAST_FAILED_QUIET` | a cast fails with **no** error shown (spammy retry, reticle cancel, …) | player | same |
3265+
| `UNIT_SPELLCAST_CHANNEL_START` | a channel begins | all | same |
3266+
| `UNIT_SPELLCAST_CHANNEL_UPDATE` | pushback shortens a channel | player | same |
3267+
| `UNIT_SPELLCAST_CHANNEL_STOP` | a channel ends | all | same |
3268+
| `UNIT_SPELLCAST_RETICLE_TARGET` | a ground-target reticle appears (AoE placement — Blizzard, Flare, …) | player | `unit, "", spellID, spellName, rank` |
3269+
| `UNIT_SPELLCAST_RETICLE_CLEAR` | the reticle is placed or cancelled | player | `unit, "", spellID, spellName, rank` |
3270+
3271+
`unit` (arg1) is the token of the casting unit — `"player"` for your own
3272+
casts, or a unit token (`"target"`, `"focus"`, `"party3"`, `"nameplate2"`,
3273+
`"pet"`, `"mouseover"`, …) for another unit. `spellName` / `rank` are
3274+
ClassicAPI tail extensions (modern stops at `spellID`); addons reading only
3275+
the first three positional args are unaffected.
3276+
3277+
**Per-token fan-out.** A caster GUID can map to several tokens at once (your
3278+
`target` is also `party2` and `nameplate1`). Like retail, the event fires
3279+
**once per token** currently pointing at the caster, so a `target`-frame
3280+
cast bar, a `party2` frame, and a nameplate cast bar each get their own
3281+
event with the same castGUID. Tokens are resolved fresh at fire time (they
3282+
shift frame-to-frame). If you run **SuperWoW**, its raw-GUID token (`"0x…"`)
3283+
is deliberately filtered out — only standard tokens are fanned out.
3284+
3285+
**Non-player limits.** Only the six events above fire for other units, and
3286+
they're **best-effort** — driven purely by the packets an observer receives:
3287+
- `SENT` never fires (only your own outgoing casts are visible).
3288+
- `DELAYED` / `CHANNEL_UPDATE` never fire (pushback is sent only to the
3289+
caster, so another unit's bar can't stretch/shrink from damage).
3290+
- `FAILED` never fires (a pre-cast requirement failure is client-local).
3291+
- A remote cast/channel only has timing from the moment its
3292+
`SMSG_SPELL_START` was observed; casters who were already casting when
3293+
they came into range have no start time.
3294+
3295+
**Channels never fire `INTERRUPTED`.** Retail emits only `CHANNEL_STOP` when a
3296+
channel ends, whether it completed or was cut short (verified against retail),
3297+
so ClassicAPI matches that for both the player and other units. `INTERRUPTED`
3298+
is a cast-only event.
3299+
3300+
**Reticle events** fire for ground-targeted (AoE) spells only, always for the
3301+
player: `RETICLE_TARGET` when the placement reticle comes up, `RETICLE_CLEAR`
3302+
when it's placed or cancelled. There's no cast yet, so the castGUID slot
3303+
(arg2) is empty — retail pushes `nil` there, but the engine's event
3304+
dispatcher can't emit a `nil` mid-argument-list, so ClassicAPI pushes `""`
3305+
instead. `unit` (arg1) and `spellID` (arg3) are exact; arg2 is the only
3306+
difference and is inconsequential for a reticle.
3307+
3308+
**castGUID.** A synthesized string in the modern shape
3309+
`Cast-<type>-<serverID>-<instanceID>-<zoneUID>-<spellID>-<castUID>`. Vanilla
3310+
can't know server / instance / zone, so those three fields are `0`; the
3311+
load-bearing parts are the `spellID` (field 6, which addons `strsplit("-")`
3312+
out) and a unique-per-cast `castUID` (field 7). **Every event of one cast
3313+
carries the same castGUID**, so `SENT` → `START`/`CHANNEL_START` →
3314+
`SUCCEEDED` → `STOP`/`CHANNEL_STOP` all pair up — including across the caster
3315+
and observers (they converge on the same value), and a chained same-spell
3316+
recast gets its own castUID. The `type` and `castUID` follow the
3317+
[spell-cast-GUID spec](https://warcraft.wiki.gg/wiki/GUID#Cast):
3318+
- **Type 3** (real casts — the common case): `castUID` is time-based — the
3319+
low 23 bits are the cast's UNIX-epoch second, the higher bits a per-second
3320+
counter.
3321+
- **Type 2** (`UNIT_SPELLCAST_FAILED` — a local-only cast that never reached
3322+
the server): `castUID` is a plain locally-incrementing integer.
3323+
3324+
**Ordering** matches modern:
3325+
3326+
- Cast-time spell: `SENT → START → SUCCEEDED → STOP`.
3327+
- Channel: `SENT → CHANNEL_START → SUCCEEDED → CHANNEL_STOP` (CHANNEL_START
3328+
before SUCCEEDED, as on retail).
3329+
- Instant: `SENT → SUCCEEDED`.
3330+
3331+
**INTERRUPTED vs FAILED vs FAILED_QUIET** follow modern's split: a spell that
3332+
never started (out of range, not enough mana, on cooldown, LoS to a target)
3333+
fires `FAILED`, except for a fixed whitelist of "quiet" `SpellCastResult`
3334+
codes that fire `FAILED_QUIET` instead — `SPELL_IN_PROGRESS` (casting while
3335+
already casting / a spell-queue rejection), `DONT_REPORT` (fake fails, a
3336+
cancelled ground reticle), and `CHARMED`. That whitelist mirrors the 3.3.5
3337+
client's own unit-spellcast dispatch, mapped to vanilla's `SpellCastResult`
3338+
enum. A spell that was *already casting* and gets stopped (an enemy kick,
3339+
moving to cancel, breaking LoS mid-cast) fires `INTERRUPTED`. Holding
3340+
the cast key while running fires `INTERRUPTED` repeatedly (once per retry),
3341+
each reusing the interrupted cast's castGUID — matching retail.
3342+
3343+
**Channel pushback (player).** Taking damage while channeling shortens the
3344+
channel in vanilla; `CHANNEL_UPDATE` fires on each hit and
3345+
[`C_Spell.UnitChannelInfo`](#c_spellunitchannelinfounit--c_spellchannelinfo)'s
3346+
`endTimeMs` re-anchors to the server's new remaining time, so cast bars
3347+
shrink correctly. (The event carries no time — like retail it's a "re-read
3348+
now" trigger; timing is read back from `UnitChannelInfo`.)
3349+
3350+
Every fire is gated on whether any frame is registered for that event, so
3351+
the whole system costs one pointer-compare per state transition when no
3352+
addon uses it (no arg synthesis, no DBC lookups, no per-token fan-out).
3353+
3354+
```lua
3355+
local f = CreateFrame("Frame")
3356+
for _, e in ipairs({
3357+
"UNIT_SPELLCAST_START", "UNIT_SPELLCAST_STOP",
3358+
"UNIT_SPELLCAST_SUCCEEDED", "UNIT_SPELLCAST_CHANNEL_START",
3359+
}) do f:RegisterEvent(e) end
3360+
f:SetScript("OnEvent", function()
3361+
-- vanilla passes event/arg1/... as globals, not function params
3362+
if arg1 == "target" then print(event, arg3) end -- arg3 = spellID
3363+
end)
3364+
```
3365+
3366+
> **Additive to the vanilla `SPELLCAST_*` events.** The engine's own arg-less
3367+
> `SPELLCAST_START` / `SPELLCAST_CHANNEL_UPDATE` / … still fire as before;
3368+
> these `UNIT_`-prefixed events are the modern layer on top. The empowered-cast
3369+
> events (`UNIT_SPELLCAST_EMPOWER_*`, a Dragonflight addition) are not
3370+
> implemented — vanilla has no empowered casts.
3371+
32473372
## Expansion
32483373

32493374
Helpers shipped by modern Classic Era / Cata Classic for addons that
@@ -11486,131 +11611,6 @@ otherwise it falls back to `name`/`displayName`/`textureID`/`spellID` with
1148611611
**`nil` times**. The player path is unchanged (full timing). Same
1148711612
placeholder fields as `C_Spell.UnitCastingInfo`.
1148811613

11489-
### `UNIT_SPELLCAST_*` events
11490-
11491-
Backport of the TBC+ cast/channel events to 1.12, for the **local player and
11492-
other units**. Ported cast-bar / rotation addons (anything written against
11493-
the modern signature) register these instead of vanilla's arg-less
11494-
`SPELLCAST_*` events and read `unit, castGUID, spellID` directly. Thirteen
11495-
events are provided; six also fire for non-player units:
11496-
11497-
| Event | Fires when | Units | Args |
11498-
|-------|-----------|-------|------|
11499-
| `UNIT_SPELLCAST_SENT` | `CMSG_CAST_SPELL` leaves the client (earliest point) | player | `unit, target, castGUID, spellID, spellName, rank` |
11500-
| `UNIT_SPELLCAST_START` | a cast-time spell begins | all | `unit, castGUID, spellID, spellName, rank` |
11501-
| `UNIT_SPELLCAST_STOP` | a cast-time spell ends (any reason) | all | same |
11502-
| `UNIT_SPELLCAST_DELAYED` | pushback extends the cast | player | same |
11503-
| `UNIT_SPELLCAST_SUCCEEDED` | the spell goes off (`SMSG_SPELL_GO`) — incl. instants | all | same |
11504-
| `UNIT_SPELLCAST_INTERRUPTED` | a started **cast** is interrupted (kick, movement, LoS) — never for channels | all | same |
11505-
| `UNIT_SPELLCAST_FAILED` | a cast is rejected before it starts (range, mana, cooldown) with an error shown | player | same |
11506-
| `UNIT_SPELLCAST_FAILED_QUIET` | a cast fails with **no** error shown (spammy retry, reticle cancel, …) | player | same |
11507-
| `UNIT_SPELLCAST_CHANNEL_START` | a channel begins | all | same |
11508-
| `UNIT_SPELLCAST_CHANNEL_UPDATE` | pushback shortens a channel | player | same |
11509-
| `UNIT_SPELLCAST_CHANNEL_STOP` | a channel ends | all | same |
11510-
| `UNIT_SPELLCAST_RETICLE_TARGET` | a ground-target reticle appears (AoE placement — Blizzard, Flare, …) | player | `unit, "", spellID, spellName, rank` |
11511-
| `UNIT_SPELLCAST_RETICLE_CLEAR` | the reticle is placed or cancelled | player | `unit, "", spellID, spellName, rank` |
11512-
11513-
`unit` (arg1) is the token of the casting unit — `"player"` for your own
11514-
casts, or a unit token (`"target"`, `"focus"`, `"party3"`, `"nameplate2"`,
11515-
`"pet"`, `"mouseover"`, …) for another unit. `spellName` / `rank` are
11516-
ClassicAPI tail extensions (modern stops at `spellID`); addons reading only
11517-
the first three positional args are unaffected.
11518-
11519-
**Per-token fan-out.** A caster GUID can map to several tokens at once (your
11520-
`target` is also `party2` and `nameplate1`). Like retail, the event fires
11521-
**once per token** currently pointing at the caster, so a `target`-frame
11522-
cast bar, a `party2` frame, and a nameplate cast bar each get their own
11523-
event with the same castGUID. Tokens are resolved fresh at fire time (they
11524-
shift frame-to-frame). If you run **SuperWoW**, its raw-GUID token (`"0x…"`)
11525-
is deliberately filtered out — only standard tokens are fanned out.
11526-
11527-
**Non-player limits.** Only the six events above fire for other units, and
11528-
they're **best-effort** — driven purely by the packets an observer receives:
11529-
- `SENT` never fires (only your own outgoing casts are visible).
11530-
- `DELAYED` / `CHANNEL_UPDATE` never fire (pushback is sent only to the
11531-
caster, so another unit's bar can't stretch/shrink from damage).
11532-
- `FAILED` never fires (a pre-cast requirement failure is client-local).
11533-
- A remote cast/channel only has timing from the moment its
11534-
`SMSG_SPELL_START` was observed; casters who were already casting when
11535-
they came into range have no start time.
11536-
11537-
**Channels never fire `INTERRUPTED`.** Retail emits only `CHANNEL_STOP` when a
11538-
channel ends, whether it completed or was cut short (verified against retail),
11539-
so ClassicAPI matches that for both the player and other units. `INTERRUPTED`
11540-
is a cast-only event.
11541-
11542-
**Reticle events** fire for ground-targeted (AoE) spells only, always for the
11543-
player: `RETICLE_TARGET` when the placement reticle comes up, `RETICLE_CLEAR`
11544-
when it's placed or cancelled. There's no cast yet, so the castGUID slot
11545-
(arg2) is empty — retail pushes `nil` there, but the engine's event
11546-
dispatcher can't emit a `nil` mid-argument-list, so ClassicAPI pushes `""`
11547-
instead. `unit` (arg1) and `spellID` (arg3) are exact; arg2 is the only
11548-
difference and is inconsequential for a reticle.
11549-
11550-
**castGUID.** A synthesized string in the modern shape
11551-
`Cast-<type>-<serverID>-<instanceID>-<zoneUID>-<spellID>-<castUID>`. Vanilla
11552-
can't know server / instance / zone, so those three fields are `0`; the
11553-
load-bearing parts are the `spellID` (field 6, which addons `strsplit("-")`
11554-
out) and a unique-per-cast `castUID` (field 7). **Every event of one cast
11555-
carries the same castGUID**, so `SENT` → `START`/`CHANNEL_START` →
11556-
`SUCCEEDED` → `STOP`/`CHANNEL_STOP` all pair up — including across the caster
11557-
and observers (they converge on the same value), and a chained same-spell
11558-
recast gets its own castUID. The `type` and `castUID` follow the
11559-
[spell-cast-GUID spec](https://warcraft.wiki.gg/wiki/GUID#Cast):
11560-
- **Type 3** (real casts — the common case): `castUID` is time-based — the
11561-
low 23 bits are the cast's UNIX-epoch second, the higher bits a per-second
11562-
counter.
11563-
- **Type 2** (`UNIT_SPELLCAST_FAILED` — a local-only cast that never reached
11564-
the server): `castUID` is a plain locally-incrementing integer.
11565-
11566-
**Ordering** matches modern:
11567-
11568-
- Cast-time spell: `SENT → START → SUCCEEDED → STOP`.
11569-
- Channel: `SENT → CHANNEL_START → SUCCEEDED → CHANNEL_STOP` (CHANNEL_START
11570-
before SUCCEEDED, as on retail).
11571-
- Instant: `SENT → SUCCEEDED`.
11572-
11573-
**INTERRUPTED vs FAILED vs FAILED_QUIET** follow modern's split: a spell that
11574-
never started (out of range, not enough mana, on cooldown, LoS to a target)
11575-
fires `FAILED`, except for a fixed whitelist of "quiet" `SpellCastResult`
11576-
codes that fire `FAILED_QUIET` instead — `SPELL_IN_PROGRESS` (casting while
11577-
already casting / a spell-queue rejection), `DONT_REPORT` (fake fails, a
11578-
cancelled ground reticle), and `CHARMED`. That whitelist mirrors the 3.3.5
11579-
client's own unit-spellcast dispatch, mapped to vanilla's `SpellCastResult`
11580-
enum. A spell that was *already casting* and gets stopped (an enemy kick,
11581-
moving to cancel, breaking LoS mid-cast) fires `INTERRUPTED`. Holding
11582-
the cast key while running fires `INTERRUPTED` repeatedly (once per retry),
11583-
each reusing the interrupted cast's castGUID — matching retail.
11584-
11585-
**Channel pushback (player).** Taking damage while channeling shortens the
11586-
channel in vanilla; `CHANNEL_UPDATE` fires on each hit and
11587-
[`C_Spell.UnitChannelInfo`](#c_spellunitchannelinfounit--c_spellchannelinfo)'s
11588-
`endTimeMs` re-anchors to the server's new remaining time, so cast bars
11589-
shrink correctly. (The event carries no time — like retail it's a "re-read
11590-
now" trigger; timing is read back from `UnitChannelInfo`.)
11591-
11592-
Every fire is gated on whether any frame is registered for that event, so
11593-
the whole system costs one pointer-compare per state transition when no
11594-
addon uses it (no arg synthesis, no DBC lookups, no per-token fan-out).
11595-
11596-
```lua
11597-
local f = CreateFrame("Frame")
11598-
for _, e in ipairs({
11599-
"UNIT_SPELLCAST_START", "UNIT_SPELLCAST_STOP",
11600-
"UNIT_SPELLCAST_SUCCEEDED", "UNIT_SPELLCAST_CHANNEL_START",
11601-
}) do f:RegisterEvent(e) end
11602-
f:SetScript("OnEvent", function()
11603-
-- vanilla passes event/arg1/... as globals, not function params
11604-
if arg1 == "target" then print(event, arg3) end -- arg3 = spellID
11605-
end)
11606-
```
11607-
11608-
> **Additive to the vanilla `SPELLCAST_*` events.** The engine's own arg-less
11609-
> `SPELLCAST_START` / `SPELLCAST_CHANNEL_UPDATE` / … still fire as before;
11610-
> these `UNIT_`-prefixed events are the modern layer on top. The empowered-cast
11611-
> events (`UNIT_SPELLCAST_EMPOWER_*`, a Dragonflight addition) are not
11612-
> implemented — vanilla has no empowered casts.
11613-
1161411614
### `C_Spell.GetSpellLevelInfo(spellID)`
1161511615

1161611616
Returns the raw `Spell.dbc` level fields for a spell:

0 commit comments

Comments
 (0)