• 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!
  • If you're using Gesior 2012 or MyAAC, please review this thread for information about a serious security vulnerability and a fix.

TFS 0.X NPC with another NPC's speech

potinho

Well-Known Member
Joined
Oct 11, 2009
Messages
1,203
Solutions
17
Reaction score
98
Location
Brazil
Hello everyone, everything good?

I think it's the weirdest problem I've ever seen happen on a server, I have no idea what it causes. Sometimes on my server, when I talk to an NPC it responds as if it were another, example: my NPC Djin sometimes responds as if it were my NPC Task, it's very strange, the console doesn't give any error. Then I say "bye" and say "hi" again and he responds normally, has anyone been through this?

It only makes sense to me if it's something in the NPC lib, so I'll post npchandler.lua, follow:

Lua:
-- Advanced NPC System (Created by Jiddo),
-- Modified by TheForgottenServer Team,
-- Modified by The OTX Server Team.

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 conversation behaviors.
    CONVERSATION_DEFAULT = 0 -- Conversation through default window, like it was before 8.2 update.
    CONVERSATION_PRIVATE = 1 -- Conversation through NPCs chat window, as of 8.2 update. (Default)
        --Small Note: Private conversations also means the NPC will use multi-focus system.

    -- Currently applied conversation behavior. CONVERSATION_PRIVATE is default.
    NPCHANDLER_CONVBEHAVIOR = CONVERSATION_DEFAULT

    -- 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_MISSINGITEM        = 10 -- When the player is trying to sell an item he does not have.
    MESSAGE_NEEDMONEY        = 11 -- Same as above, used for shop window.
    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_PLACEDINQUEUE        = 22 -- When the player has been placed in the costumer queue.

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

    -- 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|'
    TAG_QUEUESIZE = '|QUEUESIZE|'

    NpcHandler = {
        keywordHandler = nil,
        focuses = nil,
        talkStart = nil,
        idleTime = 300,
        talkRadius = 4,
        talkDelayTime = 350, -- Seconds to delay outgoing messages.
        queue = nil,
        talkDelay = nil,
        callbackFunctions = nil,
        modules = nil,
        shopItems = nil, -- They must be here since ShopModule uses "static" functions
        messages = {
            -- These are the default replies of all npcs. They can/should be changed individually for each npc.
            [MESSAGE_GREET]     = 'Welcome, |PLAYERNAME|! I have been expecting you.',
            [MESSAGE_FAREWELL]     = 'Good bye, |PLAYERNAME|!',
            [MESSAGE_BUY]         = 'Do you want to buy |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?',
            [MESSAGE_ONBUY]     = 'It was a pleasure doing business with you.',
            [MESSAGE_BOUGHT]     = 'Bought |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.',
            [MESSAGE_SELL]         = 'Do you want to sell |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?',
            [MESSAGE_ONSELL]     = 'Thank you for this |ITEMNAME|, |PLAYERNAME|.',
            [MESSAGE_SOLD]         = 'Sold |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.',
            [MESSAGE_MISSINGMONEY]    = 'Sorry, you don\'t have enough money.',
            [MESSAGE_MISSINGITEM]     = 'You don\'t even have that item, |PLAYERNAME|!',
            [MESSAGE_NEEDMONEY]     = 'You do not have enough money.',
            [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]     = 'Next, please!',
            [MESSAGE_WALKAWAY]     = 'How rude!',
            [MESSAGE_DECLINE]    = 'Not good enough, is it... ?',
            [MESSAGE_SENDTRADE]    = 'Here\'s my offer, |PLAYERNAME|. Don\'t you like it?',
            [MESSAGE_NOSHOP]    = 'Sorry, I\'m not offering anything.',
            [MESSAGE_ONCLOSESHOP]    = 'Thank you, come back when you want something more.',
            [MESSAGE_ALREADYFOCUSED]= '|PLAYERNAME|! I am already talking to you...',
            [MESSAGE_PLACEDINQUEUE] = '|PLAYERNAME|, please wait for your turn. There are |QUEUESIZE| customers before you.'
        }
    }

    -- Creates a new NpcHandler with an empty callbackFunction stack.
    function NpcHandler:new(keywordHandler)
        local obj = {}
        obj.messages = {}

        obj.keywordHandler = keywordHandler
        obj.queue = Queue:new(obj)
        obj.focuses = 0
        obj.talkStart = 0
        obj.callbackFunctions = {}
        obj.modules = {}
        obj.talkDelay = {}
        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((newFocus ~= 0 and not isCreature(newFocus))) then
            return
        end

        self.focuses = newFocus
        self:updateFocus(true)
    end
    NpcHandler.changeFocus = NpcHandler.addFocus -- "changeFocus" looks better for CONVERSATION_DEFAULT

    -- Function used to verify if npc is focused to certain player
    function NpcHandler:isFocused(focus, creatureCheck)
        local creatureCheck = creatureCheck or false
        if(creatureCheck or isCreature(self.focuses)) then
            return self.focuses == focus
        end

        self:changeFocus(0)
        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(creatureCheck)
        local creatureCheck = creatureCheck or false
        if(creatureCheck or isCreature(self.focuses)) then
            doNpcSetCreatureFocus(self.focuses)
            return
        end

        doNpcSetCreatureFocus(0)
    end

    -- Used when the npc should un-focus the player.
    function NpcHandler:releaseFocus(focus)
        if(self.focuses == focus) then
            self:changeFocus(0)
        end
    end

    -- Internal un-focusing function, beware using!
    function NpcHandler:unsetFocus(focus, pos)
        if(type(self.focuses) ~= "table" or pos == nil or self.focuses[pos] == nil) then
            return
        end

        table.remove(self.focuses, pos)
        self.talkStart[focus] = nil
        self:updateFocus()
    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 or module == nil) then
            return false
        end

        module:init(self)
        if(module.parseParameters ~= nil) then
            module:parseParameters()
        end

        table.insert(self.modules, module)
        return true
    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 _, 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_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)
        for search, replace in pairs(parseInfo) do
            if(replace ~= nil) then
                msg = msg:gsub(search, replace)
            end
        end

        return msg
    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(cid)) then
            if(self:processModuleCallback(CALLBACK_FAREWELL)) then
                if(self.queue == nil or not self.queue:greetNext()) then
                    local msg = self:getMessage(MESSAGE_FAREWELL)
                    msg = self:parseMessage(msg, { [TAG_PLAYERNAME] = getPlayerName(cid) or -1 })

                    self:resetNpc(cid)
                    self:say(msg)
                    self:releaseFocus(cid)
                end
            end
        end
    end

    -- Greets a new player.
    function NpcHandler:greet(cid)
        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)
                msg = self:parseMessage(msg, { [TAG_PLAYERNAME] = getCreatureName(cid) })

                self:addFocus(cid)
                self:say(msg)
            end
        end
    end

    -- Handles onCreatureAppear events. If you with to handle this yourself, please use the CALLBACK_CREATURE_APPEAR callback.
    function NpcHandler:onCreatureAppear(cid)
        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)
        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, class, msg)
        local callback = self:getCallback(CALLBACK_CREATURE_SAY)
        if(callback == nil or callback(cid, class, msg)) then
            if(self:processModuleCallback(CALLBACK_CREATURE_SAY, cid, class, msg)) then
                if(not self:isInRange(cid)) then
                    return
                end

                if(self.keywordHandler ~= nil) 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, class, msg)) then
                            self.talkStart = os.time()
                        end
                    else
                        self.talkStart = os.time()
                    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)) then
                if(self:isFocused(cid)) then
                    local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) }
                    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)) 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 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
            for i, speech in pairs(self.talkDelay) do
                if((speech.cid == nil or speech.cid == 0) and speech.time ~= nil and speech.message ~= nil) then
                    if(os.mtime() >= speech.time) then
                        selfSay(speech.message)
                        self.talkDelay[i] = nil
                    end
                elseif(isCreature(speech.cid) and speech.start ~= nil and speech.time ~= nil and speech.message ~= nil) then
                    if(os.mtime() >= speech.time) then
                        local talkStart = self.talkStart
                        if(speech.force or (self:isFocused(speech.cid) and talkStart == speech.start)) then
                            selfSay(speech.message)
                        end

                        self.talkDelay[i] = nil
                    end
                else
                    self.talkDelay[i] = nil
                end
            end

            if(self:processModuleCallback(CALLBACK_ONTHINK)) then
                if(self.focuses ~= 0) then
                    if(not self:isInRange(self.focuses)) then
                        self:onWalkAway(self.focuses)
                    elseif((os.time() - self.talkStart) > self.idleTime) then
                        self:unGreet(self.focuses)
                    else
                        self:updateFocus()
                    end
                end
            end
        end
    end

    -- Tries to greet the player with the given cid.
    function NpcHandler:onGreet(cid)
        if(self:isInRange(cid)) then
            if(self.focuses == 0) then
                self:greet(cid)
            elseif(self.focuses == cid) then
                local msg = self:getMessage(MESSAGE_ALREADYFOCUSED)
                local parseInfo = { [TAG_PLAYERNAME] = getCreatureName(cid) }
                msg = self:parseMessage(msg, parseInfo)
                self:say(msg)
            else
                if(not self.queue:isInQueue(cid)) then
                    self.queue:push(cid)
                end

                local msg = self:getMessage(MESSAGE_PLACEDINQUEUE)
                local parseInfo = { [TAG_PLAYERNAME] = getCreatureName(cid), [TAG_QUEUESIZE] = self.queue:getSize() }
                msg = self:parseMessage(msg, parseInfo)
                self:say(msg)
            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(cid)) then
                if(self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid)) then
                    if(self.queue == nil or not self.queue:greetNext()) then
                        local msg = self:getMessage(MESSAGE_WALKAWAY)
                        self:resetNpc(cid)
                        self:say(self:parseMessage(msg, { [TAG_PLAYERNAME] = getPlayerName(cid) or -1 }))
                        self:releaseFocus(cid)
                    end
                end
            end
        end
    end

    -- Returns true if cid is within the talkRadius of this npc.
    function NpcHandler:isInRange(cid)
        if not isPlayer(cid) then
            return false
        end

        local distance = getNpcDistanceTo(cid) or -1
        return distance ~= -1 and distance <= self.talkRadius
    end

    -- Resets the npc into it's initial state (in regard of the keyrodhandler).
    --    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

    -- Makes the npc represented by this instance of NpcHandler say something.
    --    This implements the currently set type of talkdelay.
    function NpcHandler:say(message, focus, delay, force)
        local delay = delay or 0
        if(NPCHANDLER_TALKDELAY == TALKDELAY_NONE or delay <= 0) then
            selfSay(message)
            return
        end

        -- TODO: Add an event handling method for delayed messages
        table.insert(self.talkDelay, {
            id = getNpcId(),
            cid = focus,
            message = message,
            time = os.mtime() + (delay and delay or self.talkDelayTime),
            start = os.time(),
            force = force or false
        })
    end
