added mounts, auras, wings and shop. Packet compression does not work extendedOpCodes does not work but shop is because of extra configuration

This commit is contained in:
ErikasKontenis
2022-08-08 18:55:47 +03:00
parent b130e66149
commit 1c35d04337
58 changed files with 2909 additions and 77 deletions

6
data/XML/auras.xml Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<auras>
<!-- <aura id="1" clientid="368" name="Widow Queen" speed="20" />
<aura id="2" clientid="369" name="Racing Bird" speed="20" />
<aura id="3" clientid="370" name="War Bear" speed="20" /> -->
</auras>

106
data/XML/mounts.xml Normal file
View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<mounts>
<mount id="1" clientid="368" name="Widow Queen" speed="20" premium="no" />
<!--
<mount id="2" clientid="369" name="Racing Bird" speed="20" premium="yes" />
<mount id="3" clientid="370" name="War Bear" speed="20" premium="yes" />
<mount id="4" clientid="371" name="Black Sheep" speed="20" premium="yes" />
<mount id="5" clientid="372" name="Midnight Panther" speed="20" premium="yes" />
<mount id="6" clientid="373" name="Draptor" speed="20" premium="yes" />
<mount id="7" clientid="374" name="Titanica" speed="20" premium="yes" />
<mount id="8" clientid="375" name="Tin Lizzard" speed="20" premium="yes" />
<mount id="9" clientid="376" name="Blazebringer" speed="20" premium="yes" />
<mount id="10" clientid="377" name="Rapid Boar" speed="20" premium="yes" />
<mount id="11" clientid="378" name="Stampor" speed="20" premium="yes" />
<mount id="12" clientid="379" name="Undead Cavebear" speed="20" premium="yes" />
<mount id="13" clientid="387" name="Donkey" speed="20" premium="yes" />
<mount id="14" clientid="388" name="Tiger Slug" speed="20" premium="yes" />
<mount id="15" clientid="389" name="Uniwheel" speed="20" premium="yes" />
<mount id="16" clientid="390" name="Crystal Wolf" speed="20" premium="yes" />
<mount id="17" clientid="392" name="War Horse" speed="20" premium="yes" />
<mount id="18" clientid="401" name="Kingly Deer" speed="20" premium="yes" />
<mount id="19" clientid="402" name="Tamed Panda" speed="20" premium="yes" />
<mount id="20" clientid="405" name="Dromedary" speed="20" premium="yes" />
<mount id="21" clientid="406" name="Scorpion King" speed="20" premium="yes" />
<mount id="22" clientid="421" name="Rented Horse" speed="20" premium="no" />
<mount id="23" clientid="426" name="Armoured War Horse" speed="20" premium="no" />
<mount id="24" clientid="427" name="Shadow Draptor" speed="20" premium="yes" />
<mount id="25" clientid="437" name="Rented Horse" speed="20" premium="no" />
<mount id="26" clientid="438" name="Rented Horse" speed="20" premium="no" />
<mount id="27" clientid="447" name="Lady Bug" speed="20" premium="yes" />
<mount id="28" clientid="450" name="Manta Ray" speed="20" premium="yes" />
<mount id="29" clientid="502" name="Ironblight" speed="20" premium="yes" />
<mount id="30" clientid="503" name="Magma Crawler" speed="20" premium="yes" />
<mount id="31" clientid="506" name="Dragonling" speed="20" premium="yes" />
<mount id="32" clientid="515" name="Gnarlhound" speed="20" premium="yes" />
<mount id="33" clientid="521" name="Crimson Ray" speed="20" premium="no" />
<mount id="34" clientid="522" name="Steelbeak" speed="20" premium="no" />
<mount id="35" clientid="526" name="Water Buffalo" speed="20" premium="yes" />
<mount id="36" clientid="546" name="Tombstinger" speed="20" premium="no" />
<mount id="37" clientid="547" name="Platesaurian" speed="20" premium="no" />
<mount id="38" clientid="548" name="Ursagrodon" speed="20" premium="yes" />
<mount id="39" clientid="559" name="The Hellgrip" speed="20" premium="yes" />
<mount id="40" clientid="571" name="Noble Lion" speed="20" premium="yes" />
<mount id="41" clientid="572" name="Desert King" speed="20" premium="no" />
<mount id="42" clientid="580" name="Shock Head" speed="20" premium="yes" />
<mount id="43" clientid="606" name="Walker" speed="20" premium="yes" />
<mount id="44" clientid="621" name="Azudocus" speed="20" premium="no" />
<mount id="45" clientid="622" name="Carpacosaurus" speed="20" premium="no" />
<mount id="46" clientid="624" name="Death Crawler" speed="20" premium="no" />
<mount id="47" clientid="626" name="Flamesteed" speed="20" premium="no" />
<mount id="48" clientid="627" name="Jade Lion" speed="20" premium="no" />
<mount id="49" clientid="628" name="Jade Pincer" speed="20" premium="no" />
<mount id="50" clientid="629" name="Nethersteed" speed="20" premium="no" />
<mount id="51" clientid="630" name="Tempest" speed="20" premium="no" />
<mount id="52" clientid="631" name="Winter King" speed="20" premium="no" />
<mount id="53" clientid="644" name="Doombringer" speed="20" premium="no" />
<mount id="54" clientid="647" name="Woodland Prince" speed="20" premium="no" />
<mount id="55" clientid="648" name="Hailstorm Fury" speed="20" premium="no" />
<mount id="56" clientid="649" name="Siegebreaker" speed="20" premium="no" />
<mount id="57" clientid="650" name="Poisonbane" speed="20" premium="no" />
<mount id="58" clientid="651" name="Blackpelt" speed="20" premium="no" />
<mount id="59" clientid="669" name="Golden Dragonfly" speed="20" premium="no" />
<mount id="60" clientid="670" name="Steel Bee" speed="20" premium="no" />
<mount id="61" clientid="671" name="Copper Fly" speed="20" premium="no" />
<mount id="62" clientid="672" name="Tundra Rambler" speed="20" premium="no" />
<mount id="63" clientid="673" name="Highland Yak" speed="20" premium="no" />
<mount id="64" clientid="674" name="Glacier Vagabond" speed="20" premium="no" />
<mount id="65" clientid="688" name="Flying Divan" speed="20" premium="no" />
<mount id="66" clientid="689" name="Magic Carpet" speed="20" premium="no" />
<mount id="67" clientid="690" name="Floating Kashmir" speed="20" premium="no" />
<mount id="68" clientid="691" name="Ringtail Waccoon" speed="20" premium="no" />
<mount id="69" clientid="692" name="Night Waccoon" speed="20" premium="no" />
<mount id="70" clientid="693" name="Emerald Waccoon" speed="20" premium="no" />
<mount id="71" clientid="682" name="Glooth Glider" speed="20" premium="yes" />
<mount id="72" clientid="685" name="Shadow Hart" speed="20" premium="no" />
<mount id="73" clientid="686" name="Black Stag" speed="20" premium="no" />
<mount id="74" clientid="687" name="Emperor Deer" speed="20" premium="no" />
<mount id="75" clientid="726" name="Flitterkatzen" speed="20" premium="no" />
<mount id="76" clientid="727" name="Venompaw" speed="20" premium="no" />
<mount id="77" clientid="728" name="Batcat" speed="20" premium="no" />
<mount id="78" clientid="734" name="Sea Devil" speed="20" premium="no" />
<mount id="79" clientid="735" name="Coralripper" speed="20" premium="no" />
<mount id="80" clientid="736" name="Plumfish" speed="20" premium="no" />
<mount id="81" clientid="738" name="Gorongra" speed="20" premium="no" />
<mount id="82" clientid="739" name="Noctungra" speed="20" premium="no" />
<mount id="83" clientid="740" name="Silverneck" speed="20" premium="no" />
<mount id="84" clientid="761" name="Slagsnare" speed="20" premium="no" />
<mount id="85" clientid="762" name="Nightstinger" speed="20" premium="no" />
<mount id="86" clientid="763" name="Razorcreep" speed="20" premium="no" />
<mount id="87" clientid="848" name="Rift Runner" speed="20" premium="yes" />
<mount id="88" clientid="849" name="Nightdweller" speed="20" premium="no" />
<mount id="89" clientid="850" name="Frostflare" speed="20" premium="no" />
<mount id="90" clientid="851" name="Cinderhoof" speed="20" premium="no" />
<mount id="91" clientid="868" name="Mouldpincer" speed="20" premium="no" />
<mount id="92" clientid="869" name="Bloodcurl" speed="20" premium="no" />
<mount id="93" clientid="870" name="Leafscuttler" speed="20" premium="no" />
<mount id="94" clientid="883" name="Sparkion" speed="20" premium="yes" />
<mount id="95" clientid="886" name="Swamp Snapper" speed="20" premium="no" />
<mount id="96" clientid="887" name="Mould Shell" speed="20" premium="no" />
<mount id="97" clientid="888" name="Reed Lurker" speed="20" premium="no" />
<mount id="98" clientid="889" name="Neon Sparkid" speed="20" premium="yes" />
<mount id="99" clientid="890" name="Vortexion" speed="20" premium="yes" />
<mount id="100" clientid="901" name="Ivory Fang" speed="20" premium="no" />
<mount id="101" clientid="902" name="Shadow Claw" speed="20" premium="no" />
<mount id="102" clientid="903" name="Snow Pelt" speed="20" premium="no" /> -->
</mounts>

