• 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] buy duplicates from the game store

Shoorkill

Active Member
Joined
Dec 17, 2018
Messages
153
Reaction score
25
hello friends, I'm using game-store on my server, with SQL charging and in-game currency, if I have 100 in the account per db, and 100 physical points in the backpack, it doesn't interfere, the only problem is that in charging if the The shop item costs 50 points, and if I have 50 points he charges the correct amount, if I have 75 he only charges 50, but if I have 100 (i.e. double) the purchase is duplicate, can someone help me fix this?

LUA:
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,
    addPremiumDays = function(cost, image, title, description, count, callback)
      if not callback then
        callback = defaultPremiumBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="image",
        image=image,
        title=title,
        description=description,
        count=count,
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end
  }
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,
    addPremiumDays = function(cost, image, title, description, count, callback)
      if not callback then
        callback = defaultPremiumBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="image",
        image=image,
        title=title,
        description=description,
        count=count,
      })
      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
 
  init(player)
 
  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) -- Atualiza as categorias, talvez inválido
        return sendMessage(player, "AVISO!", "Oferta inválida")     
    end

    -- Verifica a quantidade de moeda física (ID 6527)
    local physicalCurrencyId = 6527
    local playerCurrency = player:getItemCount(physicalCurrencyId)
    local cost = offer['cost']  -- Custo da oferta em moeda física

    -- Verifica se o jogador possui moedas suficientes
    if playerCurrency < cost then
        return sendMessage(player, "AVISO!", "Você não tem moedas suficientes para comprar " .. offer['title'] .."!", true)   
    end

    -- Criar uma mochila para itens comuns
    local backpack = nil
    if offer['type'] == 'item' then -- Verificar se é um item comum
        backpack = player:addItem(28436, 1, false)
        if not backpack then
            return sendMessage(player, "AVISO!", "Não foi possível criar a mochila.")
        end
        backpack:setAttribute(ITEM_ATTRIBUTE_NAME, "Store Bag")
    end

    -- Chama o callback para processar a compra
    local status = callback(player, offer, backpack)

    -- Se a compra foi bem-sucedida, desconta a moeda física e registra a compra
    if status == true then   
        -- Deduz a quantidade de moeda física (ID 6527) após a compra
        player:removeItem(physicalCurrencyId, cost)

        -- Registra a compra no histórico
        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)) .. ")")

        -- Envia a mensagem de sucesso
        return sendMessage(player, "Sucesso!", "Você comprou " .. offer['title'] .."!", true)
    end
    
    -- Se a compra falhou ou o jogador já possui o item, envia uma mensagem de erro
    if status == nil or status == false then
        status = "Você já possui este(a) " .. offer['title']
    end
    
    -- Envia a mensagem de erro
    sendMessage(player, "AVISO!", status)
end

-- Função para enviar o histórico de compras com moeda física
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"] = "comprado em " .. result.getDataString(resultId, "date") .. " por " .. result.getDataInt(resultId, "cost") .. " moedas físicas."
    until not result.next(resultId)
    result.free(resultId)
  end

  sendJSON(player, "history", history)
end

-- Funções de callback
function defaultItemBuyAction(player, offer, backpack)
    if not backpack then
        return "Erro: mochila não encontrada."
    end

    local itemId = offer["itemId"]
    local itemCount = offer["count"] or 1
    local isStackable = isItemStackable(itemId)  -- Supondo que esta função verifica se o item é empilhável

    if isStackable then
        -- Lógica para itens empilháveis
        local fullStacks = math.floor(itemCount / 100)
        local remainder = itemCount % 100

        for i = 1, fullStacks do
            local addedItem = backpack:addItem(itemId, 100)
            if not addedItem then
                return "Não foi possível adicionar o item na mochila."
            end
        end

        if remainder > 0 then
            local addedItem = backpack:addItem(itemId, remainder)
            if not addedItem then
                return "Não foi possível adicionar o item na mochila."
            end
        end
    else
        -- Lógica para itens não empilháveis
        for i = 1, itemCount do
            local addedItem = backpack:addItem(itemId, 1)
            if not addedItem then
                return "Não foi possível adicionar o item na mochila."
            end
        end
    end

    return true
end

