Untitled Diff

Created Diff never expires
0 removals
362 lines
8 additions
369 lines
-- BETA VERSION, net tested yet
-- BETA VERSION, net tested yet
-- Instruction:
-- Instruction:
-- creaturescripts.xml <event type="extendedopcode" name="Shop" script="shop.lua" />
-- creaturescripts.xml <event type="extendedopcode" name="Shop" script="shop.lua" />
-- and in login.lua player:registerEvent("Shop")
-- and in login.lua player:registerEvent("Shop")
-- create sql table shop_history
-- create sql table shop_history
-- set variables
-- set variables
-- set up function init(), add there items and categories, follow examples
-- 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
-- set up callbacks at the bottom to add player item/outfit/whatever you want


local SHOP_EXTENDED_OPCODE = 201
local SHOP_EXTENDED_OPCODE = 201
local SHOP_OFFERS = {}
local SHOP_OFFERS = {}
local SHOP_CALLBACKS = {}
local SHOP_CALLBACKS = {}
local SHOP_CATEGORIES = nil
local SHOP_CATEGORIES = nil
local SHOP_BUY_URL = "http://otland.net" -- can be empty
local SHOP_BUY_URL = "http://otland.net" -- can be empty
local SHOP_AD = { -- can be nil
local SHOP_AD = { -- can be nil
image = "https://s3.envato.com/files/62273611/PNG%20Blue/Banner%20blue%20468x60.png",
image = "https://s3.envato.com/files/62273611/PNG%20Blue/Banner%20blue%20468x60.png",
url = "http://otclient.ovh",
url = "http://otclient.ovh",
text = ""
text = ""
}
}
local MAX_PACKET_SIZE = 50000
local MAX_PACKET_SIZE = 50000


