• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

About a new NPC system

Robson Dias

Member
Joined
Nov 13, 2017
Messages
23
Reaction score
11
Location
Brazil
Hello,
I've developed a custom NPC system to use in my project, because I don't really like the way the current system works. And I wanted an opinion on how it’s going.

I am not a professional .LUA programmer then you will see some mistakes and bad practices.
It aready fills everything that I need to continue my project. But there may be some bugs that I haven't found yet or I'll never find (laughs).

Lua:
npc:addAction(
    action type,
    parameters (keywords, replys, talkstates),
    condition (condition to match this action)
    action (function to execute if condition matches)
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "mission" },
        reply = "Shush!! I don't want everybody to know what I am up to. Listen, things are not going too well, I need a new attraction. Do you want to help me?",
        talkstate = 1
    },
    function( player, parameters, self )
        return player:isDruid()
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "yes" },
        reply = "Then take this money."
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 1
    end,
    function( player, parameters, self )
        player:addItem( 2160, 1)
        return true
    end
)

Skjaar.lua (100% working according to rl tibia)

Lua:
--[[
* RL860 - Real Map Cloje Project v8.60
* Skjaar.lua: Database for Skjaar, guardian of the crypt in the mount sternum and master mage
]]--

local npc = OtNpcSystem:Init()

function onCreatureAppear( cid )
    npc:onCreatureAppear( cid )
end

function onCreatureDisappear( cid )
    npc:onCreatureDisappear( cid )
end

function onCreatureSay( cid, type, msg )
    npc:onCreatureSay( cid, type, msg )
end

function onThink()
    npc:onThink()
end

npc:addAction(
    ACTION_NOTFOCUSED,
    {
        keywords = { "hi$" },
        reply = "I don't talk to little children!!"
    },
    function( player, parameters, self )
        return player:getLevel() < 15
    end
)

npc:addAliasKeyword( { "hello$" } )

npc:addAction(
    ACTION_GREET,
    {
        keywords = { "hi$" },
        reply = "Hail, friend of nature! How may I help you?"
    },
    function( player, parameters, self )
        return player:isDruid()
    end
)

npc:addAliasKeyword( { "hello$" } )

npc:addAction(
    ACTION_GREET,
    {
        keywords = { "hi$" },
        reply = "Another creature who believes thinks physical strength is more important than wisdom! Why are you disturbing me?"
    },
    function( player, parameters, self )
        return player:isKnight()
    end
)

npc:addAliasKeyword( { "hello$" } )

npc:addAction(
    ACTION_GREET,
    {
        keywords = { "hi$" },
        reply = "Neither strong enough to be a knight nor wise enough to be a real mage. You like it easy, don't you? Why are you disturbing me?"
    },
    function( player, parameters, self )
        return player:isPaladin()
    end
)

npc:addAliasKeyword( { "hello$" } )

npc:addAction(
    ACTION_GREET,
    {
        keywords = { "hi$" },
        reply = "It's good to see somebody who has chosen the path of wisdom. What do you want?"
    },
    function( player, parameters, self )
        return player:isSorcerer()
    end
)

npc:addAliasKeyword( { "hello$" } )

npc:addAction(
    ACTION_VANISH,
    {
        reply = "Run away, unworthy %N!"
    }
)

npc:addAction(
    ACTION_FAREWELL,
    {
        keywords = { "bye" },
        reply = "Farewell, %N!"
    }
)

npc:addAliasKeyword( { "farewell" } )

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "job" },
        reply = "Once I was the master of all mages, but now I only protect this crypt."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "name" },
        reply = "I am Skjaar the Mage, master of all spells."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "door" },
        reply = "This door seals a crypt."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "crypt" },
        reply = "Here lies my master. Only his closest followers may enter."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "help" },
        reply = "I'm not here to help anybody. I only protect my master's crypt."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "mountain" },
        reply = "Hundreds of years my master's castle stood on the top of this mountain. Now there is a volcano."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "volcano" },
        reply = "I can still feel the magical energy in the volcano."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "castle" },
        reply = "The castle was destroyed when my master tried to summon a nameless creature. All that is left is this volcano."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "time" },
        reply = "To those who have lived for a thousand years time holds no more terror."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "master" },
        reply = "If you are one of his followers, you need not ask about him, for you will know. And if you aren't, you are not worthy anyway!"
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "idiot" },
        reply = "Take this for your words!"
    },
    function( player, parameters, self )
        player:addHealth( -( player:getHealth() - 1 ) )
        player:getPosition():sendMagicEffect( CONST_ME_MAGIC_RED )
        self:setIdle( player:getId() )
        return true
    end
)

