feat: new shop (based in store) (#1151)
|
|
@ -398,7 +398,7 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu
|
|||
<summary>Layout based on tibia 13 by @marcosvf132</summary>
|
||||
|
||||
- by [@marcosvf132](https://github.com/marcosvf132)
|
||||
- Game_shop v8
|
||||
- Game_shop based in Store by [@Oskar1121](https://github.com/Oskar1121/Store) and modified/fixed by [@Nottinghster](https://github.com/Nottinghster/)
|
||||
- Minimap WorldTime
|
||||
|
||||
- tfs c++(old): `void ProtocolGame::sendWorldTime()`
|
||||
|
|
|
|||
41
modules/game_shop/changename.otui
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
MainWindow
|
||||
size: 400 125
|
||||
!text: 'Enter New Character Name'
|
||||
@onEnter: modules.game_shop.confirmChangeName()
|
||||
|
||||
Button
|
||||
id: buttonCancel
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
!text: 'Cancel'
|
||||
width: 64
|
||||
@onClick: modules.game_shop.cancelChangeName()
|
||||
|
||||
Button
|
||||
id: buttonConfirm
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: prev.left
|
||||
margin-right: 13
|
||||
!text: 'Ok'
|
||||
width: 64
|
||||
@onClick: modules.game_shop.confirmChangeName()
|
||||
|
||||
HorizontalSeparator
|
||||
anchors.bottom: prev.top
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
margin-bottom: 10
|
||||
|
||||
Label
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
text-align: top
|
||||
!text: 'Please enter the new name for your character:'
|
||||
|
||||
TextEdit
|
||||
id: targetName
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 5
|
||||
849
modules/game_shop/game_shop.lua
Normal file
|
|
@ -0,0 +1,849 @@
|
|||
local DONATION_URL = nil
|
||||
local GAME_SHOP_CODE = 201
|
||||
|
||||
local categories = {}
|
||||
local offers = {}
|
||||
local history = {}
|
||||
|
||||
local gameShopWindow = nil
|
||||
local selected = nil
|
||||
local selectedOffer = nil
|
||||
local changeNameWindow = nil
|
||||
local msgWindow = nil
|
||||
local transferWindow = nil
|
||||
|
||||
local premiumPoints = 0
|
||||
local premiumSecondPoints = -1
|
||||
|
||||
local CATEGORY_NONE = -1
|
||||
local CATEGORY_PREMIUM = 0
|
||||
local CATEGORY_ITEM = 1
|
||||
local CATEGORY_BLESSING = 2
|
||||
local CATEGORY_OUTFIT = 3
|
||||
local CATEGORY_MOUNT = 4
|
||||
local CATEGORY_EXTRAS = 5
|
||||
|
||||
local searchResultCategoryId = "Search Results"
|
||||
|
||||
function init()
|
||||
connect(
|
||||
g_game,
|
||||
{
|
||||
onGameStart = create,
|
||||
onGameEnd = destroy
|
||||
}
|
||||
)
|
||||
|
||||
ProtocolGame.registerExtendedOpcode(GAME_SHOP_CODE, onExtendedOpcode)
|
||||
if g_game.isOnline() then
|
||||
create()
|
||||
end
|
||||
end
|
||||
|
||||
function terminate()
|
||||
disconnect(
|
||||
g_game,
|
||||
{
|
||||
onGameStart = create,
|
||||
onGameEnd = destroy
|
||||
}
|
||||
)
|
||||
|
||||
ProtocolGame.unregisterExtendedOpcode(GAME_SHOP_CODE, onExtendedOpcode)
|
||||
destroy()
|
||||
end
|
||||
|
||||
function onExtendedOpcode(protocol, code, buffer)
|
||||
local json_status, json_data =
|
||||
pcall(
|
||||
function()
|
||||
return json.decode(buffer)
|
||||
end
|
||||
)
|
||||
if not json_status then
|
||||
g_logger.error("SHOP json error: " .. json_data)
|
||||
return false
|
||||
end
|
||||
|
||||
local action = json_data["action"]
|
||||
local data = json_data["data"]
|
||||
if not action or not data then
|
||||
return false
|
||||
end
|
||||
|
||||
if action == "fetchBase" then
|
||||
onGameShopFetchBase(data)
|
||||
elseif action == "fetchOffers" then
|
||||
onGameShopFetchOffers(data)
|
||||
elseif action == "points" then
|
||||
onGameShopUpdatePoints(data)
|
||||
elseif action == "history" then
|
||||
onGameShopUpdateHistory(data)
|
||||
elseif action == "msg" then
|
||||
onGameShopMsg(data)
|
||||
end
|
||||
end
|
||||
|
||||
function create()
|
||||
if gameShopWindow then
|
||||
return
|
||||
end
|
||||
gameShopWindow = g_ui.displayUI("game_shop")
|
||||
gameShopWindow:hide()
|
||||
|
||||
local protocolGame = g_game.getProtocolGame()
|
||||
if protocolGame then
|
||||
protocolGame:sendExtendedOpcode(GAME_SHOP_CODE, json.encode({action = "fetch", data = {}}))
|
||||
end
|
||||
createTransferWindow()
|
||||
end
|
||||
|
||||
function destroy()
|
||||
if gameShopWindow then
|
||||
gameShopWindow:destroy()
|
||||
gameShopWindow = nil
|
||||
end
|
||||
|
||||
if msgWindow then
|
||||
msgWindow:destroy()
|
||||
msgWindow = nil
|
||||
end
|
||||
|
||||
if changeNameWindow then
|
||||
changeNameWindow:destroy()
|
||||
changeNameWindow = nil
|
||||
end
|
||||
|
||||
if transferWindow then
|
||||
transferWindow:destroy()
|
||||
transferWindow = nil
|
||||
end
|
||||
|
||||
selected = nil
|
||||
selectedOffer = nil
|
||||
end
|
||||
|
||||
function onGameShopFetchBase(data)
|
||||
for i = 1, #data.categories do
|
||||
addCategory(data.categories[i])
|
||||
end
|
||||
|
||||
DONATION_URL = data.url
|
||||
end
|
||||
|
||||
function hideTransferWindow()
|
||||
if transferWindow then
|
||||
transferWindow:hide()
|
||||
end
|
||||
end
|
||||
|
||||
function show()
|
||||
hideTransferWindow()
|
||||
if not gameShopWindow then
|
||||
return
|
||||
end
|
||||
|
||||
hideHistory()
|
||||
gameShopWindow:show()
|
||||
gameShopWindow:raise()
|
||||
gameShopWindow:focus()
|
||||
end
|
||||
|
||||
function hide()
|
||||
hideTransferWindow()
|
||||
if gameShopWindow then
|
||||
gameShopWindow:hide()
|
||||
end
|
||||
end
|
||||
|
||||
function showHistory()
|
||||
deselect()
|
||||
gameShopWindow:getChildById("offers"):hide()
|
||||
gameShopWindow:getChildById("history"):show()
|
||||
end
|
||||
|
||||
function hideHistory()
|
||||
gameShopWindow:getChildById("offers"):show()
|
||||
gameShopWindow:getChildById("history"):hide()
|
||||
end
|
||||
|
||||
local entriesPerPage = 26
|
||||
local currentPage = 1
|
||||
local totalPages = 1
|
||||
|
||||
function updateHistory()
|
||||
local historyPanel = gameShopWindow:getChildById("history")
|
||||
local historyList = historyPanel:getChildById("list")
|
||||
historyList:destroyChildren()
|
||||
|
||||
local index = ((currentPage - 1) * entriesPerPage) + 1
|
||||
for i = index, math.min(#history, index + entriesPerPage - 1) do
|
||||
local widget = g_ui.createWidget("HistoryWidget", historyList)
|
||||
widget:getChildById("date"):setText(history[i].date)
|
||||
widget:getChildById("price"):setText((history[i].price > 0 and "+" or "") .. comma_value(history[i].price))
|
||||
widget:getChildById("price"):setOn(history[i].price > 0)
|
||||
widget:getChildById("coin"):setOn(history[i].isSecondPrice)
|
||||
|
||||
if history[i].count > 1 then
|
||||
widget:getChildById("description"):setText(history[i].count .. " " .. history[i].name)
|
||||
else
|
||||
widget:getChildById("description"):setText(history[i].name)
|
||||
end
|
||||
end
|
||||
|
||||
historyPanel:getChildById("pageLabel"):setText("Page " .. currentPage .. "/" .. totalPages)
|
||||
end
|
||||
|
||||
function onGameShopUpdateHistory(historyList)
|
||||
-- date
|
||||
-- price
|
||||
-- name
|
||||
-- count
|
||||
currentPage = 1
|
||||
history = historyList
|
||||
totalPages = math.max(1, math.ceil(#history / entriesPerPage))
|
||||
|
||||
local historyPanel = gameShopWindow:getChildById("history")
|
||||
updateHistory()
|
||||
historyPanel:getChildById("nextPageButton"):setVisible(totalPages > 1)
|
||||
end
|
||||
|
||||
function prevPage()
|
||||
if currentPage == 1 then
|
||||
return true
|
||||
end
|
||||
|
||||
currentPage = currentPage - 1
|
||||
|
||||
local historyPanel = gameShopWindow:getChildById("history")
|
||||
updateHistory()
|
||||
|
||||
historyPanel:getChildById("nextPageButton"):setVisible(currentPage < totalPages)
|
||||
historyPanel:getChildById("prevPageButton"):setVisible(currentPage > 1)
|
||||
end
|
||||
|
||||
function nextPage()
|
||||
if currentPage == totalPages then
|
||||
return true
|
||||
end
|
||||
|
||||
currentPage = currentPage + 1
|
||||
|
||||
local historyPanel = gameShopWindow:getChildById("history")
|
||||
updateHistory()
|
||||
|
||||
historyPanel:getChildById("nextPageButton"):setVisible(currentPage < totalPages)
|
||||
historyPanel:getChildById("prevPageButton"):setVisible(currentPage > 1)
|
||||
end
|
||||
|
||||
function deselect()
|
||||
if selected then
|
||||
selected:getChildById("button"):setChecked(false)
|
||||
local arrow = selected:getChildById("selectArrow")
|
||||
if arrow then
|
||||
arrow:hide()
|
||||
end
|
||||
|
||||
if not selected:getChildById("subCategories") then
|
||||
selected = selected:getParent():getParent()
|
||||
selected:getChildById("expandArrow"):show()
|
||||
end
|
||||
|
||||
selected:setHeight(22)
|
||||
selected:getChildById("subCategories"):hide()
|
||||
end
|
||||
end
|
||||
|
||||
function comma_value(n)
|
||||
local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$")
|
||||
return left .. (num:reverse():gsub("(%d%d%d)", "%1,"):reverse()) .. right
|
||||
end
|
||||
|
||||
function buyPoints()
|
||||
g_platform.openUrl(DONATION_URL)
|
||||
end
|
||||
|
||||
function onGameShopFetchOffers(data)
|
||||
-- parent
|
||||
-- name
|
||||
-- id
|
||||
-- price
|
||||
-- isSecondPrice
|
||||
-- count
|
||||
-- description
|
||||
-- categoryId
|
||||
offers[data.category] = data.offers
|
||||
if not selected and data.category == "Premium Time" then
|
||||
select(gameShopWindow:getChildById("categoriesList"):getChildren()[1]:getChildById("button"))
|
||||
end
|
||||
end
|
||||
|
||||
function addCategory(data)
|
||||
-- title
|
||||
-- parent
|
||||
-- iconId
|
||||
-- categoryId
|
||||
-- description
|
||||
|
||||
categories[data.title] = data
|
||||
|
||||
local categoriesList = gameShopWindow:getChildById("categoriesList")
|
||||
local category
|
||||
if data.parent then
|
||||
local parentPanel = categoriesList:getChildById(data.parent)
|
||||
category = g_ui.createWidget("ShopSubCategory", parentPanel:getChildById("subCategories"))
|
||||
parentPanel:getChildById("expandArrow"):show()
|
||||
else
|
||||
category = g_ui.createWidget("ShopCategory", categoriesList)
|
||||
end
|
||||
|
||||
category:setId(data.title)
|
||||
category:getChildById("button"):setIconClip(data.iconId * 13 .. " 0 13 13")
|
||||
category:getChildById("name"):setText(data.title)
|
||||
end
|
||||
|
||||
function onGameShopUpdatePoints(data)
|
||||
premiumPoints = tonumber(data.points)
|
||||
premiumSecondPoints = tonumber(data.secondPoints)
|
||||
local pointsWidget = gameShopWindow:getChildById("balance"):getChildById("value")
|
||||
pointsWidget:setText(comma_value(premiumPoints))
|
||||
|
||||
local balanceSecondWidget = gameShopWindow:getChildById("balanceSecond")
|
||||
if premiumSecondPoints ~= -1 then
|
||||
balanceSecondWidget:getChildById("value"):setText(comma_value(premiumSecondPoints))
|
||||
balanceSecondWidget:show()
|
||||
balanceSecondWidget:setWidth(105)
|
||||
balanceSecondWidget:setMarginLeft(6)
|
||||
transferWindow.taskPointsLabelCoin:show()
|
||||
transferWindow.taskPointsAmountScrollbar:show()
|
||||
transferWindow.taskPointsCoin:show()
|
||||
transferWindow.taskPointsBalance:show()
|
||||
transferWindow.taskPointsBalance:setText(tr("Transferable Task points: ") .. comma_value(premiumSecondPoints))
|
||||
transferWindow.taskPointsAmountScrollbar:setMaximum(premiumSecondPoints)
|
||||
else
|
||||
balanceSecondWidget:hide()
|
||||
balanceSecondWidget:setWidth(1)
|
||||
balanceSecondWidget:setMarginLeft(0)
|
||||
transferWindow.taskPointsBalance:hide()
|
||||
transferWindow.taskPointsAmountLabel:hide()
|
||||
transferWindow.taskPointsLabelCoin:hide()
|
||||
transferWindow.taskPointsAmountScrollbar:hide()
|
||||
transferWindow.taskPointsCoin:hide()
|
||||
end
|
||||
|
||||
transferWindow.coinsBalance:setText(tr("Transferable Tibia Coins: ") .. comma_value(premiumPoints))
|
||||
transferWindow.coinsAmountScrollbar:setMaximum(premiumPoints)
|
||||
end
|
||||
|
||||
function select(self, ignoreSearch)
|
||||
hideHistory()
|
||||
if not ignoreSearch then
|
||||
eraseSearchResults()
|
||||
end
|
||||
|
||||
local selfParent = self:getParent()
|
||||
local panel = selfParent:getChildById("subCategories")
|
||||
if panel then
|
||||
deselect()
|
||||
selected = selfParent
|
||||
|
||||
if panel:getChildCount() > 0 then
|
||||
panel:show()
|
||||
selfParent:setHeight((panel:getChildCount() + 1) * 22)
|
||||
selfParent:getChildById("expandArrow"):hide()
|
||||
select(panel:getChildren()[1]:getChildById("button"))
|
||||
else
|
||||
self:setChecked(true)
|
||||
end
|
||||
else
|
||||
if selected then
|
||||
selected:getChildById("button"):setChecked(false)
|
||||
|
||||
local arrow = selected:getChildById("selectArrow")
|
||||
if arrow then
|
||||
arrow:hide()
|
||||
end
|
||||
end
|
||||
|
||||
selected = selfParent
|
||||
|
||||
self:setChecked(true)
|
||||
selfParent:getChildById("selectArrow"):show()
|
||||
end
|
||||
|
||||
showOffers(selfParent:getId())
|
||||
end
|
||||
|
||||
function selectOffer(self)
|
||||
if selectedOffer then
|
||||
selectedOffer:setChecked(false)
|
||||
end
|
||||
|
||||
self:setChecked(true)
|
||||
selectedOffer = self
|
||||
|
||||
updateDescription(self)
|
||||
end
|
||||
|
||||
function showOffers(id)
|
||||
local offersCache = offers[id]
|
||||
if not offersCache then
|
||||
return
|
||||
end
|
||||
|
||||
local currentOutfit = g_game.getLocalPlayer():getOutfit()
|
||||
local offersPanel = gameShopWindow:getChildById("offers")
|
||||
local offersList = offersPanel:getChildById("offersList")
|
||||
offersList:destroyChildren()
|
||||
|
||||
for i = 1, #offersCache do
|
||||
local widget = offersList:getChildById(offersCache[i].name)
|
||||
local price = offersCache[i].price
|
||||
if widget then
|
||||
local additionalPriceWidget = widget:getChildById("additionalPrice")
|
||||
additionalPriceWidget:getChildById("coin"):setOn(offersCache[i].isSecondPrice)
|
||||
additionalPriceWidget:getChildById("value"):setText(comma_value(price))
|
||||
additionalPriceWidget:show()
|
||||
|
||||
local additionalCountWidget = widget:getChildById("additionalCount")
|
||||
additionalCountWidget:setText(offersCache[i].count .. "x")
|
||||
additionalCountWidget:show()
|
||||
|
||||
widget:getChildById("count"):show()
|
||||
widget.additionalPriceValue = price
|
||||
widget.additionalIsSecondPrice = isSecondPrice
|
||||
widget.additionalCountValue = offersCache[i].count
|
||||
|
||||
if i == 2 then
|
||||
selectOffer(widget)
|
||||
end
|
||||
else
|
||||
local widget = g_ui.createWidget("OfferWidget", offersList)
|
||||
local priceWidget = widget:getChildById("price")
|
||||
priceWidget:getChildById("coin"):setOn(offersCache[i].isSecondPrice)
|
||||
priceWidget:getChildById("value"):setText(comma_value(price))
|
||||
|
||||
widget:getChildById("name"):setText(offersCache[i].name)
|
||||
widget:getChildById("count"):setText(offersCache[i].count .. "x")
|
||||
widget:setId(offersCache[i].name)
|
||||
widget.data = offersCache[i]
|
||||
widget.categoryId = id
|
||||
|
||||
local imagePanel = widget:getChildById("imagePanel")
|
||||
|
||||
if type(offersCache[i].id) == "string" then
|
||||
local image = imagePanel:getChildById("image")
|
||||
image:show()
|
||||
image:setImageSource("/game_shop/images/" .. offersCache[i].id)
|
||||
elseif type(offersCache[i].id) == "number" then
|
||||
local categoryId = offersCache[i].categoryId
|
||||
widget.offerCategoryId = categoryId
|
||||
if categoryId == CATEGORY_ITEM then
|
||||
local item = imagePanel:getChildById("item")
|
||||
item:show()
|
||||
item:setItemId(offersCache[i].id)
|
||||
widget:getChildById("count"):show()
|
||||
elseif categoryId == CATEGORY_OUTFIT then
|
||||
local outfit = imagePanel:getChildById("outfit")
|
||||
currentOutfit.type = offersCache[i].id
|
||||
outfit:show()
|
||||
outfit:setOutfit(currentOutfit)
|
||||
elseif categoryId == CATEGORY_MOUNT then
|
||||
local mount = imagePanel:getChildById("mount")
|
||||
mount:show()
|
||||
mount:setOutfit({type = offersCache[i].id})
|
||||
end
|
||||
end
|
||||
|
||||
if i == 1 then
|
||||
selectOffer(widget)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function updateDescription(self)
|
||||
local offersPanel = gameShopWindow:getChildById("offers")
|
||||
local offerDetails = offersPanel:getChildById("offerDetails")
|
||||
offerDetails:show()
|
||||
offerDetails:getChildById("name"):setText(self.data.name)
|
||||
|
||||
local descriptionPanel = offerDetails:getChildById("description")
|
||||
local widget = descriptionPanel:getChildren()[1]
|
||||
if not widget then
|
||||
widget = g_ui.createWidget("OfferDescripionLabel", descriptionPanel)
|
||||
end
|
||||
|
||||
local description = categories[self.categoryId].description
|
||||
if not description or description == "" then
|
||||
description = self.data.description
|
||||
end
|
||||
|
||||
widget:setText(description)
|
||||
|
||||
local buyButton = offerDetails:getChildById("buyButton")
|
||||
local priceWidget = offerDetails:getChildById("price")
|
||||
local additionalBuyButton = offerDetails:getChildById("additionalBuyButton")
|
||||
local additionalPriceWidget = offerDetails:getChildById("additionalPrice")
|
||||
|
||||
priceWidget:setOn(self.data.isSecondPrice)
|
||||
priceWidget:setText(comma_value(self.data.price))
|
||||
|
||||
local globalPoints = self.data.isSecondPrice and premiumSecondPoints or premiumPoints
|
||||
priceWidget:setEnabled(self.data.price <= globalPoints)
|
||||
buyButton:setEnabled(self.data.price <= globalPoints)
|
||||
|
||||
if self.additionalPriceValue and self.additionalCountValue then
|
||||
buyButton:setText("Buy " .. self.data.count)
|
||||
|
||||
additionalPriceWidget:setEnabled(self.additionalPriceValue <= globalPoints)
|
||||
additionalBuyButton:setText("Buy " .. self.additionalCountValue)
|
||||
additionalBuyButton:show()
|
||||
additionalBuyButton:setEnabled(self.additionalPriceValue <= globalPoints)
|
||||
additionalBuyButton.price = self.additionalPriceValue
|
||||
additionalBuyButton.count = self.additionalCountValue
|
||||
buyButton.secondPrice = self.data.secondPrice
|
||||
buyButton.price = self.data.price
|
||||
buyButton.count = self.data.count
|
||||
|
||||
additionalPriceWidget:setOn(self.data.isSecondPrice)
|
||||
additionalPriceWidget:setText(comma_value(self.additionalPriceValue))
|
||||
additionalPriceWidget:show()
|
||||
else
|
||||
additionalBuyButton:hide()
|
||||
|
||||
buyButton.secondPrice = nil
|
||||
buyButton.price = nil
|
||||
buyButton.count = nil
|
||||
|
||||
buyButton:setText("Buy")
|
||||
additionalPriceWidget:hide()
|
||||
end
|
||||
|
||||
local currentOutfit = g_game.getLocalPlayer():getOutfit()
|
||||
local imagePanel = offerDetails:getChildById("imagePanel")
|
||||
local image = imagePanel:getChildById("image")
|
||||
local item = imagePanel:getChildById("item")
|
||||
local outfit = imagePanel:getChildById("outfit")
|
||||
local mount = imagePanel:getChildById("mount")
|
||||
image:hide()
|
||||
item:hide()
|
||||
outfit:hide()
|
||||
mount:hide()
|
||||
if type(self.data.id) == "string" then
|
||||
image:show()
|
||||
image:setImageSource("/game_shop/images/" .. self.data.id)
|
||||
elseif type(self.data.id) == "number" then
|
||||
-- local categoryId = categories[self.categoryId].categoryId
|
||||
local categoryId = self.offerCategoryId
|
||||
if categoryId == CATEGORY_ITEM then
|
||||
item:show()
|
||||
item:setItemId(self.data.id)
|
||||
elseif categoryId == CATEGORY_OUTFIT then
|
||||
currentOutfit.type = self.data.id
|
||||
outfit:show()
|
||||
outfit:setOutfit(currentOutfit)
|
||||
elseif categoryId == CATEGORY_MOUNT then
|
||||
mount:show()
|
||||
mount:setOutfit({type = self.data.id})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function onOfferBuy(self)
|
||||
if not selectedOffer then
|
||||
displayInfoBox("Error", "Something went wrong, make sure to select category and offer.")
|
||||
return
|
||||
end
|
||||
|
||||
hide()
|
||||
|
||||
local title = "Purchase Confirmation"
|
||||
local msg
|
||||
if self.count and self.count > 1 then
|
||||
msg =
|
||||
"Do you want to buy " ..
|
||||
self.count .. "x " .. selectedOffer.data.name .. " for " .. comma_value(self.price) .. " points?"
|
||||
else
|
||||
msg =
|
||||
"Do you want to buy " ..
|
||||
selectedOffer.data.name .. " for " .. comma_value(selectedOffer.data.price) .. " points?"
|
||||
end
|
||||
|
||||
if selectedOffer.data.name == "Name Change" then
|
||||
msgWindow =
|
||||
displayGeneralBox(
|
||||
title,
|
||||
msg,
|
||||
{
|
||||
{text = "Yes", callback = changeName},
|
||||
{text = "No", callback = buyCanceled},
|
||||
anchor = AnchorHorizontalCenter
|
||||
},
|
||||
changeName,
|
||||
buyCanceled
|
||||
)
|
||||
else
|
||||
msgWindow =
|
||||
displayGeneralBox(
|
||||
title,
|
||||
msg,
|
||||
{
|
||||
{text = "Yes", callback = buyConfirmed},
|
||||
{text = "No", callback = buyCanceled},
|
||||
anchor = AnchorHorizontalCenter
|
||||
},
|
||||
buyConfirmed,
|
||||
buyCanceled
|
||||
)
|
||||
end
|
||||
|
||||
if self.count and self.count > 1 then
|
||||
msgWindow.count = self.count
|
||||
msgWindow.price = self.price
|
||||
else
|
||||
msgWindow.count = selectedOffer.data.count
|
||||
msgWindow.price = selectedOffer.data.price
|
||||
end
|
||||
end
|
||||
|
||||
function buyConfirmed()
|
||||
local protocolGame = g_game.getProtocolGame()
|
||||
if protocolGame then
|
||||
protocolGame:sendExtendedOpcode(
|
||||
GAME_SHOP_CODE,
|
||||
json.encode(
|
||||
{
|
||||
action = "purchase",
|
||||
data = {
|
||||
count = msgWindow.count,
|
||||
price = msgWindow.price,
|
||||
name = selectedOffer.data.name,
|
||||
id = selectedOffer.data.id,
|
||||
parent = selectedOffer.data.parent
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
msgWindow:destroy()
|
||||
msgWindow = nil
|
||||
end
|
||||
|
||||
function buyCanceled()
|
||||
msgWindow:destroy()
|
||||
msgWindow = nil
|
||||
show()
|
||||
end
|
||||
|
||||
function changeName()
|
||||
msgWindow:destroy()
|
||||
msgWindow = nil
|
||||
if changeNameWindow then
|
||||
return
|
||||
end
|
||||
|
||||
changeNameWindow = g_ui.displayUI("changename")
|
||||
end
|
||||
|
||||
function confirmChangeName()
|
||||
local protocolGame = g_game.getProtocolGame()
|
||||
if protocolGame then
|
||||
protocolGame:sendExtendedOpcode(
|
||||
GAME_SHOP_CODE,
|
||||
json.encode(
|
||||
{
|
||||
action = "purchase",
|
||||
data = {
|
||||
count = selectedOffer.data.count,
|
||||
price = selectedOffer.data.price,
|
||||
name = selectedOffer.data.name,
|
||||
id = selectedOffer.data.id,
|
||||
parent = selectedOffer.data.parent,
|
||||
nick = changeNameWindow:getChildById("targetName"):getText()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
changeNameWindow:destroy()
|
||||
changeNameWindow = nil
|
||||
end
|
||||
end
|
||||
|
||||
function cancelChangeName()
|
||||
changeNameWindow:destroy()
|
||||
changeNameWindow = nil
|
||||
end
|
||||
|
||||
function onGameShopMsg(data)
|
||||
local type = data.type
|
||||
local text = data.msg
|
||||
|
||||
local title = nil
|
||||
local close = false
|
||||
if type == "info" then
|
||||
title = "Store Information"
|
||||
close = data.close
|
||||
elseif type == "error" then
|
||||
title = "Store Error"
|
||||
close = true
|
||||
end
|
||||
|
||||
if close then
|
||||
hideHistory()
|
||||
hide()
|
||||
end
|
||||
|
||||
displayInfoBoxWithCallback(
|
||||
title,
|
||||
text,
|
||||
{{text = "Ok", callback = defaultCallback}},
|
||||
function()
|
||||
show()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function displayInfoBoxWithCallback(title, message, callback)
|
||||
local messageBox
|
||||
local defaultCallback = function()
|
||||
if callback then
|
||||
show()
|
||||
end
|
||||
messageBox:ok()
|
||||
end
|
||||
|
||||
messageBox =
|
||||
UIMessageBox.display(
|
||||
title,
|
||||
message,
|
||||
{{text = "Ok", callback = defaultCallback}},
|
||||
defaultCallback,
|
||||
defaultCallback
|
||||
)
|
||||
return messageBox
|
||||
end
|
||||
|
||||
function changeCoinsAmount(value)
|
||||
transferWindow:getChildById("coinsAmountLabel"):setText("Amount to gift: " .. comma_value(value))
|
||||
end
|
||||
|
||||
function changeTaskPointsAmount(value)
|
||||
transferWindow:getChildById("taskPointsAmountLabel"):setText("Amount to gift: " .. comma_value(value))
|
||||
end
|
||||
|
||||
function confirmGiftCoins()
|
||||
if not transferWindow then
|
||||
return
|
||||
end
|
||||
|
||||
local protocolGame = g_game.getProtocolGame()
|
||||
if protocolGame then
|
||||
protocolGame:sendExtendedOpcode(
|
||||
GAME_SHOP_CODE,
|
||||
json.encode(
|
||||
{
|
||||
action = "transfer",
|
||||
data = {
|
||||
amount = tonumber(transferWindow.coinsAmountScrollbar:getValue()),
|
||||
amountSecond = tonumber(transferWindow.taskPointsAmountScrollbar:getValue()),
|
||||
target = transferWindow.recipient:getText()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
transferWindow.recipient:setText("")
|
||||
transferWindow.coinsAmountScrollbar:setValue(0)
|
||||
transferWindow.taskPointsAmountScrollbar:setValue(0)
|
||||
end
|
||||
end
|
||||
|
||||
function cancelGiftCoins()
|
||||
if transferWindow then
|
||||
transferWindow:hide()
|
||||
show()
|
||||
end
|
||||
end
|
||||
|
||||
function createTransferWindow()
|
||||
if not transferWindow then
|
||||
transferWindow = g_ui.displayUI("giftcoins")
|
||||
transferWindow:hide()
|
||||
end
|
||||
end
|
||||
|
||||
function toggle()
|
||||
if not gameShopWindow then
|
||||
return
|
||||
end
|
||||
|
||||
if gameShopWindow:isVisible() then
|
||||
return hide()
|
||||
end
|
||||
|
||||
show()
|
||||
end
|
||||
|
||||
function toggleGiftCoins()
|
||||
if transferWindow then
|
||||
hide()
|
||||
transferWindow:show()
|
||||
transferWindow:raise()
|
||||
transferWindow:focus()
|
||||
transferWindow:setOn(premiumSecondPoints ~= -1)
|
||||
end
|
||||
end
|
||||
|
||||
function onTypeSearch(self)
|
||||
gameShopWindow:getChildById("searchButton"):setEnabled(#self:getText() > 2)
|
||||
end
|
||||
|
||||
function eraseSearchResults()
|
||||
local widget = gameShopWindow:getChildById("categoriesList"):getChildById(searchResultCategoryId)
|
||||
if widget then
|
||||
if selected == widget then
|
||||
selected = nil
|
||||
end
|
||||
widget:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
function onSearch()
|
||||
local searchTextEdit = gameShopWindow:getChildById("searchTextEdit")
|
||||
local text = searchTextEdit:getText()
|
||||
|
||||
if #text < 3 then
|
||||
return
|
||||
end
|
||||
|
||||
eraseSearchResults()
|
||||
addCategory(
|
||||
{
|
||||
title = searchResultCategoryId,
|
||||
iconId = 7,
|
||||
categoryId = CATEGORY_NONE
|
||||
}
|
||||
)
|
||||
|
||||
offers[searchResultCategoryId] = {}
|
||||
local results = {}
|
||||
local searchTerm = text:lower()
|
||||
|
||||
for categoryId, offerData in pairs(offers) do
|
||||
for _, offer in pairs(offerData) do
|
||||
if string.find(offer.name:lower(), searchTerm) then
|
||||
table.insert(results, offer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, offer in ipairs(results) do
|
||||
table.insert(offers[searchResultCategoryId], offer)
|
||||
end
|
||||
|
||||
local children = gameShopWindow:getChildById("categoriesList"):getChildren()
|
||||
select(children[#children]:getChildById("button"), true)
|
||||
searchTextEdit:clearText()
|
||||
end
|
||||
9
modules/game_shop/game_shop.otmod
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
Module
|
||||
name: game_shop
|
||||
description: In-game shop
|
||||
author: MatC, Nottinghster
|
||||
website: None
|
||||
sandboxed: true
|
||||
scripts: [ game_shop ]
|
||||
@onLoad: init()
|
||||
@onUnload: terminate()
|
||||
649
modules/game_shop/game_shop.otui
Normal file
|
|
@ -0,0 +1,649 @@
|
|||
ShopCategory < FlatPanel
|
||||
height: 22
|
||||
margin: 5
|
||||
focusable: false
|
||||
|
||||
Button
|
||||
id: button
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 22
|
||||
icon-source: images/categories
|
||||
icon-align: left
|
||||
icon-offset: 6 0
|
||||
@onClick: modules.game_shop.select(self)
|
||||
$checked:
|
||||
image-clip: 0 46 22 23
|
||||
text-offset: 1 1
|
||||
|
||||
Label
|
||||
id: name
|
||||
anchors.fill: prev
|
||||
text-offset: 24 0
|
||||
text-align: left
|
||||
color: #C0C0C0
|
||||
|
||||
UIWidget
|
||||
id: expandArrow
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
margin-top: 8
|
||||
margin-right: 8
|
||||
image-source: images/arrow_down
|
||||
visible: false
|
||||
|
||||
FlatPanel
|
||||
id: subCategories
|
||||
anchors.top: button.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
layout: verticalBox
|
||||
visible: false
|
||||
image-color: #A0A0A0
|
||||
|
||||
ShopSubCategory < UIWidget
|
||||
height: 22
|
||||
focusable: false
|
||||
|
||||
Button
|
||||
id: button
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-left: 14
|
||||
height: 22
|
||||
icon-source: images/categories
|
||||
icon-align: left
|
||||
icon-offset: 6 0
|
||||
@onClick: modules.game_shop.select(self)
|
||||
$checked:
|
||||
image-clip: 0 46 22 23
|
||||
text-offset: 1 1
|
||||
|
||||
Label
|
||||
id: name
|
||||
anchors.fill: prev
|
||||
text-offset: 24 0
|
||||
text-align: left
|
||||
color: #C0C0C0
|
||||
|
||||
UIWidget
|
||||
id: selectArrow
|
||||
anchors.verticalcenter: parent.verticalcenter
|
||||
anchors.left: parent.left
|
||||
margin-top: 1
|
||||
margin-left: 4
|
||||
image-source: images/arrow_right
|
||||
visible: false
|
||||
|
||||
HistoryLabel < Label
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
color: #C0C0C0
|
||||
text-offset: 3 0
|
||||
|
||||
HistoryWidget < UIWidget
|
||||
height: 15
|
||||
background-color: alpha
|
||||
focusable: false
|
||||
$hover:
|
||||
background-color: #555555
|
||||
$pressed:
|
||||
background-color: #272727
|
||||
|
||||
$alternate:
|
||||
background-color: #383838
|
||||
$alternate hover:
|
||||
background-color: #2e2e2e
|
||||
$alternate pressed:
|
||||
background-color: #1d1d1d
|
||||
|
||||
HistoryLabel
|
||||
id: date
|
||||
anchors.left: parent.left
|
||||
margin-left: 1
|
||||
width: 150
|
||||
|
||||
VerticalSeparator
|
||||
anchors.top: parent.top
|
||||
anchors.left: prev.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
HistoryLabel
|
||||
id: price
|
||||
anchors.left: prev.right
|
||||
text-align: right
|
||||
margin-left: 1
|
||||
size: 70 21
|
||||
color: #D33C3C
|
||||
$on:
|
||||
color: #44AD25
|
||||
|
||||
UIWidget
|
||||
id: coin
|
||||
anchors.verticalcenter: parent.verticalcenter
|
||||
anchors.left: prev.right
|
||||
image-source: images/coin
|
||||
margin-left: 6
|
||||
margin-bottom: 1
|
||||
phantom: true
|
||||
$on:
|
||||
image-source: images/coin_second
|
||||
|
||||
VerticalSeparator
|
||||
anchors.top: parent.top
|
||||
anchors.left: prev.right
|
||||
anchors.bottom: parent.bottom
|
||||
margin-left: 3
|
||||
|
||||
HistoryLabel
|
||||
id: description
|
||||
anchors.left: prev.right
|
||||
anchors.right: parent.right
|
||||
margin-left: 1
|
||||
margin-right: 1
|
||||
height: 21
|
||||
|
||||
HistoryHeader < FlatPanel
|
||||
anchors.top: parent.top
|
||||
image-color: #A0A0A0
|
||||
text-align: left
|
||||
text-offset: 3 0
|
||||
focusable: false
|
||||
|
||||
OfferBuyButton < Button
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: prev.left
|
||||
anchors.right: prev.right
|
||||
image-color: #3B86C7
|
||||
focusable: false
|
||||
@onClick: modules.game_shop.onOfferBuy(self)
|
||||
$disabled:
|
||||
image-color: #1B3F5E
|
||||
|
||||
OfferBuyPrice < FlatPanel
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: prev.left
|
||||
anchors.right: prev.right
|
||||
margin-top: 4
|
||||
image-color: #A0A0A0
|
||||
height: 21
|
||||
text: 0
|
||||
text-align: center
|
||||
color: #C0C0C0
|
||||
icon: images/coin
|
||||
icon-size: 12 12
|
||||
icon-offset-x: 88
|
||||
icon-offset-y: 4
|
||||
focusable: false
|
||||
$disabled:
|
||||
color: #ff1f1f
|
||||
$on:
|
||||
icon: images/coin_second
|
||||
|
||||
OfferDescripionLabel < Label
|
||||
text-align: topLeft
|
||||
text-wrap: true
|
||||
text-vertical-auto-resize: true
|
||||
color: #C0C0C0
|
||||
|
||||
ShopBalanceWidget < FlatPanel
|
||||
image-color: #A0A0A0
|
||||
height: 21
|
||||
focusable: false
|
||||
|
||||
Label
|
||||
id: value
|
||||
anchors.fill: parent
|
||||
margin-right: 20
|
||||
text-align: right
|
||||
text: 0
|
||||
color: #C0C0C0
|
||||
|
||||
UIWidget
|
||||
id: coin
|
||||
anchors.right: parent.right
|
||||
anchors.verticalcenter: parent.verticalcenter
|
||||
image-source: images/coin
|
||||
margin-right: 3
|
||||
phantom: true
|
||||
$on:
|
||||
image-source: images/coin_second
|
||||
|
||||
ShopBalanceSecondWidget < FlatPanel
|
||||
image-color: #A0A0A0
|
||||
focusable: false
|
||||
|
||||
Label
|
||||
id: value
|
||||
anchors.fill: parent
|
||||
margin-right: 20
|
||||
text-align: right
|
||||
text: 0
|
||||
color: #C0C0C0
|
||||
|
||||
UIWidget
|
||||
anchors.right: parent.right
|
||||
anchors.verticalcenter: parent.verticalcenter
|
||||
image-source: images/coin_second
|
||||
margin-right: 3
|
||||
phantom: true
|
||||
|
||||
OfferPriceLabel < Label
|
||||
anchors.left: parent.left
|
||||
margin-right: 6
|
||||
margin-bottom: 2
|
||||
text-align: right
|
||||
color: #C0C0C0
|
||||
visible: false
|
||||
|
||||
OfferWidget < FlatPanel
|
||||
height: 78
|
||||
margin-top: 5
|
||||
phantom: false
|
||||
border: 0 white
|
||||
focusable: false
|
||||
@onClick: modules.game_shop.selectOffer(self)
|
||||
$checked:
|
||||
border: 2 white
|
||||
|
||||
ShopBalanceWidget
|
||||
id: price
|
||||
size: 100 21
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
margin: 4
|
||||
|
||||
ShopBalanceWidget
|
||||
id: additionalPrice
|
||||
size: 100 21
|
||||
anchors.right: prev.right
|
||||
anchors.bottom: prev.top
|
||||
margin-bottom: 4
|
||||
visible: false
|
||||
|
||||
OfferPriceLabel
|
||||
id: count
|
||||
anchors.right: price.left
|
||||
anchors.verticalcenter: price.verticalcenter
|
||||
|
||||
OfferPriceLabel
|
||||
id: additionalCount
|
||||
anchors.right: additionalPrice.left
|
||||
anchors.verticalcenter: additionalPrice.verticalcenter
|
||||
|
||||
FlatPanel
|
||||
id: imagePanel
|
||||
anchors.left: parent.left
|
||||
anchors.verticalcenter: parent.verticalcenter
|
||||
margin-left: 4
|
||||
size: 70 70
|
||||
|
||||
UIWidget
|
||||
id: image
|
||||
anchors.fill: parent
|
||||
margin: 3
|
||||
visible: false
|
||||
phantom: true
|
||||
|
||||
UICreature
|
||||
id: outfit
|
||||
anchors.fill: parent
|
||||
margin-right: 6
|
||||
margin-bottom: 6
|
||||
visible: false
|
||||
phantom: true
|
||||
animate: true
|
||||
|
||||
UICreature
|
||||
id: mount
|
||||
anchors.fill: parent
|
||||
margin-right: 8
|
||||
margin-bottom: 8
|
||||
visible: false
|
||||
phantom: true
|
||||
animate: true
|
||||
|
||||
UIItem
|
||||
id: item
|
||||
anchors.centerIn: parent
|
||||
size: 34 34
|
||||
virtual: true
|
||||
visible: false
|
||||
phantom: true
|
||||
|
||||
Label
|
||||
id: name
|
||||
anchors.top: parent.top
|
||||
anchors.left: prev.right
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
margin: 8
|
||||
text-align: topLeft
|
||||
text-wrap: true
|
||||
color: #C0C0C0
|
||||
|
||||
MainWindow
|
||||
id: shopWindow
|
||||
!text: 'Store'
|
||||
size: 776 533
|
||||
color: #909090
|
||||
@onEscape: modules.game_shop.hide()
|
||||
@onEnter: modules.game_shop.onSearch()
|
||||
|
||||
FlatPanel
|
||||
id: categoriesList
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
margin-bottom: 40
|
||||
width: 180
|
||||
image-color: #C0C0C0
|
||||
layout: verticalBox
|
||||
focusable: false
|
||||
|
||||
TextEdit
|
||||
id: searchTextEdit
|
||||
width: 150
|
||||
anchors.left: prev.left
|
||||
anchors.right: prev.right
|
||||
anchors.bottom: prev.bottom
|
||||
margin: 7
|
||||
margin-right: 29
|
||||
placeholder: Type to search
|
||||
placeholder-color: #667070
|
||||
color: #C0C0C0
|
||||
@onTextChange: modules.game_shop.onTypeSearch(self)
|
||||
|
||||
Button
|
||||
id: searchButton
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
anchors.bottom: prev.bottom
|
||||
icon-source: images/search
|
||||
icon-align: center
|
||||
icon-size: 16 16
|
||||
icon-offset: 2 2
|
||||
icon-color: #FFFFFFFF
|
||||
width: 22
|
||||
enabled: false
|
||||
focusable: false
|
||||
@onClick: modules.game_shop.onSearch()
|
||||
$pressed:
|
||||
icon-offset: 4 4
|
||||
$disabled:
|
||||
icon-color: #C0C0C066
|
||||
|
||||
HorizontalSeparator
|
||||
anchors.top: categoriesList.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
margin-top: 8
|
||||
|
||||
Button
|
||||
id: buttonCancel
|
||||
!text: 'Close'
|
||||
width: 64
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
color: #C0C0C0
|
||||
focusable: false
|
||||
@onClick: modules.game_shop.hide()
|
||||
|
||||
Button
|
||||
id: historyButtonTop
|
||||
!text: 'History'
|
||||
width: 64
|
||||
anchors.top: prev.top
|
||||
anchors.right: prev.left
|
||||
margin-right: 5
|
||||
color: #C0C0C0
|
||||
focusable: false
|
||||
@onClick: modules.game_shop.showHistory()
|
||||
|
||||
ShopBalanceWidget
|
||||
id: balance
|
||||
size: 105 21
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
ShopBalanceSecondWidget
|
||||
id: balanceSecond
|
||||
size: 105 21
|
||||
margin-left: 6
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
|
||||
Button
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
text: Get
|
||||
text-align: left
|
||||
text-offset: 16 0
|
||||
color: #C0C0C0
|
||||
image-color: #89ff89
|
||||
icon: images/coin
|
||||
icon-size: 12 12
|
||||
icon-offset: 40 5
|
||||
size: 64 20
|
||||
margin-left: 6
|
||||
focusable: false
|
||||
@onClick: modules.game_shop.buyPoints()
|
||||
$pressed:
|
||||
text-offset: 17 1
|
||||
icon-offset: 41 5
|
||||
$on:
|
||||
icon: images/coin_second
|
||||
|
||||
Button
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
size: 87 20
|
||||
margin-left: 6
|
||||
icon: images/giftcoin
|
||||
icon-offset: 0 0
|
||||
focusable: false
|
||||
@onClick: modules.game_shop.toggleGiftCoins()
|
||||
$pressed:
|
||||
icon-offset: 1 0
|
||||
|
||||
FlatPanel
|
||||
id: history
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.left: categoriesList.right
|
||||
anchors.bottom: categoriesList.bottom
|
||||
margin-left: 6
|
||||
padding: 7
|
||||
visible: false
|
||||
focusable: false
|
||||
|
||||
HistoryHeader
|
||||
anchors.left: parent.left
|
||||
text: Date
|
||||
size: 150 16
|
||||
|
||||
VerticalSeparator
|
||||
anchors.top: parent.top
|
||||
anchors.left: prev.right
|
||||
height: 16
|
||||
|
||||
HistoryHeader
|
||||
anchors.left: prev.right
|
||||
margin-left: 1
|
||||
text: Balance
|
||||
size: 90 16
|
||||
|
||||
VerticalSeparator
|
||||
anchors.top: parent.top
|
||||
anchors.left: prev.right
|
||||
height: 16
|
||||
|
||||
HistoryHeader
|
||||
anchors.left: prev.right
|
||||
anchors.right: parent.right
|
||||
margin-left: 1
|
||||
text: Description
|
||||
height: 16
|
||||
|
||||
FlatPanel
|
||||
id: list
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: next.top
|
||||
margin-top: 1
|
||||
margin-bottom: 5
|
||||
layout: verticalBox
|
||||
|
||||
Button
|
||||
id: prevPageButton
|
||||
text: Prev Page
|
||||
width: 96
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
visible: false
|
||||
@onClick: modules.game_shop.prevPage()
|
||||
|
||||
Button
|
||||
id: nextPageButton
|
||||
text: Next Page
|
||||
width: 96
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
visible: false
|
||||
@onClick: modules.game_shop.nextPage()
|
||||
|
||||
Label
|
||||
id: pageLabel
|
||||
anchors.verticalcenter: prevPageButton.verticalcenter
|
||||
anchors.left: prevPageButton.right
|
||||
anchors.right: nextPageButton.left
|
||||
text-align: center
|
||||
color: #C0C0C0
|
||||
text: Page 1/1
|
||||
|
||||
FlatPanel
|
||||
id: offers
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.left: categoriesList.right
|
||||
anchors.bottom: categoriesList.bottom
|
||||
margin-left: 6
|
||||
visible: false
|
||||
focusable: false
|
||||
|
||||
ScrollableFlatPanel
|
||||
id: offersList
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
padding-right: 17
|
||||
padding-left: 5
|
||||
margin: 5
|
||||
width: 260
|
||||
vertical-scrollbar: offersListScrollBar
|
||||
layout: verticalBox
|
||||
|
||||
VerticalScrollBar
|
||||
id: offersListScrollBar
|
||||
anchors.top: prev.top
|
||||
anchors.bottom: prev.bottom
|
||||
anchors.right: prev.right
|
||||
step: 50
|
||||
pixels-scroll: true
|
||||
|
||||
UIWidget
|
||||
id: offerDetails
|
||||
anchors.top: prev.top
|
||||
anchors.bottom: prev.bottom
|
||||
anchors.left: prev.right
|
||||
anchors.right: parent.right
|
||||
margin-left: 5
|
||||
margin-right: 5
|
||||
visible: false
|
||||
|
||||
Label
|
||||
id: name
|
||||
anchors.fill: parent
|
||||
text-align: top
|
||||
margin-top: 3
|
||||
color: #C0C0C0
|
||||
|
||||
FlatPanel
|
||||
id: imagePanel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
margin-top: 23
|
||||
margin-left: 5
|
||||
size: 130 130
|
||||
|
||||
UIWidget
|
||||
id: image
|
||||
anchors.fill: parent
|
||||
margin: 3
|
||||
visible: false
|
||||
|
||||
UICreature
|
||||
id: outfit
|
||||
anchors.fill: parent
|
||||
margin-right: 6
|
||||
margin-bottom: 6
|
||||
visible: false
|
||||
animate: true
|
||||
|
||||
UICreature
|
||||
id: mount
|
||||
anchors.fill: parent
|
||||
margin-right: 8
|
||||
margin-bottom: 8
|
||||
visible: false
|
||||
animate: true
|
||||
|
||||
UIItem
|
||||
id: item
|
||||
anchors.centerIn: parent
|
||||
size: 68 68
|
||||
virtual: true
|
||||
visible: false
|
||||
|
||||
OfferBuyButton
|
||||
id: buyButton
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
anchors.right: parent.right
|
||||
margin-left: 8
|
||||
margin-right: 3
|
||||
|
||||
OfferBuyPrice
|
||||
id: price
|
||||
|
||||
OfferBuyButton
|
||||
id: additionalBuyButton
|
||||
margin-top: 4
|
||||
|
||||
OfferBuyPrice
|
||||
id: additionalPrice
|
||||
|
||||
ScrollableFlatPanel
|
||||
id: description
|
||||
anchors.top: imagePanel.bottom
|
||||
anchors.left: imagePanel.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
image-color: #A0A0A0
|
||||
margin-top: 10
|
||||
margin-right: 13
|
||||
padding: 5
|
||||
vertical-scrollbar: descriptionScrollBar
|
||||
layout: verticalBox
|
||||
|
||||
VerticalScrollBar
|
||||
id: descriptionScrollBar
|
||||
anchors.top: prev.top
|
||||
anchors.bottom: prev.bottom
|
||||
anchors.left: prev.right
|
||||
step: 50
|
||||
pixels-scroll: true
|
||||
28
modules/game_shop/gift.otui
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
MainWindow
|
||||
size: 400 110
|
||||
!text: 'Enter player name'
|
||||
@onEnter: modules.game_shop.confirmGift()
|
||||
|
||||
Button
|
||||
id: buttonConfirm
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
!text: 'Confirm'
|
||||
width: 64
|
||||
@onClick: modules.game_shop.confirmGift()
|
||||
|
||||
Button
|
||||
id: buttonCancel
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
!text: 'Cancel'
|
||||
width: 64
|
||||
@onClick: modules.game_shop.cancelGift()
|
||||
|
||||
TextEdit
|
||||
id: targetName
|
||||
anchors.top: parent.top
|
||||
anchors.left: buttonCancel.right
|
||||
anchors.right: buttonConfirm.left
|
||||
margin-left: 10
|
||||
margin-right: 10
|
||||
148
modules/game_shop/giftcoins.otui
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
GiftCoinsLabel < Label
|
||||
color: #C0C0C0
|
||||
|
||||
MainWindow
|
||||
id: transferWindow
|
||||
!text: tr('Gift Tibia Coins')
|
||||
size: 280 238
|
||||
color: #909090
|
||||
@onEscape: modules.game_shop.cancelGiftCoins()
|
||||
$on:
|
||||
size: 280 300
|
||||
|
||||
GiftCoinsLabel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 4
|
||||
text-wrap: true
|
||||
height: 56
|
||||
text: Please select the amount of Tibia Coins you like to gift and enter the name of the character that should receive the Tibia Coins.
|
||||
|
||||
GiftCoinsLabel
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
margin-top: 10
|
||||
text: Reciepient:
|
||||
|
||||
TextEdit
|
||||
id: recipient
|
||||
anchors.verticalCenter: prev.verticalCenter
|
||||
anchors.right: parent.right
|
||||
width: 150
|
||||
text-align: left
|
||||
|
||||
GiftCoinsLabel
|
||||
id: coinsBalance
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
margin-top: 10
|
||||
text-align: left
|
||||
text-horizontal-auto-resize: true
|
||||
text: Transferable Tibia Coins:
|
||||
|
||||
UIWidget
|
||||
anchors.left: prev.right
|
||||
anchors.verticalcenter: prev.verticalcenter
|
||||
image-source: images/coin
|
||||
margin-left: 6
|
||||
phantom: true
|
||||
|
||||
GiftCoinsLabel
|
||||
id: coinsAmountLabel
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
margin-top: 10
|
||||
text-align: left
|
||||
text-horizontal-auto-resize: true
|
||||
text: Amount to gift: 0
|
||||
|
||||
UIWidget
|
||||
anchors.left: prev.right
|
||||
anchors.verticalcenter: prev.verticalcenter
|
||||
image-source: images/coin
|
||||
margin-left: 6
|
||||
phantom: true
|
||||
|
||||
HorizontalScrollBar
|
||||
id: coinsAmountScrollbar
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 10
|
||||
step: 25
|
||||
minimum: 0
|
||||
maximum: 0
|
||||
@onValueChange: modules.game_shop.changeCoinsAmount(self:getValue())
|
||||
|
||||
GiftCoinsLabel
|
||||
id: taskPointsBalance
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
margin-top: 10
|
||||
text-align: left
|
||||
text-horizontal-auto-resize: true
|
||||
text: Transferable Task points:
|
||||
|
||||
UIWidget
|
||||
id: taskPointsCoin
|
||||
anchors.left: prev.right
|
||||
anchors.verticalcenter: prev.verticalcenter
|
||||
image-source: images/coin_second
|
||||
margin-left: 6
|
||||
phantom: true
|
||||
|
||||
GiftCoinsLabel
|
||||
id: taskPointsAmountLabel
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
margin-top: 10
|
||||
text-align: left
|
||||
text-horizontal-auto-resize: true
|
||||
text: Amount to gift: 0
|
||||
|
||||
UIWidget
|
||||
id: taskPointsLabelCoin
|
||||
anchors.left: prev.right
|
||||
anchors.verticalcenter: prev.verticalcenter
|
||||
image-source: images/coin_second
|
||||
margin-left: 6
|
||||
phantom: true
|
||||
|
||||
HorizontalScrollBar
|
||||
id: taskPointsAmountScrollbar
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 10
|
||||
step: 25
|
||||
minimum: 0
|
||||
maximum: 0
|
||||
@onValueChange: modules.game_shop.changeTaskPointsAmount(self:getValue())
|
||||
|
||||
HorizontalSeparator
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: cancelButton.top
|
||||
margin-bottom: 8
|
||||
|
||||
Button
|
||||
id: cancelButton
|
||||
!text: tr('Cancel')
|
||||
font: cipsoftFont
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
size: 45 21
|
||||
margin-top: 15
|
||||
margin-right: 5
|
||||
@onClick: modules.game_shop.cancelGiftCoins()
|
||||
|
||||
Button
|
||||
id: giftButton
|
||||
!text: tr('Gift')
|
||||
font: cipsoftFont
|
||||
size: 45 21
|
||||
anchors.verticalCenter: prev.verticalCenter
|
||||
anchors.right: prev.left
|
||||
margin-right: 5
|
||||
@onClick: modules.game_shop.confirmGiftCoins()
|
||||
BIN
modules/game_shop/images/180_days.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
modules/game_shop/images/30_days.png
Normal file
|
After Width: | Height: | Size: 741 B |
BIN
modules/game_shop/images/360_days.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
modules/game_shop/images/90_days.png
Normal file
|
After Width: | Height: | Size: 914 B |
BIN
modules/game_shop/images/All_regular_Blessings.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
modules/game_shop/images/Blood_of_the_Mountain.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
modules/game_shop/images/Heart_of_the_Mountain.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
modules/game_shop/images/Name_Change.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
modules/game_shop/images/Sex_Change.png
Normal file
|
After Width: | Height: | Size: 765 B |
BIN
modules/game_shop/images/Temple_Teleport.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
modules/game_shop/images/The_Embrace_of_Tibia.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
modules/game_shop/images/The_Fire_of_the_Suns.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
modules/game_shop/images/The_Spark_of_the_Phoenix.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
modules/game_shop/images/The_Spiritual_Shielding.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
modules/game_shop/images/The_Wisdom_of_Solitude.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
modules/game_shop/images/Twist_of_Fate.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
modules/game_shop/images/arrow_down.png
Normal file
|
After Width: | Height: | Size: 561 B |
BIN
modules/game_shop/images/arrow_right.png
Normal file
|
After Width: | Height: | Size: 566 B |
BIN
modules/game_shop/images/categories.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
modules/game_shop/images/coin.png
Normal file
|
After Width: | Height: | Size: 508 B |
BIN
modules/game_shop/images/coin_second.png
Normal file
|
After Width: | Height: | Size: 328 B |
BIN
modules/game_shop/images/ex/00001[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
modules/game_shop/images/ex/00002[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
modules/game_shop/images/ex/00005[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
modules/game_shop/images/ex/00010[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
modules/game_shop/images/ex/00012[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
modules/game_shop/images/ex/00013[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
modules/game_shop/images/ex/00015[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
modules/game_shop/images/ex/00017[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
modules/game_shop/images/ex/00020[27x52x8BPP].png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
modules/game_shop/images/ex/00021[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
modules/game_shop/images/ex/00025[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
modules/game_shop/images/ex/00026[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
modules/game_shop/images/ex/00027[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
modules/game_shop/images/ex/00029[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
modules/game_shop/images/ex/00031[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
modules/game_shop/images/ex/00036[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
modules/game_shop/images/ex/00045[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
modules/game_shop/images/ex/00051[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
modules/game_shop/images/ex/00052[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
modules/game_shop/images/ex/00053[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
modules/game_shop/images/ex/00054[64x64x4BPP].png
Normal file
|
After Width: | Height: | Size: 670 B |
BIN
modules/game_shop/images/ex/00057[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 796 B |
BIN
modules/game_shop/images/ex/00058[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
modules/game_shop/images/ex/00060[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
modules/game_shop/images/ex/00062[64x64x8BPP].png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
modules/game_shop/images/ex/00063[64x64x4BPP].png
Normal file
|
After Width: | Height: | Size: 735 B |
BIN
modules/game_shop/images/giftcoin.png
Normal file
|
After Width: | Height: | Size: 308 B |
BIN
modules/game_shop/images/mount.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
modules/game_shop/images/search.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -1,361 +0,0 @@
|
|||
-- Instruction:
|
||||
-- creaturescripts.xml <event type="extendedopcode" name="Shop" script="shop.lua" />
|
||||
-- and in login.lua player:registerEvent("Shop")
|
||||
-- create sql table shop_history
|
||||
-- set variables
|
||||
-- set up function init(), add there items and categories, follow examples
|
||||
-- set up callbacks at the bottom to add player item/outfit/whatever you want
|
||||
|
||||
local SHOP_EXTENDED_OPCODE = 201
|
||||
local SHOP_OFFERS = {}
|
||||
local SHOP_CALLBACKS = {}
|
||||
local SHOP_CATEGORIES = nil
|
||||
local SHOP_BUY_URL = "http://otland.net" -- can be empty
|
||||
local SHOP_AD = { -- can be nil
|
||||
image = "",
|
||||
url = "http://otclient.ovh",
|
||||
text = ""
|
||||
}
|
||||
local MAX_PACKET_SIZE = 50000
|
||||
-- local MAX_PACKET_SIZE = 5088
|
||||
--[[ SQL TABLE
|
||||
|
||||
CREATE TABLE `shop_history` (
|
||||
`id` int(11) NOT NULL,
|
||||
`account` int(11) NOT NULL,
|
||||
`player` int(11) NOT NULL,
|
||||
`date` datetime NOT NULL,
|
||||
`title` varchar(100) NOT NULL,
|
||||
`cost` int(11) NOT NULL,
|
||||
`details` varchar(500) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
ALTER TABLE `shop_history`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
ALTER TABLE `shop_history`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
|
||||
]]--
|
||||
|
||||
function init()
|
||||
-- print(json.encode(g_game.getLocalPlayer():getOutfit())) -- in console in otclient, will print current outfit and mount
|
||||
|
||||
SHOP_CATEGORIES = {}
|
||||
|
||||
local category1 = addCategory({
|
||||
type="item",
|
||||
item=ItemType(2160):getClientId(),
|
||||
count=100,
|
||||
name="Items"
|
||||
})
|
||||
local category2 = addCategory({
|
||||
type="outfit",
|
||||
name="Outfits",
|
||||
outfit={
|
||||
mount=0,
|
||||
feet=114,
|
||||
legs=114,
|
||||
body=116,
|
||||
type=143,
|
||||
auxType=0,
|
||||
addons=3,
|
||||
head=2,
|
||||
rotating=true
|
||||
}
|
||||
})
|
||||
local category3 = addCategory({
|
||||
type="image",
|
||||
image="http://otclient.ovh/images/137.png",
|
||||
name="Category with http image"
|
||||
})
|
||||
local category4 = addCategory({
|
||||
type="image",
|
||||
image="/data/images/game/states/electrified.png",
|
||||
name="Category with local image"
|
||||
})
|
||||
|
||||
|
||||
category1.addItem(1, 2160, 1, "1 Crystal coin", "description of cristal coin")
|
||||
category1.addItem(5, 2160, 5, "5 Crystal coin", "description of cristal coin")
|
||||
category1.addItem(50, 2160, 50, "50 Crystal coin", "description of cristal coin")
|
||||
category1.addItem(90, 2160, 100, "100 Crystal coin", "description of cristal coin")
|
||||
category1.addItem(200, 2493, 1, "Demon helmet1", "woo\ndemon helmet\nnice, you should buy it")
|
||||
category1.addItem(1, 2160, 1, "1 Crystal coin1", "description of cristal coin")
|
||||
category1.addItem(5, 2160, 5, "5 Crystal coin1", "description of cristal coin")
|
||||
category1.addItem(50, 2160, 50, "50 Crystal coin1", "description of cristal coin")
|
||||
category1.addItem(90, 2160, 100, "100 Crystal coin1", "description of cristal coin")
|
||||
category1.addItem(200, 2493, 1, "Demon helmet2", "woo\ndemon helmet\nnice, you should buy it")
|
||||
category1.addItem(1, 2160, 1, "1 Crystal coin3", "description of cristal coin")
|
||||
category1.addItem(5, 2160, 5, "5 Crystal coin3", "description of cristal coin")
|
||||
category1.addItem(50, 2160, 50, "50 Crystal coin3", "description of cristal coin")
|
||||
category1.addItem(90, 2160, 100, "100 Crystal coin3", "description of cristal coin")
|
||||
category1.addItem(200, 2493, 1, "Demon helmet3", "wooxD\ndemon helmet\nnice, you should buy it")
|
||||
|
||||
category2.addOutfit(500, {
|
||||
mount=0,
|
||||
feet=114,
|
||||
legs=114,
|
||||
body=116,
|
||||
type=143,
|
||||
auxType=0,
|
||||
addons=3,
|
||||
head=2,
|
||||
rotating=true
|
||||
}, "title of this cool outfit or whatever", "this is your new cool outfit. You can buy it here.\nsrlsy")
|
||||
category2.addOutfit(100, {
|
||||
mount=682,
|
||||
feet=0,
|
||||
legs=0,
|
||||
body=0,
|
||||
type=143,
|
||||
auxType=0,
|
||||
addons=0,
|
||||
head=0,
|
||||
rotating=true
|
||||
}, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
|
||||
|
||||
category2.addOutfit(100, {
|
||||
mount=0,
|
||||
feet=0,
|
||||
legs=0,
|
||||
body=0,
|
||||
type=35,
|
||||
auxType=0,
|
||||
addons=0,
|
||||
head=0,
|
||||
rotating=true
|
||||
}, "Demon outfit", "Want be a demon?\nNo problem")
|
||||
category2.addOutfit(100, {
|
||||
mount=0,
|
||||
feet=0,
|
||||
legs=0,
|
||||
body=0,
|
||||
type=35,
|
||||
auxType=0,
|
||||
addons=0,
|
||||
head=0,
|
||||
rotating=false
|
||||
}, "Demon outfit2", "This one is not rotating")
|
||||
|
||||
category4.addImage(10000, "/data/images/game/states/haste.png", "Offer with local image", "another local image\n/data/images/game/states/haste.png")
|
||||
category4.addImage(10000, "http://otclient.ovh/images/freezing.png", "Offer with remote image and custom buy action", "blalasdasd image\nhttp://otclient.ovh/images/freezing.png", customImageBuyAction)
|
||||
end
|
||||
|
||||
function addCategory(data)
|
||||
data['offers'] = {}
|
||||
table.insert(SHOP_CATEGORIES, data)
|
||||
table.insert(SHOP_CALLBACKS, {})
|
||||
local index = #SHOP_CATEGORIES
|
||||
return {
|
||||
addItem = function(cost, itemId, count, title, description, callback)
|
||||
if not callback then
|
||||
callback = defaultItemBuyAction
|
||||
end
|
||||
table.insert(SHOP_CATEGORIES[index]['offers'], {
|
||||
cost=cost,
|
||||
type="item",
|
||||
item=ItemType(itemId):getClientId(), -- displayed
|
||||
itemId=itemId,
|
||||
count=count,
|
||||
title=title,
|
||||
description=description
|
||||
})
|
||||
table.insert(SHOP_CALLBACKS[index], callback)
|
||||
end,
|
||||
addOutfit = function(cost, outfit, title, description, callback)
|
||||
if not callback then
|
||||
callback = defaultOutfitBuyAction
|
||||
end
|
||||
table.insert(SHOP_CATEGORIES[index]['offers'], {
|
||||
cost=cost,
|
||||
type="outfit",
|
||||
outfit=outfit,
|
||||
title=title,
|
||||
description=description
|
||||
})
|
||||
table.insert(SHOP_CALLBACKS[index], callback)
|
||||
end,
|
||||
addImage = function(cost, image, title, description, callback)
|
||||
if not callback then
|
||||
callback = defaultImageBuyAction
|
||||
end
|
||||
table.insert(SHOP_CATEGORIES[index]['offers'], {
|
||||
cost=cost,
|
||||
type="image",
|
||||
image=image,
|
||||
title=title,
|
||||
description=description
|
||||
})
|
||||
table.insert(SHOP_CALLBACKS[index], callback)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
function getPoints(player)
|
||||
local points = 0
|
||||
local resultId = db.storeQuery("SELECT `premium_points` FROM `accounts` WHERE `id` = " .. player:getAccountId())
|
||||
if resultId ~= false then
|
||||
points = result.getDataInt(resultId, "premium_points")
|
||||
result.free(resultId)
|
||||
end
|
||||
return points
|
||||
end
|
||||
|
||||
function getStatus(player)
|
||||
local status = {
|
||||
ad = SHOP_AD,
|
||||
points = getPoints(player),
|
||||
buyUrl = SHOP_BUY_URL
|
||||
}
|
||||
return status
|
||||
end
|
||||
|
||||
function sendJSON(player, action, data, forceStatus)
|
||||
local status = nil
|
||||
if not player:getStorageValue(1150001) or player:getStorageValue(1150001) + 10 < os.time() or forceStatus then
|
||||
status = getStatus(player)
|
||||
end
|
||||
player:setStorageValue(1150001, os.time())
|
||||
|
||||
|
||||
local buffer = json.encode({action = action, data = data, status = status})
|
||||
local s = {}
|
||||
for i=1, #buffer, MAX_PACKET_SIZE do
|
||||
s[#s+1] = buffer:sub(i,i+MAX_PACKET_SIZE - 1)
|
||||
end
|
||||
local msg = NetworkMessage()
|
||||
if #s == 1 then
|
||||
msg:addByte(50)
|
||||
msg:addByte(SHOP_EXTENDED_OPCODE)
|
||||
msg:addString(s[1])
|
||||
msg:sendToPlayer(player)
|
||||
return
|
||||
end
|
||||
-- split message if too big
|
||||
msg:addByte(50)
|
||||
msg:addByte(SHOP_EXTENDED_OPCODE)
|
||||
msg:addString("S" .. s[1])
|
||||
msg:sendToPlayer(player)
|
||||
for i=2,#s - 1 do
|
||||
msg = NetworkMessage()
|
||||
msg:addByte(50)
|
||||
msg:addByte(SHOP_EXTENDED_OPCODE)
|
||||
msg:addString("P" .. s[i])
|
||||
msg:sendToPlayer(player)
|
||||
end
|
||||
msg = NetworkMessage()
|
||||
msg:addByte(50)
|
||||
msg:addByte(SHOP_EXTENDED_OPCODE)
|
||||
msg:addString("E" .. s[#s])
|
||||
msg:sendToPlayer(player)
|
||||
end
|
||||
|
||||
function sendMessage(player, title, msg, forceStatus)
|
||||
sendJSON(player, "message", {title=title, msg=msg}, forceStatus)
|
||||
end
|
||||
|
||||
function onExtendedOpcode(player, opcode, buffer)
|
||||
if opcode ~= SHOP_EXTENDED_OPCODE then
|
||||
return false
|
||||
end
|
||||
local status, json_data = pcall(function() return json.decode(buffer) end)
|
||||
if not status then
|
||||
return false
|
||||
end
|
||||
|
||||
local action = json_data['action']
|
||||
local data = json_data['data']
|
||||
if not action or not data then
|
||||
return false
|
||||
end
|
||||
|
||||
if SHOP_CATEGORIES == nil then
|
||||
init()
|
||||
end
|
||||
|
||||
if action == 'init' then
|
||||
sendJSON(player, "categories", SHOP_CATEGORIES)
|
||||
elseif action == 'buy' then
|
||||
processBuy(player, data)
|
||||
elseif action == "history" then
|
||||
sendHistory(player)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function processBuy(player, data)
|
||||
local categoryId = tonumber(data["category"])
|
||||
local offerId = tonumber(data["offer"])
|
||||
local offer = SHOP_CATEGORIES[categoryId]['offers'][offerId]
|
||||
local callback = SHOP_CALLBACKS[categoryId][offerId]
|
||||
if not offer or not callback or data["title"] ~= offer["title"] or data["cost"] ~= offer["cost"] then
|
||||
sendJSON(player, "categories", SHOP_CATEGORIES) -- refresh categories, maybe invalid
|
||||
return sendMessage(player, "Error!", "Invalid offer")
|
||||
end
|
||||
local points = getPoints(player)
|
||||
if not offer['cost'] or offer['cost'] > points or points < 1 then
|
||||
return sendMessage(player, "Error!", "You don't have enough points to buy " .. offer['title'] .."!", true)
|
||||
end
|
||||
local status = callback(player, offer)
|
||||
if status == true then
|
||||
db.query("UPDATE `accounts` set `premium_points` = `premium_points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())
|
||||
db.asyncQuery("INSERT INTO `shop_history` (`account`, `player`, `date`, `title`, `cost`, `details`) VALUES ('" .. player:getAccountId() .. "', '" .. player:getGuid() .. "', NOW(), " .. db.escapeString(offer['title']) .. ", " .. db.escapeString(offer['cost']) .. ", " .. db.escapeString(json.encode(offer)) .. ")")
|
||||
return sendMessage(player, "Success!", "You bought " .. offer['title'] .."!", true)
|
||||
end
|
||||
if status == nil or status == false then
|
||||
status = "Unknown error while buying " .. offer['title']
|
||||
end
|
||||
sendMessage(player, "Error!", status)
|
||||
end
|
||||
|
||||
function sendHistory(player)
|
||||
if player:getStorageValue(1150002) and player:getStorageValue(1150002) + 10 > os.time() then
|
||||
return -- min 10s delay
|
||||
end
|
||||
player:setStorageValue(1150002, os.time())
|
||||
|
||||
local history = {}
|
||||
local resultId = db.storeQuery("SELECT * FROM `shop_history` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")
|
||||
|
||||
if resultId ~= false then
|
||||
repeat
|
||||
local details = result.getDataString(resultId, "details")
|
||||
local status, json_data = pcall(function() return json.decode(details) end)
|
||||
if not status then
|
||||
json_data = {
|
||||
type = "image",
|
||||
title = result.getDataString(resultId, "title"),
|
||||
cost = result.getDataInt(resultId, "cost")
|
||||
}
|
||||
end
|
||||
table.insert(history, json_data)
|
||||
history[#history]["description"] = "Bought on " .. result.getDataString(resultId, "date") .. " for " .. result.getDataInt(resultId, "cost") .. " points."
|
||||
until not result.next(resultId)
|
||||
result.free(resultId)
|
||||
end
|
||||
|
||||
sendJSON(player, "history", history)
|
||||
end
|
||||
|
||||
-- BUY CALLBACKS
|
||||
-- May be useful: print(json.encode(offer))
|
||||
|
||||
function defaultItemBuyAction(player, offer)
|
||||
-- todo: check if has capacity
|
||||
if player:addItem(offer["itemId"], offer["count"], false) then
|
||||
return true
|
||||
end
|
||||
return "Can't add item! Do you have enough space?"
|
||||
end
|
||||
|
||||
function defaultOutfitBuyAction(player, offer)
|
||||
return "default outfit buy action is not implemented"
|
||||
end
|
||||
|
||||
function defaultImageBuyAction(player, offer)
|
||||
return "default image buy action is not implemented"
|
||||
end
|
||||
|
||||
function customImageBuyAction(player, offer)
|
||||
return "custom image buy action is not implemented. Offer: " .. offer['title']
|
||||
end
|
||||
846
modules/game_shop/serverSIDE/data/scripts/game_shop.lua
Normal file
|
|
@ -0,0 +1,846 @@
|
|||
local DONATION_URL = "https://github.com/mehah/otclient"
|
||||
local GAME_SHOP = nil
|
||||
local SECOND_CURRENCY_ENABLED = false
|
||||
|
||||
local LoginEvent = CreatureEvent("GameShopLogin")
|
||||
|
||||
local chars = {
|
||||
' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
|
||||
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
|
||||
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
|
||||
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
|
||||
}
|
||||
|
||||
local ExtendedOPCodes = {
|
||||
CODE_GAMESHOP = 201
|
||||
}
|
||||
|
||||
local forbiddenWords = {
|
||||
'gm','adm','tutor','god','cm','admin','owner','g m','g o d','g0d','g 0 d','c m','administrator','senior','a d m',
|
||||
'Trainer','Devil My Cry','Lavahole','Deaththrower','A Carved Stone Tile','Acolyte Of The Cult','Adept Of The Cult','Amazon','Ancient Scarab','Ashmunrah','Assassin','Azure Frog','Badger','Bandit','Banshee','Barbarian Bloodwalker','Barbarian Brutetamer','Barbarian Headsplitter','Barbarian Skullhunter','Bat','Bear','Behemoth','Beholder','Betrayed Wraith','Black Knight','Black Sheep','Blightwalker','Blood Crab','Blue Butterfly','Blue Djinn','Bonebeast','Braindeath','Bug','Carniphila','Carrion Worm','Cave Rat','Centipede','Chakoya Toolshaper','Chakoya Tribewarden','Chakoya Windcaller','Chicken','Cobra','Coral Frog','Crab','Crimson Frog','Crocodile','Crypt Shambler','Crystal Spider','Cyclops','Dark Magician','Dark Monk','Dark Torturer','Deathslicer','Deer','Defiler','Demon Skeleton','Demon','Destroyer','Diabolic Imp','Dipthrah','Dog','Dragon Lord','Dragon','Dwarf Geomancer','Dwarf Guard','Dwarf Soldier','Dwarf','Dworc Fleshhunter','Dworc Venomsniper','Dworc Voodoomaster','Efreet','Elder Beholder','Elephant','Elf Arcanist','Elf Scout','Elf','Enlightened Of The Cult','Eye Of The Seven','Fire Devil','Fire Elemental','Flamethrower','Flamingo','Frost Dragon','Frost Giant','Frost Giantess','Frost Troll','Fury','Gargoyle','Gazer','Ghost','Ghoul','Giant Spider','Goblin','Green Djinn','Green Frog','Hand Of Cursed Fate','Hell Hole','Hellfire Fighter','Hellhound','Hero','Hunter','Husky','Hyaena','Hydra','Ice Golem','Ice Witch','Juggernaut','Kongra','Larva','Lich','Lion','Lizard Noble','Lizard Sentinel','Lizard Snakecharmer','Lizard Templar','Lost Soul','Magic Pillar','Magicthrower','Mahrdis','Mammoth','Marid','Massive Fire Elemental','Massive Water Elemental','Merlkin','Minotaur Archer','Minotaur Guard','Minotaur Mage','Minotaur','Monk','Morguthis','Mummy','Necromancer','Nightmare','Nomad','Novice Of The Cult','Omruc','Orc Berserker','Orc Leader','Orc Rider','Orc Shaman','Orc Spearman','Orc Warlord','Orc Warrior','Orc','Orchid Frog','Panda','Parrot','Penguin','Phantasm Summon','Phantasm','Pig','Pillar','Pirate Buccaneer','Pirate Corsair','Pirate Cutthroat','Pirate Ghost','Pirate Marauder','Pirate Skeleton','Plaguesmith','Plaguethrower','Poison Spider','Polar Bear','Priestess','Purple Butterfly','Quara Constrictor Scout','Quara Constrictor','Quara Hydromancer Scout','Quara Hydromancer','Quara Mantassin Scout','Quara Mantassin','Quara Pincher Scout','Quara Pincher','Quara Predator Scout','Quara Predator','Rabbit','Rahemos','Rat','Red Butterfly','Rotworm','Scarab','Scorpion','Seagull','Serpent Spawn','Sheep','Shredderthrower','Sibang','Silver Rabbit','Skeleton','Skunk','Slime','Smuggler','Snake','Son Of Verminor','Spectre','Spider','Spit Nettle','Stalker','Stone Golem','Swamp Troll','Tarantula','Terror Bird','Thalas','Thornback Tortoise','Tiger','Toad','Tortoise','Troll','Undead Dragon','Valkyrie','Vampire','Vashresamun','War wolf','Warlock','Wasp','Wild Warrior','Winter Wolf','Witch','Wolf','Wyvern','Yellow Butterfly','Yeti','Annihilon','Apprentice Sheng','Barbaria','Bones','Brutus Bloodbeard','Countess Sorrow','Deadeye Devious','Demodras','Dharalion','Dire Penguin','Dracola','Fernfang','Ferumbras','Fluffy','Foreman Kneebiter','General Murius','Ghazbaran','Golgordan','Grorlam','Hairman The Huge', 'Hellgorak','Koshei The Deathless','Latrivan','Lethal Lissy','Mad Technomancer','Madareth','Man In The Cave','Massacre','Minishabaal','Morgaroth','Mr. Punish','Munster','Necropharus','Orshabaal', 'Ron the Ripper','The Abomination','The Evil Eye','The Handmaiden','The Horned Fox','The Imperor','The Old Widow','The Plasmother','Thul','Tiquandas Revenge','Undead Minion','Ungreez','Ushuriel','Xenia','Zugurosh'
|
||||
}
|
||||
|
||||
local maxWords = 5
|
||||
local maxLength = 20
|
||||
local minChars = 2
|
||||
|
||||
function LoginEvent.onLogin(player)
|
||||
player:registerEvent("GameShopExtended")
|
||||
return true
|
||||
end
|
||||
|
||||
local CATEGORY_NONE = -1
|
||||
local CATEGORY_PREMIUM = 0
|
||||
local CATEGORY_ITEM = 1
|
||||
local CATEGORY_BLESSING = 2
|
||||
local CATEGORY_OUTFIT = 3
|
||||
local CATEGORY_MOUNT = 4
|
||||
local CATEGORY_EXTRAS = 5
|
||||
|
||||
local HEALTH_POTION_DESCRIPTION = "Restores your character's hit points.\n\n- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded"
|
||||
local MANA_POTION_DESCRIPTION = "Refills your character's mana.\n\n- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded"
|
||||
|
||||
function gameShopInitialize()
|
||||
GAME_SHOP = {
|
||||
categories = {},
|
||||
categoriesId = {},
|
||||
offers = {}
|
||||
}
|
||||
|
||||
addCategory(nil, "Premium Time", 20, CATEGORY_PREMIUM, "Enhance your gaming experience by gaining additional abilities and advantages:\n\n* access to Premium areas\n* use Tibia's transport system (ships, carpet)\n* more spells\n* rent houses\n* found guilds\n* larger Depots\n* and many more\n\n- valid for all characters on this account\n- activated at purchase")
|
||||
addItem("Premium Time", "30 Days of Premium Time", "30_days", 250, false, 30)
|
||||
addItem("Premium Time", "90 Days of Premium Time", "90_days", 750, false, 90)
|
||||
addItem("Premium Time", "180 Days of Premium Time", "180_days", 1500, false, 180)
|
||||
addItem("Premium Time", "360 Days of Premium Time", "360_days", 3000, false, 360)
|
||||
|
||||
addCategory(nil, "Consumables", 6, CATEGORY_NONE)
|
||||
addCategory("Consumables", "Blessings", 8, CATEGORY_BLESSING, "Reduces your character's chance to lose any items as well as the amount of your character's experience and skill loss upon death:\n\n* 1 blessing = 8.00% less Skill / XP loss, 30% equipment protection\n* 2 blessing = 16.00% less Skill / XP loss, 55% equipment protection\n* 3 blessing = 24.00% less Skill / XP loss, 75% equipment protection\n* 4 blessing = 32.00% less Skill / XP loss, 90% equipment protection\n* 5 blessing = 40.00% less Skill / XP loss, 100% equipment protection\n* 6 blessing = 48.00% less Skill / XP loss, 100% equipment protection\n* 7 blessing = 56.00% less Skill / XP loss, 100% equipment protection\n\n- only usable by purchasing character\n- maximum amount that can be owned by character: 5\n- added directly to the Record of Blessings\n- characters with a red or black skull will always lose all equipment upon death")
|
||||
addItem("Blessings", "All regular Blessings", "All_regular_Blessings", 130, false, -1)
|
||||
addItem("Blessings", "The Spiritual Shielding", "The_Spiritual_Shielding", 25, false, 1)
|
||||
addItem("Blessings", "The Embrace of Tibia", "The_Embrace_of_Tibia", 25, false, 2)
|
||||
addItem("Blessings", "The Fire of the Suns", "The_Fire_of_the_Suns", 25, false, 3)
|
||||
addItem("Blessings", "The Wisdom of Solitude", "The_Wisdom_of_Solitude", 25, false, 4)
|
||||
addItem("Blessings", "The Spark of the Phoenix", "The_Spark_of_the_Phoenix", 25, false, 5)
|
||||
|
||||
addCategory("Consumables", "Potions", 10, CATEGORY_ITEM)
|
||||
addItem("Potions", "Mana Potion", 7620, 6, false, 125, MANA_POTION_DESCRIPTION)
|
||||
addItem("Potions", "Mana Potion", 7620, 12, false, 300, MANA_POTION_DESCRIPTION)
|
||||
addItem("Potions", "Strong Mana Potion", 7589, 7, false, 100, MANA_POTION_DESCRIPTION)
|
||||
addItem("Potions", "Strong Mana Potion", 7589, 17, false, 250, MANA_POTION_DESCRIPTION)
|
||||
addItem("Potions", "Great Mana Potion", 7590, 11, false, 100, MANA_POTION_DESCRIPTION)
|
||||
addItem("Potions", "Great Mana Potion", 7590, 26, false, 250, MANA_POTION_DESCRIPTION)
|
||||
addItem("Potions", "Health Potion", 7618, 6, false, 125, HEALTH_POTION_DESCRIPTION)
|
||||
addItem("Potions", "Health Potion", 7618, 11, false, 300, HEALTH_POTION_DESCRIPTION)
|
||||
addItem("Potions", "Strong Health Potion", 7588, 10, false, 100, HEALTH_POTION_DESCRIPTION)
|
||||
addItem("Potions", "Strong Health Potion", 7588, 21, false, 250, HEALTH_POTION_DESCRIPTION)
|
||||
addItem("Potions", "Great Health Potion", 7591, 18, false, 100, HEALTH_POTION_DESCRIPTION)
|
||||
addItem("Potions", "Great Health Potion", 7591, 41, false, 250, HEALTH_POTION_DESCRIPTION)
|
||||
|
||||
addCategory("Consumables", "Runes", 19, CATEGORY_ITEM)
|
||||
addItem("Runes", "Animate Dead Rune", 2316, 75, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nAfter a long time of research, the magicians of Edron succeeded in storing some life energy in a rune. When this energy was unleashed onto a body it was found that an undead creature arose that could be mentally controlled by the user of the rune. This rune is useful to create allies in combat.")
|
||||
addItem("Runes", "Avalanche Rune", 2274, 12, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nThe ice damage which arises from this rune is a useful weapon in every battle but it comes in particularly handy if you fight against a horde of creatures dominated by the element fire.")
|
||||
addItem("Runes", "Chameleon Rune", 2291, 42, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nThe metamorphosis caused by this rune is only superficial, and while casters who are using the rune can take on the exterior form of nearly any inanimate object, they will always retain their original smell and mental abilities. So there is no real practical use for this rune, making this largely a fun rune.")
|
||||
addItem("Runes", "Convince Creature Rune", 2290, 16, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nUsing this rune together with some mana, you can convince certain creatures. The needed amount of mana is determined by the power of the creature one wishes to convince, so the amount of mana to convince a rat is lower than that which is needed for an orc.")
|
||||
addItem("Runes", "Cure Poison Rune", 2266, 13, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nIn the old days, many adventurers fell prey to poisonous creatures that were roaming the caves and forests. After many years of research druids finally succeeded in altering the cure poison spell so it could be bound to a rune. By using this rune it is possible to stop the effect of any known poison.")
|
||||
addItem("Runes", "Disintegrate Rune", 2310, 5, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nNothing is worse than being cornered when fleeing from an enemy you just cannot beat, especially if the obstacles in your way are items you could easily remove if only you had the time! However, there is one reliable remedy: The Disintegrate rune will instantly destroy up to 500 movable items that are in your way, making room for a quick escape.")
|
||||
addItem("Runes", "Energy Bomb Rune", 2262, 40, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nUsing the Energy Bomb rune will create a field of deadly energy that deals damage to all who carelessly step into it. Its area of effect is covering a full 9 square metres! Creatures that are caught in the middle of an Energy Bomb are frequently confused by the unexpected effect, and some may even stay in the field of deadly sparks for a while.")
|
||||
addItem("Runes", "Energy Field Rune", 2277, 8, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nThis spell creates a limited barrier made up of crackling energy that will cause electrical damage to all those passing through. Since there are few creatures that are immune to the harmful effects of energy this spell is not to be underestimated.")
|
||||
addItem("Runes", "Energy Wall Rune", 2279, 17, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nCasting this spell generates a solid wall made up of magical energy. Walls made this way surpass any other magically created obstacle in width, so it is always a good idea to have an Energy Wall rune or two in one's pocket when travelling through the wilderness.")
|
||||
addItem("Runes", "Explosion Rune", 2313, 6, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nThis rune must be aimed at areas rather than at specific creatures, so it is possible for explosions to be unleashed even if no targets are close at all. These explosions cause a considerable physical damage within a substantial blast radius.")
|
||||
addItem("Runes", "Fireball Rune", 2302, 6, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nWhen this rune is used a massive fiery ball is released which hits the aimed foe with immense power. It is especially effective against opponents of the element earth.")
|
||||
addItem("Runes", "Fire Bomb Rune", 2305, 29, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nThis rune is a deadly weapon in the hands of the skilled user. On releasing it an area of 9 square metres is covered by searing flames that will scorch all those that are unfortunate enough to be caught in them. Worse, many monsters are confused by the unexpected blaze, and with a bit of luck a caster will even manage to trap his opponents by using the spell.")
|
||||
addItem("Runes", "Fire Field Rune", 2301, 6, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nWhen this rune is used a field of one square metre is covered by searing fire that will last for some minutes, gradually diminishing as the blaze wears down. As with all field spells, Fire Field is quite useful to block narrow passageways or to create large, connected barriers.")
|
||||
addItem("Runes", "Fire Wall Rune", 2303, 12, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nThis rune offers reliable protection against all creatures that are afraid of fire. The exceptionally long duration of the spell as well as the possibility to form massive barriers or even protective circles out of fire walls make this a versatile, practical spell.")
|
||||
addItem("Runes", "Great Fireball Rune", 2304, 12, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nA shot of this rune affects a huge area - up to 37 square metres! It stands to reason that the Great Fireball is a favourite of most Tibians, as it is well suited both to hit whole crowds of monsters and individual targets that are difficult to hit because they are fast or hard to spot.")
|
||||
addItem("Runes", "Icicle Rune", 2271, 6, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nParticularly creatures determined by the element fire are vulnerable against this ice-cold rune. Being hit by the magic stored in this rune, an ice arrow seems to pierce the heart of the struck victim. The damage done by this rune is quite impressive which makes this a quite popular rune among Tibian mages.")
|
||||
addItem("Runes", "Intense Healing Rune", 2265, 19, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nThis rune is commonly used by young adventurers who are not skilled enough to use the rune's stronger version. Also, since the rune's effectiveness is determined by the user's magic skill, it is still popular among experienced spell casters who use it to get effective healing magic at a cheap price.")
|
||||
addItem("Runes", "Magic Wall Rune", 2293, 23, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nThis spell causes all particles that are contained in the surrounding air to quickly gather and contract until a solid wall is formed that covers one full square metre. The wall that is formed that way is impenetrable to any missiles or to light and no creature or character can walk through it. However, the wall will only last for a couple of seconds.")
|
||||
addItem("Runes", "Poison Bomb Rune", 2286, 19, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nThis rune causes an area of 9 square metres to be contaminated with toxic gas that will poison anybody who is caught within it. Conceivable applications include the blocking of areas or the combat against fast-moving or invisible targets. Keep in mind, however, that there are a number of creatures that are immune to poison.")
|
||||
addItem("Runes", "Poison Wall Rune", 2289, 10, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nWhen this rune is used a wall of concentrated toxic fumes is created which inflicts a moderate poison on all those who are foolish enough to enter it. The effect is usually impressive enough to discourage monsters from doing so, although few of the stronger ones will hesitate if there is nothing but a poison wall between them and their dinner.")
|
||||
addItem("Runes", "Soulfire Rune", 2308, 9, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nSoulfire is an immensely evil spell as it directly targets a creature's very life essence. When the rune is used on a victim, its soul is temporarily moved out of its body, casting it down into the blazing fires of hell itself! Note that the experience and the mental strength of the caster influence the damage that is caused.")
|
||||
addItem("Runes", "Stone Shower Rune", 2288, 9, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nParticularly creatures with an affection to energy will suffer greatly from this rune filled with powerful earth damage. As the name already says, a shower of stones drums on the opponents of the rune user in an area up to 37 squares.")
|
||||
addItem("Runes", "Sudden Death Rune", 2268, 28, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nNearly no other spell can compare to Sudden Death when it comes to sheer damage. For this reason it is immensely popular despite the fact that only a single target is affected. However, since the damage caused by the rune is of deadly nature, it is less useful against most undead creatures.")
|
||||
addItem("Runes", "Thunderstorm Rune", 2315, 9, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nFlashes filled with dangerous energy hit the rune user's opponent when this rune is being used. It is especially effective against ice dominated creatures. Covering up an area up to 37 squares, this rune is particularly useful when you meet a whole mob of opponents.")
|
||||
addItem("Runes", "Ultimate Healing Rune", 2273, 35, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nThe coveted Ultimate Healing rune is an all-time favourite among all vocations. No other healing enchantments that are bound into runes can compare to its salutary effect.")
|
||||
addItem("Runes", "Wild Growth Rune", 2269, 32, false, 250, "- only usable by purchasing character\n- will be sent to your backpack\n- cannot be purchased by characters with protection zone block or battle sign\n- cannot be purchased if capacity is exceeded\n\nBy unleashing this spell, all seeds that are lying dormant in the surrounding quickly sprout and grow into full-sized plants, thus forming an impenetrable thicket. Unfortunately, plant life created this way is short-lived and will collapse within minutes, so the magically created obstacle will not last long.")
|
||||
|
||||
addCategory(nil, "Cosmetics", 21, CATEGORY_NONE)
|
||||
addCategory("Cosmetics", "Mounts", 14, CATEGORY_MOUNT)
|
||||
addItem("Mounts", "Arctic Unicorn", 1018, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Arctic Unicorn lives in a deep rivalry with its cousin the Blazing Unicorn. Even though they were born in completely different areas, they somehow share the same bloodline. The eternal battle between fire and ice continues. Who will win? Tangerine vs.crystal blue! The choice is yours!")
|
||||
addItem("Mounts", "Armoured War Horse", 426, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Armoured War Horse is a dangerous black beauty! When you see its threatening, blood-red eyes coming towards you, you'll know trouble is on its way. Protected by its heavy armour plates, the warhorse is the perfect partner for dangerous hunting sessions and excessive enemy slaughtering.")
|
||||
addItem("Mounts", "Batcat", 728, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nRumour has it that many years ago elder witches had gathered to hold a magical feast high up in the mountains. They had crossbred Batcat to easily conquer rocky canyons and deep valleys. Nobody knows what happened on their way up but only the mount has been seen ever since.")
|
||||
addItem("Mounts", "Battle Badger", 1247, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nBadgers have been a staple of the Tibian fauna for a long time, and finally some daring souls have braved the challenge to tame some exceptional specimens - and succeeded! While the common badger you can encounter during your travels might seem like a rather unassuming creature, the Battle Badger, the Ether Badger, and the Zaoan Badger are fierce and mighty beasts, which are at your beck and call.")
|
||||
addItem("Mounts", "Black Stag", 686, 660, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nTreat your character to a new travelling companion with a gentle nature and an impressive antler: The noble Black Stag will carry you through the deepest snow.")
|
||||
addItem("Mounts", "Blackpelt", 651, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Blackpelt is out searching for the best bamboo in Tibia. Its heavy armour allows it to visit even the most dangerous places. Treat it nicely with its favourite food from time to time and it will become a loyal partner.")
|
||||
addItem("Mounts", "Blazing Unicorn", 1017, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Blazing Unicorn lives in a deep rivalry with its cousin the Arctic Unicorn. Even though they were born in completely different areas, they somehow share the same bloodline. The eternal battle between fire and ice continues. Who will win? Crystal blue vs. tangerine! The choice is yours!")
|
||||
addItem("Mounts", "Bloodcurl", 869, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nYou are fascinated by insectoid creatures and can picture yourself riding one during combat or just for travelling? The Bloodcurl will carry you through the Tibian wilderness with ease.")
|
||||
addItem("Mounts", "Bog Tyrant", 1743, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThis monstrous creature lords over swamps, its body covered in toxic moss and grime.")
|
||||
addItem("Mounts", "Bogwurm", 1447, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nBurrowing from the depths of forgotten tunnels, this creature thrives in the shadows. The Bogwurm is as tough as steel and as fast as fear.")
|
||||
addItem("Mounts", "Boisterous Bull", 1672, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nA symbol of raw power and determination. The Boisterous Bull is as loud as it is loyal.")
|
||||
addItem("Mounts", "Boreal Owl", 1106, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nOwls have always been a symbol of mystery, magic and wisdom in Tibian myths and fairy tales. Having one of these enigmatic creatures of the night as a trustworthy companion provides you with a silent guide whose ever-watchful eyes will cut through the shadows, help you navigate the darkness and unravel great secrets.")
|
||||
addItem("Mounts", "Brass Speckled Koi", 1609, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nElegant, serene, and ever-gliding — these koi mounts are symbols of balance and beauty.")
|
||||
addItem("Mounts", "Bumblebee", 1778, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nBuzzing with energy, the Bumblebee zips through the skies leaving a trail of pollen and sparks.")
|
||||
addItem("Mounts", "Bunny Dray", 1180, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nYour lower back worsens with every trip you spend on the back of your mount and you are looking for a more comfortable alternative to travel through the lands? Say no more! The Bunny Dray comes with two top-performing hares that never get tired thanks to the brand new and highly innovative propulsion technology. Just keep some back-up carrots in your pocket and you will be fine!")
|
||||
addItem("Mounts", "Caped Snowman", 1169, 900, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nWhen the nights are getting longer and freezing wind brings driving snow into the land, snowmen rise and shine on every corner. Lately, a peaceful, arcane creature has found shelter in one of them and used its magical power to call the Caped Snowman into being. Wrap yourself up well and warmly and jump on the back of your new frosty companion.")
|
||||
addItem("Mounts", "Cave Tarantula", 1026, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIt is said that the Cave Tarantula was born long before Banor walked the earth of Tibia. While its parents died in the war against the cruel hordes sent by Brog and Zathroth, their child survived by hiding in skulls of burned enemies. It never left its hiding spot and as it grew older, the skulls merged into its body. Now, it is fully-grown and thirsts for revenge.")
|
||||
addItem("Mounts", "Cerberus Champion", 1209, 1250, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nA fierce and grim guardian of the underworld has risen to fight side by side with the bravest warriors in order to send evil creatures into the realm of the dead. The three headed Cerberus Champion is constantly baying for blood and using its sharp fangs it easily rips apart even the strongest armour and shield.")
|
||||
addItem("Mounts", "Cinderhoof", 851, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIf you are more of an imp than an angel, you may prefer riding out on a Cinderhoof to scare fellow Tibians on their festive strolls. Its devilish mask, claw-like hands and sharp hooves makes it the perfect companion for any daring adventurer who likes to stand out.")
|
||||
-- addItem("Mounts", "Cinnamon Ibex", 1528, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nSurefooted and resilient, these ibexes were raised among mountain peaks. Their floral names are a nod to the patches of wildflowers found high above the clouds.")
|
||||
-- addItem("Mounts", "Cony Cart", 1181, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nYour lower back worsens with every trip you spend on the back of your mount and you are looking for a more comfortable alternative to travel through the lands? Say no more! The Cony Cart comes with two top-performing hares that never get tired thanks to the brand new and highly innovative propulsion technology. Just keep some back-up carrots in your pocket and you will be fine!")
|
||||
-- addItem("Mounts", "Copper Fly", 671, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIf you are more interested in the achievements of science, you may enjoy a ride on the Copper Fly, one of the new insect-like flying machines. Even if you do not move around, the wings of these unusual vehicles are always in motion.")
|
||||
-- addItem("Mounts", "Coral Rhea", 1325, 500, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nDespite their inability to fly, these strong-legged birds are lightning-fast and utterly reliable when crossing large distances over dry terrain.")
|
||||
-- addItem("Mounts", "Coralripper", 735, 570, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIf the Coralripper moves its fins, it generates enough air pressure that it can even float over land. Its numerous eyes allow it to quickly detect dangers even in confusing situations and eliminate them with one powerful bite. If you watch your fingers, you are going to be good friends.")
|
||||
-- addItem("Mounts", "Corpsefire Skull", 1687, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nForged from cursed bones and shadowed flame, this skull mount is only tamed by death itself.")
|
||||
-- addItem("Mounts", "Cranium Spider", 1025, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIt is said that the Cranium Spider was born long before Banor walked the earth of Tibia. While its parents died in the war against the cruel hordes sent by Brog and Zathroth, their child survived by hiding in skulls of burned enemies. It never left its hiding spot and as it grew older, the skulls merged into its body. Now, it is fully-grown and thirsts for revenge.")
|
||||
-- addItem("Mounts", "Crimson Fang", 1744, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nBlood-red and battle-scarred, this predator obeys no one except its chosen rider.")
|
||||
-- addItem("Mounts", "Crimson Ray", 521, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nHave you ever dreamed of gliding through the air on the back of a winged creature? With its deep red wings, the majestic Crimson Ray is a worthy mount for courageous heroes. Feel like a king on its back as you ride into your next adventure.")
|
||||
-- addItem("Mounts", "Cunning Hyaena", 1334, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThese rugged animals are fearsome in packs and cunning when left alone. Riding one gives you not only speed but a fearsome reputation.")
|
||||
-- addItem("Mounts", "Dandelion", 1441, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThese graceful mounts are named after the vibrant flowers they resemble. They are calm, enduring, and add a blooming touch to your journey.")
|
||||
-- addItem("Mounts", "Darkfire Devourer", 1677, 1300, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nFrom the deepest rift beneath hell, the Darkfire Devourer hungers for conquest. Only the strongest may ride it.")
|
||||
-- addItem("Mounts", "Dawn Strayer", 1286, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nSome spirits are born from pure elements. The Dawn Strayer is a beacon of hope and a bringer of light. Ride it to greet the new day and strike fear into the hearts of nocturnal foes.")
|
||||
-- addItem("Mounts", "Dawnbringer Pegasus", 1727, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nA majestic celestial horse born of morning light, it shines with hope.")
|
||||
-- addItem("Mounts", "Death Crawler", 624, 600, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Death Crawler is a scorpion that has surpassed the natural boundaries of its own kind. Way bigger, stronger and faster than ordinary scorpions, it makes a perfect companion for fearless heroes and explorers. Just be careful of his poisonous sting when you mount it.")
|
||||
-- addItem("Mounts", "Desert King", 572, 450, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIts roaring is piercing marrow and bone and can be heard over ten miles away. The Desert King is the undisputed ruler of its territory and no one messes with this animal. Show no fear and prove yourself worthy of its trust and you will get yourself a valuable companion for your adventures.")
|
||||
-- addItem("Mounts", "Doom Skull", 1685, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nForged from cursed bones and shadowed flame, this skull mount is only tamed by death itself.")
|
||||
-- addItem("Mounts", "Doombringer", 644, 780, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nOnce captured and held captive by a mad hunter, the Doombringer is the result of sick experiments. Fed only with demon dust and concentrated demonic blood it had to endure a dreadful transformation. The demonic blood that is now running through its veins, however, provides it with incredible strength and endurance.")
|
||||
-- addItem("Mounts", "Dreadhare", 906, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nDo you like fluffy bunnies but think they are too small? Do you admire the majesty of stags and their antlers but are afraid of their untameable wilderness? Do not worry, the mystic creature Dreadhare consolidates the best qualities of both animals. Hop on its backs and enjoy the ride.")
|
||||
-- addItem("Mounts", "Dusk Pryer", 1285, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nSome spirits are born from pure elements. The Dusk Pryer embodies the essence of twilight, ever seeking knowledge hidden in the transition between light and darkness.")
|
||||
-- addItem("Mounts", "Ebony Tiger", 1091, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIt is said that in ancient times, the sabre-tooth tiger was already used as a mount by elder warriors of Svargrond. As seafaring began to expand, this noble big cat was also transported to other regions in Tibia. Influenced by the new environment and climatic changes, the fur of the Ebony Tiger has developed its extraordinary colouring over several generations.")
|
||||
-- addItem("Mounts", "Ember Saurian", 960, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThousands of years ago, its ancestors ruled the world. Only recently, it found its way into Tibia. The Ember Saurian has been spotted in a sea of flames and fire deep down in the depths of Kazordoon.")
|
||||
-- addItem("Mounts", "Emerald Raven", 1453, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nGraceful and mysterious, ravens have long been symbols of magic. The Emerald Raven’s plumage glows faintly, enchanted by forest spirits.")
|
||||
-- addItem("Mounts", "Emerald Sphinx", 951, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nRide an Emerald Sphinx on your way through ancient chambers and tombs and have a loyal friend by your side while fighting countless mummies and other creatures.")
|
||||
-- addItem("Mounts", "Emerald Waccoon", 693, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nWaccoons are cuddly creatures that love nothing more than to be petted and snuggled! Share a hug, ruffle the fur of the Emerald Waccoon and scratch it behind its ears to make it happy.")
|
||||
-- addItem("Mounts", "Emperor Deer", 687, 660, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nTreat your character to a new travelling companion with a gentle nature and an impressive antler: The noble Emperor Deer will carry you through the deepest snow.")
|
||||
-- addItem("Mounts", "Ether Badger", 1248, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nBadgers have been a staple of the Tibian fauna for a long time, and finally some daring souls have braved the challenge to tame some exceptional specimens - and succeeded! While the common badger you can encounter during your travels might seem like a rather unassuming creature, the Battle Badger, the Ether Badger, and the Zaoan Badger are fierce and mighty beasts, which are at your beck and call.")
|
||||
-- addItem("Mounts", "Eventide Nandu", 1326, 500, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nDespite their inability to fly, these strong-legged birds are lightning-fast and utterly reliable when crossing large distances over dry terrain.")
|
||||
-- addItem("Mounts", "Feral Tiger", 1092, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nAs seafaring began to expand, this noble big cat was also transported to other regions in Tibia. Influenced by the new environment and climatic changes, the fur of the Feral Tiger has developed its extraordinary colouring over several generations.")
|
||||
-- addItem("Mounts", "Festive Mammoth", 1381, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nNot all mammoths are grim and gruff. This one's festive nature and cheerful demeanor will lighten even the darkest dungeon.")
|
||||
-- addItem("Mounts", "Festive Snowman", 1167, 900, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nWhen the nights are getting longer and freezing wind brings driving snow into the land, snowmen rise and shine on every corner. Lately, a peaceful, arcane creature has found shelter in one of them and used its magical power to call the Festive Snowman into being. Wrap yourself up well and warmly and jump on the back of your new frosty companion.")
|
||||
-- addItem("Mounts", "Flamesteed", 626, 900, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nOnce a majestic and proud warhorse, the Flamesteed has fallen in a horrible battle many years ago. Driven by agony and pain, its spirit once again took possession of its rotten corpse to avenge its death. Stronger than ever, it seeks a master to join the battlefield, aiming for nothing but death and destruction.")
|
||||
-- addItem("Mounts", "Flitterkatzen", 726, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nRumour has it that many years ago elder witches had gathered to hold a magical feast high up in the mountains. They had crossbred Flitterkatzen to easily conquer rocky canyons and deep valleys. Nobody knows what happened on their way up but only the mount has been seen ever since.")
|
||||
-- addItem("Mounts", "Floating Augur", 1266, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThese creatures are Floating Savants whose mind has been warped and bent to focus their extraordinary mental capabilities on one single goal: to do their master's bidding. Instead of being filled with an endless pursuit of knowledge, their live is now one of continuous thralldom and serfhood. The Floating Sage, the Floating Scholar and the Floating Augur are at your disposal.")
|
||||
-- addItem("Mounts", "Floating Kashmir", 690, 900, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Floating Kashmir is the perfect mount for those who are too busy to take care of an animal mount or simply like to travel on a beautiful, magic hand-woven carpet.")
|
||||
-- addItem("Mounts", "Floating Sage", 1264, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThese creatures are Floating Savants whose mind has been warped and bent to focus their extraordinary mental capabilities on one single goal: to do their master's bidding. Instead of being filled with an endless pursuit of knowledge, their live is now one of continuous thralldom and serfhood. The Floating Sage, the Floating Scholar and the Floating Augur are at your disposal.")
|
||||
-- addItem("Mounts", "Floating Scholar", 1265, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThese creatures are Floating Savants whose mind has been warped and bent to focus their extraordinary mental capabilities on one single goal: to do their master's bidding. Instead of being filled with an endless pursuit of knowledge, their live is now one of continuous thralldom and serfhood. The Floating Sage, the Floating Scholar and the Floating Augur are at your disposal.")
|
||||
-- addItem("Mounts", "Flying Divan", 688, 900, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Flying Divan is the perfect mount for those who are too busy to take care of an animal mount or simply like to travel on a beautiful, magic hand-woven carpet.")
|
||||
-- addItem("Mounts", "Foxmouse", 1632, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nAn unusual crossbreed of cunning and curiosity, this mount’s playful energy is matched by its speed.")
|
||||
-- addItem("Mounts", "Frostbringer", 1615, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThis mount was born of the first winter storm and embodies the fury and beauty of snow.")
|
||||
-- addItem("Mounts", "Frostflare", 850, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIf you are more of an imp than an angel, you may prefer riding out on a Frostflare to scare fellow Tibians on their festive strolls. Its devilish mask, claw-like hands and sharp hooves makes it the perfect companion for any daring adventurer who likes to stand out.")
|
||||
-- addItem("Mounts", "Glacier Vagabond", 674, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nWith its thick, shaggy hair, the Glacier Vagabond will keep you warm even in the chilly climate of the Ice Islands. Due to its calm and peaceful nature, it is not letting itself getting worked up easily.")
|
||||
-- addItem("Mounts", "Glacier Wyrm", 1742, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nCold-blooded and ancient, the Glacier Wyrm coils through frostbitten mountains in silence.")
|
||||
-- addItem("Mounts", "Gloom Widow", 1027, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIt is said that the Gloom Widow was born long before Banor walked the earth of Tibia. While its parents died in the war against the cruel hordes sent by Brog and Zathroth, their child survived by hiding in skulls of burned enemies. It never left its hiding spot and as it grew older, the skulls merged into its body. Now, it is fully-grown and thirsts for revenge.")
|
||||
-- addItem("Mounts", "Gloomwurm", 1448, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nBurrowing from the depths of forgotten tunnels, this creature thrives in the shadows. The Gloomwurm is as tough as steel and as fast as fear.")
|
||||
-- addItem("Mounts", "Gold Sphinx", 950, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nRide a Gold Sphinx on your way through ancient chambers and tombs and have a loyal friend by your side while fighting countless mummies and other creatures.")
|
||||
-- addItem("Mounts", "Golden Dragonfly", 669, 600, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIf you are more interested in the achievements of science, you may enjoy a ride on the Golden Dragonfly, one of the new insect-like flying machines. Even if you do not move around, the wings of these unusual vehicles are always in motion.")
|
||||
-- addItem("Mounts", "Gorgon Hydra", 1724, 1000, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nFew dare gaze into the many eyes of the Gorgon Hydra. This fearsome creature obeys no one — except you.")
|
||||
-- addItem("Mounts", "Gorongra", 738, 720, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nGet yourself a mighty travelling companion with broad shoulders and a gentle heart. Gorongra is a physically imposing creature that is much more peaceful than its relatives, Tiquanda's wild kongras, and will carry you safely wherever you ask it to go.")
|
||||
-- addItem("Mounts", "Hailstorm Fury", 648, 780, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nOnce captured and held captive by a mad hunter, the Hailstorm Fury is the result of sick experiments. Fed only with demon dust and concentrated demonic blood it had to endure a dreadful transformation. The demonic blood that is now running through its veins, however, provides it with incredible strength and endurance.")
|
||||
-- addItem("Mounts", "Highland Yak", 673, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nWith its thick, shaggy hair, the Highland Yak will keep you warm even in the chilly climate of the Ice Islands. Due to its calm and peaceful nature, it is not letting itself getting worked up easily.")
|
||||
-- addItem("Mounts", "Holiday Mammoth", 1380, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nNot all mammoths are grim and gruff. This one's festive nature and cheerful demeanor will lighten even the darkest dungeon.")
|
||||
-- addItem("Mounts", "Hyacinth", 1439, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThese graceful mounts are named after the vibrant flowers they resemble. They are calm, enduring, and add a blooming touch to your journey.")
|
||||
-- addItem("Mounts", "Icebreacher", 1617, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThis mount was born of the first winter storm and embodies the fury and beauty of snow.")
|
||||
-- addItem("Mounts", "Ink Spotted Koi", 1610, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nElegant, serene, and ever-gliding — these koi mounts are symbols of balance and beauty.")
|
||||
-- addItem("Mounts", "Ivory Fang", 901, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIncredible strength and smartness, an irrepressible will to survive, passionately hunting in groups. If these attributes apply to your character, we have found the perfect partner for you. Have a proper look at Ivory Fang, which stands loyally by its master's side in every situation. It is time to become the leader of the wolf pack!")
|
||||
-- addItem("Mounts", "Jackalope", 905, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nDo you like fluffy bunnies but think they are too small? Do you admire the majesty of stags and their antlers but are afraid of their untameable wilderness? Do not worry, the mystic creature Jackalope consolidates the best qualities of both animals. Hop on its backs and enjoy the ride.")
|
||||
-- addItem("Mounts", "Jade Lion", 627, 450, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIts roaring is piercing marrow and bone and can be heard over ten miles away. The Jade Lion is the undisputed ruler of its territory and no one messes with this animal. Show no fear and prove yourself worthy of its trust and you will get yourself a valuable companion for your adventures.")
|
||||
-- addItem("Mounts", "Jade Pincer", 628, 600, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Jade Pincer is a scorpion that has surpassed the natural boundaries of its own kind. Way bigger, stronger and faster than ordinary scorpions, it makes a perfect companion for fearless heroes and explorers. Just be careful of his poisonous sting when you mount it.")
|
||||
-- addItem("Mounts", "Jade Shrine", 1492, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nImbued with ancient divine energy, this shrine floats effortlessly. The Jade Shrine blesses its rider with peace and clarity.")
|
||||
-- addItem("Mounts", "Jousting Eagle", 1208, 800, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nHigh above the clouds far away from dry land, the training of giant eagles takes place. Only the cream of the crop is able to survive in such harsh environment long enough to call themselves Jousting Eagles while the weaklings find themselves at the bottom of the sea. The tough ones become noble and graceful mounts that are well known for their agility and endurance.")
|
||||
-- addItem("Mounts", "Jousting Horse", 1579, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nBred for ceremony and flair, this horse carries itself with grace and rhythm. Perfect for heroes who crave the spotlight.")
|
||||
-- addItem("Mounts", "Jungle Saurian", 959, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThousands of years ago, its ancestors ruled the world. Only recently, it found its way into Tibia. The Jungle Saurian likes to hide in dense wood and overturned trees.")
|
||||
-- addItem("Mounts", "Jungle Tiger", 1093, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nAs seafaring began to expand, this noble big cat was also transported to other regions in Tibia. Influenced by the new environment and climatic changes, the fur of the Jungle Tiger has developed its extraordinary colouring over several generations.")
|
||||
-- addItem("Mounts", "Lagoon Saurian", 961, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThousands of years ago, its ancestors ruled the world. Only recently, it found its way into Tibia. The Lagoon Saurian feels most comfortable in torrential rivers and behind dangerous waterfalls.")
|
||||
-- addItem("Mounts", "Leafscuttler", 870, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nYou are fascinated by insectoid creatures and can picture yourself riding one during combat or just for travelling? The Leafscuttler will carry you through the Tibian wilderness with ease.")
|
||||
-- addItem("Mounts", "Magic Carpet", 689, 900, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Magic Carpet is the perfect mount for those who are too busy to take care of an animal mount or simply like to travel on a beautiful, magic hand-woven carpet.")
|
||||
-- addItem("Mounts", "Magma Skull", 1686, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nForged from cursed bones and shadowed flame, this skull mount is only tamed by death itself.")
|
||||
-- addItem("Mounts", "Marsh Toad", 1052, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Magic Carpet is the perfect mount for those who are too busy to take cFor centuries, humans and monsters have dumped their garbage in the swamps around Venore. The combination of old, rusty weapons, stale mana and broken runes have turned some of the swamp dwellers into gigantic frogs. Benefit from those mutations and make the Marsh Toad a faithful mount for your adventures even beyond the bounds of the swamp.")
|
||||
-- addItem("Mounts", "Merry Mammoth", 1379, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nNot all mammoths are grim and gruff. This one's festive nature and cheerful demeanor will lighten even the darkest dungeon.")
|
||||
-- addItem("Mounts", "Mint Ibex", 1527, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nSurefooted and resilient, these ibexes were raised among mountain peaks. Their floral names are a nod to the patches of wildflowers found high above the clouds.")
|
||||
-- addItem("Mounts", "Mould Shell", 887, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nYou are intrigued by tortoises and would love to throne on a tortoise shell when travelling the Tibian wilderness? The Mould Shell might become your new trustworthy companion then, which will transport you safely and even carry you during combat.")
|
||||
-- addItem("Mounts", "Mouldpincer", 868, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nYou are fascinated by insectoid creatures and can picture yourself riding one during combat or just for travelling? The Mouldpincer will carry you through the Tibian wilderness with ease.")
|
||||
-- addItem("Mounts", "Muffled Snowman", 1168, 900, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nWhen the nights are getting longer and freezing wind brings driving snow into the land, snowmen rise and shine on every corner. Lately, a peaceful, arcane creature has found shelter in one of them and used its magical power to call the Muffled Snowman into being. Wrap yourself up well and warmly and jump on the back of your new frosty companion.")
|
||||
-- addItem("Mounts", "Mystic Raven", 1454, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nGraceful and mysterious, ravens have long been symbols of magic. The Mystic Raven’s plumage glows faintly, enchanted by forest spirits.")
|
||||
-- addItem("Mounts", "Nethersteed", 629, 900, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nOnce a majestic and proud warhorse, the Nethersteed has fallen in a horrible battle many years ago. Driven by agony and pain, its spirit once again took possession of its rotten corpse to avenge its death. Stronger than ever, it seeks a master to join the battlefield, aiming for nothing but death and destruction.")
|
||||
-- addItem("Mounts", "Night Waccoon", 692, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nWaccoons are cuddly creatures that love nothing more than to be petted and snuggled! Share a hug, ruffle the fur of the Night Waccoon and scratch it behind its ears to make it happy.")
|
||||
-- addItem("Mounts", "Nightdweller", 849, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIf you are more of an imp than an angel, you may prefer riding out on a Nightdweller to scare fellow Tibians on their festive strolls. Its devilish mask, claw-like hands and sharp hooves makes it the perfect companion for any daring adventurer who likes to stand out.")
|
||||
-- addItem("Mounts", "Nightmarish Crocovile", 1185, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nTo the keen observer, the crocovile is clearly a relative of the crocodile, albeit their look suggests an even more aggressive nature. While it is true that the power of its massive and muscular body can not only crush enemies dead but also break through any gate like a battering ram, a crocovile is, above all, a steadfast companion showing unwavering loyalty to its owner.")
|
||||
-- addItem("Mounts", "Nightstinger", 762, 780, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Nightstinger has external characteristics of different breeds. It is assumed that his brain is also composed of many different species, which makes it completely unpredictable. Only few have managed to approach this creature unharmed and only the best could tame it.")
|
||||
-- addItem("Mounts", "Noctungra", 739, 720, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nGet yourself a mighty travelling companion with broad shoulders and a gentle heart. Noctungra is a physically imposing creature that is much more peaceful than its relatives, Tiquanda's wild kongras, and will carry you safely wherever you ask it to go.")
|
||||
-- addItem("Mounts", "Obsidian Shrine", 1493, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nImbued with ancient divine energy, this shrine floats effortlessly. The Obsidian Shrine blesses its rider with peace and clarity.")
|
||||
-- addItem("Mounts", "Obstinate Ox", 1674, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nA symbol of raw power and determination. The Obstinate Ox is as loud as it is loyal.")
|
||||
-- addItem("Mounts", "Parade Horse", 1578, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nBred for ceremony and flair, this horse carries itself with grace and rhythm. Perfect for heroes who crave the spotlight.")
|
||||
-- addItem("Mounts", "Peony", 1440, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThese graceful mounts are named after the vibrant flowers they resemble. They are calm, enduring, and add a blooming touch to your journey.")
|
||||
-- addItem("Mounts", "Plumfish", 736, 570, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIf the Plumfish moves its fins, it generates enough air pressure that it can even float over land. Its numerous eyes allow it to quickly detect dangers even in confusing situations and eliminate them with one powerful bite. If you watch your fingers, you are going to be good friends.")
|
||||
-- addItem("Mounts", "Poisonbane", 650, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Poisonbane is out searching for the best bamboo in Tibia. Its heavy armour allows it to visit even the most dangerous places. Treat it nicely with its favourite food from time to time and it will become a loyal partner.")
|
||||
-- addItem("Mounts", "Poppy Ibex", 1526, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nSurefooted and resilient, these ibexes were raised among mountain peaks. Their floral names are a nod to the patches of wildflowers found high above the clouds.")
|
||||
-- addItem("Mounts", "Prismatic Unicorn", 1019, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nLegend has it that a mare and a stallion once reached the end of a rainbow and decided to stay there. Influenced by the mystical power of the rainbow, the mare gave birth to an exceptional foal: Not only the big, strong horn on its forehead but the unusual colouring of its hair makes the Prismatic Unicorn a unique mount in every respect.")
|
||||
-- addItem("Mounts", "Rabbit Rickshaw", 1179, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nYour lower back worsens with every trip you spend on the back of your mount and you are looking for a more comfortable alternative to travel through the lands? Say no more! The Rabbit Rickshaw comes with two top-performing hares that never get tired thanks to the brand new and highly innovative propulsion technology. Just keep some back-up carrots in your pocket and you will be fine!")
|
||||
-- addItem("Mounts", "Radiant Raven", 1455, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nGraceful and mysterious, ravens have long been symbols of magic. The Radiant Raven’s plumage glows faintly, enchanted by forest spirits.")
|
||||
-- addItem("Mounts", "Razorcreep", 763, 780, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Razorcreep has external characteristics of different breeds. It is assumed that his brain is also composed of many different species, which makes it completely unpredictable. Only few have managed to approach this creature unharmed and only the best could tame it.")
|
||||
-- addItem("Mounts", "Reed Lurker", 888, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nYou are intrigued by tortoises and would love to throne on a tortoise shell when travelling the Tibian wilderness? The Reed Lurker might become your new trustworthy companion then, which will transport you safely and even carry you during combat.")
|
||||
-- addItem("Mounts", "Rift Watcher", 1391, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nSilent, patient and always on the lookout for rifts in reality, these beings are drawn to those who walk between worlds. The Rift Watcher will not falter.")
|
||||
-- addItem("Mounts", "Ringtail Waccoon", 691, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nWaccoons are cuddly creatures that love nothing more than to be petted and snuggled! Share a hug, ruffle the fur of the Ringtail Waccoon and scratch it behind its ears to make it happy.")
|
||||
-- addItem("Mounts", "River Crocovile", 1183, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nTo the keen observer, the crocovile is clearly a relative of the crocodile, albeit their look suggests an even more aggressive nature. While it is true that the power of its massive and muscular body can not only crush enemies dead but also break through any gate like a battering ram, a crocovile is, above all, a steadfast companion showing unwavering loyalty to its owner.")
|
||||
-- addItem("Mounts", "Rune Watcher", 1390, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nSilent, patient and always on the lookout for rifts in reality, these beings are drawn to those who walk between worlds. The Rune Watcher will not falter.")
|
||||
-- addItem("Mounts", "Rustwurm", 1446, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nBurrowing from the depths of forgotten tunnels, this creature thrives in the shadows. The Rustwurm is as tough as steel and as fast as fear.")
|
||||
-- addItem("Mounts", "Sanguine Frog", 1053, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nFor centuries, humans and monsters have dumped their garbage in the swamps around Venore. The combination of old, rusty weapons, stale mana and broken runes have turned some of the swamp dwellers into gigantic frogs. Benefit from those mutations and make the Sanguine Frog a faithful mount for your adventures even beyond the bounds of the swamp.")
|
||||
-- addItem("Mounts", "Savanna Ostrich", 1324, 500, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nDespite their inability to fly, these strong-legged birds are lightning-fast and utterly reliable when crossing large distances over dry terrain.")
|
||||
-- addItem("Mounts", "Scruffy Hyaena", 1335, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThese rugged animals are fearsome in packs and cunning when left alone. Riding one gives you not only speed but a fearsome reputation.")
|
||||
-- addItem("Mounts", "Sea Devil", 734, 570, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIf the Sea Devil moves its fins, it generates enough air pressure that it can even float over land. Its numerous eyes allow it to quickly detect dangers even in confusing situations and eliminate them with one powerful bite. If you watch your fingers, you are going to be good friends.")
|
||||
-- addItem("Mounts", "Shadow Claw", 902, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIncredible strength and smartness, an irrepressible will to survive, passionately hunting in groups. If these attributes apply to your character, we have found the perfect partner for you. Have a proper look at Shadow Claw, which stands loyally by its master's side in every situation. It is time to become the leader of the wolf pack!")
|
||||
-- addItem("Mounts", "Shadow Draptor", 427, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nA wild, ancient creature, which had been hiding in the depths of the shadows for a very long time, has been spotted in Tibia again! The almighty Shadow Draptor has returned and only the bravest Tibians can control such a beast!")
|
||||
-- addItem("Mounts", "Shadow Hart", 685, 660, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nTreat your character to a new travelling companion with a gentle nature and an impressive antler: The noble Shadow Hart will carry you through the deepest snow.")
|
||||
-- addItem("Mounts", "Shadow Sphinx", 952, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nRide a Shadow Sphinx on your way through ancient chambers and tombs and have a loyal friend by your side while fighting countless mummies and other creatures.")
|
||||
-- addItem("Mounts", "Siegebreaker", 649, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Siegebreaker is out searching for the best bamboo in Tibia. Its heavy armour allows it to visit even the most dangerous places. Treat it nicely with its favourite food from time to time and it will become a loyal partner.")
|
||||
-- addItem("Mounts", "Silverneck", 740, 720, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nGet yourself a mighty travelling companion with broad shoulders and a gentle heart. Silverneck is a physically imposing creature that is much more peaceful than its relatives, Tiquanda's wild kongras, and will carry you safely wherever you ask it to go.")
|
||||
-- addItem("Mounts", "Skybreaker Pegasus", 1729, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nA mount that rides thunderclouds and breaks the heavens. The storm is its domain.")
|
||||
-- addItem("Mounts", "Slagsnare", 761, 780, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Slagsnare has external characteristics of different breeds. It is assumed that his brain is also composed of many different species, which makes it completely unpredictable. Only few have managed to approach this creature unharmed and only the best could tame it.")
|
||||
-- addItem("Mounts", "Snow Pelt", 903, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIncredible strength and smartness, an irrepressible will to survive, passionately hunting in groups. If these attributes apply to your character, we have found the perfect partner for you. Have a proper look at Snow Pelt, which stands loyally by its master's side in every situation. It is time to become the leader of the wolf pack!")
|
||||
-- addItem("Mounts", "Snow Strider", 1284, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nSome spirits are born from pure elements. The Snow Strider came into being through a mix of pristine ice and eternal northern wind. With sharp hooves and sturdy legs, it glides effortlessly over snow and ice.")
|
||||
-- addItem("Mounts", "Snowy Owl", 1105, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nOwls have always been a symbol of mystery, magic and wisdom in Tibian myths and fairy tales. Having one of these enigmatic creatures of the night as a trustworthy companion provides you with a silent guide whose ever-watchful eyes will cut through the shadows, help you navigate the darkness and unravel great secrets.")
|
||||
-- addItem("Mounts", "Spirit of Purity", 1682, 1000, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nA radiant entity of light and truth, this mount protects those with noble hearts.")
|
||||
-- addItem("Mounts", "Steel Bee", 670, 600, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIf you are more interested in the achievements of science, you may enjoy a ride on the Steel Bee, one of the new insect-like flying machines. Even if you do not move around, the wings of these unusual vehicles are always in motion.")
|
||||
-- addItem("Mounts", "Steelbeak", 522, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nForged by only the highest skilled blacksmiths in the depths of Kazordoon's furnaces, a wild animal made out of the finest steel arose from glowing embers and blazing heat. Protected by its impenetrable armour, the Steelbeak is ready to accompany its master on every battleground.")
|
||||
-- addItem("Mounts", "Surly Steer", 1673, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nA symbol of raw power and determination. The Surly Steer is as loud as it is loyal.")
|
||||
-- addItem("Mounts", "Swamp Crocovile", 1184, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nTo the keen observer, the crocovile is clearly a relative of the crocodile, albeit their look suggests an even more aggressive nature. While it is true that the power of its massive and muscular body can not only crush enemies dead but also break through any gate like a battering ram, a crocovile is, above all, a steadfast companion showing unwavering loyalty to its owner.")
|
||||
-- addItem("Mounts", "Swamp Snapper", 886, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nYou are intrigued by tortoises and would love to throne on a tortoise shell when travelling the Tibian wilderness? The Swamp Snapper might become your new trustworthy companion then, which will transport you safely and even carry you during combat.")
|
||||
-- addItem("Mounts", "Tangerine Flecked Koi", 1608, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nElegant, serene, and ever-gliding — these koi mounts are symbols of balance and beauty.")
|
||||
-- addItem("Mounts", "Tawny Owl", 1104, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nHaving one of these enigmatic creatures of the night as a trustworthy companion provides you with a silent guide whose ever-watchful eyes will cut through the shadows, help you navigate the darkness and unravel great secrets.")
|
||||
-- addItem("Mounts", "Tempest", 630, 900, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nOnce a majestic and proud warhorse, the Tempest has fallen in a horrible battle many years ago. Driven by agony and pain, its spirit once again took possession of its rotten corpse to avenge its death. Stronger than ever, it seeks a master to join the battlefield, aiming for nothing but death and destruction.")
|
||||
-- addItem("Mounts", "Tombstinger", 546, 600, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThe Tombstinger is a scorpion that has surpassed the natural boundaries of its own kind. Way bigger, stronger and faster than ordinary scorpions, it makes a perfect companion for fearless heroes and explorers. Just be careful of his poisonous sting when you mount it.")
|
||||
-- addItem("Mounts", "Topaz Shrine", 1491, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nImbued with ancient divine energy, this shrine floats effortlessly. The Topaz Shrine blesses its rider with peace and clarity.")
|
||||
-- addItem("Mounts", "Tourney Horse", 1580, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nBred for ceremony and flair, this horse carries itself with grace and rhythm. Perfect for heroes who crave the spotlight.")
|
||||
-- addItem("Mounts", "Toxic Toad", 1054, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nFor centuries, humans and monsters have dumped their garbage in the swamps around Venore. The combination of old, rusty weapons, stale mana and broken runes have turned some of the swamp dwellers into gigantic frogs. Benefit from those mutations and make the Toxic Toad a faithful mount for your adventures even beyond the bounds of the swamp.")
|
||||
-- addItem("Mounts", "Tundra Rambler", 672, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nWith its thick, shaggy hair, the Tundra Rambler will keep you warm even in the chilly climate of the Ice Islands. Due to its calm and peaceful nature, it is not letting itself getting worked up easily.")
|
||||
-- addItem("Mounts", "Venompaw", 727, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nRumour has it that many years ago elder witches had gathered to hold a magical feast high up in the mountains. They had crossbred Venompaw to easily conquer rocky canyons and deep valleys. Nobody knows what happened on their way up but only the mount has been seen ever since.")
|
||||
-- addItem("Mounts", "Void Watcher", 1389, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nSilent, patient and always on the lookout for rifts in reality, these beings are drawn to those who walk between worlds. The Void Watcher will not falter.")
|
||||
-- addItem("Mounts", "Voracious Hyaena", 1333, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThese rugged animals are fearsome in packs and cunning when left alone. Riding one gives you not only speed but a fearsome reputation.")
|
||||
-- addItem("Mounts", "Winter King", 631, 450, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nIts roaring is piercing marrow and bone and can be heard over ten miles away. The Winter King is the undisputed ruler of its territory and no one messes with this animal. Show no fear and prove yourself worthy of its trust and you will get yourself a valuable companion for your adventures.")
|
||||
-- addItem("Mounts", "Winterstride", 1616, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThis mount was born of the first winter storm and embodies the fury and beauty of snow.")
|
||||
-- addItem("Mounts", "Wolpertinger", 907, 870, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nOnce captured and held captive by a mad hunter, the Woodland Prince is the result of sick experiments. Fed only with demon dust and concentrated demonic blood it had to endure a dreadful transformation. The demonic blood that is now running through its veins, however, provides it with incredible strength and endurance.")
|
||||
-- addItem("Mounts", "Woodland Prince", 647, 780, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nOnce captured and held captive by a mad hunter, the Woodland Prince is the result of sick experiments. Fed only with demon dust and concentrated demonic blood it had to endure a dreadful transformation. The demonic blood that is now running through its veins, however, provides it with incredible strength and endurance.")
|
||||
-- addItem("Mounts", "Wrathfire Pegasus", 1728, 750, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nThis pegasus was forged in wrath and fire. Ride it to war, and leave a trail of flame.")
|
||||
-- addItem("Mounts", "Zaoan Badger", 1249, 690, false, 1, "- only usable by purchasing character\n- provides character with a speed boost\n\nBadgers have been a staple of the Tibian fauna for a long time, and finally some daring souls have braved the challenge to tame some exceptional specimens - and succeeded! While the common badger you can encounter during your travels might seem like a rather unassuming creature, the Battle Badger, the Ether Badger, and the Zaoan Badger are fierce and mighty beasts, which are at your beck and call.")
|
||||
|
||||
addCategory("Cosmetics", "Outfits", 15, CATEGORY_OUTFIT)
|
||||
addItem("Outfits", "Elementalist", 432, 450, false, 1, "The warm and cosy cloak of the Winter Warden outfit will keep you warm in every situation. Best thing, it is not only comfortable but fashionable as well. You will be the envy of any snow queen or king, guaranteed!")
|
||||
addItem("Outfits", "Deepling", 463, 450, false, 1, "The Trailblazer is on a mission of enlightenment and carries the flame of wisdom near and far. The everlasting shine brightens the hearts and minds of all creatures its rays touch, bringing light even to the darkest corners of the world as a beacon of insight and knowledge.")
|
||||
addItem("Outfits", "Insectoid", 465, 450, false, 1, "Do you worship warm temperatures and are opposed to the thought of long and dark winter nights? Do you refuse to spend countless evenings in front of your chimney while ice-cold wind whistles through the cracks and niches of your house? It is time to stop freezing and to become an honourable Sun Priest! With this stylish outfit, you can finally show the world your unconditional dedication and commitment to the sun!")
|
||||
addItem("Outfits", "Entrepreneur", 472, 450, false, 1, "The mutated pumpkin is too weak for your mighty weapons? Time to show that evil vegetable how to scare the living daylight out of people! Put on a scary looking pumpkin on your head and spread terror and fear amongst the Tibian population.")
|
||||
|
||||
addCategory(nil, "Extras", 9, CATEGORY_NONE)
|
||||
addCategory("Extras", "Extra Services", 7, CATEGORY_EXTRAS)
|
||||
addItem("Extra Services", "Name Change", "Name_Change", 250, false, 1, "Tired of your current character name? Purchase a new one!\n\n\n- only usable by purchasing character\n- relog required after purchase to finalise the name change")
|
||||
addItem("Extra Services", "Sex Change", "Sex_Change", 120, false, 1, "Turns your female character into a male one - or vice versa.\n\n\n- only usable by purchasing character\n- activated at purchase\n- you will keep all outfits you have purchased or earned in quest")
|
||||
|
||||
addCategory("Extras", "Useful Things", 24, CATEGORY_EXTRAS)
|
||||
addItem("Useful Things", "Temple Teleport", "Temple_Teleport", 15, false, 1, "Teleports you instantly to your home temple.\n\n- only usable by purchasing character\n- use it to teleport you to your home temple\n- cannot be used while having a battle sign or a protection zone block")
|
||||
end
|
||||
|
||||
function addCategory(parent, title, iconId, categoryId, description)
|
||||
GAME_SHOP.categoriesId[title] = categoryId
|
||||
table.insert(GAME_SHOP.categories, {
|
||||
title = title,
|
||||
parent = parent,
|
||||
iconId = iconId,
|
||||
categoryId = categoryId,
|
||||
description = description
|
||||
})
|
||||
end
|
||||
|
||||
function addItem(parent, name, id, price, isSecondPrice, count, description)
|
||||
if not GAME_SHOP.offers[parent] then
|
||||
GAME_SHOP.offers[parent] = {}
|
||||
end
|
||||
|
||||
local serverId = id
|
||||
if type(id) == "number" and GAME_SHOP.categoriesId[parent] == CATEGORY_ITEM then
|
||||
id = ItemType(id):getClientId()
|
||||
end
|
||||
|
||||
table.insert(GAME_SHOP.offers[parent], {
|
||||
parent = parent,
|
||||
name = name,
|
||||
serverId = serverId,
|
||||
id = id,
|
||||
price = price,
|
||||
isSecondPrice = isSecondPrice,
|
||||
count = count,
|
||||
description = description,
|
||||
categoryId = GAME_SHOP.categoriesId[parent]
|
||||
})
|
||||
end
|
||||
|
||||
function gameShopPurchase(player, offer)
|
||||
local offers = GAME_SHOP.offers[offer.parent]
|
||||
if not offers then
|
||||
return errorMsg(player, "Something went wrong, try again or contact server admin [#1]!")
|
||||
end
|
||||
|
||||
for i = 1, #offers do
|
||||
if offers[i].name == offer.name and offers[i].price == offer.price and offers[i].count == offer.count then
|
||||
local points = 0
|
||||
local query = ""
|
||||
if offers[i].isSecondPrice then
|
||||
points = getSecondCurrency(player)
|
||||
query = "points_second"
|
||||
else
|
||||
points = getPoints(player)
|
||||
query = "points"
|
||||
end
|
||||
|
||||
if offers[i].price > points then
|
||||
return errorMsg(player, "You don't have enough points!")
|
||||
end
|
||||
|
||||
offer.serverId = offers[i].serverId
|
||||
local status = finalizePurchase(player, offer)
|
||||
if status then
|
||||
return errorMsg(player, status)
|
||||
end
|
||||
|
||||
local aid = player:getAccountId()
|
||||
local escapeName = db.escapeString(offers[i].name)
|
||||
local escapePrice = db.escapeString(-offers[i].price)
|
||||
local escapeIsSecondPrice = db.escapeString(offers[i].isSecondPrice and "1" or "0")
|
||||
local escapeCount = offers[i].count and db.escapeString(offers[i].count) or 0
|
||||
if GAME_SHOP.categoriesId[offer.parent] == CATEGORY_PREMIUM then
|
||||
escapeCount = 0
|
||||
end
|
||||
|
||||
db.query("UPDATE `znote_accounts` set `" .. query .. "` = `" .. query .. "` - " .. offers[i].price .. " WHERE `id` = " .. aid)
|
||||
db.asyncQuery("INSERT INTO `shop_history` VALUES (NULL, " .. aid .. ", " .. player:getGuid() .. ", NOW(), " .. escapeName .. ", " .. escapePrice .. ", " .. escapeIsSecondPrice .. ", " .. escapeCount .. ", NULL)")
|
||||
addEvent(gameShopUpdateHistory, 1000, player:getId())
|
||||
addEvent(gameShopUpdatePoints, 1000, player:getId())
|
||||
return infoMsg(player, "You've bought " .. offers[i].name .. "!", true)
|
||||
end
|
||||
end
|
||||
|
||||
return errorMsg(player, "Something went wrong, try again or contact server admin [#3]!")
|
||||
end
|
||||
|
||||
function finalizePurchase(player, offer)
|
||||
local categoryId = GAME_SHOP.categoriesId[offer.parent]
|
||||
if categoryId == CATEGORY_PREMIUM then
|
||||
return defaultPremiumCallback(player, offer)
|
||||
elseif categoryId == CATEGORY_ITEM then
|
||||
return defaultItemCallback(player, offer)
|
||||
elseif categoryId == CATEGORY_BLESSING then
|
||||
return defaultBlessingCallback(player, offer)
|
||||
elseif categoryId == CATEGORY_OUTFIT then
|
||||
return defaultOutfitCallback(player, offer)
|
||||
elseif categoryId == CATEGORY_MOUNT then
|
||||
return defaultMountCallback(player, offer)
|
||||
elseif categoryId == CATEGORY_EXTRAS then
|
||||
return defaultExtrasCallback(player, offer)
|
||||
end
|
||||
|
||||
return "Something went wrong, try again or contact server admin [#2]!"
|
||||
end
|
||||
|
||||
function defaultPremiumCallback(player, offer)
|
||||
player:addPremiumDays(offer.count)
|
||||
return false
|
||||
end
|
||||
|
||||
function defaultItemCallback(player, offer)
|
||||
local inPz = player:getTile():hasFlag(TILESTATE_PROTECTIONZONE)
|
||||
local inFight = player:isPzLocked() or player:getCondition(CONDITION_INFIGHT, CONDITIONID_DEFAULT)
|
||||
if not inPz or inFight then
|
||||
return "Cannot be used while having a battle sign or a protection zone block."
|
||||
end
|
||||
|
||||
local weight = ItemType(offer.serverId):getWeight(offer.count)
|
||||
if player:getFreeCapacity() < weight then
|
||||
return "This item is too heavy for you!"
|
||||
end
|
||||
|
||||
local item = player:getSlotItem(CONST_SLOT_BACKPACK)
|
||||
if not item then
|
||||
return "You don't have enough space in backpack."
|
||||
end
|
||||
|
||||
local slots = item:getEmptySlots(true)
|
||||
if slots <= 0 then
|
||||
return "You don't have enough space in backpack."
|
||||
end
|
||||
|
||||
if player:addItem(offer.serverId, offer.count, false) then
|
||||
return false
|
||||
end
|
||||
|
||||
return "Something went wrong, item couldn't be added."
|
||||
end
|
||||
|
||||
function defaultBlessingCallback(player, offer)
|
||||
if offer.count == -1 then
|
||||
for i = 1, 5 do
|
||||
if not player:hasBlessing(i) then
|
||||
for i = 1, 5 do
|
||||
player:addBlessing(i)
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return "You already have all blessings."
|
||||
elseif player:hasBlessing(offer.count) then
|
||||
return "You already have this blessing."
|
||||
end
|
||||
|
||||
player:addBlessing(offer.count)
|
||||
return false
|
||||
end
|
||||
|
||||
function defaultOutfitCallback(player, offer)
|
||||
if player:hasOutfit(offer.id, offer.count) then
|
||||
return "You already have this outfit."
|
||||
end
|
||||
|
||||
player:addOutfitAddon(offer.id, offer.count)
|
||||
return false
|
||||
end
|
||||
|
||||
function defaultMountCallback(player, offer)
|
||||
if player:hasMount(offer.id) then
|
||||
return "You already have this mount."
|
||||
end
|
||||
|
||||
if not player:addMount(offer.id) then
|
||||
return "Something went wrong, mount cannot be added."
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function defaultExtrasCallback(player, offer)
|
||||
if offer.name == "Name Change" then
|
||||
return defaultChangeNameCallback(player, offer)
|
||||
elseif offer.name == "Sex Change" then
|
||||
return defaultChangeSexCallback(player)
|
||||
elseif offer.name == "Temple Teleport" then
|
||||
return defaultTeleportCallback(player)
|
||||
end
|
||||
|
||||
return "Something went wrong, extra service couldn't be executed."
|
||||
end
|
||||
|
||||
function defaultChangeSexCallback(player)
|
||||
local inPz = player:getTile():hasFlag(TILESTATE_PROTECTIONZONE)
|
||||
local inFight = player:isPzLocked() or player:getCondition(CONDITION_INFIGHT, CONDITIONID_DEFAULT)
|
||||
if not inPz or inFight then
|
||||
return "Cannot be used while having a battle sign or a protection zone block."
|
||||
end
|
||||
|
||||
if not player:getGroup() then
|
||||
return "You can't do this."
|
||||
end
|
||||
|
||||
player:setSex(player:getSex() == PLAYERSEX_FEMALE and PLAYERSEX_MALE or PLAYERSEX_FEMALE)
|
||||
|
||||
local outfit = player:getOutfit()
|
||||
if player:getSex(player) == PLAYERSEX_MALE then
|
||||
outfit.lookType = 128
|
||||
else
|
||||
outfit.lookType = 136
|
||||
end
|
||||
|
||||
player:setOutfit(outfit)
|
||||
return false
|
||||
end
|
||||
|
||||
function defaultChangeNameCallback(player, offer)
|
||||
local inPz = player:getTile():hasFlag(TILESTATE_PROTECTIONZONE)
|
||||
local inFight = player:isPzLocked() or player:getCondition(CONDITION_INFIGHT, CONDITIONID_DEFAULT)
|
||||
if not inPz or inFight then
|
||||
return "Cannot be used while having a battle sign or a protection zone block."
|
||||
end
|
||||
|
||||
if not player:getGroup() then
|
||||
return "You can't do this."
|
||||
end
|
||||
|
||||
local characterName = offer.nick:trim()
|
||||
local v = getValid(characterName:lower(), false)
|
||||
if not validName(v) then
|
||||
return "You can't use this character name."
|
||||
end
|
||||
|
||||
if getPlayerDatabaseInfo(v) then
|
||||
return "Character name already taken."
|
||||
end
|
||||
|
||||
local lastName = player:getName()
|
||||
db.query("UPDATE players SET name = "..db.escapeString(characterName).." WHERE name = "..db.escapeString(lastName)..";")
|
||||
db.query("UPDATE player_deaths SET killed_by = "..db.escapeString(characterName)..", mostdamage_by = "..db.escapeString(characterName).." WHERE killed_by = "..db.escapeString(lastName).." OR mostdamage_by = "..db.escapeString(lastName)..";")
|
||||
db.query("UPDATE player_deaths_backup SET killed_by = "..db.escapeString(characterName)..", mostdamage_by = "..db.escapeString(characterName).." WHERE killed_by = "..db.escapeString(lastName).." OR mostdamage_by = "..db.escapeString(lastName)..";")
|
||||
db.query(string.format("INSERT INTO `change_name_history` (`player_id`, `last_name`, `current_name`, `changed_name_in`) VALUES (%d, %s, %s, %d)", player:getGuid(), db.escapeString(lastName), db.escapeString(characterName), os.time()))
|
||||
return false
|
||||
end
|
||||
|
||||
function defaultTeleportCallback(player, offer)
|
||||
local inFight = player:isPzLocked() or player:getCondition(CONDITION_INFIGHT, CONDITIONID_DEFAULT)
|
||||
if inFight then
|
||||
return "Cannot be used while having a battle sign or a protection zone block."
|
||||
end
|
||||
|
||||
player:teleportTo(player:getTown():getTemplePosition())
|
||||
player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function getValid(name, opt)
|
||||
local function tchelper(first, rest)
|
||||
return first:upper()..rest:lower()
|
||||
end
|
||||
|
||||
return opt and name:gsub("(%a)([%w_']*)", tchelper) or name:gsub("^%l", string.upper)
|
||||
end
|
||||
|
||||
function wordCount(str)
|
||||
local count = 0
|
||||
for word in string.gmatch(str, "%a+") do
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
function validName(name)
|
||||
if not name then
|
||||
return false
|
||||
end
|
||||
|
||||
if name:len() < minChars then
|
||||
return false
|
||||
end
|
||||
|
||||
for i = 1, #forbiddenWords do
|
||||
for word in string.gmatch(name, "%a+") do
|
||||
if word:lower() == forbiddenWords[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, name:len() do
|
||||
if not(isInArray(chars, name:sub(i,i))) or wordCount(name) > maxWords or name:len() > maxLength or string.find(name, " ") then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function gameShopUpdateHistory(player)
|
||||
if type(player) == "number" then
|
||||
player = Player(player)
|
||||
end
|
||||
|
||||
local history = {}
|
||||
local resultId = db.storeQuery("SELECT * FROM `shop_history` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")
|
||||
if resultId ~= false then
|
||||
repeat
|
||||
table.insert(history, {
|
||||
date = result.getDataString(resultId, "date"),
|
||||
price = result.getDataInt(resultId, "price"),
|
||||
isSecondPrice = result.getDataInt(resultId, "costSecond") == 1,
|
||||
name = result.getDataString(resultId, "title"),
|
||||
count = result.getDataInt(resultId, "count")
|
||||
})
|
||||
until not result.next(resultId)
|
||||
result.free(resultId)
|
||||
end
|
||||
|
||||
player:sendExtendedOpcode(ExtendedOPCodes.CODE_GAMESHOP, json.encode({action = "history", data = history}))
|
||||
end
|
||||
|
||||
local ExtendedEvent = CreatureEvent("GameShopExtended")
|
||||
|
||||
function ExtendedEvent.onExtendedOpcode(player, opcode, buffer)
|
||||
if opcode == ExtendedOPCodes.CODE_GAMESHOP then
|
||||
if not GAME_SHOP then
|
||||
gameShopInitialize()
|
||||
addEvent(refreshPlayersPoints, 10 * 1000)
|
||||
end
|
||||
|
||||
local status, json_data =
|
||||
pcall(
|
||||
function()
|
||||
return json.decode(buffer)
|
||||
end
|
||||
)
|
||||
if not status then
|
||||
return
|
||||
end
|
||||
|
||||
local action = json_data.action
|
||||
local data = json_data.data
|
||||
if not action or not data then
|
||||
return
|
||||
end
|
||||
|
||||
if action == "fetch" then
|
||||
gameShopFetch(player)
|
||||
elseif action == "purchase" then
|
||||
gameShopPurchase(player, data)
|
||||
elseif action == "transfer" then
|
||||
gameShopTransferCoins(player, data)
|
||||
elseif action == "changeName" then
|
||||
gameShopChangeName(player, data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function gameShopFetch(player)
|
||||
gameShopUpdatePoints(player)
|
||||
gameShopUpdateHistory(player)
|
||||
|
||||
player:sendExtendedOpcode(ExtendedOPCodes.CODE_GAMESHOP, json.encode({action = "fetchBase", data = {categories = GAME_SHOP.categories, url = DONATION_URL}}))
|
||||
|
||||
for category, offersTable in pairs(GAME_SHOP.offers) do
|
||||
player:sendExtendedOpcode(ExtendedOPCodes.CODE_GAMESHOP, json.encode({action = "fetchOffers", data = {category = category, offers = offersTable}}))
|
||||
end
|
||||
end
|
||||
|
||||
function gameShopUpdatePoints(player)
|
||||
if type(player) == "number" then
|
||||
player = Player(player)
|
||||
end
|
||||
|
||||
player:sendExtendedOpcode(ExtendedOPCodes.CODE_GAMESHOP, json.encode({action = "points", data = {
|
||||
points = getPoints(player), secondPoints = getSecondCurrency(player)}}))
|
||||
end
|
||||
|
||||
function gameShopUpdatePointsAndRemovePlayer(player)
|
||||
if type(player) == "number" then
|
||||
player = Player(player)
|
||||
end
|
||||
|
||||
player:sendExtendedOpcode(ExtendedOPCodes.CODE_GAMESHOP, json.encode({action = "points", data = {
|
||||
points = getPoints(player), secondPoints = getSecondCurrency(player)}}))
|
||||
player:remove()
|
||||
end
|
||||
|
||||
function gameShopChangeName(player, offer)
|
||||
local offers = GAME_SHOP.offers[offer.category]
|
||||
if not offers then
|
||||
return errorMsg(player, "Something went wrong, try again or contact server admin [#1]!")
|
||||
end
|
||||
|
||||
if not offer.nick then
|
||||
return errorMsg(player, "You need to choose a new nickname.")
|
||||
end
|
||||
|
||||
for i = 1, #offers do
|
||||
if offers[i].title == offer.title and offers[i].price == offer.price then
|
||||
local callback = offers[i].callback
|
||||
if not callback then
|
||||
return errorMsg(player, "Something went wrong, try again or contact server admin [#2]!")
|
||||
end
|
||||
|
||||
local points = getPoints(player)
|
||||
if offers[i].price > points then
|
||||
return errorMsg(player, "You don't have enough points!")
|
||||
end
|
||||
|
||||
if player:getName() == offer.nick then
|
||||
return errorMsg(player, "Please choose a new nickname different from your previous one")
|
||||
end
|
||||
|
||||
local status = callback(player, offer)
|
||||
if status ~= true then
|
||||
return errorMsg(player, status)
|
||||
end
|
||||
|
||||
local aid = player:getAccountId()
|
||||
local escapeTitle = db.escapeString(offers[i].title)
|
||||
local escapePrice = db.escapeString(-offers[i].price)
|
||||
local escapeIsSecondPrice = db.escapeString(-offers[i].isSecondPrice)
|
||||
local escapeCount = offers[i].count and db.escapeString(offers[i].count) or 0
|
||||
db.query("UPDATE `znote_accounts` set `points` = `points` - " .. offers[i].price .. " WHERE `id` = " .. aid)
|
||||
db.asyncQuery("INSERT INTO `shop_history` VALUES (NULL, '" .. aid .. "', '" .. player:getGuid() .. "', NOW(), " .. escapeTitle .. ", " .. escapePrice .. ", " .. escapeIsSecondPrice .. ", " .. escapeCount .. ", NULL)")
|
||||
|
||||
addEvent(gameShopUpdateHistory, 1000, player:getId())
|
||||
addEvent(gameShopUpdatePointsAndRemovePlayer, 1000, player:getId())
|
||||
return infoMsg(player, "You've bought " .. offers[i].title .. "! Please log out of your account and join us with your new name already set.", true)
|
||||
end
|
||||
end
|
||||
|
||||
return errorMsg(player, "Something went wrong, try again or contact server admin [#4]!")
|
||||
end
|
||||
|
||||
function gameShopTransferCoins(player, transfer)
|
||||
local receiver = transfer.target
|
||||
local amount = transfer.amount
|
||||
local amountSecond = transfer.amountSecond
|
||||
if not receiver then
|
||||
return errorMsg(player, "Target player not found!")
|
||||
end
|
||||
|
||||
if amount > getPoints(player) then
|
||||
return errorMsg(player, "You don't have enough points!")
|
||||
end
|
||||
|
||||
if SECOND_CURRENCY_ENABLED then
|
||||
if amountSecond > getPoints(player) then
|
||||
return errorMsg(player, "You don't have enough points!")
|
||||
end
|
||||
end
|
||||
|
||||
if receiver:lower() == player:getName():lower() then
|
||||
return errorMsg(player, "You can't transfer coins to yourself.")
|
||||
end
|
||||
|
||||
local accountId = 0
|
||||
local GUID = 0
|
||||
local resultId = db.storeQuery("SELECT `id`, `account_id` FROM `players` WHERE `name` = " .. db.escapeString(receiver))
|
||||
if resultId ~= false then
|
||||
accountId = result.getDataInt(resultId, "account_id")
|
||||
GUID = result.getDataInt(resultId, "id")
|
||||
result.free(resultId)
|
||||
end
|
||||
|
||||
if accountId == 0 then
|
||||
return errorMsg(player, "Target player not found!")
|
||||
end
|
||||
|
||||
if accountId == player:getAccountId() then
|
||||
return errorMsg(player, "You can't transfer coins to yourself.")
|
||||
end
|
||||
|
||||
local aid = player:getAccountId()
|
||||
local title = "Coin Transfer from " .. player:getName() .. " to " .. receiver:sub(1, 1):upper() .. receiver:sub(2, receiver:len()):lower()
|
||||
local escapeTitle = db.escapeString(title)
|
||||
if amount > 0 then
|
||||
db.query("UPDATE `znote_accounts` set `points` = `points` - " .. amount .. " WHERE `id` = " .. aid)
|
||||
db.query("UPDATE `znote_accounts` set `points` = `points` + " .. amount .. " WHERE `id` = " .. accountId)
|
||||
|
||||
db.asyncQuery("INSERT INTO `shop_history` VALUES (NULL, '" .. aid .. "', '" .. player:getGuid() .. "', NOW(), " .. escapeTitle .. ", " .. db.escapeString(-amount) .. ", 0, 1, " .. db.escapeString(receiver) .. ")")
|
||||
db.asyncQuery("INSERT INTO `shop_history` VALUES (NULL, '" .. accountId .. "', '" .. GUID .. "', NOW(), " .. escapeTitle .. ", " .. db.escapeString(amount) .. ", 0, 1, " .. db.escapeString(player:getName()) .. ")")
|
||||
end
|
||||
|
||||
if amountSecond > 0 then
|
||||
db.query("UPDATE `znote_accounts` set `points_second` = `points_second` - " .. amountSecond .. " WHERE `id` = " .. aid)
|
||||
db.query("UPDATE `znote_accounts` set `points_second` = `points_second` + " .. amountSecond .. " WHERE `id` = " .. accountId)
|
||||
|
||||
db.asyncQuery("INSERT INTO `shop_history` VALUES (NULL, '" .. aid .. "', '" .. player:getGuid() .. "', NOW(), " .. escapeTitle .. ", " .. db.escapeString(-amountSecond) .. ", 1, 1, " .. db.escapeString(receiver) .. ")")
|
||||
db.asyncQuery("INSERT INTO `shop_history` VALUES (NULL, '" .. accountId .. "', '" .. GUID .. "', NOW(), " .. escapeTitle .. ", " .. db.escapeString(amountSecond) .. ", 1, 1, " .. db.escapeString(player:getName()) .. ")")
|
||||
end
|
||||
|
||||
addEvent(gameShopUpdateHistory, 1000, player:getId())
|
||||
addEvent(gameShopUpdatePoints, 1000, player:getId())
|
||||
|
||||
local targetPlayer = Player(receiver)
|
||||
if targetPlayer then
|
||||
addEvent(gameShopUpdateHistory, 1000, targetPlayer:getId())
|
||||
addEvent(gameShopUpdatePoints, 1000, targetPlayer:getId())
|
||||
end
|
||||
|
||||
local message = "You've sent "
|
||||
if amount > 0 then
|
||||
message = message .. amount .. " Tibia coins "
|
||||
end
|
||||
|
||||
if amountSecond > 0 then
|
||||
message = message .. (amount > 0 and " and " or "") .. amountSecond .. " Task points "
|
||||
end
|
||||
|
||||
return infoMsg(player, message .. " to " .. receiver .. "!", true)
|
||||
end
|
||||
|
||||
function getPoints(player)
|
||||
local points = 0
|
||||
local resultId = db.storeQuery("SELECT `points` FROM `znote_accounts` WHERE `id` = " .. player:getAccountId())
|
||||
if resultId ~= false then
|
||||
points = result.getDataInt(resultId, "points")
|
||||
result.free(resultId)
|
||||
end
|
||||
|
||||
return points
|
||||
end
|
||||
|
||||
function getSecondCurrency(player)
|
||||
if not SECOND_CURRENCY_ENABLED then
|
||||
return -1
|
||||
end
|
||||
|
||||
local points = 0
|
||||
local resultId = db.storeQuery("SELECT `points_second` FROM `znote_accounts` WHERE `id` = " .. player:getAccountId())
|
||||
if resultId ~= false then
|
||||
points = result.getDataInt(resultId, "points_second")
|
||||
result.free(resultId)
|
||||
end
|
||||
|
||||
return points
|
||||
end
|
||||
|
||||
function errorMsg(player, msg)
|
||||
player:sendExtendedOpcode(ExtendedOPCodes.CODE_GAMESHOP, json.encode({action = "msg", data = {type = "error", msg = msg}}))
|
||||
end
|
||||
|
||||
function infoMsg(player, msg, close)
|
||||
if not close then
|
||||
close = false
|
||||
end
|
||||
|
||||
player:sendExtendedOpcode(ExtendedOPCodes.CODE_GAMESHOP, json.encode({action = "msg", data = {type = "info", msg = msg, close = close}}))
|
||||
end
|
||||
|
||||
function refreshPlayersPoints()
|
||||
for _, p in ipairs(Game.getPlayers()) do
|
||||
if p:getIp() > 0 then
|
||||
gameShopUpdatePoints(p)
|
||||
end
|
||||
end
|
||||
addEvent(refreshPlayersPoints, 10 * 1000)
|
||||
end
|
||||
|
||||
LoginEvent:type("login")
|
||||
LoginEvent:register()
|
||||
ExtendedEvent:type("extendedopcode")
|
||||
ExtendedEvent:register()
|
||||
|
|
@ -1,30 +1,61 @@
|
|||
|
||||
-- Instruction:
|
||||
-- creaturescripts.xml <event type="extendedopcode" name="Shop" script="shop.lua" />
|
||||
-- and in login.lua player:registerEvent("Shop")
|
||||
-- create sql table shop_history
|
||||
-- set variables
|
||||
-- set up function init(), add there items and categories, follow examples
|
||||
-- set up callbacks at the bottom to add player item/outfit/whatever you want
|
||||
|
||||
-- add json lib dofile('data/lib/json.lua') in \data\lib\lib.lua
|
||||
|
||||
--[[ SQL TABLE - ZNOTE AAC
|
||||
|
||||
--[[ SQL TABLE
|
||||
|
||||
CREATE TABLE `shop_history` (
|
||||
CREATE TABLE `znote_accounts` (
|
||||
`id` int(11) NOT NULL,
|
||||
`account_id` int(11) NOT NULL,
|
||||
`ip` bigint(20) UNSIGNED NOT NULL,
|
||||
`created` int(11) NOT NULL,
|
||||
`points` int(11) DEFAULT 0,
|
||||
`points_second` int(11) DEFAULT 0,
|
||||
`cooldown` int(11) DEFAULT 0,
|
||||
`active` tinyint(4) NOT NULL DEFAULT 0,
|
||||
`active_email` tinyint(4) NOT NULL DEFAULT 0,
|
||||
`activekey` int(11) NOT NULL DEFAULT 0,
|
||||
`flag` varchar(20) NOT NULL,
|
||||
`secret` char(16) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `shop_history` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`account` int(11) NOT NULL,
|
||||
`player` int(11) NOT NULL,
|
||||
`date` datetime NOT NULL,
|
||||
`title` varchar(100) NOT NULL,
|
||||
`cost` int(11) NOT NULL,
|
||||
`details` varchar(500) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`price` int(11) NOT NULL,
|
||||
`costSecond` int(11) NOT NULL,
|
||||
`count` int(11) NOT NULL DEFAULT '0',
|
||||
`target` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
FOREIGN KEY (`account`) REFERENCES `accounts` (`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`player`) REFERENCES `players` (`id`) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
ALTER TABLE `shop_history`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
ALTER TABLE `shop_history`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
CREATE TABLE IF NOT EXISTS `change_name_history` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`player_id` int(11) NOT NULL,
|
||||
`last_name` varchar(30) NOT NULL,
|
||||
`current_name` varchar(30) NOT NULL,
|
||||
`changed_name_in` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `player_deaths_backup` (
|
||||
`player_id` int NOT NULL,
|
||||
`time` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`level` int NOT NULL DEFAULT '1',
|
||||
`killed_by` varchar(255) NOT NULL,
|
||||
`is_player` tinyint NOT NULL DEFAULT '1',
|
||||
`mostdamage_by` varchar(100) NOT NULL,
|
||||
`mostdamage_is_player` tinyint NOT NULL DEFAULT '0',
|
||||
`unjustified` tinyint NOT NULL DEFAULT '0',
|
||||
`mostdamage_unjustified` tinyint NOT NULL DEFAULT '0',
|
||||
FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE,
|
||||
KEY `killed_by` (`killed_by`),
|
||||
KEY `mostdamage_by` (`mostdamage_by`)
|
||||
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
|
||||
|
||||
]]--
|
||||
|
|
|
|||
|
|
@ -1,676 +0,0 @@
|
|||
-- private variables
|
||||
local SHOP_EXTENTED_OPCODE = 201
|
||||
|
||||
shop = nil
|
||||
transferWindow = nil
|
||||
local otcv8shop = false
|
||||
local shopButton = nil
|
||||
local msgWindow = nil
|
||||
local browsingHistory = false
|
||||
local transferValue = 0
|
||||
|
||||
-- for classic store
|
||||
local storeUrl = ""
|
||||
local coinsPacketSize = 0
|
||||
|
||||
local CATEGORIES = {}
|
||||
local HISTORY = {}
|
||||
local STATUS = {}
|
||||
local AD = {}
|
||||
|
||||
local selectedOffer = {}
|
||||
|
||||
local function sendAction(action, data)
|
||||
|
||||
local protocolGame = g_game.getProtocolGame()
|
||||
if data == nil then
|
||||
data = {}
|
||||
end
|
||||
if protocolGame then
|
||||
protocolGame:sendExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, {
|
||||
action = action,
|
||||
data = data
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- public functions
|
||||
function init()
|
||||
connect(g_game, {
|
||||
onGameStart = check,
|
||||
onGameEnd = hide
|
||||
|
||||
})
|
||||
|
||||
ProtocolGame.registerExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, onExtendedJSONOpcode)
|
||||
|
||||
if g_game.isOnline() then
|
||||
check()
|
||||
end
|
||||
createShop()
|
||||
createTransferWindow()
|
||||
end
|
||||
|
||||
function terminate()
|
||||
disconnect(g_game, {
|
||||
onGameStart = check,
|
||||
onGameEnd = hide
|
||||
|
||||
})
|
||||
|
||||
ProtocolGame.unregisterExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, onExtendedJSONOpcode)
|
||||
|
||||
if shopButton then
|
||||
shopButton:destroy()
|
||||
shopButton = nil
|
||||
end
|
||||
if shop then
|
||||
disconnect(shop.categories, {
|
||||
onChildFocusChange = changeCategory
|
||||
})
|
||||
shop:destroy()
|
||||
shop = nil
|
||||
end
|
||||
if msgWindow then
|
||||
msgWindow:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
function check()
|
||||
otcv8shop = false
|
||||
sendAction("init")
|
||||
end
|
||||
|
||||
function hide()
|
||||
if not shop then
|
||||
return
|
||||
end
|
||||
shop:hide()
|
||||
end
|
||||
|
||||
function show()
|
||||
if not shop then
|
||||
return
|
||||
end
|
||||
|
||||
shop:show()
|
||||
shop:raise()
|
||||
shop:focus()
|
||||
end
|
||||
|
||||
function softHide()
|
||||
if not transferWindow then
|
||||
return
|
||||
end
|
||||
|
||||
transferWindow:hide()
|
||||
shop:show()
|
||||
end
|
||||
|
||||
function showTransfer()
|
||||
if not shop or not transferWindow then
|
||||
return
|
||||
end
|
||||
|
||||
hide()
|
||||
transferWindow:show()
|
||||
transferWindow:raise()
|
||||
transferWindow:focus()
|
||||
end
|
||||
|
||||
function hideTransfer()
|
||||
if not shop or not transferWindow then
|
||||
return
|
||||
end
|
||||
|
||||
transferWindow:hide()
|
||||
show()
|
||||
end
|
||||
|
||||
function toggle()
|
||||
if not shop then
|
||||
return
|
||||
end
|
||||
if shop:isVisible() then
|
||||
return hide()
|
||||
end
|
||||
show()
|
||||
check()
|
||||
end
|
||||
|
||||
function createShop()
|
||||
if shop then
|
||||
return
|
||||
end
|
||||
shop = g_ui.displayUI('shop')
|
||||
shop:hide()
|
||||
-- shopButton = modules.game_mainpanel.addStoreButton('store', tr('Shop'), '/images/options/store_large', toggle,false, 8) -- \game_mainpanel\mainpanel.lua
|
||||
shopButton = nil
|
||||
|
||||
connect(shop.categories, {
|
||||
onChildFocusChange = changeCategory
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
function createTransferWindow()
|
||||
if transferWindow then
|
||||
return
|
||||
end
|
||||
transferWindow = g_ui.displayUI('transfer')
|
||||
transferWindow:hide()
|
||||
end
|
||||
|
||||
function onStoreInit(url, coins)
|
||||
if otcv8shop then
|
||||
return
|
||||
end
|
||||
storeUrl = url
|
||||
if storeUrl:len() > 0 then
|
||||
if storeUrl:sub(storeUrl:len(), storeUrl:len()) ~= "/" then
|
||||
storeUrl = storeUrl .. "/"
|
||||
end
|
||||
storeUrl = storeUrl .. "64/"
|
||||
if storeUrl:sub(1, 4):lower() ~= "http" then
|
||||
storeUrl = "http://" .. storeUrl
|
||||
end
|
||||
end
|
||||
coinsPacketSize = coins
|
||||
createShop()
|
||||
createTransferWindow()
|
||||
end
|
||||
|
||||
function onStoreCategories(categories)
|
||||
if not shop or otcv8shop then
|
||||
return
|
||||
end
|
||||
local correctCategories = {}
|
||||
for i, category in ipairs(categories) do
|
||||
local image = ""
|
||||
if category.icon:len() > 0 then
|
||||
image = storeUrl .. category.icon
|
||||
end
|
||||
table.insert(correctCategories, {
|
||||
type = "image",
|
||||
image = image,
|
||||
name = category.name,
|
||||
offers = {}
|
||||
})
|
||||
end
|
||||
processCategories(correctCategories)
|
||||
end
|
||||
|
||||
function onStoreOffers(categoryName, offers)
|
||||
if not shop or otcv8shop then
|
||||
return
|
||||
end
|
||||
local updated = false
|
||||
|
||||
for i, category in ipairs(CATEGORIES) do
|
||||
if category.name == categoryName then
|
||||
if #category.offers ~= #offers then
|
||||
updated = true
|
||||
end
|
||||
for i = 1, #category.offers do
|
||||
if category.offers[i].title ~= offers[i].name or category.offers[i].id ~= offers[i].id or
|
||||
category.offers[i].cost ~= offers[i].price then
|
||||
updated = true
|
||||
end
|
||||
end
|
||||
if updated then
|
||||
for offer in pairs(category.offers) do
|
||||
category.offers[offer] = nil
|
||||
end
|
||||
for i, offer in ipairs(offers) do
|
||||
local image = ""
|
||||
if offer.icon:len() > 0 then
|
||||
image = storeUrl .. offer.icon
|
||||
end
|
||||
table.insert(category.offers, {
|
||||
id = offer.id,
|
||||
type = "image",
|
||||
image = image,
|
||||
cost = offer.price,
|
||||
title = offer.name,
|
||||
description = offer.description
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not updated then
|
||||
return
|
||||
end
|
||||
|
||||
local activeCategory = shop.categories:getFocusedChild()
|
||||
changeCategory(activeCategory, activeCategory)
|
||||
end
|
||||
|
||||
function onStoreTransactionHistory(currentPage, hasNextPage, offers)
|
||||
if not shop or otcv8shop then
|
||||
return
|
||||
end
|
||||
HISTORY = {}
|
||||
for i, offer in ipairs(offers) do
|
||||
table.insert(HISTORY, {
|
||||
id = offer.id,
|
||||
type = "image",
|
||||
image = storeUrl .. offer.icon,
|
||||
cost = offer.price,
|
||||
title = offer.name,
|
||||
description = offer.description
|
||||
})
|
||||
end
|
||||
|
||||
if not browsingHistory then
|
||||
return
|
||||
end
|
||||
clearOffers()
|
||||
shop.categories:focusChild(nil)
|
||||
for i, transaction in ipairs(HISTORY) do
|
||||
addOffer(0, transaction)
|
||||
end
|
||||
end
|
||||
|
||||
function onStorePurchase(message)
|
||||
if not shop or otcv8shop then
|
||||
return
|
||||
end
|
||||
if not transferWindow:isVisible() then
|
||||
processMessage({
|
||||
title = "Successful shop purchase",
|
||||
msg = message
|
||||
})
|
||||
else
|
||||
processMessage({
|
||||
title = "Successfuly gifted coins",
|
||||
msg = message
|
||||
})
|
||||
softHide()
|
||||
end
|
||||
end
|
||||
|
||||
function onStoreError(errorType, message)
|
||||
if not shop or otcv8shop then
|
||||
return
|
||||
end
|
||||
if not transferWindow:isVisible() then
|
||||
processMessage({
|
||||
title = "Shop Error",
|
||||
msg = message
|
||||
})
|
||||
else
|
||||
processMessage({
|
||||
title = "Gift coins error",
|
||||
msg = message
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function onCoinBalance(coins, transferableCoins)
|
||||
if not shop or otcv8shop then
|
||||
return
|
||||
end
|
||||
shop.infoPanel.points:setText(tr("Points:") .. " " .. coins)
|
||||
transferWindow.coinsBalance:setText(tr('Transferable Tibia Coins: ') .. coins)
|
||||
transferWindow.coinsAmount:setMaximum(coins)
|
||||
shop.infoPanel.buy:hide()
|
||||
shop.infoPanel:setHeight(20)
|
||||
end
|
||||
|
||||
function transferCoins()
|
||||
if not transferWindow then
|
||||
return
|
||||
end
|
||||
local amount = 0
|
||||
amount = transferWindow.coinsAmount:getValue()
|
||||
local recipient = transferWindow.recipient:getText()
|
||||
|
||||
g_game.transferCoins(recipient, amount)
|
||||
transferWindow.recipient:setText('')
|
||||
transferWindow.coinsAmount:setValue(0)
|
||||
end
|
||||
|
||||
function onExtendedJSONOpcode(protocol, code, json_data)
|
||||
createShop()
|
||||
createTransferWindow()
|
||||
|
||||
local action = json_data['action']
|
||||
local data = json_data['data']
|
||||
local status = json_data['status']
|
||||
if not action or not data then
|
||||
return false
|
||||
end
|
||||
|
||||
otcv8shop = true
|
||||
if action == 'categories' then
|
||||
processCategories(data)
|
||||
elseif action == 'history' then
|
||||
processHistory(data)
|
||||
elseif action == 'message' then
|
||||
processMessage(data)
|
||||
end
|
||||
|
||||
if status then
|
||||
processStatus(status)
|
||||
end
|
||||
end
|
||||
|
||||
function clearOffers()
|
||||
while shop.offers:getChildCount() > 0 do
|
||||
local child = shop.offers:getLastChild()
|
||||
shop.offers:destroyChildren(child)
|
||||
end
|
||||
end
|
||||
|
||||
function clearCategories()
|
||||
CATEGORIES = {}
|
||||
clearOffers()
|
||||
while shop.categories:getChildCount() > 0 do
|
||||
local child = shop.categories:getLastChild()
|
||||
shop.categories:destroyChildren(child)
|
||||
end
|
||||
end
|
||||
|
||||
function clearHistory()
|
||||
HISTORY = {}
|
||||
if browsingHistory then
|
||||
clearOffers()
|
||||
end
|
||||
end
|
||||
|
||||
function processCategories(data)
|
||||
if table.equal(CATEGORIES, data) then
|
||||
return
|
||||
end
|
||||
clearCategories()
|
||||
CATEGORIES = data
|
||||
for i, category in ipairs(data) do
|
||||
addCategory(category)
|
||||
end
|
||||
if not browsingHistory then
|
||||
local firstCategory = shop.categories:getChildByIndex(1)
|
||||
if firstCategory then
|
||||
firstCategory:focus()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function processHistory(data)
|
||||
if table.equal(HISTORY, data) then
|
||||
return
|
||||
end
|
||||
HISTORY = data
|
||||
if browsingHistory then
|
||||
showHistory(true)
|
||||
end
|
||||
end
|
||||
|
||||
function processMessage(data)
|
||||
if msgWindow then
|
||||
msgWindow:destroy()
|
||||
end
|
||||
|
||||
local title = tr(data["title"])
|
||||
local msg = data["msg"]
|
||||
msgWindow = displayInfoBox(title, msg)
|
||||
msgWindow.onDestroy = function(widget)
|
||||
if widget == msgWindow then
|
||||
msgWindow = nil
|
||||
end
|
||||
end
|
||||
msgWindow:show()
|
||||
msgWindow:raise()
|
||||
msgWindow:focus()
|
||||
end
|
||||
local function formatNumberWithCommas(value)
|
||||
local formattedValue = string.format("%d", value)
|
||||
-- Add commas to the formatted value
|
||||
formattedValue = formattedValue:reverse():gsub("(%d%d%d)", "%1,")
|
||||
return formattedValue:reverse():gsub("^,", "")
|
||||
end
|
||||
|
||||
function processStatus(data)
|
||||
if table.equal(STATUS, data) then
|
||||
return
|
||||
end
|
||||
STATUS = data
|
||||
|
||||
if data['ad'] then
|
||||
processAd(data['ad'])
|
||||
end
|
||||
if data['points'] then
|
||||
shop.infoPanel.points:setText(tr("Points:") .. " " .. formatNumberWithCommas(data['points']))
|
||||
end
|
||||
if data['buyUrl'] and data['buyUrl']:sub(1, 4):lower() == "http" then
|
||||
shop.infoPanel.buy:show()
|
||||
shop.infoPanel.buy.onMouseRelease = function()
|
||||
scheduleEvent(function()
|
||||
g_platform.openUrl(data['buyUrl'])
|
||||
end, 50)
|
||||
end
|
||||
else
|
||||
shop.infoPanel.buy:hide()
|
||||
shop.infoPanel:setHeight(20)
|
||||
end
|
||||
end
|
||||
|
||||
function processAd(data)
|
||||
if table.equal(AD, data) then
|
||||
return
|
||||
end
|
||||
AD = data
|
||||
|
||||
if data['image'] then
|
||||
|
||||
shop.adPanel:setHeight(shop.infoPanel:getHeight())
|
||||
shop.adPanel.ad:setText("")
|
||||
shop.adPanel.ad:setImageSource(data['image'])
|
||||
shop.adPanel.ad:setImageFixedRatio(true)
|
||||
shop.adPanel.ad:setImageAutoResize(true)
|
||||
shop.adPanel.ad:setHeight(shop.infoPanel:getHeight())
|
||||
|
||||
elseif data['text'] and data['text']:len() > 0 then
|
||||
shop.adPanel:setHeight(shop.infoPanel:getHeight())
|
||||
shop.adPanel.ad:setText(data['text'])
|
||||
shop.adPanel.ad:setHeight(shop.infoPanel:getHeight())
|
||||
else
|
||||
shop.adPanel:setHeight(0)
|
||||
end
|
||||
if data['url'] and data['url']:sub(1, 4):lower() == "http" then
|
||||
shop.adPanel.ad.onMouseRelease = function()
|
||||
scheduleEvent(function()
|
||||
g_platform.openUrl(data['url'])
|
||||
end, 50)
|
||||
end
|
||||
else
|
||||
shop.adPanel.ad.onMouseRelease = nil
|
||||
end
|
||||
end
|
||||
|
||||
function addCategory(data)
|
||||
|
||||
local category
|
||||
if data["type"] == "item" then
|
||||
category = g_ui.createWidget('ShopCategoryItem', shop.categories)
|
||||
category.item:setItemId(data["item"])
|
||||
category.item:setItemCount(data["count"])
|
||||
-- category.item:setShowCount(false)
|
||||
elseif data["type"] == "outfit" then
|
||||
category = g_ui.createWidget('ShopCategoryCreature', shop.categories)
|
||||
category.creature:setOutfit(data["outfit"])
|
||||
|
||||
elseif data["type"] == "shader" then
|
||||
category = g_ui.createWidget('ShopCategoryCreature', shop.categories)
|
||||
category.creature:setOutfit(g_game.getLocalPlayer():getOutfit())
|
||||
category.creature:getCreature():setShader(data["shader"])
|
||||
|
||||
elseif data["type"] == "image" then
|
||||
category = g_ui.createWidget('ShopCategoryImage', shop.categories)
|
||||
if data["image"] and data["image"]:sub(1, 4):lower() == "http" then
|
||||
HTTP.downloadImage(data['image'], function(path, err)
|
||||
if err then
|
||||
g_logger.warning("HTTP error: " .. err .. " - " .. data["image"])
|
||||
return
|
||||
end
|
||||
category.image:setImageSource(path)
|
||||
end)
|
||||
else
|
||||
category.image:setImageSource(data["image"])
|
||||
end
|
||||
else
|
||||
g_logger.error("Invalid shop category type: " .. tostring(data["type"]))
|
||||
return
|
||||
end
|
||||
category:setId("category_" .. shop.categories:getChildCount())
|
||||
category.name:setText(data["name"])
|
||||
end
|
||||
|
||||
function showHistory(force)
|
||||
if browsingHistory and not force then
|
||||
return
|
||||
end
|
||||
|
||||
sendAction("history")
|
||||
|
||||
browsingHistory = true
|
||||
clearOffers()
|
||||
shop.categories:focusChild(nil)
|
||||
for i, transaction in ipairs(HISTORY) do
|
||||
addOffer(0, transaction)
|
||||
end
|
||||
end
|
||||
|
||||
function addOffer(category, data)
|
||||
local offer
|
||||
if data["type"] == "item" then
|
||||
offer = g_ui.createWidget('ShopOfferItem', shop.offers)
|
||||
offer.item:setItemId(data["item"])
|
||||
offer.item:setItemCount(data["count"])
|
||||
-- offer.item:setShowCount(false)
|
||||
|
||||
elseif data["type"] == "effect" then
|
||||
|
||||
offer = g_ui.createWidget('ShopOfferCreature', shop.offers)
|
||||
offer.creature:setOutfit(g_game.getLocalPlayer():getOutfit())
|
||||
offer.creature:getCreature():attachEffect(g_attachedEffects.getById(data["title"]))
|
||||
|
||||
elseif data["type"] == "shader" then
|
||||
|
||||
offer = g_ui.createWidget('ShopOfferCreature', shop.offers)
|
||||
offer.creature:setOutfit(g_game.getLocalPlayer():getOutfit())
|
||||
offer.creature:getCreature():setShader(data["title"])
|
||||
|
||||
elseif data["type"] == "outfit" then
|
||||
offer = g_ui.createWidget('ShopOfferCreature', shop.offers)
|
||||
offer.creature:setOutfit(data["outfit"])
|
||||
if data["outfit"]["rotating"] then
|
||||
-- offer.creature:setAutoRotating(true)
|
||||
end
|
||||
elseif data["type"] == "image" then
|
||||
offer = g_ui.createWidget('ShopOfferImage', shop.offers)
|
||||
if data["image"] and data["image"]:sub(1, 4):lower() == "http" then
|
||||
HTTP.downloadImage(data['image'], function(path, err)
|
||||
if err then
|
||||
g_logger.warning("HTTP error: " .. err .. " - " .. data['image'])
|
||||
return
|
||||
end
|
||||
if not offer.image then
|
||||
return
|
||||
end
|
||||
offer.image:setImageSource(path)
|
||||
end)
|
||||
elseif data["image"] and data["image"]:len() > 1 then
|
||||
offer.image:setImageSource(data["image"])
|
||||
end
|
||||
else
|
||||
g_logger.error("Invalid shop offer type: " .. tostring(data["type"]))
|
||||
return
|
||||
end
|
||||
offer:setId("offer_" .. category .. "_" .. shop.offers:getChildCount())
|
||||
offer.title:setColoredText(data["title"] .. " {[" .. data["cost"] .. " points], #ff0000}")
|
||||
offer.description:setText(data["description"])
|
||||
offer.offerId = data["id"]
|
||||
if category ~= 0 then
|
||||
offer.onDoubleClick = buyOffer
|
||||
offer.buyButton.onClick = function()
|
||||
buyOffer(offer)
|
||||
end
|
||||
else
|
||||
offer.buyButton:hide()
|
||||
end
|
||||
end
|
||||
|
||||
function changeCategory(widget, newCategory)
|
||||
if not newCategory then
|
||||
return
|
||||
end
|
||||
|
||||
browsingHistory = false
|
||||
local id = tonumber(newCategory:getId():split("_")[2])
|
||||
clearOffers()
|
||||
for i, offer in ipairs(CATEGORIES[id]["offers"]) do
|
||||
addOffer(id, offer)
|
||||
end
|
||||
end
|
||||
|
||||
function buyOffer(widget)
|
||||
if not widget then
|
||||
return
|
||||
end
|
||||
local split = widget:getId():split("_")
|
||||
if #split ~= 3 then
|
||||
return
|
||||
end
|
||||
local category = tonumber(split[2])
|
||||
local offer = tonumber(split[3])
|
||||
local item = CATEGORIES[category]["offers"][offer]
|
||||
if not item then
|
||||
return
|
||||
end
|
||||
|
||||
selectedOffer = {
|
||||
category = category,
|
||||
offer = offer,
|
||||
title = item.title,
|
||||
cost = item.cost,
|
||||
id = widget.offerId
|
||||
}
|
||||
|
||||
scheduleEvent(function()
|
||||
if msgWindow then
|
||||
msgWindow:destroy()
|
||||
end
|
||||
|
||||
local title = tr("Buying from shop")
|
||||
local msg = "Do you want to buy " .. item.title .. " for " .. item.cost .. " premium points?"
|
||||
msgWindow = displayGeneralBox(title, msg, {
|
||||
{
|
||||
text = tr('Yes'),
|
||||
callback = buyConfirmed
|
||||
},
|
||||
{
|
||||
text = tr('No'),
|
||||
callback = buyCanceled
|
||||
},
|
||||
anchor = AnchorHorizontalCenter
|
||||
}, buyConfirmed, buyCanceled)
|
||||
msgWindow:show()
|
||||
msgWindow:raise()
|
||||
msgWindow:focus()
|
||||
msgWindow:raise()
|
||||
end, 50)
|
||||
end
|
||||
|
||||
function buyConfirmed()
|
||||
msgWindow:destroy()
|
||||
msgWindow = nil
|
||||
sendAction("buy", selectedOffer)
|
||||
|
||||
end
|
||||
|
||||
function buyCanceled()
|
||||
msgWindow:destroy()
|
||||
msgWindow = nil
|
||||
selectedOffer = {}
|
||||
end
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
Module
|
||||
name: game_shop
|
||||
description: Game shop
|
||||
author: otclient.ovh
|
||||
website: http://otclient.ovh
|
||||
sandboxed: true
|
||||
scripts: [ shop ]
|
||||
dependencies: [ client_topmenu ]
|
||||
@onLoad: init()
|
||||
@onUnload: terminate()
|
||||
|
|
@ -1,246 +0,0 @@
|
|||
ShopCategory < Panel
|
||||
height: 36
|
||||
focusable: true
|
||||
background: alpha
|
||||
|
||||
$focus:
|
||||
background: #99999999
|
||||
|
||||
Label
|
||||
id: name
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
margin-left: 40
|
||||
text-align: left
|
||||
color: white
|
||||
font: verdana-11px-rounded
|
||||
|
||||
ShopCategoryItem < ShopCategory
|
||||
UIItem
|
||||
id: item
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
margin-top: 2
|
||||
margin-bottom: 2
|
||||
margin-left: 2
|
||||
virtual: true
|
||||
size: 32 32
|
||||
|
||||
ShopCategoryCreature < ShopCategory
|
||||
UICreature
|
||||
id: creature
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
margin-top: 2
|
||||
margin-bottom: 2
|
||||
margin-left: 2
|
||||
size: 32 32
|
||||
|
||||
|
||||
ShopCategoryImage < ShopCategory
|
||||
Label
|
||||
id: image
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
margin-top: 2
|
||||
margin-bottom: 2
|
||||
margin-left: 2
|
||||
size: 32 32
|
||||
|
||||
|
||||
|
||||
ShopOffer < Panel
|
||||
height: 56
|
||||
background: alpha
|
||||
|
||||
$focus:
|
||||
background: #99999999
|
||||
|
||||
Label
|
||||
id: title
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
margin-top: 4
|
||||
margin-left: 55
|
||||
text-align: topleft
|
||||
color: white
|
||||
font: verdana-11px-rounded
|
||||
|
||||
Label
|
||||
id: description
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
margin-left: 55
|
||||
margin-right: 55
|
||||
text-align: topleft
|
||||
text-auto-resize: true
|
||||
text-wrap: true
|
||||
color: white
|
||||
font: verdana-11px-rounded
|
||||
|
||||
Button
|
||||
id: buyButton
|
||||
text: BUY
|
||||
height: 25
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: prev.right
|
||||
anchors.right: parent.right
|
||||
margin-right: 15
|
||||
text-align: center
|
||||
|
||||
ShopOfferItem < ShopOffer
|
||||
UIItem
|
||||
id: item
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
margin-top: 4
|
||||
margin-bottom: 4
|
||||
margin-left: 2
|
||||
virtual: true
|
||||
size: 48 48
|
||||
|
||||
ShopOfferCreature < ShopOffer
|
||||
UICreature
|
||||
id: creature
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
margin-top: 4
|
||||
margin-bottom: 4
|
||||
margin-left: 2
|
||||
size: 48 48
|
||||
|
||||
ShopOfferImage < ShopOffer
|
||||
Label
|
||||
id: image
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
margin-top: 4
|
||||
margin-bottom: 4
|
||||
margin-left: 2
|
||||
size: 48 48
|
||||
|
||||
MainWindow
|
||||
id: shopWindow
|
||||
!text: tr('Shop')
|
||||
size: 750 500
|
||||
@onEscape: modules.game_shop.hide()
|
||||
$mobile:
|
||||
size: 500 360
|
||||
|
||||
Panel
|
||||
id: infoPanel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: 230
|
||||
height: 60
|
||||
|
||||
Label
|
||||
id: points
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
margin-top: 10
|
||||
text: -
|
||||
text-auto-resize: true
|
||||
|
||||
Button
|
||||
id: buy
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 150
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 10
|
||||
visible: false
|
||||
!text: tr("Buy points2")
|
||||
|
||||
Panel
|
||||
id: adPanel
|
||||
anchors.top: parent.top
|
||||
anchors.left: infoPanel.right
|
||||
anchors.right: parent.right
|
||||
margin-left: 10
|
||||
height: 0
|
||||
|
||||
Label
|
||||
id: ad
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
text-auto-resize: true
|
||||
text-wrap: true
|
||||
text-align: center
|
||||
font: sans-bold-16px
|
||||
|
||||
TextList
|
||||
id: categories
|
||||
vertical-scrollbar: categoriesScrollBar
|
||||
anchors.top: infoPanel.bottom
|
||||
anchors.left: infoPanel.left
|
||||
anchors.right: infoPanel.right
|
||||
anchors.bottom: transactionHistory.top
|
||||
margin-top: 10
|
||||
margin-bottom: 10
|
||||
padding: 1
|
||||
focusable: false
|
||||
|
||||
VerticalScrollBar
|
||||
id: categoriesScrollBar
|
||||
anchors.top: categories.top
|
||||
anchors.bottom: categories.bottom
|
||||
anchors.right: categories.right
|
||||
step: 50
|
||||
pixels-scroll: true
|
||||
|
||||
TextList
|
||||
id: offers
|
||||
vertical-scrollbar: offersScrollBar
|
||||
anchors.top: adPanel.bottom
|
||||
anchors.left: adPanel.left
|
||||
anchors.right: adPanel.right
|
||||
anchors.bottom: transactionHistory.top
|
||||
margin-top: 10
|
||||
margin-bottom: 10
|
||||
padding: 1
|
||||
focusable: false
|
||||
|
||||
VerticalScrollBar
|
||||
id: offersScrollBar
|
||||
anchors.top: offers.top
|
||||
anchors.bottom: offers.bottom
|
||||
anchors.right: offers.right
|
||||
step: 50
|
||||
pixels-scroll: true
|
||||
|
||||
Button
|
||||
id: transactionHistory
|
||||
!text: tr('Transaction history')
|
||||
width: 128
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
@onClick: modules.game_shop.showHistory()
|
||||
|
||||
Label
|
||||
id: transferOpen
|
||||
!text: tr('by kondra (otclient@otclient.ovh)')
|
||||
width: 450
|
||||
anchors.left: prev. right
|
||||
margin-left: 45
|
||||
anchors.verticalCenter: prev.verticalCenter
|
||||
|
||||
|
||||
Button
|
||||
id: buttonCancel
|
||||
!text: tr('Close')
|
||||
width: 64
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
@onClick: modules.game_shop.hide()
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
MainWindow
|
||||
id: transferWindow
|
||||
!text: tr('Gift Tibia Coins')
|
||||
size: 280 240
|
||||
@onEscape: modules.game_shop.hideTransfer()
|
||||
|
||||
Label
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
text-wrap: true
|
||||
height: 56
|
||||
!text: tr('Please select the amount of Tibia Coins you would like to gift and enter the name of the character that should receive the Tibia Coins.')
|
||||
|
||||
Label
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
margin-top: 20
|
||||
!text: tr('Reciepient:')
|
||||
|
||||
TextEdit
|
||||
id: recipient
|
||||
anchors.verticalCenter: prev.verticalCenter
|
||||
anchors.right: parent.right
|
||||
width: 150
|
||||
text-align: left
|
||||
|
||||
Label
|
||||
id: coinsBalance
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 10
|
||||
text-align: center
|
||||
!text: tr('Transferable Tibia Coins:')
|
||||
|
||||
Label
|
||||
id: coinsAmountLabel
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
margin-top: 20
|
||||
!text: tr('Amount to gift: ')
|
||||
|
||||
SpinBox
|
||||
id: coinsAmount
|
||||
anchors.right: parent.right
|
||||
width: 100
|
||||
anchors.verticalCenter: prev.verticalCenter
|
||||
text: 0
|
||||
minimum: 0
|
||||
maximum: 0
|
||||
focusable: true
|
||||
editable: true
|
||||
|
||||
HorizontalSeparator
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: cancelButton.top
|
||||
margin-bottom: 8
|
||||
|
||||
Button
|
||||
id: cancelButton
|
||||
!text: tr('Cancel')
|
||||
font: cipsoftFont
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
size: 45 21
|
||||
margin-top: 15
|
||||
margin-right: 5
|
||||
@onClick: modules.game_shop.hideTransfer()
|
||||
|
||||
Button
|
||||
id: giftButton
|
||||
!text: tr('Gift')
|
||||
font: cipsoftFont
|
||||
size: 45 21
|
||||
anchors.verticalCenter: prev.verticalCenter
|
||||
anchors.right: prev.left
|
||||
margin-right: 5
|
||||
@onClick: modules.game_shop.transferCoins()
|
||||