4
data/XML/shaders.xml Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<shaders>
<shader id="1" name="Rainbow Outfit" premium="no" />
</shaders>

6
data/XML/wings.xml Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<wings>
<!-- <wing id="1" clientid="368" name="Widow Queen" speed="20" />
<wing id="2" clientid="369" name="Racing Bird" speed="20" />
<wing id="3" clientid="370" name="War Bear" speed="20" /> -->
</wings>

View File

@@ -16,4 +16,6 @@
<!-- Svargrond Arena: Killing a boss -->
<event type="kill" name="SvargrondArenaKill" script="arena_kill.lua" />
<event type="extendedopcode" name="Shop" script="shop.lua" />
</creaturescripts>

View File

@@ -324,6 +324,7 @@ function onLogin(player)
player:registerEvent("InquisitionUngreez")
player:registerEvent("InquisitionBosses")
player:registerEvent("SvargrondArenaKill")
player:registerEvent("Shop")
return true
end

View File

@@ -0,0 +1,362 @@
-- BETA VERSION, net tested yet
-- 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 = "https://s3.envato.com/files/62273611/PNG%20Blue/Banner%20blue%20468x60.png",
url = "http://otclient.ovh",
text = ""
}
local MAX_PACKET_SIZE = 50000
--[[ 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(6561):getId(),
count=1,
name="Items"
})
local category2 = addCategory({
type="outfit",
name="Outfits",
outfit={
mount=0,
feet=0,
legs=0,
body=0,
type=136,
auxType=0,
addons=0,
head=0,
rotating=false
}
})
local category3 = addCategory({
type="outfit",
name="Mounts",
outfit={
mount=0,
feet=0,
legs=0,
body=0,
type=368,
auxType=0,
addons=0,
head=0,
rotating=false
}
})
local category4 = addCategory({
type="item",
item=ItemType(5919):getId(),
count=1,
name="Addon Items"
})
local category5 = addCategory({
type="item",
item=ItemType(4835):getId(),
count=1,
name="Quest Items"
})
local category6 = addCategory({
type="item",
item=ItemType(3734):getId(),
count=1,
name="Decorations"
})
-- local category4 = addCategory({
-- type="image",
-- image="/data/images/game/states/electrified.png",
-- name="Category with local image"
-- })
category1.addItem(50, 6561, 1, "Ceremonial Ankh", "gives you all blessings")
category1.addItem(20, 5908, 1, "Obsidian Knife", "")
category1.addItem(200, 5797, 1, "Dice", "")
category1.addItem(100, 6549, 1, "Green Djinn Access", "The magical powder will bless you with the power to convince the green djinns")
category1.addItem(100, 6551, 1, "Blue Djinn Access", "The magical powder will bless you with the power to convince the blue djinns")
category1.addItem(100, 3252, 1, "Postman Access", "The magical horn will grant you the trustworthy postman rank")
category2.addOutfit(150, {
mount=0,
feet=0,
legs=0,
body=0,
type=162,
auxType=0,
addons=0,
head=0,
rotating=true
}, "Monk", "")
category3.addOutfit(75, {
mount=1,
feet=0,
legs=0,
body=0,
type=368,
auxType=0,
addons=0,
head=0,
rotating=true
}, "Widow Queen", "")
category4.addItem(150, 5919, 1, "Dragon Claw", "It is the claw of Demodras")
category5.addItem(30, 4835, 1, "Snake Destroyer", "")
category6.addItem(10, 3734, 10, "10x Blood Herb", "")
--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):getId(), -- 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 `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 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 `znote_accounts` set `points` = `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

