Emudevs - MMORPG Development

Immerse yourself in a world of limitless possibilities and open the gateway to mmo emulation.

[Beginner] - Mr Spell

iThorgrim

Developer
Developer

0

0%

Status

Offline

Posts

5

Likes

2

Rep

0

Bits

1,083

1

Years of Service

LEVEL 3
140 XP
Hello,

Today we're going to take a look at how to create a small script; the famous Mr Spell used on many, if not almost all, UltraFun servers this decade.

This tutorial is aimed at beginner developers wishing to learn how to create a "complex" gossip_menu.
The script is neither optimized nor intended for any use other than the theme of this tutorial.

This tutorial has been translated from French to English by a translator (DeepL). I apologize in advance for any mistakes that may be made, as I've normally made sure that the essence of the tutorial is not altered.

In this tutorial, we will look at concepts such as :
- Using multidimensional arrays.
- The use of loops.

Let's get started!

Mr Spell
The Mr. Spell is an NPC who allows you to learn a spell that is not of your class.
He's widely used on UltraFun servers to ... to ...

- What's the Mr Spell for, Bob?
- It's to make the game more fun.
- Ah.


So it's used a lot on servers that lack imagination and have trouble crapping out content. (Yes, I take sides during my written tutorials, I don't like ultraFun garbage servers).

Anyway ... enough of this nonsense, it's time to get to the heart of the matter.
To start with, we're going to declare the tables that will be useful to us.

Declarations
Code:
local MrSpell = {}

MrSpell.Locale = {
    [1] = 'Warrioir',
}

MrSpell.spellList = {
    [1] = {},
}

MrSpell.spellCost = { }

So, as you can see, I've already put in some information, simply to help you with the rest.
There are still 2 empty tables:
Code:
MrSpell.spellList = {
    [1] = {},
}

MrSpell.spellCost = { }
- MrSpell.spellList: to store spells sorted by class.
- MrSpell.spellCost: used to store spell prices.

Code:
local MrSpell = {}

MrSpell.Local = {
    [1] = 'Warrior',
}

MrSpell.spellList = {
    [1] = {
        [12550] = 'Mortal strike',
        [69] = 'Heroic strike',
    },
}

MrSpell.spellCost = {
    [12550] = 50,
    [69] = 100,
}
Here I'm saying that spell 12550 is called 'Mortal Strike' and that this same spell will cost 50 of our currency.

OnHello
Code:
function MrSpell.onHello(event, player, object)
    local pClass = player:GetClass();

    for classId, className in pairs(MrSpell.Locale) do
        if (pClass ~= classId) then
            player:GossipMenuAddItem(0, className, 0, classId)
        end
    end

    player:GossipSendMenu(1, object);
end
Here it's very simple: when our player talks to the NPC, he's asked to display the list of classes and not to display the class that corresponds to the player.

For example, a Warrior can't see the Warrior class and buy a Warrior spell.

If you wish to enable this, simply remove the :
Code:
if (pClass ~= classId) then
    ...
end
And so Warriors can buy Warrior spells.

I don't really need to explain what's going on in this piece of code ... I've made a "for" loop to browse my table and display information in a Gossip menu.
Now we need to make the GossipSelect, which will contain 3/4 of our code.

OnSelect

Code:
function MrSpell.onSelect(event, player, object, sender, intid)
    for classId, classSubArray in pairs(MrSpell.spellList) do

    end
end
We start by looping through the spells in our complete list.

Code:
function MrSpell.onSelect(event, player, object, sender, intid)
    for classId, classSubArray in pairs(MrSpell.spellList) do
        if (intid == classId) then
            for spellId, spellName in pairs(classSubArray) do

            end
        end
    end
end
Here we check that our intid is equal to the classId.
This will be used to display spells that only concern the class we've selected in the main menu.

Then we loop the MrSpell.spellList subarray "for".
(Let me explain):
Code:
MrSpell.spellList = { }
This is the "main" array, a simple array, except that we're asking it to store spells for a certain class.

Code:
MrSpell.spellList = {
    [1] = {
        [12550] = 'Mortal strike',
        [69] = 'Heroic strike',
    },
};
So in our table, we have an array.
We say that MrSpell.spellList is a multidimensional array.
And since we need to display the spell names, we'll make a "for" loop to display them.

