summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Pickering <alexandermpickering@gmail.com>2016-05-21 19:29:52 -0400
committerAlexander Pickering <alexandermpickering@gmail.com>2016-05-21 19:29:52 -0400
commit6f25e16528f9f035c784055304b9f07c59f6782d (patch)
tree1d3bc0a7959a185b82dc01dd24b800850cc11783
parentdc64170e1c5fe08513ca46afa8224d4e9d95753b (diff)
downloadgmstranded-6f25e16528f9f035c784055304b9f07c59f6782d.tar.gz
gmstranded-6f25e16528f9f035c784055304b9f07c59f6782d.tar.bz2
gmstranded-6f25e16528f9f035c784055304b9f07c59f6782d.zip
First pass at createing a databaseing system
-rw-r--r--gamemode/configure_me.lua13
-rw-r--r--gamemode/server/database.lua106
-rw-r--r--gamemode/server/glon.lua365
-rw-r--r--gamemode/server/pon.lua361
-rw-r--r--gamemode/server/von.lua801
5 files changed, 1588 insertions, 58 deletions
diff --git a/gamemode/configure_me.lua b/gamemode/configure_me.lua
index def6e00..4c007f7 100644
--- a/gamemode/configure_me.lua
+++ b/gamemode/configure_me.lua
@@ -5,6 +5,12 @@
Additional content by
Djarex, Scott, Apickx
+
+ Bits and peices for saveing:
+ GLON:Deco Da Man
+ vON:Alexandru-Mihai "Vercas" Maftei
+ pON:thelastpenguin™
+
*/
//Make sure that this is the gamemode folder name
@@ -69,3 +75,10 @@ GMS.AllowedStools = {
"colour",
"material",
}
+
+//Configuration for how data is saved
+//Can be: "GLON", "VON"(CURRENTLY BROKEN), "PON", "JSON"
+GAMEMODE.Compressor = "PON"
+
+//Can be: "File:Text", "File:Compressed"
+GAMEMODE.StorageMethod = "File:Compressed"
diff --git a/gamemode/server/database.lua b/gamemode/server/database.lua
index 20efafc..9f8608d 100644
--- a/gamemode/server/database.lua
+++ b/gamemode/server/database.lua
@@ -2,84 +2,74 @@
--[[
Uses:
GM.StorageMethod
- Can be: "Text", "Static Map", "LZWCompressed"
- If method is "Static Map", then GM.StoreageStaticMap must be a table of strings to string ID's.
- GM.StorageBackend
- Can be: "File"
+ Can be: "File:Text", "File:Compressed"
+ GM.Compressor
+ Can be: "GLON", "VON", "PON", "JSON"
GM.StorageDebug
Can be: true, false
Enables extra debugging to find errors easily
]]
+--[[
+Provides:
+ loadTable(uniqueID)
+ loads a table with the given unique id, and returns it
+ storeTable(uniqueID, table)
+ stores the given table into the database, needs a uniqueid to find it again.
+]]
print("Hello from database.lua!")
+GAMEMODE = GAMEMODE or {}
function loadTable(uniqueIdentifier)
-
- return nil
+ local rfile = file.Open("gmstranded/" .. uniqueIdentifier .. ".txt", "r", "DATA")
+ local text = rfile:Read(rfile:Size())
+ if(GAMEMODE.StorageMethod == "File:Compressed") then
+ text = util.Decompress(text)
+ end
+ return convertTextTable(text)
end
function storeTable(uniqueIdentifier, table)
- if (GM.StorageDebug) then
- print("Asked to store:")
- PrintTable(table)
+ local storetext = convertTableText(table)
+ if(GAMEMODE.StorageMethod == "File:Compressed") then
+ storetext = util.Compress(storetext)
end
- if (GM.StorageDebug) then
- for k,v in pairs(tbl) do
- if (isfunction(v)) then
- print("Database.lua error: tried to save a table that has a function in it!")
- PrintTable(tbl)
- print("At field:")
- print(k)
- end
- end
+ if !(file.IsDir("gmstranded","DATA")) then
+ file.CreateDir("gmstranded")
end
-
+ local tfile = file.Open("gmstranded/" .. uniqueIdentifier .. ".txt", "w","DATA")
+ tfile:Write(storetext)
+ tfile:Flush()
+ tfile:Close()
end
---Remove symbols that might be used as part of the storage scheme
-local function encodeSymbolic(mstring)
- local replacements = {
- {"\\", "\\\\"},
- {"{","\\{"},
- {"}","\\}"},
- {"(","\\("},
- {")","\\)"},
- }
- local ostring = mstring
- for k,v in pairs(replacements) do
- ostring = string.Replace(ostring,v[1],v[2])
+function convertTableText(tbl)
+ if(GAMEMODE.Compressor == "GLON") then
+ return glon.encode(tbl)
+ elseif(GAMEMODE.Compressor == "VON") then
+ return von.serialize(tbl)
+ elseif(GAMEMODE.Compressor == "PON") then
+ return pon.encode(tbl)
+ elseif(GAMEMODE.Compressor == "JSON") then
+ return util.TableToJSON(tbl)
+ else
+ error("GM.Compressor is not set correctly! It is:" .. GM.Compressor .. " but should be one of:GLON,VON,PON,JSON")
end
end
-local function convertTableText(tbl)
- local output = "t("
- local function addtoout(k)
- if (istable(k)) then
- output = output .. convertTableText(k)
- elseif (isstring(k)) then
- output = output .. "s(" .. encodeSymbolic(k) .. ")"
- elseif (isnumber(k)) then
- output = output .. "n(" .. k .. ")"
- elseif (isbool(k)) then
- if (k) then output = output .. "b(true)"
- else output = output .. "b(false)" end
- elseif (isangle(k)) then
- output = output .. "a(" .. k.p .. "," .. k.y .. "," .. k.r .. ")"
- elseif (isvector(k)) then
- output = output .. "v(" .. k.x .. "," .. k.y .. "," .. k.z .. ")"
- else
- assert(true,"Tried to store unknown value type:")
- printi(k)
- end
- end
- for k,v in pairs(tbl) do
- addtoout(k)
- output = output .. ":"
- addtoout(v)
+function convertTextTable(text)
+ if(GAMEMODE.Compressor == "GLON") then
+ return glon.decode(text)
+ elseif(GAMEMODE.Compressor == "VON") then
+ return von.deserialize(text)
+ elseif(GAMEMODE.Compressor == "PON") then
+ return pon.decode(text)
+ elseif(GAMEMODE.Compressor == "JSON") then
+ return util.JSONToTable(text)
+ else
+ error("GM.Compressor is not set correctly! It is:" .. GM.Compressor .. " but should be one of:GLON,VON,PON,JSON")
end
- output = output .. ")"
end
-
--[[
local function convertTableStatic(tbl)
if (GM.StorageStaticMap == nil) then
diff --git a/gamemode/server/glon.lua b/gamemode/server/glon.lua
new file mode 100644
index 0000000..ccc7376
--- /dev/null
+++ b/gamemode/server/glon.lua
@@ -0,0 +1,365 @@
+-- GLON: Garry's Mod Lua Object Notation
+-- A dialect of LON: Lua Object Notation
+-- Made entirely by Deco Da Man
+-- Types:
+ -- 2: table
+ -- 3: array
+ -- 4: fasle boolean
+ -- 5: true boolean
+ -- 6: number (NOT COMPRESSED, it isn't worth it)
+ -- 7: string
+ ---- non-LON types start here!
+ -- 8: Vector (NOT COMPRESSED, it isn't worth it)
+ -- 9: Angle (NOT COMPRESSED, it isn't worth it)
+ -- 10: Entity (Can do players, vehicles, npcs, weapons and any other type of entity (-1 for null entity))
+ -- 11: Player (By UserID)
+ -- 12: CEffectData
+ -- 13: ConVar (Not ClientConVar)
+ -- 15: Color
+ -- 255: reference (Sends the ID of the table to use (for "local t = {} t.a=t"))
+local pairs = pairs
+local type = type
+local string = string
+local math = math
+local tostring = tostring
+local ValidEntity = ValidEntity
+local error = error
+local print = print
+local setmetatable = setmetatable
+local Vector = Vector
+local Angle = Angle
+local Entity = Entity
+local EffectData = EffectData
+local GetConVar = GetConVar
+local tonumber = tonumber
+local player = player
+module("glon")
+local idcount = {}
+local encode_types
+local decode_types
+local function InDataEscape(s)
+ return string.gsub(string.gsub(s, "([\1\2])", "\2%1"), "%z", "\2\3")
+end
+encode_types = {
+ ["nil"] = {nil, function()
+ return "", nil
+ end},
+ table = {2, function(o, rtabs)
+ for k,v in pairs(rtabs) do
+ if v == o then
+ return tostring( k ) .. "\1", 255
+ end
+ end
+ rtabs[#rtabs + 1] = o
+ local is_array = true
+ local i = 0
+ for k,v in pairs(o) do
+ i = i + 1
+ if k ~= i or type(k) ~= "number" or math.floor(k) ~= k then
+ is_array = false
+ break end
+ end
+ local s = ""
+ for k,v in pairs(o) do
+ if not is_array then
+ s = s .. Write(k, rtabs)
+ end
+ s = s .. Write(v, rtabs)
+ end
+ return s .. "\1", is_array and 3
+ end},
+ boolean = {4, function(o)
+ return "", o and 5
+ end},
+ number = {6, function(o)
+ o = o == 0 and "" or o
+ return tostring(o) .. "\1"
+ end},
+ string = {7, function(o)
+ return InDataEscape(o) .. "\1"
+ end},
+ -- non-LON types start here!
+ Vector = {8, function(o)
+ return o.x .. "\1" .. o.y .. "\1" .. o.z .. "\1"
+ end},
+ Angle = {9, function(o)
+ return o.p .. "\1" .. o.y .. "\1" .. o.r .. "\1"
+ end},
+ Entity = {10, function(o)
+ return (ValidEntity(o) and o:EntIndex() or -1) .. "\1"
+ end},
+ Player = {11, function(o)
+ return o:EntIndex() .. "\1"
+ end},
+ CEffectData = {12, function(o, rtabs)
+ local t = {}
+ if o:GetAngle() ~= Angle(0,0,0) then
+ t.a = o:GetAngle()
+ end
+ if o:GetAttachment() ~= 0 then
+ t.h = o:GetAttachment()
+ end
+ if o:GetEntity():IsValid() then
+ t.e = o:GetEntity()
+ end
+ if o:GetMagnitude() ~= 0 then
+ t.m = o:GetMagnitude()
+ end
+ if o:GetNormal() ~= Vector(0,0,0) then
+ t.n = o:GetNormal()
+ end
+ if o:GetOrigin() ~= Vector(0,0,0) then
+ t.o = o:GetOrigin()
+ end
+ if o:GetRadius() ~= 0 then
+ t.r = o:GetRadius()
+ end
+ if o:GetScale() ~= 0 then
+ t.c = o:GetScale()
+ end
+ if o:GetStart() ~= 0 then
+ t.s = o:GetStart()
+ end
+ if o:GetSurfaceProp() ~= 0 then
+ t.p = o:GetSurfaceProp()
+ end
+ return encode_types.table[2](t, rtabs)
+ end},
+ ConVar = {13, function(o)
+ return InDataEscape(o:GetName()) .. "\1"
+ end},
+ PhysObj = {14, function(o)
+ local parent, obj, id = o:GetEntity()
+ for i = 1, parent:GetPhysicsObjectCount() do
+ obj = parent:GetPhysicsObjectNum()
+ if obj == o then
+ id = i
+ break end
+ end
+ return parent:EntIndex() .. "\1" .. id .. "\1"
+ end},
+ Color = {15, function(o)
+ return o.r .. "\1" .. o.g .. "\1" .. o.b .. "\1" .. o.a .. "\1"
+ end},
+}
+function Write(data, rtabs)
+ local t = encode_types[type(data)]
+ if t then
+ local data, id_override = t[2](data, rtabs)
+ local char = id_override or t[1] or ""
+ if char ~= "" then char = string.char(char) end
+ return char .. (data or "")
+ else
+ error(string.format("Tried to write unwriteable type: %s",
+ type(data)))
+ end
+end
+local CEffectDataTranslation = {
+ a = "Angle",
+ h = "Attachment",
+ e = "Entity",
+ m = "Magnitude",
+ n = "Normal",
+ o = "Origin",
+ r = "Radius",
+ c = "Scale",
+ s = "Start",
+ p = "SurfaceProp",
+}
+decode_types = {
+ -- \2\6omg\1\6omgavalue\1\1
+ [2 ] = function(reader, rtabs) -- table
+ local t, c, pos = {}, reader:Next()
+ rtabs[#rtabs + 1] = t
+ local stage = false
+ local key
+ while true do
+ c, pos = reader:Peek()
+ if c == "\1" then
+ if stage then
+ error(string.format("Expected value to match key at %s! (Got EO Table)",
+ pos))
+ else
+ reader:Next()
+ return t
+ end
+ else
+ if stage then
+ t[key] = Read(reader, rtabs)
+ else
+ key = Read(reader, rtabs)
+ end
+ stage = not stage
+ end
+ end
+ end,
+ [3] = function(reader, rtabs) -- array
+ local t, i, c, pos = {}, 1, reader:Next()
+ rtabs[#rtabs + 1] = t
+ while true do
+ c, pos = reader:Peek()
+ if c == "\1" then
+ reader:Next()
+ return t
+ else
+ t[i] = Read(reader, rtabs)
+ i = i + 1
+ end
+ end
+ end,
+ [4 ] = function(reader) -- false boolean
+ reader:Next()
+ return false
+ end,
+ [5 ] = function(reader) -- true boolean
+ reader:Next()
+ return true
+ end,
+ [6 ] = function(reader) -- number
+ local s, c, pos, e = "", reader:Next()
+ while true do
+ c = reader:Next()
+ if not c then
+ error(string.format("Expected \1 to end number at %s! (Got EOF!)",
+ pos))
+ elseif c == "\1" then
+ break
+ else
+ s = s .. c
+ end
+ end
+ if s == "" then s = "0" end
+ local n = tonumber(s)
+ if not n then
+ error(string.format("Invalid number at %s! (%q)",
+ pos, s))
+ end
+ return n
+ end,
+ [7 ] = function(reader) -- string
+ local s, c, pos, e = "", reader:Next()
+ while true do
+ c = reader:Next()
+ if not c then
+ error(string.format("Expected unescaped \1 to end string at position %s! (Got EOF)",
+ pos))
+ elseif e then
+ if c == "\3" then
+ s = s .. "\0"
+ else
+ s = s .. c
+ end
+ e = false
+ elseif c == "\2" then
+ e = true
+ elseif c == "\1" then
+ return s
+ else
+ s = s .. c
+ end
+ end
+ end,
+ [8 ] = function(reader) -- Vector
+ local x = decode_types[6](reader)
+ reader:StepBack()
+ local y = decode_types[6](reader)
+ reader:StepBack()
+ local z = decode_types[6](reader)
+ return Vector(x, y, z)
+ end,
+ [9 ] = function(reader) -- Angle
+ local p = decode_types[6](reader)
+ reader:StepBack()
+ local y = decode_types[6](reader)
+ reader:StepBack()
+ local r = decode_types[6](reader)
+ return Angle(p, y, r)
+ end,
+ [10 ] = function(reader) -- Entity
+ return Entity(decode_types[6](reader))
+ end,
+ [11 ] = function(reader) -- Player
+ local num = decode_types[6](reader)
+ return player.GetByID(num)
+ end,
+ [12 ] = function(reader, rtabs) -- CEffectData
+ local t = decode_types[2](reader, rtabs)
+ local d = EffectData()
+ for k,v in pairs(t) do
+ d["Set" .. CEffectDataTranslation[k]](d, v)
+ end
+ return d
+ end,
+ [13 ] = function(reader) -- ConVar
+ return GetConVar(decode_types[7](reader))
+ end,
+ [15 ] = function(reader) -- Color
+ local r = decode_types[6](reader)
+ reader:StepBack()
+ local g = decode_types[6](reader)
+ reader:StepBack()
+ local b = decode_types[6](reader)
+ reader:StepBack()
+ local a = decode_types[6](reader)
+ return Color(r, g, b, a)
+ end,
+ [255] = function(reader, rtabs) -- Reference
+ return rtabs[decode_types[6](reader) - 1]
+ end
+}
+function Read(reader, rtabs)
+ local t, pos = reader:Peek()
+ if not t then
+ error(string.format("Expected type ID at %s! (Got EOF)",
+ pos))
+ else
+ local dt = decode_types[string.byte(t)]
+ if not dt then
+ error(string.format("Unknown type ID, %s!",
+ string.byte(t)))
+ else
+ return dt(reader, rtabs or {0})
+ end
+ end
+end
+local reader_meta = {}
+reader_meta.__index = reader_meta
+function reader_meta:Next()
+ self.i = self.i + 1
+ self.c = string.sub(self.s, self.i, self.i)
+ if self.c == "" then self.c = nil end
+ self.p = string.sub(self.s, self.i + 1, self.i + 1)
+ if self.p == "" then self.p = nil end
+ return self.c, self.i
+end
+function reader_meta:StepBack()
+ self.i = self.i-1
+ self.c = string.sub(self.s, self.i, self.i)
+ if self.c == "" then self.c = nil end
+ self.p = string.sub(self.s, self.i + 1, self.i + 1)
+ if self.p == "" then self.p = nil end
+ return self.c, self.i
+end
+function reader_meta:Peek()
+ return self.p, self.i + 1
+end
+function decode(data)
+ if type(data) == "nil" then
+ return nil
+ elseif type(data) ~= "string" then
+ error(string.format("Expected string to decode! (Got type %s)",
+ type(data)
+ ))
+ elseif data:len() == 0 then
+ return nil
+ end
+ return Read(setmetatable({
+ s = data,
+ i = 0,
+ c = string.sub(data, 0, 0),
+ p = string.sub(data, 1, 1),
+ }, reader_meta), {})
+end
+function encode(data)
+ return Write(data, {0}) -- to use the first key, to prevent it from interfereing with \1s. You can have up to 254 unique tables (including arrays)
+end
diff --git a/gamemode/server/pon.lua b/gamemode/server/pon.lua
new file mode 100644
index 0000000..3c48a90
--- /dev/null
+++ b/gamemode/server/pon.lua
@@ -0,0 +1,361 @@
+--[[
+RECOMMENDED VERSION
+VERSION 1.2.1
+Copyright thelastpenguin™
+ You may use this for any purpose as long as:
+ - You don't remove this copyright notice.
+ - You don't claim this to be your own.
+ - You properly credit the author, thelastpenguin™, if you publish your work based on (and/or using) this.
+
+ If you modify the code for any purpose, the above still applies to the modified code.
+
+ The author is not held responsible for any d amages incured from the use of pON, you use it at your own risk.
+DATA TYPES SUPPORTED:
+ - tables - k,v - pointers
+ - strings - k,v - pointers
+ - numbers - k,v
+ - booleans- k,v
+ - Vectors - k,v
+ - Angles - k,v
+ - Entities- k,v
+ - Players - k,v
+
+CHANGE LOG
+V 1.1.0
+ - Added Vehicle, NPC, NextBot, Player, Weapon
+V 1.2.0
+ - Added custom handling for k,v tables without any array component.
+V 1.2.1
+ - fixed deserialization bug.
+V 1.3.0
+ - added custom handling of strings without any escaped characters.
+V 1.4.0
+ - added detection of numbers without requiring 'n' datatype. (10 datatypes one for each num it could start with)
+
+THANKS TO...
+ - VERCAS for the inspiration.
+]]
+
+
+local pon = {};
+_G.pon = pon;
+
+local type, count = type, table.Count ;
+local tonumber = tonumber ;
+
+do
+ local encode = {};
+
+ local tryCache ;
+
+ local cacheSize = 0;
+
+ encode['table'] = function( self, tbl, output, cache )
+
+ if( cache[ tbl ] )then
+ output[ #output + 1 ] = '('..cache[tbl]..')';
+ return ;
+ else
+ cacheSize = cacheSize + 1;
+ cache[ tbl ] = cacheSize;
+ end
+ -- CALCULATE COMPONENT SIZES
+ local nSize = #tbl;
+ local kvSize = count( tbl ) - nSize;
+
+ if( nSize == 0 and kvSize > 0 )then
+ output[ #output + 1 ] = '[';
+ else
+ output[ #output + 1 ] = '{';
+
+ if nSize > 0 then
+ for i = 1, nSize do
+ local v = tbl[ i ];
+ if v == nil then continue end
+ local tv = type( v );
+ -- HANDLE POINTERS
+ if( tv == 'string' )then
+ local pid = cache[ v ];
+ if( pid )then
+ output[ #output + 1 ] = '('..pid..')';
+ else
+ cacheSize = cacheSize + 1;
+ cache[ v ] = cacheSize;
+
+ self.string( self, v, output, cache );
+ end
+ else
+ self[ tv ]( self, v, output, cache );
+ end
+ end
+ end
+ end
+
+ if( kvSize > 0 )then
+ if( nSize > 0 )then
+ output[ #output + 1 ] = '~';
+ end
+ for k,v in next, tbl do
+ if( type( k ) ~= 'number' or k < 1 or k > nSize )then
+ local tk, tv = type( k ), type( v );
+
+ -- THE KEY
+ if( tk == 'string' )then
+ local pid = cache[ k ];
+ if( pid )then
+ output[ #output + 1 ] = '('..pid..')';
+ else
+ cacheSize = cacheSize + 1;
+ cache[ k ] = cacheSize;
+
+ self.string( self, k, output, cache );
+ end
+ else
+ self[ tk ]( self, k, output, cache );
+ end
+
+ -- THE VALUE
+ if( tv == 'string' )then
+ local pid = cache[ v ];
+ if( pid )then
+ output[ #output + 1 ] = '('..pid..')';
+ else
+ cacheSize = cacheSize + 1;
+ cache[ v ] = cacheSize;
+
+ self.string( self, v, output, cache );
+ end
+ else
+ self[ tv ]( self, v, output, cache );
+ end
+
+ end
+ end
+ end
+ output[ #output + 1 ] = '}';
+ end
+ -- ENCODE STRING
+ local gsub = string.gsub ;
+ encode['string'] = function( self, str, output )
+ --if tryCache( str, output ) then return end
+ local estr, count = gsub( str, ";", "\\;");
+ if( count == 0 )then
+ output[ #output + 1 ] = '\''..str..';';
+ else
+ output[ #output + 1 ] = '"'..estr..'";';
+ end
+ end
+ -- ENCODE NUMBER
+ encode['number'] = function( self, num, output )
+ output[ #output + 1 ] = tonumber( num )..';';
+ end
+ -- ENCODE BOOLEAN
+ encode['boolean'] = function( self, val, output )
+ output[ #output + 1 ] = val and 't' or 'f'
+ end
+ -- ENCODE VECTOR
+ encode['Vector'] = function( self, val, output )
+ output[ #output + 1 ] = ('v'..val.x..','..val.y)..(','..val.z..';');
+ end
+ -- ENCODE ANGLE
+ encode['Angle'] = function( self, val, output )
+ output[ #output + 1 ] = ('a'..val.p..','..val.y)..(','..val.r..';');
+ end
+ encode['Entity'] = function( self, val, output )
+ output[ #output + 1] = 'E'..(IsValid( val ) and (val:EntIndex( )..';') or '#');
+ end
+ encode['Player'] = encode['Entity'];
+ encode['Vehicle'] = encode['Entity'];
+ encode['Weapon'] = encode['Entity'];
+ encode['NPC'] = encode['Entity'];
+ encode['NextBot'] = encode['Entity'];
+
+ do
+ local empty, concat = table.Empty, table.concat ;
+ function pon.encode( tbl )
+ local output = {};
+ cacheSize = 0;
+ encode[ 'table' ]( encode, tbl, output, {} );
+ local res = concat( output );
+
+ return res;
+ end
+ end
+end
+
+do
+ local tonumber = tonumber ;
+ local find, sub, gsub, Explode = string.find, string.sub, string.gsub, string.Explode ;
+ local Vector, Angle, Entity = Vector, Angle, Entity ;
+
+ local decode = {};
+ decode['{'] = function( self, index, str, cache )
+
+ local cur = {};
+ cache[ #cache + 1 ] = cur;
+
+ local k, v, tk, tv = 1, nil, nil, nil;
+ while( true )do
+ tv = sub( str, index, index );
+ if( not tv or tv == '~' )then
+ index = index + 1;
+ break ;
+ end
+ if( tv == '}' )then
+ return index + 1, cur;
+ end
+
+ -- READ THE VALUE
+ index = index + 1;
+ index, v = self[ tv ]( self, index, str, cache );
+ cur[ k ] = v;
+
+ k = k + 1;
+ end
+
+ while( true )do
+ tk = sub( str, index, index );
+ if( not tk or tk == '}' )then
+ index = index + 1;
+ break ;
+ end
+
+ -- READ THE KEY
+
+ index = index + 1;
+ index, k = self[ tk ]( self, index, str, cache );
+
+ -- READ THE VALUE
+ tv = sub( str, index, index );
+ index = index + 1;
+ index, v = self[ tv ]( self, index, str, cache );
+
+ cur[ k ] = v;
+ end
+
+ return index, cur;
+ end
+ decode['['] = function( self, index, str, cache )
+
+ local cur = {};
+ cache[ #cache + 1 ] = cur;
+
+ local k, v, tk, tv = 1, nil, nil, nil;
+ while( true )do
+ tk = sub( str, index, index );
+ if( not tk or tk == '}' )then
+ index = index + 1;
+ break ;
+ end
+
+ -- READ THE KEY
+
+ index = index + 1;
+ index, k = self[ tk ]( self, index, str, cache );
+ -- READ THE VALUE
+ tv = sub( str, index, index );
+ index = index + 1;
+ index, v = self[ tv ]( self, index, str, cache );
+
+ cur[ k ] = v;
+ end
+
+ return index, cur;
+ end
+
+ -- STRING
+ decode['"'] = function( self, index, str, cache )
+ local finish = find( str, '";', index, true );
+ local res = gsub( sub( str, index, finish - 1 ), '\\;', ';' );
+ index = finish + 2;
+
+ cache[ #cache + 1 ] = res;
+ return index, res;
+ end
+ -- STRING NO ESCAPING NEEDED
+ decode['\''] = function( self, index, str, cache )
+ local finish = find( str, ';', index, true );
+ local res = sub( str, index, finish - 1 )
+ index = finish + 1;
+
+ cache[ #cache + 1 ] = res;
+ return index, res;
+ end
+
+ -- NUMBER
+ decode['n'] = function( self, index, str, cache )
+ index = index - 1;
+ local finish = find( str, ';', index, true );
+ local num = tonumber( sub( str, index, finish - 1 ) );
+ index = finish + 1;
+ return index, num;
+ end
+ decode['0'] = decode['n'];
+ decode['1'] = decode['n'];
+ decode['2'] = decode['n'];
+ decode['3'] = decode['n'];
+ decode['4'] = decode['n'];
+ decode['5'] = decode['n'];
+ decode['6'] = decode['n'];
+ decode['7'] = decode['n'];
+ decode['8'] = decode['n'];
+ decode['9'] = decode['n'];
+ decode['-'] = decode['n'];
+
+ -- POINTER
+ decode['('] = function( self, index, str, cache )
+ local finish = find( str, ')', index, true );
+ local num = tonumber( sub( str, index, finish - 1 ) );
+ index = finish + 1;
+ return index, cache[ num ];
+ end
+
+ -- BOOLEAN. ONE DATA TYPE FOR YES, ANOTHER FOR NO.
+ decode[ 't' ] = function( self, index )
+ return index, true;
+ end
+ decode[ 'f' ] = function( self, index )
+ return index, false;
+ end
+
+ -- VECTOR
+ decode[ 'v' ] = function( self, index, str, cache )
+ local finish = find( str, ';', index, true );
+ local vecStr = sub( str, index, finish - 1 );
+ index = finish + 1; -- update the index.
+ local segs = Explode( ',', vecStr, false );
+ return index, Vector( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) );
+ end
+ -- ANGLE
+ decode[ 'a' ] = function( self, index, str, cache )
+ local finish = find( str, ';', index, true );
+ local angStr = sub( str, index, finish - 1 );
+ index = finish + 1; -- update the index.
+ local segs = Explode( ',', angStr, false );
+ return index, Angle( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) );
+ end
+ -- ENTITY
+ decode[ 'E' ] = function( self, index, str, cache )
+ if( str[index] == '#' )then
+ index = index + 1;
+ return NULL ;
+ else
+ local finish = find( str, ';', index, true );
+ local num = tonumber( sub( str, index, finish - 1 ) );
+ index = finish + 1;
+ return index, Entity( num );
+ end
+ end
+ -- PLAYER
+ decode[ 'P' ] = function( self, index, str, cache )
+ local finish = find( str, ';', index, true );
+ local num = tonumber( sub( str, index, finish - 1 ) );
+ index = finish + 1;
+ return index, Entity( num ) or NULL;
+ end
+
+ function pon.decode( data )
+ local _, res = decode[sub(data,1,1)]( decode, 2, data, {});
+ return res;
+ end
+end
diff --git a/gamemode/server/von.lua b/gamemode/server/von.lua
new file mode 100644
index 0000000..9d118c0
--- /dev/null
+++ b/gamemode/server/von.lua
@@ -0,0 +1,801 @@
+--[[ vON 1.3.4
+ Copyright 2012-2014 Alexandru-Mihai Maftei
+ aka Vercas
+ GitHub Repository:
+ https://github.com/vercas/vON
+ You may use this for any purpose as long as:
+ - You don't remove this copyright notice.
+ - You don't claim this to be your own.
+ - You properly credit the author (Vercas) if you publish your work based on (and/or using) this.
+ If you modify the code for any purpose, the above obligations still apply.
+ If you make any interesting modifications, try forking the GitHub repository instead.
+ Instead of copying this code over for sharing, rather use the link:
+ https://github.com/vercas/vON/blob/master/von.lua
+ The author may not be held responsible for any damage or losses directly or indirectly caused by
+ the use of vON.
+ If you disagree with the above, don't use the code.
+-----------------------------------------------------------------------------------------------------------------------------
+
+ Thanks to the following people for their contribution:
+ - Divran Suggested improvements for making the code quicker.
+ Suggested an excellent new way of deserializing strings.
+ Lead me to finding an extreme flaw in string parsing.
+ - pennerlord Provided some performance tests to help me improve the code.
+ - Chessnut Reported bug with handling of nil values when deserializing array components.
+ - People who contributed on the GitHub repository by reporting bugs, posting fixes, etc.
+-----------------------------------------------------------------------------------------------------------------------------
+
+ The vanilla types supported in this release of vON are:
+ - table
+ - number
+ - boolean
+ - string
+ - nil
+ The Garry's Mod-specific types supported in this release are:
+ - Vector
+ - Angle
+ + Entities:
+ - Entity
+ - Vehicle
+ - Weapon
+ - NPC
+ - Player
+ - NextBot
+ These are the types one would normally serialize.
+-----------------------------------------------------------------------------------------------------------------------------
+
+ New in this version:
+ - Fixed addition of extra entity types. I messed up really badly.
+--]]
+
+
+
+local _deserialize, _serialize, _d_meta, _s_meta, d_findVariable, s_anyVariable
+local sub, gsub, find, insert, concat, error, tonumber, tostring, type, next = string.sub, string.gsub, string.find, table.insert, table.concat, error, tonumber, tostring, type, next
+
+
+
+--[[ This section contains localized functions which (de)serialize
+ variables according to the types found. ]]
+
+
+
+-- This is kept away from the table for speed.
+function d_findVariable(s, i, len, lastType, jobstate)
+ local i, c, typeRead, val = i or 1
+
+ -- Keep looping through the string.
+ while true do
+ -- Stop at the end. Throw an error. This function MUST NOT meet the end!
+ if i > len then
+ error("vON: Reached end of string, cannot form proper variable.")
+ end
+
+ -- Cache the character. Nobody wants to look for the same character ten times.
+ c = sub(s, i, i)
+
+ -- If it just read a type definition, then a variable HAS to come after it.
+ if typeRead then
+ -- Attempt to deserialize a variable of the freshly read type.
+ val, i = _deserialize[lastType](s, i, len, false, jobstate)
+ -- Return the value read, the index of the last processed character, and the type of the last read variable.
+ return val, i, lastType
+
+ -- @ means nil. It should not even appear in the output string of the serializer. Nils are useless to store.
+ elseif c == "@" then
+ return nil, i, lastType
+
+ -- $ means a table reference will follow - a number basically.
+ elseif c == "$" then
+ lastType = "table_reference"
+ typeRead = true
+
+ -- n means a number will follow. Base 10... :C
+ elseif c == "n" then
+ lastType = "number"
+ typeRead = true
+
+ -- b means boolean flags.
+ elseif c == "b" then
+ lastType = "boolean"
+ typeRead = true
+
+ -- ' means the start of a string.
+ elseif c == "'" then
+ lastType = "string"
+ typeRead = true
+
+ -- " means the start of a string prior to version 1.2.0.
+ elseif c == "\"" then
+ lastType = "oldstring"
+ typeRead = true
+
+ -- { means the start of a table!
+ elseif c == "{" then
+ lastType = "table"
+ typeRead = true
+
+
+--[[ Garry's Mod types go here ]]
+
+ -- e means an entity ID will follow.
+ elseif c == "e" then
+ lastType = "Entity"
+ typeRead = true
+--[[
+ -- c means a vehicle ID will follow.
+ elseif c == "c" then
+ lastType = "Vehicle"
+ typeRead = true
+ -- w means a weapon entity ID will follow.
+ elseif c == "w" then
+ lastType = "Weapon"
+ typeRead = true
+ -- x means a NPC ID will follow.
+ elseif c == "x" then
+ lastType = "NPC"
+ typeRead = true
+--]]
+ -- p means a player ID will follow.
+ -- Kept for backwards compatibility.
+ elseif c == "p" then
+ lastType = "Entity"
+ typeRead = true
+
+ -- v means a vector will follow. 3 numbers.
+ elseif c == "v" then
+ lastType = "Vector"
+ typeRead = true
+
+ -- a means an Euler angle will follow. 3 numbers.
+ elseif c == "a" then
+ lastType = "Angle"
+ typeRead = true
+
+--[[ Garry's Mod types end here ]]
+
+
+ -- If no type has been found, attempt to deserialize the last type read.
+ elseif lastType then
+ val, i = _deserialize[lastType](s, i, len, false, jobstate)
+ return val, i, lastType
+
+ -- This will occur if the very first character in the vON code is wrong.
+ else
+ error("vON: Malformed data... Can't find a proper type definition. Char#" .. i .. ":" .. c)
+ end
+
+ -- Move the pointer one step forward.
+ i = i + 1
+ end
+end
+
+-- This is kept away from the table for speed.
+-- Yeah, ton of parameters.
+function s_anyVariable(data, lastType, isNumeric, isKey, isLast, jobstate)
+ local tp = type(data)
+
+ if jobstate[1] and jobstate[2][data] then
+ tp = "table_reference"
+ end
+
+ -- Basically, if the type changes.
+ if lastType ~= tp then
+ -- Remember the new type. Caching the type is useless.
+ lastType = tp
+
+ if _serialize[lastType] then
+ -- Return the serialized data and the (new) last type.
+ -- The second argument, which is true now, means that the data type was just changed.
+ return _serialize[lastType](data, true, isNumeric, isKey, isLast, false, jobstate), lastType
+ else
+ error("vON: No serializer defined for type \"" .. lastType .. "\"!")
+ end
+ end
+
+ -- Otherwise, simply serialize the data.
+ return _serialize[lastType](data, false, isNumeric, isKey, isLast, false, jobstate), lastType
+end
+
+
+
+--[[ This section contains the tables with the functions necessary
+ for decoding basic Lua data types. ]]
+
+
+
+_deserialize = {
+-- Well, tables are very loose...
+-- The first table doesn't have to begin and end with { and }.
+ ["table"] = function(s, i, len, unnecessaryEnd, jobstate)
+ local ret, numeric, i, c, lastType, val, ind, expectValue, key = {}, true, i or 1, nil, nil, nil, 1
+ -- Locals, locals, locals, locals, locals, locals, locals, locals and locals.
+
+ if sub(s, i, i) == "#" then
+ local e = find(s, "#", i + 2, true)
+
+ if e then
+ local id = tonumber(sub(s, i + 1, e - 1))
+
+ if id then
+ if jobstate[1][id] and not jobstate[2] then
+ error("vON: There already is a table of reference #" .. id .. "! Missing an option maybe?")
+ end
+
+ jobstate[1][id] = ret
+
+ i = e + 1
+ else
+ error("vON: Malformed table! Reference ID starting at char #" .. i .. " doesn't contain a number!")
+ end
+ else
+ error("vON: Malformed table! Cannot find end of reference ID start at char #" .. i .. "!")
+ end
+ end
+
+ -- Keep looping.
+ while true do
+ -- Until it meets the end.
+ if i > len then
+ -- Yeah, if the end is unnecessary, it won't spit an error. The main chunk doesn't require an end, for example.
+ if unnecessaryEnd then
+ return ret, i
+
+ -- Otherwise, the data has to be damaged.
+ else
+ error("vON: Reached end of string, incomplete table definition.")
+ end
+ end
+
+ -- Cache the character.
+ c = sub(s, i, i)
+ --print(i, "table char:", c, tostring(unnecessaryEnd))
+
+ -- If it's the end of a table definition, return.
+ if c == "}" then
+ return ret, i
+
+ -- If it's the component separator, switch to key:value pairs.
+ elseif c == "~" then
+ numeric = false
+
+ elseif c == ";" then
+ -- Lol, nothing!
+ -- Remenant from numbers, for faster parsing.
+
+ -- OK, now, if it's on the numeric component, simply add everything encountered.
+ elseif numeric then
+ -- Find a variable and it's value
+ val, i, lastType = d_findVariable(s, i, len, lastType, jobstate)
+ -- Add it to the table.
+ ret[ind] = val
+
+ ind = ind + 1
+
+ -- Otherwise, if it's the key:value component...
+ else
+ -- If a value is expected...
+ if expectValue then
+ -- Read it.
+ val, i, lastType = d_findVariable(s, i, len, lastType, jobstate)
+ -- Add it?
+ ret[key] = val
+ -- Clean up.
+ expectValue, key = false, nil
+
+ -- If it's the separator...
+ elseif c == ":" then
+ -- Expect a value next.
+ expectValue = true
+
+ -- But, if there's a key read already...
+ elseif key then
+ -- Then this is malformed.
+ error("vON: Malformed table... Two keys declared successively? Char#" .. i .. ":" .. c)
+
+ -- Otherwise the key will be read.
+ else
+ -- I love multi-return and multi-assignement.
+ key, i, lastType = d_findVariable(s, i, len, lastType, jobstate)
+ end
+ end
+
+ i = i + 1
+ end
+
+ return nil, i
+ end,
+
+-- Just a number which points to a table.
+ ["table_reference"] = function(s, i, len, unnecessaryEnd, jobstate)
+ local i, a = i or 1
+ -- Locals, locals, locals, locals
+
+ a = find(s, "[;:}~]", i)
+
+ if a then
+ local n = tonumber(sub(s, i, a - 1))
+
+ if n then
+ return jobstate[1][n] or error("vON: Table reference does not point to a (yet) known table!"), a - 1
+ else
+ error("vON: Table reference definition does not contain a valid number!")
+ end
+ end
+
+ -- Using %D breaks identification of negative numbers. :(
+
+ error("vON: Number definition started... Found no end.")
+ end,
+
+
+-- Numbers are weakly defined.
+-- The declaration is not very explicit. It'll do it's best to parse the number.
+-- Has various endings: \n, }, ~, : and ;, some of which will force the table deserializer to go one char backwards.
+ ["number"] = function(s, i, len, unnecessaryEnd, jobstate)
+ local i, a = i or 1
+ -- Locals, locals, locals, locals
+
+ a = find(s, "[;:}~]", i)
+
+ if a then
+ return tonumber(sub(s, i, a - 1)) or error("vON: Number definition does not contain a valid number!"), a - 1
+ end
+
+ -- Using %D breaks identification of negative numbers. :(
+
+ error("vON: Number definition started... Found no end.")
+ end,
+
+
+-- A boolean is A SINGLE CHARACTER, either 1 for true or 0 for false.
+-- Any other attempt at boolean declaration will result in a failure.
+ ["boolean"] = function(s, i, len, unnecessaryEnd, jobstate)
+ local c = sub(s,i,i)
+ -- Only one character is needed.
+
+ -- If it's 1, then it's true
+ if c == "1" then
+ return true, i
+
+ -- If it's 0, then it's false.
+ elseif c == "0" then
+ return false, i
+ end
+
+ -- Any other supposely "boolean" is just a sign of malformed data.
+ error("vON: Invalid value on boolean type... Char#" .. i .. ": " .. c)
+ end,
+
+
+-- Strings prior to 1.2.0
+ ["oldstring"] = function(s, i, len, unnecessaryEnd, jobstate)
+ local res, i, a = "", i or 1
+ -- Locals, locals, locals, locals
+
+ while true do
+ a = find(s, "\"", i, true)
+
+ if a then
+ if sub(s, a - 1, a - 1) == "\\" then
+ res = res .. sub(s, i, a - 2) .. "\""
+ i = a + 1
+ else
+ return res .. sub(s, i, a - 2), a
+ end
+ else
+ error("vON: Old string definition started... Found no end.")
+ end
+ end
+ end,
+
+-- Strings after 1.2.0
+ ["string"] = function(s, i, len, unnecessaryEnd, jobstate)
+ local res, i, a = "", i or 1
+ -- Locals, locals, locals, locals
+
+ while true do
+ a = find(s, "\"", i, true)
+
+ if a then
+ if sub(s, a - 1, a - 1) == "\\" then
+ res = res .. sub(s, i, a - 2) .. "\""
+ i = a + 1
+ else
+ return res .. sub(s, i, a - 1), a
+ end
+ else
+ error("vON: String definition started... Found no end.")
+ end
+ end
+ end,
+}
+
+
+
+_serialize = {
+-- Uh. Nothing to comment.
+-- Ton of parameters.
+-- Makes stuff faster than simply passing it around in locals.
+-- table.concat works better than normal concatenations WITH LARGE-ISH STRINGS ONLY.
+ ["table"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
+ --print(string.format("data: %s; mustInitiate: %s; isKey: %s; isLast: %s; nice: %s; indent: %s; first: %s", tostring(data), tostring(mustInitiate), tostring(isKey), tostring(isLast), tostring(nice), tostring(indent), tostring(first)))
+
+ local result, keyvals, len, keyvalsLen, keyvalsProgress, val, lastType, newIndent, indentString = {}, {}, #data, 0, 0
+ -- Locals, locals, locals, locals, locals, locals, locals, locals, locals and locals.
+
+ -- First thing to be done is separate the numeric and key:value components of the given table in two tables.
+ -- pairs(data) is slower than next, data as far as my tests tell me.
+ for k, v in next, data do
+ -- Skip the numeric keyz.
+ if type(k) ~= "number" or k < 1 or k > len or (k % 1 ~= 0) then -- k % 1 == 0 is, as proven by personal benchmarks,
+ keyvals[#keyvals + 1] = k -- the quickest way to check if a number is an integer.
+ end -- k % 1 ~= 0 is the fastest way to check if a number
+ end -- is NOT an integer. > is proven slower.
+
+ keyvalsLen = #keyvals
+
+ -- Main chunk - no initial character.
+ if not first then
+ result[#result + 1] = "{"
+ end
+
+ if jobstate[1] and jobstate[1][data] then
+ if jobstate[2][data] then
+ error("vON: Table #" .. jobstate[1][data] .. " written twice..?")
+ end
+
+ result[#result + 1] = "#"
+ result[#result + 1] = jobstate[1][data]
+ result[#result + 1] = "#"
+
+ jobstate[2][data] = true
+ end
+
+ -- Add numeric values.
+ if len > 0 then
+ for i = 1, len do
+ val, lastType = s_anyVariable(data[i], lastType, true, false, i == len and not first, jobstate)
+ result[#result + 1] = val
+ end
+ end
+
+ -- If there are key:value pairs.
+ if keyvalsLen > 0 then
+ -- Insert delimiter.
+ result[#result + 1] = "~"
+
+ -- Insert key:value pairs.
+ for _i = 1, keyvalsLen do
+ keyvalsProgress = keyvalsProgress + 1
+
+ val, lastType = s_anyVariable(keyvals[_i], lastType, false, true, false, jobstate)
+
+ result[#result + 1] = val..":"
+
+ val, lastType = s_anyVariable(data[keyvals[_i]], lastType, false, false, keyvalsProgress == keyvalsLen and not first, jobstate)
+
+ result[#result + 1] = val
+ end
+ end
+
+ -- Main chunk needs no ending character.
+ if not first then
+ result[#result + 1] = "}"
+ end
+
+ return concat(result)
+ end,
+
+-- Number which points to table.
+ ["table_reference"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
+ data = jobstate[1][data]
+
+ -- If a number hasn't been written before, add the type prefix.
+ if mustInitiate then
+ if isKey or isLast then
+ return "$"..data
+ else
+ return "$"..data..";"
+ end
+ end
+
+ if isKey or isLast then
+ return data
+ else
+ return data..";"
+ end
+ end,
+
+
+-- Normal concatenations is a lot faster with small strings than table.concat
+-- Also, not so branched-ish.
+ ["number"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
+ -- If a number hasn't been written before, add the type prefix.
+ if mustInitiate then
+ if isKey or isLast then
+ return "n"..data
+ else
+ return "n"..data..";"
+ end
+ end
+
+ if isKey or isLast then
+ return data
+ else
+ return data..";"
+ end
+ end,
+
+
+-- I hope gsub is fast enough.
+ ["string"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
+ if sub(data, #data, #data) == "\\" then -- Hah, old strings fix this best.
+ return "\"" .. gsub(data, "\"", "\\\"") .. "v\""
+ end
+
+ return "'" .. gsub(data, "\"", "\\\"") .. "\""
+ end,
+
+
+-- Fastest.
+ ["boolean"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
+ -- Prefix if we must.
+ if mustInitiate then
+ if data then
+ return "b1"
+ else
+ return "b0"
+ end
+ end
+
+ if data then
+ return "1"
+ else
+ return "0"
+ end
+ end,
+
+
+-- Fastest.
+ ["nil"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
+ return "@"
+ end,
+}
+
+
+
+--[[ This section handles additions necessary for Garry's Mod. ]]
+
+
+
+if gmod then -- Luckily, a specific table named after the game is present in Garry's Mod.
+ local Entity = Entity
+
+
+
+ local extra_deserialize = {
+-- Entities are stored simply by the ID. They're meant to be transfered, not stored anyway.
+-- Exactly like a number definition, except it begins with "e".
+ ["Entity"] = function(s, i, len, unnecessaryEnd, jobstate)
+ local i, a = i or 1
+ -- Locals, locals, locals, locals
+
+ a = find(s, "[;:}~]", i)
+
+ if a then
+ return Entity(tonumber(sub(s, i, a - 1))), a - 1
+ end
+
+ error("vON: Entity ID definition started... Found no end.")
+ end,
+
+
+-- A pair of 3 numbers separated by a comma (,).
+ ["Vector"] = function(s, i, len, unnecessaryEnd, jobstate)
+ local i, a, x, y, z = i or 1
+ -- Locals, locals, locals, locals
+
+ a = find(s, ",", i)
+
+ if a then
+ x = tonumber(sub(s, i, a - 1))
+ i = a + 1
+ end
+
+ a = find(s, ",", i)
+
+ if a then
+ y = tonumber(sub(s, i, a - 1))
+ i = a + 1
+ end
+
+ a = find(s, "[;:}~]", i)
+
+ if a then
+ z = tonumber(sub(s, i, a - 1))
+ end
+
+ if x and y and z then
+ return Vector(x, y, z), a - 1
+ end
+
+ error("vON: Vector definition started... Found no end.")
+ end,
+
+
+-- A pair of 3 numbers separated by a comma (,).
+ ["Angle"] = function(s, i, len, unnecessaryEnd, jobstate)
+ local i, a, p, y, r = i or 1
+ -- Locals, locals, locals, locals
+
+ a = find(s, ",", i)
+
+ if a then
+ p = tonumber(sub(s, i, a - 1))
+ i = a + 1
+ end
+
+ a = find(s, ",", i)
+
+ if a then
+ y = tonumber(sub(s, i, a - 1))
+ i = a + 1
+ end
+
+ a = find(s, "[;:}~]", i)
+
+ if a then
+ r = tonumber(sub(s, i, a - 1))
+ end
+
+ if p and y and r then
+ return Angle(p, y, r), a - 1
+ end
+
+ error("vON: Angle definition started... Found no end.")
+ end,
+ }
+
+ local extra_serialize = {
+-- Same as numbers, except they start with "e" instead of "n".
+ ["Entity"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
+ data = data:EntIndex()
+
+ if mustInitiate then
+ if isKey or isLast then
+ return "e"..data
+ else
+ return "e"..data..";"
+ end
+ end
+
+ if isKey or isLast then
+ return data
+ else
+ return data..";"
+ end
+ end,
+
+
+-- 3 numbers separated by a comma.
+ ["Vector"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
+ if mustInitiate then
+ if isKey or isLast then
+ return "v"..data.x..","..data.y..","..data.z
+ else
+ return "v"..data.x..","..data.y..","..data.z..";"
+ end
+ end
+
+ if isKey or isLast then
+ return data.x..","..data.y..","..data.z
+ else
+ return data.x..","..data.y..","..data.z..";"
+ end
+ end,
+
+
+-- 3 numbers separated by a comma.
+ ["Angle"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
+ if mustInitiate then
+ if isKey or isLast then
+ return "a"..data.p..","..data.y..","..data.r
+ else
+ return "a"..data.p..","..data.y..","..data.r..";"
+ end
+ end
+
+ if isKey or isLast then
+ return data.p..","..data.y..","..data.r
+ else
+ return data.p..","..data.y..","..data.r..";"
+ end
+ end,
+ }
+
+ for k, v in pairs(extra_serialize) do
+ _serialize[k] = v
+ end
+
+ for k, v in pairs(extra_deserialize) do
+ _deserialize[k] = v
+ end
+
+ local extraEntityTypes = { "Vehicle", "Weapon", "NPC", "Player", "NextBot" }
+
+ for i = 1, #extraEntityTypes do
+ _serialize[extraEntityTypes[i]] = _serialize.Entity
+ end
+end
+
+
+
+--[[ This section exposes the functions of the library. ]]
+
+
+
+local function checkTableForRecursion(tab, checked, assoc)
+ local id = checked.ID
+
+ if not checked[tab] and not assoc[tab] then
+ assoc[tab] = id
+ checked.ID = id + 1
+ else
+ checked[tab] = true
+ end
+
+ for k, v in pairs(tab) do
+ if type(k) == "table" and not checked[k] then
+ checkTableForRecursion(k, checked, assoc)
+ end
+
+ if type(v) == "table" and not checked[v] then
+ checkTableForRecursion(v, checked, assoc)
+ end
+ end
+end
+
+
+
+local _s_table = _serialize.table
+local _d_table = _deserialize.table
+
+_d_meta = {
+ __call = function(self, str, allowIdRewriting)
+ if type(str) == "string" then
+ return _d_table(str, nil, #str, true, {{}, allowIdRewriting})
+ end
+
+ error("vON: You must deserialize a string, not a "..type(str))
+ end
+}
+_s_meta = {
+ __call = function(self, data, checkRecursion)
+ if type(data) == "table" then
+ if checkRecursion then
+ local assoc, checked = {}, {ID = 1}
+
+ checkTableForRecursion(data, checked, assoc)
+
+ return _s_table(data, nil, nil, nil, nil, true, {assoc, {}})
+ end
+
+ return _s_table(data, nil, nil, nil, nil, true, {false})
+ end
+
+ error("vON: You must serialize a table, not a "..type(data))
+ end
+}
+
+
+
+von = {
+ version = "1.3.4",
+ versionNumber = 1003004, -- Reserving 3 digits per version component.
+
+ deserialize = setmetatable(_deserialize,_d_meta),
+ serialize = setmetatable(_serialize,_s_meta)
+}
+
+return von