Summ's Shop System [Drag & Buy, many options, V1.0]
Requirements:
• onMoveItem: http://otland.net/f35/creatureevent-onmoveitem-cid-item-count-tocontainer-fromcontainer-185781/
• Source Edits: Scroll down to the end of the post
• Following scripts
[Tested on TFS 0.4 r5512]
How does it work:
You can set up a shop with items just laying on counters/tables. The player can the look at the item to see it's price.
The item can be dragged into the player's mainbackpack. Either the player will directly pay the item then or he can put everything he needs in his backpack and pay when he is leaving the store or pay the bill at the NPC. (Everything depending on the settings.)
Items can be sold by just dragging them onto the counters with items on them or by dragging them into a shopcontainer.
If you have any questions feel free to ask. Also view the image below.
If you are using a NPC the player can view the bill like this:
hi - bill - (Is asked if he wants to pay it now) - Yes/No
Scripts:
In data/lib create shopsystemLib.lua and add:
In data\creaturescripts\creaturescripts.xml add:
In data\creaturescripts\scripts\login.lua add:
Create data\creaturescripts\scripts\shopsystem.lua and add:
In data\movements\movements.xml add:
Create data\movements\scripts\moveShopsystem.lua and add:
Create data\npc\Alva.xml and add:
Create data\npc\scripts\shop_default.lua and add:
Setup:
Config:
shopContainer = 65530, --> This is the actionid you have to set for shop containers
instantBuy = false, --> If this is true you will buy the items instantly when dragging, otherwise you can pay @ leaving or @ npc
buyItemsOnExit = true, --> If true you will automatically pay items when leaving the shop, otherwise you have to use the NPC
canLogoutInShop = true, --> If true you can logout in shops otherwise you cannot
removeIfNoMoney = true,--> If the player has not enough money and this is true all items he selected will be removed when trying to leave, otherwise he will be pushed back (can't leave shop)
npcMessage = true, --> If true the npc of the shop will send "private" messages to the player on different events.
First you have to map a show like in the image below.
You add counters etc and put all items you want to be for sale on them, if you have several small or similar items you can also put them in a container/backpack (like the moonbackpack).
NOTE: Set the "count" for stackable items to 100!
Now set a shopid (the example shop uses 65520).
If you want to create a new shop you would set all items marked light red to 65521.
If you have a container storing items you also have to set the items inside to 65521, the CONTAINER itself has to be set to the shopContainer ID you set in config.
Now regarding the entrance and exit you set the orange tiles to actionid 65521 aswell, for the exit (darkred) you subtract 1000 from the id, so the exit tiles would have actionid 64521!
NOTE: It is important to set all tiles on which the player can enter and leave the shop to the right id, there may not be any way to leave the shop without passing such a tile.
If you are using NPCs you also have to copy the NPC files now and create a new one. In the new script file you can edit the text and make sure to set local shop = 65520 to the correct shop id for the new NPC.
Setting up the shop:
In the shopsystemLib.lua file you also have to add the new shop in shops table under the id 65521.
You can set a new name for the shop and the correct NPC name (shop owner) also you can change the NPC responses.
The offers are structered the same for buying and selling.
[ITEMID] = BUY/SELL PRICE,
Also you have to set an unused storageid for storageBuy = xxxxxx,. DO NOT use the shopid, you can use shopid - 1000 here too.
Source Edits:
configmanager.cpp
At the end of:
[cpp]bool ConfigManager::load()[/cpp]
Add:
[cpp] m_confNumber[MIN_SHOP_ID] = getGlobalNumber("startShopsId", 0);
m_confNumber[MAX_SHOP_ID] = getGlobalNumber("endShopsId", 0);[/cpp]
configmanager.h
Before:
[cpp] LAST_NUMBER_CONFIG /* this must be the last one */[/cpp]
Add:
[cpp] MIN_SHOP_ID,
MAX_SHOP_ID,[/cpp]
actions.cpp
After:
[cpp]ReturnValue Actions::canUse(const Player* player, const Position& pos, const Item* item)
{[/cpp]
Add:
[cpp]
const int32_t actionId = item->getActionId();
if(actionId != 0 && actionId >= g_config.getNumber(ConfigManager::MIN_SHOP_ID) && actionId <= g_config.getNumber(ConfigManager::MAX_SHOP_ID))
return RET_CANNOTUSETHISOBJECT; [/cpp]
game.cpp
After:
[cpp]Item* updateItem = NULL;[/cpp]
Add:
[cpp] int32_t _aid = item->getActionId();
boost::any _shop = item->getAttribute("shop"), _price = item->getAttribute("price");[/cpp]
Change:
[cpp]if(toItem && toItem->getID() == item->getID())[/cpp]
To:
[cpp]if(toItem && toItem->getID() == item->getID() && _aid == toItem->getActionId())[/cpp]
After:
[cpp] if(moveItem)
toCylinder->__addThing(actor, index, moveItem);
[/cpp]
Add:
[cpp] if(moveItem) {
moveItem->setActionId(_aid, false);
moveItem->setAttribute("shop", _shop);
moveItem->setAttribute("price", _price);
}[/cpp]
player.cpp
Change:
[cpp]else if(inventoryItem->isStackable() && item->getID() == inventoryItem->getID() && inventoryItem->getItemCount() < 100)[/cpp]
To:
[cpp]else if(inventoryItem->isStackable() && item->getID() == inventoryItem->getID() && inventoryItem->getItemCount() < 100 && item->getActionId() == inventoryItem->getActionId())[/cpp]
Change:
[cpp]if(destItem->isStackable() && item->getID() == destItem->getID() && destItem->getItemCount() < 100)[/cpp]
To:
[cpp]if(destItem->isStackable() && item->getID() == destItem->getID() && destItem->getItemCount() < 100 && item->getActionId() == destItem->getActionId())[/cpp]
Change:
[cpp]&& tmpItem->getID() == item->getID() && tmpItem->getItemCount() < 100)[/cpp]
To:
[cpp]&& tmpItem->getID() == item->getID() && tmpItem->getItemCount() < 100 && tmpItem->getActionId() == item->getActionId())[/cpp]
container.cpp
Change:
[cpp]if((*cit) != item && (*cit)->getID() == item->getID() && (*cit)->getItemCount() < 100)[/cpp]
To:
[cpp]if((*cit) != item && (*cit)->getID() == item->getID() && (*cit)->getItemCount() < 100 && (*cit)->getActionId() == item->getActionId())[/cpp]
Change:
[cpp]if(destItem && destItem->getID() == item->getID() && destItem->getItemCount() < 100)[/cpp]
To:
[cpp]if(destItem && destItem->getID() == item->getID() && destItem->getItemCount() < 100 && destItem->getActionId() == item->getActionId())[/cpp]
Change:
[cpp]if((*cit)->getID() == item->getID() && (*cit)->getItemCount() < 100)[/cpp]
To:
[cpp]if((*cit)->getID() == item->getID() && (*cit)->getItemCount() < 100 && (*cit)->getActionId() == item->getActionId())[/cpp]
Requirements:
• onMoveItem: http://otland.net/f35/creatureevent-onmoveitem-cid-item-count-tocontainer-fromcontainer-185781/
• Source Edits: Scroll down to the end of the post
• Following scripts
[Tested on TFS 0.4 r5512]
How does it work:
You can set up a shop with items just laying on counters/tables. The player can the look at the item to see it's price.
The item can be dragged into the player's mainbackpack. Either the player will directly pay the item then or he can put everything he needs in his backpack and pay when he is leaving the store or pay the bill at the NPC. (Everything depending on the settings.)
Items can be sold by just dragging them onto the counters with items on them or by dragging them into a shopcontainer.
If you have any questions feel free to ask. Also view the image below.
If you are using a NPC the player can view the bill like this:
hi - bill - (Is asked if he wants to pay it now) - Yes/No
Scripts:
In data/lib create shopsystemLib.lua and add:
Lua:
shops = {
[65520] = { -- Example Shop
name = 'Alva\'s Magic Store',
offerBuy = {
[2268] = 120, -- sudden death rune
[2273] = 80, -- ultimate healing rune
[2269] = 100, -- wild growth rune
[2183] = 15000, -- hailstorm rod
[2187] = 15000, -- wand of inferno
[7620] = 50, -- mana potion
[7589] = 80, -- strong mana potion
[7590] = 120, -- great mana potion
[7618] = 45, -- health potion
[7588] = 100, -- strong health potion
[7591] = 190, -- great health potion
[8473] = 300, -- ultimate health potion
[8472] = 190 -- great spirit potion
},
offerSell = {
[2390] = 10000,
[2391] = 8000,
[8472] = 30
},
storageBuy = 64520,
npcName = "Alva",
npcMessages = {
[1] = "Thanks for your purchase, |PLAYER|!", -- When paying
[2] = "Come back when you got the money..", -- Not enough money
[3] = "See you next time!", -- When leaving
[4] = "Hey! Come back and pay for the stuff.",-- When buyItemsOnExit = false and leaving
[5] = "Welcome to |NAME|'s magic store.", -- When entering
-- onBuy
[6] = "An excellent choice!",
[7] = "You will like it.",
[8] = "Only the best quality!"
}
}
}
shopsConfig = {
shopContainer = 65530,
instantBuy = false,
buyItemsOnExit = true,
canLogoutInShop = true,
removeIfNoMoney = true,
npcMessage = true,
npcExhaust = 3
}
function doPlayerSendShopMessage(cid, shop, id, shared)
if not(shopsConfig.npcMessage) then
return true
end
if not(shops[shop]) then
return true
end
local exhaust = getCreatureStorage(cid, shops[shop].npcName .. (shared or id))
if exhaust > os.time() then
return true
end
local gsubs = {
["|PLAYER|"] = getCreatureName(cid),
["|NAME|"] = shops[shop].npcName
}
local npc = getCreatureByName(shops[shop].npcName)
if isCreature(npc) then
local message = shops[shop].npcMessages[id]
for k, v in pairs(gsubs) do
message = message:gsub(k, v)
end
doCreatureSay(npc, message, TALKTYPE_PRIVATE_FROM, false, cid)
setPlayerStorageValue(cid, shops[shop].npcName .. (shared or id), os.time() + shopsConfig.npcExhaust)
end
end
function string.wordUpper(str)
local result, first = '', true
for word in string.gmatch(str, "%a+") do
result = result .. (first and "" or " ") .. word:sub(1, 1):upper() .. word:sub(2):lower()
first = false
end
return result
end
function getTopContainer(uid)
local ret = uid
local tmpParent = getItemParent(ret)
while (type(tmpParent) == "boolean" and tmpParent or type(tmpParent) == "table" and tmpParent.uid > 0 and not(isCreature(tmpParent.uid))) do
ret = tmpParent.uid
tmpParent = getItemParent(ret)
end
return ret
end
function getShopActivity(cid, shop)
if not shop then
return 0
end
return getPlayerStorageValue(cid, shops[shop].storageBuy)
end
function addShopActivity(cid, shop, count)
local value = math.max(0, getShopActivity(cid, shop) + count)
setPlayerStorageValue(cid, shops[shop].storageBuy, value)
return value
end
function resetShopActivity(cid, shop)
setPlayerStorageValue(cid, shops[shop].storageBuy, 0)
end
function isInShop(cid, shop)
if tonumber(getPlayerStorageValue(cid, shop)) >= 1 then
return true
end
return false
end
function getActiveShops(cid)
local ret = {}
for shop, _ in pairs(shops) do
if isInShop(cid, shop) then
table.insert(ret, shop)
end
end
return ret
end
function getActiveShop(cid)
return getActiveShops(cid)[1]
end
function isShopOffer(item)
if shops[item.actionid] then
return true
end
return false
end
function isShopUnpaid(item)
local attr = getItemAttribute(item.uid, "shop")
if not attr then
return false
end
if shops[attr] then
return true
end
return false
end
function isShopItem(item)
return isShopUnpaid(item) or isShopOffer(item)
end
function getAllContainerItems(uid)
local containers, items = {uid}, {}
while #containers > 0 do
for k = (getContainerSize(containers[1]) - 1), 0, -1 do
local tmp = getContainerItem(containers[1], k)
if not(isContainer(tmp.uid)) then
table.insert(items, tmp)
else
table.insert(containers, tmp.uid)
end
end
table.remove(containers, 1)
end
return items
end
function doPlayerRemoveShopItems(cid)
if not(getPlayerSlotItem(cid, CONST_SLOT_BACKPACK).uid > 0) then return true end
local items = getAllContainerItems(getPlayerSlotItem(cid, CONST_SLOT_BACKPACK).uid)
for i = 1, #items do
if isShopUnpaid(items[i]) then
doRemoveItem(items[i].uid)
end
end
end
function getPlayerBill(cid)
local backpack = getPlayerSlotItem(cid, CONST_SLOT_BACKPACK)
if not(backpack.uid > 0) or not(isContainer(backpack.uid)) then
return nil
end
local items, ret, totalCost, totalItems = getAllContainerItems(backpack.uid), {}, 0, 0
for i = 1, #items do
if isShopUnpaid(items[i]) then
if ret[getItemNameById(items[i].itemid)] then
ret[getItemNameById(items[i].itemid)] = ret[getItemNameById(items[i].itemid)] + math.max(1, items[i].type)
else
ret[getItemNameById(items[i].itemid)] = math.max(1, items[i].type)
end
totalItems = totalItems + math.max(1, items[i].type)
totalCost = totalCost + getItemAttribute(items[i].uid, "price") * math.max(1, items[i].type)
end
end
if totalItems == 0 then
return nil
end
return ret, totalCost, totalItems
end
RETURN_NOITEMS = 0
RETURN_SUCCESS = 1
RETURN_NOMONEY = 2
function buyShopItems(cid)
local backpack = getPlayerSlotItem(cid, CONST_SLOT_BACKPACK)
if not(backpack.uid > 0) or not(isContainer(backpack.uid)) then
return RETURN_NOITEMS, 0
end
local items, shopItems, totalCost = getAllContainerItems(backpack.uid), {}, 0
for i = 1, #items do
if isShopUnpaid(items[i]) then
table.insert(shopItems, items[i])
totalCost = totalCost + getItemAttribute(items[i].uid, "price") * math.max(1, items[i].type)
end
end
if #shopItems == 0 then
return RETURN_NOITEMS, 0
end
if doPlayerRemoveMoney(cid, totalCost) then
for i = 1, #shopItems do
doItemEraseAttribute(shopItems[i].uid, 'shop')
doItemEraseAttribute(shopItems[i].uid, 'aid')
end
doSendMagicEffect(getThingPos(cid), CONST_ME_GIFT_WRAPS)
return RETURN_SUCCESS, totalCost
else
return RETURN_NOMONEY, totalCost
end
end
In data\creaturescripts\creaturescripts.xml add:
XML:
<event type="logout" name="ShopLog" event="script" value="shopsystem.lua"/>
<event type="moveitem" name="ShopMove" event="script" value="shopsystem.lua"/>
<event type="look" name="ShopLook" event="script" value="shopsystem.lua"/>
<event type="traderequest" name="ShopTrade" event="script" value="shopsystem.lua"/>
In data\creaturescripts\scripts\login.lua add:
Lua:
registerCreatureEvent(cid, "ShopMove")
registerCreatureEvent(cid, "ShopLook")
registerCreatureEvent(cid, "ShopTrade")
Create data\creaturescripts\scripts\shopsystem.lua and add:
Lua:
function onMoveItem(cid, item, count, toContainer, fromContainer, fromPos, toPos)
local shop = getActiveShop(cid)
if not shop then
return true
end
local position = getThingPos(cid)
-- block "shop containers" from being moved
if item.actionid == shopsConfig.shopContainer then
doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTMOVABLE)
return false
-- sell items by moving them into shop containers
elseif (toContainer.actionid == shopsConfig.shopContainer or getThingFromPos({x=toPos.x,y=toPos.y,z=toPos.z,stackpos=255}).actionid == shopsConfig.shopContainer or shops[getThingFromPos({x=toPos.x,y=toPos.y,z=toPos.z,stackpos=255}).actionid]) and not(isShopItem(item)) then
local sellPrice, itemName = shops[shop].offerSell[item.itemid], getItemNameById(item.itemid)
if not sellPrice then
doPlayerSay(cid, string.format("You cannot sell '%s'.", string.wordUpper(itemName)), TALKTYPE_MONSTER, false, cid)
return false
end
if isShopItem(item) then
doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTMOVABLE)
return false
end
doPlayerAddMoney(cid, sellPrice * count)
doRemoveItem(item.uid)
doSendMagicEffect(position, CONST_ME_FIREWORK_YELLOW)
doSendMagicEffect(position, CONST_ME_FIREWORK_RED)
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, string.format("You sold %s '%s' for %d gold.", (count > 1 and count .. "x" or "a"), string.wordUpper(itemName), count * sellPrice))
return false
elseif isShopUnpaid(item) then
-- handle moving unpaid items in backpacks
if toContainer.uid > 0 and getTopContainer(toContainer.uid) == getPlayerSlotItem(cid, CONST_SLOT_BACKPACK).uid then
return true
elseif isContainer(getPlayerSlotItem(cid, CONST_SLOT_BACKPACK).uid) and toPos.x == 65535 and toPos.y == CONST_SLOT_BACKPACK then
return true
-- remove unpaid by putting in shop container
elseif toPos.x == 65535 and toContainer.actionid == shopsConfig.shopContainer then
doPlayerSay(cid, "You removed the item from your buying list.", TALKTYPE_MONSTER, false, cid)
addShopActivity(cid, shop, -count)
return doRemoveItem(item.uid) and false
--remove unpaid by throwing on shop item
elseif getThingFromPos({x=toPos.x,y=toPos.y,z=toPos.z,stackpos=255}).actionid == item.actionid then
doPlayerSay(cid, "You removed the item from your buying list.", TALKTYPE_MONSTER, false, cid)
addShopActivity(cid, shop, -count)
return doRemoveItem(item.uid) and false
end
return doPlayerSay(cid, 'This item is not paid yet.', TALKTYPE_MONSTER, false, cid) and false
-- player tries to block shop with items
elseif toPos.x ~= 65535 and (isShopOffer(getThingFromPos({x=toPos.x,y=toPos.y,z=toPos.z,stackpos=255})) or isInArray({shopsConfig.shopContainer},getThingFromPos({x=toPos.x,y=toPos.y,z=toPos.z,stackpos=255}).actionid)) then
if isShopItem(item) then
return doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) and false
end
return doSendMagicEffect(position, CONST_ME_POFF) and false
-- buying items / adding to buylist
elseif isShopOffer(item) and toPos.x == 65535 then
local itemEx, _toContainer = doCreateItemEx(item.itemid, count), nil
local slotItem = getPlayerSlotItem(cid, toPos.y)
if toPos.y >= CONST_SLOT_FIRST and toPos.y <= CONST_SLOT_LAST and slotItem.uid > 0 and isContainer(slotItem.uid) and toPos.y == CONST_SLOT_BACKPACK then
_toContainer = slotItem.uid
elseif toContainer.uid > 0 and getPlayerSlotItem(cid, CONST_SLOT_BACKPACK).uid == getTopContainer(toContainer.uid) then
local containerSlot = getContainerItem(toContainer.uid, toPos.z)
if containerSlot.uid > 0 and isContainer(containerSlot.uid) then
_toContainer = containerSlot.uid
else
_toContainer = toContainer.uid
end
end
if not _toContainer then
return doPlayerSay(cid, "Drag the item to your mainbackpack to buy it.", TALKTYPE_MONSTER, false, cid) and false
end
local price = shops[item.actionid].offerBuy[item.itemid]
if not price then
return doPlayerSay(cid, "Shop: Item has no sell price. Report it to the Admin.", TALKTYPE_MONSTER, false, cid) and false
end
if shopsConfig.instantBuy then
if getPlayerMoney(cid) >= price * count then
local ret = doAddContainerItemEx(_toContainer, itemEx)
if ret ~= RETURNVALUE_NOERROR then
doPlayerSendDefaultCancel(cid, ret)
return false
end
doPlayerRemoveMoney(cid, price * count)
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, string.format("You bought %s '%s' for %d gold.", (count > 1 and count .. "x" or "a"), string.wordUpper(getItemNameById(item.itemid)), price * count))
else
doPlayerSendCancel(cid, string.format("You do not have enough gold. [%d gold]", price * count))
end
else
doItemSetAttribute(itemEx, "aid", item.actionid)
doItemSetAttribute(itemEx, "shop", item.actionid)
doItemSetAttribute(itemEx, "price", price)
local ret = doAddContainerItemEx(_toContainer, itemEx)
if ret ~= RETURNVALUE_NOERROR then
doPlayerSendDefaultCancel(cid, ret)
return false
end
addShopActivity(cid, item.actionid, count)
end
doPlayerSendShopMessage(cid, getActiveShop(cid), math.random(6, 8), 100)
return false
-- dragging shopitem to ground
elseif isShopOffer(item) then
doPlayerSay(cid, 'Drag the item to your mainbackpack to buy it.', TALKTYPE_MONSTER, false, cid)
return doSendMagicEffect(position, CONST_ME_POFF) and false
-- trying to drop mainbackpack with shopitems
elseif isContainer(item.uid) and getShopActivity(cid, getActiveShop(cid)) > 0 and (not(toContainer.uid > 0) or getTopContainer(toContainer.uid) ~= getPlayerSlotItem(cid, CONST_SLOT_BACKPACK).uid) then
if item.uid == getPlayerSlotItem(cid, CONST_SLOT_BACKPACK).uid or fromContainer.uid > 0 and getTopContainer(fromContainer.uid) == getPlayerSlotItem(cid, CONST_SLOT_BACKPACK).uid then
doPlayerSay(cid, 'There might still be unpaid items in there.', TALKTYPE_MONSTER, false, cid)
return doSendMagicEffect(position, CONST_ME_POFF) and false
end
end
return true
end
function onLogout(cid)
local shop = getActiveShop(cid)
if not shop then
return true
end
if not shopsConfig.canLogoutInShop then
return doPlayerSendCancel(cid, "You cannot logout in this shop.") and false
end
if getPlayerSlotItem(cid, CONST_SLOT_BACKPACK).uid > 0 then
doPlayerRemoveShopItems(cid)
end
resetShopActivity(cid, shop)
return true
end
function onLook(cid, thing, position, lookDistance)
if isCreature(thing.uid) then
return true
end
if not isMovable(thing.uid) then
return true
end
if isShopOffer(thing) or isShopUnpaid(thing) then
doPlayerSendTextMessage(cid, MESSAGE_STATUS_SMALL, string.wordUpper(getItemNameById(thing.itemid)) .. ': ' .. shops[thing.actionid].offerBuy[thing.itemid] .. ' gold coins each')
return true
end
local shop = getActiveShop(cid)
if not shop then
return true
end
local sellPrice = shops[shop].offerSell[thing.itemid]
if not sellPrice then
return true
end
doPlayerSendTextMessage(cid, MESSAGE_STATUS_SMALL, "You can sell ".. string.wordUpper(getItemNameById(thing.itemid)) .." for " .. sellPrice .. " gold.")
return true
end
function onTradeRequest(cid, target, item)
local shop = getActiveShop(cid)
if not shop then
return true
end
if isContainer(item.uid) then
local items = getAllContainerItems(item.uid)
for i = 1, #items do
if isShopItem(items[i]) then
return doPlayerSay(cid, "You cannot trade shop items", TALKTYPE_MONSTER, false, cid) and false
end
end
elseif isShopItem(item) then
return doPlayerSay(cid, "You cannot trade shop items", TALKTYPE_MONSTER, false, cid) and false
end
return true
end
In data\movements\movements.xml add:
XML:
<movevent type="StepIn" actionid="65520" event="script" value="moveShopsystem.lua"/>
<movevent type="StepIn" actionid="64520" event="script" value="moveShopsystem.lua"/>
Create data\movements\scripts\moveShopsystem.lua and add:
Lua:
function onStepIn(cid, item, position, lastPosition, fromPosition)
if not(isPlayer(cid)) then
return true
end
-- Exit
if shops[item.actionid + 1000] then
if getShopActivity(cid, item.actionid + 1000) > 0 then
if not(shopsConfig.buyItemsOnExit) then
doTeleportThing(cid, fromPosition)
doSendMagicEffect(fromPosition, CONST_ME_POFF)
doPlayerSendShopMessage(cid, getActiveShop(cid), 4)
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "You have to pay for your items before leaving the shop.")
return true
end
local ret, totalCost = buyShopItems(cid)
if ret ~= RET_NOITEMS then
if ret == RETURN_NOMONEY then
if shopsConfig.removeIfNoMoney then
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "You had not enough money to buy the items.")
doPlayerRemoveShopItems(cid)
else
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, string.format("You need %d gold to pay for all items from the shop.", totalCost))
end
doPlayerSendShopMessage(cid, item.actionid + 1000, 2)
end
if ret == RETURN_SUCCESS then
doPlayerSendShopMessage(cid, item.actionid + 1000, 1)
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, string.format("You bought all items for %d gold.", totalCost))
end
if ret == RETURN_NOMONEY and not(shopsConfig.removeIfNoMoney) then
doTeleportThing(cid, fromPosition)
doSendMagicEffect(fromPosition, CONST_ME_POFF)
return true
end
end
end
resetShopActivity(cid, item.actionid + 1000)
doPlayerSendShopMessage(cid, getActiveShop(cid), 3, 101)
setPlayerStorageValue(cid, item.actionid + 1000, -1)
-- Enter
elseif shops[item.actionid] then
if isInShop(cid, item.actionid)then
return true
end
setPlayerStorageValue(cid, item.actionid, 1)
if not(shopsConfig.npcMessage) then doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, 'Welcome to '.. shops[item.actionid].name) end
doPlayerSendShopMessage(cid, getActiveShop(cid), 5, 101)
end
return true
end
Create data\npc\Alva.xml and add:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<npc name="Alva" nameDescription="Alva" script="shop_default.lua" walkinterval="2000" floorchange="0" skull="green">
<health now="100" max="100"/>
<look type="130" head="39" body="122" legs="125" feet="57" addons="0"/>
<parameters>
<parameter key="message_greet" value="Hello |PLAYERNAME|. Did you come here to pay your {bill} or do you want to know which items I {buy}?"/>
</parameters>
</npc>
Create data\npc\scripts\shop_default.lua and add:
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 onPlayerEndTrade(cid) npcHandler:onPlayerEndTrade(cid) end
function onPlayerCloseChannel(cid) npcHandler:onPlayerCloseChannel(cid) end
local talk = {}
local npcConfig = {
messages = {
"Out of supplies? Buy health and mana potions here!",
"High quality wands and rods for sale."
},
exhaust = {19, 33},
storage = 1000
}
function onThink()
if getCreatureStorage(getNpcCid(), npcConfig.storage) > os.time() then
return true
end
selfSay(npcConfig.messages[math.random(#npcConfig.messages)])
doPlayerSetStorageValue(getNpcCid(), npcConfig.storage, os.time() + math.random(npcConfig.exhaust[1], npcConfig.exhaust[2]))
return true
end
local shop = 65520
function creatureSayCallback(cid, type, msg)
if(not npcHandler:isFocused(cid)) then
return false
end
talk[cid] = talk[cid] or 0
local talkUser = NPCHANDLER_CONVBEHAVIOR == CONVERSATION_DEFAULT and 0 or cid
if talk[cid] == 1 then
if msgcontains(msg, "yes") then
local ret, cost = buyShopItems(cid)
if ret == RETURN_SUCCESS then
selfSay("Thanks you. It was a pleasure!", cid)
resetShopActivity(cid, shop)
elseif ret == RETURN_NOMONEY then
selfSay("Sorry |PLAYERNAME|, but you need " .. cost .. " gold to pay your bill.", cid)
end
else
selfSay("Put the items you do not want back and come to me to pay your {bill}.", cid)
talk[cid] = 0
end
return true
end
if msgcontains(msg, "buy") or msgcontains(msg, "offer") or msgcontains(msg, "trade") then
local offerStr, first = "", true
for id, price in pairs(shops[shop].offerSell) do
offerStr = offerStr .. (first and "" or ", ") .. "{" .. getItemInfo(id).plural .. "}" .. " (" .. price .. " gold)"
first = false
end
offerStr = offerStr:reverse():gsub(",", "dna ", 1):reverse()
selfSay("I buy " .. offerStr .. ". I sell everything you see lying around here.", cid)
elseif not(shopsConfig.buyItemsOnExit) and (msgcontains(msg, "bill") or msgcontains(msg, "pay")) then
local items, cost, count = getPlayerBill(cid)
if not(items) then
selfSay("There are no items on your bill. Happy shopping!", cid)
return true
end
local itemsStr = ""
for name, amount in pairs(items) do
itemsStr = itemsStr .. amount .. "x " .. name .. "\n"
end
doShowTextDialog(cid, 1967, string.format("Bill from %s [%s]\nTotal cost: %d gold coins\n\nItem List:\n%s", shops[shop].name, getCreatureName(cid), cost, itemsStr), false, 1000)
selfSay("Have a look at your bill. Do you want to buy these items for " .. cost .. " gold?", cid)
talk[cid] = 1
elseif shopsConfig.buyItemsOnExit and (msgcontains(msg, "bill") or msgcontains(msg, "pay")) then
selfSay("I will cash up when you are leaving my store. No worries!", cid)
end
return true
end
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:setCallback(CALLBACK_GREET, creatureGreetCallback)
npcHandler:addModule(FocusModule:new())
Setup:
Config:
shopContainer = 65530, --> This is the actionid you have to set for shop containers
instantBuy = false, --> If this is true you will buy the items instantly when dragging, otherwise you can pay @ leaving or @ npc
buyItemsOnExit = true, --> If true you will automatically pay items when leaving the shop, otherwise you have to use the NPC
canLogoutInShop = true, --> If true you can logout in shops otherwise you cannot
removeIfNoMoney = true,--> If the player has not enough money and this is true all items he selected will be removed when trying to leave, otherwise he will be pushed back (can't leave shop)
npcMessage = true, --> If true the npc of the shop will send "private" messages to the player on different events.
First you have to map a show like in the image below.
You add counters etc and put all items you want to be for sale on them, if you have several small or similar items you can also put them in a container/backpack (like the moonbackpack).
NOTE: Set the "count" for stackable items to 100!
Now set a shopid (the example shop uses 65520).
If you want to create a new shop you would set all items marked light red to 65521.
If you have a container storing items you also have to set the items inside to 65521, the CONTAINER itself has to be set to the shopContainer ID you set in config.
Now regarding the entrance and exit you set the orange tiles to actionid 65521 aswell, for the exit (darkred) you subtract 1000 from the id, so the exit tiles would have actionid 64521!
NOTE: It is important to set all tiles on which the player can enter and leave the shop to the right id, there may not be any way to leave the shop without passing such a tile.
If you are using NPCs you also have to copy the NPC files now and create a new one. In the new script file you can edit the text and make sure to set local shop = 65520 to the correct shop id for the new NPC.
Setting up the shop:
In the shopsystemLib.lua file you also have to add the new shop in shops table under the id 65521.
You can set a new name for the shop and the correct NPC name (shop owner) also you can change the NPC responses.
The offers are structered the same for buying and selling.
[ITEMID] = BUY/SELL PRICE,
Also you have to set an unused storageid for storageBuy = xxxxxx,. DO NOT use the shopid, you can use shopid - 1000 here too.
Source Edits:
configmanager.cpp
At the end of:
[cpp]bool ConfigManager::load()[/cpp]
Add:
[cpp] m_confNumber[MIN_SHOP_ID] = getGlobalNumber("startShopsId", 0);
m_confNumber[MAX_SHOP_ID] = getGlobalNumber("endShopsId", 0);[/cpp]
configmanager.h
Before:
[cpp] LAST_NUMBER_CONFIG /* this must be the last one */[/cpp]
Add:
[cpp] MIN_SHOP_ID,
MAX_SHOP_ID,[/cpp]
actions.cpp
After:
[cpp]ReturnValue Actions::canUse(const Player* player, const Position& pos, const Item* item)
{[/cpp]
Add:
[cpp]
const int32_t actionId = item->getActionId();
if(actionId != 0 && actionId >= g_config.getNumber(ConfigManager::MIN_SHOP_ID) && actionId <= g_config.getNumber(ConfigManager::MAX_SHOP_ID))
return RET_CANNOTUSETHISOBJECT; [/cpp]
game.cpp
After:
[cpp]Item* updateItem = NULL;[/cpp]
Add:
[cpp] int32_t _aid = item->getActionId();
boost::any _shop = item->getAttribute("shop"), _price = item->getAttribute("price");[/cpp]
Change:
[cpp]if(toItem && toItem->getID() == item->getID())[/cpp]
To:
[cpp]if(toItem && toItem->getID() == item->getID() && _aid == toItem->getActionId())[/cpp]
After:
[cpp] if(moveItem)
toCylinder->__addThing(actor, index, moveItem);
[/cpp]
Add:
[cpp] if(moveItem) {
moveItem->setActionId(_aid, false);
moveItem->setAttribute("shop", _shop);
moveItem->setAttribute("price", _price);
}[/cpp]
player.cpp
Change:
[cpp]else if(inventoryItem->isStackable() && item->getID() == inventoryItem->getID() && inventoryItem->getItemCount() < 100)[/cpp]
To:
[cpp]else if(inventoryItem->isStackable() && item->getID() == inventoryItem->getID() && inventoryItem->getItemCount() < 100 && item->getActionId() == inventoryItem->getActionId())[/cpp]
Change:
[cpp]if(destItem->isStackable() && item->getID() == destItem->getID() && destItem->getItemCount() < 100)[/cpp]
To:
[cpp]if(destItem->isStackable() && item->getID() == destItem->getID() && destItem->getItemCount() < 100 && item->getActionId() == destItem->getActionId())[/cpp]
Change:
[cpp]&& tmpItem->getID() == item->getID() && tmpItem->getItemCount() < 100)[/cpp]
To:
[cpp]&& tmpItem->getID() == item->getID() && tmpItem->getItemCount() < 100 && tmpItem->getActionId() == item->getActionId())[/cpp]
container.cpp
Change:
[cpp]if((*cit) != item && (*cit)->getID() == item->getID() && (*cit)->getItemCount() < 100)[/cpp]
To:
[cpp]if((*cit) != item && (*cit)->getID() == item->getID() && (*cit)->getItemCount() < 100 && (*cit)->getActionId() == item->getActionId())[/cpp]
Change:
[cpp]if(destItem && destItem->getID() == item->getID() && destItem->getItemCount() < 100)[/cpp]
To:
[cpp]if(destItem && destItem->getID() == item->getID() && destItem->getItemCount() < 100 && destItem->getActionId() == item->getActionId())[/cpp]
Change:
[cpp]if((*cit)->getID() == item->getID() && (*cit)->getItemCount() < 100)[/cpp]
To:
[cpp]if((*cit)->getID() == item->getID() && (*cit)->getItemCount() < 100 && (*cit)->getActionId() == item->getActionId())[/cpp]
Last edited: