Jens
The BestDivine
LEVEL 3
165 XP
Greetings!
Welcome to part 3 of my TrinityCore spell system guide.
Today, we will be focusing on SpellEffects and SpellMgr.cpp
Welcome to part 3 of my TrinityCore spell system guide.
Today, we will be focusing on SpellEffects and SpellMgr.cpp
NOTE: I recognize this is very late, however I haven't done much wow emulation stuff lately as I have been busy with work and life.
My apologies for the delay!
[SpellEffects] - Starting from the top, we must begin by speaking about SpellEffects. SpellEffects are what handles the individual things spells can do.
For example:
- Teleport you somewhere
- Deal damage
- Crowd-control you
- Stun you
It's incredibly important that SpellEffects is programmed properly as this is where your spell's main functionality takes place.
Let's take a look at some code examples:
C++:
void Spell::EffectKnockBack()
{
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
return;
if (!unitTarget)
return;
if (m_caster->GetAffectingPlayer())
if (Creature* creatureTarget = unitTarget->ToCreature())
if (creatureTarget->isWorldBoss() || creatureTarget->IsDungeonBoss())
return;
// Spells with SPELL_EFFECT_KNOCK_BACK (like Thunderstorm) can't knockback target if target has ROOT/STUN
if (unitTarget->HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_STUNNED))
return;
// Instantly interrupt non melee spells being cast
if (unitTarget->IsNonMeleeSpellCast(true))
unitTarget->InterruptNonMeleeSpells(true);
float ratio = 0.1f;
float speedxy = float(effectInfo->MiscValue) * ratio;
float speedz = float(damage) * ratio;
if (speedxy < 0.01f && speedz < 0.01f)
return;
Position origin;
if (effectInfo->Effect == SPELL_EFFECT_KNOCK_BACK_DEST)
{
if (m_targets.HasDst())
origin = destTarget->GetPosition();
else
return;
}
else //if (effectInfo->Effect == SPELL_EFFECT_KNOCK_BACK)
origin = m_caster->GetPosition();
unitTarget->KnockbackFrom(origin, speedxy, speedz);
Unit::ProcSkillsAndAuras(GetUnitCasterForEffectHandlers(), unitTarget, PROC_FLAG_NONE, { PROC_FLAG_NONE, PROC_FLAG_2_KNOCKBACK },
PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_HIT, PROC_HIT_NONE, nullptr, nullptr, nullptr);
}
This spell effect is used to handle the "Knockback" mechanic. Now, the way a spell knows if it's a knockback mechanic is if it's Spell.dbc value has that spell effect, or if the programmer manually sets it. You can find references to these effects on wowhead when searching for a spell or in SpellWorks as we spoke about last time.
I'm not going to walk through the entire script, but if you're at all familiar with programming you can get an idea of what is going on. It's simply taking the target's position, determining if it's a knockback effect, and calculating the speed at which they are knocked back. This is given in vector-form which sends the target in a particular direction.
Here's one more:
C++:
void Spell::EffectApplyAura()
{
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
return;
if (!_spellAura || !unitTarget)
return;
// register target/effect on aura
AuraApplication* aurApp = _spellAura->GetApplicationOfTarget(unitTarget->GetGUID());
if (!aurApp)
aurApp = unitTarget->_CreateAuraApplication(_spellAura, 1 << effectInfo->EffectIndex);
else
aurApp->UpdateApplyEffectMask(aurApp->GetEffectsToApply() | 1 << effectInfo->EffectIndex, false);
}
Same exact thing, this simply handles the effect of applying an aura. Any spell that would apply an aura, for example Arcane Brilliance, will have the SpellEffect.
All of these functions are handled inside of the SpellEffects.cpp file and can be modified if needed. Keep in mind, however, modifying something in here changes the effect for every single spell in the game. You may need to have conditions for specific spells if needed.
[SpellMgr.cpp] - This script, also known as the Spell Manager, handles a very important part of why wow emulation can be difficult.
Each spell has something called flags. Flags are what give the spell certain "attributes" or "conditions" for behaving in certain ways. Take this flag for example: SPELL_ATTR0_PASSIVE.
If you to guess, odds are you'd guess correctly: This determines if the spell is passive.
So, whenever you go to cast the spell, it checks if the spell is passive and if it is, it does not allow you to cast it. That particular check is handled inside of the "CastSpellOpcode" function, which is run whenever the opcode CMSG_CAST_SPELL(which you learned about in our first guide) is sent from the client. It's checking here, rather than in Spell.cpp, whether or not this is a passive ability. If it is, simply cancel the opcode as we don't want to cast anything. Treat it as if it's not even a spell.
C++:
// Check possible spell cast overrides
spellInfo = caster->GetCastSpellInfo(spellInfo);
if (spellInfo->IsPassive())
return;
These particular spell attributes are incredibly important as they dictate how the spell will behave under certain circumstances. There are over 100 different spell attributes that all get used to this day.
Spell Attributes aren't the only type of flag spells have. Another type of spell flag I want to talk about is InterruptFlags. These flags handle what gives the spell a reason to either be removed( in the case of an aura ) or canceled while casting.
For example:
C++:
if (Unit* unitCaster = m_caster->ToUnit())
{
// stealth must be removed at cast starting (at show channel bar)
// skip triggered spell (item equip spell casting and other not explicit character casts/item uses)
if (!(_triggeredCastFlags & TRIGGERED_IGNORE_AURA_INTERRUPT_FLAGS) && !m_spellInfo->HasAttribute(SPELL_ATTR2_NOT_AN_ACTION))
unitCaster->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Action, m_spellInfo);
// Do not register as current spell when requested to ignore cast in progress
// We don't want to interrupt that other spell with cast time
if (!willCastDirectly || !(_triggeredCastFlags & TRIGGERED_IGNORE_CAST_IN_PROGRESS))
unitCaster->SetCurrentCastSpell(this);
}
There are two different types of flags here: 1. InterruptFlags, and 2. TriggeredFlags.
Let's start with the InterruptFlag SpellAuraInterruptFlags::Action - This means that if the spell has this flag and an action has taken place, in this case a spell was cast, we need to remove that aura as it has this Interrupt Flag.
A great example of this being used is the spell Invisibility for mages. If you perform any action, whether that's talking to an npc or casting a spell, this aura will be removed.
This same string of code is being used throughout the core to check these particular circumstances, this one just happens to reside in the spell:
Finally, let's speak quickly about TriggerFlags. These flags are designed to be added over-time and aren't added inside of SpellWorks, as opposed to InterruptFlags.
But, as you can see, we are checking certain circumstances such as: If we have this flag TRIGGERED_IGNORE_AURA_INTERRUPT_FLAGS, then we can skip this.
So, if I added this triggerflag to Invisibility, it would override the InterruptFlag.
Now, where does the SpellMgr.cpp script fit into this? Well, same as I spoke about SpellScripts last time, not everything is added inside of every spell on creation. Just like before, sometimes we need to hard-code certain information.
C++:
case 117708:// Maddening Shout
spellInfo->AttributesEx5 |= SPELL_ATTR5_USABLE_WHILE_FEARED;
break;
Take this spell as an example. For some reason, Blizzard didn't add this attribute to Maddening Shout, therefore we need to. The reason we need to add this is because we know Maddening Shout needs to be able to be cast while feared, so we add this flag.
Going line by line, we can see that we are getting Maddening Shout's spellId which is 117708, and then we are getting the AttributesEx5, which is just the "container" for the 5th set of attributes, in this case, SPELL_ATTR5, and then we are doing a bitwise OR, ( |= ), which basically just means to "add" this flag to the Attributes "container".
NOTE: If you want more information on this bitwise operator, simply google "bitwise operators" and study them as they come in handy when working with data such as this that can be handled inside of bits.
A rough explanation of how this works is this: imagine we have 4 bits, 0000, and we want to assign SPELL_ATTR5_USABLE_WHILE_FEARED, and maybe this particular flag is 0001, therefore we use the OR operator which essentially adds this bit to ours, which means our set of bits become 0001, if we have another flag, we can do the same and maybe it now becomes 0011. So, if we want to determine if 0001 exists in our bitmap, we can use the bitwise AND (&) operator to see if 0001 is a part of 0011, which we know it is, which means it would exist.
So, a recap:
Spell Effects handle how the spell should function.
Spell Flags gives the spell certain conditional operations
SpellMgr.cpp allows us to modify these two things to make this spell do what we want it to do.
Thanks for reading!
I can't promise there will be a part 4, but if you're interested in something in particular, please write in the comments below!