• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

Summ's Shop System [Drag & Buy, many options, V1.0]

Summ

(\/)(;,,;)(\/) Y not?
Staff member
Global Moderator
Joined
Oct 15, 2008
Messages
4,152
Solutions
12
Reaction score
1,107
Location
Germany :O
Summ's Shop System [Drag & Buy, many options, V1.0]

6iw5Q1i.png

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
7XeP2or.png
mgzPLuN.png


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!
XmCCWQ4.png

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:
awesome release, of course i will use it :)
gj summ, always contributing
 
I would love some testers which can give feedback on the mechanics of the system, bugs or improvements.
Thanks guys.
 
This is insanely awesome! (Could you perhaps provide the small shop map? :D)

I'll post again if I end up using this, but even if I don't, this idea is superb.
Awesome work Summ! Keep it up, I love it.

Red
 
Thats one hell of a work, well thought off, keep up the good stuff you always surprise me from new :D
 
Damn, too many codes n lines

I'm too lazy to read and understand but the idea doesn't seem bad to me.
 
Wow! Really nice idea and script! It'd be awesome if you could create a shop where people could add their own items, but that's just a suggestion. :)
 
Someone actually tested it?
 
Include drop items to sell?
You can push the items on the sell list either in one of the shop containers (shop backpacks) or drag it onto one item on the counter and it will be sold.
 
Back
Top