Ir para conteúdo
  • Cadastre-se

Posts Recomendados

Acabei de ver que postei no lugar errado, alguém pode por favor, mover para a área certa?

E aí galera, blz? Então, aí vai o script do init.lua, da store in-game do OTX3, que está bugado nas versões que vocês encontrarem por aí, bugado como? Se o player não tiver cap, remove os coins, não entrega o item, e não devolve o coin depoois e se você liberar o cap também não dá o item... Ou seja, você perde os coins. 

O que foi alterado? Foi adicionada uma função que verifica se houve erro no script e retorna antes de retirar os coins. 
Fixado bug nos items agrupáveis também. 
Há um pequeno erro, se for adicionado mais de 1 item (não agrupável) e não houver cap (por exemplo, 10 blesseds shields, e o player só tem cap para 5, irá entregar os 5 e não vai retirar os coins)
Então, não aconselho adicionar mais de 1 item não agrupável (não acontece com items agrupáveis, pois são entregados em um parcel)
Acredito que para arrumar este erro, basta colocar os items para irem dentro de um parcel assim como os items agrupáveis, mas acho desnecessário, caso alguém queira, é uma modificação simples de se fazer, então faça por si só.

Aí vai

 

Spoiler

-- Please don't edit those information!
GameStore = {
    ModuleName = "GameStore",
    Developer = "OTX TEAM",
    Version = "0.3",
    LastUpdated = "24-09-2016 07:15PM"
}

--== Enums ==--
GameStore.OfferTypes = {
    OFFER_TYPE_NONE = 0, -- (this will disable offer)
    OFFER_TYPE_ITEM = 1,
    OFFER_TYPE_STACKABLE = 2,
    OFFER_TYPE_OUTFIT = 3,
    OFFER_TYPE_OUTFIT_ADDON = 4,
    OFFER_TYPE_MOUNT = 5,
    OFFER_TYPE_NAMECHANGE = 6,
    OFFER_TYPE_SEXCHANGE = 7,
    OFFER_TYPE_PROMOTION = 8
}

GameStore.ClientOfferTypes = {
    CLIENT_STORE_OFFER_OTHER = 0,
    CLIENT_STORE_OFFER_NAMECHANGE = 1
}

GameStore.HistoryTypes = {
    HISTORY_TYPE_NONE = 0,
    HISTORY_TYPE_GIFT = 1,
    HISTORY_TYPE_REFUND = 2
}

GameStore.States = {
    STATE_NONE = 0,
    STATE_NEW = 1,
    STATE_SALE = 2,
    STATE_TIMED = 3
}

GameStore.StoreErrors = {
    STORE_ERROR_PURCHASE = 0,
    STORE_ERROR_NETWORK = 1,
    STORE_ERROR_HISTORY = 2,
    STORE_ERROR_TRANSFER = 3,
    STORE_ERROR_INFORMATION = 4
}

GameStore.ServiceTypes = {
    SERVICE_STANDERD = 0,
    SERVICE_OUTFITS = 3,
    SERVICE_MOUNTS = 4
}

GameStore.SendingPackets = {
    S_CoinBalance = 0xDF, -- 223
    S_StoreError = 0xE0, -- 224
    S_RequestPurchaseData = 0xE1, -- 225
    S_CoinBalanceUpdating = 0xF2, -- 242
    S_OpenStore = 0xFB, -- 251
    S_StoreOffers = 0xFC, -- 252
    S_OpenTransactionHistory = 0xFD, -- 253
    S_CompletePurchase = 0xFE  -- 254
}
GameStore.RecivedPackets = {
    C_StoreEvent = 0xE9, -- 233
    C_TransferCoins = 0xEF, -- 239
    C_OpenStore = 0xFA, -- 250
    C_RequestStoreOffers = 0xFB, -- 251
    C_BuyStoreOffer = 0xFC, -- 252
    C_OpenTransactionHistory = 0xFD, -- 253
    C_RequestTransactionHistory = 0xFE, -- 254
}

GameStore.DefaultValues = {
    DEFAULT_VALUE_ENTRIES_PER_PAGE    = 16
}
GameStore.DefaultDescriptions = {
    OUTFIT = {"This outfit looks nice. Only high-class people are able to wear it!",
        "An outfit that was created to suit you. We are sure you'll like it.",
        "Legend says only smart people should wear it, otherwise you will burn!"},
    MOUNT = {"This is a fantastic mount that helps to become faster, try it!",
        "The first rider of this mount became the leader of his country! legends say that."},
    NAMECHANGE = {"Are you hunted? Tired of that? Get a new name, a new life!",
        "A new name to suit your needs!"},
    SEXCHANGE = {"Bored of your character's sex? Get a new sex for him now!!"}
}

--==Parsing==--
GameStore.isItsPacket = function(byte)
    for k, v in pairs(GameStore.RecivedPackets) do
        if v == byte then
            return true
        end
    end
    return false
end

function onRecvbyte(player, msg, byte)
    if byte == GameStore.RecivedPackets.C_StoreEvent then
        -- Not Used!
    elseif byte == GameStore.RecivedPackets.C_TransferCoins then
        parseTransferCoins(player, msg)
    elseif byte == GameStore.RecivedPackets.C_OpenStore then
        parseOpenStore(player, msg)
    elseif byte == GameStore.RecivedPackets.C_RequestStoreOffers then
        parseRequestStoreOffers(player, msg)
    elseif byte == GameStore.RecivedPackets.C_BuyStoreOffer then
        parseBuyStoreOffer(player, msg)
    elseif byte == GameStore.RecivedPackets.C_OpenTransactionHistory then
        parseOpenTransactionHistory(player, msg)
    elseif byte == GameStore.RecivedPackets.C_RequestTransactionHistory then
        parseRequestTransactionHistory(player, msg)
    end
    return true
end
function parseTransferCoins(player, msg)
    local reciver = msg:getString()
    local amount = msg:getU32()

    if reciver:lower() == player:getName():lower() then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You can't transfer coins to yourself.")
    end

    local resultId = db.storeQuery("SELECT `account_id` FROM `players` WHERE `name` = " .. db.escapeString(reciver:lower()) .. "")
    if not resultId then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "We couldn't find that player.")
    end

    local accountId = result.getDataInt(resultId, "account_id")
    if accountId == player:getAccountId() then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You cannot transfer coin to a character in the same account.")
    end

    if not player:canRemoveCoins(amount) then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You don't have enough funds to transfer these coins.")
    end

    player:removeCoinsBalance(amount)
    db.asyncQuery("UPDATE `accounts` SET `coins` = `coins` + " .. amount .. " WHERE `id` = " .. accountId)
    addPlayerEvent(sendStorePurchaseSuccessful, 550, player, "You have transfered " .. amount .. " coins to " .. reciver .. " successfully")

    -- Adding history for both reciver/sender
    GameStore.insertHistory(accountId, GameStore.HistoryTypes.HISTORY_TYPE_NONE, player:getName() .. " transfered you this amount.", amount)
    GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, "You transfered this amount to " .. reciver, -1 * amount) -- negative
end

function parseOpenStore(player, msg)
    openStore(player)

    local serviceType = msg:getByte()
    local category = GameStore.Categories and GameStore.Categories[1] or nil

    if serviceType == GameStore.ServiceTypes.SERVICE_OUTFITS then
        category = GameStore.getCategoryByName("outfits")
    elseif serviceType == GameStore.ServiceTypes.SERVICE_MOUNTS then
        category = GameStore.getCategoryByName("mounts")
    end

    if category then
        addPlayerEvent(sendShowStoreOffers, 350, player, category)
    end
end

function parseRequestStoreOffers(player, msg)
    local serviceType = GameStore.ServiceTypes.SERVICE_STANDERD
    if player:getClient().version >= 1092 then
        serviceType = msg:getByte()
    end
    local categoryName = msg:getString()

    local category = GameStore.getCategoryByName(categoryName)
    if category then
        addPlayerEvent(sendShowStoreOffers, 350, player, category)
    end
end

function parseBuyStoreOffer(player, msg)
    local offerId = msg:getU32()
    local productType = msg:getByte()

    local offer = GameStore.getOfferById(offerId)
    if offer then
        -- If we don't add type, or offer type is fake
        if not offer.type or offer.type == GameStore.OfferTypes.OFFER_TYPE_NONE then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is either fake or corrupt.")
        end

        -- If no thing id,
        if offer.type ~= GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and offer.type ~= GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE and not offer.thingId then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is either fake or corrupt.")
        end

        -- We remove coins before doing everything, if it fails, we add coins back!
        if not player:canRemoveCoins(offer.price) then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "We couldn't remove coins from your account, try again later.")
        end

        -- count is used in type(item), so we need to show (i.e 10x crystal coins)
        local offerCountStr = offer.count and (offer.count .. "x ") or ""
        -- The message which we will send to player!
        local message = "You have purchased " .. offerCountStr .. offer.name .. " for " .. offer.price .. " coins."

        -- If offer is item.
        if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM then
            local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
            if inbox and inbox:getEmptySlots() > offer.count then
                for t = 1,offer.count do
                    if inbox:addItem(offer.thingId, offer.count or 1) then
                    else
                    player:canRemoveCoins(offer.price)
                    return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free cap or slots in your store inbox.")
                    end
                end
            else
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free cap or slots in your store inbox.")
            end
        -- If offer is Stackable.
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE then
            local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
            if inbox and inbox:getEmptySlots() > 0 then
                local parcel = inbox:addItem(2596, 1)
                local packagename = ''.. offer.count..'x '.. offer.name ..' package.'
                for e = 1,offer.count do
                    if parcel then
                        parcel:setAttribute(ITEM_ATTRIBUTE_NAME, packagename)
                        parcel:addItem(offer.thingId, 1)
                    else
                    player:canRemoveCoins(offer.count)
                    doPlayerRemoveItem(cid, 2596, 1)
                    return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free cap or slots in your store inbox.")
                    end
                end
            else
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free cap or slots in your store inbox.")
            end
        -- If offer is outfit/addon
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT or offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
            local outfitLookType
            if player:getSex() == PLAYERSEX_MALE then
                outfitLookType = offer.thingId.male
            else
                outfitLookType = offer.thingId.female
            end
            if not outfitLookType then
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This outfit seems not to suit your sex, we are sorry for that!")
            end

            player:addOutfitAddon(outfitLookType, offer.addon or 0)
        -- If offer is mount
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_MOUNT then
            player:addMount(offer.thingId)
        -- If offer is name change
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE then
            -- If player typed name yet!
            if productType == GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE then
                local newName = msg:getString()

                local resultId = db.storeQuery("SELECT * FROM `players` WHERE `name` = " .. db.escapeString(newName) .. "")
                if resultId ~= false then
                    return addPlayerEvent(sendStoreError, 650, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This name is already used, please try again!")
                end

                local result = GameStore.canChangeToName(newName)
                if not result.ability then
                    return addPlayerEvent(sendStoreError, 650, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, result.reason)
                end

                newName = newName:lower():gsub("(%l)(%w*)", function(a, b) return string.upper(a) .. b end)
                db.asyncQuery("UPDATE `players` SET `name` = " .. db.escapeString(newName) .. " WHERE `id` = " .. player:getGuid())
                message =  "You have successfully changed you name, you must relog to see changes."
            -- If not, we ask him to do!
            else
                return addPlayerEvent(sendRequestPurchaseData, 250, player, offer.id, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE)
            end
        -- If offer is sex change
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE then
            player:toggleSex()
        -- If offer is promotion
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PROMOTION then
            if not GameStore.addPromotionToPlayer(player, offer.thingId) then
                return false
            end
        -- You can add whatever offer types to suit your needs!
        else
            -- ToDo :: implement purchase function
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This offer is fake, please contact admin.")
        end
        -- Removing coins
        player:removeCoinsBalance(offer.price)
        -- We add this purchase to history!
        GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, offerCountStr .. offer.name, offer.price * -1)
        -- Send to client that purchase is successful!
        return addPlayerEvent(sendStorePurchaseSuccessful, 650, player, message)
    end

    -- If we didn't found the offer or error happened
    addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_INFORMATION, "We couldn't locate this offer, please try again later.")
end

-- Both functions use same formula!
function parseOpenTransactionHistory(player, msg)
    local page = 1
    GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE = msg:getByte()
    sendStoreTransactionHistory(player, page, GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE)
end

function parseRequestTransactionHistory(player, msg)
    local page = msg:getU32()
    sendStoreTransactionHistory(player, page, GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE)
end