View File

@@ -185,7 +185,8 @@ function Player:onLook(thing, position, distance)
if thing:isCreature() then
if thing:isPlayer() then
description = string.format("%s\nIP: %s.", description, Game.convertIpToString(thing:getIp()))
local client = thing:getClient()
description = string.format("%s\nIP: %s PING: %i FPS: %i.", description, Game.convertIpToString(thing:getIp()), client.ping, client.fps)
end
end
end
@@ -208,7 +209,8 @@ function Player:onLookInBattleList(creature, distance)
)
if creature:isPlayer() then
description = string.format("%s\nIP: %s", description, Game.convertIpToString(creature:getIp()))
local client = thing:getClient()
description = string.format("%s\nIP: %s PING: %i FPS: %i.", description, Game.convertIpToString(thing:getIp()), client.ping, client.fps)
end
end
self:sendTextMessage(MESSAGE_INFO_DESCR, description)

View File

@@ -11,4 +11,5 @@ dofile('data/lib/core/teleport.lua')
dofile('data/lib/core/tile.lua')
dofile('data/lib/core/vocation.lua')
dofile('data/lib/core/guildwars.lua')
dofile('data/lib/core/svargrondArenaQuest.lua')
dofile('data/lib/core/svargrondArenaQuest.lua')
dofile('data/lib/core/json.lua')

