[GUIDE] - TrinityCore Spell System Guide & Walkthrough - Part 2

Jens

The Best
Divine
Jens Rep
0
0
0
Rep
0
Jens Vouches
0
0
0
Vouches
0
Posts
49
Likes
66
Bits
2 YEARS
2 YEARS OF SERVICE
LEVEL 3 165 XP
Greetings!
Welcome to Part 2 of my TrinityCore spell system guide.
Today we will be focusing on SpellScripts and how they work!
Let's begin:
[SpellScript Definition] - Essentially the way a SpellScript behaves is on top of what base DBC values of spells. When Blizzard first started designing spells, they baked all of the information necessary into our DBC files. You can see some of these variables inside of a program that we will discuss later called SpellWorks.

However, as time went on, Blizzard started to hard-code in certain aspects of spells, not bothering to store that information inside of these DBC files. Therefore, it's up to us to hardcode the same information so that we can make sure each spell is behaving properly. The way this is done is using SpellScripts. SpellScripts are called all-throughout the spell's lifecycle. From the OnCast function to OnHit and so on, these functions override base functions built into the core in order to cause new things to happen.

For example, let's take a look at a SpellScript for Alter Time in Mists of Pandaria. If you're unaware, Alter Time is a spell that sets a player's health, buffs, etc at a certain moment once the spell is cast. Then, if the spell is cast again, it reverses your character back to that amount of health, those buffs, etc.


C++:
// Alter Time - 127140 (overrided)
class spell_mage_alter_time_overrided : public SpellScriptLoader
{
    public:
        spell_mage_alter_time_overrided() : SpellScriptLoader("spell_mage_alter_time_overrided") { }

        class spell_mage_alter_time_overrided_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_mage_alter_time_overrided_SpellScript);

            void HandleAfterCast()
            {
                if (Player* _player = GetCaster()->ToPlayer())
                    if (_player->HasAura(SPELL_MAGE_ALTER_TIME))
                        _player->RemoveAura(SPELL_MAGE_ALTER_TIME);
            }

            void Register()
            {
                AfterCast += SpellCastFn(spell_mage_alter_time_overrided_SpellScript::HandleAfterCast);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_mage_alter_time_overrided_SpellScript();
        }
};

Here is that SpellScript. Let's break it down.

Now, if you're unfamiliar with classes and things such as that, I'd recommend doing some research, however it's not particularly pertinent to know as this is a beginner guide.

[Function] - HandleAfterCast
Now, common sense dictates that, whatever we're doing inside of this function, will be handled after the cast is completed. What this means is: Once a cast is finished (remember, TrinityCore considers "cast" as the spell actually going off, not the delayed timer that is run for a CastTime), do whatever is inside of this function.
Going line by line, we can see that we're getting our Player* object, following up by asking if that Player* has the aura called SPELL_MAGE_ALTER_TIME, which was defined at the top of the script, and then is having that aura be removed.

Now, this is a very simple example of a SpellScript, but it works nonetheless. If you ever need to do something specfic to a spell, perhaps add an Aura after the spell is cast or remove an aura, this is how that is done. However, if you've ever had the chance to look at a less-developed version of a TrinityCore fork. For example, let's say a cataclysm, MoP, WoD-era core, you will notice that there are a lot of spell references inside of other scripts that aren't SpellScripts.

These are referred to as "hackfixes" per se. Now, not all of them are fully hackfixed, some needed to be references for very niche spells. However, the vast majority of spells, if programmed correctly, should be done inside of their respective SpellScript. This is because SpellScripts aren't run on the main thread. Doing them outside of the main thread is more efficient and will allow the server to run more smoothly.

Let's take a look at one more SpellScript that may be more complex.


C++:
// Living Bomb - 44457
class spell_mage_living_bomb : public SpellScriptLoader
{
    public:
        spell_mage_living_bomb() : SpellScriptLoader("spell_mage_living_bomb") { }