--==Sending==--
function openStore(player)
    if not GameStore.Categories then
        return false
    end
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_OpenStore)
    msg:addByte(0x00)

    msg:addU16(#GameStore.Categories)
    for k, category in ipairs(GameStore.Categories) do
        msg:addString(category.name)
        msg:addString(category.description)

        if player:getClient().version >= 1093 then
            msg:addByte(category.state or GameStore.States.STATE_NONE)
        end

        msg:addByte(#category.icons)
        for m, icon in ipairs(category.icons) do
            msg:addString(icon)
        end

        msg:addString(category.parentCategory)
    end
    msg:sendToPlayer(player)

    sendCoinBalanceUpdating(player, true)
end

function sendShowStoreOffers(player, category)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_StoreOffers)

    msg:addString(category.name)

    msg:addU16(category.offers and #category.offers or 0x00)

    if category.offers then
        for k, offer in ipairs(category.offers) do
            msg:addU32(offer.id and offer.id or 0xFFFF) -- we later detect this number!

            local name = ""
            if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM and offer.count then
                name = offer.count .. "x "
            end

            if offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE and offer.count then
                name = offer.count .. "x "
            end

            name = name .. (offer.name or "Something Special")

            msg:addString(name)
            msg:addString(offer.description or GameStore.getDefaultDescription(offer.type))

            msg:addU32(offer.price and offer.price or 0xFFFF)
            msg:addByte(offer.state or GameStore.States.STATE_NONE) -- default is none

            local disabled, disabledReason = 0, ""
            if offer.disabled == true or not offer.type then
                disabled = 1
            end

            if offer.type ~= GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and offer.type ~= GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE and not offer.thingId then
                disabled = 1
            end

            if disabled == 1 and offer.disabledReason then -- dynamic disable
                disabledReason = offer.disabledReason
            end

            if disabled ~= 1 then
                if offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT or offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
                    local outfitLookType
                    if player:getSex() == PLAYERSEX_MALE then
                        outfitLookType = offer.thingId.male
                    else
                        outfitLookType = offer.thingId.female
                    end

                    if outfitLookType then
                        if offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT and player:hasOutfit(outfitLookType) then
                            disabled = 1
                            disabledReason = "You already have this outfit."
                        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
                            if player:hasOutfit(outfitLookType) then
                                if player:hasOutfit(outfitLookType, offer.addon) then
                                    disabled = 1
                                    disabledReason = "You already have this addon."
                                end
                            else
                                disabled = 1
                                disabledReason = "You don't have the outfit, you can't buy the addon."
                            end
                        end
                    else
                        disabled = 1
                        disabledReason = "The offer is fake."
                    end
                elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_MOUNT then
                    local hasMount = player:hasMount(offer.thingId)
                    if hasMount == true then
                        disabled = 1
                        disabledReason = "You already have this mount."
                    end
                elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PROMOTION then
                    if GameStore.canAddPromotionToPlayer(player, offer.thingId).ability == false then
                        disabled = 1
                        disabledReason = "You can't get this promotion"
                    end
                end
            end

            msg:addByte(disabled)

            if disabled == 1 and player:getClient().version >= 1093 then
                msg:addString(disabledReason)
            end

            msg:addByte(#offer.icons)
            for k, icon in ipairs(offer.icons) do
                msg:addString(icon)
            end

            msg:addU16(0) -- We still don't support SubOffers!
        end
    end
    msg:sendToPlayer(player)
end

function sendStoreTransactionHistory(player, page, entriesPerPage)
    local entries = GameStore.retrieveHistoryEntries(player:getAccountId()) -- this makes everything easy!
    if #entries == 0 then
        return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_HISTORY, "You don't have any entries yet.")
    end

    local toSkip = (page - 1) * entriesPerPage
    for i = 1, toSkip do
        table.remove(entries, 1) -- we remove first!
    end

    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_OpenTransactionHistory)
    msg:addU32(page)
    msg:addU32(#entries > entriesPerPage and 0x01 or 0x00)

    msg:addByte(#entries >= entriesPerPage and entriesPerPage or #entries)
    for k, entry in ipairs(entries) do
        if k >= entriesPerPage then break end
        msg:addU32(entry.time)
        msg:addByte(entry.mode)
        msg:addU32(entry.amount)
        msg:addString(entry.description)
    end
    msg:sendToPlayer(player)
end

function sendStorePurchaseSuccessful(player, message)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CompletePurchase)

    msg:addByte(0x00)

    msg:addString(message)
    msg:addU32(player:getCoinsBalance())
    msg:addU32(player:getCoinsBalance())

    msg:sendToPlayer(player)
end

function sendStoreError(player, errorType, message)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_StoreError)

    msg:addByte(errorType)
    msg:addString(message)

    msg:sendToPlayer(player)
end

function sendCoinBalanceUpdating(player, updating)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CoinBalanceUpdating)
    msg:addByte(0x00)
    msg:sendToPlayer(player)

    if updating == true then
        sendUpdateCoinBalance(player)
    end
end

function sendUpdateCoinBalance(player)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CoinBalanceUpdating)
    msg:addByte(0x01)

    msg:addByte(GameStore.SendingPackets.S_CoinBalance)
    msg:addByte(0x01)

    msg:addU32(player:getCoinsBalance())
    msg:addU32(player:getCoinsBalance())

    msg:sendToPlayer(player)
end

function sendRequestPurchaseData(player, offerId, type)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_RequestPurchaseData)
    msg:addU32(offerId)
    msg:addByte(type)
    msg:sendToPlayer(player)
end

--==GameStoreFunctions==--
GameStore.getCategoryByName = function(name)
    for k, category in ipairs(GameStore.Categories) do
        if category.name:lower() == name:lower() then
            return category
        end
    end
    return nil
end

GameStore.getOfferById = function(id)
    for Cat_k, category in ipairs(GameStore.Categories) do
        if category.offers then
            for Off_k, offer in ipairs(category.offers) do
                if offer.id == id then
                    return offer
                end
            end
        end
    end
    return nil
end

GameStore.insertHistory = function(accountId, mode, description, amount)
    return db.asyncQuery(string.format("INSERT INTO `store_history`(`account_id`, `mode`, `description`, `coin_amount`, `time`) VALUES (%s, %s, %s, %s, %s)", accountId, mode, db.escapeString(description), amount, os.time()))
end

GameStore.retrieveHistoryEntries = function(accountId)
    local entries = {}
    local resultId = db.storeQuery("SELECT * FROM `store_history` WHERE `account_id` = " .. accountId .. " ORDER BY `time` DESC LIMIT 15;")
    if resultId ~= false then
        repeat
            local entry = {
                mode = result.getDataInt(resultId, "mode"),
                description = result.getDataString(resultId, "description"),
                amount = result.getDataInt(resultId, "coin_amount"),
                time = result.getDataInt(resultId, "time"),
            }
            table.insert(entries, entry)
        until not result.next(resultId)
        result.free(resultId)
    end
    return entries
end