--[[ SQL TABLE
--[[ SQL TABLE


CREATE TABLE `shop_history` (
CREATE TABLE `shop_history` (
`id` int(11) NOT NULL,
`id` int(11) NOT NULL,
`account` int(11) NOT NULL,
`account` int(11) NOT NULL,
`player` int(11) NOT NULL,
`player` int(11) NOT NULL,
`date` datetime NOT NULL,
`date` datetime NOT NULL,
`title` varchar(100) NOT NULL,
`title` varchar(100) NOT NULL,
`cost` int(11) NOT NULL,
`cost` int(11) NOT NULL,
`details` varchar(500) NOT NULL
`details` varchar(500) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


ALTER TABLE `shop_history`
ALTER TABLE `shop_history`
ADD PRIMARY KEY (`id`);
ADD PRIMARY KEY (`id`);
ALTER TABLE `shop_history`
ALTER TABLE `shop_history`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;


]]--
]]--


function init()
function init()
-- print(json.encode(g_game.getLocalPlayer():getOutfit())) -- in console in otclient, will print current outfit and mount
-- print(json.encode(g_game.getLocalPlayer():getOutfit())) -- in console in otclient, will print current outfit and mount
SHOP_CATEGORIES = {}
SHOP_CATEGORIES = {}


local category1 = addCategory({
local category1 = addCategory({
type="item",
type="item",
item=ItemType(2160):getClientId(),
item=ItemType(2160):getClientId(),
count=100,
count=100,
name="Items"
name="Items"
})
})
local category2 = addCategory({
local category2 = addCategory({
type="outfit",
type="outfit",
name="Outfits",
name="Outfits",
outfit={
outfit={
mount=0,
mount=0,
feet=114,
feet=114,
legs=114,
legs=114,
body=116,
body=116,
type=143,
type=143,
auxType=0,
auxType=0,
addons=3,
addons=3,
head=2,
head=2,
rotating=true
rotating=true
}
}
})
})
local category3 = addCategory({
local category3 = addCategory({
type="image",
type="image",
image="http://otclient.ovh/images/137.png",
image="http://otclient.ovh/images/137.png",
name="Category with http image"
name="Category with http image"
})
})
local category4 = addCategory({
local category4 = addCategory({
type="image",
type="image",
image="/data/images/game/states/electrified.png",
image="/data/images/game/states/electrified.png",
name="Category with local image"
name="Category with local image"
})
})
category1.addItem(1, 2160, 1, "1 Crystal coin", "description of cristal coin")
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(5, 2160, 5, "5 Crystal coin", "description of cristal coin")
category1.addItem(50, 2160, 50, "50 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(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(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(1, 2160, 1, "1 Crystal coin1", "description of cristal coin")
category1.addItem(5, 2160, 5, "5 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(50, 2160, 50, "50 Crystal coin1", "description of cristal coin")
category1.addItem(90, 2160, 100, "100 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(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(1, 2160, 1, "1 Crystal coin3", "description of cristal coin")
category1.addItem(5, 2160, 5, "5 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(50, 2160, 50, "50 Crystal coin3", "description of cristal coin")
category1.addItem(90, 2160, 100, "100 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")
category1.addItem(200, 2493, 1, "Demon helmet3", "wooxD\ndemon helmet\nnice, you should buy it")
category2.addOutfit(500, {
category2.addOutfit(500, {
mount=0,
mount=0,
feet=114,
feet=114,
legs=114,
legs=114,
body=116,
body=116,
type=143,
type=143,
auxType=0,
auxType=0,
addons=3,
addons=3,
head=2,
head=2,
rotating=true
rotating=true
}, "title of this cool outfit or whatever", "this is your new cool outfit. You can buy it here.\nsrlsy")
}, "title of this cool outfit or whatever", "this is your new cool outfit. You can buy it here.\nsrlsy")
category2.addOutfit(100, {
category2.addOutfit(100, {
mount=682,
mount=682,
feet=0,
feet=0,
legs=0,
legs=0,
body=0,
body=0,
type=143,
type=143,
auxType=0,
auxType=0,
addons=0,
addons=0,
head=0,
head=0,
rotating=true
rotating=true
}, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
}, "MOUNT!!!", "DOUBLE CLICK TO BUY THIS MOUNT. IDK NAME")
category2.addOutfit(100, {
category2.addOutfit(100, {
mount=0,
mount=0,
feet=0,
feet=0,
legs=0,
legs=0,
body=0,
body=0,
type=35,
type=35,
auxType=0,
auxType=0,
addons=0,
addons=0,
head=0,
head=0,
rotating=true
rotating=true
}, "Demon outfit", "Want be a demon?\nNo problem")
}, "Demon outfit", "Want be a demon?\nNo problem")
category2.addOutfit(100, {
category2.addOutfit(100, {
mount=0,
mount=0,
feet=0,
feet=0,
legs=0,
legs=0,
body=0,
body=0,
type=35,
type=35,
auxType=0,
auxType=0,
addons=0,
addons=0,
head=0,
head=0,
rotating=false
rotating=false
}, "Demon outfit2", "This one is not rotating")
}, "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, "/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)
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
print(category1)
end


function addCategory(data)
function addCategory(data)
print("[addCategory] input data: " .. data)
data['offers'] = {}
data['offers'] = {}
table.insert(SHOP_CATEGORIES, data)
table.insert(SHOP_CATEGORIES, data)
table.insert(SHOP_CALLBACKS, {})
table.insert(SHOP_CALLBACKS, {})
local index = #SHOP_CATEGORIES
local index = #SHOP_CATEGORIES
return {
return {
addItem = function(cost, itemId, count, title, description, callback)
addItem = function(cost, itemId, count, title, description, callback)
if not callback then
if not callback then
callback = defaultItemBuyAction
callback = defaultItemBuyAction
end
end
table.insert(SHOP_CATEGORIES[index]['offers'], {
table.insert(SHOP_CATEGORIES[index]['offers'], {
cost=cost,
cost=cost,
type="item",
type="item",
item=ItemType(itemId):getClientId(), -- displayed
item=ItemType(itemId):getClientId(), -- displayed
itemId=itemId,
itemId=itemId,
count=count,
count=count,
title=title,
title=title,
description=description
description=description
})
})
table.insert(SHOP_CALLBACKS[index], callback)
table.insert(SHOP_CALLBACKS[index], callback)
end,
end,
addOutfit = function(cost, outfit, title, description, callback)
addOutfit = function(cost, outfit, title, description, callback)
if not callback then
if not callback then
callback = defaultOutfitBuyAction
callback = defaultOutfitBuyAction
end
end
table.insert(SHOP_CATEGORIES[index]['offers'], {
table.insert(SHOP_CATEGORIES[index]['offers'], {
cost=cost,
cost=cost,
type="outfit",
type="outfit",
outfit=outfit,
outfit=outfit,
title=title,
title=title,
description=description
description=description
})
})
table.insert(SHOP_CALLBACKS[index], callback)
table.insert(SHOP_CALLBACKS[index], callback)
end,
end,
addImage = function(cost, image, title, description, callback)
addImage = function(cost, image, title, description, callback)
if not callback then
if not callback then
callback = defaultImageBuyAction
callback = defaultImageBuyAction
end
end
table.insert(SHOP_CATEGORIES[index]['offers'], {
table.insert(SHOP_CATEGORIES[index]['offers'], {
cost=cost,
cost=cost,
type="image",
type="image",
image=image,
image=image,
title=title,
title=title,
description=description
description=description
})
})
table.insert(SHOP_CALLBACKS[index], callback)
table.insert(SHOP_CALLBACKS[index], callback)
print("[addCategory] SHOP_CATEGORIES at end of function: " .. SHOP_CATEGORIES)

end
end
}
}
end
end


function getPoints(player)
function getPoints(player)
local points = 0
local points = 0
local resultId = db.storeQuery("SELECT `premium_points` FROM `accounts` WHERE `id` = " .. player:getAccountId())
local resultId = db.storeQuery("SELECT `premium_points` FROM `accounts` WHERE `id` = " .. player:getAccountId())
if resultId ~= false then
if resultId ~= false then
points = result.getDataInt(resultId, "premium_points")
points = result.getDataInt(resultId, "premium_points")
result.free(resultId)
result.free(resultId)
end
end
return points
return points
end
end


function getStatus(player)
function getStatus(player)
local status = {
local status = {
ad = SHOP_AD,
ad = SHOP_AD,
points = getPoints(player),
points = getPoints(player),
buyUrl = SHOP_BUY_URL
buyUrl = SHOP_BUY_URL
}
}
return status
return status
end
end


function sendJSON(player, action, data, forceStatus)
function sendJSON(player, action, data, forceStatus)
local status = nil
local status = nil
if not player:getStorageValue(1150001) or player:getStorageValue(1150001) + 10 < os.time() or forceStatus then
if not player:getStorageValue(1150001) or player:getStorageValue(1150001) + 10 < os.time() or forceStatus then
status = getStatus(player)
status = getStatus(player)
end
end
player:setStorageValue(1150001, os.time())
player:setStorageValue(1150001, os.time())


local buffer = json.encode({action = action, data = data, status = status})
local buffer = json.encode({action = action, data = data, status = status})
print("[sendJSON] buffer: " .. buffer )
local s = {}
local s = {}
for i=1, #buffer, MAX_PACKET_SIZE do
for i=1, #buffer, MAX_PACKET_SIZE do
s[#s+1] = buffer:sub(i,i+MAX_PACKET_SIZE - 1)
s[#s+1] = buffer:sub(i,i+MAX_PACKET_SIZE - 1)
end
end

local msg = NetworkMessage()
local msg = NetworkMessage()
if #s == 1 then
if #s == 1 then
msg:addByte(50)
msg:addByte(50)
msg:addByte(SHOP_EXTENDED_OPCODE)
msg:addByte(SHOP_EXTENDED_OPCODE)
msg:addString(s[1])
msg:addString(s[1])
msg:sendToPlayer(player)
msg:sendToPlayer(player)
return
return
end
end
-- split message if too big
-- split message if too big
msg:addByte(50)
msg:addByte(50)
msg:addByte(SHOP_EXTENDED_OPCODE)
msg:addByte(SHOP_EXTENDED_OPCODE)
msg:addString("S" .. s[1])
msg:addString("S" .. s[1])
msg:sendToPlayer(player)
msg:sendToPlayer(player)
for i=2,#s - 1 do
for i=2,#s - 1 do
msg = NetworkMessage()
msg = NetworkMessage()
msg:addByte(50)
msg:addByte(50)
msg:addByte(SHOP_EXTENDED_OPCODE)
msg:addByte(SHOP_EXTENDED_OPCODE)
msg:addString("P" .. s[i])
msg:addString("P" .. s[i])
msg:sendToPlayer(player)
msg:sendToPlayer(player)
end
end
msg = NetworkMessage()
msg = NetworkMessage()
msg:addByte(50)
msg:addByte(50)
msg:addByte(SHOP_EXTENDED_OPCODE)
msg:addByte(SHOP_EXTENDED_OPCODE)
msg:addString("E" .. s[#s])
msg:addString("E" .. s[#s])
msg:sendToPlayer(player)
msg:sendToPlayer(player)
end
end


function sendMessage(player, title, msg, forceStatus)
function sendMessage(player, title, msg, forceStatus)
sendJSON(player, "message", {title=title, msg=msg}, forceStatus)
sendJSON(player, "message", {title=title, msg=msg}, forceStatus)
end
end


function onExtendedOpcode(player, opcode, buffer)
function onExtendedOpcode(player, opcode, buffer)
if opcode ~= SHOP_EXTENDED_OPCODE then
if opcode ~= SHOP_EXTENDED_OPCODE then
return false
return false
end
end
local status, json_data = pcall(function() return json.decode(buffer) end)
local status, json_data = pcall(function() return json.decode(buffer) end)
if not status then
if not status then
return false
return false
end
end


local action = json_data['action']
local action = json_data['action']
local data = json_data['data']
local data = json_data['data']
if not action or not data then
if not action or not data then
return false
return false
end
end


if SHOP_CATEGORIES == nil then
if SHOP_CATEGORIES == nil then
init()
init()
end
end


if action == 'init' then
if action == 'init' then
print("init action sending json with categories: " .. json.encode(SHOP_CATEGORIES))
sendJSON(player, "categories", SHOP_CATEGORIES)
sendJSON(player, "categories", SHOP_CATEGORIES)
elseif action == 'buy' then
elseif action == 'buy' then
processBuy(player, data)
processBuy(player, data)
elseif action == "history" then
elseif action == "history" then
sendHistory(player)
sendHistory(player)
end
end
return true
return true
end
end


function processBuy(player, data)
function processBuy(player, data)
local categoryId = tonumber(data["category"])
local categoryId = tonumber(data["category"])
local offerId = tonumber(data["offer"])
local offerId = tonumber(data["offer"])
local offer = SHOP_CATEGORIES[categoryId]['offers'][offerId]
local offer = SHOP_CATEGORIES[categoryId]['offers'][offerId]
local callback = SHOP_CALLBACKS[categoryId][offerId]
local callback = SHOP_CALLBACKS[categoryId][offerId]
if not offer or not callback or data["title"] ~= offer["title"] or data["cost"] ~= offer["cost"] then
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
sendJSON(player, "categories", SHOP_CATEGORIES) -- refresh categories, maybe invalid
return sendMessage(player, "Error!", "Invalid offer")
return sendMessage(player, "Error!", "Invalid offer")
end
end
local points = getPoints(player)
local points = getPoints(player)
if not offer['cost'] or offer['cost'] > points or points < 1 then
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)
return sendMessage(player, "Error!", "You don't have enough points to buy " .. offer['title'] .."!", true)
end
end
local status = callback(player, offer)
local status = callback(player, offer)
if status == true then
if status == true then
db.query("UPDATE `accounts` set `premium_points` = `premium_points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())
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)) .. ")")
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)
return sendMessage(player, "Success!", "You bought " .. offer['title'] .."!", true)
end
end
if status == nil or status == false then
if status == nil or status == false then
status = "Unknown error while buying " .. offer['title']
status = "Unknown error while buying " .. offer['title']
end
end
sendMessage(player, "Error!", status)
sendMessage(player, "Error!", status)
end
end


function sendHistory(player)
function sendHistory(player)
if player:getStorageValue(1150002) and player:getStorageValue(1150002) + 10 > os.time() then
if player:getStorageValue(1150002) and player:getStorageValue(1150002) + 10 > os.time() then
return -- min 10s delay
return -- min 10s delay
end
end
player:setStorageValue(1150002, os.time())
player:setStorageValue(1150002, os.time())
local history = {}
local history = {}
local resultId = db.storeQuery("SELECT * FROM `shop_history` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")
local resultId = db.storeQuery("SELECT * FROM `shop_history` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")


if resultId ~= false then
if resultId ~= false then
repeat
repeat
local details = result.getDataString(resultId, "details")
local details = result.getDataString(resultId, "details")
local status, json_data = pcall(function() return json.decode(details) end)
local status, json_data = pcall(function() return json.decode(details) end)
if not status then
if not status then
json_data = {
json_data = {
type = "image",
type = "image",
title = result.getDataString(resultId, "title"),
title = result.getDataString(resultId, "title"),
cost = result.getDataInt(resultId, "cost")
cost = result.getDataInt(resultId, "cost")
}
}
end
end
table.insert(history, json_data)
table.insert(history, json_data)
history[#history]["description"] = "Bought on " .. result.getDataString(resultId, "date") .. " for " .. result.getDataInt(resultId, "cost") .. " points."
history[#history]["description"] = "Bought on " .. result.getDataString(resultId, "date") .. " for " .. result.getDataInt(resultId, "cost") .. " points."
until not result.next(resultId)
until not result.next(resultId)
result.free(resultId)
result.free(resultId)
end
end
sendJSON(player, "history", history)
sendJSON(player, "history", history)
end
end


-- BUY CALLBACKS
-- BUY CALLBACKS
-- May be useful: print(json.encode(offer))
-- May be useful: print(json.encode(offer))


function defaultItemBuyAction(player, offer)
function defaultItemBuyAction(player, offer)
-- todo: check if has capacity
-- todo: check if has capacity
if player:addItem(offer["itemId"], offer["count"], false) then
if player:addItem(offer["itemId"], offer["count"], false) then
return true
return true
end
end
return "Can't add item! Do you have enough space?"
return "Can't add item! Do you have enough space?"
end
end


function defaultOutfitBuyAction(player, offer)
function defaultOutfitBuyAction(player, offer)
return "default outfit buy action is not implemented"
return "default outfit buy action is not implemented"
end
end


function defaultImageBuyAction(player, offer)
function defaultImageBuyAction(player, offer)
return "default image buy action is not implemented"
return "default image buy action is not implemented"
end
end


function customImageBuyAction(player, offer)
function customImageBuyAction(player, offer)
return "custom image buy action is not implemented. Offer: " .. offer['title']
return "custom image buy action is not implemented. Offer: " .. offer['title']
end
end