end
 

Xikini

I whore myself out for likes
Senator
Joined
Nov 17, 2010
Messages
6,245
Solutions
545
Reaction score
4,488
These are all self-contained functions, so is unlikely the issue.

My initial guess would be that something is loaded globally, such as a table, and your npc's are setup similar to one another, and are grabbing from the same place.

If that doesn't appear to be the case, then you'll need to start adding prints into the npc's and troubleshoot from there.
 
OP
OP
potinho

potinho

Well-Known Member
Joined
Oct 11, 2009
Messages
1,203
Solutions
17
Reaction score
98
Location
Brazil
Got it Xikini, yeah, that just really confirms what I said: I have no idea what it is hahaha. Could you help me identify? Here's the script of two NPCs I know conflict sometimes:

Lua:
domodlib('task_func')
local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)
NpcSystem.parseParameters(npcHandler)
local talkState = {}
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
function creatureSayCallback(cid, type, msg)
    if(not npcHandler:isFocused(cid)) then
        return false
    end
    local talkUser,msg, str,rst = NPCHANDLER_CONVbehavior == CONVERSATION_DEFAULT and 0 or cid, msg:lower(),"",""
    local task,daily, hours = getTaskMission(cid),getDailyTaskMission(cid), 24
    if isInArray({"task","tasks","missao","mission"}, msg) then
        if task_sys[task] then
            if getPlayerStorageValue(cid, task_sys[task].start) <= 0 then
                if getPlayerLevel(cid) >= task_sys[task].level then
                    setPlayerStorageValue(cid, task_sys[task].start, 1)
                    npcHandler:say("[Task System] Congratulations, you are now participating in the Task of "..task_sys[task].name.." and must kill "..task_sys[task].count.." from this list: "..getMonsterFromList(task_sys[task].monsters_list)..". "..(#task_sys[task].items > 0 and "Oh, and please bring "..getItemsFromList(task_sys[task].items).." to me." or "").."" , cid)
                else
                    npcHandler:say("Sorry, but you need level "..task_sys[task].level.." to be able to participate in the Task of "..task_sys[task].name.."!", cid)
                end
            else
                npcHandler:say("Sorry, but you are currently on task "..task_sys[task].name..". You can ask for a {reward} if you are done.", cid)
            end
        else
            npcHandler:say("Sorry, but for now I don't have any more tasks for you!", cid)
        end
    elseif isInArray({"diaria","daili","daily","dayli","diario","task daily","daily task"}, msg) then
        if getPlayerStorageValue(cid, task_sys_storages[6]) - os.time() > 0 then
            npcHandler:say("Sorry you must wait until "..os.date("%d %B %Y %X ", getPlayerStorageValue(cid,task_sys_storages[6])).." to start a new daily task!", cid) return true
        elseif daily_task[daily] and getPlayerStorageValue(cid, task_sys_storages[5]) >= daily_task[daily].count then
            npcHandler:say("Sorry, you have a task to {deliver}!", cid) return true
        end
        local r = doRandomDailyTask(cid)
        if r == 0 then
            npcHandler:say("Sorry, but you don't have the level to complete any daily Tasks.", cid) return true
        end
        setPlayerStorageValue(cid, task_sys_storages[4], r)
        setPlayerStorageValue(cid, task_sys_storages[6], os.time()+hours*3600)
        setPlayerStorageValue(cid, task_sys_storages[7], 1)
        setPlayerStorageValue(cid, task_sys_storages[5], 0)
       local dtask = daily_task[r]
        npcHandler:say("[Daily Task System] Congratulations, you are now participating in the Task Daily of "..dtask.name.." and must kill "..dtask.count.." monsters from this list: "..getMonsterFromList(dtask.monsters_list).." until "..os.date("%d %B %Y %X ", getPlayerStorageValue(cid,task_sys_storages[6]))..". Good lucky!" , cid)
    elseif isInArray({"receber","reward","recompensa","report","reportar","entregar","entrega","deliver","prize","premio"}, msg) then
        local v, k = task_sys[task], daily_task[daily] 
        if v then -- original task
            if getPlayerStorageValue(cid, v.start) > 0 then
                if getPlayerStorageValue(cid,task_sys_storages[3]) >= v.count then
                    if #v.items > 0 and not doRemoveItemsFromList(cid, v.items) then
                        npcHandler:say("Sorry, but you also need to deliver the items from this list: "..getItemsFromList(v.items), cid) return true
                    end
             if v.exp > 0 then doPlayerAddExp(cid, v.exp) str = str.."".. (str == "" and "" or ", ") .." "..v.exp.." de exp" end
                 if v.points > 0 then setPlayerStorageValue(cid, task_sys_storages[2], (getTaskPoints(cid)+v.points)) str = str.."".. (str == "" and "" or ", ") .." + "..v.points.." task points" end
                 if v.money > 0 then doPlayerAddMoney(cid, v.money) str = str.."".. (str == "" and "" or ", ") ..""..v.money.." gps" end
                 if table.maxn(v.reward) > 0 then GiveRewardsTask(cid, v.reward) str = str.."".. (str == "" and "" or ", ") ..""..getItemsFromList(v.reward) end
                    npcHandler:say("Thanks for your help! Rewards: "..(str == "" and "none" or ""..str.."").." for having completed the task of the "..v.name, cid)
                    setPlayerStorageValue(cid, task_sys_storages[3], 0)
                    setPlayerStorageValue(cid, task_sys_storages[1], (task+1))
                else
                    npcHandler:say("Sorry, but you haven't finished your task of "..v.name..". I need you to kill more "..(getPlayerStorageValue(cid, task_sys_storages[3]) < 0 and v.count or -(getPlayerStorageValue(cid,task_sys_storages[3])-v.count)).." Of these terrible monsters!", cid)
                end
            end
        end
        if k then -- daily task
            if getPlayerStorageValue(cid, task_sys_storages[7]) > 0 then
                if getPlayerStorageValue(cid, task_sys_storages[5]) >= k.count then
                if k.exp > 0 then doPlayerAddExp(cid, k.exp) rst = rst.."".. (rst == "" and "" or ", ") .." "..k.exp.." de exp" end
                 if k.points > 0 then setPlayerStorageValue(cid, task_sys_storages[2], (getTaskPoints(cid)+k.points)) rst = rst.."".. (rst == "" and "" or ", ") .." + "..k.points.." task points" end
                 if k.money > 0 then doPlayerAddMoney(cid, k.money) rst = rst.."".. (rst == "" and "" or ", ") ..""..k.money.." gps" end
                 if table.maxn(k.reward) > 0 then GiveRewardsTask(cid, k.reward) rst = rst.."".. (rst == "" and "" or ", ") ..""..getItemsFromList(k.reward) end
                    npcHandler:say("Thanks for your help! Rewards: "..(rst == "" and "none" or ""..rst.."").." for having completed the task of the "..k.name, cid)
                    setPlayerStorageValue(cid, task_sys_storages[4], 0)
                    setPlayerStorageValue(cid, task_sys_storages[5], 0)
                    setPlayerStorageValue(cid, task_sys_storages[7], 0)
                    else
                    npcHandler:say("Sorry, but you haven't finished your daily task of "..k.name..". I need you to kill more "..(getPlayerStorageValue(cid, task_sys_storages[5]) < 0 and k.count or -(getPlayerStorageValue(cid,task_sys_storages[5])-k.count)).." These monsters!", cid)
                end
            end
        end
    elseif msg == "no" then 
        selfSay("Okay then.", cid) 
        talkState[talkUser] = 0 
        npcHandler:releaseFocus(cid) 
    end
    return true
end
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())

Lua:
local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)
NpcSystem.parseParameters(npcHandler)
local talkState = {}

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
function greet(cid)                    talkState[cid] = 0     return true       end