GameStore.getDefaultDescription = function(offerType)
    local t, descList = GameStore.OfferTypes
    if offerType == t.OFFER_TYPE_OUTFIT or offerType == t.OFFER_TYPE_OUTFIT_ADDON then
        descList = GameStore.DefaultDescriptions.OUTFIT
    elseif offerType == t.OFFER_TYPE_MOUNT then
        descList = GameStore.DefaultDescriptions.MOUNT
    elseif offerType == t.OFFER_TYPE_NAMECHANGE then
        descList = GameStore.DefaultDescriptions.NAMECHANGE
    elseif offerType == t.OFFER_TYPE_SEXCHANGE then
        descList = GameStore.DefaultDescriptions.SEXCHANGE
    else
        return ""
    end

    return descList[math.floor(math.random(1, #descList))] or ""
end

GameStore.canChangeToName = function(name)
    local result = {
        ability = false
    }
    if name:len() < 3 or name:len() > 14 then
        result.reason = "Your new name's length should be lower than 3 or higher than 14."
        return result
    end

    -- just copied from znote aac.
    local words = {"owner", "gamemaster", "hoster", "admin", "staff", "tibia", "account", "god", "anal", "ass", "fuck", "sex", "hitler", "pussy", "dick", "rape", "cm", "gm", "tutor", "counsellor"}
    local split = name:split(" ")
    for k, word in ipairs(words) do
        for k, nameWord in ipairs(split) do
            if nameWord:lower() == word then
                result.reason = "You can't use word \"" .. word .. "\" in your new name."
                return result
            end
        end
    end

    if MonsterType(name) then
        result.reason = "Your new name \"" .. name .. "\" can't be a monster's name."
        return result
    elseif Npc(name) then
        result.reason = "Your new name \"" .. name .. "\" can't be a npc's name."
        return result
    end

    local letters = "{}|_*+-=<>0123456789@#%^&()/*\\.,:;~!\"$"
    for i = 1, letters:len() do
        local c = letters:sub(i, i)
        for i = 1, name:len() do
            local m = name:sub(i, i)
            if m == c then
                result.reason = "You can't use this letter \"" .. c .. "\" in your new name."
                return result
            end
        end
    end
    result.ability = true
    return result
end

GameStore.canAddPromotionToPlayer = function(player, promotion, send)
    local result = {
        ability = true
    }
    local vocation = player:getVocation()
    -- Working --
    local vocationCopy, baseVocation = vocation, vocation
    vocation = vocation:getDemotion()
    while vocation do
        baseVocation = vocation
        vocation = vocation:getDemotion()
    end

    local baseVocationsCount = GameStore.BaseVocationsCount or 4

    local newVocId = (baseVocationsCount * promotion) + baseVocation:getId()

    if not Vocation(newVocId) then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is fake, please report it!")
        end
        result.ability = false
        return result
    end
    -- If promotion is less than player's voc, or player don't have previous promotion
    if newVocId <= vocationCopy:getId() then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You already have this promotion!")
        end
        result.ability = false
        return result
    end

    if (newVocId - baseVocationsCount) ~= vocationCopy:getId() then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You need higher promotion to get his one.")
        end
        result.ability = false
        return result
    end

    result.vocId = newVocId
    return result
end

GameStore.addPromotionToPlayer = function(player, promotion)
    local result = GameStore.canAddPromotionToPlayer(player, promotion, true)
    if result.ability == false then return false end

    local basics = {
        health = 185,
        mana = 40,
        cap = 500
    }

    player:setVocation(result.vocId)
    local newVoc = player:getVocation()
    player:setMaxHealth(basics.health + (newVoc:getHealthGain() * player:getLevel()))
    player:setMaxMana(basics.mana + (newVoc:getManaGain() * player:getLevel()))
    player:setCapacity(basics.cap + (newVoc:getCapacityGain() * player:getLevel()))

    player:addHealth(player:getMaxHealth())
    player:addMana(player:getMaxMana())

    player:sendTextMessage(MESSAGE_INFO_DESCR, "You have been promoted to " .. newVoc:getName())
    return true
end

--==Player==--
function Player.getCoinsBalance(self)
    resultId = db.storeQuery("SELECT `coins` FROM `accounts` WHERE `id` = " .. self:getAccountId())
    if not resultId then return 0 end
    return result.getDataInt(resultId, "coins")
end

function Player.setCoinsBalance(self, coins)
    db.asyncQuery("UPDATE `accounts` SET `coins` = " .. coins .. " WHERE `id` = " .. self:getAccountId())
    return true
end

function Player.canRemoveCoins(self, coins)
    if self:getCoinsBalance() < coins then
        return false
    end
    return true
end

function Player.removeCoinsBalance(self, coins)
    if self:canRemoveCoins(coins) then
        return self:setCoinsBalance(self:getCoinsBalance() - coins)
    end

    return false
end

function Player.addCoinsBalance(self, coins, update)
    self:setCoinsBalance(self:getCoinsBalance() + coins)
    if update then sendCoinBalanceUpdating(self, true) end
    return true
end

function Player.toggleSex(self)
    local currentSex = self:getSex()
    local playerOutfit = self:getOutfit()

    if currentSex == PLAYERSEX_FEMALE then
        self:setSex(PLAYERSEX_MALE)
        playerOutfit.lookType = 128
    else
        self:setSex(PLAYERSEX_FEMALE)
        playerOutfit.lookType = 136
    end
    self:setOutfit(playerOutfit)
end
 



Foi útil? Rep+ (foi difícil arrumar, apesar de parecer simples)

Foto

Spoiler

 

 

bug arrumado.png

Editado por Eduardo Dantas (veja o histórico de edições)
Link para o post
Compartilhar em outros sites
Em 04/02/2017 ás 00:36, Eduardo Dantas disse:

Acabei de ver que postei no lugar errado, alguém pode por favor, mover para a área certa?

E aí galera, blz? Então, aí vai o script do init.lua, da store in-game do OTX3, que está bugado nas versões que vocês encontrarem por aí, bugado como? Se o player não tiver cap, remove os coins, não entrega o item, e não devolve o coin depoois e se você liberar o cap também não dá o item... Ou seja, você perde os coins. 

O que foi alterado? Foi adicionada uma função que verifica se houve erro no script e retorna antes de retirar os coins. 
Fixado bug nos items agrupáveis também. 
Há um pequeno erro, se for adicionado mais de 1 item (não agrupável) e não houver cap (por exemplo, 10 blesseds shields, e o player só tem cap para 5, irá entregar os 5 e não vai retirar os coins)
Então, não aconselho adicionar mais de 1 item não agrupável (não acontece com items agrupáveis, pois são entregados em um parcel)
Acredito que para arrumar este erro, basta colocar os items para irem dentro de um parcel assim como os items agrupáveis, mas acho desnecessário, caso alguém queira, é uma modificação simples de se fazer, então faça por si só.

Aí vai

 

  Ocultar conteúdo

-- Please don't edit those information!
GameStore = {
    ModuleName = "GameStore",
    Developer = "OTX TEAM",
    Version = "0.3",
    LastUpdated = "24-09-2016 07:15PM"
}

--== Enums ==--
GameStore.OfferTypes = {
    OFFER_TYPE_NONE = 0, -- (this will disable offer)
    OFFER_TYPE_ITEM = 1,
    OFFER_TYPE_STACKABLE = 2,
    OFFER_TYPE_OUTFIT = 3,
    OFFER_TYPE_OUTFIT_ADDON = 4,
    OFFER_TYPE_MOUNT = 5,
    OFFER_TYPE_NAMECHANGE = 6,
    OFFER_TYPE_SEXCHANGE = 7,
    OFFER_TYPE_PROMOTION = 8
}

GameStore.ClientOfferTypes = {
    CLIENT_STORE_OFFER_OTHER = 0,
    CLIENT_STORE_OFFER_NAMECHANGE = 1
}

GameStore.HistoryTypes = {
    HISTORY_TYPE_NONE = 0,
    HISTORY_TYPE_GIFT = 1,
    HISTORY_TYPE_REFUND = 2
}

GameStore.States = {
    STATE_NONE = 0,
    STATE_NEW = 1,
    STATE_SALE = 2,
    STATE_TIMED = 3
}

GameStore.StoreErrors = {
    STORE_ERROR_PURCHASE = 0,
    STORE_ERROR_NETWORK = 1,
    STORE_ERROR_HISTORY = 2,
    STORE_ERROR_TRANSFER = 3,
    STORE_ERROR_INFORMATION = 4
}

GameStore.ServiceTypes = {
    SERVICE_STANDERD = 0,
    SERVICE_OUTFITS = 3,
    SERVICE_MOUNTS = 4
}

GameStore.SendingPackets = {
    S_CoinBalance = 0xDF, -- 223
    S_StoreError = 0xE0, -- 224
    S_RequestPurchaseData = 0xE1, -- 225
    S_CoinBalanceUpdating = 0xF2, -- 242
    S_OpenStore = 0xFB, -- 251
    S_StoreOffers = 0xFC, -- 252
    S_OpenTransactionHistory = 0xFD, -- 253
    S_CompletePurchase = 0xFE  -- 254
}
GameStore.RecivedPackets = {
    C_StoreEvent = 0xE9, -- 233
    C_TransferCoins = 0xEF, -- 239
    C_OpenStore = 0xFA, -- 250
    C_RequestStoreOffers = 0xFB, -- 251
    C_BuyStoreOffer = 0xFC, -- 252
    C_OpenTransactionHistory = 0xFD, -- 253
    C_RequestTransactionHistory = 0xFE, -- 254
}

GameStore.DefaultValues = {
    DEFAULT_VALUE_ENTRIES_PER_PAGE    = 16
}
GameStore.DefaultDescriptions = {
    OUTFIT = {"This outfit looks nice. Only high-class people are able to wear it!",
        "An outfit that was created to suit you. We are sure you'll like it.",
        "Legend says only smart people should wear it, otherwise you will burn!"},
    MOUNT = {"This is a fantastic mount that helps to become faster, try it!",
        "The first rider of this mount became the leader of his country! legends say that."},
    NAMECHANGE = {"Are you hunted? Tired of that? Get a new name, a new life!",
        "A new name to suit your needs!"},
    SEXCHANGE = {"Bored of your character's sex? Get a new sex for him now!!"}
}

--==Parsing==--
GameStore.isItsPacket = function(byte)
    for k, v in pairs(GameStore.RecivedPackets) do
        if v == byte then
            return true
        end
    end
    return false
end

function onRecvbyte(player, msg, byte)
    if byte == GameStore.RecivedPackets.C_StoreEvent then
        -- Not Used!
    elseif byte == GameStore.RecivedPackets.C_TransferCoins then
        parseTransferCoins(player, msg)
    elseif byte == GameStore.RecivedPackets.C_OpenStore then
        parseOpenStore(player, msg)
    elseif byte == GameStore.RecivedPackets.C_RequestStoreOffers then
        parseRequestStoreOffers(player, msg)
    elseif byte == GameStore.RecivedPackets.C_BuyStoreOffer then
        parseBuyStoreOffer(player, msg)
    elseif byte == GameStore.RecivedPackets.C_OpenTransactionHistory then
        parseOpenTransactionHistory(player, msg)
    elseif byte == GameStore.RecivedPackets.C_RequestTransactionHistory then
        parseRequestTransactionHistory(player, msg)
    end
    return true
end
function parseTransferCoins(player, msg)
    local reciver = msg:getString()
    local amount = msg:getU32()

    if reciver:lower() == player:getName():lower() then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You can't transfer coins to yourself.")
    end

    local resultId = db.storeQuery("SELECT `account_id` FROM `players` WHERE `name` = " .. db.escapeString(reciver:lower()) .. "")
    if not resultId then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "We couldn't find that player.")
    end

    local accountId = result.getDataInt(resultId, "account_id")
    if accountId == player:getAccountId() then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You cannot transfer coin to a character in the same account.")
    end

    if not player:canRemoveCoins(amount) then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You don't have enough funds to transfer these coins.")
    end

    player:removeCoinsBalance(amount)
    db.asyncQuery("UPDATE `accounts` SET `coins` = `coins` + " .. amount .. " WHERE `id` = " .. accountId)
    addPlayerEvent(sendStorePurchaseSuccessful, 550, player, "You have transfered " .. amount .. " coins to " .. reciver .. " successfully")

    -- Adding history for both reciver/sender
    GameStore.insertHistory(accountId, GameStore.HistoryTypes.HISTORY_TYPE_NONE, player:getName() .. " transfered you this amount.", amount)
    GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, "You transfered this amount to " .. reciver, -1 * amount) -- negative
end

function parseOpenStore(player, msg)
    openStore(player)

    local serviceType = msg:getByte()
    local category = GameStore.Categories and GameStore.Categories[1] or nil

    if serviceType == GameStore.ServiceTypes.SERVICE_OUTFITS then
        category = GameStore.getCategoryByName("outfits")
    elseif serviceType == GameStore.ServiceTypes.SERVICE_MOUNTS then
        category = GameStore.getCategoryByName("mounts")
    end

    if category then
        addPlayerEvent(sendShowStoreOffers, 350, player, category)
    end
end

function parseRequestStoreOffers(player, msg)
    local serviceType = GameStore.ServiceTypes.SERVICE_STANDERD
    if player:getClient().version >= 1092 then
        serviceType = msg:getByte()
    end
    local categoryName = msg:getString()

    local category = GameStore.getCategoryByName(categoryName)
    if category then
        addPlayerEvent(sendShowStoreOffers, 350, player, category)
    end
end

function parseBuyStoreOffer(player, msg)
    local offerId = msg:getU32()
    local productType = msg:getByte()

    local offer = GameStore.getOfferById(offerId)
    if offer then
        -- If we don't add type, or offer type is fake
        if not offer.type or offer.type == GameStore.OfferTypes.OFFER_TYPE_NONE then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is either fake or corrupt.")
        end

        -- If no thing id,
        if offer.type ~= GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and offer.type ~= GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE and not offer.thingId then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is either fake or corrupt.")
        end

        -- We remove coins before doing everything, if it fails, we add coins back!
        if not player:canRemoveCoins(offer.price) then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "We couldn't remove coins from your account, try again later.")
        end

        -- count is used in type(item), so we need to show (i.e 10x crystal coins)
        local offerCountStr = offer.count and (offer.count .. "x ") or ""
        -- The message which we will send to player!
        local message = "You have purchased " .. offerCountStr .. offer.name .. " for " .. offer.price .. " coins."

        -- If offer is item.
        if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM then
            local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
            if inbox and inbox:getEmptySlots() > offer.count then
                for t = 1,offer.count do
                    if inbox:addItem(offer.thingId, offer.count or 1) then
                    else
                    player:canRemoveCoins(offer.price)
                    return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free cap or slots in your store inbox.")
                    end
                end
            else
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free cap or slots in your store inbox.")
            end
        -- If offer is Stackable.
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE then
            local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
            if inbox and inbox:getEmptySlots() > 0 then
                local parcel = inbox:addItem(2596, 1)
                local packagename = ''.. offer.count..'x '.. offer.name ..' package.'
                for e = 1,offer.count do
                    if parcel then
                        parcel:setAttribute(ITEM_ATTRIBUTE_NAME, packagename)
                        parcel:addItem(offer.thingId, 1)
                    else
                    player:canRemoveCoins(offer.count)
                    doPlayerRemoveItem(cid, 2596, 1)
                    return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free cap or slots in your store inbox.")
                    end
                end
            else
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free cap or slots in your store inbox.")
            end
        -- If offer is outfit/addon
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT or offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
            local outfitLookType
            if player:getSex() == PLAYERSEX_MALE then
                outfitLookType = offer.thingId.male
            else
                outfitLookType = offer.thingId.female
            end
            if not outfitLookType then
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This outfit seems not to suit your sex, we are sorry for that!")
            end

            player:addOutfitAddon(outfitLookType, offer.addon or 0)
        -- If offer is mount
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_MOUNT then
            player:addMount(offer.thingId)
        -- If offer is name change
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE then
            -- If player typed name yet!
            if productType == GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE then
                local newName = msg:getString()

                local resultId = db.storeQuery("SELECT * FROM `players` WHERE `name` = " .. db.escapeString(newName) .. "")
                if resultId ~= false then
                    return addPlayerEvent(sendStoreError, 650, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This name is already used, please try again!")
                end

                local result = GameStore.canChangeToName(newName)
                if not result.ability then
                    return addPlayerEvent(sendStoreError, 650, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, result.reason)
                end

                newName = newName:lower():gsub("(%l)(%w*)", function(a, b) return string.upper(a) .. b end)
                db.asyncQuery("UPDATE `players` SET `name` = " .. db.escapeString(newName) .. " WHERE `id` = " .. player:getGuid())
                message =  "You have successfully changed you name, you must relog to see changes."
            -- If not, we ask him to do!
            else
                return addPlayerEvent(sendRequestPurchaseData, 250, player, offer.id, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE)
            end
        -- If offer is sex change
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE then
            player:toggleSex()
        -- If offer is promotion
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PROMOTION then
            if not GameStore.addPromotionToPlayer(player, offer.thingId) then
                return false
            end
        -- You can add whatever offer types to suit your needs!
        else
            -- ToDo :: implement purchase function
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This offer is fake, please contact admin.")
        end
        -- Removing coins
        player:removeCoinsBalance(offer.price)
        -- We add this purchase to history!
        GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, offerCountStr .. offer.name, offer.price * -1)
        -- Send to client that purchase is successful!
        return addPlayerEvent(sendStorePurchaseSuccessful, 650, player, message)
    end

    -- If we didn't found the offer or error happened
    addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_INFORMATION, "We couldn't locate this offer, please try again later.")
end

-- Both functions use same formula!
function parseOpenTransactionHistory(player, msg)
    local page = 1
    GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE = msg:getByte()
    sendStoreTransactionHistory(player, page, GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE)
end

function parseRequestTransactionHistory(player, msg)
    local page = msg:getU32()
    sendStoreTransactionHistory(player, page, GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE)
end

--==Sending==--
function openStore(player)
    if not GameStore.Categories then
        return false
    end
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_OpenStore)
    msg:addByte(0x00)

    msg:addU16(#GameStore.Categories)
    for k, category in ipairs(GameStore.Categories) do
        msg:addString(category.name)
        msg:addString(category.description)

        if player:getClient().version >= 1093 then
            msg:addByte(category.state or GameStore.States.STATE_NONE)
        end

        msg:addByte(#category.icons)
        for m, icon in ipairs(category.icons) do
            msg:addString(icon)
        end

        msg:addString(category.parentCategory)
    end
    msg:sendToPlayer(player)

    sendCoinBalanceUpdating(player, true)
end

function sendShowStoreOffers(player, category)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_StoreOffers)

    msg:addString(category.name)

    msg:addU16(category.offers and #category.offers or 0x00)

    if category.offers then
        for k, offer in ipairs(category.offers) do
            msg:addU32(offer.id and offer.id or 0xFFFF) -- we later detect this number!

            local name = ""
            if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM and offer.count then
                name = offer.count .. "x "
            end

            if offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE and offer.count then
                name = offer.count .. "x "
            end

            name = name .. (offer.name or "Something Special")

            msg:addString(name)
            msg:addString(offer.description or GameStore.getDefaultDescription(offer.type))

            msg:addU32(offer.price and offer.price or 0xFFFF)
            msg:addByte(offer.state or GameStore.States.STATE_NONE) -- default is none

            local disabled, disabledReason = 0, ""
            if offer.disabled == true or not offer.type then
                disabled = 1
            end

            if offer.type ~= GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and offer.type ~= GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE and not offer.thingId then
                disabled = 1
            end

            if disabled == 1 and offer.disabledReason then -- dynamic disable
                disabledReason = offer.disabledReason
            end

            if disabled ~= 1 then
                if offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT or offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
                    local outfitLookType
                    if player:getSex() == PLAYERSEX_MALE then
                        outfitLookType = offer.thingId.male
                    else
                        outfitLookType = offer.thingId.female
                    end

                    if outfitLookType then
                        if offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT and player:hasOutfit(outfitLookType) then
                            disabled = 1
                            disabledReason = "You already have this outfit."
                        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
                            if player:hasOutfit(outfitLookType) then
                                if player:hasOutfit(outfitLookType, offer.addon) then
                                    disabled = 1
                                    disabledReason = "You already have this addon."
                                end
                            else
                                disabled = 1
                                disabledReason = "You don't have the outfit, you can't buy the addon."
                            end
                        end
                    else
                        disabled = 1
                        disabledReason = "The offer is fake."
                    end
                elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_MOUNT then
                    local hasMount = player:hasMount(offer.thingId)
                    if hasMount == true then
                        disabled = 1
                        disabledReason = "You already have this mount."
                    end
                elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PROMOTION then
                    if GameStore.canAddPromotionToPlayer(player, offer.thingId).ability == false then
                        disabled = 1
                        disabledReason = "You can't get this promotion"
                    end
                end
            end

            msg:addByte(disabled)

            if disabled == 1 and player:getClient().version >= 1093 then
                msg:addString(disabledReason)
            end

            msg:addByte(#offer.icons)
            for k, icon in ipairs(offer.icons) do
                msg:addString(icon)
            end

            msg:addU16(0) -- We still don't support SubOffers!
        end
    end
    msg:sendToPlayer(player)
end

function sendStoreTransactionHistory(player, page, entriesPerPage)
    local entries = GameStore.retrieveHistoryEntries(player:getAccountId()) -- this makes everything easy!
    if #entries == 0 then
        return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_HISTORY, "You don't have any entries yet.")
    end

    local toSkip = (page - 1) * entriesPerPage
    for i = 1, toSkip do
        table.remove(entries, 1) -- we remove first!
    end

    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_OpenTransactionHistory)
    msg:addU32(page)
    msg:addU32(#entries > entriesPerPage and 0x01 or 0x00)

    msg:addByte(#entries >= entriesPerPage and entriesPerPage or #entries)
    for k, entry in ipairs(entries) do
        if k >= entriesPerPage then break end
        msg:addU32(entry.time)
        msg:addByte(entry.mode)
        msg:addU32(entry.amount)
        msg:addString(entry.description)
    end
    msg:sendToPlayer(player)
end

function sendStorePurchaseSuccessful(player, message)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CompletePurchase)

    msg:addByte(0x00)

    msg:addString(message)
    msg:addU32(player:getCoinsBalance())
    msg:addU32(player:getCoinsBalance())

    msg:sendToPlayer(player)
end

function sendStoreError(player, errorType, message)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_StoreError)

    msg:addByte(errorType)
    msg:addString(message)

    msg:sendToPlayer(player)
end

function sendCoinBalanceUpdating(player, updating)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CoinBalanceUpdating)
    msg:addByte(0x00)
    msg:sendToPlayer(player)

    if updating == true then
        sendUpdateCoinBalance(player)
    end