function defaultOutfitBuyAction(player, offer)
    local outfit = offer['outfit']
    local mountId = outfit['mount']
    local wingId = outfit['wing']
    local auraId = outfit['auras']
    local shaderId = outfit['shaderAdd']
 
    if player:getStorageValue(outfit['storage']) > 0 then
        local message = "Você já possui esta " .. outfit['name'] .. "!"
        player:sendTextMessage(MESSAGE_INFO_DESCR, message)
        return false
    end

    -- Verifica a quantidade de moeda física (ID 6527)
    local physicalCurrencyId = 6527
    local playerCurrency = player:getItemCount(physicalCurrencyId)
    if offer['cost'] > playerCurrency then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "Você não possui moedas suficientes para comprar este outfit.")
        return false
    end

    -- Deduz a moeda física (ID 6527)
    player:removeItem(physicalCurrencyId, offer['cost'])

    if outfit['lookType'] then
        local sex = player:getSex()
        local lookType = outfit['lookType'][sex + 1]
        local addons = outfit['addons']

        player:addOutfitAddon(lookType, addons)
        player:addOutfit(outfit['type'], addons)
    end
    
    player:setStorageValue(outfit['storage'], 1)
    
    local message = "Você comprou o " .. outfit['name'] .. "!"
    player:sendTextMessage(MESSAGE_INFO_DESCR, message)
    
    if mountId and mountId > 0 then
        player:addMount(mountId)
        player:setStorageValue(outfit['storage'], 1)
    end
    
    if wingId and wingId > 0 then
        player:addWings(wingId) 
        player:setStorageValue(outfit['storage'], 1)
    end

    if auraId and auraId > 0 then
        player:addAura(auraId) 
        player:setStorageValue(outfit['storage'], 1)
    end
    
    if shaderId and tonumber(shaderId) and tonumber(shaderId) > 0 then
        player:addShader(tonumber(shaderId))
        player:setStorageValue(outfit['storage'], 1)
    end

    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)
  -- Verifica se o jogador tem pontos suficientes
  local currentPoints = getPoints(player)
  if currentPoints < offer.cost then
    sendMessage(player, "Purchase Error", "You do not have enough points to purchase this image.")
    return false
  end

  -- Atualiza os pontos do jogador no banco de dados de forma atômica
  local query = string.format("UPDATE `accounts` SET `premium_points` = `premium_points` - %d WHERE `id` = %d AND `premium_points` >= %d", offer.cost, player:getAccountId(), offer.cost)
  local updateResult = db.query(query)
 
  if not updateResult then
    sendMessage(player, "Purchase Error", "There was an error updating your points. Please try again.")
    return false
  end

  -- Adiciona a imagem ao perfil do jogador
  local success = addImageToPlayerProfile(player, offer.image)
  if not success then
    -- Se houver um erro ao adicionar a imagem, reembolsa os pontos
    db.query(string.format("UPDATE `accounts` SET `premium_points` = `premium_points` + %d WHERE `id` = %d", offer.cost, player:getAccountId()))
    sendMessage(player, "Purchase Error", "There was an error adding the image to your profile. Your points have been refunded.")
    return false
  end

  -- Envia uma mensagem de confirmação para o jogador
  sendMessage(player, "Image Purchased", "You have successfully purchased the image: " .. offer.title)
  return true
end

-- Função fictícia para adicionar a imagem ao perfil do jogador
function addImageToPlayerProfile(player, imageUrl)
  -- Implemente a lógica para adicionar a imagem ao perfil do jogador aqui
  -- Retorne true se a imagem foi adicionada com sucesso, ou false se houve um erro
  return true -- Simulando sucesso
end

function customImageBuyAction(player, offer)
  return "A acao de compra personalizada de imagem nao esta implementada. Oferta: " .. offer['title']
end
 

function defaultImageBuyAction(player, offer)
  -- Verifica se o jogador tem pontos suficientes
  local currentPoints = getPoints(player)
  if currentPoints < offer.cost then
    sendMessage(player, "Purchase Error", "You do not have enough points to purchase this image.")
    return false
  end

  -- Atualiza os pontos do jogador no banco de dados de forma atômica
  local query = string.format("UPDATE `accounts` SET `premium_points` = `premium_points` - %d WHERE `id` = %d AND `premium_points` >= %d", offer.cost, player:getAccountId(), offer.cost)
  local updateResult = db.query(query)
 
  if not updateResult then
    sendMessage(player, "Purchase Error", "There was an error updating your points. Please try again.")
    return false
  end

  -- Adiciona a imagem ao perfil do jogador
  local success = addImageToPlayerProfile(player, offer.image)
  if not success then
    -- Se houver um erro ao adicionar a imagem, reembolsa os pontos
    db.query(string.format("UPDATE `accounts` SET `premium_points` = `premium_points` + %d WHERE `id` = %d", offer.cost, player:getAccountId()))
    sendMessage(player, "Purchase Error", "There was an error adding the image to your profile. Your points have been refunded.")
    return false
  end

  -- Envia uma mensagem de confirmação para o jogador
  sendMessage(player, "Image Purchased", "You have successfully purchased the image: " .. offer.title)
  return true
end

-- Função fictícia para adicionar a imagem ao perfil do jogador
function addImageToPlayerProfile(player, imageUrl)
  -- Implemente a lógica para adicionar a imagem ao perfil do jogador aqui
  -- Retorne true se a imagem foi adicionada com sucesso, ou false se houve um erro
  return true -- Simulando sucesso
end

function customImageBuyAction(player, offer)
  return "A acao de compra personalizada de imagem nao esta implementada. Oferta: " .. offer['title']
end
 
Back
Top