Ir para conteúdo
  • 0
FaelDanil

Scripting Erro Trade

Pergunta

FaelDanil    0
FaelDanil

Antes de fazer a sua pergunta, tenha certeza de ter lido as regras da seção e o guia abaixo:

https://forums.otserv.com.br/index.php?/forums/topic/168583-regras-da-seção/

https://forums.otserv.com.br/index.php?/forums/topic/165121-como-fazer-uma-pergunta-ou-o-grande-guia-do-usuário-com-dúvidas/

Descreva em algumas palavras a base utilizada. (Nome do servidor / Nome do cliente / Nome do website / etc.).

 

Base:

The Forgotten Server 1.3, Versão: 8.6

 

Qual é a sua pergunta?

Estou com um erro ao dar trade nos NPCs

 

Você tem o código disponível? Se tiver poste-o na caixa de código que está dentro do spoiler abaixo:

Rashid.lua:

Spoiler

local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)
NpcSystem.parseParameters(npcHandler)

function onCreatureAppear(cid)            npcHandler:onCreatureAppear(cid)            end
function onCreatureDisappear(cid)        npcHandler:onCreatureDisappear(cid)            end
function onCreatureSay(cid, type, msg)        npcHandler:onCreatureSay(cid, type, msg)        end
function onThink()                npcHandler:onThink()                    end

local function getTable()
local list = 
{
    {name = 'Abyss Hammer',            id = 7414,        sell = 20000,        buy = 0},
    {name = 'Amber Staff',            id = 7426,        sell = 8000,        buy = 0},
    {name = 'Ancient Amulet',        id = 2142,        sell = 200,            buy = 0},
    {name = 'Assassin Dagger',        id = 7404,        sell = 20000,        buy = 0},
    {name = 'Bandana',                id = 5917,        sell = 150,            buy = 0},
    {name = 'Beastslayer axe',        id = 3962,        sell = 1500,        buy = 0},
    {name = 'Beetle Necklace',        id = 11374,        sell = 1500,        buy = 0},
    {name = 'Berserker',            id = 7403,        sell = 40000,        buy = 0},
    {name = 'Blacksteel Sword',        id = 7406,        sell = 6000,        buy = 0},
    {name = 'Blessed Sceptre',        id = 7429,        sell = 40000,        buy = 0},
    {name = 'Bone Shield',            id = 2541,        sell = 80,            buy = 0},
    {name = 'Bonelord Helmet',        id = 3972,        sell = 7500,        buy = 0},
    {name = 'Brutetamer\'s Staff',    id = 7379,        sell = 1500,        buy = 0},
    {name = 'Buckle',                id = 20109,        sell = 7000,        buy = 0},
    {name = 'Castle Shield',        id = 2535,        sell = 5000,        buy = 0},
    {name = 'Chain Bolter',            id = 8850,        sell = 40000,        buy = 0},
    {name = 'Chaos Mace',            id = 7427,        sell = 9000,        buy = 0},
    {name = 'Cobra Crown',            id = 12630,        sell = 50000,        buy = 0},
    {name = 'Coconut Shoes',        id = 9931,        sell = 500,            buy = 0},
    {name = 'Composite Hornbow',    id = 8855,        sell = 25000,        buy = 0},
    {name = 'Cranial Basher',        id = 7415,        sell = 30000,        buy = 0},
    {name = 'Crocodile Boots',        id = 3982,        sell = 1000,        buy = 0},
    {name = 'Crystal Crossbow',        id = 18453,        sell = 35000,        buy = 0},
    {name = 'Crystal Mace',            id = 2445,        sell = 12000,        buy = 0},
    {name = 'Crystal Necklace',        id = 2125,        sell = 400,            buy = 0},
    {name = 'Crystal Ring',            id = 2124,        sell = 250,            buy = 0},
    {name = 'Crystal Sword',        id = 7449,        sell = 600,            buy = 0},
    {name = 'Crystalline Armor',    id = 8878,        sell = 16000,        buy = 0},
    {name = 'Daramian Mace',        id = 2439,        sell = 110,            buy = 0},
    {name = 'Daramian Waraxe',        id = 2440,        sell = 1000,        buy = 0},
    {name = 'Dark Shield',            id = 2521,        sell = 400,            buy = 0},
    {name = 'Death Ring',            id = 6300,        sell = 1000,        buy = 0},
    {name = 'Demon Shield',            id = 2520,        sell = 30000,        buy = 0},
    {name = 'Demonbone Amulet',        id = 2136,        sell = 32000,        buy = 0},
    {name = 'Demonrage Sword',        id = 7382,        sell = 36000,        buy = 0},
    {name = 'Devil Helmet',            id = 2462,        sell = 1000,        buy = 0},
    {name = 'Diamond Sceptre',        id = 7387,        sell = 3000,        buy = 0},
    {name = 'Divine Plate',            id = 8885,        sell = 55000,        buy = 0},
    {name = 'Djinn Blade',            id = 2451,        sell = 15000,        buy = 0},
    {name = 'Doll',                    id = 2110,        sell = 200,            buy = 0},
    {name = 'Dragon Scale Mail',    id = 2492,        sell = 40000,        buy = 0},
    {name = 'Dragon Slayer',        id = 7402,        sell = 15000,        buy = 0},
    {name = 'Dragonbone Staff',        id = 7430,        sell = 3000,        buy = 0},
    {name = 'Dreaded Cleaver',        id = 7419,        sell = 10000,        buy = 0},
    {name = 'Dwarven Armor',        id = 2503,        sell = 30000,        buy = 0},
    {name = 'Earth Cranial Basher',    id = 7866,        sell = 30000,        buy = 0},
    {name = 'Earth Dragon Slayer',    id = 7858,        sell = 15000,        buy = 0},
    {name = 'Earth Headchopper',    id = 7862,        sell = 6000,        buy = 0},
    {name = 'Earth Heroic axe',        id = 7861,        sell = 30000,        buy = 0},
    {name = 'Earth Mystic Blade',    id = 7856,        sell = 30000,        buy = 0},
    {name = 'Earth Orcish Maul',    id = 7867,        sell = 6000,        buy = 0},
    {name = 'Earth Relic Sword',    id = 7855,        sell = 25000,        buy = 0},
    {name = 'Earth War axe',        id = 7863,        sell = 12000,        buy = 0},
    {name = 'Elvish Bow',            id = 7438,        sell = 2000,        buy = 0},
    {name = 'Emerald Bangle',        id = 2127,        sell = 800,            buy = 0},
    {name = 'Energy Cranial Basher',id = 7881,        sell = 30000,        buy = 0},
    {name = 'Energy Dragon Slayer',    id = 7873,        sell = 15000,        buy = 0},
    {name = 'Energy Headchopper',    id = 7877,        sell = 6000,        buy = 0},
    {name = 'Energy Heroic axe',    id = 7876,        sell = 30000,        buy = 0},
    {name = 'Energy Mystic Blade',    id = 7871,        sell = 30000,        buy = 0},
    {name = 'Energy Orcish Maul',    id = 7882,        sell = 6000,        buy = 0},
    {name = 'Energy Relic Sword',    id = 7870,        sell = 25000,        buy = 0},
    {name = 'Energy War axe',        id = 7878,        sell = 12000,        buy = 0},
    {name = 'Epee',                    id = 2438,        sell = 8000,        buy = 0},
    {name = 'Fiery Cranial Basher',    id = 7756,        sell = 30000,        buy = 0},
    {name = 'Fiery Dragon Slayer',    id = 7748,        sell = 15000,        buy = 0},
    {name = 'Fiery Headchopper',    id = 7752,        sell = 6000,        buy = 0},
    {name = 'Fiery Heroic axe',        id = 7751,        sell = 30000,        buy = 0},
    {name = 'Fiery Mystic Blade',    id = 7746,        sell = 30000,        buy = 0},
    {name = 'Fiery Orcish Maul',    id = 7757,        sell = 6000,        buy = 0},
    {name = 'Fiery Relic Sword',    id = 7745,        sell = 25000,        buy = 0},
    {name = 'Fiery War axe',        id = 7753,        sell = 12000,        buy = 0},
    {name = 'Flower Dress',            id = 9929,        sell = 1000,        buy = 0},
    {name = 'Flower Wreath',        id = 9927,        sell = 500,            buy = 0},
    {name = 'Fur Boots',            id = 7457,        sell = 2000,        buy = 0},
    {name = 'Furry Club',            id = 7432,        sell = 1000,        buy = 0},
    {name = 'Glacier Amulet',        id = 7888,        sell = 1500,        buy = 0},
    {name = 'Glacier Kilt',            id = 7896,        sell = 11000,        buy = 0},
    {name = 'Glacier Mask',            id = 7902,        sell = 2500,        buy = 0},
    {name = 'Glacier Robe',            id = 7897,        sell = 11000,        buy = 0},
    {name = 'Glacier Shoes',        id = 7892,        sell = 2500,        buy = 0},
    {name = 'Gold Ring',            id = 2179,        sell = 8000,        buy = 0},
    {name = 'Golden Armor',            id = 2466,        sell = 20000,        buy = 0},
    {name = 'Golden Legs',            id = 2470,        sell = 30000,        buy = 0},
    {name = 'Guardian Halberd',        id = 2427,        sell = 11000,        buy = 0},
    {name = 'Hammer of Wrath',        id = 2444,        sell = 30000,        buy = 0},
    {name = 'Headchopper',            id = 7380,        sell = 6000,        buy = 0},
    {name = 'Heavy Mace',            id = 2452,        sell = 50000,        buy = 0},
    {name = 'Heavy Machete',        id = 2442,        sell = 90,            buy = 0},
    {name = 'Heavy Trident',        id = 13838,        sell = 2000,        buy = 0},
    {name = 'Helmet of the Lost',    id = 20132,        sell = 2000,        buy = 0},
    {name = 'Heroic axe',            id = 7389,        sell = 30000,        buy = 0},
    {name = 'Hibiscus Dress',        id = 8873,        sell = 3000,        buy = 0},
    {name = 'Icy Blacksteel Sword',    id = 7766,        sell = 6000,        buy = 0},
    {name = 'Icy Cranial Basher',    id = 7775,        sell = 30000,        buy = 0},
    {name = 'Icy Dragon Slayer',    id = 7767,        sell = 15000,        buy = 0},
    {name = 'Icy Headchopper',        id = 7771,        sell = 6000,        buy = 0},
    {name = 'Icy Heroic axe',        id = 7770,        sell = 30000,        buy = 0},
    {name = 'Icy Mystic Blade',        id = 7765,        sell = 30000,        buy = 0},
    {name = 'Icy Orcish Maul',        id = 7776,        sell = 6000,        buy = 0},
    {name = 'Icy Relic Sword',        id = 7764,        sell = 25000,        buy = 0},
    {name = 'Icy War axe',            id = 7772,        sell = 12000,        buy = 0},
    {name = 'Jade Hammer',            id = 7422,        sell = 25000,        buy = 0},
    {name = 'Krimhorn Helmet',        id = 7461,        sell = 200,            buy = 0},
    {name = 'Lavos Armor',            id = 8877,        sell = 16000,        buy = 0},
    {name = 'Leaf Legs',            id = 9928,        sell = 500,            buy = 0},
    {name = 'Leopard Armor',        id = 3968,        sell = 1000,        buy = 0},
    {name = 'Leviathan\'s Amulet',    id = 10220,        sell = 3000,        buy = 0},
    {name = 'Light Shovel',            id = 5710,        sell = 300,            buy = 0},
    {name = 'Lightning Boots',        id = 7893,        sell = 2500,        buy = 0},
    {name = 'Lightning Headband',    id = 7901,        sell = 2500,        buy = 0},
    {name = 'Lightning Legs',        id = 7895,        sell = 11000,        buy = 0},
    {name = 'Lightning Pendant',    id = 7889,        sell = 1500,        buy = 0},
    {name = 'Lightning Robe',        id = 7898,        sell = 11000,        buy = 0},
    {name = 'Lunar Staff',            id = 7424,        sell = 5000,        buy = 0},
    {name = 'Magic Plate Armor',    id = 2472,        sell = 90000,        buy = 0},
    {name = 'Magma Amulet',            id = 7890,        sell = 1500,        buy = 0},
    {name = 'Magma Boots',            id = 7891,        sell = 2500,        buy = 0},
    {name = 'Magma Coat',            id = 7899,        sell = 11000,        buy = 0},
    {name = 'Magma Legs',            id = 7894,        sell = 11000,        buy = 0},
    {name = 'Magma Monocle',        id = 7900,        sell = 2500,        buy = 0},
    {name = 'Mammoth Fur Cape',        id = 7463,        sell = 6000,        buy = 0},
    {name = 'Mammoth Fur Shorts',    id = 7464,        sell = 850,            buy = 0},
    {name = 'Mammoth Whopper',        id = 7381,        sell = 300,            buy = 0},
    {name = 'Mastermind Shield',    id = 2514,        sell = 50000,        buy = 0},
    {name = 'Medusa Shield',        id = 2536,        sell = 9000,        buy = 0},
    {name = 'Mercenary Sword',        id = 7386,        sell = 12000,        buy = 0},
    {name = 'Mycological Bow',        id = 18454,        sell = 35000,        buy = 0},
    {name = 'Mystic Blade',            id = 7384,        sell = 30000,        buy = 0},
    {name = 'Naginata',                id = 2426,        sell = 2000,        buy = 0},
    {name = 'Nightmare Blade',        id = 7418,        sell = 35000,        buy = 0},
    {name = 'Noble axe',            id = 7456,        sell = 10000,        buy = 0},
    {name = 'Norse Shield',            id = 7460,        sell = 1500,        buy = 0},
    {name = 'Orcish Maul',            id = 7392,        sell = 6000,        buy = 0},
    {name = 'Pair of Iron Fists',    id = 20108,        sell = 4000,        buy = 0},
    {name = 'Paladin Armor',        id = 8891,        sell = 15000,        buy = 0},
    {name = 'Patched Boots',        id = 2641,        sell = 2000,        buy = 0},
    {name = 'Pharaoh Sword',        id = 2446,        sell = 23000,        buy = 0},
    {name = 'Pirate Boots',            id = 5462,        sell = 3000,        buy = 0},
    {name = 'Pirate Hat',            id = 6096,        sell = 1000,        buy = 0},
    {name = 'Pirate Knee Breeches',    id = 5918,        sell = 200,            buy = 0},
    {name = 'Pirate Shirt',            id = 6095,        sell = 500,            buy = 0},
    {name = 'Pirate Voodoo Doll',    id = 5810,        sell = 500,            buy = 0},
    {name = 'Platinum Amulet',        id = 2171,        sell = 2500,        buy = 0},
    {name = 'Ragnir Helmet',        id = 7462,        sell = 400,            buy = 0},
    {name = 'Relic Sword',            id = 7383,        sell = 25000,        buy = 0},
    {name = 'Ring of the Sky',        id = 2123,        sell = 30000,        buy = 0},
    {name = 'Royal axe',            id = 7434,        sell = 40000,        buy = 0},
    {name = 'Ruby Necklace',        id = 2133,        sell = 2000,        buy = 0},
    {name = 'Ruthless axe',            id = 6553,        sell = 45000,        buy = 0},
    {name = 'Sacred Tree Amulet',    id = 10219,        sell = 3000,        buy = 0},
    {name = 'Sapphire Hammer',        id = 7437,        sell = 7000,        buy = 0},
    {name = 'Scarab Amulet',        id = 2135,        sell = 200,            buy = 0},
    {name = 'Scarab Shield',        id = 2540,        sell = 2000,        buy = 0},
    {name = 'Shockwave Amulet',        id = 10221,        sell = 3000,        buy = 0},
    {name = 'Silver Brooch',        id = 2134,        sell = 150,            buy = 0},
    {name = 'Silver Dagger',        id = 2402,        sell = 500,            buy = 0},
    {name = 'Skull Helmet',            id = 5741,        sell = 40000,        buy = 0},
    {name = 'Skullcracker Armor',    id = 8889,        sell = 18000,        buy = 0},
    {name = 'Spiked Squelcher',        id = 7452,        sell = 5000,        buy = 0},
    {name = 'Steel Boots',            id = 2645,        sell = 30000,        buy = 0},
    {name = 'Swamplair Armor',        id = 8880,        sell = 16000,        buy = 0},
    {name = 'Taurus Mace',            id = 7425,        sell = 500,            buy = 0},
    {name = 'Tempest Shield',        id = 2542,        sell = 35000,        buy = 0},
    {name = 'Terra Amulet',            id = 7887,        sell = 1500,        buy = 0},
    {name = 'Terra Boots',            id = 7886,        sell = 2500,        buy = 0},
    {name = 'Terra Hood',            id = 7903,        sell = 2500,        buy = 0},
    {name = 'Terra Legs',            id = 7885,        sell = 11000,        buy = 0},
    {name = 'Terra Mantle',            id = 7884,        sell = 11000,        buy = 0},
    {name = 'The Justice Seeker',    id = 7390,        sell = 40000,        buy = 0},
    {name = 'Tortoise Shield',        id = 6131,        sell = 150,            buy = 0},
    {name = 'Vile axe',                id = 7388,        sell = 30000,        buy = 0},
    {name = 'Voodoo Doll',            id = 2322,        sell = 400,            buy = 0},
    {name = 'War axe',                id = 2454,        sell = 12000,        buy = 0},
    {name = 'War Horn',                id = 2079,        sell = 8000,        buy = 0},
    {name = 'Witch Hat',            id = 10570,        sell = 5000,        buy = 0},
    {name = 'Wyvern Fang',            id = 7408,        sell = 1500,        buy = 0}
}
return list
end