end

function sendUpdateCoinBalance(player)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CoinBalanceUpdating)
    msg:addByte(0x01)

    msg:addByte(GameStore.SendingPackets.S_CoinBalance)
    msg:addByte(0x01)

    msg:addU32(player:getCoinsBalance())
    msg:addU32(player:getCoinsBalance())

    msg:sendToPlayer(player)
end

function sendRequestPurchaseData(player, offerId, type)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_RequestPurchaseData)
    msg:addU32(offerId)
    msg:addByte(type)
    msg:sendToPlayer(player)
end

--==GameStoreFunctions==--
GameStore.getCategoryByName = function(name)
    for k, category in ipairs(GameStore.Categories) do
        if category.name:lower() == name:lower() then
            return category
        end
    end
    return nil
end

GameStore.getOfferById = function(id)
    for Cat_k, category in ipairs(GameStore.Categories) do
        if category.offers then
            for Off_k, offer in ipairs(category.offers) do
                if offer.id == id then
                    return offer
                end
            end
        end
    end
    return nil
end

GameStore.insertHistory = function(accountId, mode, description, amount)
    return db.asyncQuery(string.format("INSERT INTO `store_history`(`account_id`, `mode`, `description`, `coin_amount`, `time`) VALUES (%s, %s, %s, %s, %s)", accountId, mode, db.escapeString(description), amount, os.time()))
end

GameStore.retrieveHistoryEntries = function(accountId)
    local entries = {}
    local resultId = db.storeQuery("SELECT * FROM `store_history` WHERE `account_id` = " .. accountId .. " ORDER BY `time` DESC LIMIT 15;")
    if resultId ~= false then
        repeat
            local entry = {
                mode = result.getDataInt(resultId, "mode"),
                description = result.getDataString(resultId, "description"),
                amount = result.getDataInt(resultId, "coin_amount"),
                time = result.getDataInt(resultId, "time"),
            }
            table.insert(entries, entry)
        until not result.next(resultId)
        result.free(resultId)
    end
    return entries
end

GameStore.getDefaultDescription = function(offerType)
    local t, descList = GameStore.OfferTypes
    if offerType == t.OFFER_TYPE_OUTFIT or offerType == t.OFFER_TYPE_OUTFIT_ADDON then
        descList = GameStore.DefaultDescriptions.OUTFIT
    elseif offerType == t.OFFER_TYPE_MOUNT then
        descList = GameStore.DefaultDescriptions.MOUNT
    elseif offerType == t.OFFER_TYPE_NAMECHANGE then
        descList = GameStore.DefaultDescriptions.NAMECHANGE
    elseif offerType == t.OFFER_TYPE_SEXCHANGE then
        descList = GameStore.DefaultDescriptions.SEXCHANGE
    else
        return ""
    end

    return descList[math.floor(math.random(1, #descList))] or ""
end

GameStore.canChangeToName = function(name)
    local result = {
        ability = false
    }
    if name:len() < 3 or name:len() > 14 then
        result.reason = "Your new name's length should be lower than 3 or higher than 14."
        return result
    end

    -- just copied from znote aac.
    local words = {"owner", "gamemaster", "hoster", "admin", "staff", "tibia", "account", "god", "anal", "ass", "fuck", "sex", "hitler", "pussy", "dick", "rape", "cm", "gm", "tutor", "counsellor"}
    local split = name:split(" ")
    for k, word in ipairs(words) do
        for k, nameWord in ipairs(split) do
            if nameWord:lower() == word then
                result.reason = "You can't use word \"" .. word .. "\" in your new name."
                return result
            end
        end
    end

    if MonsterType(name) then
        result.reason = "Your new name \"" .. name .. "\" can't be a monster's name."
        return result
    elseif Npc(name) then
        result.reason = "Your new name \"" .. name .. "\" can't be a npc's name."
        return result
    end

    local letters = "{}|_*+-=<>0123456789@#%^&()/*\\.,:;~!\"$"
    for i = 1, letters:len() do
        local c = letters:sub(i, i)
        for i = 1, name:len() do
            local m = name:sub(i, i)
            if m == c then
                result.reason = "You can't use this letter \"" .. c .. "\" in your new name."
                return result
            end
        end
    end
    result.ability = true
    return result
end

GameStore.canAddPromotionToPlayer = function(player, promotion, send)
    local result = {
        ability = true
    }
    local vocation = player:getVocation()
    -- Working --
    local vocationCopy, baseVocation = vocation, vocation
    vocation = vocation:getDemotion()
    while vocation do
        baseVocation = vocation
        vocation = vocation:getDemotion()
    end

    local baseVocationsCount = GameStore.BaseVocationsCount or 4

    local newVocId = (baseVocationsCount * promotion) + baseVocation:getId()

    if not Vocation(newVocId) then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is fake, please report it!")
        end
        result.ability = false
        return result
    end
    -- If promotion is less than player's voc, or player don't have previous promotion
    if newVocId <= vocationCopy:getId() then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You already have this promotion!")
        end
        result.ability = false
        return result
    end

    if (newVocId - baseVocationsCount) ~= vocationCopy:getId() then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You need higher promotion to get his one.")
        end
        result.ability = false
        return result
    end

    result.vocId = newVocId
    return result
end

GameStore.addPromotionToPlayer = function(player, promotion)
    local result = GameStore.canAddPromotionToPlayer(player, promotion, true)
    if result.ability == false then return false end

    local basics = {
        health = 185,
        mana = 40,
        cap = 500
    }

    player:setVocation(result.vocId)
    local newVoc = player:getVocation()
    player:setMaxHealth(basics.health + (newVoc:getHealthGain() * player:getLevel()))
    player:setMaxMana(basics.mana + (newVoc:getManaGain() * player:getLevel()))
    player:setCapacity(basics.cap + (newVoc:getCapacityGain() * player:getLevel()))

    player:addHealth(player:getMaxHealth())
    player:addMana(player:getMaxMana())

    player:sendTextMessage(MESSAGE_INFO_DESCR, "You have been promoted to " .. newVoc:getName())
    return true
end

--==Player==--
function Player.getCoinsBalance(self)
    resultId = db.storeQuery("SELECT `coins` FROM `accounts` WHERE `id` = " .. self:getAccountId())
    if not resultId then return 0 end
    return result.getDataInt(resultId, "coins")
end

function Player.setCoinsBalance(self, coins)
    db.asyncQuery("UPDATE `accounts` SET `coins` = " .. coins .. " WHERE `id` = " .. self:getAccountId())
    return true
end

function Player.canRemoveCoins(self, coins)
    if self:getCoinsBalance() < coins then
        return false
    end
    return true
end

function Player.removeCoinsBalance(self, coins)
    if self:canRemoveCoins(coins) then
        return self:setCoinsBalance(self:getCoinsBalance() - coins)
    end

    return false
end

function Player.addCoinsBalance(self, coins, update)
    self:setCoinsBalance(self:getCoinsBalance() + coins)
    if update then sendCoinBalanceUpdating(self, true) end
    return true
end

function Player.toggleSex(self)
    local currentSex = self:getSex()
    local playerOutfit = self:getOutfit()

    if currentSex == PLAYERSEX_FEMALE then
        self:setSex(PLAYERSEX_MALE)
        playerOutfit.lookType = 128
    else
        self:setSex(PLAYERSEX_FEMALE)
        playerOutfit.lookType = 136
    end
    self:setOutfit(playerOutfit)
end
 



Foi útil? Rep+ (foi difícil arrumar, apesar de parecer simples)

Foto

  Mostrar conteúdo oculto

 

 

bug arrumado.png

Só existe um detalhe: Quando o player tem pouca cap e por exemplo, compra uma grande quantidade de items agrupaveis, a store envia a quantidade que o player suporta, mas não desconta dos coins do mesmo.

CONHEÇA MEU PROJETO:

WWW.ETERNUS-GLOBAL.COM

 

tibia-logo.gif

Link para o post
Compartilhar em outros sites

 

16 horas atrás, Strix Seran disse:

Só existe um detalhe: Quando o player tem pouca cap e por exemplo, compra uma grande quantidade de items agrupaveis, a store envia a quantidade que o player suporta, mas não desconta dos coins do mesmo.



Use este: 

Spoiler

-- Please don't edit those information!
GameStore = {
    ModuleName = "GameStore",
    Developer = "OTX TEAM",
    Version = "0.3",
    LastUpdated = "24-09-2016 07:15PM"
}

--== Enums ==--
GameStore.OfferTypes = {
    OFFER_TYPE_NONE = 0, -- (this will disable offer)
    OFFER_TYPE_ITEM = 1,
    OFFER_TYPE_STACKABLE = 2,
    OFFER_TYPE_OUTFIT = 3,
    OFFER_TYPE_OUTFIT_ADDON = 4,
    OFFER_TYPE_MOUNT = 5,
    OFFER_TYPE_NAMECHANGE = 6,
    OFFER_TYPE_SEXCHANGE = 7,
    OFFER_TYPE_PROMOTION = 8
}

GameStore.ClientOfferTypes = {
    CLIENT_STORE_OFFER_OTHER = 0,
    CLIENT_STORE_OFFER_NAMECHANGE = 1
}

GameStore.HistoryTypes = {
    HISTORY_TYPE_NONE = 0,
    HISTORY_TYPE_GIFT = 1,
    HISTORY_TYPE_REFUND = 2
}

GameStore.States = {
    STATE_NONE = 0,
    STATE_NEW = 1,
    STATE_SALE = 2,
    STATE_TIMED = 3
}

GameStore.StoreErrors = {
    STORE_ERROR_PURCHASE = 0,
    STORE_ERROR_NETWORK = 1,
    STORE_ERROR_HISTORY = 2,
    STORE_ERROR_TRANSFER = 3,
    STORE_ERROR_INFORMATION = 4
}

GameStore.ServiceTypes = {
    SERVICE_STANDERD = 0,
    SERVICE_OUTFITS = 3,
    SERVICE_MOUNTS = 4
}

GameStore.SendingPackets = {
    S_CoinBalance = 0xDF, -- 223
    S_StoreError = 0xE0, -- 224
    S_RequestPurchaseData = 0xE1, -- 225
    S_CoinBalanceUpdating = 0xF2, -- 242
    S_OpenStore = 0xFB, -- 251
    S_StoreOffers = 0xFC, -- 252
    S_OpenTransactionHistory = 0xFD, -- 253
    S_CompletePurchase = 0xFE  -- 254
}
GameStore.RecivedPackets = {
    C_StoreEvent = 0xE9, -- 233
    C_TransferCoins = 0xEF, -- 239
    C_OpenStore = 0xFA, -- 250
    C_RequestStoreOffers = 0xFB, -- 251
    C_BuyStoreOffer = 0xFC, -- 252
    C_OpenTransactionHistory = 0xFD, -- 253
    C_RequestTransactionHistory = 0xFE, -- 254
}

GameStore.DefaultValues = {
    DEFAULT_VALUE_ENTRIES_PER_PAGE    = 16
}
GameStore.DefaultDescriptions = {
    OUTFIT = {"This outfit looks nice. Only high-class people are able to wear it!",
        "An outfit that was created to suit you. We are sure you'll like it.",
        "Legend says only smart people should wear it, otherwise you will burn!"},
    MOUNT = {"This is a fantastic mount that helps to become faster, try it!",
        "The first rider of this mount became the leader of his country! legends say that."},
    NAMECHANGE = {"Are you hunted? Tired of that? Get a new name, a new life!",
        "A new name to suit your needs!"},
    SEXCHANGE = {"Bored of your character's sex? Get a new sex for him now!!"}
}

--==Parsing==--
GameStore.isItsPacket = function(byte)
    for k, v in pairs(GameStore.RecivedPackets) do
        if v == byte then
            return true
        end
    end
    return false
end

function onRecvbyte(player, msg, byte)
    if byte == GameStore.RecivedPackets.C_StoreEvent then
        -- Not Used!
    elseif byte == GameStore.RecivedPackets.C_TransferCoins then
        parseTransferCoins(player, msg)
    elseif byte == GameStore.RecivedPackets.C_OpenStore then
        parseOpenStore(player, msg)
    elseif byte == GameStore.RecivedPackets.C_RequestStoreOffers then
        parseRequestStoreOffers(player, msg)
    elseif byte == GameStore.RecivedPackets.C_BuyStoreOffer then
        parseBuyStoreOffer(player, msg)
    elseif byte == GameStore.RecivedPackets.C_OpenTransactionHistory then
        parseOpenTransactionHistory(player, msg)
    elseif byte == GameStore.RecivedPackets.C_RequestTransactionHistory then
        parseRequestTransactionHistory(player, msg)
    end
    return true
end
function parseTransferCoins(player, msg)
    local reciver = msg:getString()
    local amount = msg:getU32()

    if reciver:lower() == player:getName():lower() then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You can't transfer coins to yourself.")
    end

    local resultId = db.storeQuery("SELECT `account_id` FROM `players` WHERE `name` = " .. db.escapeString(reciver:lower()) .. "")
    if not resultId then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "We couldn't find that player.")
    end

    local accountId = result.getDataInt(resultId, "account_id")
    if accountId == player:getAccountId() then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You cannot transfer coin to a character in the same account.")
    end

    if not player:canRemoveCoins(amount) then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You don't have enough funds to transfer these coins.")
    end

    player:removeCoinsBalance(amount)
    db.asyncQuery("UPDATE `accounts` SET `coins` = `coins` + " .. amount .. " WHERE `id` = " .. accountId)
    addPlayerEvent(sendStorePurchaseSuccessful, 550, player, "You have transfered " .. amount .. " coins to " .. reciver .. " successfully")

    -- Adding history for both reciver/sender
    GameStore.insertHistory(accountId, GameStore.HistoryTypes.HISTORY_TYPE_NONE, player:getName() .. " transfered you this amount.", amount)
    GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, "You transfered this amount to " .. reciver, -1 * amount) -- negative
end

function parseOpenStore(player, msg)
    openStore(player)

    local serviceType = msg:getByte()
    local category = GameStore.Categories and GameStore.Categories[1] or nil

    if serviceType == GameStore.ServiceTypes.SERVICE_OUTFITS then
        category = GameStore.getCategoryByName("outfits")
    elseif serviceType == GameStore.ServiceTypes.SERVICE_MOUNTS then
        category = GameStore.getCategoryByName("mounts")
    end

    if category then
        addPlayerEvent(sendShowStoreOffers, 350, player, category)
    end
end

function parseRequestStoreOffers(player, msg)
    local serviceType = GameStore.ServiceTypes.SERVICE_STANDERD
    if player:getClient().version >= 1092 then
        serviceType = msg:getByte()
    end
    local categoryName = msg:getString()

    local category = GameStore.getCategoryByName(categoryName)
    if category then
        addPlayerEvent(sendShowStoreOffers, 350, player, category)
    end
end

function parseBuyStoreOffer(player, msg)
    local offerId = msg:getU32()
    local productType = msg:getByte()

    local offer = GameStore.getOfferById(offerId)
    if offer then
        -- If we don't add type, or offer type is fake
        if not offer.type or offer.type == GameStore.OfferTypes.OFFER_TYPE_NONE then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is either fake or corrupt.")
        end

        -- If no thing id,
        if offer.type ~= GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and offer.type ~= GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE and not offer.thingId then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is either fake or corrupt.")
        end

        -- We remove coins before doing everything, if it fails, we add coins back!
        if not player:canRemoveCoins(offer.price) then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "We couldn't remove coins from your account, try again later.")
        end

        -- count is used in type(item), so we need to show (i.e 10x crystal coins)
        local offerCountStr = offer.count and (offer.count .. "x ") or ""
        -- The message which we will send to player!
        local message = "You have purchased " .. offerCountStr .. offer.name .. " for " .. offer.price .. " coins."

        -- If offer is item.
        if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM then
            local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
            if inbox and inbox:getEmptySlots() > offer.count then
                if player:getFreeCapacity() > ItemType(offer.thingId):getWeight(offer.count or 1) then
                    for t = 1,offer.count do
                        inbox:addItem(offer.thingId, offer.count or 1)
                    end
                else
                    return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You don't have enough capacity.")
                end
            else
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free slots in your store inbox.")
            end
            -- If offer is Stackable.
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE then
            local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
            if inbox and inbox:getEmptySlots() > 0 then
                if player:getFreeCapacity() > (ItemType(offer.thingId):getWeight(offer.count or 1) + ItemType(2596):getWeight(1)) then -- if player has cap for a parcel + offer weight
                    local parcel = inbox:addItem(2596, 1)
                    local packagename = ''.. offer.count..'x '.. offer.name ..' package.'
                    local pendingCount = offer.count;
                    if parcel then
                        parcel:setAttribute(ITEM_ATTRIBUTE_NAME, packagename)
                        while pendingCount > 0  do
                            if(pendingCount <= 100) then
                                parcel:addItem(offer.thingId, pendingCount)
                                pendingCount = 0
                            else
                                parcel:addItem(offer.thingId, 100)
                                pendingCount= pendingCount - 100
                            end
                        end
                    else --error creating the parcel item
                        return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "There was an error processing your purchase and your coins were not used. If this error persists, contact someone from staff.")
                    end
                else
                    return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You don't have enough capacity.")
                end
            else
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free slots in your store inbox.")
            end
            -- If offer is outfit/addon
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT or offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
            local outfitLookType
            if player:getSex() == PLAYERSEX_MALE then
                outfitLookType = offer.thingId.male
            else
                outfitLookType = offer.thingId.female
            end
            if not outfitLookType then
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This outfit seems not to suit your sex, we are sorry for that!")
            end

            player:addOutfitAddon(outfitLookType, offer.addon or 0)
            -- If offer is mount
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_MOUNT then
            player:addMount(offer.thingId)
            -- If offer is name change
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE then
            -- If player typed name yet!
            if productType == GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE then
                local newName = msg:getString()

                local resultId = db.storeQuery("SELECT * FROM `players` WHERE `name` = " .. db.escapeString(newName) .. "")
                if resultId ~= false then
                    return addPlayerEvent(sendStoreError, 650, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This name is already used, please try again!")
                end

                local result = GameStore.canChangeToName(newName)
                if not result.ability then
                    return addPlayerEvent(sendStoreError, 650, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, result.reason)
                end

                newName = newName:lower():gsub("(%l)(%w*)", function(a, b) return string.upper(a) .. b end)
                db.asyncQuery("UPDATE `players` SET `name` = " .. db.escapeString(newName) .. " WHERE `id` = " .. player:getGuid())
                message =  "You have successfully changed you name, you must relog to see changes."
                -- If not, we ask him to do!
            else
                return addPlayerEvent(sendRequestPurchaseData, 250, player, offer.id, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE)
            end
            -- If offer is sex change
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE then
            player:toggleSex()
            -- If offer is promotion
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PROMOTION then
            if not GameStore.addPromotionToPlayer(player, offer.thingId) then
                return false
            end
            -- You can add whatever offer types to suit your needs!
        else
            -- ToDo :: implement purchase function
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This offer is fake, please contact admin.")
        end
        -- Removing coins
        player:removeCoinsBalance(offer.price)
        -- We add this purchase to history!
        GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, offerCountStr .. offer.name, offer.price * -1)
        -- Send to client that purchase is successful!
        return addPlayerEvent(sendStorePurchaseSuccessful, 650, player, message)
    end

    -- If we didn't found the offer or error happened
    addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_INFORMATION, "We couldn't locate this offer, please try again later.")
