Advertisement
Guest User

npchandler.lua

a guest
Sep 22nd, 2018
87
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.63 KB | None | 0 0
  1. -- This file is part of Jiddo's advanced NpcSystem v3.0x. This NpcSystem is free to use by anyone, for any purpose.
  2. -- Initial release date: 2007-02-21
  3. -- Credits: Jiddo, honux(I'm using a modified version of his Find function).
  4. -- Please include full credits where ever you use this system, or parts of it.
  5. -- For support, questions and updates, please consult the following thread:
  6. -- http://opentibia.net/topic/59592-release-advanced-npc-system-v30a/
  7.  
  8. if(NpcHandler == nil) then
  9.  
  10. -- Constant talkDelay behaviors.
  11. TALKDELAY_NONE = 0 -- No talkDelay. NPC will reply immediately.
  12. TALKDELAY_ONTHINK = 1 -- talkDelay handled through the onThink callback function. (Default)
  13. TALKDELAY_EVENT = 2 -- Not yet implemented
  14.  
  15. -- Currently applied talkDelay behavior. TALKDELAY_ONTHINK is default.
  16. NPCHANDLER_TALKDELAY = TALKDELAY_ONTHINK
  17.  
  18.  
  19.  
  20. -- Constant indexes for defining default messages.
  21. MESSAGE_GREET = 1 -- When the player greets the NPC.
  22. MESSAGE_FAREWELL = 2 -- When the player unGreets the NPC.
  23. MESSAGE_BUY = 3 -- When the NPC asks the player if he wants to buy something.
  24. MESSAGE_SELL = 4 -- When the NPC asks the player if he wants to sell something.
  25. MESSAGE_ONBUY = 5 -- When the player successfully buys something
  26. MESSAGE_ONSELL = 6 -- When the player successfully sells something
  27. MESSAGE_NEEDMOREMONEY = 7 -- When the player does not have enough money
  28. MESSAGE_NOTHAVEITEM = 8 -- When the player is trying to sell an item he does not have.
  29. MESSAGE_IDLETIMEOUT = 9 -- When the player has been idle for longer then idleTime allows.
  30. MESSAGE_WALKAWAY = 10 -- When the player walks out of the talkRadius of the NPC.
  31. MESSAGE_ALREADYFOCUSED = 11 -- When the player already has the focus of this NPC.
  32. MESSAGE_PLACEDINQUEUE = 12 -- When the player has been placed in the costumer queue.
  33. MESSAGE_DECLINE = 13 -- When the player says no to something.
  34.  
  35. -- Constant indexes for callback functions. These are also used for module callback ids.
  36. CALLBACK_CREATURE_APPEAR = 1
  37. CALLBACK_CREATURE_DISAPPEAR = 2
  38. CALLBACK_CREATURE_SAY = 3
  39. CALLBACK_ONTHINK = 4
  40. CALLBACK_GREET = 5
  41. CALLBACK_FAREWELL = 6
  42. CALLBACK_MESSAGE_DEFAULT = 7
  43.  
  44. -- Additional module callback IDs
  45. CALLBACK_MODULE_INIT = 10
  46. CALLBACK_MODULE_RESET = 11
  47.  
  48.  
  49. -- Constant strings defining the keywords to replace in the default messages.
  50. TAG_PLAYERNAME = '|PLAYERNAME|'
  51. TAG_ITEMCOUNT = '|ITEMCOUNT|'
  52. TAG_TOTALCOST = '|TOTALCOST|'
  53. TAG_ITEMNAME = '|ITEMNAME|'
  54. TAG_QUEUESIZE = '|QUEUESIZE|'
  55. TAG_TIME = '|TIME|'
  56. TAG_TRAVELCOST = '|TRAVELCOST|'
  57.  
  58.  
  59. NpcHandler = {
  60. keywordHandler = nil,
  61. queue = nil,
  62. focus = 0,
  63. talkStart = 0,
  64. idleTime = 30,
  65. talkRadius = 5,
  66. talkDelay = nil,
  67. talkEvent = nil,
  68. callbackFunctions = nil,
  69. modules = nil,
  70. messages = {
  71.  
  72. -- These are the default replies of all NPC's. They can/should be changed individually for each NPC.
  73. [MESSAGE_GREET] = 'Welcome, |PLAYERNAME|! I have been expecting you.',
  74. [MESSAGE_FAREWELL] = 'Good bye, |PLAYERNAME|!',
  75. [MESSAGE_BUY] = 'Do you want to buy |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?',
  76. [MESSAGE_SELL] = 'Do you want to sell |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?',
  77. [MESSAGE_ONBUY] = 'It was a pleasure doing business with you.',
  78. [MESSAGE_ONSELL] = 'Thank you for this item, |PLAYERNAME|.',
  79. [MESSAGE_NEEDMOREMONEY] = 'You do not have enough money.',
  80. [MESSAGE_NOTHAVEITEM] = 'You don\'t even have that item!',
  81. [MESSAGE_IDLETIMEOUT] = 'Next please!',
  82. [MESSAGE_WALKAWAY] = 'How rude!',
  83. [MESSAGE_ALREADYFOCUSED]= '|PLAYERNAME|, I am already talking to you.',
  84. [MESSAGE_PLACEDINQUEUE] = '|PLAYERNAME|, please wait for your turn. There are |QUEUESIZE| customers before you.',
  85. [MESSAGE_DECLINE] = 'Not good enough, is it?'
  86. }
  87. }
  88.  
  89.  
  90. -- Creates a new NpcHandler with an empty callbackFunction stack.
  91. function NpcHandler:new(keywordHandler)
  92. local obj = {}
  93. obj.callbackFunctions = {}
  94. obj.modules = {}
  95. obj.talkDelay = {
  96. message = nil,
  97. time = nil
  98. }
  99. obj.talkEvent = {}
  100. obj.queue = Queue:new(obj)
  101. obj.keywordHandler = keywordHandler
  102. obj.messages = {}
  103. setmetatable(obj.messages, self.messages)
  104. self.messages.__index = self.messages
  105.  
  106. setmetatable(obj, self)
  107. self.__index = self
  108. return obj
  109. end
  110.  
  111. -- Re-defines the maximum idle time allowed for a player when talking to this npc.
  112. function NpcHandler:setMaxIdleTime(newTime)
  113. self.idleTime = newTime
  114. end
  115.  
  116. -- Attaches a new costumer queue to this npcHandler.
  117. function NpcHandler:setQueue(newQueue)
  118. self.queue = newQueue
  119. self.queue:setHandler(self)
  120. end
  121.  
  122. -- Attaches a new keyword handler to this npcHandler
  123. function NpcHandler:setKeywordHandler(newHandler)
  124. self.keywordHandler = newHandler
  125. end
  126.  
  127. -- Function used to change the focus of this NPC.
  128. function NpcHandler:changeFocus(newFocus)
  129. self.focus = newFocus
  130. self:updateFocus()
  131. end
  132.  
  133. -- This function should be called on each onThink and makes sure the NPC faces the player it is talking to.
  134. -- Should also be called whenever a new player is focused.
  135. function NpcHandler:updateFocus()
  136. doNpcSetCreatureFocus(self.focus)
  137. end
  138.  
  139. -- Used when the NPC should release focus from the player.
  140. function NpcHandler:releaseFocus()
  141. if self.talkEvent[self.focus] then
  142. self:cancelStory(self.talkEvent[self.focus])
  143. end
  144. return self:changeFocus(0)
  145. end
  146.  
  147. -- Returns the callback function with the specified id or nil if no such callback function exists.
  148. function NpcHandler:getCallback(id)
  149. local ret = nil
  150. if(self.callbackFunctions ~= nil) then
  151. ret = self.callbackFunctions[id]
  152. end
  153. return ret
  154. end
  155.  
  156. -- Changes the callback function for the given id to callback.
  157. function NpcHandler:setCallback(id, callback)
  158. if(self.callbackFunctions ~= nil) then
  159. self.callbackFunctions[id] = callback
  160. end
  161. end
  162.  
  163. -- Adds a module to this npcHandler and initiates it.
  164. function NpcHandler:addModule(module)
  165. if(self.modules ~= nil) then
  166. table.insert(self.modules, module)
  167. module:init(self)
  168. end
  169. end
  170.  
  171. -- Calls the callback function represented by id for all modules added to this npcHandler with the given arguments.
  172. function NpcHandler:processModuleCallback(id, ...)
  173. local ret = true
  174. for i, module in pairs(self.modules) do
  175. local tmpRet = true
  176. if(id == CALLBACK_CREATURE_APPEAR and module.callbackOnCreatureAppear ~= nil) then
  177. tmpRet = module:callbackCreatureAppear(unpack(arg))
  178.  
  179. elseif(id == CALLBACK_CREATURE_DISAPPEAR and module.callbackOnCreatureDisappear ~= nil) then
  180. tmpRet = module:callbackCreatureDisappear(unpack(arg))
  181.  
  182. elseif(id == CALLBACK_CREATURE_SAY and module.callbackOnCreatureSay ~= nil) then
  183. tmpRet = module:callbackCreatureSay(unpack(arg))
  184.  
  185. elseif(id == CALLBACK_ONTHINK and module.callbackOnThink ~= nil) then
  186. tmpRet = module:callbackOnThink(unpack(arg))
  187.  
  188. elseif(id == CALLBACK_GREET and module.callbackOnGreet ~= nil) then
  189. tmpRet = module:callbackOnGreet(unpack(arg))
  190.  
  191. elseif(id == CALLBACK_FAREWELL and module.callbackOnFarewell ~= nil) then
  192. tmpRet = module:callbackOnFarewell(unpack(arg))
  193.  
  194. elseif(id == CALLBACK_MESSAGE_DEFAULT and module.callbackOnMessageDefault ~= nil) then
  195. tmpRet = module:callbackOnMessageDefault(unpack(arg))
  196.  
  197. elseif(id == CALLBACK_MODULE_RESET and module.callbackOnModuleReset ~= nil) then
  198. tmpRet = module:callbackOnModuleReset(unpack(arg))
  199. end
  200. if(not tmpRet) then
  201. ret = false
  202. break
  203. end
  204. end
  205. return ret
  206. end
  207.  
  208. -- Returns the message represented by id.
  209. function NpcHandler:getMessage(id)
  210. local ret = nil
  211. if(self.messages ~= nil) then
  212. ret = self.messages[id]
  213. end
  214. return ret
  215. end
  216.  
  217. -- Changes the default response message with the specified id to newMessage.
  218. function NpcHandler:setMessage(id, newMessage)
  219. if(self.messages ~= nil) then
  220. self.messages[id] = newMessage
  221. end
  222. end
  223.  
  224. -- Translates all message tags found in MSG using parseInfo
  225. function NpcHandler:parseMessage(msg, parseInfo)
  226. local ret = msg
  227. for search, replace in pairs(parseInfo) do
  228. ret = string.gsub(ret, search, replace)
  229. end
  230. return ret
  231. end
  232.  
  233. -- Makes sure the NPC unfocused the currently focused player, and greets the next player in the queue is it is not empty.
  234. function NpcHandler:unGreet()
  235. if(self.focus == 0) then
  236. return
  237. end
  238. local callback = self:getCallback(CALLBACK_FAREWELL)
  239. if(callback == nil or callback()) then
  240. if(self:processModuleCallback(CALLBACK_FAREWELL)) then
  241. if(self.queue == nil or not self.queue:greetNext()) then
  242. local msg = self:getMessage(MESSAGE_FAREWELL)
  243. local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(self.focus) }
  244. msg = self:parseMessage(msg, parseInfo)
  245. self:say(msg)
  246. self:releaseFocus()
  247. end
  248. end
  249. end
  250. end
  251.  
  252. -- Greets a new player.
  253. function NpcHandler:greet(cid)
  254. if(cid ~= 0) then
  255. local callback = self:getCallback(CALLBACK_GREET)
  256. if(callback == nil or callback(cid)) then
  257. if(self:processModuleCallback(CALLBACK_GREET, cid)) then
  258. local msg = self:getMessage(MESSAGE_GREET)
  259. local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) }
  260. msg = self:parseMessage(msg, parseInfo)
  261. self:say(msg)
  262. else
  263. return
  264. end
  265. else
  266. return
  267. end
  268. end
  269. self:changeFocus(cid)
  270. end
  271.  
  272. -- Handles onCreatureAppear events. If you with to handle this yourself, please use the CALLBACK_CREATURE_APPEAR callback.
  273. function NpcHandler:onCreatureAppear(cid)
  274. local callback = self:getCallback(CALLBACK_CREATURE_APPEAR)
  275. if(callback == nil or callback(cid)) then
  276. if(self:processModuleCallback(CALLBACK_CREATURE_APPEAR, cid)) then
  277.  
  278. end
  279. end
  280. end
  281.  
  282. -- Handles onCreatureDisappear events. If you with to handle this yourself, please use the CALLBACK_CREATURE_DISAPPEAR callback.
  283. function NpcHandler:onCreatureDisappear(cid)
  284. local callback = self:getCallback(CALLBACK_CREATURE_DISAPPEAR)
  285. if(callback == nil or callback(cid)) then
  286. if(self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid)) then
  287. if(self.focus == cid) then
  288. self:unGreet()
  289. end
  290. end
  291. end
  292. end
  293.  
  294. -- Handles onCreatureSay events. If you with to handle this yourself, please use the CALLBACK_CREATURE_SAY callback.
  295. function NpcHandler:onCreatureSay(cid, msgtype, msg)
  296. local callback = self:getCallback(CALLBACK_CREATURE_SAY)
  297. if(callback == nil or callback(cid, msgtype, msg)) then
  298. if(self:processModuleCallback(CALLBACK_CREATURE_SAY, cid, msgtype, msg)) then
  299. if(not self:isInRange(cid)) then
  300. return
  301. end
  302. if(self.keywordHandler ~= nil) then
  303. local ret = self.keywordHandler:processMessage(cid, msg)
  304. if(not ret) then
  305. local callback = self:getCallback(CALLBACK_MESSAGE_DEFAULT)
  306. if(callback ~= nil and callback(cid, msgtype, msg)) then
  307. self.talkStart = os.time()
  308. end
  309. else
  310. self.talkStart = os.time()
  311. end
  312. end
  313. end
  314. end
  315. end
  316.  
  317. -- Handles onThink events. If you with to handle this yourself, please use the CALLBACK_ONTHINK callback.
  318. function NpcHandler:onThink()
  319. local callback = self:getCallback(CALLBACK_ONTHINK)
  320. if(callback == nil or callback()) then
  321.  
  322. if(NPCHANDLER_TALKDELAY == TALKDELAY_ONTHINK and self.talkDelay.time ~= nil and self.talkDelay.message ~= nil and os.time() >= self.talkDelay.time) then
  323. selfSay(self.talkDelay.message)
  324. self.talkDelay.time = nil
  325. self.talkDelay.message = nil
  326. end
  327.  
  328. if(self:processModuleCallback(CALLBACK_ONTHINK)) then
  329. if(self.focus ~= 0) then
  330. if(not self:isInRange(self.focus)) then
  331. self:onWalkAway(self.focus)
  332. elseif(os.time() - self.talkStart > self.idleTime) then
  333. if self.queue:greetNext() then
  334. self.talkStart = self.talkStart + self.idleTime
  335. else
  336. self:unGreet()
  337. end
  338. else
  339. self:updateFocus()
  340. end
  341. end
  342. end
  343. end
  344. end
  345.  
  346. -- Tries to greet the player with the given CID. This function does not override queue order, current focus etc.
  347. function NpcHandler:onGreet(cid)
  348. if(self:isInRange(cid)) then
  349. if(self.focus == 0) then
  350. self:greet(cid)
  351. elseif(cid == self.focus) then
  352. local msg = self:getMessage(MESSAGE_ALREADYFOCUSED)
  353. local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) }
  354. msg = self:parseMessage(msg, parseInfo)
  355. self:say(msg)
  356. else
  357. if(not self.queue:isInQueue(cid)) then
  358. self.queue:push(cid)
  359. end
  360.  
  361. if self.talkEvent[self.focus] then
  362. return
  363. end
  364.  
  365. local msg = self:getMessage(MESSAGE_PLACEDINQUEUE)
  366. local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid), [TAG_QUEUESIZE] = self.queue:getSize() }
  367. msg = self:parseMessage(msg, parseInfo)
  368. self:say(msg)
  369. end
  370. end
  371. end
  372.  
  373. -- Simply calls the underlying unGreet function.
  374. function NpcHandler:onFarewell()
  375. self:unGreet()
  376. end
  377.  
  378. -- Should be called on this NPC's focus if the distance to focus is greater then talkRadius.
  379. function NpcHandler:onWalkAway(cid)
  380. if(cid == self.focus) then
  381. local callback = self:getCallback(CALLBACK_CREATURE_DISAPPEAR)
  382. if(callback == nil or callback()) then
  383. if(self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid)) then
  384. if(self.queue == nil or not self.queue:greetNext()) then
  385. local msg = self:getMessage(MESSAGE_WALKAWAY)
  386. local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(self.focus) }
  387. msg = self:parseMessage(msg, parseInfo)
  388. self:say(msg)
  389. self:releaseFocus()
  390. end
  391. end
  392. end
  393. end
  394. end
  395.  
  396. -- Returns true if CID is within the talkRadius of this NPC.
  397. function NpcHandler:isInRange(cid)
  398. local playerPos = getPlayerPosition(cid)
  399. if playerPos == LUA_ERROR or playerPos == LUA_NO_ERROR then
  400. return false
  401. end
  402.  
  403. local sx = selfGetPosition().x
  404. local sy = selfGetPosition().y
  405. local sz = selfGetPosition().z
  406.  
  407. local dx = math.abs(sx-playerPos.x)
  408. local dy = math.abs(sy-playerPos.y)
  409. local dz = math.abs(sz-playerPos.z)
  410.  
  411. local dist = (dx^2 + dy^2)^0.5
  412.  
  413. return (dist <= self.talkRadius and dz == 0)
  414. end
  415.  
  416. -- Resets the NPC into it's initial state (in regard of the keywordHandler).
  417. -- All modules are also receiving a reset call through their callbackOnModuleReset function.
  418. function NpcHandler:resetNpc()
  419. if(self:processModuleCallback(CALLBACK_MODULE_RESET)) then
  420. self.keywordHandler:reset()
  421. end
  422. end
  423.  
  424. function NpcHandler:cancelStory(event)
  425. if event ~= nil then
  426. stopEvent(event)
  427. event = nil
  428. end
  429. end
  430.  
  431. function NpcHandler:story(messages, npc, delay)
  432.  
  433. if doCreatureSay(npc, messages[1], 1) then
  434. table.remove(messages, 1)
  435. end
  436.  
  437. if messages[1] then
  438. self.talkEvent[self.focus] = addEvent(
  439. function(messages, npc, delay)
  440. return self:story(messages, npc, delay)
  441. end, delay, messages, npc, delay
  442. )
  443. self.talkDelay.time = os.time() + math.max(0, delay / 1000)
  444. return
  445. end
  446. self.talkEvent[self.focus] = nil
  447. end
  448.  
  449. -- Makes the NPC represented by this instance of NpcHandler say something.
  450. -- This implements the currently set type of talkDelay.
  451. function NpcHandler:say(message, delay)
  452.  
  453. if self.talkEvent[self.focus] then
  454. self:cancelStory(self.talkEvent[self.focus])
  455. end
  456.  
  457. if not tonumber(delay) or delay < 0 then
  458. delay = 1000
  459. end
  460.  
  461. if type(message) == 'table' then
  462. self.talkEvent[self.focus] = addEvent(
  463. function(message, npc, delay)
  464. return self:story(message, npc, delay)
  465. end, 1000, message, getNpcCid(), delay
  466. )
  467. else
  468. self.talkDelay.message = message
  469. end
  470.  
  471. self.talkDelay.time = os.time() + math.max(0, delay / 1000)
  472. end
  473. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement