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

TFS 1.X+ TFS 1.5 - Downgrade Nekiro 7.72 - Add in-game shop OTClientv8

Forkz

Well-Known Member
Joined
Jun 29, 2020
Messages
380
Solutions
1
Reaction score
89
Hi otlanders,
I was trying to put the game store on my server, I'll show the steps below that I did, but after all that I couldn't access the server anymore, it appears as offline.

Source:
Code:
https://github.com/OTCv8/forgottenserver/commit/2839d4d7a8ad3597eff6c786f4ceb9b1b4b4456b#diff-0ac48e116773f94794384faa2005802f970883b286811fcb44057e44b9140c8c

Datapack:
creaturescripts/creaturescripts.xml
XML:
    <event type="extendedopcode" name="GameStore" script="game_store.lua" />
creaturescripts/scripts/login.lua
Lua:
player:registerEvent("GameStore")
creaturescripts/scripts/game_store.lua
Lua:
https://github.com/OTCv8/otcv8-tools/blob/main/server/shop/shop.lua
lib/lib.lua
Lua:
dofile('data/lib/core/json.lua')
lib/core/json.lua
Lua:
https://github.com/OTCv8/otcv8-tools/blob/main/server/json.lua
otclient/modules/game_features/features.lua
Lua:
    if(version >= 770) then
        g_game.enableFeature(GameLooktypeU16)
        g_game.enableFeature(GameMessageStatements)
        g_game.enableFeature(GameLoginPacketEncryption)
        g_game.enableFeature(GameExtendedOpcode)
    end

database create:
Code:
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;

After doing all these steps, I could no longer connect to the game, no error appears, I just have no connection.

Could anyone tell me what happened?
 
I managed to put points on the site I bought and the points appeared in the game thanks... I use TFS 1.5 8.0


in linux it works for me but can't get it working in windows, could you share your script?
I did it step by step that the guy posted about doubts, so I tested it and it worked... see redo the process that will work yes good luck..

store poit.png
Post automatically merged:

Hi otlanders,
I was trying to put the game store on my server, I'll show the steps below that I did, but after all that I couldn't access the server anymore, it appears as offline.

Source:
Code:
https://github.com/OTCv8/forgottenserver/commit/2839d4d7a8ad3597eff6c786f4ceb9b1b4b4456b#diff-0ac48e116773f94794384faa2005802f970883b286811fcb44057e44b9140c8c

Datapack:
creaturescripts/creaturescripts.xml
XML:
    <event type="extendedopcode" name="GameStore" script="game_store.lua" />
creaturescripts/scripts/login.lua
Lua:
player:registerEvent("GameStore")
creaturescripts/scripts/game_store.lua
Lua:
https://github.com/OTCv8/otcv8-tools/blob/main/server/shop/shop.lua
lib/lib.lua
Lua:
dofile('data/lib/core/json.lua')
lib/core/json.lua
Lua:
https://github.com/OTCv8/otcv8-tools/blob/main/server/json.lua
otclient/modules/game_features/features.lua
Lua:
    if(version >= 770) then
        g_game.enableFeature(GameLooktypeU16)
        g_game.enableFeature(GameMessageStatements)
        g_game.enableFeature(GameLoginPacketEncryption)
        g_game.enableFeature(GameExtendedOpcode)
    end

database create:
Code:
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;

After doing all these steps, I could no longer connect to the game, no error appears, I just have no connection.

Could anyone tell me what happened?
the business is RSA, did you put it correctly? it happened that I can no longer enter or connect the server.. I had to redo the process calmly and it worked
 
I managed to put points on the site I bought and the points appeared in the game thanks... I use TFS 1.5 8.0



I did it step by step that the guy posted about doubts, so I tested it and it worked... see redo the process that will work yes good luck..

View attachment 75280
Post automatically merged:


the business is RSA, did you put it correctly? it happened that I can no longer enter or connect the server.. I had to redo the process calmly and it worked
hehe now shop work good in windows, don't know what was the problem but it's solved
Sin título.png
 
I got it here normally you posted it above.. I did it step by step and compiled the source

explain to me how do I put points in this store?
View attachment 75269
what version are you using?
Post automatically merged:

I managed to put points on the site I bought and the points appeared in the game thanks... I use TFS 1.5 8.0



I did it step by step that the guy posted about doubts, so I tested it and it worked... see redo the process that will work yes good luck..

View attachment 75280
Post automatically merged:


the business is RSA, did you put it correctly? it happened that I can no longer enter or connect the server.. I had to redo the process calmly and it worked
maybe that's it, because I can no longer connect to the server, do you change something in rsa?
 
what version are you using?
Post automatically merged:


maybe that's it, because I can no longer connect to the server, do you change something in rsa?
it's probably creaturescripts or something like that or maybe you put it wrong, redo the process calmly bro.. look, I downloaded it and compiled it in source, I did it step by step in tfs 1.5 7.72 it worked ;/

NOTE: GOOGLE IS SHIT LOL772.png
Post automatically merged:

what version are you using?
Post automatically merged:


maybe that's it, because I can no longer connect to the server, do you change something in rsa?
I didn't change anything, I just put it in the right place.
 
Last edited:
TFS 1.5 DOWNGREADES NEKIRO 8.0
I did the step by step, but the shop does not recognize the points that I have on the site, and when I click on the outfits tab the client date and I get this error in the exe:
[Error - mysql_real_query] Query: SELECT premium_points FROM accounts WHERE id = 5
Message: Unknown column 'premium_points' in 'field list'
 
TFS 1.5 DOWNGREADES NEKIRO 8.0
I did the step by step, but the shop does not recognize the points that I have on the site, and when I click on the outfits tab the client date and I get this error in the exe:
[Error - mysql_real_query] Query: SELECT premium_points FROM accounts WHERE id = 5
Message: Unknown column 'premium_points' in 'field list'
Which database are you using?
 
TFS 1.5 DOWNGREADES NEKIRO 8.0
I did the step by step, but the shop does not recognize the points that I have on the site, and when I click on the outfits tab the client date and I get this error in the exe:
[Error - mysql_real_query] Query: SELECT premium_points FROM accounts WHERE id = 5
Message: Unknown column 'premium_points' in 'field list'
you need to add a database sql by phpmyadmin


SQL:
ALTER TABLE `accounts` ADD `premium_points` varchar(100) DEFAULT NULL
 
I made the change, but I don't receive the points I send on the website, and if I click on the outfits tab, the client closes...
Its easy, you need to change all query that update and select the points to your points system.

Like here:

Just see how premium points work in your website, where they are (znote for exemple: znote_accounts with name points) and change in the script where is needed

In znote this part will be changed to something like:
SELECT points FROM znote_accounts
 
I made the change, but I don't receive the points I send on the website, and if I click on the outfits tab, the client closes...
What's your site? znote? geisor? did you put the id of the outflit correctly? if it's a different id, that's why it debugs (client closes),
you will have to redo the process and it will work bro
 
same issue here. I use ZnoteACC
If using znote, use its scripts.

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://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 category1 = addCategory({
    type="item",
    item=ItemType(2160):getClientId(),
    count=100,
    name="Items"
  })
  local category2 = addCategory({
    type="outfit",
    name="Outfits",
    outfit={
        mount=0,
        feet=114,
        legs=114,
        body=116,
        type=143,
        auxType=0,
        addons=3,
        head=2,
        rotating=true
    }
  })
  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(500, {
        mount=0,
        feet=114,
        legs=114,
        body=116,
        type=143,
        auxType=0,
        addons=3,
        head=2,
        rotating=true
    }, "title of this cool outfit or whatever", "this is your new cool outfit. You can buy it here.\nsrlsy")
    category2.addOutfit(100, {
        mount=682,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=true
    }, "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=true
    }, "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 `points` FROM `znote_accounts` WHERE `id` = " .. player:getAccountId())
  if resultId ~= false then
    points = result.getDataInt(resultId, "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 `znote_accounts` set `points` = `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
 
If using znote, use its scripts.

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://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 category1 = addCategory({
    type="item",
    item=ItemType(2160):getClientId(),
    count=100,
    name="Items"
  })
  local category2 = addCategory({
    type="outfit",
    name="Outfits",
    outfit={
        mount=0,
        feet=114,
        legs=114,
        body=116,
        type=143,
        auxType=0,
        addons=3,
        head=2,
        rotating=true
    }
  })
  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(500, {
        mount=0,
        feet=114,
        legs=114,
        body=116,
        type=143,
        auxType=0,
        addons=3,
        head=2,
        rotating=true
    }, "title of this cool outfit or whatever", "this is your new cool outfit. You can buy it here.\nsrlsy")
    category2.addOutfit(100, {
        mount=682,
        feet=0,
        legs=0,
        body=0,
        type=143,
        auxType=0,
        addons=0,
        head=0,
        rotating=true
    }, "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=true
    }, "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 `points` FROM `znote_accounts` WHERE `id` = " .. player:getAccountId())
  if resultId ~= false then
    points = result.getDataInt(resultId, "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 `znote_accounts` set `points` = `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
the script is partially working but stil if i click in outfit section i get client crash
Post automatically merged:

I manage to enable outfit section don't know how but can't buy the outfits
what else i shopuld add or change?

@Forkz
 
Last edited:
the script is partially working but stil if i click in outfit section i get client crash
Post automatically merged:

I manage to enable outfit section don't know how but can't buy the outfits
what else i shopuld add or change?

@Forkz
It only works for items for now, we are seeing if any developers can contribute to add the outfit codes
 
Back
Top