end

-- Both functions use same formula!
function parseOpenTransactionHistory(player, msg)
    local page = 1
    GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE = msg:getByte()
    sendStoreTransactionHistory(player, page, GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE)
end

function parseRequestTransactionHistory(player, msg)
    local page = msg:getU32()
    sendStoreTransactionHistory(player, page, GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE)
end

--==Sending==--
function openStore(player)
    if not GameStore.Categories then
        return false
    end
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_OpenStore)
    msg:addByte(0x00)

    msg:addU16(#GameStore.Categories)
    for k, category in ipairs(GameStore.Categories) do
        msg:addString(category.name)
        msg:addString(category.description)

        if player:getClient().version >= 1093 then
            msg:addByte(category.state or GameStore.States.STATE_NONE)
        end

        msg:addByte(#category.icons)
        for m, icon in ipairs(category.icons) do
            msg:addString(icon)
        end

        msg:addString(category.parentCategory)
    end
    msg:sendToPlayer(player)

    sendCoinBalanceUpdating(player, true)
end

function sendShowStoreOffers(player, category)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_StoreOffers)

    msg:addString(category.name)

    msg:addU16(category.offers and #category.offers or 0x00)

    if category.offers then
        for k, offer in ipairs(category.offers) do
            msg:addU32(offer.id and offer.id or 0xFFFF) -- we later detect this number!

            local name = ""
            if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM and offer.count then
                name = offer.count .. "x "
            end

            if offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE and offer.count then
                name = offer.count .. "x "
            end

            name = name .. (offer.name or "Something Special")

            msg:addString(name)
            msg:addString(offer.description or GameStore.getDefaultDescription(offer.type))

            msg:addU32(offer.price and offer.price or 0xFFFF)
            msg:addByte(offer.state or GameStore.States.STATE_NONE) -- default is none

            local disabled, disabledReason = 0, ""
            if offer.disabled == true or not offer.type then
                disabled = 1
            end

            if offer.type ~= GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and offer.type ~= GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE and not offer.thingId then
                disabled = 1
            end

            if disabled == 1 and offer.disabledReason then -- dynamic disable
                disabledReason = offer.disabledReason
            end

            if disabled ~= 1 then
                if offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT or offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
                    local outfitLookType
                    if player:getSex() == PLAYERSEX_MALE then
                        outfitLookType = offer.thingId.male
                    else
                        outfitLookType = offer.thingId.female
                    end

                    if outfitLookType then
                        if offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT and player:hasOutfit(outfitLookType) then
                            disabled = 1
                            disabledReason = "You already have this outfit."
                        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
                            if player:hasOutfit(outfitLookType) then
                                if player:hasOutfit(outfitLookType, offer.addon) then
                                    disabled = 1
                                    disabledReason = "You already have this addon."
                                end
                            else
                                disabled = 1
                                disabledReason = "You don't have the outfit, you can't buy the addon."
                            end
                        end
                    else
                        disabled = 1
                        disabledReason = "The offer is fake."
                    end
                elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_MOUNT then
                    local hasMount = player:hasMount(offer.thingId)
                    if hasMount == true then
                        disabled = 1
                        disabledReason = "You already have this mount."
                    end
                elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PROMOTION then
                    if GameStore.canAddPromotionToPlayer(player, offer.thingId).ability == false then
                        disabled = 1
                        disabledReason = "You can't get this promotion"
                    end
                end
            end

            msg:addByte(disabled)

            if disabled == 1 and player:getClient().version >= 1093 then
                msg:addString(disabledReason)
            end

            msg:addByte(#offer.icons)
            for k, icon in ipairs(offer.icons) do
                msg:addString(icon)
            end

            msg:addU16(0) -- We still don't support SubOffers!
        end
    end
    msg:sendToPlayer(player)
end

function sendStoreTransactionHistory(player, page, entriesPerPage)
    local entries = GameStore.retrieveHistoryEntries(player:getAccountId()) -- this makes everything easy!
    if #entries == 0 then
        return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_HISTORY, "You don't have any entries yet.")
    end

    local toSkip = (page - 1) * entriesPerPage
    for i = 1, toSkip do
        table.remove(entries, 1) -- we remove first!
    end

    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_OpenTransactionHistory)
    msg:addU32(page)
    msg:addU32(#entries > entriesPerPage and 0x01 or 0x00)

    msg:addByte(#entries >= entriesPerPage and entriesPerPage or #entries)
    for k, entry in ipairs(entries) do
        if k >= entriesPerPage then break end
        msg:addU32(entry.time)
        msg:addByte(entry.mode)
        msg:addU32(entry.amount)
        msg:addString(entry.description)
    end
    msg:sendToPlayer(player)
end

function sendStorePurchaseSuccessful(player, message)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CompletePurchase)

    msg:addByte(0x00)

    msg:addString(message)
    msg:addU32(player:getCoinsBalance())
    msg:addU32(player:getCoinsBalance())

    msg:sendToPlayer(player)
end

function sendStoreError(player, errorType, message)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_StoreError)

    msg:addByte(errorType)
    msg:addString(message)

    msg:sendToPlayer(player)
end

function sendCoinBalanceUpdating(player, updating)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CoinBalanceUpdating)
    msg:addByte(0x00)
    msg:sendToPlayer(player)

    if updating == true then
        sendUpdateCoinBalance(player)
    end
end

function sendUpdateCoinBalance(player)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CoinBalanceUpdating)
    msg:addByte(0x01)

    msg:addByte(GameStore.SendingPackets.S_CoinBalance)
    msg:addByte(0x01)

    msg:addU32(player:getCoinsBalance())
    msg:addU32(player:getCoinsBalance())

    msg:sendToPlayer(player)
end

function sendRequestPurchaseData(player, offerId, type)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_RequestPurchaseData)
    msg:addU32(offerId)
    msg:addByte(type)
    msg:sendToPlayer(player)
end

--==GameStoreFunctions==--
GameStore.getCategoryByName = function(name)
    for k, category in ipairs(GameStore.Categories) do
        if category.name:lower() == name:lower() then
            return category
        end
    end
    return nil
end

GameStore.getOfferById = function(id)
    for Cat_k, category in ipairs(GameStore.Categories) do
        if category.offers then
            for Off_k, offer in ipairs(category.offers) do
                if offer.id == id then
                    return offer
                end
            end
        end
    end
    return nil
end

GameStore.insertHistory = function(accountId, mode, description, amount)
    return db.asyncQuery(string.format("INSERT INTO `store_history`(`account_id`, `mode`, `description`, `coin_amount`, `time`) VALUES (%s, %s, %s, %s, %s)", accountId, mode, db.escapeString(description), amount, os.time()))
end

GameStore.retrieveHistoryEntries = function(accountId)
    local entries = {}
    local resultId = db.storeQuery("SELECT * FROM `store_history` WHERE `account_id` = " .. accountId .. " ORDER BY `time` DESC LIMIT 15;")
    if resultId ~= false then
        repeat
            local entry = {
                mode = result.getDataInt(resultId, "mode"),
                description = result.getDataString(resultId, "description"),
                amount = result.getDataInt(resultId, "coin_amount"),
                time = result.getDataInt(resultId, "time"),
            }
            table.insert(entries, entry)
        until not result.next(resultId)
        result.free(resultId)
    end
    return entries