Code:
function MrSpell.onSelect(event, player, object, sender, intid)
    for classId, classSubArray in pairs(MrSpell.spellList) do
        if (intid == classId) then
            for spellId, spellName in pairs(classSubArray) do
                local pSpell = player:HasSpell(spellId);

                local msg = '';
                if (pSpell) then
                    msg = '|CFF009D09Learn|r';
                else
                    msg = '|CFF9D1C00Not learn|r';
                end

                player:GossipMenuAddItem(0, spellName..' '..msg, 0, spellId)
                player:GossipSendMenu(1, object);
            end
            return
        end
    end
end
So here we can see that I've added a local variable (pSpell) whose job is to check whether or not we know the spell.

Depending on whether we know the spell or not, the local variable "msg" will store the information "Already learned" or "Not learned".
This will be used to put it next to the spell name, so that the player is aware of whether he has learned the spell or not.

We then simply list the spells in the chosen category, adding the small local variable "msg" to inform the player.
Code:
function MrSpell.onSelect(event, player, object, sender, intid)
    for classId, classSubArray in pairs(MrSpell.spellList) do
        if (intid == classId) then
            for spellId, spellName in pairs(classSubArray) do
                local pSpell = player:HasSpell(spellId);

                local msg = '';
                if (pSpell) then
                    msg = '|CFF009D09Learn|r';
                else
                    msg = '|CFF9D1C00Not learn|r';
                end

                player:GossipMenuAddItem(0, spellName..' '..msg, 0, spellId)
                player:GossipSendMenu(1, object);
            end
            return
        end
       
        for spellId, _ in pairs(classSubArray) do
            if (intid == spellId) then

            end
        end
    end
end
Here, we're going to use the same method as above, except we're going to reverse the technique.
First, we'll list the spells in our category and check whether the intid sent corresponds to a spell in our category.

Code:
function MrSpell.onSelect(event, player, object, sender, intid)
    for classId, classSubArray in pairs(MrSpell.spellList) do
        if (intid == classId) then
            for spellId, spellName in pairs(classSubArray) do
                local pSpell = player:HasSpell(spellId);

                local msg = '';
                if (pSpell) then
                    msg = '|CFF009D09Learn|r';
                else
                    msg = '|CFF9D1C00Not learn|r';
                end

                player:GossipMenuAddItem(0, spellName..' '..msg, 0, spellId)
                player:GossipSendMenu(1, object);
            end
            return
        end
       
        for spellId, _ in pairs(classSubArray) do
            if (intid == spellId) then
                for spellCostId, Cost in pairs(MrSpell.spellCost) do
                    if (spellId == spellCostId) then
                        local pMoney = player:GetItemCount(40752);
                    end
                end
            end
        end
    end
end

Then we ask him to list all the spell prices.
Then we'll ask him to check that the spell we've chosen corresponds to a spell that has a price (this will come in handy later on).
I then ask to register the number of copies of item 40752 I own.
Note that you can change the item entry here to the one of your choice.

Code:
function MrSpell.onSelect(event, player, object, sender, intid)
    for classId, classSubArray in pairs(MrSpell.spellList) do
        if (intid == classId) then
            for spellId, spellName in pairs(classSubArray) do
                local pSpell = player:HasSpell(spellId);

                local msg = '';
                if (pSpell) then
                    msg = '|CFF009D09Learn|r';
                else
                    msg = '|CFF9D1C00Not learn|r';
                end

                player:GossipMenuAddItem(0, spellName..' '..msg, 0, spellId)
                player:GossipSendMenu(1, object);
            end
           return
        end
       
        for spellId, _ in pairs(classSubArray) do
            if (intid == spellId) then
                for spellCostId, Cost in pairs(MrSpell.spellCost) do
                    if (spellId == spellCostId) then
                        local pMoney = player:GetItemCount(40752);

                        if (pMoney >= Cost)then
                            local pSpell = player:HasSpell(spellId);
                        else
                            player:SendNotification('You don\'t have enough points to perform this action.');
                            MrSpell.onHello(event, player, object);
                        end
                    end
                end
            end
        end
    end
end
The NPC checks that I have the right number of points or that I have more points than the price, otherwise I'll get an error message and be sent back to the main menu.
If I have the right number of points, I ask him to check if I have the spell.