npc:addAliasKeyword( { "fuck" } )
npc:addAliasKeyword( { "asshole" } )

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "key" },
        reply = "I will give the key to the crypt only to the closest followers of my master. Would you like me to test you?",
        talkstate = 1
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "yes" },
        reply = "Before we start I must ask you for a small donation of 1000 gold coins. Are you willing to pay 1000 gold coins for the test?",
        talkstate = 2
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 1
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "no" },
        reply = "Then leave, unworthy worm!"
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 1
    end,
    function( player, parameters, self )
        self:setIdle( player:getId() )
        return true
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "" },
        reply = "You're not worthy if you cannot make up your mind. Leave!"
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 1
    end,
    function( player, parameters, self )
        self:setIdle( player:getId() )
        return true
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "yes" },
        reply = "All right then. Here comes the first question. What was the name of Dago's favourite pet?",
        talkstate = 3
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 2 and player:getMoney() >= 1000
    end,
    function( player, parameters, self )
        player:removeMoney( 1000 )
        return true
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "yes" },
        reply = "You don't even have the money to make a donation? Then go!"
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 2
    end,
    function( player, parameters, self )
        self:setIdle( player:getId() )
        return true
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "no" },
        reply = "You're not worthy then. Now leave!"
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 2
    end,
    function( player, parameters, self )
        self:setIdle( player:getId() )
        return true
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "" },
        reply = "You're not worthy if you cannot make up your mind. Leave!"
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 2
    end,
    function( player, parameters, self )
        self:setIdle( player:getId() )
        return true
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "redips" },
        reply = "Perhaps you knew him after all. Tell me - how many fingers did he have when he died?",
        talkstate = 4
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 3
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "" },
        reply = "You are wrong. Get lost!"
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 3
    end,
    function( player, parameters, self )
        self:setIdle( player:getId() )
        return true
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "7" },
        reply = "Also true. But can you also tell me the colour of the deamons in which master specialized?",
        talkstate = 5
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 4
    end
)

npc:addAliasKeyword( { "seven" } )

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "" },
        reply = "You are wrong. Get lost!"
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 4
    end,
    function( player, parameters, self )
        self:setIdle( player:getId() )
        return true
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "black" },
        reply = "It seems you are worthy after all. Do you want the key to the crypt?",
        talkstate = 6
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 5
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "" },
        reply = "You are wrong. Get lost!"
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 5
    end,
    function( player, parameters, self )
        self:setIdle( player:getId() )
        return true
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "yes" },
        reply = "Here you are."
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 6
    end,
    function( player, parameters, self )
        player:addItem( 2089, 1 ):setActionId( 3142 )
        return true
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "" },
        reply = "It is always a wise decision to leave the dead alone."
    },
    function( player, parameters, self )
        return self:getTalkState( player:getId() ) == 6
    end
)

Boozer.lua

Lua:
--[[
* RL860 - Real Map Clone Project v8.60
* Boozer.lua: Database for the host Boozer
]]--

local npc = OtNpcSystem:Init()

function onCreatureAppear( cid )
    npc:onCreatureAppear( cid )
end

function onCreatureDisappear( cid )
    npc:onCreatureDisappear( cid )
end

function onCreatureSay( cid, type, msg )
    npc:onCreatureSay( cid, type, msg )
end

function onThink()
    npc:onThink()
end

npc:addAction(
    ACTION_GREET,
    {
        keywords = { "hi$" },
        reply = "Welcome to the Hard Rock Racing Track, %N."
    }
)

npc:addAliasKeyword( { "hello$" } )

npc:addAction(
    ACTION_VANISH,
    {
        reply = "You'll be back."
    }
)