end

GameStore.getDefaultDescription = function(offerType)
    local t, descList = GameStore.OfferTypes
    if offerType == t.OFFER_TYPE_OUTFIT or offerType == t.OFFER_TYPE_OUTFIT_ADDON then
        descList = GameStore.DefaultDescriptions.OUTFIT
    elseif offerType == t.OFFER_TYPE_MOUNT then
        descList = GameStore.DefaultDescriptions.MOUNT
    elseif offerType == t.OFFER_TYPE_NAMECHANGE then
        descList = GameStore.DefaultDescriptions.NAMECHANGE
    elseif offerType == t.OFFER_TYPE_SEXCHANGE then
        descList = GameStore.DefaultDescriptions.SEXCHANGE
    else
        return ""
    end

    return descList[math.floor(math.random(1, #descList))] or ""
end

GameStore.canChangeToName = function(name)
    local result = {
        ability = false
    }
    if name:len() < 3 or name:len() > 14 then
        result.reason = "Your new name's length should be lower than 3 or higher than 14."
        return result
    end

    -- just copied from znote aac.
    local words = {"owner", "gamemaster", "hoster", "admin", "staff", "tibia", "account", "god", "anal", "ass", "fuck", "sex", "hitler", "pussy", "dick", "rape", "cm", "gm", "tutor", "counsellor"}
    local split = name:split(" ")
    for k, word in ipairs(words) do
        for k, nameWord in ipairs(split) do
            if nameWord:lower() == word then
                result.reason = "You can't use word \"" .. word .. "\" in your new name."
                return result
            end
        end
    end

    if MonsterType(name) then
        result.reason = "Your new name \"" .. name .. "\" can't be a monster's name."
        return result
    elseif Npc(name) then
        result.reason = "Your new name \"" .. name .. "\" can't be a npc's name."
        return result
    end

    local letters = "{}|_*+-=<>0123456789@#%^&()/*\\.,:;~!\"$"
    for i = 1, letters:len() do
        local c = letters:sub(i, i)
        for i = 1, name:len() do
            local m = name:sub(i, i)
            if m == c then
                result.reason = "You can't use this letter \"" .. c .. "\" in your new name."
                return result
            end
        end
    end
    result.ability = true
    return result
end

GameStore.canAddPromotionToPlayer = function(player, promotion, send)
    local result = {
        ability = true
    }
    local vocation = player:getVocation()
    -- Working --
    local vocationCopy, baseVocation = vocation, vocation
    vocation = vocation:getDemotion()
    while vocation do
        baseVocation = vocation
        vocation = vocation:getDemotion()
    end

    local baseVocationsCount = GameStore.BaseVocationsCount or 4

    local newVocId = (baseVocationsCount * promotion) + baseVocation:getId()

    if not Vocation(newVocId) then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is fake, please report it!")
        end
        result.ability = false
        return result
    end
    -- If promotion is less than player's voc, or player don't have previous promotion
    if newVocId <= vocationCopy:getId() then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You already have this promotion!")
        end
        result.ability = false
        return result
    end

    if (newVocId - baseVocationsCount) ~= vocationCopy:getId() then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You need higher promotion to get his one.")
        end
        result.ability = false
        return result
    end

    result.vocId = newVocId
    return result
end

GameStore.addPromotionToPlayer = function(player, promotion)
    local result = GameStore.canAddPromotionToPlayer(player, promotion, true)
    if result.ability == false then return false end

    local basics = {
        health = 185,
        mana = 40,
        cap = 500
    }

    player:setVocation(result.vocId)
    local newVoc = player:getVocation()
    player:setMaxHealth(basics.health + (newVoc:getHealthGain() * player:getLevel()))
    player:setMaxMana(basics.mana + (newVoc:getManaGain() * player:getLevel()))
    player:setCapacity(basics.cap + (newVoc:getCapacityGain() * player:getLevel()))

    player:addHealth(player:getMaxHealth())
    player:addMana(player:getMaxMana())

    player:sendTextMessage(MESSAGE_INFO_DESCR, "You have been promoted to " .. newVoc:getName())
    return true
end

--==Player==--
function Player.getCoinsBalance(self)
    resultId = db.storeQuery("SELECT `coins` FROM `accounts` WHERE `id` = " .. self:getAccountId())
    if not resultId then return 0 end
    return result.getDataInt(resultId, "coins")
end

function Player.setCoinsBalance(self, coins)
    db.asyncQuery("UPDATE `accounts` SET `coins` = " .. coins .. " WHERE `id` = " .. self:getAccountId())
    return true
end

function Player.canRemoveCoins(self, coins)
    if self:getCoinsBalance() < coins then
        return false
    end
    return true
end

function Player.removeCoinsBalance(self, coins)
    if self:canRemoveCoins(coins) then
        return self:setCoinsBalance(self:getCoinsBalance() - coins)
    end

    return false
end

function Player.addCoinsBalance(self, coins, update)
    self:setCoinsBalance(self:getCoinsBalance() + coins)
    if update then sendCoinBalanceUpdating(self, true) end
    return true
end

function Player.toggleSex(self)
    local currentSex = self:getSex()
    local playerOutfit = self:getOutfit()

    if currentSex == PLAYERSEX_FEMALE then
        self:setSex(PLAYERSEX_MALE)
        playerOutfit.lookType = 128
    else
        self:setSex(PLAYERSEX_FEMALE)
        playerOutfit.lookType = 136
    end
    self:setOutfit(playerOutfit)
end
 

100% livre de bugs.

Lembrando que os créditos é do pessoal da OTX Team, por fixar o bug 100%

https://github.com/mattyx14/otxserver/pull/186

Editado por Eduardo Dantas (veja o histórico de edições)
Link para o post
Compartilhar em outros sites
1 hora atrás, Eduardo Dantas disse:

 



Use este: 

  Ocultar conteúdo

-- Please don't edit those information!
GameStore = {
    ModuleName = "GameStore",
    Developer = "OTX TEAM",
    Version = "0.3",
    LastUpdated = "24-09-2016 07:15PM"
}

--== Enums ==--
GameStore.OfferTypes = {
    OFFER_TYPE_NONE = 0, -- (this will disable offer)
    OFFER_TYPE_ITEM = 1,
    OFFER_TYPE_STACKABLE = 2,
    OFFER_TYPE_OUTFIT = 3,
    OFFER_TYPE_OUTFIT_ADDON = 4,
    OFFER_TYPE_MOUNT = 5,
    OFFER_TYPE_NAMECHANGE = 6,
    OFFER_TYPE_SEXCHANGE = 7,
    OFFER_TYPE_PROMOTION = 8
}

GameStore.ClientOfferTypes = {
    CLIENT_STORE_OFFER_OTHER = 0,
    CLIENT_STORE_OFFER_NAMECHANGE = 1
}

GameStore.HistoryTypes = {
    HISTORY_TYPE_NONE = 0,
    HISTORY_TYPE_GIFT = 1,
    HISTORY_TYPE_REFUND = 2
}

GameStore.States = {
    STATE_NONE = 0,
    STATE_NEW = 1,
    STATE_SALE = 2,
    STATE_TIMED = 3
}

GameStore.StoreErrors = {
    STORE_ERROR_PURCHASE = 0,
    STORE_ERROR_NETWORK = 1,
    STORE_ERROR_HISTORY = 2,
    STORE_ERROR_TRANSFER = 3,
    STORE_ERROR_INFORMATION = 4
}

GameStore.ServiceTypes = {
    SERVICE_STANDERD = 0,
    SERVICE_OUTFITS = 3,
    SERVICE_MOUNTS = 4
}

GameStore.SendingPackets = {
    S_CoinBalance = 0xDF, -- 223
    S_StoreError = 0xE0, -- 224
    S_RequestPurchaseData = 0xE1, -- 225
    S_CoinBalanceUpdating = 0xF2, -- 242
    S_OpenStore = 0xFB, -- 251
    S_StoreOffers = 0xFC, -- 252
    S_OpenTransactionHistory = 0xFD, -- 253
    S_CompletePurchase = 0xFE  -- 254
}
GameStore.RecivedPackets = {
    C_StoreEvent = 0xE9, -- 233
    C_TransferCoins = 0xEF, -- 239
    C_OpenStore = 0xFA, -- 250
    C_RequestStoreOffers = 0xFB, -- 251
    C_BuyStoreOffer = 0xFC, -- 252
    C_OpenTransactionHistory = 0xFD, -- 253
    C_RequestTransactionHistory = 0xFE, -- 254
}

GameStore.DefaultValues = {
    DEFAULT_VALUE_ENTRIES_PER_PAGE    = 16
}
GameStore.DefaultDescriptions = {
    OUTFIT = {"This outfit looks nice. Only high-class people are able to wear it!",
        "An outfit that was created to suit you. We are sure you'll like it.",
        "Legend says only smart people should wear it, otherwise you will burn!"},
    MOUNT = {"This is a fantastic mount that helps to become faster, try it!",
        "The first rider of this mount became the leader of his country! legends say that."},
    NAMECHANGE = {"Are you hunted? Tired of that? Get a new name, a new life!",
        "A new name to suit your needs!"},
    SEXCHANGE = {"Bored of your character's sex? Get a new sex for him now!!"}
}

--==Parsing==--
GameStore.isItsPacket = function(byte)
    for k, v in pairs(GameStore.RecivedPackets) do
        if v == byte then
            return true
        end
    end
    return false
end

function onRecvbyte(player, msg, byte)
    if byte == GameStore.RecivedPackets.C_StoreEvent then
        -- Not Used!
    elseif byte == GameStore.RecivedPackets.C_TransferCoins then
        parseTransferCoins(player, msg)
    elseif byte == GameStore.RecivedPackets.C_OpenStore then
        parseOpenStore(player, msg)
    elseif byte == GameStore.RecivedPackets.C_RequestStoreOffers then
        parseRequestStoreOffers(player, msg)
    elseif byte == GameStore.RecivedPackets.C_BuyStoreOffer then
        parseBuyStoreOffer(player, msg)
    elseif byte == GameStore.RecivedPackets.C_OpenTransactionHistory then
        parseOpenTransactionHistory(player, msg)
    elseif byte == GameStore.RecivedPackets.C_RequestTransactionHistory then
        parseRequestTransactionHistory(player, msg)
    end
    return true
end
function parseTransferCoins(player, msg)
    local reciver = msg:getString()
    local amount = msg:getU32()

    if reciver:lower() == player:getName():lower() then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You can't transfer coins to yourself.")
    end

    local resultId = db.storeQuery("SELECT `account_id` FROM `players` WHERE `name` = " .. db.escapeString(reciver:lower()) .. "")
    if not resultId then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "We couldn't find that player.")
    end

    local accountId = result.getDataInt(resultId, "account_id")
    if accountId == player:getAccountId() then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You cannot transfer coin to a character in the same account.")
    end

    if not player:canRemoveCoins(amount) then
        return addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_TRANSFER, "You don't have enough funds to transfer these coins.")
    end

    player:removeCoinsBalance(amount)
    db.asyncQuery("UPDATE `accounts` SET `coins` = `coins` + " .. amount .. " WHERE `id` = " .. accountId)
    addPlayerEvent(sendStorePurchaseSuccessful, 550, player, "You have transfered " .. amount .. " coins to " .. reciver .. " successfully")

    -- Adding history for both reciver/sender
    GameStore.insertHistory(accountId, GameStore.HistoryTypes.HISTORY_TYPE_NONE, player:getName() .. " transfered you this amount.", amount)
    GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, "You transfered this amount to " .. reciver, -1 * amount) -- negative
end

function parseOpenStore(player, msg)
    openStore(player)

    local serviceType = msg:getByte()
    local category = GameStore.Categories and GameStore.Categories[1] or nil

    if serviceType == GameStore.ServiceTypes.SERVICE_OUTFITS then
        category = GameStore.getCategoryByName("outfits")
    elseif serviceType == GameStore.ServiceTypes.SERVICE_MOUNTS then
        category = GameStore.getCategoryByName("mounts")
    end

    if category then
        addPlayerEvent(sendShowStoreOffers, 350, player, category)
    end
end

function parseRequestStoreOffers(player, msg)
    local serviceType = GameStore.ServiceTypes.SERVICE_STANDERD
    if player:getClient().version >= 1092 then
        serviceType = msg:getByte()
    end
    local categoryName = msg:getString()

    local category = GameStore.getCategoryByName(categoryName)
    if category then
        addPlayerEvent(sendShowStoreOffers, 350, player, category)
    end
end

