-- 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] 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((isItemRune(itemid) or isItemFluidContainer(itemid)) and subType == nil) then
print('[Warning] 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((isItemRune(itemid) or isItemFluidContainer(itemid)) and subType == nil) then
print('[Warning] NpcSystem:', 'SubType missing for parameter item:', item)
else
local names = {}
table.insert(names, 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
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
else
print('[Warning] 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)
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 = {}
table.insert(names, name)
self:addSellableItem(names, itemid, cost, realName)
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] 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((isItemRune(itemid) or isItemFluidContainer(itemid)) and subType == nil) then
print('[Warning] NpcSystem:', 'SubType missing for parameter item:', item)
else
local names = {}
table.insert(names, 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 = {}
table.insert(obj, 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 (getItemNameById will be used)
function ShopModule:addBuyableItem(names, itemid, cost, subType, realName)
if(SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK) then
if(self.npcHandler.shopItems[itemid] == nil) then
self.npcHandler.shopItems[itemid] = {buyPrice = -1, sellPrice = -1, subType = 1, realName = ""}
end
self.npcHandler.shopItems[itemid].buyPrice = cost
self.npcHandler.shopItems[itemid].realName = realName or getItemNameById(itemid)
self.npcHandler.shopItems[itemid].subType = subType or 1
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 getItemNameById(itemid),
subType = subType or 1
}
keywords = {}
table.insert(keywords, 'buy')
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
-- 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 (getItemNameById 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 getItemNameById(itemid),
subType = subType or 1
}
keywords = {}
table.insert(keywords, 'buy')
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
-- 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 (getItemNameById will be used)
function ShopModule:addSellableItem(names, itemid, cost, realName)
if(SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK) then
if(self.npcHandler.shopItems[itemid] == nil) then
self.npcHandler.shopItems[itemid] = {buyPrice = -1, sellPrice = -1, subType = 1, realName = ""}
end
self.npcHandler.shopItems[itemid].sellPrice = cost
self.npcHandler.shopItems[itemid].realName = realName or getItemNameById(itemid)
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 getItemNameById(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)
if(self.npcHandler.shopItems[itemid] == nil) then
error("[ShopModule.onBuy]", "items[itemid] == nil")
return false
end
if(self.npcHandler.shopItems[itemid].buyPrice == -1) then
error("[ShopModule.onSell]", "Attempt to buy a non-buyable item")
return false
end
local backpack = 1988
local totalCost = amount * self.npcHandler.shopItems[itemid].buyPrice
if(inBackpacks) then
totalCost = 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] = self.npcHandler.shopItems[itemid].realName
}
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 = self.npcHandler.shopItems[itemid].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)
if(NPCHANDLER_CONVBEHAVIOR ~= CONVERSATION_DEFAULT) then
self.npcHandler.talkStart[cid] = os.time()
else
self.npcHandler.talkStart = os.time()
end
if(a > 0) then
doPlayerRemoveMoney(cid, ((a * self.npcHandler.shopItems[itemid].buyPrice) + (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)
if(NPCHANDLER_CONVBEHAVIOR ~= CONVERSATION_DEFAULT) then
self.npcHandler.talkStart[cid] = os.time()
else
self.npcHandler.talkStart = os.time()
end
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, ignoreCap, inBackpacks)
if(self.npcHandler.shopItems[itemid] == nil) then
error("[ShopModule.onSell]", "items[itemid] == nil")
return false
end
if(self.npcHandler.shopItems[itemid].sellPrice == -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 * self.npcHandler.shopItems[itemid].sellPrice,
[TAG_ITEMNAME] = self.npcHandler.shopItems[itemid].realName
}
if(subType < 1) then
subType = -1
end
if(doPlayerRemoveItem(cid, itemid, amount, subType)) then
local msg = self.npcHandler:getMessage(MESSAGE_SOLD)
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, msg)
doPlayerAddMoney(cid, amount * self.npcHandler.shopItems[itemid].sellPrice)
if(NPCHANDLER_CONVBEHAVIOR ~= CONVERSATION_DEFAULT) then
self.npcHandler.talkStart[cid] = os.time()
else
self.npcHandler.talkStart = os.time()
end
return true
else
local msg = self.npcHandler:getMessage(MESSAGE_NEEDITEM)
msg = self.npcHandler:parseMessage(msg, parseInfo)
doPlayerSendCancel(cid, msg)
if(NPCHANDLER_CONVBEHAVIOR ~= CONVERSATION_DEFAULT) then
self.npcHandler.talkStart[cid] = os.time()
else
self.npcHandler.talkStart = os.time()
end
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
local itemWindow = {}
for itemid, attr in pairs(module.npcHandler.shopItems) do
local item = {id = itemid, buy = attr.buyPrice, sell = attr.sellPrice, subType = attr.subType, name = attr.realName}
table.insert(itemWindow, item)
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)) then
return false
end
local parentParameters = node:getParent():getParameters()
local parseInfo = {
[TAG_PLAYERNAME] = getPlayerName(cid),
[TAG_ITEMCOUNT] = module.amount,
[TAG_TOTALCOST] = parentParameters.cost * module.amount,
[TAG_ITEMNAME] = parentParameters.realName
}
if(parentParameters.eventType == SHOPMODULE_SELL_ITEM) then
local ret = doPlayerSellItem(cid, parentParameters.itemid, module.amount, parentParameters.cost * module.amount)
if(ret) 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(parentParameters.eventType == SHOPMODULE_BUY_ITEM) then
local ret = doPlayerBuyItem(cid, parentParameters.itemid, module.amount, parentParameters.cost * module.amount, parentParameters.subType)
if(ret) then
if parentParameters.itemid == ITEM_PARCEL then
doPlayerBuyItem(cid, ITEM_LABEL, module.amount, 0, parentParameters.subType)
end
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
elseif(parentParameters.eventType == SHOPMODULE_BUY_ITEM_CONTAINER) then
local ret = doPlayerBuyItemContainer(cid, parentParameters.container, parentParameters.itemid, module.amount, parentParameters.cost * module.amount, parentParameters.subType)
if(ret) 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()
return true
end
-- onDecliune 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)) then
return false
end
local parentParameters = node:getParent():getParameters()
local parseInfo = {
[TAG_PLAYERNAME] = getPlayerName(cid),
[TAG_ITEMCOUNT] = module.amount,
[TAG_TOTALCOST] = parentParameters.cost * module.amount,
[TAG_ITEMNAME] = parentParameters.realName
}
local msg = module.npcHandler:parseMessage(module.noText, parseInfo)
module.npcHandler:say(msg, cid)
module.npcHandler:resetNpc()
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
local count = module:getCount(message)
module.amount = count
local parseInfo = {
[TAG_PLAYERNAME] = getPlayerName(cid),
[TAG_ITEMCOUNT] = module.amount,
[TAG_TOTALCOST] = parameters.cost * module.amount,
[TAG_ITEMNAME] = parameters.realName
}
if(parameters.eventType == SHOPMODULE_SELL_ITEM) then
local msg = module.npcHandler:getMessage(MESSAGE_SELL)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
elseif(parameters.eventType == SHOPMODULE_BUY_ITEM) then
local msg = module.npcHandler:getMessage(MESSAGE_BUY)
msg = module.npcHandler:parseMessage(msg, parseInfo)
module.npcHandler:say(msg, cid)
elseif(parameters.eventType == 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