local sellableItemList = {
    -- {"item name", itemid, value}
    {"steel shield", 2509, 77},
    {"battle shield", 2513, 88},
    {"tempest shield", 2542, 88000},
    {"phoenix shield", 2539, 28000},
    {"dark helmet", 2490, 110},
    {"royal helmet", 2498, 22000},
    {"warrior helmet", 2475, 4400},
    {"crusader helmet", 2497, 9900},
    {"crown helmet", 2491, 3300},
    {"devil helmet", 2462, 1100},
    {"mystic turban", 2663, 550},
    {"chain helmet", 2458, 50},
    {"iron helmet", 2459, 40},
    {"steel boots", 2645, 27500},
    {"boots of haste", 2195, 22000},
    {"heavy mace", 2452, 88000},
    {"talon", 2151, 320},
    {"snake god's wristguard", 5128, 60000},
    {"royal scale robe", 5124, 75000},
    {"spellbook of enlightenment", 5160, 55000},
    --{"boh", 2195, 22000},
    {"golden boots", 2646, 310000},
    {"magic plate armor", 2472, 165000},
    --{"mpa", 2472, 165000},
    {"dragon scale mail", 2492, 44000},
    --{"dsm", 2492, 44000},
    {"demon armor", 2494, 330000},
    {"golden armor", 2466, 22000},
    {"crown armor", 2487, 11000},
    {"knight armor", 2476, 5500},
    {"blue robe", 2656, 11000},
    {"dragon slayer", 5131, 15000},
    {"lady armor", 2500, 2750},
    {"plate armor", 2463, 440},
    {"brass armor", 2465, 220},
    {"chain armor", 2464, 110},
    {"golden legs", 2470, 88000},
    {"crown legs", 2488, 13200},
    {"knight legs", 2477, 6600},
    {"plate legs", 2647, 550},
    {"brass legs", 2478, 110},
    {"mastermind shield", 2514, 88000},
    --{"mms", 2514, 88000},
    {"demon shield", 2520, 33000},
    {"skull helmet", 5145, 110000},
    {"zaoan helmet", 5154, 110000},
    {"zaoan legs", 5156, 110000},
    {"blue legs", 5157, 88000},
    {"focus cape", 5159, 96000},
    {"blessed shield", 2523, 550000},
    {"great shield", 2522, 440000},
    {"vampire shield", 2534, 16500},
    {"medusa shield", 2536, 8800},
    {"amazon shield", 2537, 4400},
    {"crown shield", 2519, 5500},
    {"tower shield", 2528, 4400},
    {"guardian shield", 2515, 2200},
    {"beholder shield", 2518, 1650},
    {"dragon shield", 2516, 3300},
    {"dwarven shield", 2525, 110},
    {"magic longsword", 2390, 110000},
    {"warlord sword", 2408, 990000},
    {"magic sword", 2400, 88000},
    {"giant sword", 2393, 16500},
    {"bright sword", 2407, 6600},
    {"ice rapier", 2396, 4400},
    {"fire sword", 2392, 3300},
    {"serpent sword", 2409, 1650},
    {"two handed sword", 2377, 440},
    {"broad sword", 2413, 140},
    {"short sword", 2406, 33},
    {"sword", 2376, 33},
    {"dragon lance", 2414, 9900},
    {"stonecutter axe", 2431, 66000},
    {"guardian halberd", 2427, 8250},
    {"fire axe", 2432, 11000},
    {"knight axe", 2430, 4400},
    {"double axe", 2387, 330},
    {"halberd", 2381, 440},
    {"battle axe", 2378, 165},
    {"hatchet", 2388, 22},
    {"war hammer", 2391, 1100},
    {"thunder hammer", 2421, 330000},
    {"skull staff", 2436, 4400},
    {"dragon hammer", 2434, 2200},
    {"clerical mace", 2423, 220},
    {"battle hammer", 2417, 66},
    {"mace", 2398, 33},
    {"ring of the sky", 2123, 11000},
    {"naginata", 2426, 5500},
    {"gold ring", 2179, 1100},
    {"platinum amulet", 2171, 2200},
    {"stone skin amulet", 2197, 1100},
    {"small sapphire", 2146, 250},
    {"small emerald", 2149, 250},
    {"small amethyst", 2150, 200},
    {"small ruby", 2147, 250},
    {"small diamond", 2145, 300},
    {"green gem", 2155, 5000},
    {"blue gem", 2158, 5000},
    {"yellow gem", 2154, 1000},
    {"red gem", 2156, 1000},
    {"black pearl", 2144, 280},
    {"white pearl", 2143, 160},
    {"ravager's axe", 2443, 12000},
    {"scarf", 2661, 550},
    {"steel helmet", 2457, 220}
}