local function creatureSayCallback(cid, type, msg)
    if(not npcHandler:isFocused(cid)) then
        return false
    end
    local player = Player(cid)
    if(msgcontains(msg, "mission")) then
        if(os.date("%A") == "Monday") then
            if(player:getStorageValue(Rashid.MissionStart) < 1) then
                npcHandler:say("Well, you could attempt the mission to become a recognised trader, but it requires a lot of travelling. Are you willing to try?", cid)
                npcHandler.topic[cid] = 1
            elseif(player:getStorageValue(Rashid.MissionStart) == 1) then
                npcHandler:say("Have you managed to obtain a rare deer trophy for my customer?", cid)
                npcHandler.topic[cid] = 3
            end
        elseif(os.date("%A") == "Tuesday") then
            if(player:getStorageValue(Rashid.MissionStart) == 2 and player:getStorageValue(Rashid.MissionStart+1) < 1 ) then
                npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
                npcHandler.topic[cid] = 4
            elseif(player:getStorageValue(Rashid.MissionStart + 1) == 4) then
                npcHandler:say("Did you bring me the package?", cid)
                npcHandler.topic[cid] = 6
            end
        elseif(os.date("%A") == "Wednesday") then
            if(player:getStorageValue(Rashid.MissionStart + 1) == 5 and player:getStorageValue(Rashid.MissionStart + 2) < 1 ) then
                npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
                npcHandler.topic[cid] = 7
            elseif(player:getStorageValue(Rashid.MissionStart + 2) == 2) then
                npcHandler:say("Have you brought the cheese?", cid)
                npcHandler.topic[cid] = 9
            end
        elseif(os.date("%A") == "Thursday") then
            if(player:getStorageValue(Rashid.MissionStart + 2) == 3 and player:getStorageValue(Rashid.MissionStart + 3) < 1) then
                npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
                npcHandler.topic[cid] = 10
            elseif(player:getStorageValue(Rashid.MissionStart + 3) == 2) then
                npcHandler:say("Have you brought the vase?", cid)
                npcHandler.topic[cid] = 12
            end
        elseif(os.date("%A") == "Friday") then
            if(player:getStorageValue(Rashid.MissionStart + 3) == 3 and player:getStorageValue(Rashid.MissionStart + 4) < 1) then
                npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
                npcHandler.topic[cid] = 13
            elseif(player:getStorageValue(Rashid.MissionStart + 4) == 2) then
                npcHandler:say("Have you brought a cheap but good crimson sword?", cid)
                npcHandler.topic[cid] = 15
            end
        elseif(os.date("%A") == "Saturday") then
            if(player:getStorageValue(Rashid.MissionStart + 4) == 3 and player:getStorageValue(Rashid.MissionStart + 5) < 1) then
                npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
                npcHandler.topic[cid] = 16
            elseif(player:getStorageValue(Rashid.MissionStart + 5) == 1) then
                npcHandler:say("Have you brought me a gold fish??", cid)
                npcHandler.topic[cid] = 18
            end
        elseif(os.date("%A") == "Sunday") then
            if(player:getStorageValue(Rashid.MissionStart + 5) == 2 and player:getStorageValue(Rashid.MissionStart + 6) < 1) then
                npcHandler:say("Ah, right. <ahem> I hereby declare you - one of my recognised traders! Feel free to offer me your wares!", cid)
                player:setStorageValue(Rashid.MissionStart + 6, 1)
                --player:AddArchie(id or name)
                npcHandler.topic[cid] = 0
            end
        end
    elseif(msgcontains(msg, "yes")) then
        if(npcHandler.topic[cid] == 1) then
        
            npcHandler:say(
            {
            "Very good! I need talented people who are able to handle my wares with care, find good offers and the like, so I'm going to test you. ...",
            "First, I'd like to see if you can dig up rare wares. Something like a ... mastermind shield! ...",
            "Haha, just kidding, fooled you there, didn't I? Always control your nerves, that's quite important during bargaining. ...",
            "Okay, all I want from you is one of these rare deer trophies. I have a customer here in Svargrond who ordered one, so I'd like you to deliver it tome while I'm in Svargrond. ...",
            "Everything clear and understood?"
            }, cid)
            
            npcHandler.topic[cid] = 2
        elseif(npcHandler.topic[cid] == 2) then
            npcHandler:say("Fine. Then get a hold of that deer trophy and bring it to me while I'm in Svargrond. Just ask me about your mission.", cid)
            player:setStorageValue(Rashid.MissionStart, 1)
            npcHandler.topic[cid] = 0
        elseif(npcHandler.topic[cid] == 3) then
            if(player:getItemCount(7397) >= 1) then
                player:removeItem(7397, 1)
                npcHandler:say("Well done! I'll take that from you. <snags it> Come see me another day, I'll be busy for a while now. ", cid)
                player:setStorageValue(Rashid.MissionStart, 2)
                npcHandler.topic[cid] = 0
            end
        elseif(npcHandler.topic[cid] == 4) then
            npcHandler:say(
            {
            "Alright, that's good to hear. From you as my trader and deliveryman, I expect more than finding rare items. ...",
            "You also need to be able to transport heavy wares, weaklings won't get far here. I have ordered a special package from Edron. ...",
            "Pick it up from Willard and bring it back to me while I'm in Liberty Bay. Everything clear and understood?"
            }, cid)
            npcHandler.topic[cid] = 5
        elseif(npcHandler.topic[cid] == 5) then
            npcHandler:say("Fine. Then off you go, just ask Willard about the 'package for Rashid'.", cid)
            player:setStorageValue(Rashid.MissionStart + 1, 1)
            npcHandler.topic[cid] = 0
        elseif(npcHandler.topic[cid] == 6) then
            if(player:getItemCount(7503) >= 1) then
                player:removeItem(7503, 1)
                npcHandler:say("Great. Just place it over there - yes, thanks, that's it. Come see me another day, I'll be busy for a while now. ", cid)
                player:setStorageValue(Rashid.MissionStart + 1, 5)
                npcHandler.topic[cid] = 0
            end
        elseif(npcHandler.topic[cid] == 7) then
            npcHandler:say(
            {
            "Well, that's good to hear. From you as my trader and deliveryman, I expect more than carrying heavy packages. ...",
            "You also need to be fast and deliver wares in time. I have ordered a very special cheese wheel made from Darashian milk. ...",
            "Unfortunately, the high temperature in the desert makes it rot really fast, so it must not stay in the sun for too long. ...",
            "I'm also afraid that you might not be able to use ships because of the smell of the cheese. ...",
            "Please get the cheese from Miraia and bring it to me while I'm in Port Hope. Everything clear and understood?"
            }, cid)
            npcHandler.topic[cid] = 8
        elseif(npcHandler.topic[cid] == 8) then
            npcHandler:say("Okay, then please find Miraia in Darashia and ask her about the {'scarab cheese'}.", cid)
            player:setStorageValue(Rashid.MissionStart + 2, 1)
            npcHandler.topic[cid] = 0
        elseif(npcHandler.topic[cid] == 9) then
            if(player:getItemCount(8112) >= 1) then
                player:removeItem(8112, 1)
                --if (player:getStorageValue(ExplorerSocietySTORAGE) == 1) then
                    --player:AddArchie(Just in Time)
                --end
                npcHandler:say("Mmmhh, the lovely odeur of scarab cheese! I really can't understand why most people can't stand it. Thanks, well done! ", cid)
                player:setStorageValue(Rashid.MissionStart + 2, 3)
                npcHandler.topic[cid] = 0
            end
        elseif(npcHandler.topic[cid] == 10) then
            npcHandler:say(
            {
            "Well, that's good to hear. From you as my trader and deliveryman, I expect more than bringing stinky cheese. ...",
            "I wonder if you are able to deliver goods so fragile they almost break when looked at. ...",
            "I have ordered a special elven vase from Briasol in Ab'Dendriel. Get it from him and don't even touch it, just bring it to me while I'm in Ankrahmun. Everything clear and understood?"
            }, cid)
            npcHandler.topic[cid] = 11
        elseif(npcHandler.topic[cid] == 11) then
            npcHandler:say("Okay, then please find {Briasol} in {Ab'Dendriel} and ask for a {'fine vase'}.", cid)
            player:setStorageValue(Rashid.MissionStart + 3, 1)
            player:addMoney(1000)
            npcHandler.topic[cid] = 0
        elseif(npcHandler.topic[cid] == 12) then
            if(player:getItemCount(8760) >= 1) then
                player:removeItem(8760, 1)
                npcHandler:say("I'm surprised that you managed to bring this vase without a single crack. That was what I needed to know, thank you. ", cid)
                player:setStorageValue(Rashid.MissionStart + 3, 3)
                npcHandler.topic[cid] = 0
            end
        elseif(npcHandler.topic[cid] == 13) then
            npcHandler:say(
            {
            "Fine! There's one more skill that I need to test and which is cruicial for a successful trader. ...",
            "Of course you must be able to haggle, else you won't survive long in this business. To make things as hard as possible for you, I have the perfect trade partner for you. ...",
            "Dwarves are said to be the most stubborn of all traders. Travel to {Kazordoon} and try to get the smith {Uzgod} to sell a {crimson sword} to you. ...",
            "Of course, it has to be cheap. Don't come back with anything more expensive than 400 gold. ...",
            "And the quality must not suffer, of course! Everything clear and understood?",
            "Dwarves are said to be the most stubborn of all traders. Travel to Kazordoon and try to get the smith Uzgod to sell a crimson sword to you. ..."
            }, cid)
            npcHandler.topic[cid] = 14
        elseif(npcHandler.topic[cid] == 14) then
            npcHandler:say("Okay, I'm curious how you will do with {Uzgod}. Good luck!", cid)
            player:setStorageValue(Rashid.MissionStart + 4, 1)
            npcHandler.topic[cid] = 0
        elseif(npcHandler.topic[cid] == 15) then
            if(player:getItemCount(7385) >= 1) then
                player:removeItem(7385, 1)
                npcHandler:say("Ha! You are clever indeed, well done! I'll take this from you. Come see me tomorrow, I think we two might get into business after all.", cid)
                player:setStorageValue(Rashid.MissionStart + 4, 3)
                npcHandler.topic[cid] = 0
            end
        elseif(npcHandler.topic[cid] == 16) then
            npcHandler:say(
            {
            "My friend, it seems you have already learnt a lot about the art of trading. I think you are more than worthy to become a recognised trader. ...",
            "There is just one little favour that I would ask from you... something personal, actually, forgive my boldness. ...",
            "I have always dreamed to have a small pet, one that I could take with me and which wouldn't cause problems. ...",
            "Could you - just maybe - bring me a small goldfish in a bowl? I know that you would be able to get one, wouldn't you?"
            }, cid)
            npcHandler.topic[cid] = 17
        elseif(npcHandler.topic[cid] == 17) then
            npcHandler:say("Thanks so much! I'll be waiting eagerly for your return then.", cid)
            player:setStorageValue(Rashid.MissionStart + 5, 1)
            npcHandler.topic[cid] = 0
        elseif(npcHandler.topic[cid] == 18) then
            if(player:getItemCount(8766) >= 1) then
                player:removeItem(8766, 1)
                npcHandler:say("Thank you!! Ah, this makes my day! I'll take the rest of the day off to get to know this little guy. Come see me tomorrow, if you like.", cid)
                player:setStorageValue(Rashid.MissionStart + 5, 2)
                npcHandler.topic[cid] = 0
            end
        end
    end
    return true