function parseBuyStoreOffer(player, msg)
    local offerId = msg:getU32()
    local productType = msg:getByte()

    local offer = GameStore.getOfferById(offerId)
    if offer then
        -- If we don't add type, or offer type is fake
        if not offer.type or offer.type == GameStore.OfferTypes.OFFER_TYPE_NONE then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is either fake or corrupt.")
        end

        -- If no thing id,
        if offer.type ~= GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and offer.type ~= GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE and not offer.thingId then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is either fake or corrupt.")
        end

        -- We remove coins before doing everything, if it fails, we add coins back!
        if not player:canRemoveCoins(offer.price) then
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "We couldn't remove coins from your account, try again later.")
        end

        -- count is used in type(item), so we need to show (i.e 10x crystal coins)
        local offerCountStr = offer.count and (offer.count .. "x ") or ""
        -- The message which we will send to player!
        local message = "You have purchased " .. offerCountStr .. offer.name .. " for " .. offer.price .. " coins."

        -- If offer is item.
        if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM then
            local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
            if inbox and inbox:getEmptySlots() > offer.count then
                if player:getFreeCapacity() > ItemType(offer.thingId):getWeight(offer.count or 1) then
                    for t = 1,offer.count do
                        inbox:addItem(offer.thingId, offer.count or 1)
                    end
                else
                    return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You don't have enough capacity.")
                end
            else
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free slots in your store inbox.")
            end
            -- If offer is Stackable.
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE then
            local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
            if inbox and inbox:getEmptySlots() > 0 then
                if player:getFreeCapacity() > (ItemType(offer.thingId):getWeight(offer.count or 1) + ItemType(2596):getWeight(1)) then -- if player has cap for a parcel + offer weight
                    local parcel = inbox:addItem(2596, 1)
                    local packagename = ''.. offer.count..'x '.. offer.name ..' package.'
                    local pendingCount = offer.count;
                    if parcel then
                        parcel:setAttribute(ITEM_ATTRIBUTE_NAME, packagename)
                        while pendingCount > 0  do
                            if(pendingCount <= 100) then
                                parcel:addItem(offer.thingId, pendingCount)
                                pendingCount = 0
                            else
                                parcel:addItem(offer.thingId, 100)
                                pendingCount= pendingCount - 100
                            end
                        end
                    else --error creating the parcel item
                        return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "There was an error processing your purchase and your coins were not used. If this error persists, contact someone from staff.")
                    end
                else
                    return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You don't have enough capacity.")
                end
            else
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "Please make sure you have free slots in your store inbox.")
            end
            -- If offer is outfit/addon
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT or offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
            local outfitLookType
            if player:getSex() == PLAYERSEX_MALE then
                outfitLookType = offer.thingId.male
            else
                outfitLookType = offer.thingId.female
            end
            if not outfitLookType then
                return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This outfit seems not to suit your sex, we are sorry for that!")
            end

            player:addOutfitAddon(outfitLookType, offer.addon or 0)
            -- If offer is mount
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_MOUNT then
            player:addMount(offer.thingId)
            -- If offer is name change
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE then
            -- If player typed name yet!
            if productType == GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE then
                local newName = msg:getString()

                local resultId = db.storeQuery("SELECT * FROM `players` WHERE `name` = " .. db.escapeString(newName) .. "")
                if resultId ~= false then
                    return addPlayerEvent(sendStoreError, 650, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This name is already used, please try again!")
                end

                local result = GameStore.canChangeToName(newName)
                if not result.ability then
                    return addPlayerEvent(sendStoreError, 650, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, result.reason)
                end

                newName = newName:lower():gsub("(%l)(%w*)", function(a, b) return string.upper(a) .. b end)
                db.asyncQuery("UPDATE `players` SET `name` = " .. db.escapeString(newName) .. " WHERE `id` = " .. player:getGuid())
                message =  "You have successfully changed you name, you must relog to see changes."
                -- If not, we ask him to do!
            else
                return addPlayerEvent(sendRequestPurchaseData, 250, player, offer.id, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE)
            end
            -- If offer is sex change
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE then
            player:toggleSex()
            -- If offer is promotion
        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PROMOTION then
            if not GameStore.addPromotionToPlayer(player, offer.thingId) then
                return false
            end
            -- You can add whatever offer types to suit your needs!
        else
            -- ToDo :: implement purchase function
            return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "This offer is fake, please contact admin.")
        end
        -- Removing coins
        player:removeCoinsBalance(offer.price)
        -- We add this purchase to history!
        GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, offerCountStr .. offer.name, offer.price * -1)
        -- Send to client that purchase is successful!
        return addPlayerEvent(sendStorePurchaseSuccessful, 650, player, message)
    end

    -- If we didn't found the offer or error happened
    addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_INFORMATION, "We couldn't locate this offer, please try again later.")
end

-- Both functions use same formula!
function parseOpenTransactionHistory(player, msg)
    local page = 1
    GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE = msg:getByte()
    sendStoreTransactionHistory(player, page, GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE)
end

function parseRequestTransactionHistory(player, msg)
    local page = msg:getU32()
    sendStoreTransactionHistory(player, page, GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE)
end

--==Sending==--
function openStore(player)
    if not GameStore.Categories then
        return false
    end
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_OpenStore)
    msg:addByte(0x00)

    msg:addU16(#GameStore.Categories)
    for k, category in ipairs(GameStore.Categories) do
        msg:addString(category.name)
        msg:addString(category.description)

        if player:getClient().version >= 1093 then
            msg:addByte(category.state or GameStore.States.STATE_NONE)
        end

        msg:addByte(#category.icons)
        for m, icon in ipairs(category.icons) do
            msg:addString(icon)
        end

        msg:addString(category.parentCategory)
    end
    msg:sendToPlayer(player)

    sendCoinBalanceUpdating(player, true)
end

function sendShowStoreOffers(player, category)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_StoreOffers)

    msg:addString(category.name)

    msg:addU16(category.offers and #category.offers or 0x00)

    if category.offers then
        for k, offer in ipairs(category.offers) do
            msg:addU32(offer.id and offer.id or 0xFFFF) -- we later detect this number!

            local name = ""
            if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM and offer.count then
                name = offer.count .. "x "
            end

            if offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE and offer.count then
                name = offer.count .. "x "
            end

            name = name .. (offer.name or "Something Special")

            msg:addString(name)
            msg:addString(offer.description or GameStore.getDefaultDescription(offer.type))

            msg:addU32(offer.price and offer.price or 0xFFFF)
            msg:addByte(offer.state or GameStore.States.STATE_NONE) -- default is none

            local disabled, disabledReason = 0, ""
            if offer.disabled == true or not offer.type then
                disabled = 1
            end

            if offer.type ~= GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and offer.type ~= GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE and not offer.thingId then
                disabled = 1
            end

            if disabled == 1 and offer.disabledReason then -- dynamic disable
                disabledReason = offer.disabledReason
            end

            if disabled ~= 1 then
                if offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT or offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
                    local outfitLookType
                    if player:getSex() == PLAYERSEX_MALE then
                        outfitLookType = offer.thingId.male
                    else
                        outfitLookType = offer.thingId.female
                    end

                    if outfitLookType then
                        if offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT and player:hasOutfit(outfitLookType) then
                            disabled = 1
                            disabledReason = "You already have this outfit."
                        elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
                            if player:hasOutfit(outfitLookType) then
                                if player:hasOutfit(outfitLookType, offer.addon) then
                                    disabled = 1
                                    disabledReason = "You already have this addon."
                                end
                            else
                                disabled = 1
                                disabledReason = "You don't have the outfit, you can't buy the addon."
                            end
                        end
                    else
                        disabled = 1
                        disabledReason = "The offer is fake."
                    end
                elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_MOUNT then
                    local hasMount = player:hasMount(offer.thingId)
                    if hasMount == true then
                        disabled = 1
                        disabledReason = "You already have this mount."
                    end
                elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PROMOTION then
                    if GameStore.canAddPromotionToPlayer(player, offer.thingId).ability == false then
                        disabled = 1
                        disabledReason = "You can't get this promotion"
                    end
                end
            end

            msg:addByte(disabled)

            if disabled == 1 and player:getClient().version >= 1093 then
                msg:addString(disabledReason)
            end

            msg:addByte(#offer.icons)
            for k, icon in ipairs(offer.icons) do
                msg:addString(icon)
            end

            msg:addU16(0) -- We still don't support SubOffers!
        end
    end
    msg:sendToPlayer(player)
end

function sendStoreTransactionHistory(player, page, entriesPerPage)
    local entries = GameStore.retrieveHistoryEntries(player:getAccountId()) -- this makes everything easy!
    if #entries == 0 then
        return addPlayerEvent(sendStoreError, 250, player, GameStore.StoreErrors.STORE_ERROR_HISTORY, "You don't have any entries yet.")
    end

    local toSkip = (page - 1) * entriesPerPage
    for i = 1, toSkip do
        table.remove(entries, 1) -- we remove first!
    end

    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_OpenTransactionHistory)
    msg:addU32(page)
    msg:addU32(#entries > entriesPerPage and 0x01 or 0x00)

    msg:addByte(#entries >= entriesPerPage and entriesPerPage or #entries)
    for k, entry in ipairs(entries) do
        if k >= entriesPerPage then break end
        msg:addU32(entry.time)
        msg:addByte(entry.mode)
        msg:addU32(entry.amount)
        msg:addString(entry.description)
    end
    msg:sendToPlayer(player)
end

function sendStorePurchaseSuccessful(player, message)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CompletePurchase)

    msg:addByte(0x00)

    msg:addString(message)
    msg:addU32(player:getCoinsBalance())
    msg:addU32(player:getCoinsBalance())

    msg:sendToPlayer(player)
end

function sendStoreError(player, errorType, message)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_StoreError)

    msg:addByte(errorType)
    msg:addString(message)

    msg:sendToPlayer(player)
end

function sendCoinBalanceUpdating(player, updating)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CoinBalanceUpdating)
    msg:addByte(0x00)
    msg:sendToPlayer(player)

    if updating == true then
        sendUpdateCoinBalance(player)
    end
end

function sendUpdateCoinBalance(player)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_CoinBalanceUpdating)
    msg:addByte(0x01)

    msg:addByte(GameStore.SendingPackets.S_CoinBalance)
    msg:addByte(0x01)

    msg:addU32(player:getCoinsBalance())
    msg:addU32(player:getCoinsBalance())

    msg:sendToPlayer(player)
end

function sendRequestPurchaseData(player, offerId, type)
    local msg = NetworkMessage()
    msg:addByte(GameStore.SendingPackets.S_RequestPurchaseData)
    msg:addU32(offerId)
    msg:addByte(type)
    msg:sendToPlayer(player)
end

--==GameStoreFunctions==--
GameStore.getCategoryByName = function(name)
    for k, category in ipairs(GameStore.Categories) do
        if category.name:lower() == name:lower() then
            return category
        end
    end
    return nil
end

GameStore.getOfferById = function(id)
    for Cat_k, category in ipairs(GameStore.Categories) do
        if category.offers then
            for Off_k, offer in ipairs(category.offers) do
                if offer.id == id then
                    return offer
                end
            end
        end
    end
    return nil
end

GameStore.insertHistory = function(accountId, mode, description, amount)
    return db.asyncQuery(string.format("INSERT INTO `store_history`(`account_id`, `mode`, `description`, `coin_amount`, `time`) VALUES (%s, %s, %s, %s, %s)", accountId, mode, db.escapeString(description), amount, os.time()))
end

GameStore.retrieveHistoryEntries = function(accountId)
    local entries = {}
    local resultId = db.storeQuery("SELECT * FROM `store_history` WHERE `account_id` = " .. accountId .. " ORDER BY `time` DESC LIMIT 15;")
    if resultId ~= false then
        repeat
            local entry = {
                mode = result.getDataInt(resultId, "mode"),
                description = result.getDataString(resultId, "description"),
                amount = result.getDataInt(resultId, "coin_amount"),
                time = result.getDataInt(resultId, "time"),
            }
            table.insert(entries, entry)
        until not result.next(resultId)
        result.free(resultId)
    end
    return entries
end