local function createSellableItemTextFromTable(cid, itemTable) -- {"item name", itemid, value}
    local text = ""
    local totalGold = 0
    local playerItems = {}
    for i = 1, 10 do
        local slotItem = getPlayerSlotItem(cid, i)
        if slotItem.itemid > 0 then
            table.insert(playerItems, slotItem.itemid)
        end
    end
    for i = 1, #itemTable do
        local itemCount = (isInArray(playerItems, itemTable[i][2]) and -1 or 0)
        itemCount = itemCount + getPlayerItemCount(cid, itemTable[i][2])
        if itemCount > 0 then
            if text ~= "" then
                text = text .. ", "
            end
            text = text .. itemCount .. " " .. itemTable[i][1]
            totalGold = totalGold + (itemCount * itemTable[i][3])
        end
    end
    if totalGold > 0 then
        text = text .. " for " .. totalGold .. " gold coins"
    else
        text = "Sorry, you have nothing of value to sell currently."
    end
    return text
end

local function sellAllItemsFromTable(cid, itemTable) -- {"item name", itemid, value}
    local totalGold = 0
    local playerItems = {}
    for i = 1, 10 do
        local slotItem = getPlayerSlotItem(cid, i)
        if slotItem.itemid > 0 then
            table.insert(playerItems, slotItem.itemid)
        end
    end
    for i = 1, #itemTable do
        local itemCount = (isInArray(playerItems, itemTable[i][2]) and -1 or 0)
        itemCount = itemCount + getPlayerItemCount(cid, itemTable[i][2])
        if itemCount > 0 then
            doPlayerRemoveItem(cid, itemTable[i][2], (isInArray(playerItems, itemTable[i][2]) and itemCount + 1 or itemCount))
            totalGold = totalGold + (itemCount * itemTable[i][3])
            if isInArray(playerItems, itemTable[i][2]) then
                doPlayerAddItem(cid, itemTable[i][2], 1)
            end
        end
    end
    doPlayerAddMoney(cid, totalGold)
    return true
