Descreva em algumas palavras a base utilizada. (Nome do servidor / Nome do cliente / Nome do website / etc.).
Base:
The Forgotten Server 1.3, Versão: 8.6
Qual é a sua pergunta?
Estou com um erro ao dar trade nos NPCs
Você tem o código disponível? Se tiver poste-o na caixa de código que está dentro do spoiler abaixo:
Rashid.lua:
Spoiler
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
local function creatureSayCallback(cid, type, msg)
if(not npcHandler:isFocused(cid)) then
return false
end
local player = Player(cid)
if(msgcontains(msg, "mission")) then
if(os.date("%A") == "Monday") then
if(player:getStorageValue(Rashid.MissionStart) < 1) then
npcHandler:say("Well, you could attempt the mission to become a recognised trader, but it requires a lot of travelling. Are you willing to try?", cid)
npcHandler.topic[cid] = 1
elseif(player:getStorageValue(Rashid.MissionStart) == 1) then
npcHandler:say("Have you managed to obtain a rare deer trophy for my customer?", cid)
npcHandler.topic[cid] = 3
end
elseif(os.date("%A") == "Tuesday") then
if(player:getStorageValue(Rashid.MissionStart) == 2 and player:getStorageValue(Rashid.MissionStart+1) < 1 ) then
npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
npcHandler.topic[cid] = 4
elseif(player:getStorageValue(Rashid.MissionStart + 1) == 4) then
npcHandler:say("Did you bring me the package?", cid)
npcHandler.topic[cid] = 6
end
elseif(os.date("%A") == "Wednesday") then
if(player:getStorageValue(Rashid.MissionStart + 1) == 5 and player:getStorageValue(Rashid.MissionStart + 2) < 1 ) then
npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
npcHandler.topic[cid] = 7
elseif(player:getStorageValue(Rashid.MissionStart + 2) == 2) then
npcHandler:say("Have you brought the cheese?", cid)
npcHandler.topic[cid] = 9
end
elseif(os.date("%A") == "Thursday") then
if(player:getStorageValue(Rashid.MissionStart + 2) == 3 and player:getStorageValue(Rashid.MissionStart + 3) < 1) then
npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
npcHandler.topic[cid] = 10
elseif(player:getStorageValue(Rashid.MissionStart + 3) == 2) then
npcHandler:say("Have you brought the vase?", cid)
npcHandler.topic[cid] = 12
end
elseif(os.date("%A") == "Friday") then
if(player:getStorageValue(Rashid.MissionStart + 3) == 3 and player:getStorageValue(Rashid.MissionStart + 4) < 1) then
npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
npcHandler.topic[cid] = 13
elseif(player:getStorageValue(Rashid.MissionStart + 4) == 2) then
npcHandler:say("Have you brought a cheap but good crimson sword?", cid)
npcHandler.topic[cid] = 15
end
elseif(os.date("%A") == "Saturday") then
if(player:getStorageValue(Rashid.MissionStart + 4) == 3 and player:getStorageValue(Rashid.MissionStart + 5) < 1) then
npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
npcHandler.topic[cid] = 16
elseif(player:getStorageValue(Rashid.MissionStart + 5) == 1) then
npcHandler:say("Have you brought me a gold fish??", cid)
npcHandler.topic[cid] = 18
end
elseif(os.date("%A") == "Sunday") then
if(player:getStorageValue(Rashid.MissionStart + 5) == 2 and player:getStorageValue(Rashid.MissionStart + 6) < 1) then
npcHandler:say("Ah, right. <ahem> I hereby declare you - one of my recognised traders! Feel free to offer me your wares!", cid)
player:setStorageValue(Rashid.MissionStart + 6, 1)
--player:AddArchie(id or name)
npcHandler.topic[cid] = 0
end
end
elseif(msgcontains(msg, "yes")) then
if(npcHandler.topic[cid] == 1) then
npcHandler:say(
{
"Very good! I need talented people who are able to handle my wares with care, find good offers and the like, so I'm going to test you. ...",
"First, I'd like to see if you can dig up rare wares. Something like a ... mastermind shield! ...",
"Haha, just kidding, fooled you there, didn't I? Always control your nerves, that's quite important during bargaining. ...",
"Okay, all I want from you is one of these rare deer trophies. I have a customer here in Svargrond who ordered one, so I'd like you to deliver it tome while I'm in Svargrond. ...",
"Everything clear and understood?"
}, cid)
npcHandler.topic[cid] = 2
elseif(npcHandler.topic[cid] == 2) then
npcHandler:say("Fine. Then get a hold of that deer trophy and bring it to me while I'm in Svargrond. Just ask me about your mission.", cid)
player:setStorageValue(Rashid.MissionStart, 1)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 3) then
if(player:getItemCount(7397) >= 1) then
player:removeItem(7397, 1)
npcHandler:say("Well done! I'll take that from you. <snags it> Come see me another day, I'll be busy for a while now. ", cid)
player:setStorageValue(Rashid.MissionStart, 2)
npcHandler.topic[cid] = 0
end
elseif(npcHandler.topic[cid] == 4) then
npcHandler:say(
{
"Alright, that's good to hear. From you as my trader and deliveryman, I expect more than finding rare items. ...",
"You also need to be able to transport heavy wares, weaklings won't get far here. I have ordered a special package from Edron. ...",
"Pick it up from Willard and bring it back to me while I'm in Liberty Bay. Everything clear and understood?"
}, cid)
npcHandler.topic[cid] = 5
elseif(npcHandler.topic[cid] == 5) then
npcHandler:say("Fine. Then off you go, just ask Willard about the 'package for Rashid'.", cid)
player:setStorageValue(Rashid.MissionStart + 1, 1)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 6) then
if(player:getItemCount(7503) >= 1) then
player:removeItem(7503, 1)
npcHandler:say("Great. Just place it over there - yes, thanks, that's it. Come see me another day, I'll be busy for a while now. ", cid)
player:setStorageValue(Rashid.MissionStart + 1, 5)
npcHandler.topic[cid] = 0
end
elseif(npcHandler.topic[cid] == 7) then
npcHandler:say(
{
"Well, that's good to hear. From you as my trader and deliveryman, I expect more than carrying heavy packages. ...",
"You also need to be fast and deliver wares in time. I have ordered a very special cheese wheel made from Darashian milk. ...",
"Unfortunately, the high temperature in the desert makes it rot really fast, so it must not stay in the sun for too long. ...",
"I'm also afraid that you might not be able to use ships because of the smell of the cheese. ...",
"Please get the cheese from Miraia and bring it to me while I'm in Port Hope. Everything clear and understood?"
}, cid)
npcHandler.topic[cid] = 8
elseif(npcHandler.topic[cid] == 8) then
npcHandler:say("Okay, then please find Miraia in Darashia and ask her about the {'scarab cheese'}.", cid)
player:setStorageValue(Rashid.MissionStart + 2, 1)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 9) then
if(player:getItemCount(8112) >= 1) then
player:removeItem(8112, 1)
--if (player:getStorageValue(ExplorerSocietySTORAGE) == 1) then
--player:AddArchie(Just in Time)
--end
npcHandler:say("Mmmhh, the lovely odeur of scarab cheese! I really can't understand why most people can't stand it. Thanks, well done! ", cid)
player:setStorageValue(Rashid.MissionStart + 2, 3)
npcHandler.topic[cid] = 0
end
elseif(npcHandler.topic[cid] == 10) then
npcHandler:say(
{
"Well, that's good to hear. From you as my trader and deliveryman, I expect more than bringing stinky cheese. ...",
"I wonder if you are able to deliver goods so fragile they almost break when looked at. ...",
"I have ordered a special elven vase from Briasol in Ab'Dendriel. Get it from him and don't even touch it, just bring it to me while I'm in Ankrahmun. Everything clear and understood?"
}, cid)
npcHandler.topic[cid] = 11
elseif(npcHandler.topic[cid] == 11) then
npcHandler:say("Okay, then please find {Briasol} in {Ab'Dendriel} and ask for a {'fine vase'}.", cid)
player:setStorageValue(Rashid.MissionStart + 3, 1)
player:addMoney(1000)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 12) then
if(player:getItemCount(8760) >= 1) then
player:removeItem(8760, 1)
npcHandler:say("I'm surprised that you managed to bring this vase without a single crack. That was what I needed to know, thank you. ", cid)
player:setStorageValue(Rashid.MissionStart + 3, 3)
npcHandler.topic[cid] = 0
end
elseif(npcHandler.topic[cid] == 13) then
npcHandler:say(
{
"Fine! There's one more skill that I need to test and which is cruicial for a successful trader. ...",
"Of course you must be able to haggle, else you won't survive long in this business. To make things as hard as possible for you, I have the perfect trade partner for you. ...",
"Dwarves are said to be the most stubborn of all traders. Travel to {Kazordoon} and try to get the smith {Uzgod} to sell a {crimson sword} to you. ...",
"Of course, it has to be cheap. Don't come back with anything more expensive than 400 gold. ...",
"And the quality must not suffer, of course! Everything clear and understood?",
"Dwarves are said to be the most stubborn of all traders. Travel to Kazordoon and try to get the smith Uzgod to sell a crimson sword to you. ..."
}, cid)
npcHandler.topic[cid] = 14
elseif(npcHandler.topic[cid] == 14) then
npcHandler:say("Okay, I'm curious how you will do with {Uzgod}. Good luck!", cid)
player:setStorageValue(Rashid.MissionStart + 4, 1)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 15) then
if(player:getItemCount(7385) >= 1) then
player:removeItem(7385, 1)
npcHandler:say("Ha! You are clever indeed, well done! I'll take this from you. Come see me tomorrow, I think we two might get into business after all.", cid)
player:setStorageValue(Rashid.MissionStart + 4, 3)
npcHandler.topic[cid] = 0
end
elseif(npcHandler.topic[cid] == 16) then
npcHandler:say(
{
"My friend, it seems you have already learnt a lot about the art of trading. I think you are more than worthy to become a recognised trader. ...",
"There is just one little favour that I would ask from you... something personal, actually, forgive my boldness. ...",
"I have always dreamed to have a small pet, one that I could take with me and which wouldn't cause problems. ...",
"Could you - just maybe - bring me a small goldfish in a bowl? I know that you would be able to get one, wouldn't you?"
}, cid)
npcHandler.topic[cid] = 17
elseif(npcHandler.topic[cid] == 17) then
npcHandler:say("Thanks so much! I'll be waiting eagerly for your return then.", cid)
player:setStorageValue(Rashid.MissionStart + 5, 1)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 18) then
if(player:getItemCount(8766) >= 1) then
player:removeItem(8766, 1)
npcHandler:say("Thank you!! Ah, this makes my day! I'll take the rest of the day off to get to know this little guy. Come see me tomorrow, if you like.", cid)
player:setStorageValue(Rashid.MissionStart + 5, 2)
npcHandler.topic[cid] = 0
end
end
end
return true
end
local function onTradeRequest(cid)
TradeRequest(cid, npcHandler, getTable(), Rashid, 1)
end
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 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_NEEDMONEY = 10 -- Same as above, used for shop window.
MESSAGE_MISSINGITEM = 11 -- When the player is trying to sell an item he does not have.
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_WALKAWAY_MALE = 22 -- When a male player walks out of the talkRadius of the npc.
MESSAGE_WALKAWAY_FEMALE = 23 -- When a female player walks out of the talkRadius of the npc.
-- 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
CALLBACK_ONADDFOCUS = 18
CALLBACK_ONRELEASEFOCUS = 19
CALLBACK_ONTRADEREQUEST = 20
-- Constant strings defining the keywords to replace in the default messages.
TAG_PLAYERNAME = "|PLAYERNAME|"
TAG_ITEMCOUNT = "|ITEMCOUNT|"
TAG_TOTALCOST = "|TOTALCOST|"
TAG_ITEMNAME = "|ITEMNAME|"
NpcHandler = {
keywordHandler = nil,
focuses = nil,
talkStart = nil,
idleTime = 120,
talkRadius = 3,
talkDelayTime = 1, -- Seconds to delay outgoing messages.
talkDelay = nil,
callbackFunctions = nil,
modules = nil,
shopItems = nil, -- They must be here since ShopModule uses 'static' functions
eventSay = nil,
eventDelayedSay = nil,
topic = nil,
messages = {
-- These are the default replies of all npcs. They can/should be changed individually for each npc.
[MESSAGE_GREET] = "Greetings, |PLAYERNAME|.",
[MESSAGE_FAREWELL] = "Good bye, |PLAYERNAME|.",
[MESSAGE_BUY] = "Do you want to buy |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?",
[MESSAGE_ONBUY] = "Here you are.",
[MESSAGE_BOUGHT] = "Bought |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.",
[MESSAGE_SELL] = "Do you want to sell |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?",
[MESSAGE_ONSELL] = "Here you are, |TOTALCOST| gold.",
[MESSAGE_SOLD] = "Sold |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.",
[MESSAGE_MISSINGMONEY] = "You don't have enough money.",
[MESSAGE_NEEDMONEY] = "You don't have enough money.",
[MESSAGE_MISSINGITEM] = "You don't have so many.",
[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] = "Good bye.",
[MESSAGE_WALKAWAY] = "Good bye.",
[MESSAGE_DECLINE] = "Then not.",
[MESSAGE_SENDTRADE] = "Of course, just browse through my wares.",
[MESSAGE_NOSHOP] = "Sorry, I'm not offering anything.",
[MESSAGE_ONCLOSESHOP] = "Thank you, come back whenever you're in need of something else.",
[MESSAGE_ALREADYFOCUSED]= "|PLAYERNAME|, I am already talking to you.",
[MESSAGE_WALKAWAY_MALE] = "Good bye.",
[MESSAGE_WALKAWAY_FEMALE] = "Good bye."
}
}
-- Creates a new NpcHandler with an empty callbackFunction stack.
function NpcHandler:new(keywordHandler)
local obj = {}
obj.callbackFunctions = {}
obj.modules = {}
obj.eventSay = {}
obj.eventDelayedSay = {}
obj.topic = {}
obj.focuses = {}
obj.talkStart = {}
obj.talkDelay = {}
obj.keywordHandler = keywordHandler
obj.messages = {}
obj.shopItems = {}
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 self:isFocused(newFocus) then
return
end
self.focuses[#self.focuses + 1] = newFocus
self.topic[newFocus] = 0
local callback = self:getCallback(CALLBACK_ONADDFOCUS)
if callback == nil or callback(newFocus) then
self:processModuleCallback(CALLBACK_ONADDFOCUS, newFocus)
end
self:updateFocus()
end
-- Function used to verify if npc is focused to certain player
function NpcHandler:isFocused(focus)
for k,v in pairs(self.focuses) do
if v == focus then
return true
end
end
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()
for pos, focus in pairs(self.focuses) do
if focus ~= nil then
doNpcSetCreatureFocus(focus)
return
end
end
doNpcSetCreatureFocus(0)
end
-- Used when the npc should un-focus the player.
function NpcHandler:releaseFocus(focus)
if shop_cost[focus] ~= nil then
table.remove(shop_amount, focus)
table.remove(shop_cost, focus)
table.remove(shop_rlname, focus)
table.remove(shop_itemid, focus)
table.remove(shop_container, focus)
table.remove(shop_npcuid, focus)
table.remove(shop_eventtype, focus)
table.remove(shop_subtype, focus)
table.remove(shop_destination, focus)
table.remove(shop_premium, focus)
end
if self.eventDelayedSay[focus] then
self:cancelNPCTalk(self.eventDelayedSay[focus])
end
if not self:isFocused(focus) then
return
end
local pos = nil
for k,v in pairs(self.focuses) do
if v == focus then
pos = k
end
end
table.remove(self.focuses, pos)
local callback = self:getCallback(CALLBACK_ONRELEASEFOCUS)
if callback == nil or callback(focus) then
self:processModuleCallback(CALLBACK_ONRELEASEFOCUS, focus)
end
if Player(focus) ~= nil then
closeShopWindow(focus) --Even if it can not exist, we need to prevent it.
self:updateFocus()
end
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 then
self.modules[#self.modules +1] = module
module:init(self)
end
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 i, 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_ONTRADEREQUEST and module.callbackOnTradeRequest ~= nil then
tmpRet = module:callbackOnTradeRequest(...)
elseif id == CALLBACK_ONADDFOCUS and module.callbackOnAddFocus ~= nil then
tmpRet = module:callbackOnAddFocus(...)
elseif id == CALLBACK_ONRELEASEFOCUS and module.callbackOnReleaseFocus ~= nil then
tmpRet = module:callbackOnReleaseFocus(...)
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)
local ret = msg
for search, replace in pairs(parseInfo) do
ret = string.gsub(ret, search, replace)
end
return ret
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() then
if self:processModuleCallback(CALLBACK_FAREWELL) then
local msg = self:getMessage(MESSAGE_FAREWELL)
local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
self:resetNpc(cid)
msg = self:parseMessage(msg, parseInfo)
self:say(msg, cid, true)
self:releaseFocus(cid)
end
end
end
-- Greets a new player.
function NpcHandler:greet(cid)
if cid ~= 0 then
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)
local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
msg = self:parseMessage(msg, parseInfo)
self:say(msg, cid, true)
else
return
end
else
return
end
end
self:addFocus(cid)
end
-- Handles onCreatureAppear events. If you with to handle this yourself, please use the CALLBACK_CREATURE_APPEAR callback.
function NpcHandler:onCreatureAppear(cid)
if cid == getNpcCid() and next(self.shopItems) ~= nil then
local npc = Npc()
local speechBubble = npc:getSpeechBubble()
if speechBubble == 3 then
npc:setSpeechBubble(4)
else
npc:setSpeechBubble(2)
end
end
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)
if getNpcCid() == cid then
return
end
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, msgtype, msg)
local callback = self:getCallback(CALLBACK_CREATURE_SAY)
if callback == nil or callback(cid, msgtype, msg) then
if self:processModuleCallback(CALLBACK_CREATURE_SAY, cid, msgtype, msg) then
if not self:isInRange(cid) then
return
end
if self.keywordHandler ~= nil then
if self:isFocused(cid) and msgtype == TALKTYPE_PRIVATE_PN or not self:isFocused(cid) 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, msgtype, msg) then
self.talkStart[cid] = os.time()
end
else
self.talkStart[cid] = os.time()
end
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, msgtype, msg) then
if self:isFocused(cid) then
local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
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, msgtype, msg) 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 onTradeRequest events. If you wish to handle this yourself, use the CALLBACK_ONTRADEREQUEST callback.
function NpcHandler:onTradeRequest(cid)
local callback = self:getCallback(CALLBACK_ONTRADEREQUEST)
if callback == nil or callback(cid) then
if self:processModuleCallback(CALLBACK_ONTRADEREQUEST, cid) then
return true
end
end
return false
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
if NPCHANDLER_TALKDELAY == TALKDELAY_ONTHINK then
for cid, talkDelay in pairs(self.talkDelay) do
if talkDelay.time ~= nil and talkDelay.message ~= nil and os.time() >= talkDelay.time then
selfSay(talkDelay.message, cid, talkDelay.publicize and true or false)
self.talkDelay[cid] = nil
end
end
end
if self:processModuleCallback(CALLBACK_ONTHINK) then
for pos, focus in pairs(self.focuses) do
if focus ~= nil then
if not self:isInRange(focus) then
self:onWalkAway(focus)
elseif self.talkStart[focus] ~= nil and (os.time() - self.talkStart[focus]) > self.idleTime then
self:unGreet(focus)
else
self:updateFocus()
end
end
end
end
end
end
-- Tries to greet the player with the given cid.
function NpcHandler:onGreet(cid)
if self:isInRange(cid) then
if not self:isFocused(cid) then
self:greet(cid)
return
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() then
if self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid) then
local msg = self:getMessage(MESSAGE_WALKAWAY)
local playerName = Player(cid):getName()
if not playerName then
playerName = -1
end
local parseInfo = { [TAG_PLAYERNAME] = playerName }
local message = self:parseMessage(msg, parseInfo)
local msg_male = self:getMessage(MESSAGE_WALKAWAY_MALE)
local message_male = self:parseMessage(msg_male, parseInfo)
local msg_female = self:getMessage(MESSAGE_WALKAWAY_FEMALE)
local message_female = self:parseMessage(msg_female, parseInfo)
if message_female ~= message_male then
if Player(cid):getSex() == 0 then
selfSay(message_female)
else
selfSay(message_male)
end
elseif message ~= "" then
selfSay(message)
end
self:resetNpc(cid)
self:releaseFocus(cid)
end
end
end
end
-- Returns true if cid is within the talkRadius of this npc.
function NpcHandler:isInRange(cid)
local distance = getDistanceBetween(getCreaturePosition(getNpcCid()), getCreaturePosition(cid))
if(distance == -1) then
return false
end
return (distance <= self.talkRadius)
end
-- Resets the npc into its initial state (in regard of the keywordhandler).
-- 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
function NpcHandler:cancelNPCTalk(events)
for aux = 1, #events do
stopEvent(events[aux].event)
end
events = nil
end
function NpcHandler:doNPCTalkALot(msgs, interval, pcid)
if self.eventDelayedSay[pcid] then
self:cancelNPCTalk(self.eventDelayedSay[pcid])
end
self.eventDelayedSay[pcid] = {}
local ret = {}
for aux = 1, #msgs do
self.eventDelayedSay[pcid][aux] = {}
doCreatureSayWithDelay(getNpcCid(), msgs[aux], TALKTYPE_PRIVATE_NP, ((aux-1) * (interval or 4000)) + 700, self.eventDelayedSay[pcid][aux], pcid)
ret[#ret +1] = self.eventDelayedSay[pcid][aux]
end
return(ret)
end
-- Makes the npc represented by this instance of NpcHandler say something.
-- This implements the currently set type of talkdelay.
-- shallDelay is a boolean value. If it is false, the message is not delayed. Default value is true.
function NpcHandler:say(message, focus, publicize, shallDelay, delay)
if type(message) == "table" then
return self:doNPCTalkALot(message, delay or 6000, focus)
end
if self.eventDelayedSay[focus] then
self:cancelNPCTalk(self.eventDelayedSay[focus])
end
local shallDelay = not shallDelay and true or shallDelay
if NPCHANDLER_TALKDELAY == TALKDELAY_NONE or shallDelay == false then
selfSay(message, focus, publicize and true or false)
return
end
stopEvent(self.eventSay[focus])
self.eventSay[focus] = addEvent(function(x) if isPlayer(x[3]) then doCreatureSay(x[1], x[2], TALKTYPE_PRIVATE_NP, false, x[3], getCreaturePosition(x[1])) end end, self.talkDelayTime * 1000, {getNpcCid(), message, focus})
end
end
Modules:
Spoiler
-- Advanced NPC System by Jiddo
if Modules == nil then
-- default words for greeting and ungreeting the npc. Should be a table containing all such words.
FOCUS_GREETWORDS = {"hi", "hello"}
FOCUS_FAREWELLWORDS = {"bye", "farewell"}
-- The words for requesting trade window.
SHOP_TRADEREQUEST = {"trade"}
-- The word for accepting/declining an offer. CAN ONLY CONTAIN ONE FIELD! Should be a table with a single string value.
SHOP_YESWORD = {"yes"}
SHOP_NOWORD = {"no"}
-- Pattern used to get the amount of an item a player wants to buy/sell.
PATTERN_COUNT = "%d+"
-- Constants used to separate buying from selling.
SHOPMODULE_SELL_ITEM = 1
SHOPMODULE_BUY_ITEM = 2
SHOPMODULE_BUY_ITEM_CONTAINER = 3
-- Constants used for shop mode. Notice: addBuyableItemContainer is working on all modes
SHOPMODULE_MODE_TALK = 1 -- Old system used before client version 8.2: sell/buy item name
SHOPMODULE_MODE_TRADE = 2 -- Trade window system introduced in client version 8.2
SHOPMODULE_MODE_BOTH = 3 -- Both working at one time
-- Used shop mode
SHOPMODULE_MODE = SHOPMODULE_MODE_BOTH
Modules = {
parseableModules = {}
}
StdModule = {}
-- These callback function must be called with parameters.npcHandler = npcHandler in the parameters table or they will not work correctly.
-- Notice: The members of StdModule have not yet been tested. If you find any bugs, please report them to me.
-- Usage:
-- keywordHandler:addKeyword({"offer"}, StdModule.say, {npcHandler = npcHandler, text = "I sell many powerful melee weapons."})
function StdModule.say(cid, message, keywords, parameters, node)
local npcHandler = parameters.npcHandler
if npcHandler == nil then
error("StdModule.say called without any npcHandler instance.")
end
local onlyFocus = (parameters.onlyFocus == nil or parameters.onlyFocus == true)
if not npcHandler:isFocused(cid) and onlyFocus then
return false
end
local parseInfo = {[TAG_PLAYERNAME] = Player(cid):getName()}
npcHandler:say(npcHandler:parseMessage(parameters.text or parameters.message, parseInfo), cid, parameters.publicize and true)
if not parameters.reset == false then
npcHandler:resetNpc(cid)
elseif parameters.moveup ~= nil then
npcHandler.keywordHandler:moveUp(parameters.moveup)
end
return true
end
--Usage:
-- local node1 = keywordHandler:addKeyword({"promot"}, StdModule.say, {npcHandler = npcHandler, text = "I can promote you for 20000 gold coins. Do you want me to promote you?"})
-- node1:addChildKeyword({"yes"}, StdModule.promotePlayer, {npcHandler = npcHandler, cost = 20000, level = 20}, text = "Congratulations! You are now promoted.")
-- node1:addChildKeyword({"no"}, StdModule.say, {npcHandler = npcHandler, text = "Allright then. Come back when you are ready."}, reset = true)
function StdModule.promotePlayer(cid, message, keywords, parameters, node)
local npcHandler = parameters.npcHandler
if npcHandler == nil then
error("StdModule.promotePlayer called without any npcHandler instance.")
end
if not npcHandler:isFocused(cid) then
return false
end
if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then
local promotedVoc = getPromotedVocation(getPlayerVocation(cid))
if(getPlayerStorageValue(cid, 30018) == 1) then
npcHandler:say("You are already promoted!", cid)
elseif(getPlayerLevel(cid) < parameters.level) then
npcHandler:say("I am sorry, but I can only promote you once you have reached level " .. parameters.level .. ".", cid)
elseif(doPlayerRemoveMoney(cid, parameters.cost) ~= TRUE) then
npcHandler:say("You do not have enough money!", cid)
else
doPlayerSetVocation(cid, promotedVoc)
npcHandler:say(parameters.text, cid)
end
else
npcHandler:say("You need a premium account in order to get promoted.", cid)
end
npcHandler:resetNpc(cid)
return true
end
function StdModule.learnSpell(cid, message, keywords, parameters, node)
local npcHandler = parameters.npcHandler
if npcHandler == nil then
error("StdModule.learnSpell called without any npcHandler instance.")
end
if not npcHandler:isFocused(cid) then
return false
end
if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) or not parameters.premium) then
if getPlayerLearnedInstantSpell(cid, parameters.spellName) == TRUE then
npcHandler:say("You already know this spell.", cid)
elseif getPlayerLevel(cid) < parameters.level then
npcHandler:say("You need to obtain a level of " .. parameters.level .. " or higher to be able to learn " .. parameters.spellName .. ".", cid)
elseif getPlayerVocation(cid) ~= parameters.vocation and getPlayerVocation(cid) ~= parameters.vocation + 4 and vocation ~= 9 then
npcHandler:say("This spell is not for your vocation", cid)
elseif doPlayerRemoveMoney(cid, parameters.price) == FALSE then
npcHandler:say("You do not have enough money, this spell costs " .. parameters.price .. " gold.", cid)
else
npcHandler:say("You have learned " .. parameters.spellName .. ".", cid)
playerLearnInstantSpell(cid, parameters.spellName)
end
else
npcHandler:say("You need a premium account in order to buy " .. parameters.spellName .. ".", cid)
end
npcHandler:resetNpc(cid)
return true
end
function StdModule.bless(cid, message, keywords, parameters, node)
local npcHandler = parameters.npcHandler
if npcHandler == nil then
error("StdModule.bless called without any npcHandler instance.")
end
if(not npcHandler:isFocused(cid) or getWorldType() == WORLD_TYPE_PVP_ENFORCED) then
return false
end
if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then
if getPlayerBlessing(cid, parameters.bless) then
npcHandler:say("Gods have already blessed you with this blessing!", cid)
elseif doPlayerRemoveMoney(cid, parameters.cost) == FALSE then
npcHandler:say("You don't have enough money for blessing.", cid)
else
npcHandler:say("You have been blessed by one of the five gods!", cid)
doPlayerAddBlessing(cid, parameters.bless)
end
else
npcHandler:say("You need a premium account in order to be blessed.", cid)
end
npcHandler:resetNpc(cid)
return true
end
function StdModule.travel(cid, message, keywords, parameters, node)
local npcHandler = parameters.npcHandler
if npcHandler == nil then
error("StdModule.travel called without any npcHandler instance.")
end
if(not npcHandler:isFocused(cid)) then
return false
end
if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then
if(isPlayerPzLocked(cid)) then
npcHandler:say("First get rid of those blood stains! You are not going to ruin my vehicle!", cid)
elseif(parameters.level ~= nil and getPlayerLevel(cid) < parameters.level) then
npcHandler:say("You must reach level " .. parameters.level .. " before I can let you go there.", cid)
elseif(doPlayerRemoveMoney(cid, parameters.cost) ~= TRUE) then
npcHandler:say("You don't have enough money.", cid)
else
npcHandler:say(parameters.msg or "Set the sails!", cid)
npcHandler:releaseFocus(cid)
doSendMagicEffect(getCreaturePosition(cid), CONST_ME_TELEPORT)
doTeleportThing(cid, parameters.destination)
doSendMagicEffect(parameters.destination, CONST_ME_TELEPORT)
end
else
npcHandler:say("I'm sorry, but you need a premium account in order to travel onboard our ships.", cid)
end
npcHandler:resetNpc(cid)
return true
end
FocusModule = {
npcHandler = nil
}
-- Creates a new instance of FocusModule without an associated NpcHandler.
function FocusModule:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
-- Inits the module and associates handler to it.
function FocusModule:init(handler)
self.npcHandler = handler
for i, word in pairs(FOCUS_GREETWORDS) do
local obj = {}
obj[#obj + 1] = word
obj.callback = FOCUS_GREETWORDS.callback or FocusModule.messageMatcher
handler.keywordHandler:addKeyword(obj, FocusModule.onGreet, {module = self})
end
for i, word in pairs(FOCUS_FAREWELLWORDS) do
local obj = {}
obj[#obj + 1] = word
obj.callback = FOCUS_FAREWELLWORDS.callback or FocusModule.messageMatcher
handler.keywordHandler:addKeyword(obj, FocusModule.onFarewell, {module = self})
end
return true
end
-- Greeting callback function.
function FocusModule.onGreet(cid, message, keywords, parameters)
parameters.module.npcHandler:onGreet(cid)
return true
end
-- UnGreeting callback function.
function FocusModule.onFarewell(cid, message, keywords, parameters)
if parameters.module.npcHandler:isFocused(cid) then
parameters.module.npcHandler:onFarewell(cid)
return true
else
return false
end
end
-- Custom message matching callback function for greeting messages.
function FocusModule.messageMatcher(keywords, message)
for i, word in pairs(keywords) do
if type(word) == "string" then
if string.find(message, word) and not string.find(message, "[%w+]" .. word) and not string.find(message, word .. "[%w+]") then
return true
end
end
end
return false
end
KeywordModule = {
npcHandler = nil
}
-- Add it to the parseable module list.
Modules.parseableModules["module_keywords"] = KeywordModule
function KeywordModule:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
function KeywordModule:init(handler)
self.npcHandler = handler
return true
end
-- Parses all known parameters.
function KeywordModule:parseParameters()
local ret = NpcSystem.getParameter("keywords")
if ret ~= nil then
self:parseKeywords(ret)
end
end
function KeywordModule:parseKeywords(data)
local n = 1
for keys in string.gmatch(data, "[^;]+") do
local i = 1
local keywords = {}
for temp in string.gmatch(keys, "[^,]+") do
keywords[#keywords + 1] = temp
i = i + 1
end
if i ~= 1 then
local reply = NpcSystem.getParameter("keyword_reply" .. n)
if reply ~= nil then
self:addKeyword(keywords, reply)
else
print("[Warning] NpcSystem:", "Parameter '" .. "keyword_reply" .. n .. "' missing. Skipping...")
end
else
print("[Warning] NpcSystem:", "No keywords found for keyword set #" .. n .. ". Skipping...")
end
n = n+1
end
end
function KeywordModule:addKeyword(keywords, reply)
self.npcHandler.keywordHandler:addKeyword(keywords, StdModule.say, {npcHandler = self.npcHandler, onlyFocus = true, text = reply, reset = true})
end
TravelModule = {
npcHandler = nil,
destinations = nil,
yesNode = nil,
noNode = nil,
}
-- Add it to the parseable module list.
Modules.parseableModules["module_travel"] = TravelModule
function TravelModule:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
-- Parses all known parameters.
function TravelModule:parseParameters()
local ret = NpcSystem.getParameter("travel_destinations")
if ret ~= nil then
self:parseDestinations(ret)
function TravelModule:parseDestinations(data)
for destination in string.gmatch(data, "[^;]+") do
local i = 1
local name = nil
local x = nil
local y = nil
local z = nil
local cost = nil
local premium = false
for temp in string.gmatch(destination, "[^,]+") do
if i == 1 then
name = temp
elseif i == 2 then
x = tonumber(temp)
elseif i == 3 then
y = tonumber(temp)
elseif i == 4 then
z = tonumber(temp)
elseif i == 5 then
cost = tonumber(temp)
elseif i == 6 then
premium = temp == "true"
else
print("[Warning : " .. getCreatureName(getNpcCid()) .. "] NpcSystem:", "Unknown parameter found in travel destination parameter.", temp, destination)
end
i = i + 1
end
if(name ~= nil and x ~= nil and y ~= nil and z ~= nil and cost ~= nil) then
self:addDestination(name, {x=x, y=y, z=z}, cost, premium)
else
print("[Warning] NpcSystem:", "Parameter(s) missing for travel destination:", name, x, y, z, cost, premium)
end
end
end
function TravelModule:addDestination(name, position, price, premium)
self.destinations[#self.destinations + 1] = name
local parameters = {
cost = price,
destination = position,
premium = premium,
module = self
}
local keywords = {}
keywords[#keywords + 1] = name
local keywords2 = {}
keywords2[#keywords2 + 1] = "bring me to " .. name
local node = self.npcHandler.keywordHandler:addKeyword(keywords, TravelModule.travel, parameters)
self.npcHandler.keywordHandler:addKeyword(keywords2, TravelModule.bringMeTo, parameters)
node:addChildKeywordNode(self.yesNode)
node:addChildKeywordNode(self.noNode)
if npcs_loaded_travel[getNpcCid()] == nil then
npcs_loaded_travel[getNpcCid()] = getNpcCid()
self.npcHandler.keywordHandler:addKeyword({'yes'}, TravelModule.onConfirm, {module = self})
self.npcHandler.keywordHandler:addKeyword({'no'}, TravelModule.onDecline, {module = self})
end
end
function TravelModule.travel(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) then
return false
end
local cost = parameters.cost
local destination = parameters.destination
local premium = parameters.premium
module.npcHandler:say("Do you want to travel to " .. keywords[1] .. " for " .. cost .. " gold coins?", cid)
return true
end
function TravelModule.onConfirm(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) then
return false
end
if shop_npcuid[cid] ~= getNpcCid() then
return false
end
local npcHandler = module.npcHandler
local parentParameters = node:getParent():getParameters()
local cost = shop_cost[cid]
local destination = shop_destination[cid]
local premium = shop_premium[cid]
if(not isPlayerPremiumCallback or isPlayerPremiumCallback(cid) or shop_premium[cid] ~= true) then
if(doPlayerRemoveMoney(cid, cost) ~= TRUE) then
npcHandler:say("You do not have enough money!", cid)
elseif(isPlayerPzLocked(cid)) then
npcHandler:say("Get out of there with this blood.", cid)
else
npcHandler:say("It was a pleasure doing business with you.", cid)
npcHandler:releaseFocus(cid)
doTeleportThing(cid, destination, 0)
doSendMagicEffect(destination, CONST_ME_TELEPORT)
end
else
npcHandler:say("I can only allow premium players to travel there.", cid)
end
npcHandler:resetNpc(cid)
return true
end
-- onDecline keyword callback function. Generally called when the player sais "no" after wanting to buy an item.
function TravelModule.onDecline(cid, message, keywords, parameters, node)
local module = parameters.module
if not module.npcHandler:isFocused(cid) or shop_npcuid[cid] ~= getNpcCid() then
return false
end
local parentParameters = node:getParent():getParameters()
local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_DECLINE), parseInfo)
module.npcHandler:say(msg, cid)
module.npcHandler:resetNpc(cid)
return true
end
function TravelModule.bringMeTo(cid, message, keywords, parameters, node)
local module = parameters.module
if not module.npcHandler:isFocused(cid) then
return false
end
local cost = parameters.cost
local destination = parameters.destination
local premium = parameters.premium
if(not isPlayerPremiumCallback or isPlayerPremiumCallback(cid) or parameters.premium ~= true) then
if(doPlayerRemoveMoney(cid, cost) == TRUE) then
doTeleportThing(cid, destination, 0)
doSendMagicEffect(destination, CONST_ME_TELEPORT)
end
end
return true
end
function TravelModule.listDestinations(cid, message, keywords, parameters, node)
local module = parameters.module
if not module.npcHandler:isFocused(cid) then
return false
end
local msg = "I can bring you to "
--local i = 1
local maxn = #module.destinations
for i, destination in pairs(module.destinations) do
msg = msg .. destination
if i == maxn - 1 then
msg = msg .. " and "
elseif i == maxn then
msg = msg .. "."
else
msg = msg .. ", "
end
i = i + 1
end
module.npcHandler:say(msg, cid)
module.npcHandler:resetNpc(cid)
return true
end
-- Add it to the parseable module list.
Modules.parseableModules["module_shop"] = ShopModule
-- Creates a new instance of ShopModule
function ShopModule:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
-- Parses all known parameters.
function ShopModule:parseParameters()
local ret = NpcSystem.getParameter("shop_buyable")
if ret ~= nil then
self:parseBuyable(ret)
end
local ret = NpcSystem.getParameter("shop_sellable")
if ret ~= nil then
self:parseSellable(ret)
end
local ret = NpcSystem.getParameter("shop_buyable_containers")
if ret ~= nil then
self:parseBuyableContainers(ret)
end
end
-- Parse a string contaning a set of buyable items.
function ShopModule:parseBuyable(data)
for item in string.gmatch(data, "[^;]+") do
local i = 1
local name = nil
local itemid = nil
local cost = nil
local subType = nil
local realName = nil
for temp in string.gmatch(item, "[^,]+") do
if i == 1 then
name = temp
elseif i == 2 then
itemid = tonumber(temp)
elseif i == 3 then
cost = tonumber(temp)
elseif i == 4 then
subType = tonumber(temp)
elseif i == 5 then
realName = temp
else
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Unknown parameter found in buyable items parameter.", temp, item)
end
i = i + 1
end
if SHOPMODULE_MODE == SHOPMODULE_MODE_TRADE then
if itemid ~= nil and cost ~= nil then
if subType == nil and ItemType(itemid):isFluidContainer() then
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item)
else
self:addBuyableItem(nil, itemid, cost, subType, realName)
end
else
print("[Warning] NpcSystem:", "Parameter(s) missing for item:", itemid, cost)
end
else
if name ~= nil and itemid ~= nil and cost ~= nil then
if subType == nil and ItemType(itemid):isFluidContainer() then
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item)
else
local names = {}
names[#names + 1] = name
self:addBuyableItem(names, itemid, cost, subType, realName)
end
else
print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, itemid, cost)
end
end
end
end
-- Parse a string contaning a set of sellable items.
function ShopModule:parseSellable(data)
for item in string.gmatch(data, "[^;]+") do
local i = 1
local name = nil
local itemid = nil
local cost = nil
local realName = nil
local subType = nil
for temp in string.gmatch(item, "[^,]+") do
if i == 1 then
name = temp
elseif i == 2 then
itemid = tonumber(temp)
elseif i == 3 then
cost = tonumber(temp)
elseif i == 4 then
realName = temp
elseif i == 5 then
subType = tonumber(temp)
else
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Unknown parameter found in sellable items parameter.", temp, item)
end
i = i + 1
end
if SHOPMODULE_MODE == SHOPMODULE_MODE_TRADE then
if itemid ~= nil and cost ~= nil then
self:addSellableItem(nil, itemid, cost, realName, subType)
else
print("[Warning] NpcSystem:", "Parameter(s) missing for item:", itemid, cost)
end
else
if name ~= nil and itemid ~= nil and cost ~= nil then
local names = {}
names[#names + 1] = name
self:addSellableItem(names, itemid, cost, realName, subType)
else
print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, itemid, cost)
end
end
end
end
-- Parse a string contaning a set of buyable items.
function ShopModule:parseBuyableContainers(data)
for item in string.gmatch(data, "[^;]+") do
local i = 1
local name = nil
local container = nil
local itemid = nil
local cost = nil
local subType = nil
local realName = nil
for temp in string.gmatch(item, "[^,]+") do
if i == 1 then
name = temp
elseif i == 2 then
itemid = tonumber(temp)
elseif i == 3 then
itemid = tonumber(temp)
elseif i == 4 then
cost = tonumber(temp)
elseif i == 5 then
subType = tonumber(temp)
elseif i == 6 then
realName = temp
else
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Unknown parameter found in buyable items parameter.", temp, item)
end
i = i + 1
end
if name ~= nil and container ~= nil and itemid ~= nil and cost ~= nil then
if subType == nil and ItemType(itemid):isFluidContainer() then
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item)
else
local names = {}
names[#names + 1] = name
self:addBuyableItemContainer(names, container, itemid, cost, subType, realName)
end
else
print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, container, itemid, cost)
end
end
end
-- Initializes the module and associates handler to it.
function ShopModule:init(handler)
self.npcHandler = handler
self.yesNode = KeywordNode:new(SHOP_YESWORD, ShopModule.onConfirm, {module = self})
self.noNode = KeywordNode:new(SHOP_NOWORD, ShopModule.onDecline, {module = self})
self.noText = handler:getMessage(MESSAGE_DECLINE)
if SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK then
for i, word in pairs(SHOP_TRADEREQUEST) do
local obj = {}
obj[#obj + 1] = word
obj.callback = SHOP_TRADEREQUEST.callback or ShopModule.messageMatcher
handler.keywordHandler:addKeyword(obj, ShopModule.requestTrade, {module = self})
end
end
return true
end
-- Custom message matching callback function for requesting trade messages.
function ShopModule.messageMatcher(keywords, message)
for i, word in pairs(keywords) do
if type(word) == "string" then
if string.find(message, word) and not string.find(message, "[%w+]" .. word) and not string.find(message, word .. "[%w+]") then
return true
end
end
end
return false
end
-- Resets the module-specific variables.
function ShopModule:reset()
self.amount = 0
end
-- Function used to match a number value from a string.
function ShopModule:getCount(message)
local ret = 1
local b, e = string.find(message, PATTERN_COUNT)
if b ~= nil and e ~= nil then
ret = tonumber(string.sub(message, b, e))
end
if ret <= 0 then
ret = 1
elseif ret > self.maxCount then
ret = self.maxCount
end
return ret
end
-- Adds a new buyable item.
-- names = A table containing one or more strings of alternative names to this item. Used only for old buy/sell system.
-- itemid = The itemid of the buyable item
-- cost = The price of one single item
-- subType - The subType of each rune or fluidcontainer item. Can be left out if it is not a rune/fluidcontainer. Default value is 1.
-- realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used)
function ShopModule:addBuyableItem(names, itemid, cost, itemSubType, realName)
if SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK then
if itemSubType == nil then
itemSubType = 1
end
local shopItem = self:getShopItem(itemid, itemSubType)
if shopItem == nil then
self.npcHandler.shopItems[#self.npcHandler.shopItems + 1] = {id = itemid, buy = cost, sell = -1, subType = itemSubType, name = realName or ItemType(itemid):getName()}
else
shopItem.buy = cost
end
end
if names ~= nil and SHOPMODULE_MODE ~= SHOPMODULE_MODE_TRADE then
for i, name in pairs(names) do
local parameters = {
itemid = itemid,
cost = cost,
eventType = SHOPMODULE_BUY_ITEM,
module = self,
realName = realName or ItemType(itemid):getName(),
subType = itemSubType or 1
}
keywords = {}
keywords[#keywords + 1] = "buy"
keywords[#keywords + 1] = name
local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters)
node:addChildKeywordNode(self.yesNode)
node:addChildKeywordNode(self.noNode)
end
end
if npcs_loaded_shop[getNpcCid()] == nil then
npcs_loaded_shop[getNpcCid()] = getNpcCid()
self.npcHandler.keywordHandler:addKeyword({'yes'}, ShopModule.onConfirm, {module = self})
self.npcHandler.keywordHandler:addKeyword({'no'}, ShopModule.onDecline, {module = self})
end
end
function ShopModule:getShopItem(itemId, itemSubType)
if ItemType(itemId):isFluidContainer() then
for i = 1, #self.npcHandler.shopItems do
local shopItem = self.npcHandler.shopItems
if shopItem.id == itemId and shopItem.subType == itemSubType then
return shopItem
end
end
else
for i = 1, #self.npcHandler.shopItems do
local shopItem = self.npcHandler.shopItems
if shopItem.id == itemId then
return shopItem
end
end
end
return nil
end
-- Adds a new buyable container of items.
-- names = A table containing one or more strings of alternative names to this item.
-- container = Backpack, bag or any other itemid of container where bought items will be stored
-- itemid = The itemid of the buyable item
-- cost = The price of one single item
-- subType - The subType of each rune or fluidcontainer item. Can be left out if it is not a rune/fluidcontainer. Default value is 1.
-- realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used)
function ShopModule:addBuyableItemContainer(names, container, itemid, cost, subType, realName)
if names ~= nil then
for i, name in pairs(names) do
local parameters = {
container = container,
itemid = itemid,
cost = cost,
eventType = SHOPMODULE_BUY_ITEM_CONTAINER,
module = self,
realName = realName or ItemType(itemid):getName(),
subType = subType or 1
}
keywords = {}
keywords[#keywords + 1] = "buy"
keywords[#keywords + 1] = name
local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters)
node:addChildKeywordNode(self.yesNode)
node:addChildKeywordNode(self.noNode)
end
end
end
-- Adds a new sellable item.
-- names = A table containing one or more strings of alternative names to this item. Used only by old buy/sell system.
-- itemid = The itemid of the sellable item
-- cost = The price of one single item
-- realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used)
function ShopModule:addSellableItem(names, itemid, cost, realName, itemSubType)
if SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK then
if itemSubType == nil then
itemSubType = 0
end
local shopItem = self:getShopItem(itemid, itemSubType)
if shopItem == nil then
table.insert(self.npcHandler.shopItems, {id = itemid, buy = -1, sell = cost, subType = itemSubType, name = realName or getItemName(itemid)})
else
shopItem.sell = cost
end
end
if(names ~= nil and SHOPMODULE_MODE ~= SHOPMODULE_MODE_TRADE) then
for i, name in pairs(names) do
local parameters = {
itemid = itemid,
cost = cost,
eventType = SHOPMODULE_SELL_ITEM,
module = self,
realName = realName or getItemName(itemid)
}
keywords = {}
table.insert(keywords, "sell")
table.insert(keywords, name)
local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters)
node:addChildKeywordNode(self.yesNode)
node:addChildKeywordNode(self.noNode)
end
end
end
-- onModuleReset callback function. Calls ShopModule:reset()
function ShopModule:callbackOnModuleReset()
self:reset()
return true
end
-- Callback onBuy() function. If you wish, you can change certain Npc to use your onBuy().
function ShopModule:callbackOnBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks)
local shopItem = self:getShopItem(itemid, subType)
if shopItem == nil then
error("[ShopModule.onBuy] shopItem == nil")
return false
end
if shopItem.buy == -1 then
error("[ShopModule.onSell] attempt to buy a non-buyable item")
return false
end
local backpack = 1988
local totalCost = amount * shopItem.buy
if(inBackpacks) then
totalCost = isItemStackable(itemid) == TRUE and totalCost + 20 or totalCost + (math.max(1, math.floor(amount / getContainerCapById(backpack))) * 20)
end
if(getPlayerMoney(cid) < totalCost) then
local msg = self.npcHandler:getMessage(MESSAGE_NEEDMONEY)
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendCancel(cid, msg)
return false
end
local subType = shopItem.subType or 1
local a, b = doNpcSellItem(cid, itemid, amount, subType, ignoreCap, inBackpacks, backpack)
if(a < amount) then
local msgId = MESSAGE_NEEDMORESPACE
if(a == 0) then
msgId = MESSAGE_NEEDSPACE
end
local msg = self.npcHandler:getMessage(msgId)
parseInfo[TAG_ITEMCOUNT] = a
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendCancel(cid, msg)
self.npcHandler.talkStart[cid] = os.time()
if(a > 0) then
doPlayerRemoveMoney(cid, ((a * shopItem.buy) + (b * 20)))
return true
end
return false
else
local msg = self.npcHandler:getMessage(MESSAGE_BOUGHT)
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, msg)
doPlayerRemoveMoney(cid, totalCost)
self.npcHandler.talkStart[cid] = os.time()
return true
end
end
-- Callback onSell() function. If you wish, you can change certain Npc to use your onSell().
function ShopModule:callbackOnSell(cid, itemid, subType, amount, ignoreEquipped, _)
local shopItem = self:getShopItem(itemid, subType)
if shopItem == nil then
error("[ShopModule.onSell] items[itemid] == nil")
return false
end
if shopItem.sell == -1 then
error("[ShopModule.onSell] attempt to sell a non-sellable item")
return false
end
if not isItemFluidContainer(itemid) then
subType = -1
end
if doPlayerRemoveItem(cid, itemid, amount, subType, ignoreEquipped) then
local msg = self.npcHandler:getMessage(MESSAGE_SOLD)
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, msg)
doPlayerAddMoney(cid, amount * shopItem.sell)
self.npcHandler.talkStart[cid] = os.time()
return true
else
local msg = self.npcHandler:getMessage(MESSAGE_NEEDITEM)
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendCancel(cid, msg)
self.npcHandler.talkStart[cid] = os.time()
return false
end
end
-- Callback for requesting a trade window with the NPC.
function ShopModule.requestTrade(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) then
return false
end
if(not module.npcHandler:onTradeRequest(cid)) then
return false
end
local itemWindow = {}
for i = 1, #module.npcHandler.shopItems do
table.insert(itemWindow, module.npcHandler.shopItems)
end
if(itemWindow[1] == nil) then
local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) }
local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_NOSHOP), parseInfo)
module.npcHandler:say(msg, cid)
return true
end
-- onConfirm keyword callback function. Sells/buys the actual item.
function ShopModule.onConfirm(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) or shop_npcuid[cid] ~= getNpcCid() then
return false
end
shop_npcuid[cid] = 0
if(shop_eventtype[cid] == SHOPMODULE_SELL_ITEM) then
local ret = doPlayerSellItem(cid, shop_itemid[cid], shop_amount[cid], shop_cost[cid] * shop_amount[cid])
if(ret == LUA_NO_ERROR) then
local msg = module.npcHandler:getMessage(MESSAGE_ONSELL)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
else
local msg = module.npcHandler:getMessage(MESSAGE_MISSINGITEM)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
end
elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM) then
local cost = shop_cost[cid] * shop_amount[cid]
if getPlayerMoney(cid) < cost then
local msg = module.npcHandler:getMessage(MESSAGE_MISSINGMONEY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
return false
end
local a, b = doNpcSellItem(cid, shop_itemid[cid], shop_amount[cid], shop_subtype[cid], false, false, 1988)
if(a < shop_amount[cid]) then
local msgId = MESSAGE_NEEDMORESPACE
if(a == 0) then
msgId = MESSAGE_NEEDSPACE
end
local msg = module.npcHandler:getMessage(msgId)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
if(a > 0) then
doPlayerRemoveMoney(cid, a * shop_cost[cid])
if shop_itemid[cid] == ITEM_PARCEL then
doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, 1988)
end
return true
end
return false
else
local msg = module.npcHandler:getMessage(MESSAGE_ONBUY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
doPlayerRemoveMoney(cid, cost)
if shop_itemid[cid] == ITEM_PARCEL then
doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, 1988)
end
return true
end
elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM_CONTAINER) then
local ret = doPlayerBuyItemContainer(cid, shop_container[cid], shop_itemid[cid], shop_amount[cid], shop_cost[cid] * shop_amount[cid], shop_subtype[cid])
if(ret == LUA_NO_ERROR) then
local msg = module.npcHandler:getMessage(MESSAGE_ONBUY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
else
local msg = module.npcHandler:getMessage(MESSAGE_MISSINGMONEY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
end
end
module.npcHandler:resetNpc(cid)
return true
end
-- onDecline keyword callback function. Generally called when the player sais "no" after wanting to buy an item.
function ShopModule.onDecline(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) or shop_npcuid[cid] ~= getNpcCid() then
return false
end
shop_npcuid[cid] = 0
local msg = module.npcHandler:parseMessage(module.noText, parseInfo)
module.npcHandler:say(msg, cid)
module.npcHandler:resetNpc(cid)
return true
end
-- tradeItem callback function. Makes the npc say the message defined by MESSAGE_BUY or MESSAGE_SELL
function ShopModule.tradeItem(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) then
return false
end
if(not module.npcHandler:onTradeRequest(cid)) then
return true
end
local count = module:getCount(message)
module.amount = count
if(shop_eventtype[cid] == SHOPMODULE_SELL_ITEM) then
local msg = module.npcHandler:getMessage(MESSAGE_SELL)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM) then
local msg = module.npcHandler:getMessage(MESSAGE_BUY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM_CONTAINER) then
local msg = module.npcHandler:getMessage(MESSAGE_BUY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
end
return true
end
end
Keyworldhandler:
Spoiler
-- Advanced NPC System by Jiddo
if KeywordHandler == nil then
KeywordNode = {
keywords = nil,
callback = nil,
parameters = nil,
children = nil,
parent = nil
}
-- Created a new keywordnode with the given keywords, callback function and parameters and without any childNodes.
function KeywordNode:new(keys, func, param)
local obj = {}
obj.keywords = keys
obj.callback = func
obj.parameters = param
obj.children = {}
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
-- Returns true if message contains all patterns/strings found in keywords.
function KeywordNode:checkMessage(message)
if self.keywords.callback ~= nil then
return self.keywords.callback(self.keywords, message)
end
for i,v in ipairs(self.keywords) do
if type(v) == 'string' then
local a, b = string.find(message, v)
if a == nil or b == nil then
return false
end
end
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)
local new = KeywordNode:new(keywords, callback, parameters)
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 = {}
}
-- Creates a new keywordhandler with an empty rootnode.
function KeywordHandler:new()
local obj = {}
obj.root = KeywordNode:new(nil, nil, nil)
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 = string.lower(message)
for i, childNode in pairs(node.children) do
if childNode:checkMessage(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
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)
return self:getRoot():addChildKeyword(keys, callback, parameters)
end
-- Moves the current position in the keyword hierarchy count steps upwards. Count defalut value = 1.
-- This function MIGHT not work properly yet. Use at your own risk.
function KeywordHandler:moveUp(count)
local steps = count
if steps == nil or type(steps) ~= "number" then
steps = 1
end
for i = 1, steps, 1 do
if self.lastNode[cid] == nil then
break
end
self.lastNode[cid] = self.lastNode[cid]:getParent() or self:getRoot()
end
return self.lastNode[cid]
end
end
Você tem alguma imagem que possa auxiliar no problema? Se sim, anexe-a dentro do spoiler abaixo:
Antes de fazer a sua pergunta, tenha certeza de ter lido as regras da seção e o guia abaixo:
https://forums.otserv.com.br/index.php?/forums/topic/168583-regras-da-seção/
https://forums.otserv.com.br/index.php?/forums/topic/165121-como-fazer-uma-pergunta-ou-o-grande-guia-do-usuário-com-dúvidas/
Descreva em algumas palavras a base utilizada. (Nome do servidor / Nome do cliente / Nome do website / etc.).
Base:
The Forgotten Server 1.3, Versão: 8.6
Qual é a sua pergunta?
Estou com um erro ao dar trade nos NPCs
Você tem o código disponível? Se tiver poste-o na caixa de código que está dentro do spoiler abaixo:
Rashid.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
local function getTable()
local list =
{
{name = 'Abyss Hammer', id = 7414, sell = 20000, buy = 0},
{name = 'Amber Staff', id = 7426, sell = 8000, buy = 0},
{name = 'Ancient Amulet', id = 2142, sell = 200, buy = 0},
{name = 'Assassin Dagger', id = 7404, sell = 20000, buy = 0},
{name = 'Bandana', id = 5917, sell = 150, buy = 0},
{name = 'Beastslayer axe', id = 3962, sell = 1500, buy = 0},
{name = 'Beetle Necklace', id = 11374, sell = 1500, buy = 0},
{name = 'Berserker', id = 7403, sell = 40000, buy = 0},
{name = 'Blacksteel Sword', id = 7406, sell = 6000, buy = 0},
{name = 'Blessed Sceptre', id = 7429, sell = 40000, buy = 0},
{name = 'Bone Shield', id = 2541, sell = 80, buy = 0},
{name = 'Bonelord Helmet', id = 3972, sell = 7500, buy = 0},
{name = 'Brutetamer\'s Staff', id = 7379, sell = 1500, buy = 0},
{name = 'Buckle', id = 20109, sell = 7000, buy = 0},
{name = 'Castle Shield', id = 2535, sell = 5000, buy = 0},
{name = 'Chain Bolter', id = 8850, sell = 40000, buy = 0},
{name = 'Chaos Mace', id = 7427, sell = 9000, buy = 0},
{name = 'Cobra Crown', id = 12630, sell = 50000, buy = 0},
{name = 'Coconut Shoes', id = 9931, sell = 500, buy = 0},
{name = 'Composite Hornbow', id = 8855, sell = 25000, buy = 0},
{name = 'Cranial Basher', id = 7415, sell = 30000, buy = 0},
{name = 'Crocodile Boots', id = 3982, sell = 1000, buy = 0},
{name = 'Crystal Crossbow', id = 18453, sell = 35000, buy = 0},
{name = 'Crystal Mace', id = 2445, sell = 12000, buy = 0},
{name = 'Crystal Necklace', id = 2125, sell = 400, buy = 0},
{name = 'Crystal Ring', id = 2124, sell = 250, buy = 0},
{name = 'Crystal Sword', id = 7449, sell = 600, buy = 0},
{name = 'Crystalline Armor', id = 8878, sell = 16000, buy = 0},
{name = 'Daramian Mace', id = 2439, sell = 110, buy = 0},
{name = 'Daramian Waraxe', id = 2440, sell = 1000, buy = 0},
{name = 'Dark Shield', id = 2521, sell = 400, buy = 0},
{name = 'Death Ring', id = 6300, sell = 1000, buy = 0},
{name = 'Demon Shield', id = 2520, sell = 30000, buy = 0},
{name = 'Demonbone Amulet', id = 2136, sell = 32000, buy = 0},
{name = 'Demonrage Sword', id = 7382, sell = 36000, buy = 0},
{name = 'Devil Helmet', id = 2462, sell = 1000, buy = 0},
{name = 'Diamond Sceptre', id = 7387, sell = 3000, buy = 0},
{name = 'Divine Plate', id = 8885, sell = 55000, buy = 0},
{name = 'Djinn Blade', id = 2451, sell = 15000, buy = 0},
{name = 'Doll', id = 2110, sell = 200, buy = 0},
{name = 'Dragon Scale Mail', id = 2492, sell = 40000, buy = 0},
{name = 'Dragon Slayer', id = 7402, sell = 15000, buy = 0},
{name = 'Dragonbone Staff', id = 7430, sell = 3000, buy = 0},
{name = 'Dreaded Cleaver', id = 7419, sell = 10000, buy = 0},
{name = 'Dwarven Armor', id = 2503, sell = 30000, buy = 0},
{name = 'Earth Cranial Basher', id = 7866, sell = 30000, buy = 0},
{name = 'Earth Dragon Slayer', id = 7858, sell = 15000, buy = 0},
{name = 'Earth Headchopper', id = 7862, sell = 6000, buy = 0},
{name = 'Earth Heroic axe', id = 7861, sell = 30000, buy = 0},
{name = 'Earth Mystic Blade', id = 7856, sell = 30000, buy = 0},
{name = 'Earth Orcish Maul', id = 7867, sell = 6000, buy = 0},
{name = 'Earth Relic Sword', id = 7855, sell = 25000, buy = 0},
{name = 'Earth War axe', id = 7863, sell = 12000, buy = 0},
{name = 'Elvish Bow', id = 7438, sell = 2000, buy = 0},
{name = 'Emerald Bangle', id = 2127, sell = 800, buy = 0},
{name = 'Energy Cranial Basher',id = 7881, sell = 30000, buy = 0},
{name = 'Energy Dragon Slayer', id = 7873, sell = 15000, buy = 0},
{name = 'Energy Headchopper', id = 7877, sell = 6000, buy = 0},
{name = 'Energy Heroic axe', id = 7876, sell = 30000, buy = 0},
{name = 'Energy Mystic Blade', id = 7871, sell = 30000, buy = 0},
{name = 'Energy Orcish Maul', id = 7882, sell = 6000, buy = 0},
{name = 'Energy Relic Sword', id = 7870, sell = 25000, buy = 0},
{name = 'Energy War axe', id = 7878, sell = 12000, buy = 0},
{name = 'Epee', id = 2438, sell = 8000, buy = 0},
{name = 'Fiery Cranial Basher', id = 7756, sell = 30000, buy = 0},
{name = 'Fiery Dragon Slayer', id = 7748, sell = 15000, buy = 0},
{name = 'Fiery Headchopper', id = 7752, sell = 6000, buy = 0},
{name = 'Fiery Heroic axe', id = 7751, sell = 30000, buy = 0},
{name = 'Fiery Mystic Blade', id = 7746, sell = 30000, buy = 0},
{name = 'Fiery Orcish Maul', id = 7757, sell = 6000, buy = 0},
{name = 'Fiery Relic Sword', id = 7745, sell = 25000, buy = 0},
{name = 'Fiery War axe', id = 7753, sell = 12000, buy = 0},
{name = 'Flower Dress', id = 9929, sell = 1000, buy = 0},
{name = 'Flower Wreath', id = 9927, sell = 500, buy = 0},
{name = 'Fur Boots', id = 7457, sell = 2000, buy = 0},
{name = 'Furry Club', id = 7432, sell = 1000, buy = 0},
{name = 'Glacier Amulet', id = 7888, sell = 1500, buy = 0},
{name = 'Glacier Kilt', id = 7896, sell = 11000, buy = 0},
{name = 'Glacier Mask', id = 7902, sell = 2500, buy = 0},
{name = 'Glacier Robe', id = 7897, sell = 11000, buy = 0},
{name = 'Glacier Shoes', id = 7892, sell = 2500, buy = 0},
{name = 'Gold Ring', id = 2179, sell = 8000, buy = 0},
{name = 'Golden Armor', id = 2466, sell = 20000, buy = 0},
{name = 'Golden Legs', id = 2470, sell = 30000, buy = 0},
{name = 'Guardian Halberd', id = 2427, sell = 11000, buy = 0},
{name = 'Hammer of Wrath', id = 2444, sell = 30000, buy = 0},
{name = 'Headchopper', id = 7380, sell = 6000, buy = 0},
{name = 'Heavy Mace', id = 2452, sell = 50000, buy = 0},
{name = 'Heavy Machete', id = 2442, sell = 90, buy = 0},
{name = 'Heavy Trident', id = 13838, sell = 2000, buy = 0},
{name = 'Helmet of the Lost', id = 20132, sell = 2000, buy = 0},
{name = 'Heroic axe', id = 7389, sell = 30000, buy = 0},
{name = 'Hibiscus Dress', id = 8873, sell = 3000, buy = 0},
{name = 'Icy Blacksteel Sword', id = 7766, sell = 6000, buy = 0},
{name = 'Icy Cranial Basher', id = 7775, sell = 30000, buy = 0},
{name = 'Icy Dragon Slayer', id = 7767, sell = 15000, buy = 0},
{name = 'Icy Headchopper', id = 7771, sell = 6000, buy = 0},
{name = 'Icy Heroic axe', id = 7770, sell = 30000, buy = 0},
{name = 'Icy Mystic Blade', id = 7765, sell = 30000, buy = 0},
{name = 'Icy Orcish Maul', id = 7776, sell = 6000, buy = 0},
{name = 'Icy Relic Sword', id = 7764, sell = 25000, buy = 0},
{name = 'Icy War axe', id = 7772, sell = 12000, buy = 0},
{name = 'Jade Hammer', id = 7422, sell = 25000, buy = 0},
{name = 'Krimhorn Helmet', id = 7461, sell = 200, buy = 0},
{name = 'Lavos Armor', id = 8877, sell = 16000, buy = 0},
{name = 'Leaf Legs', id = 9928, sell = 500, buy = 0},
{name = 'Leopard Armor', id = 3968, sell = 1000, buy = 0},
{name = 'Leviathan\'s Amulet', id = 10220, sell = 3000, buy = 0},
{name = 'Light Shovel', id = 5710, sell = 300, buy = 0},
{name = 'Lightning Boots', id = 7893, sell = 2500, buy = 0},
{name = 'Lightning Headband', id = 7901, sell = 2500, buy = 0},
{name = 'Lightning Legs', id = 7895, sell = 11000, buy = 0},
{name = 'Lightning Pendant', id = 7889, sell = 1500, buy = 0},
{name = 'Lightning Robe', id = 7898, sell = 11000, buy = 0},
{name = 'Lunar Staff', id = 7424, sell = 5000, buy = 0},
{name = 'Magic Plate Armor', id = 2472, sell = 90000, buy = 0},
{name = 'Magma Amulet', id = 7890, sell = 1500, buy = 0},
{name = 'Magma Boots', id = 7891, sell = 2500, buy = 0},
{name = 'Magma Coat', id = 7899, sell = 11000, buy = 0},
{name = 'Magma Legs', id = 7894, sell = 11000, buy = 0},
{name = 'Magma Monocle', id = 7900, sell = 2500, buy = 0},
{name = 'Mammoth Fur Cape', id = 7463, sell = 6000, buy = 0},
{name = 'Mammoth Fur Shorts', id = 7464, sell = 850, buy = 0},
{name = 'Mammoth Whopper', id = 7381, sell = 300, buy = 0},
{name = 'Mastermind Shield', id = 2514, sell = 50000, buy = 0},
{name = 'Medusa Shield', id = 2536, sell = 9000, buy = 0},
{name = 'Mercenary Sword', id = 7386, sell = 12000, buy = 0},
{name = 'Mycological Bow', id = 18454, sell = 35000, buy = 0},
{name = 'Mystic Blade', id = 7384, sell = 30000, buy = 0},
{name = 'Naginata', id = 2426, sell = 2000, buy = 0},
{name = 'Nightmare Blade', id = 7418, sell = 35000, buy = 0},
{name = 'Noble axe', id = 7456, sell = 10000, buy = 0},
{name = 'Norse Shield', id = 7460, sell = 1500, buy = 0},
{name = 'Orcish Maul', id = 7392, sell = 6000, buy = 0},
{name = 'Pair of Iron Fists', id = 20108, sell = 4000, buy = 0},
{name = 'Paladin Armor', id = 8891, sell = 15000, buy = 0},
{name = 'Patched Boots', id = 2641, sell = 2000, buy = 0},
{name = 'Pharaoh Sword', id = 2446, sell = 23000, buy = 0},
{name = 'Pirate Boots', id = 5462, sell = 3000, buy = 0},
{name = 'Pirate Hat', id = 6096, sell = 1000, buy = 0},
{name = 'Pirate Knee Breeches', id = 5918, sell = 200, buy = 0},
{name = 'Pirate Shirt', id = 6095, sell = 500, buy = 0},
{name = 'Pirate Voodoo Doll', id = 5810, sell = 500, buy = 0},
{name = 'Platinum Amulet', id = 2171, sell = 2500, buy = 0},
{name = 'Ragnir Helmet', id = 7462, sell = 400, buy = 0},
{name = 'Relic Sword', id = 7383, sell = 25000, buy = 0},
{name = 'Ring of the Sky', id = 2123, sell = 30000, buy = 0},
{name = 'Royal axe', id = 7434, sell = 40000, buy = 0},
{name = 'Ruby Necklace', id = 2133, sell = 2000, buy = 0},
{name = 'Ruthless axe', id = 6553, sell = 45000, buy = 0},
{name = 'Sacred Tree Amulet', id = 10219, sell = 3000, buy = 0},
{name = 'Sapphire Hammer', id = 7437, sell = 7000, buy = 0},
{name = 'Scarab Amulet', id = 2135, sell = 200, buy = 0},
{name = 'Scarab Shield', id = 2540, sell = 2000, buy = 0},
{name = 'Shockwave Amulet', id = 10221, sell = 3000, buy = 0},
{name = 'Silver Brooch', id = 2134, sell = 150, buy = 0},
{name = 'Silver Dagger', id = 2402, sell = 500, buy = 0},
{name = 'Skull Helmet', id = 5741, sell = 40000, buy = 0},
{name = 'Skullcracker Armor', id = 8889, sell = 18000, buy = 0},
{name = 'Spiked Squelcher', id = 7452, sell = 5000, buy = 0},
{name = 'Steel Boots', id = 2645, sell = 30000, buy = 0},
{name = 'Swamplair Armor', id = 8880, sell = 16000, buy = 0},
{name = 'Taurus Mace', id = 7425, sell = 500, buy = 0},
{name = 'Tempest Shield', id = 2542, sell = 35000, buy = 0},
{name = 'Terra Amulet', id = 7887, sell = 1500, buy = 0},
{name = 'Terra Boots', id = 7886, sell = 2500, buy = 0},
{name = 'Terra Hood', id = 7903, sell = 2500, buy = 0},
{name = 'Terra Legs', id = 7885, sell = 11000, buy = 0},
{name = 'Terra Mantle', id = 7884, sell = 11000, buy = 0},
{name = 'The Justice Seeker', id = 7390, sell = 40000, buy = 0},
{name = 'Tortoise Shield', id = 6131, sell = 150, buy = 0},
{name = 'Vile axe', id = 7388, sell = 30000, buy = 0},
{name = 'Voodoo Doll', id = 2322, sell = 400, buy = 0},
{name = 'War axe', id = 2454, sell = 12000, buy = 0},
{name = 'War Horn', id = 2079, sell = 8000, buy = 0},
{name = 'Witch Hat', id = 10570, sell = 5000, buy = 0},
{name = 'Wyvern Fang', id = 7408, sell = 1500, buy = 0}
}
return list
end
local function creatureSayCallback(cid, type, msg)
if(not npcHandler:isFocused(cid)) then
return false
end
local player = Player(cid)
if(msgcontains(msg, "mission")) then
if(os.date("%A") == "Monday") then
if(player:getStorageValue(Rashid.MissionStart) < 1) then
npcHandler:say("Well, you could attempt the mission to become a recognised trader, but it requires a lot of travelling. Are you willing to try?", cid)
npcHandler.topic[cid] = 1
elseif(player:getStorageValue(Rashid.MissionStart) == 1) then
npcHandler:say("Have you managed to obtain a rare deer trophy for my customer?", cid)
npcHandler.topic[cid] = 3
end
elseif(os.date("%A") == "Tuesday") then
if(player:getStorageValue(Rashid.MissionStart) == 2 and player:getStorageValue(Rashid.MissionStart+1) < 1 ) then
npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
npcHandler.topic[cid] = 4
elseif(player:getStorageValue(Rashid.MissionStart + 1) == 4) then
npcHandler:say("Did you bring me the package?", cid)
npcHandler.topic[cid] = 6
end
elseif(os.date("%A") == "Wednesday") then
if(player:getStorageValue(Rashid.MissionStart + 1) == 5 and player:getStorageValue(Rashid.MissionStart + 2) < 1 ) then
npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
npcHandler.topic[cid] = 7
elseif(player:getStorageValue(Rashid.MissionStart + 2) == 2) then
npcHandler:say("Have you brought the cheese?", cid)
npcHandler.topic[cid] = 9
end
elseif(os.date("%A") == "Thursday") then
if(player:getStorageValue(Rashid.MissionStart + 2) == 3 and player:getStorageValue(Rashid.MissionStart + 3) < 1) then
npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
npcHandler.topic[cid] = 10
elseif(player:getStorageValue(Rashid.MissionStart + 3) == 2) then
npcHandler:say("Have you brought the vase?", cid)
npcHandler.topic[cid] = 12
end
elseif(os.date("%A") == "Friday") then
if(player:getStorageValue(Rashid.MissionStart + 3) == 3 and player:getStorageValue(Rashid.MissionStart + 4) < 1) then
npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
npcHandler.topic[cid] = 13
elseif(player:getStorageValue(Rashid.MissionStart + 4) == 2) then
npcHandler:say("Have you brought a cheap but good crimson sword?", cid)
npcHandler.topic[cid] = 15
end
elseif(os.date("%A") == "Saturday") then
if(player:getStorageValue(Rashid.MissionStart + 4) == 3 and player:getStorageValue(Rashid.MissionStart + 5) < 1) then
npcHandler:say("So, my friend, are you willing to proceed to the next mission to become a recognised trader?", cid)
npcHandler.topic[cid] = 16
elseif(player:getStorageValue(Rashid.MissionStart + 5) == 1) then
npcHandler:say("Have you brought me a gold fish??", cid)
npcHandler.topic[cid] = 18
end
elseif(os.date("%A") == "Sunday") then
if(player:getStorageValue(Rashid.MissionStart + 5) == 2 and player:getStorageValue(Rashid.MissionStart + 6) < 1) then
npcHandler:say("Ah, right. <ahem> I hereby declare you - one of my recognised traders! Feel free to offer me your wares!", cid)
player:setStorageValue(Rashid.MissionStart + 6, 1)
--player:AddArchie(id or name)
npcHandler.topic[cid] = 0
end
end
elseif(msgcontains(msg, "yes")) then
if(npcHandler.topic[cid] == 1) then
npcHandler:say(
{
"Very good! I need talented people who are able to handle my wares with care, find good offers and the like, so I'm going to test you. ...",
"First, I'd like to see if you can dig up rare wares. Something like a ... mastermind shield! ...",
"Haha, just kidding, fooled you there, didn't I? Always control your nerves, that's quite important during bargaining. ...",
"Okay, all I want from you is one of these rare deer trophies. I have a customer here in Svargrond who ordered one, so I'd like you to deliver it tome while I'm in Svargrond. ...",
"Everything clear and understood?"
}, cid)
npcHandler.topic[cid] = 2
elseif(npcHandler.topic[cid] == 2) then
npcHandler:say("Fine. Then get a hold of that deer trophy and bring it to me while I'm in Svargrond. Just ask me about your mission.", cid)
player:setStorageValue(Rashid.MissionStart, 1)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 3) then
if(player:getItemCount(7397) >= 1) then
player:removeItem(7397, 1)
npcHandler:say("Well done! I'll take that from you. <snags it> Come see me another day, I'll be busy for a while now. ", cid)
player:setStorageValue(Rashid.MissionStart, 2)
npcHandler.topic[cid] = 0
end
elseif(npcHandler.topic[cid] == 4) then
npcHandler:say(
{
"Alright, that's good to hear. From you as my trader and deliveryman, I expect more than finding rare items. ...",
"You also need to be able to transport heavy wares, weaklings won't get far here. I have ordered a special package from Edron. ...",
"Pick it up from Willard and bring it back to me while I'm in Liberty Bay. Everything clear and understood?"
}, cid)
npcHandler.topic[cid] = 5
elseif(npcHandler.topic[cid] == 5) then
npcHandler:say("Fine. Then off you go, just ask Willard about the 'package for Rashid'.", cid)
player:setStorageValue(Rashid.MissionStart + 1, 1)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 6) then
if(player:getItemCount(7503) >= 1) then
player:removeItem(7503, 1)
npcHandler:say("Great. Just place it over there - yes, thanks, that's it. Come see me another day, I'll be busy for a while now. ", cid)
player:setStorageValue(Rashid.MissionStart + 1, 5)
npcHandler.topic[cid] = 0
end
elseif(npcHandler.topic[cid] == 7) then
npcHandler:say(
{
"Well, that's good to hear. From you as my trader and deliveryman, I expect more than carrying heavy packages. ...",
"You also need to be fast and deliver wares in time. I have ordered a very special cheese wheel made from Darashian milk. ...",
"Unfortunately, the high temperature in the desert makes it rot really fast, so it must not stay in the sun for too long. ...",
"I'm also afraid that you might not be able to use ships because of the smell of the cheese. ...",
"Please get the cheese from Miraia and bring it to me while I'm in Port Hope. Everything clear and understood?"
}, cid)
npcHandler.topic[cid] = 8
elseif(npcHandler.topic[cid] == 8) then
npcHandler:say("Okay, then please find Miraia in Darashia and ask her about the {'scarab cheese'}.", cid)
player:setStorageValue(Rashid.MissionStart + 2, 1)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 9) then
if(player:getItemCount(8112) >= 1) then
player:removeItem(8112, 1)
--if (player:getStorageValue(ExplorerSocietySTORAGE) == 1) then
--player:AddArchie(Just in Time)
--end
npcHandler:say("Mmmhh, the lovely odeur of scarab cheese! I really can't understand why most people can't stand it. Thanks, well done! ", cid)
player:setStorageValue(Rashid.MissionStart + 2, 3)
npcHandler.topic[cid] = 0
end
elseif(npcHandler.topic[cid] == 10) then
npcHandler:say(
{
"Well, that's good to hear. From you as my trader and deliveryman, I expect more than bringing stinky cheese. ...",
"I wonder if you are able to deliver goods so fragile they almost break when looked at. ...",
"I have ordered a special elven vase from Briasol in Ab'Dendriel. Get it from him and don't even touch it, just bring it to me while I'm in Ankrahmun. Everything clear and understood?"
}, cid)
npcHandler.topic[cid] = 11
elseif(npcHandler.topic[cid] == 11) then
npcHandler:say("Okay, then please find {Briasol} in {Ab'Dendriel} and ask for a {'fine vase'}.", cid)
player:setStorageValue(Rashid.MissionStart + 3, 1)
player:addMoney(1000)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 12) then
if(player:getItemCount(8760) >= 1) then
player:removeItem(8760, 1)
npcHandler:say("I'm surprised that you managed to bring this vase without a single crack. That was what I needed to know, thank you. ", cid)
player:setStorageValue(Rashid.MissionStart + 3, 3)
npcHandler.topic[cid] = 0
end
elseif(npcHandler.topic[cid] == 13) then
npcHandler:say(
{
"Fine! There's one more skill that I need to test and which is cruicial for a successful trader. ...",
"Of course you must be able to haggle, else you won't survive long in this business. To make things as hard as possible for you, I have the perfect trade partner for you. ...",
"Dwarves are said to be the most stubborn of all traders. Travel to {Kazordoon} and try to get the smith {Uzgod} to sell a {crimson sword} to you. ...",
"Of course, it has to be cheap. Don't come back with anything more expensive than 400 gold. ...",
"And the quality must not suffer, of course! Everything clear and understood?",
"Dwarves are said to be the most stubborn of all traders. Travel to Kazordoon and try to get the smith Uzgod to sell a crimson sword to you. ..."
}, cid)
npcHandler.topic[cid] = 14
elseif(npcHandler.topic[cid] == 14) then
npcHandler:say("Okay, I'm curious how you will do with {Uzgod}. Good luck!", cid)
player:setStorageValue(Rashid.MissionStart + 4, 1)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 15) then
if(player:getItemCount(7385) >= 1) then
player:removeItem(7385, 1)
npcHandler:say("Ha! You are clever indeed, well done! I'll take this from you. Come see me tomorrow, I think we two might get into business after all.", cid)
player:setStorageValue(Rashid.MissionStart + 4, 3)
npcHandler.topic[cid] = 0
end
elseif(npcHandler.topic[cid] == 16) then
npcHandler:say(
{
"My friend, it seems you have already learnt a lot about the art of trading. I think you are more than worthy to become a recognised trader. ...",
"There is just one little favour that I would ask from you... something personal, actually, forgive my boldness. ...",
"I have always dreamed to have a small pet, one that I could take with me and which wouldn't cause problems. ...",
"Could you - just maybe - bring me a small goldfish in a bowl? I know that you would be able to get one, wouldn't you?"
}, cid)
npcHandler.topic[cid] = 17
elseif(npcHandler.topic[cid] == 17) then
npcHandler:say("Thanks so much! I'll be waiting eagerly for your return then.", cid)
player:setStorageValue(Rashid.MissionStart + 5, 1)
npcHandler.topic[cid] = 0
elseif(npcHandler.topic[cid] == 18) then
if(player:getItemCount(8766) >= 1) then
player:removeItem(8766, 1)
npcHandler:say("Thank you!! Ah, this makes my day! I'll take the rest of the day off to get to know this little guy. Come see me tomorrow, if you like.", cid)
player:setStorageValue(Rashid.MissionStart + 5, 2)
npcHandler.topic[cid] = 0
end
end
end
return true
end
local function onTradeRequest(cid)
TradeRequest(cid, npcHandler, getTable(), Rashid, 1)
end
npcHandler:setCallback(CALLBACK_ONTRADEREQUEST, onTradeRequest)
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())
npcHancler:
-- Advanced NPC System by Jiddo
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 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_NEEDMONEY = 10 -- Same as above, used for shop window.
MESSAGE_MISSINGITEM = 11 -- When the player is trying to sell an item he does not have.
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_WALKAWAY_MALE = 22 -- When a male player walks out of the talkRadius of the npc.
MESSAGE_WALKAWAY_FEMALE = 23 -- When a female player walks out of the talkRadius of the npc.
-- 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
CALLBACK_ONADDFOCUS = 18
CALLBACK_ONRELEASEFOCUS = 19
CALLBACK_ONTRADEREQUEST = 20
-- 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|"
NpcHandler = {
keywordHandler = nil,
focuses = nil,
talkStart = nil,
idleTime = 120,
talkRadius = 3,
talkDelayTime = 1, -- Seconds to delay outgoing messages.
talkDelay = nil,
callbackFunctions = nil,
modules = nil,
shopItems = nil, -- They must be here since ShopModule uses 'static' functions
eventSay = nil,
eventDelayedSay = nil,
topic = nil,
messages = {
-- These are the default replies of all npcs. They can/should be changed individually for each npc.
[MESSAGE_GREET] = "Greetings, |PLAYERNAME|.",
[MESSAGE_FAREWELL] = "Good bye, |PLAYERNAME|.",
[MESSAGE_BUY] = "Do you want to buy |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?",
[MESSAGE_ONBUY] = "Here you are.",
[MESSAGE_BOUGHT] = "Bought |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.",
[MESSAGE_SELL] = "Do you want to sell |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?",
[MESSAGE_ONSELL] = "Here you are, |TOTALCOST| gold.",
[MESSAGE_SOLD] = "Sold |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.",
[MESSAGE_MISSINGMONEY] = "You don't have enough money.",
[MESSAGE_NEEDMONEY] = "You don't have enough money.",
[MESSAGE_MISSINGITEM] = "You don't have so many.",
[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] = "Good bye.",
[MESSAGE_WALKAWAY] = "Good bye.",
[MESSAGE_DECLINE] = "Then not.",
[MESSAGE_SENDTRADE] = "Of course, just browse through my wares.",
[MESSAGE_NOSHOP] = "Sorry, I'm not offering anything.",
[MESSAGE_ONCLOSESHOP] = "Thank you, come back whenever you're in need of something else.",
[MESSAGE_ALREADYFOCUSED]= "|PLAYERNAME|, I am already talking to you.",
[MESSAGE_WALKAWAY_MALE] = "Good bye.",
[MESSAGE_WALKAWAY_FEMALE] = "Good bye."
}
}
-- Creates a new NpcHandler with an empty callbackFunction stack.
function NpcHandler:new(keywordHandler)
local obj = {}
obj.callbackFunctions = {}
obj.modules = {}
obj.eventSay = {}
obj.eventDelayedSay = {}
obj.topic = {}
obj.focuses = {}
obj.talkStart = {}
obj.talkDelay = {}
obj.keywordHandler = keywordHandler
obj.messages = {}
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 self:isFocused(newFocus) then
return
end
self.focuses[#self.focuses + 1] = newFocus
self.topic[newFocus] = 0
local callback = self:getCallback(CALLBACK_ONADDFOCUS)
if callback == nil or callback(newFocus) then
self:processModuleCallback(CALLBACK_ONADDFOCUS, newFocus)
end
self:updateFocus()
end
-- Function used to verify if npc is focused to certain player
function NpcHandler:isFocused(focus)
for k,v in pairs(self.focuses) do
if v == focus then
return true
end
end
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()
for pos, focus in pairs(self.focuses) do
if focus ~= nil then
doNpcSetCreatureFocus(focus)
return
end
end
doNpcSetCreatureFocus(0)
end
-- Used when the npc should un-focus the player.
function NpcHandler:releaseFocus(focus)
if shop_cost[focus] ~= nil then
table.remove(shop_amount, focus)
table.remove(shop_cost, focus)
table.remove(shop_rlname, focus)
table.remove(shop_itemid, focus)
table.remove(shop_container, focus)
table.remove(shop_npcuid, focus)
table.remove(shop_eventtype, focus)
table.remove(shop_subtype, focus)
table.remove(shop_destination, focus)
table.remove(shop_premium, focus)
end
if self.eventDelayedSay[focus] then
self:cancelNPCTalk(self.eventDelayedSay[focus])
end
if not self:isFocused(focus) then
return
end
local pos = nil
for k,v in pairs(self.focuses) do
if v == focus then
pos = k
end
end
table.remove(self.focuses, pos)
self.eventSay[focus] = nil
self.eventDelayedSay[focus] = nil
self.talkStart[focus] = nil
self.topic[focus] = nil
local callback = self:getCallback(CALLBACK_ONRELEASEFOCUS)
if callback == nil or callback(focus) then
self:processModuleCallback(CALLBACK_ONRELEASEFOCUS, focus)
end
if Player(focus) ~= nil then
closeShopWindow(focus) --Even if it can not exist, we need to prevent it.
self:updateFocus()
end
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 then
self.modules[#self.modules +1] = module
module:init(self)
end
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 i, 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_ONTRADEREQUEST and module.callbackOnTradeRequest ~= nil then
tmpRet = module:callbackOnTradeRequest(...)
elseif id == CALLBACK_ONADDFOCUS and module.callbackOnAddFocus ~= nil then
tmpRet = module:callbackOnAddFocus(...)
elseif id == CALLBACK_ONRELEASEFOCUS and module.callbackOnReleaseFocus ~= nil then
tmpRet = module:callbackOnReleaseFocus(...)
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)
local ret = msg
for search, replace in pairs(parseInfo) do
ret = string.gsub(ret, search, replace)
end
return ret
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() then
if self:processModuleCallback(CALLBACK_FAREWELL) then
local msg = self:getMessage(MESSAGE_FAREWELL)
local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
self:resetNpc(cid)
msg = self:parseMessage(msg, parseInfo)
self:say(msg, cid, true)
self:releaseFocus(cid)
end
end
end
-- Greets a new player.
function NpcHandler:greet(cid)
if cid ~= 0 then
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)
local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
msg = self:parseMessage(msg, parseInfo)
self:say(msg, cid, true)
else
return
end
else
return
end
end
self:addFocus(cid)
end
-- Handles onCreatureAppear events. If you with to handle this yourself, please use the CALLBACK_CREATURE_APPEAR callback.
function NpcHandler:onCreatureAppear(cid)
if cid == getNpcCid() and next(self.shopItems) ~= nil then
local npc = Npc()
local speechBubble = npc:getSpeechBubble()
if speechBubble == 3 then
npc:setSpeechBubble(4)
else
npc:setSpeechBubble(2)
end
end
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)
if getNpcCid() == cid then
return
end
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, msgtype, msg)
local callback = self:getCallback(CALLBACK_CREATURE_SAY)
if callback == nil or callback(cid, msgtype, msg) then
if self:processModuleCallback(CALLBACK_CREATURE_SAY, cid, msgtype, msg) then
if not self:isInRange(cid) then
return
end
if self.keywordHandler ~= nil then
if self:isFocused(cid) and msgtype == TALKTYPE_PRIVATE_PN or not self:isFocused(cid) 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, msgtype, msg) then
self.talkStart[cid] = os.time()
end
else
self.talkStart[cid] = os.time()
end
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, msgtype, msg) then
if self:isFocused(cid) then
local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
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, msgtype, msg) 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 onTradeRequest events. If you wish to handle this yourself, use the CALLBACK_ONTRADEREQUEST callback.
function NpcHandler:onTradeRequest(cid)
local callback = self:getCallback(CALLBACK_ONTRADEREQUEST)
if callback == nil or callback(cid) then
if self:processModuleCallback(CALLBACK_ONTRADEREQUEST, cid) then
return true
end
end
return false
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
if NPCHANDLER_TALKDELAY == TALKDELAY_ONTHINK then
for cid, talkDelay in pairs(self.talkDelay) do
if talkDelay.time ~= nil and talkDelay.message ~= nil and os.time() >= talkDelay.time then
selfSay(talkDelay.message, cid, talkDelay.publicize and true or false)
self.talkDelay[cid] = nil
end
end
end
if self:processModuleCallback(CALLBACK_ONTHINK) then
for pos, focus in pairs(self.focuses) do
if focus ~= nil then
if not self:isInRange(focus) then
self:onWalkAway(focus)
elseif self.talkStart[focus] ~= nil and (os.time() - self.talkStart[focus]) > self.idleTime then
self:unGreet(focus)
else
self:updateFocus()
end
end
end
end
end
end
-- Tries to greet the player with the given cid.
function NpcHandler:onGreet(cid)
if self:isInRange(cid) then
if not self:isFocused(cid) then
self:greet(cid)
return
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() then
if self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid) then
local msg = self:getMessage(MESSAGE_WALKAWAY)
local playerName = Player(cid):getName()
if not playerName then
playerName = -1
end
local parseInfo = { [TAG_PLAYERNAME] = playerName }
local message = self:parseMessage(msg, parseInfo)
local msg_male = self:getMessage(MESSAGE_WALKAWAY_MALE)
local message_male = self:parseMessage(msg_male, parseInfo)
local msg_female = self:getMessage(MESSAGE_WALKAWAY_FEMALE)
local message_female = self:parseMessage(msg_female, parseInfo)
if message_female ~= message_male then
if Player(cid):getSex() == 0 then
selfSay(message_female)
else
selfSay(message_male)
end
elseif message ~= "" then
selfSay(message)
end
self:resetNpc(cid)
self:releaseFocus(cid)
end
end
end
end
-- Returns true if cid is within the talkRadius of this npc.
function NpcHandler:isInRange(cid)
local distance = getDistanceBetween(getCreaturePosition(getNpcCid()), getCreaturePosition(cid))
if(distance == -1) then
return false
end
return (distance <= self.talkRadius)
end
-- Resets the npc into its initial state (in regard of the keywordhandler).
-- 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
function NpcHandler:cancelNPCTalk(events)
for aux = 1, #events do
stopEvent(events[aux].event)
end
events = nil
end
function NpcHandler:doNPCTalkALot(msgs, interval, pcid)
if self.eventDelayedSay[pcid] then
self:cancelNPCTalk(self.eventDelayedSay[pcid])
end
self.eventDelayedSay[pcid] = {}
local ret = {}
for aux = 1, #msgs do
self.eventDelayedSay[pcid][aux] = {}
doCreatureSayWithDelay(getNpcCid(), msgs[aux], TALKTYPE_PRIVATE_NP, ((aux-1) * (interval or 4000)) + 700, self.eventDelayedSay[pcid][aux], pcid)
ret[#ret +1] = self.eventDelayedSay[pcid][aux]
end
return(ret)
end
-- Makes the npc represented by this instance of NpcHandler say something.
-- This implements the currently set type of talkdelay.
-- shallDelay is a boolean value. If it is false, the message is not delayed. Default value is true.
function NpcHandler:say(message, focus, publicize, shallDelay, delay)
if type(message) == "table" then
return self:doNPCTalkALot(message, delay or 6000, focus)
end
if self.eventDelayedSay[focus] then
self:cancelNPCTalk(self.eventDelayedSay[focus])
end
local shallDelay = not shallDelay and true or shallDelay
if NPCHANDLER_TALKDELAY == TALKDELAY_NONE or shallDelay == false then
selfSay(message, focus, publicize and true or false)
return
end
stopEvent(self.eventSay[focus])
self.eventSay[focus] = addEvent(function(x) if isPlayer(x[3]) then doCreatureSay(x[1], x[2], TALKTYPE_PRIVATE_NP, false, x[3], getCreaturePosition(x[1])) end end, self.talkDelayTime * 1000, {getNpcCid(), message, focus})
end
end
Modules:
-- Advanced NPC System by Jiddo
if Modules == nil then
-- default words for greeting and ungreeting the npc. Should be a table containing all such words.
FOCUS_GREETWORDS = {"hi", "hello"}
FOCUS_FAREWELLWORDS = {"bye", "farewell"}
-- The words for requesting trade window.
SHOP_TRADEREQUEST = {"trade"}
-- The word for accepting/declining an offer. CAN ONLY CONTAIN ONE FIELD! Should be a table with a single string value.
SHOP_YESWORD = {"yes"}
SHOP_NOWORD = {"no"}
-- Pattern used to get the amount of an item a player wants to buy/sell.
PATTERN_COUNT = "%d+"
-- Constants used to separate buying from selling.
SHOPMODULE_SELL_ITEM = 1
SHOPMODULE_BUY_ITEM = 2
SHOPMODULE_BUY_ITEM_CONTAINER = 3
-- Constants used for shop mode. Notice: addBuyableItemContainer is working on all modes
SHOPMODULE_MODE_TALK = 1 -- Old system used before client version 8.2: sell/buy item name
SHOPMODULE_MODE_TRADE = 2 -- Trade window system introduced in client version 8.2
SHOPMODULE_MODE_BOTH = 3 -- Both working at one time
-- Used shop mode
SHOPMODULE_MODE = SHOPMODULE_MODE_BOTH
Modules = {
parseableModules = {}
}
StdModule = {}
-- These callback function must be called with parameters.npcHandler = npcHandler in the parameters table or they will not work correctly.
-- Notice: The members of StdModule have not yet been tested. If you find any bugs, please report them to me.
-- Usage:
-- keywordHandler:addKeyword({"offer"}, StdModule.say, {npcHandler = npcHandler, text = "I sell many powerful melee weapons."})
function StdModule.say(cid, message, keywords, parameters, node)
local npcHandler = parameters.npcHandler
if npcHandler == nil then
error("StdModule.say called without any npcHandler instance.")
end
local onlyFocus = (parameters.onlyFocus == nil or parameters.onlyFocus == true)
if not npcHandler:isFocused(cid) and onlyFocus then
return false
end
local parseInfo = {[TAG_PLAYERNAME] = Player(cid):getName()}
npcHandler:say(npcHandler:parseMessage(parameters.text or parameters.message, parseInfo), cid, parameters.publicize and true)
if not parameters.reset == false then
npcHandler:resetNpc(cid)
elseif parameters.moveup ~= nil then
npcHandler.keywordHandler:moveUp(parameters.moveup)
end
return true
end
--Usage:
-- local node1 = keywordHandler:addKeyword({"promot"}, StdModule.say, {npcHandler = npcHandler, text = "I can promote you for 20000 gold coins. Do you want me to promote you?"})
-- node1:addChildKeyword({"yes"}, StdModule.promotePlayer, {npcHandler = npcHandler, cost = 20000, level = 20}, text = "Congratulations! You are now promoted.")
-- node1:addChildKeyword({"no"}, StdModule.say, {npcHandler = npcHandler, text = "Allright then. Come back when you are ready."}, reset = true)
function StdModule.promotePlayer(cid, message, keywords, parameters, node)
local npcHandler = parameters.npcHandler
if npcHandler == nil then
error("StdModule.promotePlayer called without any npcHandler instance.")
end
if not npcHandler:isFocused(cid) then
return false
end
if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then
local promotedVoc = getPromotedVocation(getPlayerVocation(cid))
if(getPlayerStorageValue(cid, 30018) == 1) then
npcHandler:say("You are already promoted!", cid)
elseif(getPlayerLevel(cid) < parameters.level) then
npcHandler:say("I am sorry, but I can only promote you once you have reached level " .. parameters.level .. ".", cid)
elseif(doPlayerRemoveMoney(cid, parameters.cost) ~= TRUE) then
npcHandler:say("You do not have enough money!", cid)
else
doPlayerSetVocation(cid, promotedVoc)
npcHandler:say(parameters.text, cid)
end
else
npcHandler:say("You need a premium account in order to get promoted.", cid)
end
npcHandler:resetNpc(cid)
return true
end
function StdModule.learnSpell(cid, message, keywords, parameters, node)
local npcHandler = parameters.npcHandler
if npcHandler == nil then
error("StdModule.learnSpell called without any npcHandler instance.")
end
if not npcHandler:isFocused(cid) then
return false
end
if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) or not parameters.premium) then
if getPlayerLearnedInstantSpell(cid, parameters.spellName) == TRUE then
npcHandler:say("You already know this spell.", cid)
elseif getPlayerLevel(cid) < parameters.level then
npcHandler:say("You need to obtain a level of " .. parameters.level .. " or higher to be able to learn " .. parameters.spellName .. ".", cid)
elseif getPlayerVocation(cid) ~= parameters.vocation and getPlayerVocation(cid) ~= parameters.vocation + 4 and vocation ~= 9 then
npcHandler:say("This spell is not for your vocation", cid)
elseif doPlayerRemoveMoney(cid, parameters.price) == FALSE then
npcHandler:say("You do not have enough money, this spell costs " .. parameters.price .. " gold.", cid)
else
npcHandler:say("You have learned " .. parameters.spellName .. ".", cid)
playerLearnInstantSpell(cid, parameters.spellName)
end
else
npcHandler:say("You need a premium account in order to buy " .. parameters.spellName .. ".", cid)
end
npcHandler:resetNpc(cid)
return true
end
function StdModule.bless(cid, message, keywords, parameters, node)
local npcHandler = parameters.npcHandler
if npcHandler == nil then
error("StdModule.bless called without any npcHandler instance.")
end
if(not npcHandler:isFocused(cid) or getWorldType() == WORLD_TYPE_PVP_ENFORCED) then
return false
end
if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then
if getPlayerBlessing(cid, parameters.bless) then
npcHandler:say("Gods have already blessed you with this blessing!", cid)
elseif doPlayerRemoveMoney(cid, parameters.cost) == FALSE then
npcHandler:say("You don't have enough money for blessing.", cid)
else
npcHandler:say("You have been blessed by one of the five gods!", cid)
doPlayerAddBlessing(cid, parameters.bless)
end
else
npcHandler:say("You need a premium account in order to be blessed.", cid)
end
npcHandler:resetNpc(cid)
return true
end
function StdModule.travel(cid, message, keywords, parameters, node)
local npcHandler = parameters.npcHandler
if npcHandler == nil then
error("StdModule.travel called without any npcHandler instance.")
end
if(not npcHandler:isFocused(cid)) then
return false
end
if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then
if(isPlayerPzLocked(cid)) then
npcHandler:say("First get rid of those blood stains! You are not going to ruin my vehicle!", cid)
elseif(parameters.level ~= nil and getPlayerLevel(cid) < parameters.level) then
npcHandler:say("You must reach level " .. parameters.level .. " before I can let you go there.", cid)
elseif(doPlayerRemoveMoney(cid, parameters.cost) ~= TRUE) then
npcHandler:say("You don't have enough money.", cid)
else
npcHandler:say(parameters.msg or "Set the sails!", cid)
npcHandler:releaseFocus(cid)
doSendMagicEffect(getCreaturePosition(cid), CONST_ME_TELEPORT)
doTeleportThing(cid, parameters.destination)
doSendMagicEffect(parameters.destination, CONST_ME_TELEPORT)
end
else
npcHandler:say("I'm sorry, but you need a premium account in order to travel onboard our ships.", cid)
end
npcHandler:resetNpc(cid)
return true
end
FocusModule = {
npcHandler = nil
}
-- Creates a new instance of FocusModule without an associated NpcHandler.
function FocusModule:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
-- Inits the module and associates handler to it.
function FocusModule:init(handler)
self.npcHandler = handler
for i, word in pairs(FOCUS_GREETWORDS) do
local obj = {}
obj[#obj + 1] = word
obj.callback = FOCUS_GREETWORDS.callback or FocusModule.messageMatcher
handler.keywordHandler:addKeyword(obj, FocusModule.onGreet, {module = self})
end
for i, word in pairs(FOCUS_FAREWELLWORDS) do
local obj = {}
obj[#obj + 1] = word
obj.callback = FOCUS_FAREWELLWORDS.callback or FocusModule.messageMatcher
handler.keywordHandler:addKeyword(obj, FocusModule.onFarewell, {module = self})
end
return true
end
-- Greeting callback function.
function FocusModule.onGreet(cid, message, keywords, parameters)
parameters.module.npcHandler:onGreet(cid)
return true
end
-- UnGreeting callback function.
function FocusModule.onFarewell(cid, message, keywords, parameters)
if parameters.module.npcHandler:isFocused(cid) then
parameters.module.npcHandler:onFarewell(cid)
return true
else
return false
end
end
-- Custom message matching callback function for greeting messages.
function FocusModule.messageMatcher(keywords, message)
for i, word in pairs(keywords) do
if type(word) == "string" then
if string.find(message, word) and not string.find(message, "[%w+]" .. word) and not string.find(message, word .. "[%w+]") then
return true
end
end
end
return false
end
KeywordModule = {
npcHandler = nil
}
-- Add it to the parseable module list.
Modules.parseableModules["module_keywords"] = KeywordModule
function KeywordModule:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
function KeywordModule:init(handler)
self.npcHandler = handler
return true
end
-- Parses all known parameters.
function KeywordModule:parseParameters()
local ret = NpcSystem.getParameter("keywords")
if ret ~= nil then
self:parseKeywords(ret)
end
end
function KeywordModule:parseKeywords(data)
local n = 1
for keys in string.gmatch(data, "[^;]+") do
local i = 1
local keywords = {}
for temp in string.gmatch(keys, "[^,]+") do
keywords[#keywords + 1] = temp
i = i + 1
end
if i ~= 1 then
local reply = NpcSystem.getParameter("keyword_reply" .. n)
if reply ~= nil then
self:addKeyword(keywords, reply)
else
print("[Warning] NpcSystem:", "Parameter '" .. "keyword_reply" .. n .. "' missing. Skipping...")
end
else
print("[Warning] NpcSystem:", "No keywords found for keyword set #" .. n .. ". Skipping...")
end
n = n+1
end
end
function KeywordModule:addKeyword(keywords, reply)
self.npcHandler.keywordHandler:addKeyword(keywords, StdModule.say, {npcHandler = self.npcHandler, onlyFocus = true, text = reply, reset = true})
end
TravelModule = {
npcHandler = nil,
destinations = nil,
yesNode = nil,
noNode = nil,
}
-- Add it to the parseable module list.
Modules.parseableModules["module_travel"] = TravelModule
function TravelModule:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
function TravelModule:init(handler)
self.npcHandler = handler
self.yesNode = KeywordNode:new(SHOP_YESWORD, TravelModule.onConfirm, {module = self})
self.noNode = KeywordNode:new(SHOP_NOWORD, TravelModule.onDecline, {module = self})
self.destinations = {}
return true
end
-- Parses all known parameters.
function TravelModule:parseParameters()
local ret = NpcSystem.getParameter("travel_destinations")
if ret ~= nil then
self:parseDestinations(ret)
self.npcHandler.keywordHandler:addKeyword({"destination"}, TravelModule.listDestinations, {module = self})
self.npcHandler.keywordHandler:addKeyword({"where"}, TravelModule.listDestinations, {module = self})
self.npcHandler.keywordHandler:addKeyword({"travel"}, TravelModule.listDestinations, {module = self})
end
end
function TravelModule:parseDestinations(data)
for destination in string.gmatch(data, "[^;]+") do
local i = 1
local name = nil
local x = nil
local y = nil
local z = nil
local cost = nil
local premium = false
for temp in string.gmatch(destination, "[^,]+") do
if i == 1 then
name = temp
elseif i == 2 then
x = tonumber(temp)
elseif i == 3 then
y = tonumber(temp)
elseif i == 4 then
z = tonumber(temp)
elseif i == 5 then
cost = tonumber(temp)
elseif i == 6 then
premium = temp == "true"
else
print("[Warning : " .. getCreatureName(getNpcCid()) .. "] NpcSystem:", "Unknown parameter found in travel destination parameter.", temp, destination)
end
i = i + 1
end
if(name ~= nil and x ~= nil and y ~= nil and z ~= nil and cost ~= nil) then
self:addDestination(name, {x=x, y=y, z=z}, cost, premium)
else
print("[Warning] NpcSystem:", "Parameter(s) missing for travel destination:", name, x, y, z, cost, premium)
end
end
end
function TravelModule:addDestination(name, position, price, premium)
self.destinations[#self.destinations + 1] = name
local parameters = {
cost = price,
destination = position,
premium = premium,
module = self
}
local keywords = {}
keywords[#keywords + 1] = name
local keywords2 = {}
keywords2[#keywords2 + 1] = "bring me to " .. name
local node = self.npcHandler.keywordHandler:addKeyword(keywords, TravelModule.travel, parameters)
self.npcHandler.keywordHandler:addKeyword(keywords2, TravelModule.bringMeTo, parameters)
node:addChildKeywordNode(self.yesNode)
node:addChildKeywordNode(self.noNode)
if npcs_loaded_travel[getNpcCid()] == nil then
npcs_loaded_travel[getNpcCid()] = getNpcCid()
self.npcHandler.keywordHandler:addKeyword({'yes'}, TravelModule.onConfirm, {module = self})
self.npcHandler.keywordHandler:addKeyword({'no'}, TravelModule.onDecline, {module = self})
end
end
function TravelModule.travel(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) then
return false
end
local npcHandler = module.npcHandler
shop_destination[cid] = parameters.destination
shop_cost[cid] = parameters.cost
shop_premium[cid] = parameters.premium
shop_npcuid[cid] = getNpcCid()
local cost = parameters.cost
local destination = parameters.destination
local premium = parameters.premium
module.npcHandler:say("Do you want to travel to " .. keywords[1] .. " for " .. cost .. " gold coins?", cid)
return true
end
function TravelModule.onConfirm(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) then
return false
end
if shop_npcuid[cid] ~= getNpcCid() then
return false
end
local npcHandler = module.npcHandler
local parentParameters = node:getParent():getParameters()
local cost = shop_cost[cid]
local destination = shop_destination[cid]
local premium = shop_premium[cid]
if(not isPlayerPremiumCallback or isPlayerPremiumCallback(cid) or shop_premium[cid] ~= true) then
if(doPlayerRemoveMoney(cid, cost) ~= TRUE) then
npcHandler:say("You do not have enough money!", cid)
elseif(isPlayerPzLocked(cid)) then
npcHandler:say("Get out of there with this blood.", cid)
else
npcHandler:say("It was a pleasure doing business with you.", cid)
npcHandler:releaseFocus(cid)
doTeleportThing(cid, destination, 0)
doSendMagicEffect(destination, CONST_ME_TELEPORT)
end
else
npcHandler:say("I can only allow premium players to travel there.", cid)
end
npcHandler:resetNpc(cid)
return true
end
-- onDecline keyword callback function. Generally called when the player sais "no" after wanting to buy an item.
function TravelModule.onDecline(cid, message, keywords, parameters, node)
local module = parameters.module
if not module.npcHandler:isFocused(cid) or shop_npcuid[cid] ~= getNpcCid() then
return false
end
local parentParameters = node:getParent():getParameters()
local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() }
local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_DECLINE), parseInfo)
module.npcHandler:say(msg, cid)
module.npcHandler:resetNpc(cid)
return true
end
function TravelModule.bringMeTo(cid, message, keywords, parameters, node)
local module = parameters.module
if not module.npcHandler:isFocused(cid) then
return false
end
local cost = parameters.cost
local destination = parameters.destination
local premium = parameters.premium
if(not isPlayerPremiumCallback or isPlayerPremiumCallback(cid) or parameters.premium ~= true) then
if(doPlayerRemoveMoney(cid, cost) == TRUE) then
doTeleportThing(cid, destination, 0)
doSendMagicEffect(destination, CONST_ME_TELEPORT)
end
end
return true
end
function TravelModule.listDestinations(cid, message, keywords, parameters, node)
local module = parameters.module
if not module.npcHandler:isFocused(cid) then
return false
end
local msg = "I can bring you to "
--local i = 1
local maxn = #module.destinations
for i, destination in pairs(module.destinations) do
msg = msg .. destination
if i == maxn - 1 then
msg = msg .. " and "
elseif i == maxn then
msg = msg .. "."
else
msg = msg .. ", "
end
i = i + 1
end
module.npcHandler:say(msg, cid)
module.npcHandler:resetNpc(cid)
return true
end
ShopModule = {
npcHandler = nil,
yesNode = nil,
noNode = nil,
noText = "",
maxCount = 100,
amount = 0
}
-- Add it to the parseable module list.
Modules.parseableModules["module_shop"] = ShopModule
-- Creates a new instance of ShopModule
function ShopModule:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
-- Parses all known parameters.
function ShopModule:parseParameters()
local ret = NpcSystem.getParameter("shop_buyable")
if ret ~= nil then
self:parseBuyable(ret)
end
local ret = NpcSystem.getParameter("shop_sellable")
if ret ~= nil then
self:parseSellable(ret)
end
local ret = NpcSystem.getParameter("shop_buyable_containers")
if ret ~= nil then
self:parseBuyableContainers(ret)
end
end
-- Parse a string contaning a set of buyable items.
function ShopModule:parseBuyable(data)
for item in string.gmatch(data, "[^;]+") do
local i = 1
local name = nil
local itemid = nil
local cost = nil
local subType = nil
local realName = nil
for temp in string.gmatch(item, "[^,]+") do
if i == 1 then
name = temp
elseif i == 2 then
itemid = tonumber(temp)
elseif i == 3 then
cost = tonumber(temp)
elseif i == 4 then
subType = tonumber(temp)
elseif i == 5 then
realName = temp
else
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Unknown parameter found in buyable items parameter.", temp, item)
end
i = i + 1
end
if SHOPMODULE_MODE == SHOPMODULE_MODE_TRADE then
if itemid ~= nil and cost ~= nil then
if subType == nil and ItemType(itemid):isFluidContainer() then
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item)
else
self:addBuyableItem(nil, itemid, cost, subType, realName)
end
else
print("[Warning] NpcSystem:", "Parameter(s) missing for item:", itemid, cost)
end
else
if name ~= nil and itemid ~= nil and cost ~= nil then
if subType == nil and ItemType(itemid):isFluidContainer() then
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item)
else
local names = {}
names[#names + 1] = name
self:addBuyableItem(names, itemid, cost, subType, realName)
end
else
print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, itemid, cost)
end
end
end
end
-- Parse a string contaning a set of sellable items.
function ShopModule:parseSellable(data)
for item in string.gmatch(data, "[^;]+") do
local i = 1
local name = nil
local itemid = nil
local cost = nil
local realName = nil
local subType = nil
for temp in string.gmatch(item, "[^,]+") do
if i == 1 then
name = temp
elseif i == 2 then
itemid = tonumber(temp)
elseif i == 3 then
cost = tonumber(temp)
elseif i == 4 then
realName = temp
elseif i == 5 then
subType = tonumber(temp)
else
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Unknown parameter found in sellable items parameter.", temp, item)
end
i = i + 1
end
if SHOPMODULE_MODE == SHOPMODULE_MODE_TRADE then
if itemid ~= nil and cost ~= nil then
self:addSellableItem(nil, itemid, cost, realName, subType)
else
print("[Warning] NpcSystem:", "Parameter(s) missing for item:", itemid, cost)
end
else
if name ~= nil and itemid ~= nil and cost ~= nil then
local names = {}
names[#names + 1] = name
self:addSellableItem(names, itemid, cost, realName, subType)
else
print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, itemid, cost)
end
end
end
end
-- Parse a string contaning a set of buyable items.
function ShopModule:parseBuyableContainers(data)
for item in string.gmatch(data, "[^;]+") do
local i = 1
local name = nil
local container = nil
local itemid = nil
local cost = nil
local subType = nil
local realName = nil
for temp in string.gmatch(item, "[^,]+") do
if i == 1 then
name = temp
elseif i == 2 then
itemid = tonumber(temp)
elseif i == 3 then
itemid = tonumber(temp)
elseif i == 4 then
cost = tonumber(temp)
elseif i == 5 then
subType = tonumber(temp)
elseif i == 6 then
realName = temp
else
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Unknown parameter found in buyable items parameter.", temp, item)
end
i = i + 1
end
if name ~= nil and container ~= nil and itemid ~= nil and cost ~= nil then
if subType == nil and ItemType(itemid):isFluidContainer() then
print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item)
else
local names = {}
names[#names + 1] = name
self:addBuyableItemContainer(names, container, itemid, cost, subType, realName)
end
else
print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, container, itemid, cost)
end
end
end
-- Initializes the module and associates handler to it.
function ShopModule:init(handler)
self.npcHandler = handler
self.yesNode = KeywordNode:new(SHOP_YESWORD, ShopModule.onConfirm, {module = self})
self.noNode = KeywordNode:new(SHOP_NOWORD, ShopModule.onDecline, {module = self})
self.noText = handler:getMessage(MESSAGE_DECLINE)
if SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK then
for i, word in pairs(SHOP_TRADEREQUEST) do
local obj = {}
obj[#obj + 1] = word
obj.callback = SHOP_TRADEREQUEST.callback or ShopModule.messageMatcher
handler.keywordHandler:addKeyword(obj, ShopModule.requestTrade, {module = self})
end
end
return true
end
-- Custom message matching callback function for requesting trade messages.
function ShopModule.messageMatcher(keywords, message)
for i, word in pairs(keywords) do
if type(word) == "string" then
if string.find(message, word) and not string.find(message, "[%w+]" .. word) and not string.find(message, word .. "[%w+]") then
return true
end
end
end
return false
end
-- Resets the module-specific variables.
function ShopModule:reset()
self.amount = 0
end
-- Function used to match a number value from a string.
function ShopModule:getCount(message)
local ret = 1
local b, e = string.find(message, PATTERN_COUNT)
if b ~= nil and e ~= nil then
ret = tonumber(string.sub(message, b, e))
end
if ret <= 0 then
ret = 1
elseif ret > self.maxCount then
ret = self.maxCount
end
return ret
end
-- Adds a new buyable item.
-- names = A table containing one or more strings of alternative names to this item. Used only for old buy/sell system.
-- itemid = The itemid of the buyable item
-- cost = The price of one single item
-- subType - The subType of each rune or fluidcontainer item. Can be left out if it is not a rune/fluidcontainer. Default value is 1.
-- realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used)
function ShopModule:addBuyableItem(names, itemid, cost, itemSubType, realName)
if SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK then
if itemSubType == nil then
itemSubType = 1
end
local shopItem = self:getShopItem(itemid, itemSubType)
if shopItem == nil then
self.npcHandler.shopItems[#self.npcHandler.shopItems + 1] = {id = itemid, buy = cost, sell = -1, subType = itemSubType, name = realName or ItemType(itemid):getName()}
else
shopItem.buy = cost
end
end
if names ~= nil and SHOPMODULE_MODE ~= SHOPMODULE_MODE_TRADE then
for i, name in pairs(names) do
local parameters = {
itemid = itemid,
cost = cost,
eventType = SHOPMODULE_BUY_ITEM,
module = self,
realName = realName or ItemType(itemid):getName(),
subType = itemSubType or 1
}
keywords = {}
keywords[#keywords + 1] = "buy"
keywords[#keywords + 1] = name
local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters)
node:addChildKeywordNode(self.yesNode)
node:addChildKeywordNode(self.noNode)
end
end
if npcs_loaded_shop[getNpcCid()] == nil then
npcs_loaded_shop[getNpcCid()] = getNpcCid()
self.npcHandler.keywordHandler:addKeyword({'yes'}, ShopModule.onConfirm, {module = self})
self.npcHandler.keywordHandler:addKeyword({'no'}, ShopModule.onDecline, {module = self})
end
end
function ShopModule:getShopItem(itemId, itemSubType)
if ItemType(itemId):isFluidContainer() then
for i = 1, #self.npcHandler.shopItems do
local shopItem = self.npcHandler.shopItems
if shopItem.id == itemId and shopItem.subType == itemSubType then
return shopItem
end
end
else
for i = 1, #self.npcHandler.shopItems do
local shopItem = self.npcHandler.shopItems
if shopItem.id == itemId then
return shopItem
end
end
end
return nil
end
-- Adds a new buyable container of items.
-- names = A table containing one or more strings of alternative names to this item.
-- container = Backpack, bag or any other itemid of container where bought items will be stored
-- itemid = The itemid of the buyable item
-- cost = The price of one single item
-- subType - The subType of each rune or fluidcontainer item. Can be left out if it is not a rune/fluidcontainer. Default value is 1.
-- realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used)
function ShopModule:addBuyableItemContainer(names, container, itemid, cost, subType, realName)
if names ~= nil then
for i, name in pairs(names) do
local parameters = {
container = container,
itemid = itemid,
cost = cost,
eventType = SHOPMODULE_BUY_ITEM_CONTAINER,
module = self,
realName = realName or ItemType(itemid):getName(),
subType = subType or 1
}
keywords = {}
keywords[#keywords + 1] = "buy"
keywords[#keywords + 1] = name
local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters)
node:addChildKeywordNode(self.yesNode)
node:addChildKeywordNode(self.noNode)
end
end
end
-- Adds a new sellable item.
-- names = A table containing one or more strings of alternative names to this item. Used only by old buy/sell system.
-- itemid = The itemid of the sellable item
-- cost = The price of one single item
-- realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used)
function ShopModule:addSellableItem(names, itemid, cost, realName, itemSubType)
if SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK then
if itemSubType == nil then
itemSubType = 0
end
local shopItem = self:getShopItem(itemid, itemSubType)
if shopItem == nil then
table.insert(self.npcHandler.shopItems, {id = itemid, buy = -1, sell = cost, subType = itemSubType, name = realName or getItemName(itemid)})
else
shopItem.sell = cost
end
end
if(names ~= nil and SHOPMODULE_MODE ~= SHOPMODULE_MODE_TRADE) then
for i, name in pairs(names) do
local parameters = {
itemid = itemid,
cost = cost,
eventType = SHOPMODULE_SELL_ITEM,
module = self,
realName = realName or getItemName(itemid)
}
keywords = {}
table.insert(keywords, "sell")
table.insert(keywords, name)
local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters)
node:addChildKeywordNode(self.yesNode)
node:addChildKeywordNode(self.noNode)
end
end
end
-- onModuleReset callback function. Calls ShopModule:reset()
function ShopModule:callbackOnModuleReset()
self:reset()
return true
end
-- Callback onBuy() function. If you wish, you can change certain Npc to use your onBuy().
function ShopModule:callbackOnBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks)
local shopItem = self:getShopItem(itemid, subType)
if shopItem == nil then
error("[ShopModule.onBuy] shopItem == nil")
return false
end
if shopItem.buy == -1 then
error("[ShopModule.onSell] attempt to buy a non-buyable item")
return false
end
local backpack = 1988
local totalCost = amount * shopItem.buy
if(inBackpacks) then
totalCost = isItemStackable(itemid) == TRUE and totalCost + 20 or totalCost + (math.max(1, math.floor(amount / getContainerCapById(backpack))) * 20)
end
local parseInfo = {
[TAG_PLAYERNAME] = getPlayerName(cid),
[TAG_ITEMCOUNT] = amount,
[TAG_TOTALCOST] = totalCost,
[TAG_ITEMNAME] = shopItem.name
}
if(getPlayerMoney(cid) < totalCost) then
local msg = self.npcHandler:getMessage(MESSAGE_NEEDMONEY)
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendCancel(cid, msg)
return false
end
local subType = shopItem.subType or 1
local a, b = doNpcSellItem(cid, itemid, amount, subType, ignoreCap, inBackpacks, backpack)
if(a < amount) then
local msgId = MESSAGE_NEEDMORESPACE
if(a == 0) then
msgId = MESSAGE_NEEDSPACE
end
local msg = self.npcHandler:getMessage(msgId)
parseInfo[TAG_ITEMCOUNT] = a
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendCancel(cid, msg)
self.npcHandler.talkStart[cid] = os.time()
if(a > 0) then
doPlayerRemoveMoney(cid, ((a * shopItem.buy) + (b * 20)))
return true
end
return false
else
local msg = self.npcHandler:getMessage(MESSAGE_BOUGHT)
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, msg)
doPlayerRemoveMoney(cid, totalCost)
self.npcHandler.talkStart[cid] = os.time()
return true
end
end
-- Callback onSell() function. If you wish, you can change certain Npc to use your onSell().
function ShopModule:callbackOnSell(cid, itemid, subType, amount, ignoreEquipped, _)
local shopItem = self:getShopItem(itemid, subType)
if shopItem == nil then
error("[ShopModule.onSell] items[itemid] == nil")
return false
end
if shopItem.sell == -1 then
error("[ShopModule.onSell] attempt to sell a non-sellable item")
return false
end
local parseInfo = {
[TAG_PLAYERNAME] = getPlayerName(cid),
[TAG_ITEMCOUNT] = amount,
[TAG_TOTALCOST] = amount * shopItem.sell,
[TAG_ITEMNAME] = shopItem.name
}
if not isItemFluidContainer(itemid) then
subType = -1
end
if doPlayerRemoveItem(cid, itemid, amount, subType, ignoreEquipped) then
local msg = self.npcHandler:getMessage(MESSAGE_SOLD)
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, msg)
doPlayerAddMoney(cid, amount * shopItem.sell)
self.npcHandler.talkStart[cid] = os.time()
return true
else
local msg = self.npcHandler:getMessage(MESSAGE_NEEDITEM)
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendCancel(cid, msg)
self.npcHandler.talkStart[cid] = os.time()
return false
end
end
-- Callback for requesting a trade window with the NPC.
function ShopModule.requestTrade(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) then
return false
end
if(not module.npcHandler:onTradeRequest(cid)) then
return false
end
local itemWindow = {}
for i = 1, #module.npcHandler.shopItems do
table.insert(itemWindow, module.npcHandler.shopItems)
end
if(itemWindow[1] == nil) then
local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) }
local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_NOSHOP), parseInfo)
module.npcHandler:say(msg, cid)
return true
end
local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) }
local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_SENDTRADE), parseInfo)
openShopWindow(cid, itemWindow,
function(cid, itemid, subType, amount, ignoreCap, inBackpacks) module.npcHandler:onBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks) end,
function(cid, itemid, subType, amount, ignoreCap, inBackpacks) module.npcHandler:onSell(cid, itemid, subType, amount, ignoreCap, inBackpacks) end)
module.npcHandler:say(msg, cid)
return true
end
-- onConfirm keyword callback function. Sells/buys the actual item.
function ShopModule.onConfirm(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) or shop_npcuid[cid] ~= getNpcCid() then
return false
end
shop_npcuid[cid] = 0
local parentParameters = node:getParent():getParameters()
local parseInfo = {
[TAG_PLAYERNAME] = getPlayerName(cid),
[TAG_ITEMCOUNT] = shop_amount[cid],
[TAG_TOTALCOST] = shop_cost[cid] * shop_amount[cid],
[TAG_ITEMNAME] = shop_rlname[cid]
}
if(shop_eventtype[cid] == SHOPMODULE_SELL_ITEM) then
local ret = doPlayerSellItem(cid, shop_itemid[cid], shop_amount[cid], shop_cost[cid] * shop_amount[cid])
if(ret == LUA_NO_ERROR) then
local msg = module.npcHandler:getMessage(MESSAGE_ONSELL)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
else
local msg = module.npcHandler:getMessage(MESSAGE_MISSINGITEM)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
end
elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM) then
local cost = shop_cost[cid] * shop_amount[cid]
if getPlayerMoney(cid) < cost then
local msg = module.npcHandler:getMessage(MESSAGE_MISSINGMONEY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
return false
end
local a, b = doNpcSellItem(cid, shop_itemid[cid], shop_amount[cid], shop_subtype[cid], false, false, 1988)
if(a < shop_amount[cid]) then
local msgId = MESSAGE_NEEDMORESPACE
if(a == 0) then
msgId = MESSAGE_NEEDSPACE
end
local msg = module.npcHandler:getMessage(msgId)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
if(a > 0) then
doPlayerRemoveMoney(cid, a * shop_cost[cid])
if shop_itemid[cid] == ITEM_PARCEL then
doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, 1988)
end
return true
end
return false
else
local msg = module.npcHandler:getMessage(MESSAGE_ONBUY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
doPlayerRemoveMoney(cid, cost)
if shop_itemid[cid] == ITEM_PARCEL then
doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, 1988)
end
return true
end
elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM_CONTAINER) then
local ret = doPlayerBuyItemContainer(cid, shop_container[cid], shop_itemid[cid], shop_amount[cid], shop_cost[cid] * shop_amount[cid], shop_subtype[cid])
if(ret == LUA_NO_ERROR) then
local msg = module.npcHandler:getMessage(MESSAGE_ONBUY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
else
local msg = module.npcHandler:getMessage(MESSAGE_MISSINGMONEY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
end
end
module.npcHandler:resetNpc(cid)
return true
end
-- onDecline keyword callback function. Generally called when the player sais "no" after wanting to buy an item.
function ShopModule.onDecline(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) or shop_npcuid[cid] ~= getNpcCid() then
return false
end
shop_npcuid[cid] = 0
local parentParameters = node:getParent():getParameters()
local parseInfo = {
[TAG_PLAYERNAME] = getPlayerName(cid),
[TAG_ITEMCOUNT] = shop_amount[cid],
[TAG_TOTALCOST] = shop_cost[cid] * shop_amount[cid],
[TAG_ITEMNAME] = shop_rlname[cid]
}
local msg = module.npcHandler:parseMessage(module.noText, parseInfo)
module.npcHandler:say(msg, cid)
module.npcHandler:resetNpc(cid)
return true
end
-- tradeItem callback function. Makes the npc say the message defined by MESSAGE_BUY or MESSAGE_SELL
function ShopModule.tradeItem(cid, message, keywords, parameters, node)
local module = parameters.module
if(not module.npcHandler:isFocused(cid)) then
return false
end
if(not module.npcHandler:onTradeRequest(cid)) then
return true
end
local count = module:getCount(message)
module.amount = count
shop_amount[cid] = module.amount
shop_cost[cid] = parameters.cost
shop_rlname[cid] = parameters.realName
shop_itemid[cid] = parameters.itemid
shop_container[cid] = parameters.container
shop_npcuid[cid] = getNpcCid()
shop_eventtype[cid] = parameters.eventType
shop_subtype[cid] = parameters.subType
local parseInfo = {
[TAG_PLAYERNAME] = getPlayerName(cid),
[TAG_ITEMCOUNT] = shop_amount[cid],
[TAG_TOTALCOST] = shop_cost[cid] * shop_amount[cid],
[TAG_ITEMNAME] = shop_rlname[cid]
}
if(shop_eventtype[cid] == SHOPMODULE_SELL_ITEM) then
local msg = module.npcHandler:getMessage(MESSAGE_SELL)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM) then
local msg = module.npcHandler:getMessage(MESSAGE_BUY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM_CONTAINER) then
local msg = module.npcHandler:getMessage(MESSAGE_BUY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
end
return true
end
end
Keyworldhandler:
-- Advanced NPC System by Jiddo
if KeywordHandler == nil then
KeywordNode = {
keywords = nil,
callback = nil,
parameters = nil,
children = nil,
parent = nil
}
-- Created a new keywordnode with the given keywords, callback function and parameters and without any childNodes.
function KeywordNode:new(keys, func, param)
local obj = {}
obj.keywords = keys
obj.callback = func
obj.parameters = param
obj.children = {}
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
-- Returns true if message contains all patterns/strings found in keywords.
function KeywordNode:checkMessage(message)
if self.keywords.callback ~= nil then
return self.keywords.callback(self.keywords, message)
end
for i,v in ipairs(self.keywords) do
if type(v) == 'string' then
local a, b = string.find(message, v)
if a == nil or b == nil then
return false
end
end
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)
local new = KeywordNode:new(keywords, callback, parameters)
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 = {}
}
-- Creates a new keywordhandler with an empty rootnode.
function KeywordHandler:new()
local obj = {}
obj.root = KeywordNode:new(nil, nil, nil)
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 = string.lower(message)
for i, childNode in pairs(node.children) do
if childNode:checkMessage(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
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)
return self:getRoot():addChildKeyword(keys, callback, parameters)
end
-- Moves the current position in the keyword hierarchy count steps upwards. Count defalut value = 1.
-- This function MIGHT not work properly yet. Use at your own risk.
function KeywordHandler:moveUp(count)
local steps = count
if steps == nil or type(steps) ~= "number" then
steps = 1
end
for i = 1, steps, 1 do
if self.lastNode[cid] == nil then
break
end
self.lastNode[cid] = self.lastNode[cid]:getParent() or self:getRoot()
end
return self.lastNode[cid]
end
end
Você tem alguma imagem que possa auxiliar no problema? Se sim, anexe-a dentro do spoiler abaixo:
Compartilhar este post
Link para o post
Compartilhar em outros sites