npc:addAction(
    ACTION_FAREWELL,
    {
        keywords = { "bye" },
        reply = "You'll be back."
    }
)

npc:addAliasKeyword( { "farewell" } )

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "job" },
        reply = "I am the bartender here at the racing track."
    }
)

npc:addAliasKeyword( { "tavern" } )

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "frodo" },
        reply = "I heard about his tiny tavern in Thais."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "name" },
        reply = "Just call me Boozer. Everyone does that."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "time" },
        reply = "No clue, boy."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "time" },
        reply = "No clue, girl."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "king" },
        reply = "The king is far away, so who cares?"
    }
)

npc:addAliasKeyword( { "tibianus" } )

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "army" },
        reply = "Good customers."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "ferumbras" },
        reply = "Guess he'd be bad news for business."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "excalibug" },
        reply = "Heard about it now and then. Then again I also hear there a bogeyman somewhere in the swamps."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "bogeyman" },
        reply = "Just a tale to scare the kids."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "thais" },
        reply = "If you like that Thais that much just go there."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "tibia" },
        reply = "People from all over Tibia come here to buy, sell, gamble, and get drunk until they puke."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "carlin" },
        reply = "Heard about that women there. Must visit that wenches someday."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "amazon" },
        reply = "I guess they just have not met the right man yet."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "news" },
        reply = "The swampelves, down at Shadowthorn, are up to some trouble again."
    }
)

npc:addAliasKeyword( { "rumors" } )

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "swampelves" },
        reply = "Some elves gone evil so to say. They now live in a small village to the south called Shadowthorn. No big deal. Who cares about some carrot-eating musicians at all?"
    }
)

npc:addAction(
    ACTION_TRADE,
    {
        keywords = { "trade" },
        reply = "See my wares."
    },
    function( player, parameters, self )
 
        self:addBuyableItem( player:getId(), 2689, 4 ) -- Bread
        self:addBuyableItem( player:getId(), 2012, 2, 3 ) -- Mug of Beer
        self:addBuyableItem( player:getId(), 2696, 6 ) -- Cheese
        self:addBuyableItem( player:getId(), 2687, 5 ) -- Cookie
        self:addBuyableItem( player:getId(), 2671, 8 ) -- Ham
        self:addBuyableItem( player:getId(), 8208, 10 ) -- Ice Cream Cone (Venorean Dream)
        self:addBuyableItem( player:getId(), 2012, 2, 5 ) -- Mug of Lemonade
        self:addBuyableItem( player:getId(), 2666, 5 ) -- Meat
        self:addBuyableItem( player:getId(), 2012, 1, 1 ) -- Mug of Water
        self:addBuyableItem( player:getId(), 2012, 3, 15 ) -- Mug of Wine
     
        return true
    end
)

If you are interested in this npcsystem and want to contribute
diasreuf/OtNpcSystem (https://github.com/diasreuf/OtNpcSystem)

Thanks,
diasreuf
 
Last edited:
I dont think this is correct but sure I can be wrong.
Lua:
npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "time" },
        reply = "No clue, boy."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "time" },
        reply = "No clue, girl."
    }
)
also it would be cool if you could pass userdata to replys
 
Cool, but could you describe for non-programmers/scripters whats going on? Whats new?

Doesn't really change too much
For me, the way that each npc action is handled is more readable than a lot of if/elses

I dont think this is correct but sure I can be wrong.
Lua:
npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "time" },
        reply = "No clue, boy."
    }
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "time" },
        reply = "No clue, girl."
    }
)
also it would be cool if you could pass userdata to replys

This is something I didn't see when converting the NPC haha

Lua:
npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "time" },
        reply = "No clue, boy."
    },
    function( player, parameters, self )
        return player:getSex() == 1
    end
)

npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "time" },
        reply = "No clue, girl."
    },
    function( player, parameters, self )
        return player:getSex() == 0
    end
)

userdata in reply can be done like this.

Lua:
npc:addAction(
    ACTION_KEYWORD,
    {
        keywords = { "time" },
        reply = "Hello! Current time is %X?"
    },
    function( player, parameters, self )
        parameters.reply = string.gsub( parameters.reply, "%%X", getFormattedWorldTime() )
        return true
    end
)
 
Back
Top