end

local function onTradeRequest(cid)
    TradeRequest(cid, npcHandler, getTable(), Rashid, 1)
end


npcHandler:setCallback(CALLBACK_ONTRADEREQUEST, onTradeRequest)
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())
 

npcHancler:

Spoiler

-- Advanced NPC System by Jiddo

if NpcHandler == nil then
    -- Constant talkdelay behaviors.
    TALKDELAY_NONE = 0 -- No talkdelay. Npc will reply immedeatly.
    TALKDELAY_ONTHINK = 1 -- Talkdelay handled through the onThink callback function. (Default)
    TALKDELAY_EVENT = 2 -- Not yet implemented

    -- Currently applied talkdelay behavior. TALKDELAY_ONTHINK is default.
    NPCHANDLER_TALKDELAY = TALKDELAY_ONTHINK

    -- Constant indexes for defining default messages.
    MESSAGE_GREET             = 1 -- When the player greets the npc.
    MESSAGE_FAREWELL         = 2 -- When the player unGreets the npc.
    MESSAGE_BUY             = 3 -- When the npc asks the player if he wants to buy something.
    MESSAGE_ONBUY             = 4 -- When the player successfully buys something via talk.
    MESSAGE_BOUGHT            = 5 -- When the player bought something through the shop window.
    MESSAGE_SELL             = 6 -- When the npc asks the player if he wants to sell something.
    MESSAGE_ONSELL             = 7 -- When the player successfully sells something via talk.
    MESSAGE_SOLD            = 8 -- When the player sold something through the shop window.
    MESSAGE_MISSINGMONEY        = 9 -- When the player does not have enough money.
    MESSAGE_NEEDMONEY        = 10 -- Same as above, used for shop window.
    MESSAGE_MISSINGITEM        = 11 -- When the player is trying to sell an item he does not have.
    MESSAGE_NEEDITEM        = 12 -- Same as above, used for shop window.
    MESSAGE_NEEDSPACE         = 13 -- When the player don't have any space to buy an item
    MESSAGE_NEEDMORESPACE        = 14 -- When the player has some space to buy an item, but not enough space
    MESSAGE_IDLETIMEOUT        = 15 -- When the player has been idle for longer then idleTime allows.
    MESSAGE_WALKAWAY        = 16 -- When the player walks out of the talkRadius of the npc.
    MESSAGE_DECLINE            = 17 -- When the player says no to something.
    MESSAGE_SENDTRADE        = 18 -- When the npc sends the trade window to the player
    MESSAGE_NOSHOP            = 19 -- When the npc's shop is requested but he doesn't have any
    MESSAGE_ONCLOSESHOP        = 20 -- When the player closes the npc's shop window
    MESSAGE_ALREADYFOCUSED        = 21 -- When the player already has the focus of this npc.
    MESSAGE_WALKAWAY_MALE        = 22 -- When a male player walks out of the talkRadius of the npc.
    MESSAGE_WALKAWAY_FEMALE        = 23 -- When a female player walks out of the talkRadius of the npc.

    -- Constant indexes for callback functions. These are also used for module callback ids.
    CALLBACK_CREATURE_APPEAR     = 1
    CALLBACK_CREATURE_DISAPPEAR    = 2
    CALLBACK_CREATURE_SAY         = 3
    CALLBACK_ONTHINK         = 4
    CALLBACK_GREET             = 5
    CALLBACK_FAREWELL         = 6
    CALLBACK_MESSAGE_DEFAULT     = 7
    CALLBACK_PLAYER_ENDTRADE     = 8
    CALLBACK_PLAYER_CLOSECHANNEL    = 9
    CALLBACK_ONBUY            = 10
    CALLBACK_ONSELL            = 11
    CALLBACK_ONADDFOCUS        = 18
    CALLBACK_ONRELEASEFOCUS        = 19
    CALLBACK_ONTRADEREQUEST        = 20

    -- Addidional module callback ids
    CALLBACK_MODULE_INIT        = 12
    CALLBACK_MODULE_RESET        = 13

    -- Constant strings defining the keywords to replace in the default messages.
    TAG_PLAYERNAME = "|PLAYERNAME|"
    TAG_ITEMCOUNT = "|ITEMCOUNT|"
    TAG_TOTALCOST = "|TOTALCOST|"
    TAG_ITEMNAME = "|ITEMNAME|"

    NpcHandler = {
        keywordHandler = nil,
        focuses = nil,
        talkStart = nil,
        idleTime = 120,
        talkRadius = 3,
        talkDelayTime = 1, -- Seconds to delay outgoing messages.
        talkDelay = nil,
        callbackFunctions = nil,
        modules = nil,
        shopItems = nil, -- They must be here since ShopModule uses 'static' functions
        eventSay = nil,
        eventDelayedSay = nil,
        topic = nil,
        messages = {
            -- These are the default replies of all npcs. They can/should be changed individually for each npc.
            [MESSAGE_GREET]        = "Greetings, |PLAYERNAME|.",
            [MESSAGE_FAREWELL]     = "Good bye, |PLAYERNAME|.",
            [MESSAGE_BUY]         = "Do you want to buy |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?",
            [MESSAGE_ONBUY]        = "Here you are.",
            [MESSAGE_BOUGHT]     = "Bought |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.",
            [MESSAGE_SELL]         = "Do you want to sell |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?",
            [MESSAGE_ONSELL]     = "Here you are, |TOTALCOST| gold.",
            [MESSAGE_SOLD]         = "Sold |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.",
            [MESSAGE_MISSINGMONEY]    = "You don't have enough money.",
            [MESSAGE_NEEDMONEY]     = "You don't have enough money.",
            [MESSAGE_MISSINGITEM]     = "You don't have so many.",
            [MESSAGE_NEEDITEM]    = "You do not have this object.",
            [MESSAGE_NEEDSPACE]    = "You do not have enough capacity.",
            [MESSAGE_NEEDMORESPACE]    = "You do not have enough capacity for all items.",
            [MESSAGE_IDLETIMEOUT]     = "Good bye.",
            [MESSAGE_WALKAWAY]     = "Good bye.",
            [MESSAGE_DECLINE]    = "Then not.",
            [MESSAGE_SENDTRADE]    = "Of course, just browse through my wares.",
            [MESSAGE_NOSHOP]    = "Sorry, I'm not offering anything.",
            [MESSAGE_ONCLOSESHOP]    = "Thank you, come back whenever you're in need of something else.",
            [MESSAGE_ALREADYFOCUSED]= "|PLAYERNAME|, I am already talking to you.",
            [MESSAGE_WALKAWAY_MALE]    = "Good bye.",
            [MESSAGE_WALKAWAY_FEMALE]     = "Good bye."
        }
    }

    -- Creates a new NpcHandler with an empty callbackFunction stack.
    function NpcHandler:new(keywordHandler)
        local obj = {}
        obj.callbackFunctions = {}
        obj.modules = {}
        obj.eventSay = {}
        obj.eventDelayedSay = {}
        obj.topic = {}
        obj.focuses = {}
        obj.talkStart = {}
        obj.talkDelay = {}
        obj.keywordHandler = keywordHandler
        obj.messages = {}
        obj.shopItems = {}

        setmetatable(obj.messages, self.messages)
        self.messages.__index = self.messages

        setmetatable(obj, self)
        self.__index = self
        return obj
    end

    -- Re-defines the maximum idle time allowed for a player when talking to this npc.
    function NpcHandler:setMaxIdleTime(newTime)
        self.idleTime = newTime
    end

    -- Attackes a new keyword handler to this npchandler
    function NpcHandler:setKeywordHandler(newHandler)
        self.keywordHandler = newHandler
    end

    -- Function used to change the focus of this npc.
    function NpcHandler:addFocus(newFocus)
        if self:isFocused(newFocus) then
            return
        end

        self.focuses[#self.focuses + 1] = newFocus
        self.topic[newFocus] = 0
        local callback = self:getCallback(CALLBACK_ONADDFOCUS)
        if callback == nil or callback(newFocus) then
            self:processModuleCallback(CALLBACK_ONADDFOCUS, newFocus)
        end
        self:updateFocus()
    end

    -- Function used to verify if npc is focused to certain player
    function NpcHandler:isFocused(focus)
        for k,v in pairs(self.focuses) do
            if v == focus then
                return true
            end
        end
        return false
    end

    -- This function should be called on each onThink and makes sure the npc faces the player it is talking to.
    --    Should also be called whenever a new player is focused.
    function NpcHandler:updateFocus()
        for pos, focus in pairs(self.focuses) do
            if focus ~= nil then
                doNpcSetCreatureFocus(focus)
                return
            end
        end
        doNpcSetCreatureFocus(0)
    end

    -- Used when the npc should un-focus the player.
    function NpcHandler:releaseFocus(focus)
        if shop_cost[focus] ~= nil then
            table.remove(shop_amount, focus)
            table.remove(shop_cost, focus)
            table.remove(shop_rlname, focus)
            table.remove(shop_itemid, focus)
            table.remove(shop_container, focus)
            table.remove(shop_npcuid, focus)
            table.remove(shop_eventtype, focus)
            table.remove(shop_subtype, focus)
            table.remove(shop_destination, focus)
            table.remove(shop_premium, focus)
        end

        if self.eventDelayedSay[focus] then
            self:cancelNPCTalk(self.eventDelayedSay[focus])
        end

        if not self:isFocused(focus) then
            return
        end

        local pos = nil
        for k,v in pairs(self.focuses) do
            if v == focus then
                pos = k
            end
        end
        table.remove(self.focuses, pos)

        self.eventSay[focus] = nil
        self.eventDelayedSay[focus] = nil
        self.talkStart[focus] = nil
        self.topic[focus] = nil

        local callback = self:getCallback(CALLBACK_ONRELEASEFOCUS)
        if callback == nil or callback(focus) then
            self:processModuleCallback(CALLBACK_ONRELEASEFOCUS, focus)
        end

        if Player(focus) ~= nil then
            closeShopWindow(focus) --Even if it can not exist, we need to prevent it.
            self:updateFocus()
        end
    end

    -- Returns the callback function with the specified id or nil if no such callback function exists.
    function NpcHandler:getCallback(id)
        local ret = nil
        if self.callbackFunctions ~= nil then
            ret = self.callbackFunctions[id]
        end
        return ret
    end

    -- Changes the callback function for the given id to callback.
    function NpcHandler:setCallback(id, callback)
        if self.callbackFunctions ~= nil then
            self.callbackFunctions[id] = callback
        end
    end

    -- Adds a module to this npchandler and inits it.
    function NpcHandler:addModule(module)
        if self.modules ~= nil then
            self.modules[#self.modules +1] = module
            module:init(self)
        end
    end

    -- Calls the callback function represented by id for all modules added to this npchandler with the given arguments.
    function NpcHandler:processModuleCallback(id, ...)
        local ret = true
        for i, module in pairs(self.modules) do
            local tmpRet = true
            if id == CALLBACK_CREATURE_APPEAR and module.callbackOnCreatureAppear ~= nil then
                tmpRet = module:callbackOnCreatureAppear(...)
            elseif id == CALLBACK_CREATURE_DISAPPEAR and module.callbackOnCreatureDisappear ~= nil then
                tmpRet = module:callbackOnCreatureDisappear(...)
            elseif id == CALLBACK_CREATURE_SAY and module.callbackOnCreatureSay ~= nil then
                tmpRet = module:callbackOnCreatureSay(...)
            elseif id == CALLBACK_PLAYER_ENDTRADE and module.callbackOnPlayerEndTrade ~= nil then
                tmpRet = module:callbackOnPlayerEndTrade(...)
            elseif id == CALLBACK_PLAYER_CLOSECHANNEL and module.callbackOnPlayerCloseChannel ~= nil then
                tmpRet = module:callbackOnPlayerCloseChannel(...)
            elseif id == CALLBACK_ONBUY and module.callbackOnBuy ~= nil then
                tmpRet = module:callbackOnBuy(...)
            elseif id == CALLBACK_ONSELL and module.callbackOnSell ~= nil then
                tmpRet = module:callbackOnSell(...)
            elseif id == CALLBACK_ONTRADEREQUEST and module.callbackOnTradeRequest ~= nil then
                tmpRet = module:callbackOnTradeRequest(...)
            elseif id == CALLBACK_ONADDFOCUS and module.callbackOnAddFocus ~= nil then
                tmpRet = module:callbackOnAddFocus(...)
            elseif id == CALLBACK_ONRELEASEFOCUS and module.callbackOnReleaseFocus ~= nil then
                tmpRet = module:callbackOnReleaseFocus(...)
            elseif id == CALLBACK_ONTHINK and module.callbackOnThink ~= nil then
                tmpRet = module:callbackOnThink(...)
            elseif id == CALLBACK_GREET and module.callbackOnGreet ~= nil then
                tmpRet = module:callbackOnGreet(...)
            elseif id == CALLBACK_FAREWELL and module.callbackOnFarewell ~= nil then
                tmpRet = module:callbackOnFarewell(...)
            elseif id == CALLBACK_MESSAGE_DEFAULT and module.callbackOnMessageDefault ~= nil then
                tmpRet = module:callbackOnMessageDefault(...)
            elseif id == CALLBACK_MODULE_RESET and module.callbackOnModuleReset ~= nil then
                tmpRet = module:callbackOnModuleReset(...)
            end
            if not tmpRet then
                ret = false
                break
            end
        end
        return ret
    end

    -- Returns the message represented by id.
    function NpcHandler:getMessage(id)
        local ret = nil
        if self.messages ~= nil then
            ret = self.messages[id]
        end
        return ret
    end

    -- Changes the default response message with the specified id to newMessage.
    function NpcHandler:setMessage(id, newMessage)
        if self.messages ~= nil then
            self.messages[id] = newMessage
        end
    end

    -- Translates all message tags found in msg using parseInfo
    function NpcHandler:parseMessage(msg, parseInfo)
        local ret = msg
        for search, replace in pairs(parseInfo) do
            ret = string.gsub(ret, search, replace)
        end
        return ret
    end

    -- Makes sure the npc un-focuses the currently focused player
    function NpcHandler:unGreet(cid)
        if not self:isFocused(cid) then
            return
        end

        local callback = self:getCallback(CALLBACK_FAREWELL)
        if callback == nil or callback() then
            if self:processModuleCallback(CALLBACK_FAREWELL) then
                local msg = self:getMessage(MESSAGE_FAREWELL)
                local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
                self:resetNpc(cid)
                msg = self:parseMessage(msg, parseInfo)
                self:say(msg, cid, true)
                self:releaseFocus(cid)
            end
        end
    end

    -- Greets a new player.
    function NpcHandler:greet(cid)
        if cid ~= 0  then
            local callback = self:getCallback(CALLBACK_GREET)
            if callback == nil or callback(cid) then
                if self:processModuleCallback(CALLBACK_GREET, cid) then
                    local msg = self:getMessage(MESSAGE_GREET)
                    local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
                    msg = self:parseMessage(msg, parseInfo)
                    self:say(msg, cid, true)
                else
                    return
                end
            else
                return
            end
        end
        self:addFocus(cid)
    end

    -- Handles onCreatureAppear events. If you with to handle this yourself, please use the CALLBACK_CREATURE_APPEAR callback.
    function NpcHandler:onCreatureAppear(cid)
        if cid == getNpcCid() and next(self.shopItems) ~= nil then
            local npc = Npc()
            local speechBubble = npc:getSpeechBubble()
            if speechBubble == 3 then
                npc:setSpeechBubble(4)
            else
                npc:setSpeechBubble(2)
            end
        end

        local callback = self:getCallback(CALLBACK_CREATURE_APPEAR)
        if callback == nil or callback(cid) then
            if self:processModuleCallback(CALLBACK_CREATURE_APPEAR, cid) then
                --
            end
        end
    end

    -- Handles onCreatureDisappear events. If you with to handle this yourself, please use the CALLBACK_CREATURE_DISAPPEAR callback.
    function NpcHandler:onCreatureDisappear(cid)
        if getNpcCid() == cid then
            return
        end

        local callback = self:getCallback(CALLBACK_CREATURE_DISAPPEAR)
        if callback == nil or callback(cid) then
            if self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid) then
                if self:isFocused(cid) then
                    self:unGreet(cid)
                end
            end
        end
    end

    -- Handles onCreatureSay events. If you with to handle this yourself, please use the CALLBACK_CREATURE_SAY callback.
    function NpcHandler:onCreatureSay(cid, msgtype, msg)
        local callback = self:getCallback(CALLBACK_CREATURE_SAY)
        if callback == nil or callback(cid, msgtype, msg) then
            if self:processModuleCallback(CALLBACK_CREATURE_SAY, cid, msgtype, msg) then
                if not self:isInRange(cid) then
                    return
                end

                if self.keywordHandler ~= nil then
                    if self:isFocused(cid) and msgtype == TALKTYPE_PRIVATE_PN or not self:isFocused(cid) then
                        local ret = self.keywordHandler:processMessage(cid, msg)
                        if(not ret) then
                            local callback = self:getCallback(CALLBACK_MESSAGE_DEFAULT)
                            if callback ~= nil and callback(cid, msgtype, msg) then
                                self.talkStart[cid] = os.time()
                            end
                        else
                            self.talkStart[cid] = os.time()
                        end
                    end
                end
            end
        end
    end

    -- Handles onPlayerEndTrade events. If you wish to handle this yourself, use the CALLBACK_PLAYER_ENDTRADE callback.
    function NpcHandler:onPlayerEndTrade(cid)
        local callback = self:getCallback(CALLBACK_PLAYER_ENDTRADE)
        if callback == nil or callback(cid) then
            if self:processModuleCallback(CALLBACK_PLAYER_ENDTRADE, cid, msgtype, msg) then
                if self:isFocused(cid) then
                    local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
                    local msg = self:parseMessage(self:getMessage(MESSAGE_ONCLOSESHOP), parseInfo)
                    self:say(msg, cid)
                end
            end
        end
    end

    -- Handles onPlayerCloseChannel events. If you wish to handle this yourself, use the CALLBACK_PLAYER_CLOSECHANNEL callback.
    function NpcHandler:onPlayerCloseChannel(cid)
        local callback = self:getCallback(CALLBACK_PLAYER_CLOSECHANNEL)
        if callback == nil or callback(cid) then
            if self:processModuleCallback(CALLBACK_PLAYER_CLOSECHANNEL, cid, msgtype, msg) then
                if self:isFocused(cid) then
                    self:unGreet(cid)
                end
            end
        end
    end

    -- Handles onBuy events. If you wish to handle this yourself, use the CALLBACK_ONBUY callback.
    function NpcHandler:onBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks)
        local callback = self:getCallback(CALLBACK_ONBUY)
        if callback == nil or callback(cid, itemid, subType, amount, ignoreCap, inBackpacks) then
            if self:processModuleCallback(CALLBACK_ONBUY, cid, itemid, subType, amount, ignoreCap, inBackpacks) then
                --
            end
        end
    end

    -- Handles onSell events. If you wish to handle this yourself, use the CALLBACK_ONSELL callback.
    function NpcHandler:onSell(cid, itemid, subType, amount, ignoreCap, inBackpacks)
        local callback = self:getCallback(CALLBACK_ONSELL)
        if callback == nil or callback(cid, itemid, subType, amount, ignoreCap, inBackpacks) then
            if self:processModuleCallback(CALLBACK_ONSELL, cid, itemid, subType, amount, ignoreCap, inBackpacks) then
                --
            end
        end
    end

    -- Handles onTradeRequest events. If you wish to handle this yourself, use the CALLBACK_ONTRADEREQUEST callback.
    function NpcHandler:onTradeRequest(cid)
        local callback = self:getCallback(CALLBACK_ONTRADEREQUEST)
        if callback == nil or callback(cid) then
            if self:processModuleCallback(CALLBACK_ONTRADEREQUEST, cid) then
                return true
            end
        end
        return false
    end

    -- Handles onThink events. If you wish to handle this yourself, please use the CALLBACK_ONTHINK callback.
    function NpcHandler:onThink()
        local callback = self:getCallback(CALLBACK_ONTHINK)
        if callback == nil or callback() then
            if NPCHANDLER_TALKDELAY == TALKDELAY_ONTHINK then
                for cid, talkDelay in pairs(self.talkDelay) do
                    if talkDelay.time ~= nil and talkDelay.message ~= nil and os.time() >= talkDelay.time then
                        selfSay(talkDelay.message, cid, talkDelay.publicize and true or false)
                        self.talkDelay[cid] = nil
                    end
                end
            end

            if self:processModuleCallback(CALLBACK_ONTHINK) then
                for pos, focus in pairs(self.focuses) do
                    if focus ~= nil then
                        if not self:isInRange(focus) then
                            self:onWalkAway(focus)
                        elseif self.talkStart[focus] ~= nil and (os.time() - self.talkStart[focus]) > self.idleTime then
                            self:unGreet(focus)
                        else
                            self:updateFocus()
                        end
                    end
                end
            end
        end
    end

    -- Tries to greet the player with the given cid.
    function NpcHandler:onGreet(cid)
        if self:isInRange(cid) then
            if not self:isFocused(cid) then
                self:greet(cid)
                return
            end
        end
    end

    -- Simply calls the underlying unGreet function.
    function NpcHandler:onFarewell(cid)
        self:unGreet(cid)
    end

    -- Should be called on this npc's focus if the distance to focus is greater then talkRadius.
    function NpcHandler:onWalkAway(cid)
        if self:isFocused(cid) then
            local callback = self:getCallback(CALLBACK_CREATURE_DISAPPEAR)
            if callback == nil or callback() then
                if self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid) then
                    local msg = self:getMessage(MESSAGE_WALKAWAY)
                    local playerName = Player(cid):getName()
                    if not playerName then
                        playerName = -1
                    end

                    local parseInfo = { [TAG_PLAYERNAME] = playerName }
                    local message = self:parseMessage(msg, parseInfo)

                    local msg_male = self:getMessage(MESSAGE_WALKAWAY_MALE)
                    local message_male = self:parseMessage(msg_male, parseInfo)
                    local msg_female = self:getMessage(MESSAGE_WALKAWAY_FEMALE)
                    local message_female = self:parseMessage(msg_female, parseInfo)
                    if message_female ~= message_male then
                        if Player(cid):getSex() == 0 then
                            selfSay(message_female)
                        else
                            selfSay(message_male)
                        end
                    elseif message ~= "" then
                        selfSay(message)
                    end
                    self:resetNpc(cid)
                    self:releaseFocus(cid)
                end
            end
        end
    end

    -- Returns true if cid is within the talkRadius of this npc.
    function NpcHandler:isInRange(cid)
        local distance = getDistanceBetween(getCreaturePosition(getNpcCid()), getCreaturePosition(cid))
        if(distance == -1) then
            return false
        end

        return (distance <= self.talkRadius)
    end

    -- Resets the npc into its initial state (in regard of the keywordhandler).
    --    All modules are also receiving a reset call through their callbackOnModuleReset function.
    function NpcHandler:resetNpc(cid)
        if self:processModuleCallback(CALLBACK_MODULE_RESET) then
            self.keywordHandler:reset(cid)
        end
    end

    function NpcHandler:cancelNPCTalk(events)
        for aux = 1, #events do
            stopEvent(events[aux].event)
        end
        events = nil
    end

    function NpcHandler:doNPCTalkALot(msgs, interval, pcid)
        if self.eventDelayedSay[pcid] then
            self:cancelNPCTalk(self.eventDelayedSay[pcid])
        end

        self.eventDelayedSay[pcid] = {}
        local ret = {}
        for aux = 1, #msgs do
            self.eventDelayedSay[pcid][aux] = {}
            doCreatureSayWithDelay(getNpcCid(), msgs[aux], TALKTYPE_PRIVATE_NP, ((aux-1) * (interval or 4000)) + 700, self.eventDelayedSay[pcid][aux], pcid)
            ret[#ret +1] = self.eventDelayedSay[pcid][aux]
        end
        return(ret)
    end

    -- Makes the npc represented by this instance of NpcHandler say something.
    --    This implements the currently set type of talkdelay.
    --    shallDelay is a boolean value. If it is false, the message is not delayed. Default value is true.
    function NpcHandler:say(message, focus, publicize, shallDelay, delay)
        if type(message) == "table" then
            return self:doNPCTalkALot(message, delay or 6000, focus)
        end

        if self.eventDelayedSay[focus] then
            self:cancelNPCTalk(self.eventDelayedSay[focus])
        end

        local shallDelay = not shallDelay and true or shallDelay
        if NPCHANDLER_TALKDELAY == TALKDELAY_NONE or shallDelay == false then
            selfSay(message, focus, publicize and true or false)
            return
        end

        stopEvent(self.eventSay[focus])
        self.eventSay[focus] = addEvent(function(x) if isPlayer(x[3]) then doCreatureSay(x[1], x[2], TALKTYPE_PRIVATE_NP, false, x[3], getCreaturePosition(x[1])) end end, self.talkDelayTime * 1000, {getNpcCid(), message, focus})
    end
end
 

Modules:

Spoiler

-- Advanced NPC System by Jiddo

if Modules == nil then
    -- default words for greeting and ungreeting the npc. Should be a table containing all such words.
    FOCUS_GREETWORDS = {"hi", "hello"}
    FOCUS_FAREWELLWORDS = {"bye", "farewell"}

    -- The words for requesting trade window.
    SHOP_TRADEREQUEST = {"trade"}

    -- The word for accepting/declining an offer. CAN ONLY CONTAIN ONE FIELD! Should be a table with a single string value.
    SHOP_YESWORD = {"yes"}
    SHOP_NOWORD = {"no"}

    -- Pattern used to get the amount of an item a player wants to buy/sell.
    PATTERN_COUNT = "%d+"

    -- Constants used to separate buying from selling.
    SHOPMODULE_SELL_ITEM = 1
    SHOPMODULE_BUY_ITEM = 2
    SHOPMODULE_BUY_ITEM_CONTAINER = 3

    -- Constants used for shop mode. Notice: addBuyableItemContainer is working on all modes
    SHOPMODULE_MODE_TALK = 1 -- Old system used before client version 8.2: sell/buy item name
    SHOPMODULE_MODE_TRADE = 2 -- Trade window system introduced in client version 8.2
    SHOPMODULE_MODE_BOTH = 3 -- Both working at one time

    -- Used shop mode
    SHOPMODULE_MODE = SHOPMODULE_MODE_BOTH

    Modules = {
        parseableModules = {}
    }

    StdModule = {}

    -- These callback function must be called with parameters.npcHandler = npcHandler in the parameters table or they will not work correctly.
    -- Notice: The members of StdModule have not yet been tested. If you find any bugs, please report them to me.
    -- Usage:
        -- keywordHandler:addKeyword({"offer"}, StdModule.say, {npcHandler = npcHandler, text = "I sell many powerful melee weapons."})
    function StdModule.say(cid, message, keywords, parameters, node)
        local npcHandler = parameters.npcHandler
        if npcHandler == nil then
            error("StdModule.say called without any npcHandler instance.")
        end
        local onlyFocus = (parameters.onlyFocus == nil or parameters.onlyFocus == true)
        if not npcHandler:isFocused(cid) and onlyFocus then
            return false
        end

        local parseInfo = {[TAG_PLAYERNAME] = Player(cid):getName()}
        npcHandler:say(npcHandler:parseMessage(parameters.text or parameters.message, parseInfo), cid, parameters.publicize and true)
        if not parameters.reset == false then
            npcHandler:resetNpc(cid)
        elseif parameters.moveup ~= nil then
            npcHandler.keywordHandler:moveUp(parameters.moveup)
        end

        return true
    end

    --Usage:
        -- local node1 = keywordHandler:addKeyword({"promot"}, StdModule.say, {npcHandler = npcHandler, text = "I can promote you for 20000 gold coins. Do you want me to promote you?"})
        --         node1:addChildKeyword({"yes"}, StdModule.promotePlayer, {npcHandler = npcHandler, cost = 20000, level = 20}, text = "Congratulations! You are now promoted.")
        --         node1:addChildKeyword({"no"}, StdModule.say, {npcHandler = npcHandler, text = "Allright then. Come back when you are ready."}, reset = true)
    function StdModule.promotePlayer(cid, message, keywords, parameters, node)
        local npcHandler = parameters.npcHandler
        if npcHandler == nil then
            error("StdModule.promotePlayer called without any npcHandler instance.")
        end
        if not npcHandler:isFocused(cid) then
            return false
        end

        if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then
            local promotedVoc = getPromotedVocation(getPlayerVocation(cid))
            if(getPlayerStorageValue(cid, 30018) == 1) then
                npcHandler:say("You are already promoted!", cid)
            elseif(getPlayerLevel(cid) < parameters.level) then
                npcHandler:say("I am sorry, but I can only promote you once you have reached level " .. parameters.level .. ".", cid)
            elseif(doPlayerRemoveMoney(cid, parameters.cost) ~= TRUE) then
                npcHandler:say("You do not have enough money!", cid)
            else
                doPlayerSetVocation(cid, promotedVoc)
                npcHandler:say(parameters.text, cid)
            end
        else
            npcHandler:say("You need a premium account in order to get promoted.", cid)
        end
        npcHandler:resetNpc(cid)
        return true
    end

    function StdModule.learnSpell(cid, message, keywords, parameters, node)
        local npcHandler = parameters.npcHandler
        if npcHandler == nil then
            error("StdModule.learnSpell called without any npcHandler instance.")
        end

        if not npcHandler:isFocused(cid) then
            return false
        end

        if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) or not parameters.premium) then
            if getPlayerLearnedInstantSpell(cid, parameters.spellName) == TRUE then
                npcHandler:say("You already know this spell.", cid)
            elseif getPlayerLevel(cid) < parameters.level then
                npcHandler:say("You need to obtain a level of " .. parameters.level .. " or higher to be able to learn " .. parameters.spellName .. ".", cid)
            elseif getPlayerVocation(cid) ~= parameters.vocation and getPlayerVocation(cid) ~= parameters.vocation + 4 and vocation ~= 9 then
                npcHandler:say("This spell is not for your vocation", cid)
            elseif doPlayerRemoveMoney(cid, parameters.price) == FALSE then
                npcHandler:say("You do not have enough money, this spell costs " .. parameters.price .. " gold.", cid)
            else
                npcHandler:say("You have learned " .. parameters.spellName .. ".", cid)
                playerLearnInstantSpell(cid, parameters.spellName)
            end
        else
            npcHandler:say("You need a premium account in order to buy " .. parameters.spellName .. ".", cid)
        end

        npcHandler:resetNpc(cid)
        return true
    end

    function StdModule.bless(cid, message, keywords, parameters, node)
        local npcHandler = parameters.npcHandler
        if npcHandler == nil then
            error("StdModule.bless called without any npcHandler instance.")
        end

        if(not npcHandler:isFocused(cid) or getWorldType() == WORLD_TYPE_PVP_ENFORCED) then
            return false
        end

        if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then
            if getPlayerBlessing(cid, parameters.bless) then
                npcHandler:say("Gods have already blessed you with this blessing!", cid)
            elseif doPlayerRemoveMoney(cid, parameters.cost) == FALSE then
                npcHandler:say("You don't have enough money for blessing.", cid)
            else
                npcHandler:say("You have been blessed by one of the five gods!", cid)
                doPlayerAddBlessing(cid, parameters.bless)
            end
        else
            npcHandler:say("You need a premium account in order to be blessed.", cid)
        end

        npcHandler:resetNpc(cid)
        return true
    end

    function StdModule.travel(cid, message, keywords, parameters, node)
        local npcHandler = parameters.npcHandler
        if npcHandler == nil then
            error("StdModule.travel called without any npcHandler instance.")
        end
        if(not npcHandler:isFocused(cid)) then
            return false
        end
        if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then
            if(isPlayerPzLocked(cid)) then
                npcHandler:say("First get rid of those blood stains! You are not going to ruin my vehicle!", cid)
            elseif(parameters.level ~= nil and getPlayerLevel(cid) < parameters.level) then
                npcHandler:say("You must reach level " .. parameters.level .. " before I can let you go there.", cid)
            elseif(doPlayerRemoveMoney(cid, parameters.cost) ~= TRUE) then
                npcHandler:say("You don't have enough money.", cid)
            else
                npcHandler:say(parameters.msg or "Set the sails!", cid)
                npcHandler:releaseFocus(cid)
                doSendMagicEffect(getCreaturePosition(cid), CONST_ME_TELEPORT)
                doTeleportThing(cid, parameters.destination)
                doSendMagicEffect(parameters.destination, CONST_ME_TELEPORT)
            end
        else
            npcHandler:say("I'm sorry, but you need a premium account in order to travel onboard our ships.", cid)
        end
        npcHandler:resetNpc(cid)
        return true
    end

    FocusModule = {
        npcHandler = nil
    }

    -- Creates a new instance of FocusModule without an associated NpcHandler.
    function FocusModule:new()
        local obj = {}
        setmetatable(obj, self)
        self.__index = self
        return obj
    end

    -- Inits the module and associates handler to it.
    function FocusModule:init(handler)
        self.npcHandler = handler
        for i, word in pairs(FOCUS_GREETWORDS) do
            local obj = {}
            obj[#obj + 1] = word
            obj.callback = FOCUS_GREETWORDS.callback or FocusModule.messageMatcher
            handler.keywordHandler:addKeyword(obj, FocusModule.onGreet, {module = self})
        end

        for i, word in pairs(FOCUS_FAREWELLWORDS) do
            local obj = {}
            obj[#obj + 1] = word
            obj.callback = FOCUS_FAREWELLWORDS.callback or FocusModule.messageMatcher
            handler.keywordHandler:addKeyword(obj, FocusModule.onFarewell, {module = self})
        end

        return true
    end

    -- Greeting callback function.
    function FocusModule.onGreet(cid, message, keywords, parameters)
        parameters.module.npcHandler:onGreet(cid)
        return true
    end

    -- UnGreeting callback function.
    function FocusModule.onFarewell(cid, message, keywords, parameters)
        if parameters.module.npcHandler:isFocused(cid) then
            parameters.module.npcHandler:onFarewell(cid)
            return true
        else
            return false
        end
    end

    -- Custom message matching callback function for greeting messages.
    function FocusModule.messageMatcher(keywords, message)
        for i, word in pairs(keywords) do
            if type(word) == "string" then
                if string.find(message, word) and not string.find(message, "[%w+]" .. word) and not string.find(message, word .. "[%w+]") then
                    return true
                end
            end
        end
        return false
    end

    KeywordModule = {
        npcHandler = nil
    }
    -- Add it to the parseable module list.
    Modules.parseableModules["module_keywords"] = KeywordModule

    function KeywordModule:new()
        local obj = {}
        setmetatable(obj, self)
        self.__index = self
        return obj
    end

    function KeywordModule:init(handler)
        self.npcHandler = handler
        return true
    end

    -- Parses all known parameters.
    function KeywordModule:parseParameters()
        local ret = NpcSystem.getParameter("keywords")
        if ret ~= nil then
            self:parseKeywords(ret)
        end
    end

    function KeywordModule:parseKeywords(data)
        local n = 1
        for keys in string.gmatch(data, "[^;]+") do
            local i = 1

            local keywords = {}
            for temp in string.gmatch(keys, "[^,]+") do
                keywords[#keywords + 1] = temp
                i = i + 1
            end

            if i ~= 1 then
                local reply = NpcSystem.getParameter("keyword_reply" .. n)
                if reply ~= nil then
                    self:addKeyword(keywords, reply)
                else
                    print("[Warning] NpcSystem:", "Parameter '" .. "keyword_reply" .. n .. "' missing. Skipping...")
                end
            else
                print("[Warning] NpcSystem:", "No keywords found for keyword set #" .. n .. ". Skipping...")
            end

            n = n+1
        end
    end

    function KeywordModule:addKeyword(keywords, reply)
        self.npcHandler.keywordHandler:addKeyword(keywords, StdModule.say, {npcHandler = self.npcHandler, onlyFocus = true, text = reply, reset = true})
    end

    TravelModule = {
        npcHandler = nil,
        destinations = nil,
        yesNode = nil,
        noNode = nil,
    }
    -- Add it to the parseable module list.
    Modules.parseableModules["module_travel"] = TravelModule

    function TravelModule:new()
        local obj = {}
        setmetatable(obj, self)
        self.__index = self
        return obj
    end

    function TravelModule:init(handler)
        self.npcHandler = handler
        self.yesNode = KeywordNode:new(SHOP_YESWORD, TravelModule.onConfirm, {module = self})
        self.noNode = KeywordNode:new(SHOP_NOWORD, TravelModule.onDecline, {module = self})
        self.destinations = {}
        return true
    end

    -- Parses all known parameters.
    function TravelModule:parseParameters()
        local ret = NpcSystem.getParameter("travel_destinations")
        if ret ~= nil then
            self:parseDestinations(ret)

            self.npcHandler.keywordHandler:addKeyword({"destination"}, TravelModule.listDestinations, {module = self})
            self.npcHandler.keywordHandler:addKeyword({"where"}, TravelModule.listDestinations, {module = self})
            self.npcHandler.keywordHandler:addKeyword({"travel"}, TravelModule.listDestinations, {module = self})

        end
    end

    function TravelModule:parseDestinations(data)
        for destination in string.gmatch(data, "[^;]+") do
            local i = 1

            local name = nil
            local x = nil
            local y = nil
            local z = nil
            local cost = nil
            local premium = false

            for temp in string.gmatch(destination, "[^,]+") do
                if i == 1 then
                    name = temp
                elseif i == 2 then
                    x = tonumber(temp)
                elseif i == 3 then
                    y = tonumber(temp)
                elseif i == 4 then
                    z = tonumber(temp)
                elseif i == 5 then
                    cost = tonumber(temp)
                elseif i == 6 then
                    premium = temp == "true"
                else
                    print("[Warning : " .. getCreatureName(getNpcCid()) .. "] NpcSystem:", "Unknown parameter found in travel destination parameter.", temp, destination)
                end
                i = i + 1
            end

            if(name ~= nil and x ~= nil and y ~= nil and z ~= nil and cost ~= nil) then
                self:addDestination(name, {x=x, y=y, z=z}, cost, premium)
            else
                print("[Warning] NpcSystem:", "Parameter(s) missing for travel destination:", name, x, y, z, cost, premium)
            end
        end
    end

    function TravelModule:addDestination(name, position, price, premium)
        self.destinations[#self.destinations + 1] = name

        local parameters = {
            cost = price,
            destination = position,
            premium = premium,
            module = self
        }
        local keywords = {}
        keywords[#keywords + 1] = name

        local keywords2 = {}
        keywords2[#keywords2 + 1] = "bring me to " .. name
        local node = self.npcHandler.keywordHandler:addKeyword(keywords, TravelModule.travel, parameters)
        self.npcHandler.keywordHandler:addKeyword(keywords2, TravelModule.bringMeTo, parameters)
        node:addChildKeywordNode(self.yesNode)
        node:addChildKeywordNode(self.noNode)

        if npcs_loaded_travel[getNpcCid()] == nil then
            npcs_loaded_travel[getNpcCid()] = getNpcCid()
            self.npcHandler.keywordHandler:addKeyword({'yes'}, TravelModule.onConfirm, {module = self})
            self.npcHandler.keywordHandler:addKeyword({'no'}, TravelModule.onDecline, {module = self})
        end
    end

    function TravelModule.travel(cid, message, keywords, parameters, node)
        local module = parameters.module
        if(not module.npcHandler:isFocused(cid)) then
            return false
        end

        local npcHandler = module.npcHandler

        shop_destination[cid] = parameters.destination
        shop_cost[cid] = parameters.cost
        shop_premium[cid] = parameters.premium
        shop_npcuid[cid] = getNpcCid()

        local cost = parameters.cost
        local destination = parameters.destination
        local premium = parameters.premium

        module.npcHandler:say("Do you want to travel to " .. keywords[1] .. " for " .. cost .. " gold coins?", cid)
        return true
    end

    function TravelModule.onConfirm(cid, message, keywords, parameters, node)
        local module = parameters.module
        if(not module.npcHandler:isFocused(cid)) then
            return false
        end

        if shop_npcuid[cid] ~= getNpcCid() then
            return false
        end

        local npcHandler = module.npcHandler

        local parentParameters = node:getParent():getParameters()
        local cost = shop_cost[cid]
        local destination = shop_destination[cid]
        local premium = shop_premium[cid]

        if(not isPlayerPremiumCallback or isPlayerPremiumCallback(cid) or shop_premium[cid] ~= true) then
            if(doPlayerRemoveMoney(cid, cost) ~= TRUE) then
                npcHandler:say("You do not have enough money!", cid)
            elseif(isPlayerPzLocked(cid)) then
                npcHandler:say("Get out of there with this blood.", cid)
            else
                npcHandler:say("It was a pleasure doing business with you.", cid)
                npcHandler:releaseFocus(cid)
                doTeleportThing(cid, destination, 0)
                doSendMagicEffect(destination, CONST_ME_TELEPORT)
            end
        else
            npcHandler:say("I can only allow premium players to travel there.", cid)
        end

        npcHandler:resetNpc(cid)
        return true
    end

    -- onDecline keyword callback function. Generally called when the player sais "no" after wanting to buy an item.
    function TravelModule.onDecline(cid, message, keywords, parameters, node)
        local module = parameters.module
        if not module.npcHandler:isFocused(cid) or shop_npcuid[cid] ~= getNpcCid() then
            return false
        end
        local parentParameters = node:getParent():getParameters()
        local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
        local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_DECLINE), parseInfo)
        module.npcHandler:say(msg, cid)
        module.npcHandler:resetNpc(cid)
        return true
    end

    function TravelModule.bringMeTo(cid, message, keywords, parameters, node)
        local module = parameters.module
        if not module.npcHandler:isFocused(cid) then
            return false
        end

        local cost = parameters.cost
        local destination = parameters.destination
        local premium = parameters.premium

        if(not isPlayerPremiumCallback or isPlayerPremiumCallback(cid) or parameters.premium ~= true) then
            if(doPlayerRemoveMoney(cid, cost) == TRUE) then
                doTeleportThing(cid, destination, 0)
                doSendMagicEffect(destination, CONST_ME_TELEPORT)
            end
        end
        return true
    end

    function TravelModule.listDestinations(cid, message, keywords, parameters, node)
        local module = parameters.module
        if not module.npcHandler:isFocused(cid) then
            return false
        end

        local msg = "I can bring you to "
        --local i = 1
        local maxn = #module.destinations
        for i, destination in pairs(module.destinations) do
            msg = msg .. destination
            if i == maxn - 1 then
                msg = msg .. " and "
            elseif i == maxn then
                msg = msg .. "."
            else
                msg = msg .. ", "
            end
            i = i + 1
        end

        module.npcHandler:say(msg, cid)
        module.npcHandler:resetNpc(cid)
        return true
    end

    ShopModule = {
        npcHandler = nil,
        yesNode = nil,
        noNode = nil,
        noText = "",
        maxCount = 100,
        amount = 0
    }

    -- Add it to the parseable module list.
    Modules.parseableModules["module_shop"] = ShopModule

    -- Creates a new instance of ShopModule
    function ShopModule:new()
        local obj = {}
        setmetatable(obj, self)
        self.__index = self
        return obj
    end

    -- Parses all known parameters.
    function ShopModule:parseParameters()
        local ret = NpcSystem.getParameter("shop_buyable")
        if ret ~= nil then
            self:parseBuyable(ret)
        end

        local ret = NpcSystem.getParameter("shop_sellable")
        if ret ~= nil then
            self:parseSellable(ret)
        end

        local ret = NpcSystem.getParameter("shop_buyable_containers")
        if ret ~= nil then
            self:parseBuyableContainers(ret)
        end
    end

    -- Parse a string contaning a set of buyable items.
    function ShopModule:parseBuyable(data)
        for item in string.gmatch(data, "[^;]+") do
            local i = 1

            local name = nil
            local itemid = nil
            local cost = nil
            local subType = nil
            local realName = nil

            for temp in string.gmatch(item, "[^,]+") do
                if i == 1 then
                    name = temp
                elseif i == 2 then
                    itemid = tonumber(temp)
                elseif i == 3 then
                    cost = tonumber(temp)
                elseif i == 4 then
                    subType = tonumber(temp)
                elseif i == 5 then
                    realName = temp
                else
                    print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Unknown parameter found in buyable items parameter.", temp, item)
                end
                i = i + 1
            end

            if SHOPMODULE_MODE == SHOPMODULE_MODE_TRADE then
                if itemid ~= nil and cost ~= nil then
                    if subType == nil and ItemType(itemid):isFluidContainer() then
                        print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item)
                    else
                        self:addBuyableItem(nil, itemid, cost, subType, realName)
                    end
                else
                    print("[Warning] NpcSystem:", "Parameter(s) missing for item:", itemid, cost)
                end
            else
                if name ~= nil and itemid ~= nil and cost ~= nil then
                    if subType == nil and ItemType(itemid):isFluidContainer() then
                        print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item)
                    else
                        local names = {}
                        names[#names + 1] = name
                        self:addBuyableItem(names, itemid, cost, subType, realName)
                    end
                else
                    print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, itemid, cost)
                end
            end
        end
    end

    -- Parse a string contaning a set of sellable items.
    function ShopModule:parseSellable(data)
        for item in string.gmatch(data, "[^;]+") do
            local i = 1

            local name = nil
            local itemid = nil
            local cost = nil
            local realName = nil
            local subType = nil

            for temp in string.gmatch(item, "[^,]+") do
                if i == 1 then
                    name = temp
                elseif i == 2 then
                    itemid = tonumber(temp)
                elseif i == 3 then
                    cost = tonumber(temp)
                elseif i == 4 then
                    realName = temp
                elseif i == 5 then
                    subType = tonumber(temp)
                else
                    print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Unknown parameter found in sellable items parameter.", temp, item)
                end
                i = i + 1
            end

            if SHOPMODULE_MODE == SHOPMODULE_MODE_TRADE then
                if itemid ~= nil and cost ~= nil then
                    self:addSellableItem(nil, itemid, cost, realName, subType)
                else
                    print("[Warning] NpcSystem:", "Parameter(s) missing for item:", itemid, cost)
                end
            else
                if name ~= nil and itemid ~= nil and cost ~= nil then
                    local names = {}
                    names[#names + 1] = name
                    self:addSellableItem(names, itemid, cost, realName, subType)
                else
                    print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, itemid, cost)
                end
            end
        end
    end

    -- Parse a string contaning a set of buyable items.
    function ShopModule:parseBuyableContainers(data)
        for item in string.gmatch(data, "[^;]+") do
            local i = 1

            local name = nil
            local container = nil
            local itemid = nil
            local cost = nil
            local subType = nil
            local realName = nil

            for temp in string.gmatch(item, "[^,]+") do
                if i == 1 then
                    name = temp
                elseif i == 2 then
                    itemid = tonumber(temp)
                elseif i == 3 then
                    itemid = tonumber(temp)
                elseif i == 4 then
                    cost = tonumber(temp)
                elseif i == 5 then
                    subType = tonumber(temp)
                elseif i == 6 then
                    realName = temp
                else
                    print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Unknown parameter found in buyable items parameter.", temp, item)
                end
                i = i + 1
            end

            if name ~= nil and container ~= nil and itemid ~= nil and cost ~= nil then
                if subType == nil and ItemType(itemid):isFluidContainer() then
                    print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item)
                else
                    local names = {}
                    names[#names + 1] = name
                    self:addBuyableItemContainer(names, container, itemid, cost, subType, realName)
                end
            else
                print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, container, itemid, cost)
            end
        end
    end

    -- Initializes the module and associates handler to it.
    function ShopModule:init(handler)
        self.npcHandler = handler
        self.yesNode = KeywordNode:new(SHOP_YESWORD, ShopModule.onConfirm, {module = self})
        self.noNode = KeywordNode:new(SHOP_NOWORD, ShopModule.onDecline, {module = self})
        self.noText = handler:getMessage(MESSAGE_DECLINE)

        if SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK then
            for i, word in pairs(SHOP_TRADEREQUEST) do
                local obj = {}
                obj[#obj + 1] = word
                obj.callback = SHOP_TRADEREQUEST.callback or ShopModule.messageMatcher
                handler.keywordHandler:addKeyword(obj, ShopModule.requestTrade, {module = self})
            end
        end

        return true
    end

    -- Custom message matching callback function for requesting trade messages.
    function ShopModule.messageMatcher(keywords, message)
        for i, word in pairs(keywords) do
            if type(word) == "string" then
                if string.find(message, word) and not string.find(message, "[%w+]" .. word) and not string.find(message, word .. "[%w+]") then
                    return true
                end
            end
        end

        return false
    end

    -- Resets the module-specific variables.
    function ShopModule:reset()
        self.amount = 0
    end

    -- Function used to match a number value from a string.
    function ShopModule:getCount(message)
        local ret = 1
        local b, e = string.find(message, PATTERN_COUNT)
        if b ~= nil and e ~= nil then
            ret = tonumber(string.sub(message, b, e))
        end

        if ret <= 0 then
            ret = 1
        elseif ret > self.maxCount then
            ret = self.maxCount
        end

        return ret
    end

    -- Adds a new buyable item.
    --    names = A table containing one or more strings of alternative names to this item. Used only for old buy/sell system.
    --    itemid = The itemid of the buyable item
    --    cost = The price of one single item
    --    subType - The subType of each rune or fluidcontainer item. Can be left out if it is not a rune/fluidcontainer. Default value is 1.
    --    realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used)
    function ShopModule:addBuyableItem(names, itemid, cost, itemSubType, realName)
        if SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK then
            if itemSubType == nil then
                itemSubType = 1
            end

            local shopItem = self:getShopItem(itemid, itemSubType)
            if shopItem == nil then
                self.npcHandler.shopItems[#self.npcHandler.shopItems + 1] = {id = itemid, buy = cost, sell = -1, subType = itemSubType, name = realName or ItemType(itemid):getName()}
            else
                shopItem.buy = cost
            end
        end

        if names ~= nil and SHOPMODULE_MODE ~= SHOPMODULE_MODE_TRADE then
            for i, name in pairs(names) do
                local parameters = {
                        itemid = itemid,
                        cost = cost,
                        eventType = SHOPMODULE_BUY_ITEM,
                        module = self,
                        realName = realName or ItemType(itemid):getName(),
                        subType = itemSubType or 1
                    }

                keywords = {}
                keywords[#keywords + 1] = "buy"
                keywords[#keywords + 1] = name
                local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters)
                node:addChildKeywordNode(self.yesNode)
                node:addChildKeywordNode(self.noNode)
            end
        end

        if npcs_loaded_shop[getNpcCid()] == nil then
            npcs_loaded_shop[getNpcCid()] = getNpcCid()
            self.npcHandler.keywordHandler:addKeyword({'yes'}, ShopModule.onConfirm, {module = self})
            self.npcHandler.keywordHandler:addKeyword({'no'}, ShopModule.onDecline, {module = self})
        end
    end

    function ShopModule:getShopItem(itemId, itemSubType)
        if ItemType(itemId):isFluidContainer() then
            for i = 1, #self.npcHandler.shopItems do
                local shopItem = self.npcHandler.shopItems
                if shopItem.id == itemId and shopItem.subType == itemSubType then
                    return shopItem
                end
            end
        else
            for i = 1, #self.npcHandler.shopItems do
                local shopItem = self.npcHandler.shopItems
                if shopItem.id == itemId then
                    return shopItem
                end
            end
        end
        return nil
    end

    -- Adds a new buyable container of items.
    --    names = A table containing one or more strings of alternative names to this item.
    --    container = Backpack, bag or any other itemid of container where bought items will be stored
    --    itemid = The itemid of the buyable item
    --    cost = The price of one single item
    --    subType - The subType of each rune or fluidcontainer item. Can be left out if it is not a rune/fluidcontainer. Default value is 1.
    --    realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used)
    function ShopModule:addBuyableItemContainer(names, container, itemid, cost, subType, realName)
        if names ~= nil then
            for i, name in pairs(names) do
                local parameters = {
                        container = container,
                        itemid = itemid,
                        cost = cost,
                        eventType = SHOPMODULE_BUY_ITEM_CONTAINER,
                        module = self,
                        realName = realName or ItemType(itemid):getName(),
                        subType = subType or 1
                    }

                keywords = {}
                keywords[#keywords + 1] = "buy"
                keywords[#keywords + 1] = name
                local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters)
                node:addChildKeywordNode(self.yesNode)
                node:addChildKeywordNode(self.noNode)
            end
        end
    end

    -- Adds a new sellable item.
    --    names = A table containing one or more strings of alternative names to this item. Used only by old buy/sell system.
    --    itemid = The itemid of the sellable item
    --    cost = The price of one single item
    --    realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used)
    function ShopModule:addSellableItem(names, itemid, cost, realName, itemSubType)
        if SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK then
            if itemSubType == nil then
                itemSubType = 0
            end

            local shopItem = self:getShopItem(itemid, itemSubType)
            if shopItem == nil then
                table.insert(self.npcHandler.shopItems, {id = itemid, buy = -1, sell = cost, subType = itemSubType, name = realName or getItemName(itemid)})
            else
                shopItem.sell = cost
            end
        end

        if(names ~= nil and SHOPMODULE_MODE ~= SHOPMODULE_MODE_TRADE) then
            for i, name in pairs(names) do
                local parameters = {
                        itemid = itemid,
                        cost = cost,
                        eventType = SHOPMODULE_SELL_ITEM,
                        module = self,
                        realName = realName or getItemName(itemid)
                    }

                keywords = {}
                table.insert(keywords, "sell")
                table.insert(keywords, name)
                local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters)
                node:addChildKeywordNode(self.yesNode)
                node:addChildKeywordNode(self.noNode)
            end
        end
    end

    -- onModuleReset callback function. Calls ShopModule:reset()
    function ShopModule:callbackOnModuleReset()
        self:reset()
        return true
    end

    -- Callback onBuy() function. If you wish, you can change certain Npc to use your onBuy().
    function ShopModule:callbackOnBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks)
        local shopItem = self:getShopItem(itemid, subType)
        if shopItem == nil then
            error("[ShopModule.onBuy] shopItem == nil")
            return false
        end

        if shopItem.buy == -1 then
            error("[ShopModule.onSell] attempt to buy a non-buyable item")
            return false
        end

        local backpack = 1988
        local totalCost = amount * shopItem.buy
        if(inBackpacks) then
            totalCost = isItemStackable(itemid) == TRUE and totalCost + 20 or totalCost + (math.max(1, math.floor(amount / getContainerCapById(backpack))) * 20)
        end

        local parseInfo = {
            [TAG_PLAYERNAME] = getPlayerName(cid),
            [TAG_ITEMCOUNT] = amount,
            [TAG_TOTALCOST] = totalCost,
            [TAG_ITEMNAME] = shopItem.name
        }

        if(getPlayerMoney(cid) < totalCost) then
            local msg = self.npcHandler:getMessage(MESSAGE_NEEDMONEY)
            msg = self.npcHandler:parseMessage(msg, parseInfo)
            doPlayerSendCancel(cid, msg)
            return false
        end

        local subType = shopItem.subType or 1
        local a, b = doNpcSellItem(cid, itemid, amount, subType, ignoreCap, inBackpacks, backpack)
        if(a < amount) then
            local msgId = MESSAGE_NEEDMORESPACE
            if(a == 0) then
                msgId = MESSAGE_NEEDSPACE
            end

            local msg = self.npcHandler:getMessage(msgId)
            parseInfo[TAG_ITEMCOUNT] = a
            msg = self.npcHandler:parseMessage(msg, parseInfo)
            doPlayerSendCancel(cid, msg)
            self.npcHandler.talkStart[cid] = os.time()

            if(a > 0) then
                doPlayerRemoveMoney(cid, ((a * shopItem.buy) + (b * 20)))
                return true
            end

            return false
        else
            local msg = self.npcHandler:getMessage(MESSAGE_BOUGHT)
            msg = self.npcHandler:parseMessage(msg, parseInfo)
            doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, msg)
            doPlayerRemoveMoney(cid, totalCost)
            self.npcHandler.talkStart[cid] = os.time()
            return true
        end
    end

    -- Callback onSell() function. If you wish, you can change certain Npc to use your onSell().
    function ShopModule:callbackOnSell(cid, itemid, subType, amount, ignoreEquipped, _)
        local shopItem = self:getShopItem(itemid, subType)
        if shopItem == nil then
            error("[ShopModule.onSell] items[itemid] == nil")
            return false
        end

        if shopItem.sell == -1 then
            error("[ShopModule.onSell] attempt to sell a non-sellable item")
            return false
        end

        local parseInfo = {
            [TAG_PLAYERNAME] = getPlayerName(cid),
            [TAG_ITEMCOUNT] = amount,
            [TAG_TOTALCOST] = amount * shopItem.sell,
            [TAG_ITEMNAME] = shopItem.name
        }

        if not isItemFluidContainer(itemid) then
            subType = -1
        end

        if doPlayerRemoveItem(cid, itemid, amount, subType, ignoreEquipped) then
            local msg = self.npcHandler:getMessage(MESSAGE_SOLD)
            msg = self.npcHandler:parseMessage(msg, parseInfo)
            doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, msg)
            doPlayerAddMoney(cid, amount * shopItem.sell)
            self.npcHandler.talkStart[cid] = os.time()
            return true
        else
            local msg = self.npcHandler:getMessage(MESSAGE_NEEDITEM)
            msg = self.npcHandler:parseMessage(msg, parseInfo)
            doPlayerSendCancel(cid, msg)
            self.npcHandler.talkStart[cid] = os.time()
            return false
        end
    end

    -- Callback for requesting a trade window with the NPC.
    function ShopModule.requestTrade(cid, message, keywords, parameters, node)
        local module = parameters.module
        if(not module.npcHandler:isFocused(cid)) then
            return false
        end

        if(not module.npcHandler:onTradeRequest(cid)) then
            return false
        end

        local itemWindow = {}
        for i = 1, #module.npcHandler.shopItems do
            table.insert(itemWindow, module.npcHandler.shopItems)
        end

        if(itemWindow[1] == nil) then
            local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) }
            local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_NOSHOP), parseInfo)
            module.npcHandler:say(msg, cid)
            return true
        end

        local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) }
        local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_SENDTRADE), parseInfo)
        openShopWindow(cid, itemWindow,
            function(cid, itemid, subType, amount, ignoreCap, inBackpacks) module.npcHandler:onBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks) end,
            function(cid, itemid, subType, amount, ignoreCap, inBackpacks) module.npcHandler:onSell(cid, itemid, subType, amount, ignoreCap, inBackpacks) end)
        module.npcHandler:say(msg, cid)
        return true
    end

    -- onConfirm keyword callback function. Sells/buys the actual item.
    function ShopModule.onConfirm(cid, message, keywords, parameters, node)
        local module = parameters.module
        if(not module.npcHandler:isFocused(cid)) or shop_npcuid[cid] ~= getNpcCid() then
            return false
        end
        shop_npcuid[cid] = 0

        local parentParameters = node:getParent():getParameters()
        local parseInfo = {
            [TAG_PLAYERNAME] = getPlayerName(cid),
            [TAG_ITEMCOUNT] = shop_amount[cid],
            [TAG_TOTALCOST] = shop_cost[cid] * shop_amount[cid],
            [TAG_ITEMNAME] = shop_rlname[cid]
        }

        if(shop_eventtype[cid] == SHOPMODULE_SELL_ITEM) then
            local ret = doPlayerSellItem(cid, shop_itemid[cid], shop_amount[cid], shop_cost[cid] * shop_amount[cid])
            if(ret == LUA_NO_ERROR) then
                local msg = module.npcHandler:getMessage(MESSAGE_ONSELL)
                msg = module.npcHandler:parseMessage(msg, parseInfo)
                module.npcHandler:say(msg, cid)
            else
                local msg = module.npcHandler:getMessage(MESSAGE_MISSINGITEM)
                msg = module.npcHandler:parseMessage(msg, parseInfo)
                module.npcHandler:say(msg, cid)
            end
        elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM) then
            local cost = shop_cost[cid] * shop_amount[cid]
            if getPlayerMoney(cid) < cost then
                local msg = module.npcHandler:getMessage(MESSAGE_MISSINGMONEY)
                msg = module.npcHandler:parseMessage(msg, parseInfo)
                module.npcHandler:say(msg, cid)
                return false
            end

            local a, b = doNpcSellItem(cid, shop_itemid[cid], shop_amount[cid], shop_subtype[cid], false, false, 1988)
            if(a < shop_amount[cid]) then
                local msgId = MESSAGE_NEEDMORESPACE
                if(a == 0) then
                    msgId = MESSAGE_NEEDSPACE
                end

                local msg = module.npcHandler:getMessage(msgId)
                msg = module.npcHandler:parseMessage(msg, parseInfo)
                module.npcHandler:say(msg, cid)
                if(a > 0) then
                    doPlayerRemoveMoney(cid, a * shop_cost[cid])
                    if shop_itemid[cid] == ITEM_PARCEL then
                        doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, 1988)
                    end
                    return true
                end
                return false
            else
                local msg = module.npcHandler:getMessage(MESSAGE_ONBUY)
                msg = module.npcHandler:parseMessage(msg, parseInfo)
                module.npcHandler:say(msg, cid)
                doPlayerRemoveMoney(cid, cost)
                if shop_itemid[cid] == ITEM_PARCEL then
                    doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, 1988)
                end
                return true
            end
        elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM_CONTAINER) then
            local ret = doPlayerBuyItemContainer(cid, shop_container[cid], shop_itemid[cid], shop_amount[cid], shop_cost[cid] * shop_amount[cid], shop_subtype[cid])
            if(ret == LUA_NO_ERROR) then
                local msg = module.npcHandler:getMessage(MESSAGE_ONBUY)
                msg = module.npcHandler:parseMessage(msg, parseInfo)
                module.npcHandler:say(msg, cid)
            else
                local msg = module.npcHandler:getMessage(MESSAGE_MISSINGMONEY)
                msg = module.npcHandler:parseMessage(msg, parseInfo)
                module.npcHandler:say(msg, cid)
            end
        end

        module.npcHandler:resetNpc(cid)
        return true
    end

    -- onDecline keyword callback function. Generally called when the player sais "no" after wanting to buy an item.
    function ShopModule.onDecline(cid, message, keywords, parameters, node)
        local module = parameters.module
        if(not module.npcHandler:isFocused(cid)) or shop_npcuid[cid] ~= getNpcCid() then
            return false
        end
        shop_npcuid[cid] = 0

        local parentParameters = node:getParent():getParameters()
        local parseInfo = {
            [TAG_PLAYERNAME] = getPlayerName(cid),
            [TAG_ITEMCOUNT] = shop_amount[cid],
            [TAG_TOTALCOST] = shop_cost[cid] * shop_amount[cid],
            [TAG_ITEMNAME] = shop_rlname[cid]
        }

        local msg = module.npcHandler:parseMessage(module.noText, parseInfo)
        module.npcHandler:say(msg, cid)
        module.npcHandler:resetNpc(cid)
        return true
    end

    -- tradeItem callback function. Makes the npc say the message defined by MESSAGE_BUY or MESSAGE_SELL
    function ShopModule.tradeItem(cid, message, keywords, parameters, node)
        local module = parameters.module
        if(not module.npcHandler:isFocused(cid)) then
            return false
        end

        if(not module.npcHandler:onTradeRequest(cid)) then
            return true
        end

        local count = module:getCount(message)
        module.amount = count

        shop_amount[cid] = module.amount
        shop_cost[cid] = parameters.cost
        shop_rlname[cid] = parameters.realName
        shop_itemid[cid] = parameters.itemid
        shop_container[cid] = parameters.container
        shop_npcuid[cid] = getNpcCid()
        shop_eventtype[cid] = parameters.eventType
        shop_subtype[cid] = parameters.subType

        local parseInfo = {
            [TAG_PLAYERNAME] = getPlayerName(cid),
            [TAG_ITEMCOUNT] = shop_amount[cid],
            [TAG_TOTALCOST] = shop_cost[cid] * shop_amount[cid],
            [TAG_ITEMNAME] = shop_rlname[cid]
        }

        if(shop_eventtype[cid] == SHOPMODULE_SELL_ITEM) then
            local msg = module.npcHandler:getMessage(MESSAGE_SELL)
            msg = module.npcHandler:parseMessage(msg, parseInfo)
            module.npcHandler:say(msg, cid)
        elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM) then
            local msg = module.npcHandler:getMessage(MESSAGE_BUY)
            msg = module.npcHandler:parseMessage(msg, parseInfo)
            module.npcHandler:say(msg, cid)
        elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM_CONTAINER) then
            local msg = module.npcHandler:getMessage(MESSAGE_BUY)
            msg = module.npcHandler:parseMessage(msg, parseInfo)
            module.npcHandler:say(msg, cid)
        end
        return true
    end
