• 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!

[NPC Bug] Sells bow when buying crossbow

Lyky

Well-Known Member
Joined
May 27, 2014
Messages
291
Solutions
8
Reaction score
89
Hi,
I have the simplest bug, but with this npc system i'm not sure how to fix it (looks like parsing issue); The code look ok - but i may have missed something.


oVWo8OW.png




NPC Script
Lua:
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

keywordHandler:addKeyword({'job'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "I am the humble supplier for distance fighting weapons of the Ironhouse, owned by Abran Ironeye."})
keywordHandler:addKeyword({'fletcher'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "I am the humble supplier for distance fighting weapons of the Ironhouse, owned by Abran Ironeye."})
keywordHandler:addKeyword({'name'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "People call me Xed, but my full name is Xedem."})
keywordHandler:addKeyword({'time'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "I don't know, maybe what you really need is a watch."})
keywordHandler:addKeyword({'hurt'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "Go to a priest. I am sure they will fix you up."})
keywordHandler:addKeyword({'Abran Ironeye'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "He is the owner of this market, although - just between you and me - I'm not so sure he's honest."})
keywordHandler:addKeyword({'honest'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "Well, I overheard the boss discussing some shady deals with a man in a black cloak."})
keywordHandler:addKeyword({'shady deals'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "Something about a sword only great warlords can use and a rare distance fighting item."})
keywordHandler:addKeyword({'rare distance'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "Yes, but I believe this is nothing but lies seeing that there are only a few distance fighting weapons."})
keywordHandler:addKeyword({'amazon'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "They are a band or tribe of strange women that have nothing in common with civilized men like me."})
keywordHandler:addKeyword({'general'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "You must be talking of the great general Benjamin. He saved the kingdom from ferumbras you know."})
keywordHandler:addKeyword({'army'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "We supply the archers of the army with distance fighting weapons."})
keywordHandler:addKeyword({'ferumbras'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "I heard rumours somewhere that his father was called Hugo."})
keywordHandler:addKeyword({'xed'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "Yeah, nice name, eh?"})
keywordHandler:addKeyword({'excalibug'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "I think that was the sword they were talking about. Said something about a man in Edron that could get it for him."})
keywordHandler:addKeyword({'new'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "Some people say Ferumbras isn't really dead. Crazy kids!"})
keywordHandler:addKeyword({'help'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "I sell items of the distance type."})
keywordHandler:addKeyword({'monster'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "Yeah, these awful beasts. They live in the swamps near the city and in dark dungeons."})
keywordHandler:addKeyword({'dungeon'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "Oh, they are all over. You never see more of them than in Kaz, though."})
keywordHandler:addKeyword({'kaz'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = "Oh, that's short for Kazordoon."})

local shopModule = ShopModule:new()
npcHandler:addModule(shopModule)

shopModule:addBuyableItem({'arrow','arrows'}, 2544, 3, 'arrows')
shopModule:addBuyableItem({'power bolt','power bolts'}, 2547, 100, 'power bolts')
shopModule:addBuyableItem({'bow'}, 2456, 400, 'bow')
shopModule:addSellableItem({'bow'}, 2456, 100, 'bow')
shopModule:addBuyableItem({'bolt', 'bolts'}, 2543, 4, 'bolts')
shopModule:addBuyableItem({'crossbow', 'xbow'}, 2455, 500, 'crossbow')
shopModule:addSellableItem({'crossbow', 'xbow'}, 2455, 500, 'crossbow')
shopModule:addBuyableItem({'spear', 'spears'}, 2389, 9, 'spears')

npcHandler:addModule(FocusModule:new())


If needed added npc.lua, and npcsystem.lua

NPC.lua
Lua:
-- Including the Advanced NPC System
dofile('data/npc/lib/configuration.lua')
dofile('data/npc/lib/npcsystem/npcsystem.lua')
dofile('data/npc/lib/npcsystem/customModules.lua')

function msgcontains(message, keyword)
   local message, keyword = message:lower(), keyword:lower()
   if message == keyword then
       return true
   end

   return message:find(keyword) and not message:find('(%w+)' .. keyword)
end

