Advertisement
Remikos

eee

Jul 22nd, 2015
284
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.94 KB | None | 0 0
  1. -- Advanced NPC System (Created by Jiddo),
  2. -- Modified by Talaturen.
  3.  
  4. if(NpcHandler == nil) then
  5.     -- Constant talkdelay behaviors.
  6.     TALKDELAY_NONE = 0 -- No talkdelay. Npc will reply immedeatly.
  7.     TALKDELAY_ONTHINK = 1 -- Talkdelay handled through the onThink callback function. (Default)
  8.     TALKDELAY_EVENT = 2 -- Not yet implemented
  9.  
  10.     -- Currently applied talkdelay behavior. TALKDELAY_ONTHINK is default.
  11.     --NPCHANDLER_TALKDELAY = TALKDELAY_ONTHINK
  12.     NPCHANDLER_TALKDELAY = TALKDELAY_NONE
  13.  
  14.     -- Constant conversation behaviors.
  15.     CONVERSATION_DEFAULT = 0 -- Conversation through default window, like it was before 8.2 update.
  16.     CONVERSATION_PRIVATE = 1 -- Conversation through NPCs chat window, as of 8.2 update. (Default)
  17.         --Small Note: Private conversations also means the NPC will use multi-focus system.
  18.  
  19.     -- Currently applied conversation behavior. CONVERSATION_PRIVATE is default.
  20.     NPCHANDLER_CONVBEHAVIOR = CONVERSATION_PRIVATE
  21.  
  22.     -- Constant indexes for defining default messages.
  23.     MESSAGE_GREET           = 1 -- When the player greets the npc.
  24.     MESSAGE_FAREWELL        = 2 -- When the player unGreets the npc.
  25.     MESSAGE_BUY             = 3 -- When the npc asks the player if he wants to buy something.
  26.     MESSAGE_ONBUY           = 4 -- When the player successfully buys something via talk.
  27.     MESSAGE_BOUGHT          = 5 -- When the player bought something through the shop window.
  28.     MESSAGE_SELL            = 6 -- When the npc asks the player if he wants to sell something.
  29.     MESSAGE_ONSELL          = 7 -- When the player successfully sells something via talk.
  30.     MESSAGE_SOLD            = 8 -- When the player sold something through the shop window.
  31.     MESSAGE_MISSINGMONEY        = 9 -- When the player does not have enough money.
  32.     MESSAGE_NEEDMONEY       = 10 -- Same as above, used for shop window.
  33.     MESSAGE_MISSINGITEM     = 11 -- When the player is trying to sell an item he does not have.
  34.     MESSAGE_NEEDITEM        = 12 -- Same as above, used for shop window.
  35.     MESSAGE_NEEDSPACE       = 13 -- When the player don't have any space to buy an item
  36.     MESSAGE_NEEDMORESPACE       = 14 -- When the player has some space to buy an item, but not enough space
  37.     MESSAGE_WALKAWAY        = 16 -- When the player walks out of the talkRadius of the npc.
  38.     MESSAGE_DECLINE         = 17 -- When the player says no to something.
  39.     MESSAGE_SENDTRADE       = 18 -- When the npc sends the trade window to the player
  40.     MESSAGE_NOSHOP          = 19 -- When the npc's shop is requested but he doesn't have any
  41.     MESSAGE_ONCLOSESHOP     = 20 -- When the player closes the npc's shop window
  42.     MESSAGE_ALREADYFOCUSED      = 21 -- When the player already has the focus of this npc.
  43.  
  44.     -- Constant indexes for callback functions. These are also used for module callback ids.
  45.     CALLBACK_CREATURE_APPEAR    = 1
  46.     CALLBACK_CREATURE_DISAPPEAR = 2
  47.     CALLBACK_CREATURE_SAY       = 3
  48.     CALLBACK_ONTHINK        = 4
  49.     CALLBACK_GREET          = 5
  50.     CALLBACK_FAREWELL       = 6
  51.     CALLBACK_MESSAGE_DEFAULT    = 7
  52.     CALLBACK_PLAYER_ENDTRADE    = 8
  53.     CALLBACK_PLAYER_CLOSECHANNEL    = 9
  54.     CALLBACK_ONBUY          = 10
  55.     CALLBACK_ONSELL         = 11
  56.  
  57.     -- Addidional module callback ids
  58.     CALLBACK_MODULE_INIT        = 12
  59.     CALLBACK_MODULE_RESET       = 13
  60.  
  61.     -- Constant strings defining the keywords to replace in the default messages.
  62.     TAG_PLAYERNAME = '|PLAYERNAME|'
  63.     TAG_ITEMCOUNT = '|ITEMCOUNT|'
  64.     TAG_TOTALCOST = '|TOTALCOST|'
  65.     TAG_ITEMNAME = '|ITEMNAME|'
  66.  
  67.     NpcHandler = {
  68.         keywordHandler = nil,
  69.         focuses = nil,
  70.         talkRadius = 4,
  71.         callbackFunctions = nil,
  72.         modules = nil,
  73.         shopItems = nil, -- They must be here since ShopModule uses "static" functions
  74.         messages = {
  75.             -- These are the default replies of all npcs. They can/should be changed individually for each npc.
  76.             [MESSAGE_GREET]     = 'Welcome, |PLAYERNAME|! I have been expecting you.',
  77.             [MESSAGE_FAREWELL]  = 'Good bye, |PLAYERNAME|!',
  78.             [MESSAGE_BUY]       = 'Do you want to buy |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?',
  79.             [MESSAGE_ONBUY]     = 'It was a pleasure doing business with you.',
  80.             [MESSAGE_BOUGHT]    = 'Bought |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.',
  81.             [MESSAGE_SELL]      = 'Do you want to sell |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?',
  82.             [MESSAGE_ONSELL]    = 'Thank you for this |ITEMNAME|, |PLAYERNAME| gold.',
  83.             [MESSAGE_SOLD]      = 'Sold |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.',
  84.             [MESSAGE_MISSINGMONEY]  = 'Sorry, you don\'t have enough money.',
  85.             [MESSAGE_NEEDMONEY]     = 'You do not have enough money.',
  86.             [MESSAGE_MISSINGITEM]   = 'You don\'t even have that item, |PLAYERNAME|!',
  87.             [MESSAGE_NEEDITEM]  = 'You do not have this object.',
  88.             [MESSAGE_NEEDSPACE] = 'You do not have enough capacity.',
  89.             [MESSAGE_NEEDMORESPACE] = 'You do not have enough capacity for all items.',
  90.             [MESSAGE_WALKAWAY]  = 'How rude!',
  91.             [MESSAGE_DECLINE]   = 'Not good enough, is it... ?',
  92.             [MESSAGE_SENDTRADE] = 'Here\'s my offer, |PLAYERNAME|. Don\'t you like it?',
  93.             [MESSAGE_NOSHOP]    = 'Sorry, I\'m not offering anything.',
  94.             [MESSAGE_ONCLOSESHOP]   = 'Thank you, come back when you want something more.',
  95.             [MESSAGE_ALREADYFOCUSED]= '|PLAYERNAME|! I am already talking to you...'
  96.         }
  97.     }
  98.  
  99.     -- Creates a new NpcHandler with an empty callbackFunction stack.
  100.     function NpcHandler:new(keywordHandler)
  101.         local obj = {}
  102.         obj.callbackFunctions = {}
  103.         obj.modules = {}
  104.         obj.focuses = {}
  105.         obj.keywordHandler = keywordHandler
  106.         obj.messages = {}
  107.         obj.shopItems = {}
  108.  
  109.         setmetatable(obj.messages, self.messages)
  110.         self.messages.__index = self.messages
  111.  
  112.         setmetatable(obj, self)
  113.         self.__index = self
  114.         return obj
  115.     end
  116.  
  117.     -- Attackes a new keyword handler to this npchandler
  118.     function NpcHandler:setKeywordHandler(newHandler)
  119.         self.keywordHandler = newHandler
  120.     end
  121.  
  122.     -- Function used to change the focus of this npc.
  123.     function NpcHandler:addFocus(newFocus)
  124.         if(self:isFocused(newFocus)) then
  125.             return
  126.         end
  127.  
  128.         table.insert(self.focuses, newFocus)
  129.         self:updateFocus()
  130.     end
  131.  
  132.     -- Function used to verify if npc is focused to certain player
  133.     function NpcHandler:isFocused(focus)
  134.         for k,v in pairs(self.focuses) do
  135.             if v == focus then
  136.                 return true
  137.             end
  138.         end
  139.  
  140.         return false
  141.     end
  142.  
  143.     -- This function should be called on each onThink and makes sure the npc faces the player it is talking to.
  144.     --  Should also be called whenever a new player is focused.
  145.     function NpcHandler:updateFocus()
  146.         for pos, focus in pairs(self.focuses) do
  147.             if(focus ~= nil) then
  148.                 doNpcSetCreatureFocus(focus)
  149.                 return
  150.             end
  151.         end
  152.  
  153.         doNpcSetCreatureFocus(0)
  154.     end
  155.  
  156.     -- Used when the npc should un-focus the player.
  157.     function NpcHandler:releaseFocus(focus)
  158.         if(not self:isFocused(focus)) then
  159.             return
  160.         end
  161.  
  162.         local pos = nil
  163.         for k,v in pairs(self.focuses) do
  164.             if v == focus then
  165.                 pos = k
  166.             end
  167.         end
  168.         table.remove(self.focuses, pos)
  169.         closeShopWindow(focus) --Even if it can not exist, we need to prevent it.
  170.         self:updateFocus()
  171.     end
  172.  
  173.     -- Returns the callback function with the specified id or nil if no such callback function exists.
  174.     function NpcHandler:getCallback(id)
  175.         local ret = nil
  176.         if(self.callbackFunctions ~= nil) then
  177.             ret = self.callbackFunctions[id]
  178.         end
  179.  
  180.         return ret
  181.     end
  182.  
  183.     -- Changes the callback function for the given id to callback.
  184.     function NpcHandler:setCallback(id, callback)
  185.         if(self.callbackFunctions ~= nil) then
  186.             self.callbackFunctions[id] = callback
  187.         end
  188.     end
  189.  
  190.     -- Adds a module to this npchandler and inits it.
  191.     function NpcHandler:addModule(module)
  192.         if(self.modules == nil or module == nil) then
  193.             return false
  194.         end
  195.  
  196.         module:init(self)
  197.         if(module.parseParameters ~= nil) then
  198.             module:parseParameters()
  199.         end
  200.  
  201.         table.insert(self.modules, module)
  202.         return true
  203.     end
  204.  
  205.     -- Calls the callback function represented by id for all modules added to this npchandler with the given arguments.
  206.     function NpcHandler:processModuleCallback(id, ...)
  207.         local ret = true
  208.         for i, module in pairs(self.modules) do
  209.             local tmpRet = true
  210.             if(id == CALLBACK_CREATURE_APPEAR and module.callbackOnCreatureAppear ~= nil) then
  211.                 tmpRet = module:callbackOnCreatureAppear(unpack(arg))
  212.             elseif(id == CALLBACK_CREATURE_DISAPPEAR and module.callbackOnCreatureDisappear ~= nil) then
  213.                 tmpRet = module:callbackOnCreatureDisappear(unpack(arg))
  214.             elseif(id == CALLBACK_CREATURE_SAY and module.callbackOnCreatureSay ~= nil) then
  215.                 tmpRet = module:callbackOnCreatureSay(unpack(arg))
  216.             elseif(id == CALLBACK_PLAYER_ENDTRADE and module.callbackOnPlayerEndTrade ~= nil) then
  217.                 tmpRet = module:callbackOnPlayerEndTrade(unpack(arg))
  218.             elseif(id == CALLBACK_PLAYER_CLOSECHANNEL and module.callbackOnPlayerCloseChannel ~= nil) then
  219.                 tmpRet = module:callbackOnPlayerCloseChannel(unpack(arg))
  220.             elseif(id == CALLBACK_ONBUY and module.callbackOnBuy ~= nil) then
  221.                 tmpRet = module:callbackOnBuy(unpack(arg))
  222.             elseif(id == CALLBACK_ONSELL and module.callbackOnSell ~= nil) then
  223.                 tmpRet = module:callbackOnSell(unpack(arg))
  224.             elseif(id == CALLBACK_ONTHINK and module.callbackOnThink ~= nil) then
  225.                 tmpRet = module:callbackOnThink(unpack(arg))
  226.             elseif(id == CALLBACK_GREET and module.callbackOnGreet ~= nil) then
  227.                 tmpRet = module:callbackOnGreet(unpack(arg))
  228.             elseif(id == CALLBACK_FAREWELL and module.callbackOnFarewell ~= nil) then
  229.                 tmpRet = module:callbackOnFarewell(unpack(arg))
  230.             elseif(id == CALLBACK_MESSAGE_DEFAULT and module.callbackOnMessageDefault ~= nil) then
  231.                 tmpRet = module:callbackOnMessageDefault(unpack(arg))
  232.             elseif(id == CALLBACK_MODULE_RESET and module.callbackOnModuleReset ~= nil) then
  233.                 tmpRet = module:callbackOnModuleReset(unpack(arg))
  234.             end
  235.  
  236.             if(not tmpRet) then
  237.                 ret = false
  238.                 break
  239.             end
  240.         end
  241.  
  242.         return ret
  243.     end
  244.  
  245.     -- Returns the message represented by id.
  246.     function NpcHandler:getMessage(id)
  247.         local ret = nil
  248.         if(self.messages ~= nil) then
  249.             ret = self.messages[id]
  250.         end
  251.  
  252.         return ret
  253.     end
  254.  
  255.     -- Changes the default response message with the specified id to newMessage.
  256.     function NpcHandler:setMessage(id, newMessage)
  257.         if(self.messages ~= nil) then
  258.             self.messages[id] = newMessage
  259.         end
  260.     end
  261.  
  262.     -- Translates all message tags found in msg using parseInfo
  263.     function NpcHandler:parseMessage(msg, parseInfo)
  264.         local ret = msg
  265.         for search, replace in pairs(parseInfo) do
  266.             ret = string.gsub(ret, search, replace)
  267.         end
  268.  
  269.         return ret
  270.     end
  271. -- Makes sure the npc un-focuses the currently focused player
  272.     function NpcHandler:unGreet(cid)
  273.         if(not self:isFocused(cid)) then
  274.             return
  275.         end
  276.  
  277.         local callback = self:getCallback(CALLBACK_FAREWELL)
  278.         if(callback == nil or callback(cid)) then
  279.             if(self:processModuleCallback(CALLBACK_FAREWELL)) then
  280.                 local msg = self:getMessage(MESSAGE_FAREWELL)
  281.                 local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) }
  282.                 msg = self:parseMessage(msg, parseInfo)
  283.  
  284.                 self:say(msg, cid)
  285.                 self:releaseFocus(cid)
  286.                 --self:say(msg)
  287.             end
  288.         end
  289.     end
  290.  
  291.     -- Greets a new player.
  292.     function NpcHandler:greet(cid)
  293.         if(cid ~= 0) then
  294.             local callback = self:getCallback(CALLBACK_GREET)
  295.             if(callback == nil or callback(cid)) then
  296.                 if(self:processModuleCallback(CALLBACK_GREET, cid)) then
  297.                     local msg = self:getMessage(MESSAGE_GREET)
  298.                     local parseInfo = { [TAG_PLAYERNAME] = getCreatureName(cid) }
  299.                     msg = self:parseMessage(msg, parseInfo)
  300.  
  301.                     --self:say(msg)
  302.                     self:addFocus(cid)
  303.                     self:say(msg, cid)
  304.                 end
  305.             end
  306.         end
  307.     end
  308.  
  309.     -- Handles onCreatureAppear events. If you with to handle this yourself, please use the CALLBACK_CREATURE_APPEAR callback.
  310.     function NpcHandler:onCreatureAppear(cid)
  311.         local callback = self:getCallback(CALLBACK_CREATURE_APPEAR)
  312.         if(callback == nil or callback(cid)) then
  313.             if(self:processModuleCallback(CALLBACK_CREATURE_APPEAR, cid)) then
  314.                 --
  315.             end
  316.         end
  317.     end
  318.  
  319.     -- Handles onCreatureDisappear events. If you with to handle this yourself, please use the CALLBACK_CREATURE_DISAPPEAR callback.
  320.     function NpcHandler:onCreatureDisappear(cid)
  321.         local callback = self:getCallback(CALLBACK_CREATURE_DISAPPEAR)
  322.         if(callback == nil or callback(cid)) then
  323.             if(self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid)) then
  324.                 if(self:isFocused(cid)) then
  325.                     self:unGreet(cid)
  326.                 end
  327.             end
  328.         end
  329.     end
  330.  
  331.     -- Handles onCreatureSay events. If you with to handle this yourself, please use the CALLBACK_CREATURE_SAY callback.
  332.     function NpcHandler:onCreatureSay(cid, class, msg)
  333.         local callback = self:getCallback(CALLBACK_CREATURE_SAY)
  334.         if(callback == nil or callback(cid, class, msg)) then
  335.             if(self:processModuleCallback(CALLBACK_CREATURE_SAY, cid, class, msg)) then
  336.                 if(not self:isInRange(cid)) then
  337.                     return
  338.                 end
  339.  
  340.                 if(self.keywordHandler ~= nil) then
  341.                     if (self:isFocused(cid) and class == TALKTYPE_PRIVATE_PN) or not self:isFocused(cid) then
  342.                         if not self.keywordHandler:processMessage(cid, msg) then
  343.                             local callback = self:getCallback(CALLBACK_MESSAGE_DEFAULT)
  344.                             if callback ~= nil then
  345.                                 callback(cid, class, msg)
  346.                             end
  347.                         end
  348.                     end
  349.                 end
  350.             end
  351.         end
  352.     end
  353.  
  354.     -- Handles onPlayerEndTrade events. If you wish to handle this yourself, use the CALLBACK_PLAYER_ENDTRADE callback.
  355.     function NpcHandler:onPlayerEndTrade(cid)
  356.         local callback = self:getCallback(CALLBACK_PLAYER_ENDTRADE)
  357.         if(callback == nil or callback(cid)) then
  358.             if(self:processModuleCallback(CALLBACK_PLAYER_ENDTRADE, cid, class, msg)) then
  359.                 if(self:isFocused(cid)) then
  360.                     local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) }
  361.                     local msg = self:parseMessage(self:getMessage(MESSAGE_ONCLOSESHOP), parseInfo)
  362.                     self:say(msg, cid)
  363.                 end
  364.             end
  365.         end
  366.     end
  367.  
  368.     -- Handles onPlayerCloseChannel events. If you wish to handle this yourself, use the CALLBACK_PLAYER_CLOSECHANNEL callback.
  369.     function NpcHandler:onPlayerCloseChannel(cid)
  370.         local callback = self:getCallback(CALLBACK_PLAYER_CLOSECHANNEL)
  371.         if(callback == nil or callback(cid)) then
  372.             if(self:processModuleCallback(CALLBACK_PLAYER_CLOSECHANNEL, cid, class, msg)) then
  373.                 if(self:isFocused(cid)) then
  374.                     self:unGreet(cid)
  375.                 end
  376.             end
  377.         end
  378.     end
  379.  
  380.     -- Handles onBuy events. If you wish to handle this yourself, use the CALLBACK_ONBUY callback.
  381.     function NpcHandler:onBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks)
  382.         local callback = self:getCallback(CALLBACK_ONBUY)
  383.         if(callback == nil or callback(cid, itemid, subType, amount, ignoreCap, inBackpacks)) then
  384.             if(self:processModuleCallback(CALLBACK_ONBUY, cid, itemid, subType, amount, ignoreCap, inBackpacks)) then
  385.                 --
  386.             end
  387.         end
  388.     end
  389.  
  390.     -- Handles onSell events. If you wish to handle this yourself, use the CALLBACK_ONSELL callback.
  391.     function NpcHandler:onSell(cid, itemid, subType, amount, ignoreCap, inBackpacks)
  392.         local callback = self:getCallback(CALLBACK_ONSELL)
  393.         if(callback == nil or callback(cid, itemid, subType, amount, ignoreCap, inBackpacks)) then
  394.             if(self:processModuleCallback(CALLBACK_ONSELL, cid, itemid, subType, amount, ignoreCap, inBackpacks)) then
  395.                 --
  396.             end
  397.         end
  398.     end
  399.  
  400.     -- Handles onThink events. If you wish to handle this yourself, please use the CALLBACK_ONTHINK callback.
  401.     function NpcHandler:onThink()
  402.         local callback = self:getCallback(CALLBACK_ONTHINK)
  403.         if(callback == nil or callback()) then
  404.             if(self:processModuleCallback(CALLBACK_ONTHINK)) then
  405.                 for pos, focus in pairs(self.focuses) do
  406.                     if(focus ~= nil) then
  407.                         if(not self:isInRange(focus)) then
  408.                             self:onWalkAway(focus)
  409.                         else
  410.                             self:updateFocus()
  411.                         end
  412.                     end
  413.                 end
  414.             end
  415.         end
  416.     end
  417.  
  418.     -- Tries to greet the player with the given cid.
  419.     function NpcHandler:onGreet(cid)
  420.         if(self:isInRange(cid)) then
  421.             if(not self:isFocused(cid)) then
  422.                 self:greet(cid)
  423.                 return
  424.             end
  425.         end
  426.     end
  427.  
  428.     -- Simply calls the underlying unGreet function.
  429.     function NpcHandler:onFarewell(cid)
  430.         self:unGreet(cid)
  431.     end
  432.  
  433.     -- Should be called on this npc's focus if the distance to focus is greater then talkRadius.
  434.     function NpcHandler:onWalkAway(cid)
  435.         if(self:isFocused(cid)) then
  436.             local callback = self:getCallback(CALLBACK_CREATURE_DISAPPEAR)
  437.             if(callback == nil or callback(cid)) then
  438.                 if(self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid)) then
  439.                     self:releaseFocus(cid)
  440.                     local msg = self:getMessage(MESSAGE_WALKAWAY)
  441.                     if isPlayer(cid) then
  442.                         msg = self:parseMessage(msg, { [TAG_PLAYERNAME] = getCreatureName(cid) })
  443.                     end
  444.                     self:say(msg)
  445.                 end
  446.             end
  447.         end
  448.     end
  449.  
  450.     -- Returns true if cid is within the talkRadius of this npc.
  451.     function NpcHandler:isInRange(cid)
  452.         local distance = getDistanceTo(cid) or -1
  453.         if(distance == -1) then
  454.             return false
  455.         end
  456.  
  457.         return (distance <= self.talkRadius)
  458.     end
  459.  
  460.     -- Resets the npc into it's initial state (in regard of the keyrodhandler).
  461.     --  All modules are also receiving a reset call through their callbackOnModuleReset function.
  462.     function NpcHandler:resetNpc()
  463.         if(self:processModuleCallback(CALLBACK_MODULE_RESET)) then
  464.             self.keywordHandler:reset()
  465.         end
  466.     end
  467.  
  468.     -- Makes the npc represented by this instance of NpcHandler say something.
  469.     function NpcHandler:say(message, focus)
  470.         if focus then
  471.             selfSay(message, focus)
  472.         else
  473.             selfSay(message)
  474.         end
  475.     end
  476. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement