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

OTClient Ingame store, help to setup?

Mjmackan

Mapper ~ Writer
Premium User
Joined
Jul 18, 2009
Messages
1,424
Solutions
15
Reaction score
177
Location
Sweden
Is there any tutorial or help regarding on how to setup the ingame store in otcv8? It feels like I have read most part of the entire internetz and most people that writes about it keeps it very secret, It seems like most part of the otcv8 is already finished, but that i need to create my offers in tfs/datapack? A helping hand would be awesome, any direction or anything. Do TFS 1.3 got support for this matter yet even?
 
Last edited:
Last edited:
Solution
I followed that exact one but nothing happens and I cant for the world figure it out.

Maybe interpret these last steps wrong?
-- 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

I didn't touch anything in hope of the examples set to show up.
 
Did u follow all steps?
Are u using latest tfs 1.3?

Can u explain "nothing happens"?
Does the Shop open?
Whats wrong ?
 
Did u follow all steps?
Are u using latest tfs 1.3?

Can u explain "nothing happens"?
Does the Shop open?
Whats wrong ?
I have a hard time to interpret the last 3 steps, especially this one: "set variables".
Yes, correct.

The script in creaturescripts do print when "/reload creaturescripts", however it wont print anything when clicking the ingame shop button.

The shop opens, but just as before it opens blank.
1629202211586.png
 
mmmmhmm all this tasty top secret information I likey 👍
Hungry The Office GIF
 
Hello, did someone manage to make the part of the outfit sale work? I can't complete the purchase

ererttttt.png
Post automatically merged:

Hello, did someone manage to make the part of the outfit sale work? I can't complete the purchase

ererttttt.png
I managed to make it work but the dress does not give me any solution or help?
 
Last edited:
I have added the source code, and add the json.lua file to core lib, the tables from shop.lua to my database, enabled shop.lua in creaturescripts
Im still facing this problem, can somebody please point me where the problem is? i use tfs 1.5
ERROR: Unable to send extended opcode 201, extended opcodes are not enabled on this server.
 
I have added the source code, and add the json.lua file to core lib, the tables from shop.lua to my database, enabled shop.lua in creaturescripts
Im still facing this problem, can somebody please point me where the problem is? i use tfs 1.5
ERROR: Unable to send extended opcode 201, extended opcodes are not enabled on this server.
Did you solve that?

I'm facing same problem...
I have added extended opcode to my server but when I log on otclient I got

ERROR: Unable to send extended opcode 201, extended opcodes are not enabled on this server.
ERROR: ProtocolGame parse message exception (1789 bytes, 1766 unread, last opcode is 0x0a (10), prev opcode is 0x32 (50)): InputMessage eof reached
Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772)
 
I'm still facing problems while trying to buy the outfit, the points are being removed but I'm no receiving outfit
has somebody figured this out? can you share how you did it?
Lua:
function defaultOutfitBuyAction(player, offer)
  local outfit = offer.outfit
  local addons = offer.addons or 0
 
  if player:addOutfit(outfit, addons) then
    return true
  else
    return "Failed to add outfit. Please try again later."
  end

with both codes occurs the same:
Code:
function defaultOutfitBuyAction(player, offer)
  -
 if player:addOutfit(offer["outfit"], offer["count"], false) then
   return true
 end
  return "default outfit buy action is not implemented"
end

this were the previous code:
Code:
function defaultOutfitBuyAction(player, offer)
  return "default outfit buy action is not implemented"
end
 
Any sollution? Im having the same issue when try buy mount
1692451496413.png

And thats my functions:

Code:
category3.addOutfit(75, {
        mount=1,
        feet=0,
        legs=0,
        body=0,
        type=368,
        auxType=0,
        addons=0,
        head=0,
        rotating=true
    }, "Widow Queen", "")


Lua:
function defaultOutfitBuyAction(player, offer)
  return "default outfit buy action is not implemented"
end
 
Any sollution? Im having the same issue when try buy mount
View attachment 77675

And thats my functions:

Code:
category3.addOutfit(75, {
        mount=1,
        feet=0,
        legs=0,
        body=0,
        type=368,
        auxType=0,
        addons=0,
        head=0,
        rotating=true
    }, "Widow Queen", "")


Lua:
function defaultOutfitBuyAction(player, offer)
  return "default outfit buy action is not implemented"
end
use this json file
Lua:
--
-- json.lua
--
-- Copyright (c) 2018 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--

json = { _version = "0.1.1" }

-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------

local encode

local escape_char_map = {
  [ "\\" ] = "\\\\",
  [ "\"" ] = "\\\"",
  [ "\b" ] = "\\b",
  [ "\f" ] = "\\f",
  [ "\n" ] = "\\n",
  [ "\r" ] = "\\r",
  [ "\t" ] = "\\t",
}

local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
  escape_char_map_inv[v] = k
end


local function escape_char(c)
  return escape_char_map[c] or string.format("\\u%04x", c:byte())
end


local function encode_nil(val)
  return "null"
end


local function encode_table(val, stack)
  local res = {}
  stack = stack or {}

  -- Circular reference?
  if stack[val] then error("circular reference") end

  stack[val] = true

  if val[1] ~= nil or next(val) == nil then
    -- Treat as array -- check keys are valid and it is not sparse
    local n = 0
    for k in pairs(val) do
      if type(k) ~= "number" then
        error("invalid table: mixed or invalid key types")
      end
      n = n + 1
    end
    if n ~= #val then
      error("invalid table: sparse array")
    end
    -- Encode
    for i, v in ipairs(val) do
      table.insert(res, encode(v, stack))
    end
    stack[val] = nil
    return "[" .. table.concat(res, ",") .. "]"

  else
    -- Treat as an object
    for k, v in pairs(val) do
      if type(k) ~= "string" then
        error("invalid table: mixed or invalid key types")
      end
      table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
    end
    stack[val] = nil
    return "{" .. table.concat(res, ",") .. "}"
  end
end


local function encode_string(val)
  return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end


local function encode_number(val)
  -- Check for NaN, -inf and inf
  if val ~= val or val <= -math.huge or val >= math.huge then
    error("unexpected number value '" .. tostring(val) .. "'")
  end
  return string.format("%.14g", val)
end


local type_func_map = {
  [ "nil"     ] = encode_nil,
  [ "table"   ] = encode_table,
  [ "string"  ] = encode_string,
  [ "number"  ] = encode_number,
  [ "boolean" ] = tostring,
}


encode = function(val, stack)
  local t = type(val)
  local f = type_func_map[t]
  if f then
    return f(val, stack)
  end
  error("unexpected type '" .. t .. "'")
end


function json.encode(val)
  return ( encode(val) )
end


-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------

local parse

local function create_set(...)
  local res = {}
  for i = 1, select("#", ...) do
    res[ select(i, ...) ] = true
  end
  return res
end

local space_chars   = create_set(" ", "\t", "\r", "\n")
local delim_chars   = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars  = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals      = create_set("true", "false", "null")

local literal_map = {
  [ "true"  ] = true,
  [ "false" ] = false,
  [ "null"  ] = nil,
}


local function next_char(str, idx, set, negate)
  for i = idx, #str do
    if set[str:sub(i, i)] ~= negate then
      return i
    end
  end
  return #str + 1
end


local function decode_error(str, idx, msg)
  local line_count = 1
  local col_count = 1
  for i = 1, idx - 1 do
    col_count = col_count + 1
    if str:sub(i, i) == "\n" then
      line_count = line_count + 1
      col_count = 1
    end
  end
  error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end


local function codepoint_to_utf8(n)
  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
  local f = math.floor
  if n <= 0x7f then
    return string.char(n)
  elseif n <= 0x7ff then
    return string.char(f(n / 64) + 192, n % 64 + 128)
  elseif n <= 0xffff then
    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
  elseif n <= 0x10ffff then
    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
                       f(n % 4096 / 64) + 128, n % 64 + 128)
  end
  error( string.format("invalid unicode codepoint '%x'", n) )
end


local function parse_unicode_escape(s)
  local n1 = tonumber( s:sub(3, 6),  16 )
  local n2 = tonumber( s:sub(9, 12), 16 )
  -- Surrogate pair?
  if n2 then
    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
  else
    return codepoint_to_utf8(n1)
  end
end


local function parse_string(str, i)
  local has_unicode_escape = false
  local has_surrogate_escape = false
  local has_escape = false
  local last
  for j = i + 1, #str do
    local x = str:byte(j)

    if x < 32 then
      decode_error(str, j, "control character in string")
    end

    if last == 92 then -- "\\" (escape char)
      if x == 117 then -- "u" (unicode escape sequence)
        local hex = str:sub(j + 1, j + 5)
        if not hex:find("%x%x%x%x") then
          decode_error(str, j, "invalid unicode escape in string")
        end
        if hex:find("^[dD][89aAbB]") then
          has_surrogate_escape = true
        else
          has_unicode_escape = true
        end
      else
        local c = string.char(x)
        if not escape_chars[c] then
          decode_error(str, j, "invalid escape char '" .. c .. "' in string")
        end
        has_escape = true
      end
      last = nil

    elseif x == 34 then -- '"' (end of string)
      local s = str:sub(i + 1, j - 1)
      if has_surrogate_escape then
        s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
      end
      if has_unicode_escape then
        s = s:gsub("\\u....", parse_unicode_escape)
      end
      if has_escape then
        s = s:gsub("\\.", escape_char_map_inv)
      end
      return s, j + 1

    else
      last = x
    end
  end
  decode_error(str, i, "expected closing quote for string")
end


local function parse_number(str, i)
  local x = next_char(str, i, delim_chars)
  local s = str:sub(i, x - 1)
  local n = tonumber(s)
  if not n then
    decode_error(str, i, "invalid number '" .. s .. "'")
  end
  return n, x
end


local function parse_literal(str, i)
  local x = next_char(str, i, delim_chars)
  local word = str:sub(i, x - 1)
  if not literals[word] then
    decode_error(str, i, "invalid literal '" .. word .. "'")
  end
  return literal_map[word], x
end


local function parse_array(str, i)
  local res = {}
  local n = 1
  i = i + 1
  while 1 do
    local x
    i = next_char(str, i, space_chars, true)
    -- Empty / end of array?
    if str:sub(i, i) == "]" then
      i = i + 1
      break
    end
    -- Read token
    x, i = parse(str, i)
    res[n] = x
    n = n + 1
    -- Next token
    i = next_char(str, i, space_chars, true)
    local chr = str:sub(i, i)
    i = i + 1
    if chr == "]" then break end
    if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
  end
  return res, i
end


local function parse_object(str, i)
  local res = {}
  i = i + 1
  while 1 do
    local key, val
    i = next_char(str, i, space_chars, true)
    -- Empty / end of object?
    if str:sub(i, i) == "}" then
      i = i + 1
      break
    end
    -- Read key
    if str:sub(i, i) ~= '"' then
      decode_error(str, i, "expected string for key")
    end
    key, i = parse(str, i)
    -- Read ':' delimiter
    i = next_char(str, i, space_chars, true)
    if str:sub(i, i) ~= ":" then
      decode_error(str, i, "expected ':' after key")
    end
    i = next_char(str, i + 1, space_chars, true)
    -- Read value
    val, i = parse(str, i)
    -- Set
    res[key] = val
    -- Next token
    i = next_char(str, i, space_chars, true)
    local chr = str:sub(i, i)
    i = i + 1
    if chr == "}" then break end
    if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
  end
  return res, i
end


local char_func_map = {
  [ '"' ] = parse_string,
  [ "0" ] = parse_number,
  [ "1" ] = parse_number,
  [ "2" ] = parse_number,
  [ "3" ] = parse_number,
  [ "4" ] = parse_number,
  [ "5" ] = parse_number,
  [ "6" ] = parse_number,
  [ "7" ] = parse_number,
  [ "8" ] = parse_number,
  [ "9" ] = parse_number,
  [ "-" ] = parse_number,
  [ "t" ] = parse_literal,
  [ "f" ] = parse_literal,
  [ "n" ] = parse_literal,
  [ "[" ] = parse_array,
  [ "{" ] = parse_object,
}


parse = function(str, idx)
  local chr = str:sub(idx, idx)
  local f = char_func_map[chr]
  if f then
    return f(str, idx)
  end
  decode_error(str, idx, "unexpected character '" .. chr .. "'")
end


function json.decode(str)
  if type(str) ~= "string" then
    error("expected argument of type string, got " .. type(str))
  end
  local res, idx = parse(str, next_char(str, 1, space_chars, true))
  idx = next_char(str, idx, space_chars, true)
  if idx <= #str then
    decode_error(str, idx, "trailing garbage")
  end
  return res
end

and this shop
Code:
-- BETA VERSION, net tested yet  --- https://otland.net/threads/storage-outfit.285556/page-2#post-2729258  de donde saque el script
 --- https://github.com/GalaxyCLDev/TFS-1.3/blob/main/shopwithoutfit.lua  <<< aqui hay mas code para el shop