399
data/lib/core/json.lua Normal file
View File

@@ -0,0 +1,399 @@
-- add to lib/core, later add dofile in lib/core/core.lua
--
-- json.lua
--
-- Copyright (c) 2018 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
json = { _version = "0.1.1" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\\\",
[ "\"" ] = "\\\"",
[ "\b" ] = "\\b",
[ "\f" ] = "\\f",
[ "\n" ] = "\\n",
[ "\r" ] = "\\r",
[ "\t" ] = "\\t",
}
local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return escape_char_map[c] or string.format("\\u%04x", c:byte())
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if val[1] ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(3, 6), 16 )
local n2 = tonumber( s:sub(9, 12), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local has_unicode_escape = false
local has_surrogate_escape = false
local has_escape = false
local last
for j = i + 1, #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
end
if last == 92 then -- "\\" (escape char)
if x == 117 then -- "u" (unicode escape sequence)
local hex = str:sub(j + 1, j + 5)
if not hex:find("%x%x%x%x") then
decode_error(str, j, "invalid unicode escape in string")
end
if hex:find("^[dD][89aAbB]") then
has_surrogate_escape = true
else
has_unicode_escape = true
end
else
local c = string.char(x)
if not escape_chars[c] then
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
end
has_escape = true
end
last = nil
elseif x == 34 then -- '"' (end of string)
local s = str:sub(i + 1, j - 1)
if has_surrogate_escape then
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
end
if has_unicode_escape then
s = s:gsub("\\u....", parse_unicode_escape)
end
if has_escape then
s = s:gsub("\\.", escape_char_map_inv)
end
return s, j + 1
else
last = x
end
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end

View File

@@ -25,7 +25,17 @@ local reloadTypes = {
["monster"] = { targetType = RELOAD_TYPE_MONSTERS, name = "monsters" },
["monsters"] = { targetType = RELOAD_TYPE_MONSTERS, name = "monsters" },
["mount"] = RELOAD_TYPE_MOUNTS,
["aura"] = RELOAD_TYPE_AURAS,
["auras"] = RELOAD_TYPE_AURAS,
["wing"] = RELOAD_TYPE_WINGS,
["wings"] = RELOAD_TYPE_WINGS,
["shader"] = RELOAD_TYPE_SHADERS,
["shaders"] = RELOAD_TYPE_SHADERS,
["move"] = { targetType = RELOAD_TYPE_MOVEMENTS, name = "movements" },
["movement"] = { targetType = RELOAD_TYPE_MOVEMENTS, name = "movements" },
["movements"] = { targetType = RELOAD_TYPE_MOVEMENTS, name = "movements" },