end
 

 

Keyworldhandler:

Spoiler

-- Advanced NPC System by Jiddo

if KeywordHandler == nil then
    KeywordNode = {
        keywords = nil,
        callback = nil,
        parameters = nil,
        children = nil,
        parent = nil
    }

    -- Created a new keywordnode with the given keywords, callback function and parameters and without any childNodes.
    function KeywordNode:new(keys, func, param)
        local obj = {}
        obj.keywords = keys
        obj.callback = func
        obj.parameters = param
        obj.children = {}
        setmetatable(obj, self)
        self.__index = self
        return obj
    end

    -- Calls the underlying callback function if it is not nil.
    function KeywordNode:processMessage(cid, message)
        return (self.callback == nil or self.callback(cid, message, self.keywords, self.parameters, self))
    end

    -- Returns true if message contains all patterns/strings found in keywords.
    function KeywordNode:checkMessage(message)
        if self.keywords.callback ~= nil then
            return self.keywords.callback(self.keywords, message)
        end
        for i,v in ipairs(self.keywords) do
            if type(v) == 'string' then
                local a, b = string.find(message, v)
                if a == nil or b == nil then
                    return false
                end
            end
        end
        return true
    end

    -- Returns the parent of this node or nil if no such node exists.
    function KeywordNode:getParent()
        return self.parent
    end

    -- Returns an array of the callback function parameters assosiated with this node.
    function KeywordNode:getParameters()
        return self.parameters
    end

    -- Returns an array of the triggering keywords assosiated with this node.
    function KeywordNode:getKeywords()
        return self.keywords
    end

    -- Adds a childNode to this node. Creates the childNode based on the parameters (k = keywords, c = callback, p = parameters)
    function KeywordNode:addChildKeyword(keywords, callback, parameters)
        local new = KeywordNode:new(keywords, callback, parameters)
        return self:addChildKeywordNode(new)
    end

    -- Adds a pre-created childNode to this node. Should be used for example if several nodes should have a common child.
    function KeywordNode:addChildKeywordNode(childNode)
        self.children[#self.children + 1] = childNode
        childNode.parent = self
        return childNode
    end

    KeywordHandler = {
        root = nil,
        lastNode = {}
    }

    -- Creates a new keywordhandler with an empty rootnode.
    function KeywordHandler:new()
        local obj = {}
        obj.root = KeywordNode:new(nil, nil, nil)
        setmetatable(obj, self)
        self.__index = self
        return obj
    end

    -- Resets the lastNode field, and this resetting the current position in the node hierarchy to root.
    function KeywordHandler:reset(cid)
        if self.lastNode[cid] then
            self.lastNode[cid] = nil
        end
    end

    -- Makes sure the correct childNode of lastNode gets a chance to process the message.
    function KeywordHandler:processMessage(cid, message)
        local node = self:getLastNode(cid)
        if node == nil then
            error('No root node found.')
            return false
        end

        local ret = self:processNodeMessage(node, cid, message)
        if ret then
            return true
        end

        if node:getParent() then
            node = node:getParent() -- Search through the parent.
            local ret = self:processNodeMessage(node, cid, message)
            if ret then
                return true
            end
        end

        if node ~= self:getRoot() then
            node = self:getRoot() -- Search through the root.
            local ret = self:processNodeMessage(node, cid, message)
            if ret then
                return true
            end
        end
        return false
    end

    -- Tries to process the given message using the node parameter's children and calls the node's callback function if found.
    --    Returns the childNode which processed the message or nil if no such node was found.
    function KeywordHandler:processNodeMessage(node, cid, message)
        local messageLower = string.lower(message)
        for i, childNode in pairs(node.children) do
            if childNode:checkMessage(messageLower) then
                local oldLast = self.lastNode[cid]
                self.lastNode[cid] = childNode
                childNode.parent = node -- Make sure node is the parent of childNode (as one node can be parent to several nodes).
                if childNode:processMessage(cid, message) then
                    return true
                end
                self.lastNode[cid] = oldLast
            end
        end
        return false
    end

    -- Returns the root keywordnode
    function KeywordHandler:getRoot()
        return self.root
    end

    -- Returns the last processed keywordnode or root if no last node is found.
    function KeywordHandler:getLastNode(cid)
        return self.lastNode[cid] or self:getRoot()
    end

    -- Adds a new keyword to the root keywordnode. Returns the new node.
    function KeywordHandler:addKeyword(keys, callback, parameters)
        return self:getRoot():addChildKeyword(keys, callback, parameters)
    end

    -- Moves the current position in the keyword hierarchy count steps upwards. Count defalut value = 1.
    --    This function MIGHT not work properly yet. Use at your own risk.
    function KeywordHandler:moveUp(count)
        local steps = count
        if steps == nil or type(steps) ~= "number" then
            steps = 1
        end
        for i = 1, steps, 1 do
            if self.lastNode[cid] == nil then
                break
            end
            self.lastNode[cid] = self.lastNode[cid]:getParent() or self:getRoot()
        end
        return self.lastNode[cid]
    end
end
 

 

 

Você tem alguma imagem que possa auxiliar no problema? Se sim, anexe-a dentro do spoiler abaixo:

EOma7Ah.png

Compartilhar este post


Link para o post
Compartilhar em outros sites

1 resposta a esta questão

Recommended Posts

  • 0
Majesty    1755
Majesty
Visitante
Este tópico está impedido de receber novos posts.

  • Quem Está Navegando   0 membros estão online

    Nenhum usuário registrado visualizando esta página.

×