-- 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://otland.net" -- can be empty
local SHOP_AD = { -- can be nil
  image = "https://s3.envato.com/files/62273611/PNG%20Blue/Banner%20blue%20468x60.png",
  url = "http://************",
  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 premiumCategory = addCategory({
    type="item",
    item=ItemType(28378):getClientId(),
    name="Usuarios Premium/Vip/Item"
  })
   --premiumCategory.addItem(10, 3043, 100, "Crystal Coins", "Enoy Test Server!.")
   premiumCategory.addItem(1000, 28385, 1, "x 1 Tiro de Ruleta", "Suerte!!")
   premiumCategory.addItem(3500, 28385, 5, "x 5 Tiro de Ruleta", "Suerte!!")
   premiumCategory.addItem(5000, 10135, 1, "Cuenta VIP x10", "Te costara 3500 puntos por 10 dias")
   premiumCategory.addItem(7500, 10134, 1, "Cuenta VIP x30", "Te costara 5500 puntos por 30 dias")
   premiumCategory.addItem(10000, 10133, 1, "Cuenta VIP x90", "Te costara 8000 puntos por 90 dias")
   premiumCategory.addItem(10000, 12328, 1, "Recupera tu Estamina", "Te costara 17000 puntos por 1 Recarga de estamina")
   premiumCategory.addItem(25000, 28405, 1, "Experiencia X2", "Te costara 25000 puntos por 1 Doble Experiencia (DURACION 2 HRS)")

  local category1 = addCategory({
    type="item",
    item=ItemType(2160):getClientId(),
    count=100,
    name="Items"
  })
local category2 = addCategory({
    type = "outfit",
    name = " News Outfits ",
    outfit = {
      type = 128, -- This type is for displaying an image for the store.

      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, 2160, 1, "1 Crystal coin", "description of cristal coin")
  category1.addItem(5, 2160, 5, "5 Crystal coin", "description of cristal coin")
  category1.addItem(50, 2160, 50, "50 Crystal coin", "description of cristal coin")
  category1.addItem(90, 2160, 100, "100 Crystal coin", "description of cristal coin")
  category1.addItem(200, 2493, 1, "Demon helmet1", "woo\ndemon helmet\nnice, you should buy it")
  category1.addItem(1, 2160, 1, "1 Crystal coin1", "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(100, {
storage = 535927,
    mount = 0,
    feet = 114,
    legs = 114,
    body = 116,
    type = 155, ---This 'type' is just to show image
    lookType = { 155, 151 }, --female e male
    auxType = 0,
    addons = 0,
    head = 2,
    rotating = true
}, "Nordic Chieftain", "Esta é a sua nova roupa legal. Você pode comprá-la aqui")
category2.addOutfit(9, {
storage = 535925,
    mount = 0,
    feet = 114,
    legs = 114,
    body = 116,
    type = 156, ---This 'type' is just to show image
    lookType = { 156, 152 }, --female e male
    auxType = 0,
    addons = 0,
    head = 2,
    rotating = true
}, "Nordic Chieftain", "Esta é a sua nova roupa legal. Você pode comprá-la aqui")
    
    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 defaultPremiumBuyAction(player, offer)
  if player:getPremiumDays() + offer["count"] > 360 then
    return "No puedes comprar mas de 1 año premium"
  else
    player:addPremiumDays(offer["count"])
  return true
  end
return true
end

function defaultOutfitBuyAction(player, offer)
    local outfit = offer['outfit']
   local outfitId = player:addOutfit(outfit["type"], outfit["id"], outfit["addons"], outfit["mount"])
    if player:getStorageValue(outfit['storage']) > 0 then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You already own this outfit!")
        return false
    end

    local points = getPoints(player)
    if offer['cost'] > points or points < 1 then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You don't have enough points")
        return false
    end

    db.query("UPDATE `accounts` SET `premium_points` = `premium_points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())

    -- Add the outfit based on player's sex
    local sex = player:getSex()
    local lookType = outfit.lookType[sex + 1]
    local addons = outfit.addons

    player:addOutfit(lookType, addons)
    player:sendTextMessage(MESSAGE_INFO_DESCR, "You already bought this outfit!")

    player:setStorageValue(outfit['storage'], 1)

    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 true
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
 
Any sollution? Im having the same issue when try buy mount
View attachment 77675

And thats my functions:

Code:
category3.addOutfit(75, {
        mount=1,
        feet=0,
        legs=0,
        body=0,
        type=368,
        auxType=0,
        addons=0,
        head=0,
        rotating=true
    }, "Widow Queen", "")


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