Code:
function MrSpell.onSelect(event, player, object, sender, intid)
    for classId, classSubArray in pairs(MrSpell.spellList) do
        if (intid == classId) then
            for spellId, spellName in pairs(classSubArray) do
                local pSpell = player:HasSpell(spellId);

                local msg = '';
                if (pSpell) then
                    msg = '|CFF009D09Learn|r';
                else
                    msg = '|CFF9D1C00Not learn|r';
                end

                player:GossipMenuAddItem(0, spellName..' '..msg, 0, spellId)
                player:GossipSendMenu(1, object);
            end
            return
        end
       
        for spellId, _ in pairs(classSubArray) do
            if (intid == spellId) then
                for spellCostId, Cost in pairs(MrSpell.spellCost) do
                    if (spellId == spellCostId) then
                        local pMoney = player:GetItemCount(40752);

                        if (pMoney >= Cost)then
                            local pSpell = player:HasSpell(spellId);
                            if (not(pSpell)) then
                                player:RemoveItem(40752, Cost);
                                player:LearnSpell(spellId);
                                player:GossipComplete();
                                player:SendNotification('Thank you for your purchase.');
                            else
                                player:SendNotification('You already know this spell.');
                                MrSpell.onHello(event, player, object);
                            end
                        else
                            player:SendNotification('You don\'t have enough points to perform this action.');
                            MrSpell.onHello(event, player, object);
                        end
                    end
                end
            end
        end
    end
end
And that's the end of it, I tell him that if I don't have the spell then :

- You take away the cost of the spell.
- Teach me the spell.
- Close the menu.
- Send me a message.

Otherwise, it sends me an error message and redirects me to the main menu.
All that's left is to add the Events that will govern my code.

Code:
RegisterCreatureGossipEvent(197, 1, MrSpell.onHello)
RegisterCreatureGossipEvent(197, 2, MrSpell.onSelect)
- 197 being your NPC's entry

I've tried to be as clear as possible, so if you have any questions, don't hesitate!
The complete code looks like this :
Code:
local MrSpell = {}

MrSpell.Local = {
    [1] = 'Warrior',
}

MrSpell.spellList = {
    [1] = {
        [12550] = 'Mortal strike',
        [69] = 'Heroic strike',
    },
}

MrSpell.spellCost = {
    [12550] = 50,
    [69] = 100,
}

function MrSpell.onHello(event, player, object)
    local pClass = player:GetClass();
    for classId, className in pairs(MrSpell.Locale) do
        if (pClass ~= classId) then
            player:GossipMenuAddItem(0, className, 0, classId)
        end
    end
    player:GossipSendMenu(1, object);
end

function MrSpell.onSelect(event, player, object, sender, intid)
    for classId, classSubArray in pairs(MrSpell.spellList) do

        if (intid == classId) then
            for spellId, spellName in pairs(classSubArray) do
                local pSpell = player:HasSpell(spellId);

                local msg = '';
                if (pSpell) then
                    msg = '|CFF009D09Déjà Appris|r';
                else
                    msg = '|CFF9D1C00Non appris|r';
                end

                player:GossipMenuAddItem(0, spellName..' '..msg, 0, spellId)
                player:GossipSendMenu(1, object);
            end
            return
        end

        for spellId, _ in pairs(classSubArray) do
            if (intid == spellId) then
                for spellCostId, Cost in pairs(MrSpell.spellCost) do
                    if (spellId == spellCostId) then
                        local pMoney = player:GetItemCount(40752);
                        if (pMoney >= Cost)then

                            local pSpell = player:HasSpell(spellId);
                            if (not(pSpell)) then
                                player:RemoveItem(40752, Cost);
                                player:LearnSpell(spellId);
                                player:GossipComplete();
                                player:SendNotification('Thank you for your purchase.');
                            else
                                player:SendNotification('You already know this spell.');
                                MrSpell.onHello(event, player, object);
                            end
                        else
                            player:SendNotification('You don\'t have enough points to perform this action.');
                            MrSpell.onHello(event, player, object);
                        end
                    end
                end
            end
        end
    end
end
RegisterCreatureGossipEvent(197, 1, MrSpell.onHello)
RegisterCreatureGossipEvent(197, 2, MrSpell.onSelect)
 
Last edited:

2,407

1,079

8,786

Top