        class spell_mage_living_bomb_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_mage_living_bomb_AuraScript);

            void OnTick(AuraEffect const* aurEff)
            {
                if (!GetCaster())
                    return;

                if (Unit* caster = GetCaster())
                {
                    if (Player* player = GetCaster()->ToPlayer())
                    {
                        if (player->GetSpecializationId(player->GetActiveSpecialization()) == SPEC_MAGE_FROST)
                        {
                            if (roll_chance_i(25))
                                player->AddAura(SPELL_MAGE_BRAIN_FREEZE_TRIGGERED, player);
                        }
                    }
                }
            }

            void AfterRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                AuraRemoveMode removeMode = GetTargetApplication()->GetRemoveMode();
                if (removeMode != AURA_REMOVE_BY_DEATH && removeMode != AURA_REMOVE_BY_EXPIRE)
                    return;

                if (!GetCaster())
                    return;

                if (Unit* caster = GetCaster())
                {
                    caster->CastSpell(GetTarget(), SPELL_MAGE_LIVING_BOMB_TRIGGERED, true);
                }
            }

            void Register()
            {
                OnEffectPeriodic += AuraEffectPeriodicFn(spell_mage_living_bomb_AuraScript::OnTick, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE);
                AfterEffectRemove += AuraEffectRemoveFn(spell_mage_living_bomb_AuraScript::AfterRemove, EFFECT_1, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_mage_living_bomb_AuraScript();
        }
};

Here is the SpellScript for Living Bomb, again a MoP version.

Let's break it down. Same exact thing as before, however there's now two functions.

[Function] - OnTick
Again, what does common sense say? It says that, on each TICK of Living Bomb, do whatever is inside of this function.
Starting from the top, we're first making sure we have a caster. The reason we do this is, let's say the caster of the spell logs out while the aura is on a training dummy. Well, if the caster no longer exists, it will error the script and could cause issues to the server.

Next, we're getting our Unit* object and then pointing to our Player* object as the Player* object is a sub-class of Unit*. We're then confirming that our specialization is Frost, as the Brain Freeze proc will only happen for frost mages.

Next, we know that the proc chance of a Brain Freeze proc from Living Bomb is 25%. Therefore, we check, on each tick, if that chance has happened. If so, we then go to our last step:
Applying the aura.

Now, that wasn't our only function.


[Function] - AfterRemove
To finalize, we can guess that this function is handled when the aura wears off, or ends.

The first thing we check here is to see why it was removed. We don't want our living bomb explosion to happen if the aura was removed by dispel. This is making sure it was either removed on death or removed by expiring, as in the effect ran out of time.

We then, again, make sure that our caster exists and finally we get said caster and use the CastSpell function which is the explosion on the target.

You may ask why we use CastSpell and not AddAura. This is because the "explosion" at the end of the aura is a spell, not an aura. Same as if we casted Ice Lance at the end instead.

This explosion has some attributes that prevent the player from needing to be off GCD or otherwise, thus making it seem like the player never casted anything.


This concludes part 2. Part 3 will be out in a few days! Stay tuned.

If you enjoyed, +rep and +like for me! Thanks.
 
Last edited:

Azayaka

Allelujah
Administrator
Azayaka Rep
3
0
0
Rep
9
Azayaka Vouches
0
0
0
Vouches
0
Posts
330
Likes
225
2 YEARS
2 YEARS OF SERVICE
LEVEL 101 5 XP
Very cool. Nice addition to part one.
 
Liked By 1 member :

PrivateDonut

Account Closed
banned
Rep
3
0
0
Rep
3
Vouches
0
0
0
Vouches
0
Posts
533
Likes
448
Bits
3 YEARS
3 YEARS OF SERVICE
LEVEL 31 71 XP
Nice tutorial, I'm glad you made a second part! I hope you make more to help people understand the spell system better! :pepeDS:
 
Liked By 1 member :

splicho

I see humans, but no humanity.
Administrator
splicho Rep
6
0
0
Rep
6
splicho Vouches
3
0
0
Vouches
3
Posts
1,015
Likes
1,583
Bits
3 YEARS
3 YEARS OF SERVICE
LEVEL 264 65 XP
Perfect tutorial, appreciate the contribution Jensy.
 

3,587

1,286

9,622

435

Top