Postado Fevereiro 4, 2017 8 anos 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 Mostrar conteúdo oculto -- 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 Editado Fevereiro 6, 2017 8 anos por Eduardo Dantas (veja o histórico de edições)
Postado Fevereiro 8, 2017 8 anos Em 04/02/2017 em 02: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 Mostrar conteúdo oculto Mostrar conteúdo oculto -- 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 Mostrar conteúdo oculto 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.
Postado Fevereiro 9, 2017 8 anos Autor Em 08/02/2017 em 21:17, 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: Mostrar conteúdo oculto -- 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 Fevereiro 9, 2017 8 anos por Eduardo Dantas (veja o histórico de edições)
Postado Fevereiro 9, 2017 8 anos Em 09/02/2017 em 13:34, Eduardo Dantas disse: Use este: Mostrar conteúdo oculto Mostrar conteúdo oculto -- 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! +++
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.