GameStore.getDefaultDescription = function(offerType)
    local t, descList = GameStore.OfferTypes
    if offerType == t.OFFER_TYPE_OUTFIT or offerType == t.OFFER_TYPE_OUTFIT_ADDON then
        descList = GameStore.DefaultDescriptions.OUTFIT
    elseif offerType == t.OFFER_TYPE_MOUNT then
        descList = GameStore.DefaultDescriptions.MOUNT
    elseif offerType == t.OFFER_TYPE_NAMECHANGE then
        descList = GameStore.DefaultDescriptions.NAMECHANGE
    elseif offerType == t.OFFER_TYPE_SEXCHANGE then
        descList = GameStore.DefaultDescriptions.SEXCHANGE
    else
        return ""
    end

    return descList[math.floor(math.random(1, #descList))] or ""
end

GameStore.canChangeToName = function(name)
    local result = {
        ability = false
    }
    if name:len() < 3 or name:len() > 14 then
        result.reason = "Your new name's length should be lower than 3 or higher than 14."
        return result
    end

    -- just copied from znote aac.
    local words = {"owner", "gamemaster", "hoster", "admin", "staff", "tibia", "account", "god", "anal", "ass", "fuck", "sex", "hitler", "pussy", "dick", "rape", "cm", "gm", "tutor", "counsellor"}
    local split = name:split(" ")
    for k, word in ipairs(words) do
        for k, nameWord in ipairs(split) do
            if nameWord:lower() == word then
                result.reason = "You can't use word \"" .. word .. "\" in your new name."
                return result
            end
        end
    end

    if MonsterType(name) then
        result.reason = "Your new name \"" .. name .. "\" can't be a monster's name."
        return result
    elseif Npc(name) then
        result.reason = "Your new name \"" .. name .. "\" can't be a npc's name."
        return result
    end

    local letters = "{}|_*+-=<>0123456789@#%^&()/*\\.,:;~!\"$"
    for i = 1, letters:len() do
        local c = letters:sub(i, i)
        for i = 1, name:len() do
            local m = name:sub(i, i)
            if m == c then
                result.reason = "You can't use this letter \"" .. c .. "\" in your new name."
                return result
            end
        end
    end
    result.ability = true
    return result
end

GameStore.canAddPromotionToPlayer = function(player, promotion, send)
    local result = {
        ability = true
    }
    local vocation = player:getVocation()
    -- Working --
    local vocationCopy, baseVocation = vocation, vocation
    vocation = vocation:getDemotion()
    while vocation do
        baseVocation = vocation
        vocation = vocation:getDemotion()
    end

    local baseVocationsCount = GameStore.BaseVocationsCount or 4

    local newVocId = (baseVocationsCount * promotion) + baseVocation:getId()

    if not Vocation(newVocId) then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "The offer is fake, please report it!")
        end
        result.ability = false
        return result
    end
    -- If promotion is less than player's voc, or player don't have previous promotion
    if newVocId <= vocationCopy:getId() then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You already have this promotion!")
        end
        result.ability = false
        return result
    end

    if (newVocId - baseVocationsCount) ~= vocationCopy:getId() then
        if send then
            addPlayerEvent(sendStoreError, 350, player, GameStore.StoreErrors.STORE_ERROR_NETWORK, "You need higher promotion to get his one.")
        end
        result.ability = false
        return result
    end

    result.vocId = newVocId
    return result
end

GameStore.addPromotionToPlayer = function(player, promotion)
    local result = GameStore.canAddPromotionToPlayer(player, promotion, true)
    if result.ability == false then return false end

    local basics = {
        health = 185,
        mana = 40,
        cap = 500
    }

    player:setVocation(result.vocId)
    local newVoc = player:getVocation()
    player:setMaxHealth(basics.health + (newVoc:getHealthGain() * player:getLevel()))
    player:setMaxMana(basics.mana + (newVoc:getManaGain() * player:getLevel()))
    player:setCapacity(basics.cap + (newVoc:getCapacityGain() * player:getLevel()))

    player:addHealth(player:getMaxHealth())
    player:addMana(player:getMaxMana())

    player:sendTextMessage(MESSAGE_INFO_DESCR, "You have been promoted to " .. newVoc:getName())
    return true
end

--==Player==--
function Player.getCoinsBalance(self)
    resultId = db.storeQuery("SELECT `coins` FROM `accounts` WHERE `id` = " .. self:getAccountId())
    if not resultId then return 0 end
    return result.getDataInt(resultId, "coins")
end

function Player.setCoinsBalance(self, coins)
    db.asyncQuery("UPDATE `accounts` SET `coins` = " .. coins .. " WHERE `id` = " .. self:getAccountId())
    return true
end

function Player.canRemoveCoins(self, coins)
    if self:getCoinsBalance() < coins then
        return false
    end
    return true
end

function Player.removeCoinsBalance(self, coins)
    if self:canRemoveCoins(coins) then
        return self:setCoinsBalance(self:getCoinsBalance() - coins)
    end

    return false
end

function Player.addCoinsBalance(self, coins, update)
    self:setCoinsBalance(self:getCoinsBalance() + coins)
    if update then sendCoinBalanceUpdating(self, true) end
    return true
end

function Player.toggleSex(self)
    local currentSex = self:getSex()
    local playerOutfit = self:getOutfit()

    if currentSex == PLAYERSEX_FEMALE then
        self:setSex(PLAYERSEX_MALE)
        playerOutfit.lookType = 128
    else
        self:setSex(PLAYERSEX_FEMALE)
        playerOutfit.lookType = 136
    end
    self:setOutfit(playerOutfit)
end
 

100% livre de bugs.

Lembrando que os créditos é do pessoal da OTX Team, por fixar o bug 100%

https://github.com/mattyx14/otxserver/pull/186

Parabéns, está funcionando perfeitamente! +++

CONHEÇA MEU PROJETO:

WWW.ETERNUS-GLOBAL.COM

 

tibia-logo.gif

Link para o post
Compartilhar em outros sites

Participe da conversa

Você pode postar agora e se cadastrar mais tarde. Se você tem uma conta, faça o login para postar com sua conta.

Visitante
Responder

×   Você colou conteúdo com formatação.   Remover formatação

  Apenas 75 emojis são permitidos.

×   Seu link foi automaticamente incorporado.   Mostrar como link

×   Seu conteúdo anterior foi restaurado.   Limpar o editor

×   Não é possível colar imagens diretamente. Carregar ou inserir imagens do URL.

  • Quem Está Navegando   0 membros estão online

    Nenhum usuário registrado visualizando esta página.

  • Conteúdo Similar

    • Por Anderson Sacani
      Estou criando um servidor com base nos scripts de TFS 1.x e voltado ao público da america latina por causa do baixo ping na VPS... Argentina, Bolívia, Brasil, Chile, entre outros, portanto sei que falamos em português e nossos vizinhos em espanhol.
      Todos os sistemas do meu servidor são pensados para terem traduções e venho por meio deste tópico compartilhar à vocês algumas dessas funções:
       
      Antes de qualquer coisa, você precisará adicionar a seguinte variável em alguma biblioteca:
      USER_LANGUAGE = 1022118443  
      Agora que adicionou essa variável em alguma biblioteca, poderá adicionar as seguintes funções na mesma biblioteca, porém a baixo da variável USER_LANGUAGE.
       
      A primeira função serve para retornar qual idioma o player está usando:
      --[[ getLanguage, how to use: player:getLanguage() ]] function Player.getLanguage(self) if self:isPlayer() then if self:getStorageValue(USER_LANGUAGE) < 1 then return "portuguese" else return "spanish" end else print("getLanguage: Only works on players..") end end Um exemplo de como usar: player:getLanguage()
       
      A segunda função serve para alterar o idioma do player. O ideal é que seja usada na primeira vez em que o player loga no servidor:
      --[[ setLanguage, how to use: player:setLanguage("portuguese") ]] function Player.setLanguage(self, language) local value = 0 if self:isPlayer() then if language == "portuguese" then value = 0 elseif language == "spanish" then value = 1 else print("setLanguage: Only two options available. Choose one of them: 'portuguese' or 'spanish'.") end return self:setStorageValue(USER_LANGUAGE, value) else print("setLanguage: Only works on players..") end end Exemplos de como usar:
      player:setLanguage("portuguese")
      ou
      player:setLanguage("spanish")
       
      A terceira e não menos importante função, serve para mandar uma mensagem de texto ao jogador, porém ele receberá no idioma em que escolheu:
      --[[ sendLanguageTextMessage, how to use: local portugueseMessage = "Ola, tudo bom? Isto aqui é um algoritmo!" local spanishMessage = "Hola todo bien? Esto de aqui es un algoritmo!" player:sendLanguageTextMessage(MESSAGE_EVENT_ADVANCE, portugueseMessage,spanishMessage) ]] function Player.sendLanguageTextMessage(self, type, portugueseMessage, spanishMessage) if self:isPlayer() then if self:getStorageValue(USER_LANGUAGE) < 1 then return self:sendTextMessage(type, portugueseMessage) else return self:sendTextMessage(type, spanishMessage) end else print("sendLanguageTextMessage: Only works on players..") end end Um exemplo de como usar:
      player:sendLanguageTextMessage(MESSAGE_EVENT_ADVANCE, portugueseMessage, spanishMessage)
      O primeiro parâmetro é o tipo de mensagem, o segundo parâmetro será a mensagem em português e o terceiro parâmetro será em espanhol.
    • Por danielsort
      A minha poke ball nao esta funcionando como contador aonde consigo ageitar isso?
       
       

    • Por yurikil
      Saudações a todos, venho por meio deste tópico pedir uma ajuda no qual estou tentando fazer a muito tempo. Já vi alguns post aqui mesmo no TK, mas nenhum eu tive êxito. Por isso venho pedir um socorro de como eu consigo aumentar a quantidade de MagicEffects acima de 255 no meu NewClient OTC? Se alguém puder fortalecer ficarei muito grato!!
    • Por Vodkart
      Uma função que ao invés de usar o "cid" para pegar o valor de uma storage, usa o ip do jogador.
       
      baseado neste pedido:
       
       
       
       
      INSTALAÇÃO:
       
       
      Execute essa query:
       
      CREATE TABLE `ip_storages` (         `ip` int NOT NULL default 0,         `key` int NOT NULL default 0,         `value` varchar(255) NOT NULL default 0         )  
      funções para serem adicionadas na lib:
       
      function setIpStorageValue(ip, key, value) local func = db.executeQuery or db.query local query = db.getResult("SELECT `value` FROM `ip_storages` WHERE `key` = "..key.." AND `ip` = "..ip) if query:getID() == -1 then return func("INSERT INTO `ip_storages` (`ip`, `key`, `value`) VALUES ("..ip..", "..key..", "..value..")") end return func("UPDATE `ip_storages` SET `value` = "..value.." WHERE `key` = "..key.." AND `ip` = "..ip) end function getIpStorageValue(ip, key) local ret = db.getResult("SELECT `value` FROM `ip_storages` WHERE `ip` = "..ip.." AND `key` = "..key) if ret:getID() == -1 then return -1 end return ret:getDataInt("value") or ret:getDataString("value") end  
       
       
       
      exemplo de uso:
       
      pegar baú a cada X horas:
       
      function onUse(cid, item, fromPosition, itemEx, toPosition)     local storage, hours = 18000, 24     local ip = getPlayerIp(cid)     local item = 2160     if getIpStorageValue(ip, storage) - os.time() <= 0 then         doPlayerSendTextMessage(cid,22,"Tome seu prêmio.")         setIpStorageValue(ip, storage, os.time()+hours*3600)         doPlayerAddItem(cid, item, 100)          return true     end      return doPlayerSendTextMessage(cid, MESSAGE_EVENT_DEFAULT,"Espere para pegar um novo item!") end  

      ou só podendo pegar 1x:
       
      function onUse(cid, item, fromPosition, itemEx, toPosition)     local storage, ip = 18000, getPlayerIp(cid)     local item = 2160     if getIpStorageValue(ip, storage) > 1 then     doPlayerSendTextMessage(cid, MESSAGE_EVENT_DEFAULT,"voce ja pegou o item!") return true     end         doPlayerSendTextMessage(cid,22,"Tome seu prêmio.")         setIpStorageValue(ip, storage, 1)         doPlayerAddItem(cid, item, 100)          return true end
       
    • Por Endless
      Olá meus senhores, tem muita gente que pede serviços em relação a Gerar Outfits Animadas do seu cliente, o que muitos não sabem é que existe esse sistema de Gerar as Outfit animadas gratuitamente na internet e ele foi desenvolvido pelo Gesior.pl (Polones), então todo o crédito deste tutorial em relações a códigos vão para ele.

      Primeiro de tudo, o que é esse Sistema de Gerar Outfits Animadas?

      Basicamente é 1 Sistema Web que vai ler o teu tibia.spr / tibia.dat e seu items.otb e gerar todas suas outfit do seu cliente em forma de frames e na hora de exibir o sistema utiliza uma script em php que fica responsável por montar 1 gif utilizando esses frames.
      O resultado é algo como isto:



      Existe 2 formas de utilizar o sistema, eu vou ensinar a mais fácil, para quem quiser vou deixar o link do código fonte e vocês podem simplesmente estar clonando o repositório e fazer suas alterações a vontade. (Sim, ele é open source e.e).

      Código Fonte: https://github.com/gesior/open-tibia-library 

      Explicando como utilizar o sistema:

      1° acesse o link => https://outfit-images.ots.me/generator/

      Você deve visualizar algo como isto: 



      Percebam que ele vai pedir os seguintes dados: 
      -Client Version:
      -Sprite file:
      -Dat file:
      -Otb file:

      Antes de sair adicionando, precisamos configurar nosso cliente para que o sistema consiga reconhecer ele. Abra seu cliente no object builder. Agora vá na área de effects e apague todos os efeitos. Sim apague TODOS, agora vá até Tools->Spriter Otimizer



      Clique em start e você vai ter 1 retorno parecido com isto:



      Agora aperta CTRL+SHIFT+S, vamos salvar esse cliente em 1 pasta separada (você só vai usar ele pra esse sistema, então fique tranquilo, você não vai apagar o original). Na hora de salvar deixe desmarcado as 3 opções (Extended, Transparency, Improved Animations).



      Tendo feito isso, agora vamos voltar para o site. 
      Em Client Version você vai digitar a versão de seu cliente. Exemplo: Se for 8.60 digite 860
      Em sprite file você vai selecionar o tibia.spr do seu cliente que você exportou (esse sem efeitos)
      Em dat file você vai selecionar o tibia.dat do seu cliente que você exportou (esse sem efeitos)
      Em otb file você vai selecionar o items.otb do seu servidor.

      Agora clique em Load Files. Você deve ter 1 retorno parecido com isto:



      Agora clique em Generate Imagens e ele vai começar a gerar os frames das suas outfits.



      Ao terminar ele vai efetuar sozinho o download das outfits.




      Agora acesse este repositório no github e baixe os arquivos: https://github.com/gesior/open-tibia-library/tree/master/tools/colored-outfit-images-generator

      Tendo feito o download, você vai extrair a pasta outfits.zip que você baixo e extrair a pasta que você clonou o repositório. Ao extrair o arquivo outfits.zip você vai encontrar 1 pasta chamada outfits_anim, dentro dela você deve ter algo como isto:



      Cada pasta é referente a 1 looktype do seu cliente.
      Agora copie a pasta 'outfits_anim' que está dentro da pasta outfits que você extraiu e cole dentro da pasta do repositório que foi clonado. Ficando assim:



      Pronto, agora você somente precisa deixar essa pasta em 1 servidor web e utilizar a mesma via requisição. Exemplo:
      Se você for exibir uma outfit, você pode exibir da seguinte forma:
      <img src="http://seudominio.com/nomedapastaqueestaoosarquivos/animoutfit.php/?id=270">  
      Esse id será o looktype que você deseja. Lembrando que: Você pode colocar 1 script pra puxar a outfit de cada vocação e chamar esse diretório, ele vai exibir a imagem já com o diretório correto.
      Sugestão de uso: Para derivados de NTO/DBO e afins, você pode utilizar para exibir as outfits de transformações de cada Personagens, algo como isto:



       
      Outra opção é exibir na lista de Ranking no menu da direita (Normalmente Tibia Classico faz isso)
      A criatividade fica por conta de vocês.
       
      Créditos:
      Gesior.pl por todo o código e disponibilizar para a comunidade em 1 Forum Gringo
      A mim por disponibilizar o tutorial para a comunidade.
×
×
  • Criar Novo...

Informação Importante

Confirmação de Termo