• 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!

Lua In Game Store OT Client v8 and TFS 1.3

therrax

Member
Joined
Jul 12, 2012
Messages
262
Solutions
1
Reaction score
11
Hello

TFS 1.3 - OTClient V8

I want to test in game store, but when I set up TFS, default categories with products are not loading. Am I missing something?

In TFS I added:

In creaturescripts.xml
XML:
 <event type="extendedopcode" name="Shop" script="shop.lua" />
And added shop.lua

In login.lua
Lua:
player:registerEvent("Shop")

Added sql table shop_history

Copied json.lua to folder data/lib/core

Then in core.lua added
Lua:
dofile('data/lib/core/json.lua')


For any help, I will be grateful :)
 
i added all of the commits except for this one
Lua:
static constexpr auto CLIENT_VERSION_MAX = 1098;
static constexpr auto CLIENT_VERSION_STR = "10.98";
static constexpr auto CLIENT_VERSION_MAX = 1099;
static constexpr auto CLIENT_VERSION_STR = "10.99";
because my server is 8.6 and i have this error
Code:
ERROR: Unable to send extended opcode 201, extended opcodes are not enabled on this server.
can you help me? i did all the steps that Therrax did. i use tfs 1.5
 
i added all of the commits except for this one
Lua:
static constexpr auto CLIENT_VERSION_MAX = 1098;
static constexpr auto CLIENT_VERSION_STR = "10.98";
static constexpr auto CLIENT_VERSION_MAX = 1099;
static constexpr auto CLIENT_VERSION_STR = "10.99";
because my server is 8.6 and i have this error
Code:
ERROR: Unable to send extended opcode 201, extended opcodes are not enabled on this server.
can you help me? i did all the steps that Therrax did. i use tfs 1.5
Here
Code:
ERROR: Unable to send extended opcode 201, extended opcodes are not enabled on this server.
 
Do you guys have the callback function to add players outfit?

wondering if you mind sharing?

Lua:
function defaultOutfitBuyAction(player, offer)
  return "default outfit buy action is not implemented"
end
 
Do you guys have the callback function to add players outfit?

wondering if you mind sharing?

Lua:
function defaultOutfitBuyAction(player, offer)
  return "default outfit buy action is not implemented"
end
use mine is working.
Lua:
-- BETA VERSION, net tested yet
-- Instruction:
-- creaturescripts.xml      <event type="extendedopcode" name="Shop" script="shop.lua" />
-- and in login.lua         player:registerEvent("Shop")
-- create sql table shop_history
-- set variables
-- set up function init(), add there items and categories, follow examples
-- set up callbacks at the bottom to add player item/outfit/whatever you want

local SHOP_EXTENDED_OPCODE = 201
local SHOP_OFFERS = {}
local SHOP_CALLBACKS = {}
local SHOP_CATEGORIES = nil
local SHOP_BUY_URL = "http://127.0.0.1/buypoints.php" -- can be empty
local SHOP_AD = { -- can be nil
--  image = "https://s3.envato.com/files/62273611/PNG%20Blue/Banner%20blue%20468x60.png",
image = "",
  url = "http://127.0.0.1./config.php",
  text = ""
}
local MAX_PACKET_SIZE = 50000

