compat: C_Spell.GetSpellEffectMechanics, the per-effect sibling
`SuperCleveRoidMacros/ClassicAPI.lua:267` calls it unguarded, so its absence was a raise a
session. It is not a second spelling of `GetSpellMechanicByID`, which this tree already answers:
that one reads `Spell.dbc`'s spell-level `Mechanic`, and vanilla stores some mechanics on an
*effect* instead. The consumer's own comment names the case — Rake's bleed is effect-level, so
the spell-level column says 0 while this answers `{0, 15, 0}` — and a macro conditional asking
whether its target is bleeding gets the wrong answer from the spell-level field alone.
`SpellDisplay::effect_mechanic` was already parsed for the cast-immunity scan, so this is a field
on the probe payload and a reader, not a new lookup. The three-way answer is its sibling's, for
the sibling's reason: nothing at all for an id no spell owns, three zeros for a real spell that
carries no mechanic anywhere, the ids otherwise — collapsing those two would reclassify crowd
control as ordinary damage, which is the hand-kept table this function exists to replace.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -245,6 +245,39 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<Table> {
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// GetSpellEffectMechanics(spellID) → `{m1, m2, m3}`, the per-effect `EffectMechanic[3]`
|
||||
// column, where `GetSpellMechanicByID` above reads only the spell-level `Mechanic` one.
|
||||
//
|
||||
// The two are not the same question and the consumer says why
|
||||
// (`SuperCleveRoidMacros/ClassicAPI.lua:259-263`): "vanilla stores some mechanics on an
|
||||
// effect instead — Rake's bleed is effect-level, so GetSpellMechanicByID returns 0 but this
|
||||
// returns {0,15,0}". A macro conditional that asks whether its target is bleeding gets the
|
||||
// wrong answer from the spell-level field alone.
|
||||
//
|
||||
// Same three-way answer as its sibling, for the same reason: **nothing** for an id no spell
|
||||
// owns, and a table of three numbers otherwise, with `0` in a slot meaning that effect
|
||||
// carries no mechanic. A table of zeros and no table at all must stay distinguishable.
|
||||
t.set(
|
||||
"GetSpellEffectMechanics",
|
||||
lua.create_function(|lua, spell_id: Option<u32>| {
|
||||
let Some(spell_id) = spell_id else {
|
||||
return Ok(Value::Nil);
|
||||
};
|
||||
let model = lua.app_data_ref::<Model>().expect("model app_data");
|
||||
let Some(probe) = model.spell_mechanic_probe.as_ref() else {
|
||||
return Ok(Value::Nil);
|
||||
};
|
||||
let Some(x) = probe(spell_id) else {
|
||||
return Ok(Value::Nil);
|
||||
};
|
||||
let out = lua.create_table()?;
|
||||
for (i, m) in x.effect_mechanics.iter().enumerate() {
|
||||
out.set(i + 1, *m)?;
|
||||
}
|
||||
Ok(Value::Table(out))
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// GetSpellPowerCost(spellID) → an **array of cost rows**, not a number: the consumer reads
|
||||
// `costs[1].cost` (`SuperCleveRoidMacros/Core.lua:670-672`), so the shape is a list even
|
||||
// though 1.12 has exactly one cost per spell — there is no second power type to report.
|
||||
@@ -609,6 +642,63 @@ mod tests {
|
||||
use crate::script::{SpellBookState, SpellSlotView, SpellTabView, UiScript};
|
||||
|
||||
/// `GetSpellMechanicByID`'s three-way answer, which is the consumer's own spec: nothing for an
|
||||
/// `GetSpellEffectMechanics` reads the **per-effect** column, which is a different question
|
||||
/// from its spell-level sibling and the reason the consumer calls both: vanilla stores some
|
||||
/// mechanics on an effect instead, so a spell whose `Mechanic` is 0 can still carry one
|
||||
/// (`SuperCleveRoidMacros/ClassicAPI.lua:259-263` names Rake's bleed, `{0, 15, 0}`). An id no
|
||||
/// spell owns stays distinguishable from a spell whose three effects carry none.
|
||||
#[test]
|
||||
fn the_per_effect_mechanics_are_a_table_of_three_and_absent_for_an_unknown_spell() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_spell_mechanic_probe(Box::new(move |id| match id {
|
||||
// Rake: nothing at the spell level, a bleed on the second effect.
|
||||
1822 => Some(crate::script::SpellDbcExtras {
|
||||
mechanic: 0,
|
||||
mechanic_name: None,
|
||||
effect_mechanics: [0, 15, 0],
|
||||
mana_cost: 0,
|
||||
power_type: 0,
|
||||
duration_ms: 0,
|
||||
icon_id: 0,
|
||||
}),
|
||||
// A real spell with no mechanic anywhere.
|
||||
133 => Some(crate::script::SpellDbcExtras {
|
||||
mechanic: 0,
|
||||
mechanic_name: None,
|
||||
effect_mechanics: [0; 3],
|
||||
mana_cost: 0,
|
||||
power_type: 0,
|
||||
duration_ms: 0,
|
||||
icon_id: 0,
|
||||
}),
|
||||
_ => None,
|
||||
}));
|
||||
assert_eq!(
|
||||
s.eval::<Vec<u32>>("return C_Spell.GetSpellEffectMechanics(1822)")
|
||||
.unwrap(),
|
||||
vec![0, 15, 0],
|
||||
"the effect-level bleed the spell-level column does not carry"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<Vec<u32>>("return C_Spell.GetSpellEffectMechanics(133)")
|
||||
.unwrap(),
|
||||
vec![0, 0, 0],
|
||||
"a real spell with no mechanic is three zeros, not nothing"
|
||||
);
|
||||
assert!(
|
||||
s.eval::<Option<mlua::Table>>("return C_Spell.GetSpellEffectMechanics(999999)")
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
"an id no spell owns answers nothing at all"
|
||||
);
|
||||
// And the spell-level getter still disagrees with it, which is the whole point.
|
||||
assert_eq!(
|
||||
s.eval::<i64>("local m = C_Spell.GetSpellMechanicByID(1822) return m")
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
/// id no spell owns, `0` for a real spell with no mechanic, the id otherwise. It replaces a
|
||||
/// hand-kept spellID → mechanic table, so collapsing "unknown" into "none" would quietly
|
||||
/// reclassify crowd control as ordinary damage.
|
||||
@@ -618,6 +708,7 @@ mod tests {
|
||||
let extras = |mechanic: u32, name: Option<&str>, cost: u32| crate::script::SpellDbcExtras {
|
||||
mechanic,
|
||||
mechanic_name: name.map(str::to_string),
|
||||
effect_mechanics: [0; 3],
|
||||
mana_cost: cost,
|
||||
power_type: 0,
|
||||
duration_ms: 0,
|
||||
|
||||
@@ -545,6 +545,7 @@ mod tests {
|
||||
let extras = |duration_ms: u32| crate::script::SpellDbcExtras {
|
||||
mechanic: 0,
|
||||
mechanic_name: None,
|
||||
effect_mechanics: [0; 3],
|
||||
mana_cost: 0,
|
||||
power_type: 0,
|
||||
duration_ms,
|
||||
|
||||
@@ -84,6 +84,11 @@ pub struct SpellDbcExtras {
|
||||
/// That mechanic's enUS `SpellMechanic.dbc` name, lower-case as the data holds it; `None` for
|
||||
/// mechanic 0 and for an id with no row.
|
||||
pub mechanic_name: Option<String>,
|
||||
/// `EffectMechanic[3]`, the per-effect column, where [`Self::mechanic`] is the spell-level
|
||||
/// one. Vanilla puts some mechanics on an effect instead of the spell — Rake's bleed is the
|
||||
/// consumer's own example — so a caller that reads only the spell-level field misses them.
|
||||
/// `0` in a slot means that effect carries no mechanic.
|
||||
pub effect_mechanics: [u32; 3],
|
||||
/// `manaCost` (column 32): the flat cost, in [`Self::power_type`]'s unit.
|
||||
pub mana_cost: u32,
|
||||
/// `powerType`: 0 mana, 1 rage, 3 energy — the unit [`Self::mana_cost`] is counted in.
|
||||
|
||||
Reference in New Issue
Block a user