end

function creatureSayCallback(cid, type, msg)
    if(not npcHandler:isFocused(cid)) then
        return false
    end
    
    -- sell all
    if msgcontains(msg, "sell all") and talkState[cid] == 0 then
        local text = createSellableItemTextFromTable(cid, sellableItemList)
        if text ~= "Sorry, you have nothing of value to sell currently." then
            selfSay("Do you want to sell " .. text .. "?", cid)
            talkState[cid] = 1
        else
            selfSay(text, cid)
            talkState[cid] = 0
        end
        
    -- sell all confirmation
    elseif talkState[cid] == 1 then
        if msgcontains(msg, "yes") then
            local text = createSellableItemTextFromTable(cid, sellableItemList)
            if text ~= "Sorry, you have nothing of value to sell currently." then
                sellAllItemsFromTable(cid, sellableItemList)
                selfSay("You have sold " .. text .. ".", cid)
            else
                selfSay(text, cid)
            end
        else
            selfSay("Another time, then.", cid)
        end
        talkState[cid] = 0
    
    -- sell information
    elseif msgcontains(msg, "helmets") then
        selfSay("I buy royal (22k), warrior (4,4k), crusader (9,9k), crown (3,3k), devil (1,1k), chain (35gp) and iron helmets (33gp), steel helmets (220gp) also mystic turbans (550gp).", cid)
    elseif msgcontains(msg, "boots") then
        selfSay("I buy golden boots (110k), steel boots (27,5k) and boots of haste (22k).", cid)
    elseif msgcontains(msg, "armors") then
        selfSay("I buy golden (22k), crown (11k), knight (5,5k), lady (2,7k), plate (440gp), brass (220gp) and chain armors (110gp), also mpa (165k), dsm (44k) and blue robes (16,5k).", cid)
    elseif msgcontains(msg, "legs") then
        selfSay("I buy golden (88k), crown (13,2k), knight (6,6k), plate (550gp) and brass legs (110gp).", cid)
    elseif msgcontains(msg, "shields") then
        selfSay("I buy blessed (550k), great (440k), demon (33k), vampire (16,5k), medusa (8,8k), amazon (4,4k), crown (5,5k), tower (4,4k), dragon (3,3k), guardian (2,2k), beholder (1,1k), and dwarven shields (110gp), also mms (88k).", cid)
    elseif msgcontains(msg, "swords") then
        selfSay("I buy giant (16,5k), bright (6,6k), fire (3,3k) serpent (1,65k), spike (880gp) and two handed swords (440gp), also ice rapiers (4,4k), magic longswords (165k), magic swords (99k), warlord swords (110k) broad swords (77gp), short swords (33gp), sabres (27gp) and swords (27gp).", cid)
    elseif msgcontains(msg, "axes") then
        selfSay("I buy fire (11k), guardian halberds (8,2k), naginata (5,5k) knight (2,2k), double (220gp) and battle axes (110gp), also dragon lances (11k), stonecutters axes (99k), halberds (220gp) and hatchets (22gp).", cid)
    elseif msgcontains(msg, "clubs") then
        selfSay("I buy thunder hammers (330k), war (4,4k), dragon (2,2k) and battle hammers (66gp), also skull staffs (11k) and clerical maces (220gp).", cid)
    elseif msgcontains(msg, "rings") then
        selfSay("I buy ring of the sky (11k), gold ring (1,1k).", cid)
    elseif msgcontains(msg, "amulets") then
        selfSay("I buy platinum amulet (1,1k), Stone Skin amulet (1,1k), scarf (550gp).", cid)
    
    end    
    return true  