--[[ SQL TABLE
CREATE TABLE `shop_history` (
  `id` int(11) NOT NULL,
  `account` int(11) NOT NULL,
  `player` int(11) NOT NULL,
  `date` datetime NOT NULL,
  `title` varchar(100) NOT NULL,
  `cost` int(11) NOT NULL,
  `details` varchar(500) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `shop_history`
  ADD PRIMARY KEY (`id`);
ALTER TABLE `shop_history`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
]]--

function init()
  --  print(json.encode(g_game.getLocalPlayer():getOutfit())) -- in console in otclient, will print current outfit and mount
 
  SHOP_CATEGORIES = {}

  local category1 = addCategory({
    type="item",
    item=ItemType(2160):getClientId(),
    count=100,
    name="Items"
  })
  local category2 = addCategory({
    type="outfit",
    name="Outfits & Mounts",
    outfit={
        mount=521,
        feet=114,
        legs=114,
        body=116,
        type=143,
        auxType=0,
        addons=3,
        head=2,
        rotating=false
    }
  })
  local category3 = addCategory({
    type="image",
    image="http://************/images/137.png",
    name="Category with http image"
  })
  local category4 = addCategory({
    type="image",
    image="/data/images/game/states/electrified.png",
    name="Category with local image"
  })
 
   category1.addItem(1, 5093, 1, "A Black Magic Helmet", "Description")
  category1.addItem(1, 5094, 1, "A Black Magic Amor", "Description")
  category1.addItem(1, 5095, 5, "A Black Magic Legs", "description of cristal coin")
  category1.addItem(1, 5096, 50, "Star Armor", "description of cristal coin")
  category1.addItem(1, 5155, 100, "Month of premium", "description of cristal coin")
  category1.addItem(1, 5154, 1, "15 days of premium", "woo\ndemon helmet\nnice, you should buy it")
  category1.addItem(1, 5153, 1, "7 days of premium", "description of cristal coin")
  category1.addItem(5, 2160, 5, "5 Crystal coin1", "description of cristal coin")
  category1.addItem(50, 2160, 50, "50 Crystal coin1", "description of cristal coin")
  category1.addItem(90, 2160, 100, "100 Crystal coin1", "description of cristal coin")
  category1.addItem(200, 2493, 1, "Demon helmet2", "woo\ndemon helmet\nnice, you should buy it")
  category1.addItem(1, 2160, 1, "1 Crystal coin3", "description of cristal coin")
  category1.addItem(5, 2160, 5, "5 Crystal coin3", "description of cristal coin")
  category1.addItem(50, 2160, 50, "50 Crystal coin3", "description of cristal coin")
  category1.addItem(90, 2160, 100, "100 Crystal coin3", "description of cristal coin")
  category1.addItem(200, 2493, 1, "Demon helmet3", "wooxD\ndemon helmet\nnice, you should buy it")
 
  category2.addOutfit(500, {
        mount=0,
        feet=114,
        legs=114,
        body=116,
        type=620,
        auxType=0,
        addons=3,
        head=2,
        rotating=false
    }, "Winged Outfit", "this is your new cool outfit. You can buy it here.\nsrlsy")
    category2.addOutfit(500, {
        mount=0,
        feet=114,
        legs=114,
        body=116,
        type=629,
        auxType=0,
        addons=3,
        head=2,
        rotating=false
    }, "Golden Outfit", "this is your new cool outfit. You can buy it here.\nsrlsy")
    category2.addOutfit(100, {
        --mount=682,
        mount=510,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
        category2.addOutfit(100, {
        --mount=682,
        mount=511,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
  
        category2.addOutfit(100, {
        --mount=682,
        mount=512,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
  
        category2.addOutfit(100, {
        --mount=682,
        mount=513,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
  
        category2.addOutfit(100, {
        --mount=682,
        mount=514,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
  
        category2.addOutfit(100, {
        --mount=682,
        mount=515,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
  
        category2.addOutfit(100, {
        --mount=682,
        mount=516,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
  
        category2.addOutfit(10, {
        --mount=682,
        mount=517,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
  
        category2.addOutfit(100, {
        --mount=682,
        mount=518,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
  
        category2.addOutfit(100, {
        --mount=682,
        mount=519,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
  
        category2.addOutfit(100, {
        --mount=682,
        mount=520,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")

         category2.addOutfit(100, {
        --mount=682,
        mount=521,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
  
  
    category2.addOutfit(100, {
        mount=0,
        feet=0,
        legs=0,
        body=0,
        type=35,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "Demon outfit", "Want be a demon?\nNo problem")
    category2.addOutfit(100, {
        mount=0,
        feet=0,
        legs=0,
        body=0,
        type=35,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "Demon outfit2", "This one is not rotating")
  
    category4.addImage(10000, "/data/images/game/states/haste.png", "Offer with local image", "another local image\n/data/images/game/states/haste.png")
    category4.addImage(10000, "http://************/images/freezing.png", "Offer with remote image and custom buy action", "blalasdasd image\nhttp://************/images/freezing.png", customImageBuyAction)
end

function addCategory(data)
  data['offers'] = {}
  table.insert(SHOP_CATEGORIES, data)
  table.insert(SHOP_CALLBACKS, {})
  local index = #SHOP_CATEGORIES
  return {
    addItem = function(cost, itemId, count, title, description, callback)   
      if not callback then
        callback = defaultItemBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="item",
        item=ItemType(itemId):getClientId(), -- displayed
        itemId=itemId,
        count=count,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addOutfit = function(cost, outfit, title, description, callback)
      if not callback then
        callback = defaultOutfitBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="outfit",
        outfit=outfit,
        title=title,
        description=description
      }) 
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addImage = function(cost, image, title, description, callback)
      if not callback then
        callback = defaultImageBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="image",
        image=image,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end
  }
end

function getPoints(player)
  local points = 0
  local resultId = db.storeQuery("SELECT `premium_points` FROM `accounts` WHERE `id` = " .. player:getAccountId())
  if resultId ~= false then
    points = result.getDataInt(resultId, "premium_points")
    result.free(resultId)
  end
  return points
end

function getStatus(player)
  local status = {
    ad = SHOP_AD,
    points = getPoints(player),
    buyUrl = SHOP_BUY_URL
  }
  return status
end

function sendJSON(player, action, data, forceStatus)
  local status = nil
  if not player:getStorageValue(1150001) or player:getStorageValue(1150001) + 10 < os.time() or forceStatus then
      status = getStatus(player)
  end
  player:setStorageValue(1150001, os.time())
 

  local buffer = json.encode({action = action, data = data, status = status})
  local s = {}
  for i=1, #buffer, MAX_PACKET_SIZE do
     s[#s+1] = buffer:sub(i,i+MAX_PACKET_SIZE - 1)
  end
  local msg = NetworkMessage()
  if #s == 1 then
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString(s[1])
    msg:sendToPlayer(player)
    return
  end
  -- split message if too big
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("S" .. s[1])
  msg:sendToPlayer(player)
  for i=2,#s - 1 do
    msg = NetworkMessage()
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString("P" .. s[i])
    msg:sendToPlayer(player)
  end
  msg = NetworkMessage()
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("E" .. s[#s])
  msg:sendToPlayer(player)
end

function sendMessage(player, title, msg, forceStatus)
  sendJSON(player, "message", {title=title, msg=msg}, forceStatus)
end

function onExtendedOpcode(player, opcode, buffer)
  if opcode ~= SHOP_EXTENDED_OPCODE then
    return false
  end
  local status, json_data = pcall(function() return json.decode(buffer) end)
  if not status then
    return false
  end

  local action = json_data['action']
  local data = json_data['data']
  if not action or not data then
    return false
  end

  if SHOP_CATEGORIES == nil then
    init() 
  end

  if action == 'init' then
    sendJSON(player, "categories", SHOP_CATEGORIES)
  elseif action == 'buy' then
    processBuy(player, data)
  elseif action == "history" then
    sendHistory(player)
  end
  return true
end

function processBuy(player, data)
  local categoryId = tonumber(data["category"])
  local offerId = tonumber(data["offer"])
  local offer = SHOP_CATEGORIES[categoryId]['offers'][offerId]
  local callback = SHOP_CALLBACKS[categoryId][offerId]
  if not offer or not callback or data["title"] ~= offer["title"] or data["cost"] ~= offer["cost"] then
    sendJSON(player, "categories", SHOP_CATEGORIES) -- refresh categories, maybe invalid
    return sendMessage(player, "Error!", "Invalid offer")   
  end
  local points = getPoints(player)
  if not offer['cost'] or offer['cost'] > points or points < 1 then
    return sendMessage(player, "Error!", "You don't have enough points to buy " .. offer['title'] .."!", true) 
  end
  local status = callback(player, offer)
  if status == true then 
    db.query("UPDATE `accounts` set `premium_points` = `premium_points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())
    db.asyncQuery("INSERT INTO `shop_history` (`account`, `player`, `date`, `title`, `cost`, `details`) VALUES ('" .. player:getAccountId() .. "', '" .. player:getGuid() .. "', NOW(), " .. db.escapeString(offer['title']) .. ", " .. db.escapeString(offer['cost']) .. ", " .. db.escapeString(json.encode(offer)) .. ")")
    return sendMessage(player, "Success!", "You bought " .. offer['title'] .."!", true)
  end
  if status == nil or status == false then
    status = "Unknown error while buying " .. offer['title']
  end
  sendMessage(player, "Error!", status)
end

function sendHistory(player)
  if player:getStorageValue(1150002) and player:getStorageValue(1150002) + 10 > os.time() then
    return -- min 10s delay
  end
  player:setStorageValue(1150002, os.time())
 
  local history = {}
  local resultId = db.storeQuery("SELECT * FROM `shop_history` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")

  if resultId ~= false then
    repeat
      local details = result.getDataString(resultId, "details")
      local status, json_data = pcall(function() return json.decode(details) end)
      if not status then 
        json_data = {
          type = "image",
          title = result.getDataString(resultId, "title"),
          cost = result.getDataInt(resultId, "cost")
        }
      end
      table.insert(history, json_data)
      history[#history]["description"] = "Bought on " .. result.getDataString(resultId, "date") .. " for " .. result.getDataInt(resultId, "cost") .. " points."
    until not result.next(resultId)
    result.free(resultId)
  end
 
  sendJSON(player, "history", history)
end

-- BUY CALLBACKS
-- May be useful: print(json.encode(offer))

function defaultItemBuyAction(player, offer)
  -- todo: check if has capacity
  if player:addItem(offer["itemId"], offer["count"], false) then
    return true
  end
  return "Can't add item! Do you have enough space?"
end

function defaultOutfitBuyAction(player, offer)
  return "default outfit buy action is not implemented"
end

function defaultImageBuyAction(player, offer)
  return "default image buy action is not implemented"
end

function customImageBuyAction(player, offer)
  return "custom image buy action is not implemented. Offer: " .. offer['title']
end
do not forget to register shop at login.lua and in creaturescripts.xml
 
use mine is working.
Lua:
-- BETA VERSION, net tested yet
-- Instruction:
-- creaturescripts.xml      <event type="extendedopcode" name="Shop" script="shop.lua" />
-- and in login.lua         player:registerEvent("Shop")
-- create sql table shop_history
-- set variables
-- set up function init(), add there items and categories, follow examples
-- set up callbacks at the bottom to add player item/outfit/whatever you want

local SHOP_EXTENDED_OPCODE = 201
local SHOP_OFFERS = {}
local SHOP_CALLBACKS = {}
local SHOP_CATEGORIES = nil
local SHOP_BUY_URL = "http://127.0.0.1/buypoints.php" -- can be empty
local SHOP_AD = { -- can be nil
--  image = "https://s3.envato.com/files/62273611/PNG%20Blue/Banner%20blue%20468x60.png",
image = "",
  url = "http://127.0.0.1./config.php",
  text = ""
}
local MAX_PACKET_SIZE = 50000

--[[ SQL TABLE
CREATE TABLE `shop_history` (
  `id` int(11) NOT NULL,
  `account` int(11) NOT NULL,
  `player` int(11) NOT NULL,
  `date` datetime NOT NULL,
  `title` varchar(100) NOT NULL,
  `cost` int(11) NOT NULL,
  `details` varchar(500) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `shop_history`
  ADD PRIMARY KEY (`id`);
ALTER TABLE `shop_history`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
]]--

function init()
  --  print(json.encode(g_game.getLocalPlayer():getOutfit())) -- in console in otclient, will print current outfit and mount
 
  SHOP_CATEGORIES = {}

  local category1 = addCategory({
    type="item",
    item=ItemType(2160):getClientId(),
    count=100,
    name="Items"
  })
  local category2 = addCategory({
    type="outfit",
    name="Outfits & Mounts",
    outfit={
        mount=521,
        feet=114,
        legs=114,
        body=116,
        type=143,
        auxType=0,
        addons=3,
        head=2,
        rotating=false
    }
  })
  local category3 = addCategory({
    type="image",
    image="http://************/images/137.png",
    name="Category with http image"
  })
  local category4 = addCategory({
    type="image",
    image="/data/images/game/states/electrified.png",
    name="Category with local image"
  })
 
   category1.addItem(1, 5093, 1, "A Black Magic Helmet", "Description")
  category1.addItem(1, 5094, 1, "A Black Magic Amor", "Description")
  category1.addItem(1, 5095, 5, "A Black Magic Legs", "description of cristal coin")
  category1.addItem(1, 5096, 50, "Star Armor", "description of cristal coin")
  category1.addItem(1, 5155, 100, "Month of premium", "description of cristal coin")
  category1.addItem(1, 5154, 1, "15 days of premium", "woo\ndemon helmet\nnice, you should buy it")
  category1.addItem(1, 5153, 1, "7 days of premium", "description of cristal coin")
  category1.addItem(5, 2160, 5, "5 Crystal coin1", "description of cristal coin")
  category1.addItem(50, 2160, 50, "50 Crystal coin1", "description of cristal coin")
  category1.addItem(90, 2160, 100, "100 Crystal coin1", "description of cristal coin")
  category1.addItem(200, 2493, 1, "Demon helmet2", "woo\ndemon helmet\nnice, you should buy it")
  category1.addItem(1, 2160, 1, "1 Crystal coin3", "description of cristal coin")
  category1.addItem(5, 2160, 5, "5 Crystal coin3", "description of cristal coin")
  category1.addItem(50, 2160, 50, "50 Crystal coin3", "description of cristal coin")
  category1.addItem(90, 2160, 100, "100 Crystal coin3", "description of cristal coin")
  category1.addItem(200, 2493, 1, "Demon helmet3", "wooxD\ndemon helmet\nnice, you should buy it")
 
  category2.addOutfit(500, {
        mount=0,
        feet=114,
        legs=114,
        body=116,
        type=620,
        auxType=0,
        addons=3,
        head=2,
        rotating=false
    }, "Winged Outfit", "this is your new cool outfit. You can buy it here.\nsrlsy")
    category2.addOutfit(500, {
        mount=0,
        feet=114,
        legs=114,
        body=116,
        type=629,
        auxType=0,
        addons=3,
        head=2,
        rotating=false
    }, "Golden Outfit", "this is your new cool outfit. You can buy it here.\nsrlsy")
    category2.addOutfit(100, {
        --mount=682,
        mount=510,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
        category2.addOutfit(100, {
        --mount=682,
        mount=511,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=512,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=513,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=514,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=515,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=516,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(10, {
        --mount=682,
        mount=517,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=518,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=519,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=520,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")

         category2.addOutfit(100, {
        --mount=682,
        mount=521,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
 
    category2.addOutfit(100, {
        mount=0,
        feet=0,
        legs=0,
        body=0,
        type=35,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "Demon outfit", "Want be a demon?\nNo problem")
    category2.addOutfit(100, {
        mount=0,
        feet=0,
        legs=0,
        body=0,
        type=35,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "Demon outfit2", "This one is not rotating")
 
    category4.addImage(10000, "/data/images/game/states/haste.png", "Offer with local image", "another local image\n/data/images/game/states/haste.png")
    category4.addImage(10000, "http://************/images/freezing.png", "Offer with remote image and custom buy action", "blalasdasd image\nhttp://************/images/freezing.png", customImageBuyAction)
end

function addCategory(data)
  data['offers'] = {}
  table.insert(SHOP_CATEGORIES, data)
  table.insert(SHOP_CALLBACKS, {})
  local index = #SHOP_CATEGORIES
  return {
    addItem = function(cost, itemId, count, title, description, callback) 
      if not callback then
        callback = defaultItemBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="item",
        item=ItemType(itemId):getClientId(), -- displayed
        itemId=itemId,
        count=count,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addOutfit = function(cost, outfit, title, description, callback)
      if not callback then
        callback = defaultOutfitBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="outfit",
        outfit=outfit,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addImage = function(cost, image, title, description, callback)
      if not callback then
        callback = defaultImageBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="image",
        image=image,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end
  }
end

function getPoints(player)
  local points = 0
  local resultId = db.storeQuery("SELECT `premium_points` FROM `accounts` WHERE `id` = " .. player:getAccountId())
  if resultId ~= false then
    points = result.getDataInt(resultId, "premium_points")
    result.free(resultId)
  end
  return points
end

function getStatus(player)
  local status = {
    ad = SHOP_AD,
    points = getPoints(player),
    buyUrl = SHOP_BUY_URL
  }
  return status
end

function sendJSON(player, action, data, forceStatus)
  local status = nil
  if not player:getStorageValue(1150001) or player:getStorageValue(1150001) + 10 < os.time() or forceStatus then
      status = getStatus(player)
  end
  player:setStorageValue(1150001, os.time())
 

  local buffer = json.encode({action = action, data = data, status = status})
  local s = {}
  for i=1, #buffer, MAX_PACKET_SIZE do
     s[#s+1] = buffer:sub(i,i+MAX_PACKET_SIZE - 1)
  end
  local msg = NetworkMessage()
  if #s == 1 then
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString(s[1])
    msg:sendToPlayer(player)
    return
  end
  -- split message if too big
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("S" .. s[1])
  msg:sendToPlayer(player)
  for i=2,#s - 1 do
    msg = NetworkMessage()
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString("P" .. s[i])
    msg:sendToPlayer(player)
  end
  msg = NetworkMessage()
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("E" .. s[#s])
  msg:sendToPlayer(player)
end

function sendMessage(player, title, msg, forceStatus)
  sendJSON(player, "message", {title=title, msg=msg}, forceStatus)
end

function onExtendedOpcode(player, opcode, buffer)
  if opcode ~= SHOP_EXTENDED_OPCODE then
    return false
  end
  local status, json_data = pcall(function() return json.decode(buffer) end)
  if not status then
    return false
  end

  local action = json_data['action']
  local data = json_data['data']
  if not action or not data then
    return false
  end

  if SHOP_CATEGORIES == nil then
    init()
  end

  if action == 'init' then
    sendJSON(player, "categories", SHOP_CATEGORIES)
  elseif action == 'buy' then
    processBuy(player, data)
  elseif action == "history" then
    sendHistory(player)
  end
  return true
end

function processBuy(player, data)
  local categoryId = tonumber(data["category"])
  local offerId = tonumber(data["offer"])
  local offer = SHOP_CATEGORIES[categoryId]['offers'][offerId]
  local callback = SHOP_CALLBACKS[categoryId][offerId]
  if not offer or not callback or data["title"] ~= offer["title"] or data["cost"] ~= offer["cost"] then
    sendJSON(player, "categories", SHOP_CATEGORIES) -- refresh categories, maybe invalid
    return sendMessage(player, "Error!", "Invalid offer") 
  end
  local points = getPoints(player)
  if not offer['cost'] or offer['cost'] > points or points < 1 then
    return sendMessage(player, "Error!", "You don't have enough points to buy " .. offer['title'] .."!", true)
  end
  local status = callback(player, offer)
  if status == true then
    db.query("UPDATE `accounts` set `premium_points` = `premium_points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())
    db.asyncQuery("INSERT INTO `shop_history` (`account`, `player`, `date`, `title`, `cost`, `details`) VALUES ('" .. player:getAccountId() .. "', '" .. player:getGuid() .. "', NOW(), " .. db.escapeString(offer['title']) .. ", " .. db.escapeString(offer['cost']) .. ", " .. db.escapeString(json.encode(offer)) .. ")")
    return sendMessage(player, "Success!", "You bought " .. offer['title'] .."!", true)
  end
  if status == nil or status == false then
    status = "Unknown error while buying " .. offer['title']
  end
  sendMessage(player, "Error!", status)
end

function sendHistory(player)
  if player:getStorageValue(1150002) and player:getStorageValue(1150002) + 10 > os.time() then
    return -- min 10s delay
  end
  player:setStorageValue(1150002, os.time())
 
  local history = {}
  local resultId = db.storeQuery("SELECT * FROM `shop_history` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")

  if resultId ~= false then
    repeat
      local details = result.getDataString(resultId, "details")
      local status, json_data = pcall(function() return json.decode(details) end)
      if not status then
        json_data = {
          type = "image",
          title = result.getDataString(resultId, "title"),
          cost = result.getDataInt(resultId, "cost")
        }
      end
      table.insert(history, json_data)
      history[#history]["description"] = "Bought on " .. result.getDataString(resultId, "date") .. " for " .. result.getDataInt(resultId, "cost") .. " points."
    until not result.next(resultId)
    result.free(resultId)
  end
 
  sendJSON(player, "history", history)
end

-- BUY CALLBACKS
-- May be useful: print(json.encode(offer))

function defaultItemBuyAction(player, offer)
  -- todo: check if has capacity
  if player:addItem(offer["itemId"], offer["count"], false) then
    return true
  end
  return "Can't add item! Do you have enough space?"
end

function defaultOutfitBuyAction(player, offer)
  return "default outfit buy action is not implemented"
end

function defaultImageBuyAction(player, offer)
  return "default image buy action is not implemented"
end

function customImageBuyAction(player, offer)
  return "custom image buy action is not implemented. Offer: " .. offer['title']
end
do not forget to register shop at login.lua and in creaturescripts.xml
It's giving the outfit? I'm getting default outfit buy action error.. still testing thanks. we seem to have same code
 
any chance to share it?
I've tried by many ways without getting into it

Lua:
function defaultOutfitBuyAction(player, offer) {
  let category2 = addCategory(data);
  if (offer.type == 288 && offer.category == "DemonHunter") {
    player.inventory.add(offer.type, offer.count);
    player.data.spent = player.data.spent + offer.count * offer.price;
    return true
  end
  return false
  end
 
use mine is working.
Lua:
-- BETA VERSION, net tested yet
-- Instruction:
-- creaturescripts.xml      <event type="extendedopcode" name="Shop" script="shop.lua" />
-- and in login.lua         player:registerEvent("Shop")
-- create sql table shop_history
-- set variables
-- set up function init(), add there items and categories, follow examples
-- set up callbacks at the bottom to add player item/outfit/whatever you want

local SHOP_EXTENDED_OPCODE = 201
local SHOP_OFFERS = {}
local SHOP_CALLBACKS = {}
local SHOP_CATEGORIES = nil
local SHOP_BUY_URL = "http://127.0.0.1/buypoints.php" -- can be empty
local SHOP_AD = { -- can be nil
--  image = "https://s3.envato.com/files/62273611/PNG%20Blue/Banner%20blue%20468x60.png",
image = "",
  url = "http://127.0.0.1./config.php",
  text = ""
}
local MAX_PACKET_SIZE = 50000

--[[ SQL TABLE
CREATE TABLE `shop_history` (
  `id` int(11) NOT NULL,
  `account` int(11) NOT NULL,
  `player` int(11) NOT NULL,
  `date` datetime NOT NULL,
  `title` varchar(100) NOT NULL,
  `cost` int(11) NOT NULL,
  `details` varchar(500) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `shop_history`
  ADD PRIMARY KEY (`id`);
ALTER TABLE `shop_history`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
]]--

function init()
  --  print(json.encode(g_game.getLocalPlayer():getOutfit())) -- in console in otclient, will print current outfit and mount
 
  SHOP_CATEGORIES = {}

  local category1 = addCategory({
    type="item",
    item=ItemType(2160):getClientId(),
    count=100,
    name="Items"
  })
  local category2 = addCategory({
    type="outfit",
    name="Outfits & Mounts",
    outfit={
        mount=521,
        feet=114,
        legs=114,
        body=116,
        type=143,
        auxType=0,
        addons=3,
        head=2,
        rotating=false
    }
  })
  local category3 = addCategory({
    type="image",
    image="http://************/images/137.png",
    name="Category with http image"
  })
  local category4 = addCategory({
    type="image",
    image="/data/images/game/states/electrified.png",
    name="Category with local image"
  })
 
   category1.addItem(1, 5093, 1, "A Black Magic Helmet", "Description")
  category1.addItem(1, 5094, 1, "A Black Magic Amor", "Description")
  category1.addItem(1, 5095, 5, "A Black Magic Legs", "description of cristal coin")
  category1.addItem(1, 5096, 50, "Star Armor", "description of cristal coin")
  category1.addItem(1, 5155, 100, "Month of premium", "description of cristal coin")
  category1.addItem(1, 5154, 1, "15 days of premium", "woo\ndemon helmet\nnice, you should buy it")
  category1.addItem(1, 5153, 1, "7 days of premium", "description of cristal coin")
  category1.addItem(5, 2160, 5, "5 Crystal coin1", "description of cristal coin")
  category1.addItem(50, 2160, 50, "50 Crystal coin1", "description of cristal coin")
  category1.addItem(90, 2160, 100, "100 Crystal coin1", "description of cristal coin")
  category1.addItem(200, 2493, 1, "Demon helmet2", "woo\ndemon helmet\nnice, you should buy it")
  category1.addItem(1, 2160, 1, "1 Crystal coin3", "description of cristal coin")
  category1.addItem(5, 2160, 5, "5 Crystal coin3", "description of cristal coin")
  category1.addItem(50, 2160, 50, "50 Crystal coin3", "description of cristal coin")
  category1.addItem(90, 2160, 100, "100 Crystal coin3", "description of cristal coin")
  category1.addItem(200, 2493, 1, "Demon helmet3", "wooxD\ndemon helmet\nnice, you should buy it")
 
  category2.addOutfit(500, {
        mount=0,
        feet=114,
        legs=114,
        body=116,
        type=620,
        auxType=0,
        addons=3,
        head=2,
        rotating=false
    }, "Winged Outfit", "this is your new cool outfit. You can buy it here.\nsrlsy")
    category2.addOutfit(500, {
        mount=0,
        feet=114,
        legs=114,
        body=116,
        type=629,
        auxType=0,
        addons=3,
        head=2,
        rotating=false
    }, "Golden Outfit", "this is your new cool outfit. You can buy it here.\nsrlsy")
    category2.addOutfit(100, {
        --mount=682,
        mount=510,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
        category2.addOutfit(100, {
        --mount=682,
        mount=511,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=512,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=513,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=514,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=515,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=516,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(10, {
        --mount=682,
        mount=517,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=518,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=519,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
        category2.addOutfit(100, {
        --mount=682,
        mount=520,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")

         category2.addOutfit(100, {
        --mount=682,
        mount=521,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
 
 
    category2.addOutfit(100, {
        mount=0,
        feet=0,
        legs=0,
        body=0,
        type=35,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "Demon outfit", "Want be a demon?\nNo problem")
    category2.addOutfit(100, {
        mount=0,
        feet=0,
        legs=0,
        body=0,
        type=35,
        auxType=0,
        addons=0,
        head=0,
        rotating=false
    }, "Demon outfit2", "This one is not rotating")
 
    category4.addImage(10000, "/data/images/game/states/haste.png", "Offer with local image", "another local image\n/data/images/game/states/haste.png")
    category4.addImage(10000, "http://************/images/freezing.png", "Offer with remote image and custom buy action", "blalasdasd image\nhttp://************/images/freezing.png", customImageBuyAction)
end

function addCategory(data)
  data['offers'] = {}
  table.insert(SHOP_CATEGORIES, data)
  table.insert(SHOP_CALLBACKS, {})
  local index = #SHOP_CATEGORIES
  return {
    addItem = function(cost, itemId, count, title, description, callback)  
      if not callback then
        callback = defaultItemBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="item",
        item=ItemType(itemId):getClientId(), -- displayed
        itemId=itemId,
        count=count,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addOutfit = function(cost, outfit, title, description, callback)
      if not callback then
        callback = defaultOutfitBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="outfit",
        outfit=outfit,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addImage = function(cost, image, title, description, callback)
      if not callback then
        callback = defaultImageBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="image",
        image=image,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end
  }
end

function getPoints(player)
  local points = 0
  local resultId = db.storeQuery("SELECT `premium_points` FROM `accounts` WHERE `id` = " .. player:getAccountId())
  if resultId ~= false then
    points = result.getDataInt(resultId, "premium_points")
    result.free(resultId)
  end
  return points
end

function getStatus(player)
  local status = {
    ad = SHOP_AD,
    points = getPoints(player),
    buyUrl = SHOP_BUY_URL
  }
  return status
end

function sendJSON(player, action, data, forceStatus)
  local status = nil
  if not player:getStorageValue(1150001) or player:getStorageValue(1150001) + 10 < os.time() or forceStatus then
      status = getStatus(player)
  end
  player:setStorageValue(1150001, os.time())
 

  local buffer = json.encode({action = action, data = data, status = status})
  local s = {}
  for i=1, #buffer, MAX_PACKET_SIZE do
     s[#s+1] = buffer:sub(i,i+MAX_PACKET_SIZE - 1)
  end
  local msg = NetworkMessage()
  if #s == 1 then
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString(s[1])
    msg:sendToPlayer(player)
    return
  end
  -- split message if too big
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("S" .. s[1])
  msg:sendToPlayer(player)
  for i=2,#s - 1 do
    msg = NetworkMessage()
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString("P" .. s[i])
    msg:sendToPlayer(player)
  end
  msg = NetworkMessage()
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("E" .. s[#s])
  msg:sendToPlayer(player)
end

function sendMessage(player, title, msg, forceStatus)
  sendJSON(player, "message", {title=title, msg=msg}, forceStatus)
end

function onExtendedOpcode(player, opcode, buffer)
  if opcode ~= SHOP_EXTENDED_OPCODE then
    return false
  end
  local status, json_data = pcall(function() return json.decode(buffer) end)
  if not status then
    return false
  end

  local action = json_data['action']
  local data = json_data['data']
  if not action or not data then
    return false
  end

  if SHOP_CATEGORIES == nil then
    init()
  end

  if action == 'init' then
    sendJSON(player, "categories", SHOP_CATEGORIES)
  elseif action == 'buy' then
    processBuy(player, data)
  elseif action == "history" then
    sendHistory(player)
  end
  return true
end

function processBuy(player, data)
  local categoryId = tonumber(data["category"])
  local offerId = tonumber(data["offer"])
  local offer = SHOP_CATEGORIES[categoryId]['offers'][offerId]
  local callback = SHOP_CALLBACKS[categoryId][offerId]
  if not offer or not callback or data["title"] ~= offer["title"] or data["cost"] ~= offer["cost"] then
    sendJSON(player, "categories", SHOP_CATEGORIES) -- refresh categories, maybe invalid
    return sendMessage(player, "Error!", "Invalid offer")  
  end
  local points = getPoints(player)
  if not offer['cost'] or offer['cost'] > points or points < 1 then
    return sendMessage(player, "Error!", "You don't have enough points to buy " .. offer['title'] .."!", true)
  end
  local status = callback(player, offer)
  if status == true then
    db.query("UPDATE `accounts` set `premium_points` = `premium_points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())
    db.asyncQuery("INSERT INTO `shop_history` (`account`, `player`, `date`, `title`, `cost`, `details`) VALUES ('" .. player:getAccountId() .. "', '" .. player:getGuid() .. "', NOW(), " .. db.escapeString(offer['title']) .. ", " .. db.escapeString(offer['cost']) .. ", " .. db.escapeString(json.encode(offer)) .. ")")
    return sendMessage(player, "Success!", "You bought " .. offer['title'] .."!", true)
  end
  if status == nil or status == false then
    status = "Unknown error while buying " .. offer['title']
  end
  sendMessage(player, "Error!", status)
end

function sendHistory(player)
  if player:getStorageValue(1150002) and player:getStorageValue(1150002) + 10 > os.time() then
    return -- min 10s delay
  end
  player:setStorageValue(1150002, os.time())
 
  local history = {}
  local resultId = db.storeQuery("SELECT * FROM `shop_history` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")

  if resultId ~= false then
    repeat
      local details = result.getDataString(resultId, "details")
      local status, json_data = pcall(function() return json.decode(details) end)
      if not status then
        json_data = {
          type = "image",
          title = result.getDataString(resultId, "title"),
          cost = result.getDataInt(resultId, "cost")
        }
      end
      table.insert(history, json_data)
      history[#history]["description"] = "Bought on " .. result.getDataString(resultId, "date") .. " for " .. result.getDataInt(resultId, "cost") .. " points."
    until not result.next(resultId)
    result.free(resultId)
  end
 
  sendJSON(player, "history", history)
end

-- BUY CALLBACKS
-- May be useful: print(json.encode(offer))

function defaultItemBuyAction(player, offer)
  -- todo: check if has capacity
  if player:addItem(offer["itemId"], offer["count"], false) then
    return true
  end
  return "Can't add item! Do you have enough space?"
end

function defaultOutfitBuyAction(player, offer)
  return "default outfit buy action is not implemented"
end

function defaultImageBuyAction(player, offer)
  return "default image buy action is not implemented"
end

function customImageBuyAction(player, offer)
  return "custom image buy action is not implemented. Offer: " .. offer['title']
end
do not forget to register shop at login.lua and in creaturescripts.xml
can i change the id of the clothes whenever i want? and still put some items too? tell me what is points? how to send points to players? Is it possible to change points for another currency that I want to put? thanks
 
Back
Top