function doNpcSellItem(cid, itemId, amount, subType, ignoreCap, inBackpacks, backpack)
   local amount = amount or 1
   local subType = subType or 0
   local item = 0
   local player = Player(cid)
   if ItemType(itemId):isStackable() then
       local stuff
       if inBackpacks then
           stuff = Game.createItem(backpack, 1)
           item = stuff:addItem(itemId, math.min(100, amount))
       else
           stuff = Game.createItem(itemId, math.min(100, amount))
       end

       return player:addItemEx(stuff, ignoreCap) ~= RETURNVALUE_NOERROR and 0 or amount, 0
   end

   local a = 0
   if inBackpacks then
       local container, itemType, b = Game.createItem(backpack, 1), ItemType(backpack), 1
       for i = 1, amount do
           local item = container:addItem(itemId, subType)
           if isInArray({(itemType:getCapacity() * b), amount}, i) then
               if player:addItemEx(container, ignoreCap) ~= RETURNVALUE_NOERROR then
                   b = b - 1
                   break
               end

               a = i
               if amount > i then
                   container = Game.createItem(backpack, 1)
                   b = b + 1
               end
           end
       end

       return a, b
   end

   for i = 1, amount do -- normal method for non-stackable items
       local item = Game.createItem(itemId, subType)
       if player:addItemEx(item, ignoreCap) ~= RETURNVALUE_NOERROR then
           break
       end
       a = i
   end
   return a, 0
end

local func = function(cid, text, type, e, pcid)
   local npc = Npc(cid)
   if not npc then
       return
   end

   local player = Player(pcid)
   if player then
       npc:say(text, type, false, player, npc:getPosition())
       e.done = true
   end
end

function doCreatureSayWithDelay(cid, text, type, delay, e, pcid)
   if Player(pcid) then
       e.done = false
       e.event = addEvent(func, delay < 1 and 1000 or delay, cid, text, type, e, pcid)
   end
end

function doPlayerTakeItem(cid, itemid, count)
   local player = Player(cid)
   if player:getItemCount(itemid) < count then
       return false
   end

   while count > 0 do
       local tempcount = 0
       if ItemType(itemid):isStackable() then
           tempcount = math.min (100, count)
       else
           tempcount = 1
       end

       local ret = player:removeItem(itemid, tempcount)
       if ret then
           count = count - tempcount
       else
           return false
       end
   end

   if count ~= 0 then
       return false
   end
   return true
end

function doPlayerSellItem(cid, itemid, count, cost)
   local player = Player(cid)
   if doPlayerTakeItem(cid, itemid, count) then
       if not player:addMoney(cost) then
           error('Could not add money to ' .. player:getName() .. '(' .. cost .. 'gp)')
       end
       return true
   end
   return false
end

function doPlayerBuyItemContainer(cid, containerid, itemid, count, cost, charges)
   local player = Player(cid)
   if not player:removeMoney(cost) then
       return false
   end

   for i = 1, count do
       local container = Game.createItem(containerid, 1)
       for x = 1, ItemType(containerid):getCapacity() do
           container:addItem(itemid, charges)
       end

       if player:addItemEx(container, true) ~= RETURNVALUE_NOERROR then
           return false
       end
   end
   return true
end

function getCount(string)
   local b, e = string:find("%d+")
   return b and e and tonumber(string:sub(b, e)) or -1
end

npcsystem.lua
Lua:
-- Advanced NPC System by Jiddo

shop_amount = {}
shop_cost = {}
shop_rlname = {}
shop_itemid = {}
shop_container = {}
shop_npcuid = {}
shop_eventtype = {}
shop_subtype = {}
shop_destination = {}
shop_premium = {}

npcs_loaded_shop = {}
npcs_loaded_travel = {}