end

npcHandler:setCallback(CALLBACK_GREET, greet)
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())
 
OP
OP
potinho

potinho

Well-Known Member
Joined
Oct 11, 2009
Messages
1,203
Solutions
17
Reaction score
98
Location
Brazil
Happend again, strange, i realized what i have 2 npc who sells loot, and both of this two npcs have say NPC Task speech.

I managed to reproduce the problem: a person is talking to an NPC, if I arrive and say "hi", he will say he is busy. If the person is still trapped in that NPC and I talk to another free NPC, he will say that there is a person before me, as if he was occupied. When the first person leaves the NPC and I talk to anyone else, the NPC's next speech will be that of the first NPC I spoke to, when he was serving a person.

npc bug.gif
 
Last edited:

Yodot

Member
Joined
Dec 10, 2007
Messages
170
Reaction score
21
If I had to guess, I'd say it's this line,
Lua:
function greet(cid)                    talkState[cid] = 0     return true       end
Missing from line 10 of simple_task.lua
 

danilopucci

Active Member
Joined
Nov 22, 2019
Messages
67
Solutions
2
Reaction score
39
GitHub
danilopucci
Hello potinho.

My guess would be as Xikini said about globals, but everything looks fine. What domodlib('task_func') do on first line of sample_task.lua does?