if NpcSystem == nil then
   -- Loads the underlying classes of the npcsystem.
   dofile('data/npc/lib/npcsystem/keywordhandler.lua')
   dofile('data/npc/lib/npcsystem/queue.lua')
   dofile('data/npc/lib/npcsystem/npchandler.lua')
   dofile('data/npc/lib/npcsystem/modules.lua')

   -- Global npc constants:

   -- Greeting and unGreeting keywords. For more information look at the top of modules.lua
   FOCUS_GREETWORDS = {'hi', 'hello'}
   FOCUS_FAREWELLWORDS = {'bye', 'farewell'}

   -- The word for requesting trade window. For more information look at the top of modules.lua
   SHOP_TRADEREQUEST = {'offer', 'trade'}

   -- The word for accepting/declining an offer. CAN ONLY CONTAIN ONE FIELD! For more information look at the top of modules.lua
   SHOP_YESWORD = {'yes'}
   SHOP_NOWORD = {'no'}

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

   -- Talkdelay behavior. For more information, look at the top of npchandler.lua.
   NPCHANDLER_TALKDELAY = TALKDELAY_ONTHINK

   -- Constant strings defining the keywords to replace in the default messages.
   --   For more information, look at the top of npchandler.lua...
   TAG_PLAYERNAME = '|PLAYERNAME|'
   TAG_ITEMCOUNT = '|ITEMCOUNT|'
   TAG_TOTALCOST = '|TOTALCOST|'
   TAG_ITEMNAME = '|ITEMNAME|'
   TAG_QUEUESIZE = '|QUEUESIZE|'
   TAG_TIME = '|TIME|'
   TAG_BLESSCOST = '|BLESSCOST|'
   TAG_TRAVELCOST = '|TRAVELCOST|'

   NpcSystem = {}

   -- Gets an npcparameter with the specified key. Returns nil if no such parameter is found.
   function NpcSystem.getParameter(key)
       local ret = getNpcParameter(tostring(key))
       if (type(ret) == 'number' and ret == 0) or ret == nil then
           return nil
       else
           return ret
       end
   end

   -- Parses all known parameters for the npc. Also parses parseable modules.
   function NpcSystem.parseParameters(npcHandler)
       local ret = NpcSystem.getParameter('idletime')
       if ret ~= nil then
           npcHandler.idleTime = tonumber(ret)
       end
       local ret = NpcSystem.getParameter('talkradius')
       if ret ~= nil then
           npcHandler.talkRadius = tonumber(ret)
       end
       local ret = NpcSystem.getParameter('message_greet')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_GREET, ret)
       end
       local ret = NpcSystem.getParameter('message_farewell')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_FAREWELL, ret)
       end
       local ret = NpcSystem.getParameter('message_decline')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_DECLINE, ret)
       end
       local ret = NpcSystem.getParameter('message_needmorespace')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_NEEDMORESPACE, ret)
       end
       local ret = NpcSystem.getParameter('message_needspace')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_NEEDSPACE, ret)
       end
       local ret = NpcSystem.getParameter('message_sendtrade')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_SENDTRADE, ret)
       end
       local ret = NpcSystem.getParameter('message_noshop')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_NOSHOP, ret)
       end
       local ret = NpcSystem.getParameter('message_oncloseshop')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_ONCLOSESHOP, ret)
       end
       local ret = NpcSystem.getParameter('message_onbuy')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_ONBUY, ret)
       end
       local ret = NpcSystem.getParameter('message_onsell')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_ONSELL, ret)
       end
       local ret = NpcSystem.getParameter('message_missingmoney')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_MISSINGMONEY, ret)
       end
       local ret = NpcSystem.getParameter('message_needmoney')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_NEEDMONEY, ret)
       end
       local ret = NpcSystem.getParameter('message_missingitem')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_MISSINGITEM, ret)
       end
       local ret = NpcSystem.getParameter('message_needitem')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_NEEDITEM, ret)
       end
       local ret = NpcSystem.getParameter('message_idletimeout')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_IDLETIMEOUT, ret)
       end
       local ret = NpcSystem.getParameter('message_walkaway')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_WALKAWAY, ret)
       end
       local ret = NpcSystem.getParameter('message_alreadyfocused')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_ALREADYFOCUSED, ret)
       end
       local ret = NpcSystem.getParameter('message_placedinqueue')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_PLACEDINQUEUE, ret)
       end
       local ret = NpcSystem.getParameter('message_buy')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_BUY, ret)
       end
       local ret = NpcSystem.getParameter('message_sell')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_SELL, ret)
       end
       local ret = NpcSystem.getParameter('message_bought')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_BOUGHT, ret)
       end
       local ret = NpcSystem.getParameter('message_sold')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_SOLD, ret)
       end
       local ret = NpcSystem.getParameter('message_walkaway_male')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_WALKAWAY_MALE, ret)
       end
       local ret = NpcSystem.getParameter('message_walkaway_female')
       if ret ~= nil then
           npcHandler:setMessage(MESSAGE_WALKAWAY_FEMALE, ret)
       end

       -- Parse modules.
       for parameter, module in pairs(Modules.parseableModules) do
           local ret = NpcSystem.getParameter(parameter)
           if ret ~= nil then
               local number = tonumber(ret)
               if number ~= 0 and module.parseParameters ~= nil then
                   local instance = module:new()
                   npcHandler:addModule(instance)
                   instance:parseParameters()
               end
           end
       end
   end