Anyway, I took a look on both script, and something that caught my eye is on simple_task.lua on creatureSayCallback.

It initializes a local "talkUser" and it is used only on one place.
Lua:
local talkUser,msg, str,rst = NPCHANDLER_CONVbehavior == CONVERSATION_DEFAULT and 0 or cid, msg:lower(),"",""

This does not looks correct. It should use "cid" or "talkUser"
 
OP
OP
potinho

potinho

Well-Known Member
Joined
Oct 11, 2009
Messages
1,203
Solutions
17
Reaction score
98
Location
Brazil
Hello potinho.

My guess would be as Xikini said about globals, but everything looks fine. What domodlib('task_func') do on first line of sample_task.lua does?


Anyway, I took a look on both script, and something that caught my eye is on simple_task.lua on creatureSayCallback.

It initializes a local "talkUser" and it is used only on one place.
Lua:
local talkUser,msg, str,rst = NPCHANDLER_CONVbehavior == CONVERSATION_DEFAULT and 0 or cid, msg:lower(),"",""

This does not looks correct. It should use "cid" or "talkUser"
The problem doesn't just happen with these NPCs, with any NPC I talk to and then talk to another, the problem repeats itself. I tested it with this base and it also has the same problem. From what I've managed to research, I believe it's something in npchandler

 
OP
OP
potinho

potinho

Well-Known Member
Joined
Oct 11, 2009
Messages
1,203
Solutions
17
Reaction score
98
Location
Brazil
Some guy just told me:

"You call npcHandler from addEvent function, Change self.focuses from integer to table"

But dont know what this means, dont know if helps to fix, still with problem.
 

Puppets

Obbey your master
Joined
Feb 24, 2013
Messages
196
Reaction score
11
Location
Barcelona, Catalunya
We had the same problem in our server with some NPCs, we fixed it by removing this line of code on each affected NPC file. Hope it helps :)
Lua:
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
 
OP
OP
potinho

potinho

Well-Known Member
Joined
Oct 11, 2009
Messages
1,203
Solutions
17
Reaction score
98
Location
Brazil
We had the same problem in our server with some NPCs, we fixed it by removing this line of code on each affected NPC file. Hope it helps :)
Lua:
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
removed from npc scripts? Dont work here =/
 
Top