end


keywordhandler.lua (note line 46)
Lua:
-- Advanced NPC System by Jiddo

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

   -- Created a new keywordnode with the given keywords, callback function and parameters and without any childNodes.
   function KeywordNode:new(keys, func, param, condition, action)
       local obj = {}
       obj.keywords = keys
       obj.callback = func
       obj.parameters = param
       obj.children = {}
       obj.condition = condition
       obj.action = action
       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

   function KeywordNode:processAction(cid)
       if not self.action then
           return
       end

       local player = Player(cid)
       if not player then
           return
       end

       self.action(player, self.parameters.npcHandler)
   end

   -- Returns true if message contains all patterns/strings found in keywords.
   function KeywordNode:checkMessage(cid, message)
       if self.keywords.callback ~= nil then
           local ret, data = self.keywords.callback(self.keywords, message)
           if not ret then
               return false
           end

           if self.condition and not self.condition(Player(cid), data) then
               return false
           end
           return true
       end

       local data = {}
       local last = 0
       for i = 1, #self.keywords do
           local keyword = self.keywords[i]
           if type(keyword) == 'string' then
               local a, b = string.find(message, keyword)
               if not a or not b or a < last then
                   return false
               end
               if keyword:sub(1, 1) == '%' then
                   data[#data + 1] = tonumber(message:sub(a, b)) or nil
               end
               last = a
           end
       end

       if self.condition and not self.condition(Player(cid), data) then
           return false
       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, condition, action)
       local new = KeywordNode:new(keywords, callback, parameters, condition, action)
       return self:addChildKeywordNode(new)
   end

   function KeywordNode:addAliasKeyword(keywords)
       if #self.children == 0 then
           print('KeywordNode:addAliasKeyword no previous node found')
           return false
       end

       local prevNode = self.children[#self.children]
       local new = KeywordNode:new(keywords, prevNode.callback, prevNode.parameters, prevNode.condition, prevNode.action)
       for i = 1, #prevNode.children do
           new:addChildKeywordNode(prevNode.children[i])
       end
       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 = nil
   }

   -- Creates a new keywordhandler with an empty rootnode.
   function KeywordHandler:new()
       local obj = {}
       obj.root = KeywordNode:new(nil, nil, nil)
       obj.lastNode = {}
       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 = message:lower()
       for i = 1, #node.children do
           local childNode = node.children[i]
           if childNode:checkMessage(cid, 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
                   childNode:processAction(cid)
                   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, condition, action)
       return self:getRoot():addChildKeyword(keys, callback, parameters, condition, action)
   end

   -- Adds an alias keyword for the previous node.
   function KeywordHandler:addAliasKeyword(keys)
       return self:getRoot():addAliasKeyword(keys)
   end

   -- Moves the current position in the keyword hierarchy steps upwards. Steps defalut value = 1.
   function KeywordHandler:moveUp(cid, steps)
       if steps == nil or type(steps) ~= "number" then
           steps = 1
       end

       for i = 1, steps do
           if self.lastNode[cid] == nil then
               return nil
           end
           self.lastNode[cid] = self.lastNode[cid]:getParent() or self:getRoot()
       end
       return self.lastNode[cid]
   end
end
 
Last edited:
Solution
The problem is easy, you must move the keyword crossbow before the keyword bow, cause what it does, it return the first match it finds, the string crossbow contain bow, so it returns bow, if you swtich line 37 with line 40, it will first find crossbow.
if its too hard, could someone post older way the npc's used to be scripted before advanced npc system?

I'll just script it using old way.
 
The problem is easy, you must move the keyword crossbow before the keyword bow, cause what it does, it return the first match it finds, the string crossbow contain bow, so it returns bow, if you swtich line 37 with line 40, it will first find crossbow.
 
Solution
local a, b = string.find(message, keyword)

Also, it trys to find a string, you can check if the keywords are equal or change that part of the code under keywordhandler.lua
 
Back
Top