mirror of
https://github.com/wiremod/advdupe2.git
synced 2025-03-03 18:53:05 -05:00
Initial commit.
This commit is contained in:
commit
23d7d5e9b2
11
info.txt
Normal file
11
info.txt
Normal file
@ -0,0 +1,11 @@
|
||||
"AddonInfo"
|
||||
|
||||
{
|
||||
"name" "Adv. Duplicator 2"
|
||||
"version" "SVN"
|
||||
"up_date" "SVN"
|
||||
"author_name" "Advanced Duplicator 2 Team: TB, emspike"
|
||||
"author_email" "advdupe2@gmail.com"
|
||||
"author_url" "http://facepunch.com"
|
||||
"info" "A new and much improved Adv. Duplicator."
|
||||
}
|
1057
lua/advdupe2/cl_browser.lua
Normal file
1057
lua/advdupe2/cl_browser.lua
Normal file
File diff suppressed because it is too large
Load Diff
60
lua/advdupe2/cl_file.lua
Normal file
60
lua/advdupe2/cl_file.lua
Normal file
@ -0,0 +1,60 @@
|
||||
--[[
|
||||
Title: Adv. Dupe 2 Filing Clerk (Clientside)
|
||||
|
||||
Desc: Reads/writes AdvDupe2 files.
|
||||
|
||||
Author: AD2 Team
|
||||
|
||||
Version: 1.0
|
||||
]]
|
||||
|
||||
--[[
|
||||
Name: WriteAdvDupe2File
|
||||
Desc: Writes a dupe file to the dupe folder.
|
||||
Params: <string> dupe, <string> name
|
||||
Return: <boolean> success/<string> path
|
||||
]]
|
||||
function AdvDupe2.WriteFile(name, dupe)
|
||||
|
||||
name = name:lower()
|
||||
|
||||
if name:find("[<>:\\\"|%?%*%.]") then return false end
|
||||
|
||||
name = name:gsub("//","/")
|
||||
|
||||
local path = string.format("%q/%q", self.DataFolder, name)
|
||||
|
||||
--if a file with this name already exists, we have to come up with a different name
|
||||
if file.Exists(path..".txt") then
|
||||
for i = 1, AdvDupe2.FileRenameTryLimit do
|
||||
--check if theres already a file with the name we came up with, and retry if there is
|
||||
--otherwise, we can exit the loop and write the file
|
||||
if not file.Exists(path.."_"..i..".txt") then
|
||||
path = path.."_"..i
|
||||
break
|
||||
end
|
||||
end
|
||||
--if we still can't find a unique name we give up
|
||||
if file.Exists(path..".txt") then return false end
|
||||
end
|
||||
|
||||
--write the file
|
||||
file.Write(path..".txt", dupe)
|
||||
|
||||
--returns if the write was successful and the name the path ended up being saved under
|
||||
return path..".txt", path:match("[^/]-$")
|
||||
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: ReadAdvDupe2File
|
||||
Desc: Reads a dupe file from the dupe folder.
|
||||
Params: <string> name
|
||||
Return: <string> contents
|
||||
]]
|
||||
function AdvDupe2.ReadFile(name, dirOverride)
|
||||
|
||||
--infinitely simpler than WriteAdvDupe2 :3
|
||||
return file.Read(string.format("%q/%q.txt", dirOverride or AdvDupe2.DataFolder, name))
|
||||
|
||||
end
|
144
lua/advdupe2/cl_networking.lua
Normal file
144
lua/advdupe2/cl_networking.lua
Normal file
@ -0,0 +1,144 @@
|
||||
--[[
|
||||
Title: Adv. Dupe 2 Networking (Clientside)
|
||||
|
||||
Desc: Handles file transfers and all that jazz.
|
||||
|
||||
Author: TB
|
||||
|
||||
Version: 1.0
|
||||
]]
|
||||
|
||||
include "nullesc.lua"
|
||||
|
||||
local function CheckFileNameCl(path)
|
||||
if file.Exists(path) then
|
||||
path = string.sub(path, 1, #path-4)
|
||||
for i = 1, AdvDupe2.FileRenameTryLimit do
|
||||
if not file.Exists(path.."_"..i..".txt") then
|
||||
return path.."_"..i..".txt"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: AdvDupe2_RecieveFile
|
||||
Desc: Recieve file data from the server when downloading to the client
|
||||
Params: usermessage
|
||||
Returns:
|
||||
]]
|
||||
local function AdvDupe2_RecieveFile(um)
|
||||
local status = um:ReadShort()
|
||||
|
||||
if(status==1)then AdvDupe2.NetFile = "" end
|
||||
AdvDupe2.NetFile=AdvDupe2.NetFile..um:ReadString()
|
||||
|
||||
if(status==2)then
|
||||
local path = CheckFileNameCl(AdvDupe2.SavePath)
|
||||
|
||||
file.Write(path, AdvDupe2.Null.invesc(AdvDupe2.NetFile))
|
||||
|
||||
local filename = string.Explode("/", path)
|
||||
filename = string.sub(filename[#filename], 1, -5)
|
||||
|
||||
AdvDupe2.FileBrowser:AddFileToClient(filename, AdvDupe2.SaveNode, true)
|
||||
AdvDupe2.NetFile = ""
|
||||
AdvDupe2.Notify("File successfully downloaded!",NOTIFY_GENERIC,5)
|
||||
return
|
||||
end
|
||||
|
||||
end
|
||||
usermessage.Hook("AdvDupe2_RecieveFile", AdvDupe2_RecieveFile)
|
||||
|
||||
function AdvDupe2.RemoveProgressBar()
|
||||
if !AdvDupe2 then AdvDupe2={} end
|
||||
AdvDupe2.ProgressBar = {}
|
||||
end
|
||||
|
||||
local escseqnl = { "nwl", "newl", "nwli", "nline" }
|
||||
local escseqquo = { "quo", "qte", "qwo", "quote" }
|
||||
--[[
|
||||
Name: InitializeUpload
|
||||
Desc: When the client clicks upload, prepares to send data to the server
|
||||
Params: File Data, Path to save
|
||||
Returns:
|
||||
]]
|
||||
function AdvDupe2.InitializeUpload(ReadPath, ReadArea, SavePath, SaveArea, ParentID)
|
||||
if(ReadArea==0)then
|
||||
ReadPath = AdvDupe2.DataFolder.."/"..ReadPath..".txt"
|
||||
elseif(ReadArea==1)then
|
||||
ReadPath = AdvDupe2.DataFolder.."/=Public=/"..ReadPath..".txt"
|
||||
else
|
||||
ReadPath = "adv_duplicator/"..ReadPath..".txt"
|
||||
end
|
||||
|
||||
if(!file.Exists(ReadPath))then return end
|
||||
local nwl
|
||||
local quo
|
||||
local data = AdvDupe2.Null.esc(file.Read(ReadPath))
|
||||
|
||||
for k = 1, #escseqnl do
|
||||
if(string.find(data, escseqnl[k]))then continue end
|
||||
nwl = escseqnl[k]
|
||||
data = string.gsub(data, "\10", escseqnl[k])
|
||||
break
|
||||
end
|
||||
|
||||
for k = 1, #escseqquo do
|
||||
if(string.find(data, escseqquo[k]))then continue end
|
||||
quo = escseqquo[k]
|
||||
data = string.gsub(data, [["]], escseqquo[k])
|
||||
break
|
||||
end
|
||||
|
||||
AdvDupe2.File = data
|
||||
AdvDupe2.LastPos = 0
|
||||
AdvDupe2.Length = string.len(data)
|
||||
AdvDupe2.InitProgressBar("Uploading:")
|
||||
|
||||
RunConsoleCommand("AdvDupe2_InitRecieveFile", SavePath, SaveArea, nwl, quo, ParentID)
|
||||
end
|
||||
|
||||
function AdvDupe2.UpdateProgressBar(percent)
|
||||
AdvDupe2.ProgressBar.Percent = percent
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: SendFileToServer
|
||||
Desc: Send chunks of the file data to the server
|
||||
Params: end of file
|
||||
Returns:
|
||||
]]
|
||||
local function SendFileToServer(eof, chunks)
|
||||
|
||||
for i=1,chunks do
|
||||
if(AdvDupe2.LastPos+eof>AdvDupe2.Length)then
|
||||
eof = AdvDupe2.Length
|
||||
end
|
||||
|
||||
local data = string.sub(AdvDupe2.File, AdvDupe2.LastPos, AdvDupe2.LastPos+eof)
|
||||
AdvDupe2.LastPos = AdvDupe2.LastPos+eof+1
|
||||
AdvDupe2.UpdateProgressBar(math.floor((AdvDupe2.LastPos/AdvDupe2.Length)*100))
|
||||
local status = 0
|
||||
if(AdvDupe2.LastPos>=AdvDupe2.Length)then
|
||||
status=1
|
||||
AdvDupe2.RemoveProgressBar()
|
||||
RunConsoleCommand("AdvDupe2_RecieveFile", status, data)
|
||||
break
|
||||
end
|
||||
RunConsoleCommand("AdvDupe2_RecieveFile", status, data)
|
||||
end
|
||||
end
|
||||
|
||||
usermessage.Hook("AdvDupe2_RecieveNextStep",function(um)
|
||||
SendFileToServer(um:ReadShort(), um:ReadShort())
|
||||
end)
|
||||
|
||||
usermessage.Hook("AdvDupe2_UploadRejected",function(um)
|
||||
AdvDupe2.File = nil
|
||||
AdvDupe2.LastPos = nil
|
||||
AdvDupe2.Length = nil
|
||||
if(um:ReadBool())then AdvDupe2.RemoveProgressBar() end
|
||||
end)
|
54
lua/advdupe2/nullesc.lua
Normal file
54
lua/advdupe2/nullesc.lua
Normal file
@ -0,0 +1,54 @@
|
||||
--[[
|
||||
Title: Null Escaper
|
||||
|
||||
Desc: Escapes null characters.
|
||||
|
||||
Author: AD2 Team
|
||||
|
||||
Version: 1.0
|
||||
]]
|
||||
|
||||
local char = string.char
|
||||
local find = string.find
|
||||
local gsub = string.gsub
|
||||
local match = string.match
|
||||
|
||||
local Null = {}
|
||||
|
||||
local escseq = { --no palindromes
|
||||
"bbq",
|
||||
"wtf",
|
||||
"cat",
|
||||
"car",
|
||||
"bro",
|
||||
"moo",
|
||||
"sky",
|
||||
}
|
||||
|
||||
function Null.esc(str)
|
||||
local genseq
|
||||
for i=1,#escseq do
|
||||
if not find(str, escseq[i]) then
|
||||
local genseq = escseq[i]
|
||||
return genseq.."\n"..gsub(str,"%z",genseq)
|
||||
end
|
||||
end
|
||||
for i=30,200 do
|
||||
genseq = char(i, i-1, i+1)
|
||||
if not find(str, genseq) then
|
||||
return genseq.."\n"..gsub(str,"%z",genseq)
|
||||
end
|
||||
genseq = char(i, i, i+1)
|
||||
if not find(str, genseq) then
|
||||
return genseq.."\n"..gsub(str,"%z",genseq)
|
||||
end
|
||||
end
|
||||
error("nullesc could not escape the string")
|
||||
end
|
||||
|
||||
function Null.invesc(str)
|
||||
local delim,huff = match(str,"^(.-)\n(.-)$")
|
||||
return gsub(huff,delim,"\0")
|
||||
end
|
||||
|
||||
AdvDupe2.Null = Null
|
1283
lua/advdupe2/sv_clipboard.lua
Normal file
1283
lua/advdupe2/sv_clipboard.lua
Normal file
File diff suppressed because it is too large
Load Diff
696
lua/advdupe2/sv_codec.lua
Normal file
696
lua/advdupe2/sv_codec.lua
Normal file
@ -0,0 +1,696 @@
|
||||
--[[
|
||||
Title: Adv. Dupe 2 Codec
|
||||
|
||||
Desc: Dupe encoder/decoder.
|
||||
|
||||
Author: emspike
|
||||
|
||||
Version: 1.0
|
||||
]]
|
||||
|
||||
local REVISION = 1
|
||||
|
||||
local pairs = pairs
|
||||
local type = type
|
||||
local tostring = tostring
|
||||
local tonumber = tonumber
|
||||
local error = error
|
||||
local Vector = Vector
|
||||
local Angle = Angle
|
||||
local unpack = unpack
|
||||
local format = string.format
|
||||
local char = string.char
|
||||
local byte = string.byte
|
||||
local sub = string.sub
|
||||
local gsub = string.gsub
|
||||
local find = string.find
|
||||
local gmatch = string.gmatch
|
||||
local concat = table.concat
|
||||
local remove = table.remove
|
||||
local sort = table.sort
|
||||
local merge = table.Merge
|
||||
local match = string.match
|
||||
local insert = table.insert
|
||||
|
||||
AdvDupe2.CodecRevision = REVISION
|
||||
|
||||
--[[
|
||||
Name: GenerateDupeStamp
|
||||
Desc: Generates an info table.
|
||||
Params: <player> ply
|
||||
Return: <table> stamp
|
||||
]]
|
||||
function AdvDupe2.GenerateDupeStamp(ply)
|
||||
local stamp = {}
|
||||
stamp.name = ply:GetName()
|
||||
stamp.time = os.date("%I:%M %p")
|
||||
stamp.date = os.date("%d %B %Y")
|
||||
stamp.timezone = os.date("%z")
|
||||
hook.Call("AdvDupe2_StampGenerated",GAMEMODE,stamp)
|
||||
return stamp
|
||||
end
|
||||
|
||||
local function makeInfo(tbl)
|
||||
local info = ""
|
||||
for k,v in pairs(tbl) do
|
||||
info = concat{info,k,"\1",v,"\1"}
|
||||
end
|
||||
return info.."\2"
|
||||
end
|
||||
|
||||
local AD2FF = "AD2F%s\n%s\n%s"
|
||||
|
||||
local period = CreateConVar("advdupe2_codec_pipeperiod",1,"Every this many ticks, the codec pipeline processor will run.",FCVAR_ARCHIVE,FCVAR_DONTRECORD)
|
||||
local clock = 1
|
||||
local pipelines = {}
|
||||
local function addPipeline(pipeline)
|
||||
insert(pipelines,pipeline)
|
||||
end
|
||||
local function pipeproc()
|
||||
if clock % period:GetInt() == 0 then
|
||||
done = {}
|
||||
for idx,pipeline in pairs(pipelines) do
|
||||
local i = pipeline.idx + 1
|
||||
pipeline.idx = i
|
||||
if i == pipeline.cbk then
|
||||
done[#done+1] = idx
|
||||
pipeline.info.size = #pipeline.eax
|
||||
local success, err = pcall(pipeline[i], AD2FF:format(char(pipeline.REVISION), makeInfo(pipeline.info), pipeline.eax), unpack(pipeline.args))
|
||||
if not success then ErrorNoHalt(err) end
|
||||
else
|
||||
local success, err = pcall(pipeline[i], pipeline.eax)
|
||||
if success then
|
||||
pipeline.eax = err
|
||||
else
|
||||
ErrorNoHalt(err)
|
||||
done[#done+1] = idx
|
||||
end
|
||||
end
|
||||
end
|
||||
sort(done)
|
||||
for i = #done, 1, -1 do
|
||||
remove(pipelines,done[i])
|
||||
end
|
||||
clock = 1
|
||||
else
|
||||
clock = clock + 1
|
||||
end
|
||||
end
|
||||
hook.Add("Tick","AD2CodecPipelineProc",pipeproc)
|
||||
|
||||
local encode_types, decode_types
|
||||
local str,pos
|
||||
local a,b,c,m,n,w
|
||||
|
||||
local function write(data)
|
||||
local t = encode_types[type(data)]
|
||||
if t then
|
||||
local data, id_override = t[2](data)
|
||||
return char(id_override or t[1])..data
|
||||
end
|
||||
end
|
||||
|
||||
encode_types = {
|
||||
table = {2, function(o)
|
||||
local is_array = true
|
||||
m = 0
|
||||
for k in pairs(o) do
|
||||
m = m + 1
|
||||
if k ~= m then
|
||||
is_array = false
|
||||
break
|
||||
end
|
||||
end
|
||||
local u = {}
|
||||
if is_array then
|
||||
for g = 1,#o do
|
||||
u[g] = write(o[g])
|
||||
end
|
||||
return concat(u).."\1", 3
|
||||
else
|
||||
local i = 0
|
||||
for k,v in pairs(o) do
|
||||
w = write(v)
|
||||
if w then
|
||||
i = i + 2
|
||||
u[i-1] = write(k)
|
||||
u[i] = w
|
||||
end
|
||||
end
|
||||
return concat(u).."\1"
|
||||
end
|
||||
end},
|
||||
boolean = {4, function(o)
|
||||
return "", o and 5
|
||||
end},
|
||||
number = {6, function(o)
|
||||
return (o==0 and "" or o).."\1"
|
||||
end},
|
||||
string = {7, function(o)
|
||||
return o.."\1"
|
||||
end},
|
||||
Vector = {8, function(o)
|
||||
return format("%g\1%g\1%g\1",o.x,o.y,o.z)
|
||||
end},
|
||||
Angle = {9, function(o)
|
||||
return format("%g\1%g\1%g\1",o.p,o.y,o.r)
|
||||
end}
|
||||
}
|
||||
|
||||
local function read()
|
||||
local t = byte(str,pos+1)
|
||||
if t then
|
||||
local dt = decode_types[t]
|
||||
if dt then
|
||||
pos = pos + 1
|
||||
return dt()
|
||||
else
|
||||
error(format("encountered invalid data type (%u)",t))
|
||||
end
|
||||
else
|
||||
error("expected value, got EOF")
|
||||
end
|
||||
end
|
||||
|
||||
decode_types = {
|
||||
[1 ] = function()
|
||||
error("expected value, got terminator")
|
||||
end,
|
||||
[2 ] = function() -- table
|
||||
local t = {}
|
||||
while true do
|
||||
if byte(str,pos+1) == 1 then
|
||||
pos = pos+1
|
||||
return t
|
||||
else
|
||||
t[read()] = read()
|
||||
end
|
||||
end
|
||||
end,
|
||||
[3 ] = function() -- array
|
||||
local t, i = {}, 1
|
||||
while true do
|
||||
if byte(str,pos+1) == 1 then
|
||||
pos = pos+1
|
||||
return t
|
||||
else
|
||||
t[i] = read()
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end,
|
||||
[4 ] = function() -- false boolean
|
||||
return false
|
||||
end,
|
||||
[5 ] = function() -- true boolean
|
||||
return true
|
||||
end,
|
||||
[6 ] = function() -- number
|
||||
m = find(str,"\1",pos)
|
||||
if m then
|
||||
a = tonumber(sub(str,pos+1,m-1)) or 0
|
||||
pos = m
|
||||
return a
|
||||
else
|
||||
error("expected number, got EOF")
|
||||
end
|
||||
end,
|
||||
[7 ] = function() -- string
|
||||
m = find(str,"\1",pos)
|
||||
if m then
|
||||
w = sub(str,pos+1,m-1)
|
||||
pos = m
|
||||
return w
|
||||
else
|
||||
error("expected string, got EOF")
|
||||
end
|
||||
end,
|
||||
[8 ] = function() -- Vector
|
||||
m,n = find(str,".-\1.-\1.-\1",pos)
|
||||
if m then
|
||||
a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1)
|
||||
pos = n
|
||||
return Vector(tonumber(a), tonumber(b), tonumber(c))
|
||||
else
|
||||
error("expected vector, got EOF")
|
||||
end
|
||||
end,
|
||||
[9 ] = function() -- Angle
|
||||
m,n = find(str,".-\1.-\1.-\1",pos)
|
||||
if m then
|
||||
a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1)
|
||||
pos = n
|
||||
return Angle(tonumber(a), tonumber(b), tonumber(c))
|
||||
else
|
||||
error("expected angle, got EOF")
|
||||
end
|
||||
end
|
||||
}
|
||||
local function deserialize(data)
|
||||
str = data
|
||||
pos = 0
|
||||
return read()
|
||||
end
|
||||
local function serialize(data)
|
||||
return write(data)
|
||||
end
|
||||
|
||||
local idxmem = {}
|
||||
for i=0,252 do
|
||||
idxmem[i] = char(i)
|
||||
end
|
||||
|
||||
local function encodeIndex(index)
|
||||
local buffer = {}
|
||||
local buffer_len = 0
|
||||
local temp
|
||||
while index>0 do
|
||||
temp = index>>8
|
||||
buffer_len = buffer_len + 1
|
||||
buffer[buffer_len] = index - (temp << 8)
|
||||
index = temp
|
||||
end
|
||||
return char(256 - buffer_len, unpack(buffer))
|
||||
end
|
||||
local function lzwEncode(raw)
|
||||
local dictionary_length = 256
|
||||
local dictionary = {}
|
||||
local compressed = {}
|
||||
local word = ""
|
||||
for i = 0, 255 do
|
||||
dictionary[char(i)] = i
|
||||
end
|
||||
local curchar
|
||||
local wordc
|
||||
local compressed_length = 0
|
||||
local temp
|
||||
for i = 1, #raw do
|
||||
curchar = sub(raw,i,i)
|
||||
wordc = word..curchar
|
||||
if dictionary[wordc] then
|
||||
word = wordc
|
||||
else
|
||||
dictionary[wordc] = dictionary_length
|
||||
dictionary_length = dictionary_length + 1
|
||||
|
||||
temp = idxmem[dictionary[word]]
|
||||
|
||||
compressed_length = compressed_length + 1
|
||||
if temp then
|
||||
compressed[compressed_length] = temp
|
||||
else
|
||||
temp = encodeIndex(dictionary[word])
|
||||
compressed[compressed_length] = temp
|
||||
idxmem[dictionary[word]] = temp
|
||||
end
|
||||
|
||||
word = curchar
|
||||
end
|
||||
end
|
||||
temp = idxmem[dictionary[word]]
|
||||
if temp then
|
||||
compressed[compressed_length+1] = temp
|
||||
else
|
||||
temp = encodeIndex(dictionary[word])
|
||||
compressed[compressed_length+1] = temp
|
||||
idxmem[dictionary[word]] = temp
|
||||
end
|
||||
return concat(compressed)
|
||||
end
|
||||
|
||||
local function lzwDecode(encoded)
|
||||
local dictionary_length = 256
|
||||
local dictionary = {}
|
||||
for i = 0, 255 do
|
||||
dictionary[i] = char(i)
|
||||
end
|
||||
|
||||
local pos = 2
|
||||
local decompressed = {}
|
||||
local decompressed_length = 1
|
||||
|
||||
local index = byte(encoded)
|
||||
local word = dictionary[index]
|
||||
|
||||
decompressed[decompressed_length] = dictionary[index]
|
||||
|
||||
local entry
|
||||
local encoded_length = #encoded
|
||||
local firstbyte --of an index
|
||||
while pos <= encoded_length do
|
||||
firstbyte = byte(encoded,pos)
|
||||
if firstbyte > 252 then --now we know it's a length indicator for a multibyte index
|
||||
index = 0
|
||||
firstbyte = 256 - firstbyte
|
||||
for i = pos+firstbyte, pos+1, -1 do
|
||||
index = (index << 8) | byte(encoded,i)
|
||||
end
|
||||
pos = pos + firstbyte + 1
|
||||
else
|
||||
index = firstbyte
|
||||
pos = pos + 1
|
||||
end
|
||||
entry = dictionary[index] or (word..sub(word,1,1))
|
||||
decompressed_length = decompressed_length + 1
|
||||
decompressed[decompressed_length] = entry
|
||||
dictionary[dictionary_length] = word..sub(entry,1,1)
|
||||
dictionary_length = dictionary_length + 1
|
||||
word = entry
|
||||
end
|
||||
return concat(decompressed)
|
||||
end
|
||||
|
||||
--http://en.wikipedia.org/wiki/Huffman_coding#Compression
|
||||
|
||||
local codes = {{22,5},{11,5},{58,6},{57,6},{37,6},{35,6},{13,6},{31,6},{51,6},{55,6},{26,7},{10,7},{9,6},{1,7},{59,6},{15,7},{61,7},{33,7},{97,7},{5,8},{133,8},{130,8},{65,7},{41,7},{94,7},{62,7},{17,7},{7,7},{162,8},{89,7},{87,7},{3,7},{39,7},{2,8},{66,8},{142,8},{21,8},{47,7},{50,7},{82,7},{46,7},{25,7},{19,7},{170,8},{90,9},{305,9},{290,9},{437,9},{270,9},{254,9},{85,9},{369,9},{49,9},{42,9},{53,9},{238,9},{381,9},{29,9},{346,10},{245,10},{497,9},{226,10},{327,9},{207,9},{458,9},{301,9},{81,9},{490,9},{489,9},{283,9},{103,9},{626,10},{109,9},{429,9},{262,10},{509,9},{237,9},{390,9},{233,9},{413,9},{774,10},{181,9},{323,9},{177,9},{197,9},{45,9},{730,10},{91,9},{349,9},{882,10},{63,9},{646,10},{202,10},{718,10},{325,9},{402,10},{286,9},{414,9},{117,9},{366,9},{111,8},{105,9},{67,9},{361,9},{14,9},{242,10},{453,9},{510,9},{422,9},{70,9},{166,9},{38,9},{658,10},{337,9},{294,9},{102,9},{253,9},{27,9},{411,9},{110,9},{241,9},{255,9},{213,9},{733,10},{746,10},{198,10},{454,10},{786,10},{586,10},{157,9},{846,10},{486,10},{962,10},{78,10},{610,10},{590,10},{219,9},{625,10},{493,9},{474,10},{194,10},{842,10},{974,10},{285,9},{917,10},{83,9},{127,9},{370,10},{710,10},{1013,10},{134,10},{221,10},{511,9},{998,10},{191,9},{114,10},{467,9},{209,10},{447,9},{1006,10},{382,10},{319,9},{149,9},{462,10},{126,10},{330,10},{475,9},{309,10},{98,10},{69,9},{986,10},{742,10},{810,10},{383,9},{455,9},{407,9},{155,9},{199,9},{465,10},{354,10},{618,10},{469,9},{158,9},{365,9},{206,10},{106,10},{721,10},{714,10},{870,10},{894,10},{542,10},{74,10},{347,9},{146,10},{821,10},{279,9},{638,10},{373,9},{211,9},{866,10},{231,9},{501,10},{530,10},{450,10},{230,10},{487,9},{494,10},{195,9},{23,9},{173,9},{239,9},{966,10},{6,10},{234,10},{113,10},{274,10},{334,10},{30,10},{706,10},{34,10},{914,10},{341,9},{71,9},{151,9},{339,9},{93,9},{125,9},{451,9},{754,10},{482,10},{335,9},{218,10},{994,10},{874,10},{858,10},{518,10},{498,10},{738,10},{362,10},{757,10},{477,9},{405,10},{463,9},{326,10},{495,9},{838,10},{1010,10},{298,10},{358,10},{359,9},{79,9},{977,10},{546,10},{0,2},{18,10},[0]={433,9}}
|
||||
local function huffmanEncode(raw)
|
||||
|
||||
local rawlen = #raw
|
||||
|
||||
--output is headed by the unencoded size as a 24-bit integer (65kB+ LZW encodings are easily possible here, 16MB not so much)
|
||||
local encoded = {
|
||||
char(rawlen & 0xff),
|
||||
char((rawlen >> 8) & 0xff),
|
||||
char((rawlen >> 16) & 0xff)
|
||||
}
|
||||
local encoded_length = 3
|
||||
local buffer = 0
|
||||
local buffer_length = 0
|
||||
|
||||
local code
|
||||
--the encoding would be way faster in C (most of the execution time of this function is spent calling string.byte)
|
||||
for i = 1, rawlen do
|
||||
code = codes[byte(raw,i)]
|
||||
buffer = buffer + (code[1] << buffer_length)
|
||||
buffer_length = buffer_length + code[2]
|
||||
while buffer_length>=8 do
|
||||
encoded_length = encoded_length + 1
|
||||
encoded[encoded_length] = char(buffer & 0xff)
|
||||
buffer = buffer >> 8
|
||||
buffer_length = buffer_length - 8
|
||||
end
|
||||
end
|
||||
|
||||
if buffer_length>0 then
|
||||
encoded[encoded_length+1] = char(buffer)
|
||||
end
|
||||
|
||||
return concat(encoded)
|
||||
end
|
||||
|
||||
--http://en.wikipedia.org/wiki/Huffman_coding#Decompression
|
||||
|
||||
local invcodes = {[2]={[0]="\254"},[5]={[22]="\1",[11]="\2"},[6]={[13]="\7",[35]="\6",[37]="\5",[58]="\3",[31]="\8",[9]="\13",[51]="\9",[55]="\10",[57]="\4",[59]="\15"},[7]={[1]="\14",[15]="\16",[87]="\31",[89]="\30",[62]="\26",[17]="\27",[97]="\19",[19]="\43",[10]="\12",[39]="\33",[41]="\24",[82]="\40",[3]="\32",[46]="\41",[47]="\38",[94]="\25",[65]="\23",[50]="\39",[26]="\11",[7]="\28",[33]="\18",[61]="\17",[25]="\42"},[8]={[111]="\101",[162]="\29",[2]="\34",[133]="\21",[142]="\36",[5]="\20",[21]="\37",[170]="\44",[130]="\22",[66]="\35"},[9]={[241]="\121",[361]="\104",[365]="\184",[125]="\227",[373]="\198",[253]="\117",[381]="\57",[270]="\49",[413]="\80",[290]="\47",[294]="\115",[38]="\112",[429]="\74",[433]="\0",[437]="\48",[158]="\183",[453]="\107",[166]="\111",[469]="\182",[477]="\241",[45]="\86",[489]="\69",[366]="\100",[497]="\61",[509]="\76",[49]="\53",[390]="\78",[279]="\196",[283]="\70",[414]="\98",[53]="\55",[422]="\109",[233]="\79",[349]="\89",[369]="\52",[14]="\105",[238]="\56",[319]="\162",[323]="\83",[327]="\63",[458]="\65",[335]="\231",[339]="\225",[337]="\114",[347]="\193",[493]="\139",[23]="\209",[359]="\250",[490]="\68",[42]="\54",[63]="\91",[286]="\97",[254]="\50",[510]="\108",[109]="\73",[67]="\103",[255]="\122",[69]="\170",[70]="\110",[407]="\176",[411]="\119",[110]="\120",[83]="\146",[149]="\163",[151]="\224",[85]="\51",[155]="\177",[79]="\251",[27]="\118",[447]="\159",[451]="\228",[455]="\175",[383]="\174",[463]="\243",[467]="\157",[173]="\210",[475]="\167",[177]="\84",[90]="\45",[487]="\206",[93]="\226",[495]="\245",[207]="\64",[127]="\147",[191]="\155",[511]="\153",[195]="\208",[197]="\85",[199]="\178",[181]="\82",[102]="\116",[103]="\71",[285]="\144",[105]="\102",[211]="\199",[213]="\123",[301]="\66",[305]="\46",[219]="\137",[81]="\67",[91]="\88",[157]="\130",[325]="\95",[29]="\58",[231]="\201",[117]="\99",[341]="\222",[237]="\77",[239]="\211",[71]="\223"},[10]={[710]="\149",[245]="\60",[742]="\172",[774]="\81",[134]="\151",[917]="\145",[274]="\216",[405]="\242",[146]="\194",[838]="\246",[298]="\248",[870]="\189",[1013]="\150",[894]="\190",[326]="\244",[330]="\166",[334]="\217",[465]="\179",[346]="\59",[354]="\180",[966]="\212",[974]="\143",[370]="\148",[998]="\154",[625]="\138",[382]="\161",[194]="\141",[198]="\126",[402]="\96",[206]="\185",[586]="\129",[721]="\187",[610]="\135",[618]="\181",[626]="\72",[226]="\62",[454]="\127",[658]="\113",[462]="\164",[234]="\214",[474]="\140",[242]="\106",[714]="\188",[730]="\87",[498]="\237",[746]="\125",[754]="\229",[786]="\128",[202]="\93",[18]="\255",[810]="\173",[846]="\131",[74]="\192",[842]="\142",[977]="\252",[858]="\235",[78]="\134",[874]="\234",[882]="\90",[646]="\92",[1006]="\160",[126]="\165",[914]="\221",[718]="\94",[738]="\238",[638]="\197",[482]="\230",[34]="\220",[962]="\133",[6]="\213",[706]="\219",[986]="\171",[994]="\233",[866]="\200",[1010]="\247",[98]="\169",[518]="\236",[494]="\207",[230]="\205",[542]="\191",[501]="\202",[530]="\203",[450]="\204",[209]="\158",[106]="\186",[590]="\136",[218]="\232",[733]="\124",[309]="\168",[221]="\152",[757]="\240",[113]="\215",[114]="\156",[362]="\239",[486]="\132",[358]="\249",[262]="\75",[30]="\218",[821]="\195",[546]="\253"}}
|
||||
|
||||
local function huffmanDecode(encoded)
|
||||
|
||||
local encoded_length = #encoded+1
|
||||
local h1,h2,h3 = byte(encoded, 1, 3)
|
||||
local original_length = (h3<<16) | (h2<<8) | h1
|
||||
local decoded = {}
|
||||
local decoded_length = 0
|
||||
local buffer = 0
|
||||
local buffer_length = 0
|
||||
local code
|
||||
local code_len = 2
|
||||
local temp
|
||||
local pos = 4
|
||||
|
||||
while decoded_length < original_length do
|
||||
if code_len <= buffer_length then
|
||||
temp = invcodes[code_len]
|
||||
code = buffer & (1 << code_len)-1
|
||||
if temp and temp[code] then --most of the time temp is nil
|
||||
decoded_length = decoded_length + 1
|
||||
decoded[decoded_length] = temp[code]
|
||||
buffer = buffer >> code_len
|
||||
buffer_length = buffer_length - code_len
|
||||
code_len = 2
|
||||
else
|
||||
code_len = code_len + 1
|
||||
if code_len > 10 then
|
||||
error("malformed code")
|
||||
end
|
||||
end
|
||||
else
|
||||
buffer = buffer | (byte(encoded, pos) << buffer_length)
|
||||
buffer_length = buffer_length + 8
|
||||
pos = pos + 1
|
||||
if pos > encoded_length then
|
||||
error("malformed code")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return concat(decoded)
|
||||
end
|
||||
|
||||
--escape sequences can't be palindromes
|
||||
local escseq = {
|
||||
"bbq",
|
||||
"wtf",
|
||||
"cat",
|
||||
"car",
|
||||
"bro",
|
||||
"moo",
|
||||
"sky",
|
||||
}
|
||||
|
||||
local function escapeSub(str)
|
||||
local genseq
|
||||
for i=1,#escseq do
|
||||
if not find(str, escseq[i]) then
|
||||
genseq = escseq[i]
|
||||
return genseq.."\n"..gsub(str,"\26",genseq)
|
||||
end
|
||||
end
|
||||
for i=30,200 do
|
||||
genseq = char(i, i-1, i+1)
|
||||
if not find(str, genseq) then
|
||||
return genseq.."\n"..gsub(str,"\26",genseq)
|
||||
end
|
||||
genseq = char(i, i, i+1)
|
||||
if not find(str, genseq) then
|
||||
return genseq.."\n"..gsub(str,"\26",genseq)
|
||||
end
|
||||
end
|
||||
error("<sub> could not be escaped")
|
||||
end
|
||||
|
||||
local function invEscapeSub(str)
|
||||
local escseq,body = match(str,"^(.-)\n(.-)$")
|
||||
return gsub(body,escseq,"\26")
|
||||
end
|
||||
|
||||
local dictionary
|
||||
local subtables
|
||||
|
||||
local function deserializeChunk(chunk)
|
||||
|
||||
local ctype,val = byte(chunk),sub(chunk,3)
|
||||
|
||||
if ctype == 89 then return dictionary[ val ]
|
||||
elseif ctype == 86 then
|
||||
local a,b,c = match(val,"^(.-),(.-),(.+)$")
|
||||
return Vector( tonumber(a), tonumber(b), tonumber(c) )
|
||||
elseif ctype == 65 then
|
||||
local a,b,c = match(val,"^(.-),(.-),(.+)$")
|
||||
return Angle( tonumber(a), tonumber(b), tonumber(c) )
|
||||
elseif ctype == 84 then
|
||||
local t = {}
|
||||
local tv = subtables[val]
|
||||
if not tv then
|
||||
tv = {}
|
||||
subtables[ val ] = tv
|
||||
end
|
||||
tv[#tv+1] = t
|
||||
return t
|
||||
elseif ctype == 78 then return tonumber(val)
|
||||
elseif ctype == 83 then return gsub(sub(val,2,-2),"»",";")
|
||||
elseif ctype == 66 then return val == "t"
|
||||
elseif ctype == 80 then return 1
|
||||
end
|
||||
|
||||
error(format("AD1 deserialization failed: invalid chunk (%u:%s)",ctype,val))
|
||||
|
||||
end
|
||||
|
||||
local function deserializeAD1(dupestring)
|
||||
|
||||
local header, extraHeader, dupeBlock, dictBlock = dupestring:match("%[Info%]\n(.+)\n%[More Information%]\n(.+)\n%[Save%]\n(.+)\n%[Dict%]\n(.+)")
|
||||
|
||||
if not header then
|
||||
error("unknown duplication format")
|
||||
end
|
||||
|
||||
local info = {}
|
||||
for k,v in header:gmatch("([^\n:]+):([^\n]+)") do
|
||||
info[k] = v
|
||||
end
|
||||
|
||||
local moreinfo = {}
|
||||
for k,v in extraHeader:gmatch("([^\n:]+):([^\n]+)") do
|
||||
moreinfo[k] = v
|
||||
end
|
||||
|
||||
dictionary = {}
|
||||
for k,v in dictBlock:gmatch("([^\n]+):\"(.-)\"") do
|
||||
dictionary[k] = v
|
||||
end
|
||||
|
||||
local dupe = {}
|
||||
for key,block in dupeBlock:gmatch("([^\n:]+):([^\n]+)") do
|
||||
|
||||
local tables = {}
|
||||
subtables = {}
|
||||
local head
|
||||
|
||||
for id,chunk in block:gmatch('([A-H0-9]+){(.-)}') do
|
||||
|
||||
--check if this table is the trunk
|
||||
if byte(id) == 72 then
|
||||
id = sub(id,2)
|
||||
head = id
|
||||
end
|
||||
|
||||
tables[id] = {}
|
||||
|
||||
for kv in gmatch(chunk,'[^;]+') do
|
||||
|
||||
local k,v = match(kv,'(.-)=(.+)')
|
||||
|
||||
if k then
|
||||
k = deserializeChunk( k )
|
||||
v = deserializeChunk( v )
|
||||
|
||||
tables[id][k] = v
|
||||
else
|
||||
v = deserializeChunk( kv )
|
||||
local tid = tables[id]
|
||||
tid[#tid+1]=v
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--Restore table references
|
||||
for id,tbls in pairs( subtables ) do
|
||||
for _,tbl in pairs( tbls ) do
|
||||
merge( tbl, tables[id] )
|
||||
end
|
||||
end
|
||||
|
||||
dupe[key] = tables[ head ]
|
||||
|
||||
end
|
||||
|
||||
return dupe, info, moreinfo
|
||||
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: Encode
|
||||
Desc: Generates the string for a dupe file with the given data.
|
||||
Params: <table> dupe, <table> info, <function> callback, <...> args
|
||||
Return: runs callback(<string> encoded_dupe, <...> args)
|
||||
]]
|
||||
function AdvDupe2.Encode(dupe, info, callback, ...)
|
||||
|
||||
info.check = "\r\n\t\n"
|
||||
|
||||
addPipeline{
|
||||
serialize,
|
||||
lzwEncode,
|
||||
huffmanEncode,
|
||||
escapeSub,
|
||||
callback,
|
||||
eax = dupe,
|
||||
REVISION = REVISION,
|
||||
info = info,
|
||||
args = {...},
|
||||
idx = 0,
|
||||
cbk = 5
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
--seperates the header and body and converts the header to a table
|
||||
local function getInfo(str)
|
||||
local last = str:find("\2")
|
||||
if not last then
|
||||
error("attempt to read AD2 file with malformed info block")
|
||||
end
|
||||
local info = {}
|
||||
local ss = str:sub(1,last-1)
|
||||
for k,v in ss:gmatch("(.-)\1(.-)\1") do
|
||||
info[k] = v
|
||||
end
|
||||
if not info.check or info.check ~= "\r\n\t\n" then
|
||||
error("attempt to read AD2 file with malformed info block")
|
||||
end
|
||||
return info, str:sub(last+2)
|
||||
end
|
||||
|
||||
--decoders for individual versions go here
|
||||
versions = {}
|
||||
|
||||
versions[1] = function(encodedDupe)
|
||||
local info, dupestring = getInfo(encodedDupe:sub(7))
|
||||
return deserialize(
|
||||
lzwDecode(
|
||||
huffmanDecode(
|
||||
invEscapeSub(dupestring)
|
||||
)
|
||||
)
|
||||
), info
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: Decode
|
||||
Desc: Generates the table for a dupe from the given string. Inverse of Encode
|
||||
Params: <string> encodedDupe, <function> callback, <...> args
|
||||
Return: runs callback(<boolean> success, <table/string> tbl, <table> info)
|
||||
]]
|
||||
function AdvDupe2.Decode(encodedDupe, callback, ...)
|
||||
|
||||
local sig, rev = encodedDupe:match("^(....)(.)")
|
||||
|
||||
if not rev then
|
||||
error("malformed dupe (wtf <5 chars long?!)")
|
||||
end
|
||||
|
||||
rev = rev:byte()
|
||||
|
||||
if sig ~= "AD2F" then
|
||||
if sig == "[Inf" then --legacy support, ENGAGE (AD1 dupe detected)
|
||||
local success, tbl, info, moreinfo = pcall(deserializeAD1, encodedDupe)
|
||||
|
||||
if success then
|
||||
info.size = #encodedDupe
|
||||
info.revision = 0
|
||||
info.ad1 = true
|
||||
else
|
||||
ErrorNoHalt(tbl)
|
||||
end
|
||||
|
||||
callback(success, tbl, info, moreinfo, ...)
|
||||
else
|
||||
error("unknown duplication format")
|
||||
end
|
||||
elseif rev > REVISION then
|
||||
error(format("this install lacks the codec version to parse the dupe (have rev %u, need rev %u)",REVISION,rev))
|
||||
elseif rev == 0 then
|
||||
error("attempt to use an invalid format revision (rev 0)")
|
||||
else
|
||||
local success, tbl, info = pcall(versions[rev], encodedDupe)
|
||||
|
||||
if success then
|
||||
info.revision = rev
|
||||
else
|
||||
ErrorNoHalt(tbl)
|
||||
end
|
||||
|
||||
callback(success, tbl, info, ...)
|
||||
end
|
||||
|
||||
end
|
92
lua/advdupe2/sv_file.lua
Normal file
92
lua/advdupe2/sv_file.lua
Normal file
@ -0,0 +1,92 @@
|
||||
--[[
|
||||
Title: Adv. Dupe 2 Filing Clerk (Serverside)
|
||||
|
||||
Desc: Reads/writes AdvDupe2 files.
|
||||
|
||||
Author: AD2 Team
|
||||
|
||||
Version: 1.0
|
||||
]]
|
||||
|
||||
function _R.Player:SteamIDSafe()
|
||||
return self:SteamID():gsub(":","_")
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: WriteAdvDupe2File
|
||||
Desc: Writes a dupe file to the dupe folder.
|
||||
Params: <string> dupe, <string> name
|
||||
Return: <boolean> success/<string> path
|
||||
]]
|
||||
function AdvDupe2.WriteFile(ply, name, dupe)
|
||||
|
||||
name = name:lower()
|
||||
|
||||
if name:find("[<>:\\\"|%?%*%.]") then return false end
|
||||
|
||||
name = name:gsub("//","/")
|
||||
|
||||
local path
|
||||
if SinglePlayer() then
|
||||
path = string.format("%s/%s", AdvDupe2.DataFolder, name)
|
||||
else
|
||||
path = string.format("%s/%s/%s", AdvDupe2.DataFolder, ply and ply:SteamIDSafe() or "=Public=", name)
|
||||
end
|
||||
|
||||
--if a file with this name already exists, we have to come up with a different name
|
||||
if file.Exists(path..".txt") then
|
||||
for i = 1, AdvDupe2.FileRenameTryLimit do
|
||||
--check if theres already a file with the name we came up with, and retry if there is
|
||||
--otherwise, we can exit the loop and write the file
|
||||
if not file.Exists(path.."_"..i..".txt") then
|
||||
path = path.."_"..i
|
||||
break
|
||||
end
|
||||
end
|
||||
--if we still can't find a unique name we give up
|
||||
if file.Exists(path..".txt") then return false end
|
||||
end
|
||||
|
||||
--write the file
|
||||
file.Write(path..".txt", dupe)
|
||||
|
||||
--returns if the write was successful and the name the path ended up being saved under
|
||||
return path..".txt", path:match("[^/]-$")
|
||||
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: ReadAdvDupe2File
|
||||
Desc: Reads a dupe file from the dupe folder.
|
||||
Params: <string> name
|
||||
Return: <string> contents
|
||||
]]
|
||||
function AdvDupe2.ReadFile(ply, name, dirOverride)
|
||||
if SinglePlayer() then
|
||||
return file.Read(string.format("%s/%s.txt", dirOverride or AdvDupe2.DataFolder, name))
|
||||
else
|
||||
local path = string.format("%s/%s/%s.txt", dirOverride or AdvDupe2.DataFolder, ply and ply:SteamIDSafe() or "=Public=", name)
|
||||
if(file.Size(path)/1024>tonumber(GetConVarString("AdvDupe2_MaxFileSize")))then
|
||||
return false
|
||||
else
|
||||
return file.Read(path)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function _R.Player:WriteAdvDupe2File(name, dupe)
|
||||
return AdvDupe2.WriteFile(self, name, dupe)
|
||||
end
|
||||
|
||||
function _R.Player:ReadAdvDupe2File(name)
|
||||
return AdvDupe2.ReadFile(self, name)
|
||||
end
|
||||
|
||||
function _R.Player:GetAdvDupe2Folder()
|
||||
if SinglePlayer() then
|
||||
return AdvDupe2.DataFolder
|
||||
else
|
||||
return string.format("%s/%s", AdvDupe2.DataFolder, self:SteamIDSafe())
|
||||
end
|
||||
end
|
131
lua/advdupe2/sv_misc.lua
Normal file
131
lua/advdupe2/sv_misc.lua
Normal file
@ -0,0 +1,131 @@
|
||||
--[[
|
||||
Title: Miscellaneous
|
||||
|
||||
Desc: Contains miscellaneous (serverside) things AD2 needs to function that don't fit anywhere else.
|
||||
|
||||
Author: TB
|
||||
|
||||
Version: 1.0
|
||||
]]
|
||||
|
||||
--[[
|
||||
Name: SavePositions
|
||||
Desc: Save the position of the entities to prevent sagging on dupe.
|
||||
Params: <entity> Constraint
|
||||
Returns: nil
|
||||
]]
|
||||
|
||||
local function SavePositions( Constraint )
|
||||
|
||||
if IsValid(Constraint) then
|
||||
|
||||
if Constraint.BuildDupeInfo then return end
|
||||
|
||||
if not Constraint.BuildDupeInfo then Constraint.BuildDupeInfo = {} end
|
||||
|
||||
Constraint.Identity = Constraint:GetCreationID()
|
||||
local Ent1
|
||||
local Ent2
|
||||
if IsValid(Constraint.Ent) then
|
||||
Constraint.BuildDupeInfo.Ent1Ang = Constraint.Ent:GetAngles()
|
||||
end
|
||||
|
||||
if IsValid(Constraint.Ent1) then
|
||||
Constraint.BuildDupeInfo.Ent1Ang = Constraint.Ent1:GetAngles()
|
||||
if(Constraint.Ent1:GetPhysicsObjectCount()>1)then
|
||||
Constraint.BuildDupeInfo.Bone1 = Constraint["Bone1"]
|
||||
Constraint.BuildDupeInfo.Bone1Pos = Constraint.Ent1:GetPhysicsObjectNum(Constraint["Bone1"]):GetPos() - Constraint.Ent1:GetPos()
|
||||
Constraint.BuildDupeInfo.Bone1Angle = Constraint.Ent1:GetPhysicsObjectNum(Constraint["Bone1"]):GetAngle()
|
||||
end
|
||||
if IsValid(Constraint.Ent2) then
|
||||
Constraint.BuildDupeInfo.EntityPos = Constraint.Ent1:GetPos() - Constraint.Ent2:GetPos()
|
||||
Constraint.BuildDupeInfo.Ent2Ang = Constraint.Ent2:GetAngles()
|
||||
if(Constraint.Ent2:GetPhysicsObjectCount()>1)then
|
||||
Constraint.BuildDupeInfo.Bone2 = Constraint["Bone2"]
|
||||
Constraint.BuildDupeInfo.Bone2Pos = Constraint.Ent2:GetPhysicsObjectNum(Constraint["Bone2"]):GetPos() - Constraint.Ent2:GetPos()
|
||||
Constraint.BuildDupeInfo.Bone2Angle = Constraint.Ent2:GetPhysicsObjectNum(Constraint["Bone2"]):GetAngle()
|
||||
end
|
||||
elseif IsValid(Constraint.Ent4) then
|
||||
Constraint.BuildDupeInfo.EntityPos = Constraint.Ent1:GetPos() - Constraint.Ent4:GetPos()
|
||||
Constraint.BuildDupeInfo.Ent4Ang = Constraint.Ent4:GetAngles()
|
||||
if(Constraint.Ent4:GetPhysicsObjectCount()>1)then
|
||||
Constraint.BuildDupeInfo.Bone2 = Constraint["Bone4"]
|
||||
Constraint.BuildDupeInfo.Bone2Pos = Constraint.Ent4:GetPhysicsObjectNum(Constraint["Bone4"]):GetPos() - Constraint.Ent4:GetPos()
|
||||
Constraint.BuildDupeInfo.Bone2Angle = Constraint.Ent4:GetPhysicsObjectNum(Constraint["Bone4"]):GetAngle()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
local function FixMagnet(Magnet)
|
||||
Magnet.Entity = Magnet
|
||||
end
|
||||
|
||||
//Find out when a Constraint is created
|
||||
hook.Add( "OnEntityCreated", "AdvDupe2_SavePositions", function(entity)
|
||||
|
||||
if not ValidEntity( entity ) then return end
|
||||
|
||||
local class = string.Explode("_",entity:GetClass())
|
||||
if class[2] == "magnet" then
|
||||
timer.Simple( 0, FixMagnet, entity )
|
||||
end
|
||||
|
||||
if class[1] == "phys" then
|
||||
timer.Simple( 0, SavePositions, entity )
|
||||
end
|
||||
|
||||
end )
|
||||
|
||||
-- Register camera entity class
|
||||
-- fixes key not being saved (Conna)
|
||||
local function CamRegister(Player, Pos, Ang, Key, Locked, Toggle, Vel, aVel, Frozen, Nocollide)
|
||||
if (!Key) then return end
|
||||
|
||||
local Camera = ents.Create("gmod_cameraprop")
|
||||
Camera:SetAngles(Ang)
|
||||
Camera:SetPos(Pos)
|
||||
Camera:Spawn()
|
||||
Camera:SetKey(Key)
|
||||
Camera:SetPlayer(Player)
|
||||
Camera:SetLocked(Locked)
|
||||
Camera.toggle = Toggle
|
||||
Camera:SetTracking(NULL, Vector(0))
|
||||
|
||||
if (Toggle == 1) then
|
||||
numpad.OnDown(Player, Key, "Camera_Toggle", Camera)
|
||||
else
|
||||
numpad.OnDown(Player, Key, "Camera_On", Camera)
|
||||
numpad.OnUp(Player, Key, "Camera_Off", Camera)
|
||||
end
|
||||
|
||||
if (Nocollide) then Camera:GetPhysicsObject():EnableCollisions(false) end
|
||||
|
||||
-- Merge table
|
||||
local Table = {
|
||||
key = Key,
|
||||
toggle = Toggle,
|
||||
locked = Locked,
|
||||
pl = Player,
|
||||
nocollide = nocollide
|
||||
}
|
||||
table.Merge(Camera:GetTable(), Table)
|
||||
|
||||
-- remove any camera that has the same key defined for this player then add the new one
|
||||
local ID = Player:UniqueID()
|
||||
GAMEMODE.CameraList[ID] = GAMEMODE.CameraList[ID] or {}
|
||||
local List = GAMEMODE.CameraList[ID]
|
||||
if (List[Key] and List[Key] != NULL ) then
|
||||
local Entity = List[Key]
|
||||
Entity:Remove()
|
||||
end
|
||||
List[Key] = Camera
|
||||
return Camera
|
||||
|
||||
end
|
||||
duplicator.RegisterEntityClass("gmod_cameraprop", CamRegister, "Pos", "Ang", "key", "locked", "toggle", "Vel", "aVel", "frozen", "nocollide")
|
310
lua/advdupe2/sv_networking.lua
Normal file
310
lua/advdupe2/sv_networking.lua
Normal file
@ -0,0 +1,310 @@
|
||||
--[[
|
||||
Title: Adv. Dupe 2 Networking (Serverside)
|
||||
|
||||
Desc: Handles file transfers and all that jazz.
|
||||
|
||||
Author: TB
|
||||
|
||||
Version: 1.0
|
||||
]]
|
||||
|
||||
include "nullesc.lua"
|
||||
AddCSLuaFile "nullesc.lua"
|
||||
|
||||
AdvDupe2.Network = {}
|
||||
|
||||
AdvDupe2.Network.Networks = {}
|
||||
AdvDupe2.Network.ClientNetworks = {}
|
||||
AdvDupe2.Network.SvStaggerSendRate = 0
|
||||
AdvDupe2.Network.ClStaggerSendRate = 0
|
||||
|
||||
local function CheckFileNameSv(path)
|
||||
if file.Exists(path..".txt") then
|
||||
for i = 1, AdvDupe2.FileRenameTryLimit do
|
||||
if not file.Exists(path.."_"..i..".txt") then
|
||||
return path.."_"..i..".txt"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return path..".txt"
|
||||
end
|
||||
|
||||
function AdvDupe2.UpdateProgressBar(ply,percent)
|
||||
umsg.Start("AdvDupe2_UpdateProgressBar",ply)
|
||||
umsg.Char(percent)
|
||||
umsg.End()
|
||||
end
|
||||
|
||||
function AdvDupe2.RemoveProgressBar(ply)
|
||||
umsg.Start("AdvDupe2_RemoveProgressBar",ply)
|
||||
umsg.End()
|
||||
end
|
||||
|
||||
//===========================================
|
||||
//========= Server To Client =========
|
||||
//===========================================
|
||||
|
||||
|
||||
--[[
|
||||
Name: AdvDupe2_SendFile
|
||||
Desc: Client has responded and is ready for the next chunk of data
|
||||
Params: Network table, Network ID
|
||||
Returns:
|
||||
]]
|
||||
function AdvDupe2_SendFile(ID)
|
||||
|
||||
local Net = AdvDupe2.Network.Networks[ID]
|
||||
local Network = AdvDupe2.Network
|
||||
|
||||
if(!IsValid(Net.Player))then
|
||||
AdvDupe2.Network.Networks[ID] = nil
|
||||
return
|
||||
end
|
||||
|
||||
local status = 0
|
||||
|
||||
local data = ""
|
||||
for i=1,tonumber(GetConVarString("AdvDupe2_ServerDataChunks")) do
|
||||
status = 0
|
||||
if(Net.LastPos==1)then status = 1 AdvDupe2.InitProgressBar(Net.Player,"Downloading:") end
|
||||
data = string.sub(Net.File, Net.LastPos, Net.LastPos+tonumber(GetConVarString("AdvDupe2_MaxDownloadBytes")))
|
||||
|
||||
Net.LastPos=Net.LastPos+tonumber(GetConVarString("AdvDupe2_MaxDownloadBytes"))+1
|
||||
if(Net.LastPos>=Net.Length)then status = 2 end
|
||||
|
||||
umsg.Start("AdvDupe2_RecieveFile", Net.Player)
|
||||
umsg.Short(status)
|
||||
umsg.String(data)
|
||||
umsg.End()
|
||||
|
||||
if(status==2)then break end
|
||||
end
|
||||
|
||||
AdvDupe2.UpdateProgressBar(Net.Player, math.floor((Net.LastPos/Net.Length)*100))
|
||||
|
||||
if(Net.LastPos>=Net.Length)then
|
||||
Net.Player.AdvDupe2.Downloading = false
|
||||
AdvDupe2.RemoveProgressBar(Net.Player)
|
||||
if(Net.Player.AdvDupe2.Entities && !Net.Player.AdvDupe2.GhostEntities)then
|
||||
AdvDupe2.StartGhosting(Net.Player)
|
||||
end
|
||||
|
||||
AdvDupe2.Network.Networks[ID] = nil
|
||||
return
|
||||
end
|
||||
|
||||
local Cur_Time = CurTime()
|
||||
local time = Network.SvStaggerSendRate - Cur_Time
|
||||
|
||||
timer.Simple(time, AdvDupe2_SendFile, ID)
|
||||
|
||||
if(time > 0)then
|
||||
AdvDupe2.Network.SvStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ServerSendRate")) + time
|
||||
else
|
||||
AdvDupe2.Network.SvStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ServerSendRate"))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
Name: EstablishNetwork
|
||||
Desc: Add user to the queue and set up to begin data sending
|
||||
Params: Player, File data
|
||||
Returns:
|
||||
]]
|
||||
function AdvDupe2.EstablishNetwork(ply, file)
|
||||
if(!IsValid(ply))then return end
|
||||
|
||||
if(!tobool(GetConVarString("AdvDupe2_AllowDownloading")))then
|
||||
AdvDupe2.Notify(ply,"Downloading is not allowed!",NOTIFY_ERROR,5)
|
||||
return
|
||||
end
|
||||
|
||||
file = AdvDupe2.Null.esc(file)
|
||||
|
||||
local id = ply:UniqueID()
|
||||
ply.AdvDupe2.Downloading = true
|
||||
AdvDupe2.Network.Networks[id] = {Player = ply, File=file, Length = #file, LastPos=1}
|
||||
|
||||
local Cur_Time = CurTime()
|
||||
local time = AdvDupe2.Network.SvStaggerSendRate - Cur_Time
|
||||
|
||||
if(time > 0)then
|
||||
AdvDupe2.Network.SvStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ServerSendRate")) + time
|
||||
timer.Simple(time, AdvDupe2_SendFile, id)
|
||||
else
|
||||
AdvDupe2.Network.SvStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ServerSendRate"))
|
||||
AdvDupe2_SendFile(id)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function AdvDupe2.RecieveNextStep(id)
|
||||
if(!IsValid(AdvDupe2.Network.ClientNetworks[id].Player))then AdvDupe2.Network.ClientNetworks[id] = nil return end
|
||||
umsg.Start("AdvDupe2_RecieveNextStep", AdvDupe2.Network.ClientNetworks[id].Player)
|
||||
umsg.Short(tonumber(GetConVarString("AdvDupe2_MaxUploadBytes")))
|
||||
umsg.Short(tonumber(GetConVarString("AdvDupe2_ClientDataChunks")))
|
||||
umsg.End()
|
||||
end
|
||||
|
||||
//===========================================
|
||||
//========= Client To Server =========
|
||||
//===========================================
|
||||
|
||||
|
||||
local function GetPlayersFolder(ply)
|
||||
local path
|
||||
if SinglePlayer() then
|
||||
path = string.format("%s", AdvDupe2.DataFolder)
|
||||
else
|
||||
path = string.format("%s/%s", AdvDupe2.DataFolder, ply:SteamID():gsub(":","_"))
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: AdvDupe2_InitRecieveFile
|
||||
Desc: Start the file recieving process and send the servers settings to the client
|
||||
Params: concommand
|
||||
Returns:
|
||||
]]
|
||||
local function AdvDupe2_InitRecieveFile( ply, cmd, args )
|
||||
if(!IsValid(ply))then return end
|
||||
if(!tobool(GetConVarString("AdvDupe2_AllowUploading")))then
|
||||
umsg.Start("AdvDupe2_UploadRejected", ply)
|
||||
umsg.Bool(true)
|
||||
umsg.End()
|
||||
AdvDupe2.Notify(ply, "Uploading is not allowed!",NOTIFY_ERROR,5)
|
||||
return
|
||||
elseif(ply.AdvDupe2.Pasting || ply.AdvDupe2.Downloading)then
|
||||
umsg.Start("AdvDupe2_UploadRejected", ply)
|
||||
umsg.Bool(false)
|
||||
umsg.End()
|
||||
AdvDupe2.Notify(ply, "Duplicator is Busy!",NOTIFY_ERROR,5)
|
||||
return
|
||||
end
|
||||
|
||||
local path = args[1]
|
||||
local area = tonumber(args[2])
|
||||
|
||||
if(area==0)then
|
||||
path = GetPlayersFolder(ply).."/"..path
|
||||
elseif(area==1)then
|
||||
if(!tobool(GetConVarString("AdvDupe2_AllowPublicFolder")))then
|
||||
umsg.Start("AdvDupe2_UploadRejected", ply)
|
||||
umsg.Bool(true)
|
||||
umsg.End()
|
||||
AdvDupe2.Notify(ply,"Public Folder is disabled."..dupe,NOTIFY_ERROR)
|
||||
return
|
||||
end
|
||||
path = AdvDupe2.DataFolder.."/=Public=/"..path
|
||||
else
|
||||
path = "adv_duplicator/"..ply:SteamIDSafe().."/"..path
|
||||
end
|
||||
|
||||
local id = ply:UniqueID()
|
||||
if(AdvDupe2.Network.ClientNetworks[id])then return false end
|
||||
ply.AdvDupe2.Downloading = true
|
||||
ply.AdvDupe2.Uploading = true
|
||||
|
||||
AdvDupe2.Network.ClientNetworks[id] = {Player = ply, Data = "", Size = 0, Name = path, SubN = args[3], SubQ = args[4], ParentID = tonumber(args[5]), Parts = 0}
|
||||
|
||||
local Cur_Time = CurTime()
|
||||
local time = AdvDupe2.Network.ClStaggerSendRate - Cur_Time
|
||||
if(time > 0)then
|
||||
AdvDupe2.Network.ClStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ClientSendRate")) + time
|
||||
AdvDupe2.Network.ClientNetworks[id].NextSend = time + Cur_Time
|
||||
timer.Simple(time, AdvDupe2.RecieveNextStep, id)
|
||||
else
|
||||
AdvDupe2.Network.ClStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ClientSendRate"))
|
||||
AdvDupe2.Network.ClientNetworks[id].NextSend = Cur_Time
|
||||
AdvDupe2.RecieveNextStep(id)
|
||||
end
|
||||
|
||||
end
|
||||
concommand.Add("AdvDupe2_InitRecieveFile", AdvDupe2_InitRecieveFile)
|
||||
|
||||
|
||||
local function AdvDupe2_SetNextResponse(id)
|
||||
|
||||
local Cur_Time = CurTime()
|
||||
local time = AdvDupe2.Network.ClStaggerSendRate - Cur_Time
|
||||
if(time > 0)then
|
||||
AdvDupe2.Network.ClStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ClientSendRate")) + time
|
||||
AdvDupe2.Network.ClientNetworks[id].NextSend = time + Cur_Time
|
||||
timer.Simple(time, AdvDupe2.RecieveNextStep, id)
|
||||
else
|
||||
AdvDupe2.Network.ClStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ClientSendRate"))
|
||||
AdvDupe2.Network.ClientNetworks[id].NextSend = Cur_Time
|
||||
AdvDupe2.RecieveNextStep(id)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: AdvDupe2_RecieveFile
|
||||
Desc: Recieve file data from the client to save on the server
|
||||
Params: concommand
|
||||
Returns:
|
||||
]]
|
||||
local function AdvDupe2_RecieveFile(ply, cmd, args)
|
||||
if(!IsValid(ply))then return end
|
||||
|
||||
local id = ply:UniqueID()
|
||||
if(!AdvDupe2.Network.ClientNetworks[id])then return end
|
||||
local Net = AdvDupe2.Network.ClientNetworks[id]
|
||||
|
||||
//Someone tried to mess with upload concommands
|
||||
if(Net.NextSend - CurTime()>0)then
|
||||
AdvDupe2.Network.ClientNetworks[id]=nil
|
||||
ply.AdvDupe2.Downloading = false
|
||||
ply.AdvDupe2.Uploading = false
|
||||
|
||||
umsg.Start("AdvDupe2_UploadRejected")
|
||||
umsg.Bool(true)
|
||||
umsg.End()
|
||||
AdvDupe2.Notify(ply,"Upload Rejected!",NOTIFY_GENERIC,5)
|
||||
end
|
||||
|
||||
local data = args[2]
|
||||
|
||||
Net.Data = Net.Data..data
|
||||
Net.Parts = Net.Parts + 1
|
||||
|
||||
if(tonumber(args[1])!=0)then
|
||||
Net.Data = string.gsub(Net.Data, Net.SubN, "\10")
|
||||
Net.Data = string.gsub(Net.Data, Net.SubQ, [["]])
|
||||
Net.Name = CheckFileNameSv(Net.Name)
|
||||
local filename = string.Explode("/", Net.Name)
|
||||
Net.FileName = string.sub(filename[#filename], 1, -5)
|
||||
|
||||
file.Write(Net.Name, AdvDupe2.Null.invesc(Net.Data))
|
||||
|
||||
umsg.Start("AdvDupe2_AddFile",ply)
|
||||
umsg.String(Net.FileName)
|
||||
umsg.Short(Net.ParentID)
|
||||
umsg.Bool(true)
|
||||
umsg.End()
|
||||
|
||||
AdvDupe2.Network.ClientNetworks[id]=nil
|
||||
ply.AdvDupe2.Downloading = false
|
||||
ply.AdvDupe2.Uploading = false
|
||||
if(ply.AdvDupe2.Entities && !ply.AdvDupe2.GhostEntities)then
|
||||
AdvDupe2.StartGhosting(ply)
|
||||
end
|
||||
|
||||
umsg.Start("AdvDupe2_UploadRejected")
|
||||
umsg.Bool(false)
|
||||
umsg.End()
|
||||
AdvDupe2.Notify(ply,"File successfully uploaded!",NOTIFY_GENERIC,5)
|
||||
return
|
||||
end
|
||||
|
||||
if(Net.Parts == tonumber(GetConVarString("AdvDupe2_ClientDataChunks")))then
|
||||
Net.Parts = 0
|
||||
AdvDupe2_SetNextResponse(id)
|
||||
end
|
||||
end
|
||||
concommand.Add("AdvDupe2_RecieveFile", AdvDupe2_RecieveFile)
|
69
lua/autorun/client/advdupe2_cl_init.lua
Normal file
69
lua/autorun/client/advdupe2_cl_init.lua
Normal file
@ -0,0 +1,69 @@
|
||||
AdvDupe2 = {
|
||||
Version = "1.0.0",
|
||||
Revision = 1
|
||||
}
|
||||
|
||||
AdvDupe2.DataFolder = "advdupe2" --name of the folder in data where dupes will be saved
|
||||
|
||||
AdvDupe2.FileRenameTryLimit = 256
|
||||
|
||||
include "advdupe2/cl_browser.lua"
|
||||
include "advdupe2/cl_file.lua"
|
||||
include "advdupe2/cl_networking.lua"
|
||||
|
||||
function AdvDupe2.Notify(msg,typ,dur)
|
||||
surface.PlaySound(typ == 1 and "buttons/button10.wav" or "ambient/water/drip1.wav")
|
||||
GAMEMODE:AddNotify(msg, typ or NOTIFY_GENERIC, dur or 5)
|
||||
if not SinglePlayer() then
|
||||
print("[AdvDupe2Notify]\t"..msg)
|
||||
end
|
||||
end
|
||||
|
||||
function AdvDupe2.ShowSplash()
|
||||
local ad2folder
|
||||
for k,v in pairs(GetAddonList()) do
|
||||
if GetAddonInfo(v).Name == "Adv. Duplicator 2" then
|
||||
ad2folder = v
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local splash = vgui.Create("DFrame")
|
||||
splash:SetSize(512, 316) // Make it 1/4 the users screen size
|
||||
splash:SetPos((ScrW()/2) - splash:GetWide()/2, (ScrH()/2) - splash:GetTall()/2)
|
||||
splash:SetVisible( true )
|
||||
splash:SetTitle("")
|
||||
splash:SetDraggable( true )
|
||||
splash:ShowCloseButton( true )
|
||||
splash.Paint = function( self )
|
||||
surface.SetDrawColor(255, 255, 255, 255)
|
||||
surface.DrawRect(0, 0, self:GetWide(), self:GetTall())
|
||||
end
|
||||
splash:MakePopup()
|
||||
|
||||
local logo = vgui.Create("TGAImage", splash)
|
||||
logo:SetPos(0, 24)
|
||||
logo:SetSize(512, 128)
|
||||
logo:LoadTGAImage(("addons/%s/materials/gui/ad2logo.tga"):format(ad2folder),"LOL it doesn't actually have to be 'MOD'")
|
||||
|
||||
local version = vgui.Create("DLabel", splash)
|
||||
version:SetPos(512 - (512-446)/2 - 85,140) // Position
|
||||
version:SetColor(Color(0,0,0,255)) // Color
|
||||
version:SetText(("v%s (rev. %u)"):format(AdvDupe2.Version, AdvDupe2.Revision)) // Text
|
||||
version:SizeToContents()
|
||||
|
||||
local credit = vgui.Create("DLabel", splash)
|
||||
credit:SetPos((512-446)/2 + 16,190)
|
||||
credit:SetColor(Color(64,32,16,255))
|
||||
credit:SetFont("Trebuchet24")
|
||||
credit:SetText("Developed by: TB and emspike\n\nHosted by: Google Code")
|
||||
credit:SizeToContents()
|
||||
end
|
||||
|
||||
usermessage.Hook("AdvDupe2Notify",function(um)
|
||||
AdvDupe2.Notify(um:ReadString(),um:ReadChar(),um:ReadChar())
|
||||
end)
|
||||
|
||||
timer.Simple(0, function()
|
||||
AdvDupe2.ProgressBar={}
|
||||
end)
|
88
lua/autorun/server/advdupe2_sv_init.lua
Normal file
88
lua/autorun/server/advdupe2_sv_init.lua
Normal file
@ -0,0 +1,88 @@
|
||||
AdvDupe2 = {
|
||||
Version = "1.0.0",
|
||||
Revision = 1
|
||||
}
|
||||
|
||||
AdvDupe2.DataFolder = "advdupe2" --name of the folder in data where dupes will be saved
|
||||
|
||||
AdvDupe2.FileRenameTryLimit = 256
|
||||
|
||||
include "advdupe2/sv_clipboard.lua"
|
||||
include "advdupe2/sv_codec.lua"
|
||||
include "advdupe2/sv_file.lua"
|
||||
include "advdupe2/sv_networking.lua"
|
||||
include "advdupe2/sv_misc.lua"
|
||||
|
||||
AddCSLuaFile "autorun/client/advdupe2_cl_init.lua"
|
||||
AddCSLuaFile "advdupe2/cl_browser.lua"
|
||||
AddCSLuaFile "advdupe2/cl_networking.lua"
|
||||
AddCSLuaFile "advdupe2/cl_file.lua"
|
||||
|
||||
resource.AddFile("materials/gui/ad2logo.tga")
|
||||
resource.AddFile("materials/gui/silkicons/help.vtf")
|
||||
resource.AddFile("materials/gui/silkicons/help.vmt")
|
||||
|
||||
function AdvDupe2.Notify(ply,msg,typ,dur)
|
||||
umsg.Start("AdvDupe2Notify",ply)
|
||||
umsg.String(msg)
|
||||
umsg.Char(typ or NOTIFY_GENERIC)
|
||||
umsg.Char(dur or 5)
|
||||
umsg.End()
|
||||
print("[AdvDupe2Notify]",msg)
|
||||
end
|
||||
|
||||
local function RemovePlayersFiles(ply)
|
||||
|
||||
if(SinglePlayer() || !tobool(GetConVarString("AdvDupe2_RemoveFilesOnDisconnect")))then return end
|
||||
|
||||
local function TFind(Search, Folders, Files)
|
||||
Search = string.sub(Search, 6, -2)
|
||||
for k,v in pairs(Files)do
|
||||
file.Delete(Search..v)
|
||||
end
|
||||
|
||||
for k,v in pairs(Folders)do
|
||||
file.TFind("Data/"..Search..v.."/*", TFind)
|
||||
end
|
||||
end
|
||||
file.TFind("Data/"..ply:GetAdvDupe2Folder().."/*", TFind)
|
||||
end
|
||||
|
||||
CreateConVar("AdvDupe2_MaxFileSize", "200", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_MaxEntities", "0", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_MaxConstraints", "300", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_AllowUploading", "true", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_AllowDownloading", "true", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_AllowPublicFolder", "true", {FCVAR_ARCHIVE})
|
||||
|
||||
CreateConVar("AdvDupe2_MaxContraptionEntities", "10", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_MaxContraptionConstraints", "15", {FCVAR_ARCHIVE})
|
||||
|
||||
CreateConVar("AdvDupe2_MaxAreaCopySize", "2500", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_RemoveFilesOnDisconnect", "false", {FCVAR_ARCHIVE})
|
||||
|
||||
CreateConVar("AdvDupe2_FileModificationDelay", "5", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_UpdateFilesDelay", "10", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_AllowNPCPasting", "false", {FCVAR_ARCHIVE})
|
||||
|
||||
CreateConVar("AdvDupe2_MaxDownloadBytes", "200", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_MaxUploadBytes", "180", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_ServerSendRate", "0.15", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_ClientSendRate", "0.15", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_ServerDataChunks", "4", {FCVAR_ARCHIVE})
|
||||
CreateConVar("AdvDupe2_ClientDataChunks", "4", {FCVAR_ARCHIVE})
|
||||
|
||||
cvars.AddChangeCallback("AdvDupe2_RemoveFilesOnDisconnect",
|
||||
function(cvar, preval, newval)
|
||||
if(tobool(newval))then
|
||||
hook.Add("PlayerDisconnected", "AdvDupe2_RemovePlayersFiles", RemovePlayersFiles)
|
||||
else
|
||||
hook.Remove("PlayerDisconnected", "AdvDupe2_RemovePlayersFiles")
|
||||
end
|
||||
end)
|
||||
hook.Add("Initialize", "AdvDupe2_CheckServerSettings",
|
||||
function()
|
||||
if(tobool(GetConVarString("AdvDupe2_RemoveFilesOnDisconnect")))then
|
||||
hook.Add("PlayerDisconnected", "AdvDupe2_RemovePlayersFiles", RemovePlayersFiles)
|
||||
end
|
||||
end)
|
6
lua/entities/gmod_contr_spawner/cl_init.lua
Normal file
6
lua/entities/gmod_contr_spawner/cl_init.lua
Normal file
@ -0,0 +1,6 @@
|
||||
include("shared.lua")
|
||||
|
||||
function ENT:Draw()
|
||||
self.BaseClass.Draw(self)
|
||||
self.Entity:DrawModel()
|
||||
end
|
293
lua/entities/gmod_contr_spawner/init.lua
Normal file
293
lua/entities/gmod_contr_spawner/init.lua
Normal file
@ -0,0 +1,293 @@
|
||||
--[[
|
||||
Title: Adv. Dupe 2 Contraption Spawner
|
||||
|
||||
Desc: A mobile duplicator
|
||||
|
||||
Author: TB
|
||||
|
||||
Version: 1.0
|
||||
]]
|
||||
|
||||
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
if(WireLib)then
|
||||
include('entities/base_wire_entity/init.lua')
|
||||
end
|
||||
include('shared.lua')
|
||||
|
||||
|
||||
function ENT:Initialize()
|
||||
|
||||
self.Entity:SetMoveType( MOVETYPE_NONE )
|
||||
self.Entity:PhysicsInit( SOLID_VPHYSICS )
|
||||
self.Entity:SetCollisionGroup( COLLISION_GROUP_WORLD )
|
||||
self.Entity:DrawShadow( false )
|
||||
|
||||
local phys = self.Entity:GetPhysicsObject()
|
||||
if phys:IsValid() then
|
||||
phys:Wake()
|
||||
end
|
||||
|
||||
self.UndoList = {}
|
||||
self.Ghosts = {}
|
||||
|
||||
self.SpawnLastValue = 0
|
||||
self.UndoLastValue = 0
|
||||
|
||||
self.LastSpawnTime = 0
|
||||
|
||||
self.CurrentPropCount = 0
|
||||
|
||||
if WireLib then
|
||||
self.Inputs = Wire_CreateInputs(self.Entity, {"Spawn", "Undo"})
|
||||
self.Outputs = WireLib.CreateSpecialOutputs(self.Entity, {"Out", "LastSpawned"}, { "NORMAL", "ENTITY" })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------*
|
||||
* Sets options for this spawner
|
||||
*-----------------------------------------------------------------------*/
|
||||
function ENT:SetOptions(ply, delay, undo_delay, key, undo_key, disgrav, disdrag, addvel )
|
||||
|
||||
self.delay = delay
|
||||
self.undo_delay = undo_delay
|
||||
|
||||
--Key bindings
|
||||
self.key = key
|
||||
self.undo_key = undo_key
|
||||
|
||||
numpad.Remove( self.CreateKey )
|
||||
numpad.Remove( self.UndoKey )
|
||||
self.CreateKey = numpad.OnDown( ply, self.key, "ContrSpawnerCreate", self.Entity, true )
|
||||
self.UndoKey = numpad.OnDown( ply, self.undo_key, "ContrSpawnerUndo", self.Entity, true )
|
||||
self.DisableGravity = disgrav
|
||||
self.DisableDrag = disdrag
|
||||
self.AddVelocity = addvel
|
||||
|
||||
self:ShowOutput()
|
||||
end
|
||||
|
||||
function ENT:UpdateOptions( options )
|
||||
self:SetOptions( options["delay"], options["undo_delay"], options["key"], options["undo_key"])
|
||||
end
|
||||
|
||||
|
||||
function ENT:AddGhosts()
|
||||
local moveable = self:GetPhysicsObject():IsMoveable()
|
||||
self:GetPhysicsObject():EnableMotion(false)
|
||||
|
||||
local EntTable
|
||||
local GhostEntity
|
||||
local Offset = self.DupeAngle - self.EntAngle
|
||||
local Phys
|
||||
for EntIndex,v in pairs(self.EntityTable)do
|
||||
if(EntIndex!=self.HeadEnt)then
|
||||
if(self.EntityTable[EntIndex].Class=="gmod_contr_spawner")then self.EntityTable[EntIndex] = nil continue end
|
||||
EntTable = table.Copy(self.EntityTable[EntIndex])
|
||||
if(EntTable.BuildDupeInfo && EntTable.BuildDupeInfo.PhysicsObjects)then
|
||||
Phys = EntTable.BuildDupeInfo.PhysicsObjects[0]
|
||||
else
|
||||
v.BuildDupeInfo = {}
|
||||
v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects)
|
||||
Phys = EntTable.PhysicsObjects[0]
|
||||
end
|
||||
|
||||
GhostEntity = nil
|
||||
|
||||
if(EntTable.Model==nil || !util.IsValidModel(EntTable.Model)) then EntTable.Model="models/error.mdl" end
|
||||
|
||||
if ( EntTable.Model:sub( 1, 1 ) == "*" ) then
|
||||
GhostEntity = ents.Create( "func_physbox" )
|
||||
else
|
||||
GhostEntity = ents.Create( "gmod_ghost" )
|
||||
end
|
||||
|
||||
// If there are too many entities we might not spawn..
|
||||
if ( !GhostEntity || GhostEntity == NULL ) then return end
|
||||
|
||||
duplicator.DoGeneric( GhostEntity, EntTable )
|
||||
|
||||
GhostEntity:Spawn()
|
||||
|
||||
GhostEntity:DrawShadow( false )
|
||||
GhostEntity:SetMoveType( MOVETYPE_NONE )
|
||||
GhostEntity:SetSolid( SOLID_VPHYSICS );
|
||||
GhostEntity:SetNotSolid( true )
|
||||
GhostEntity:SetRenderMode( RENDERMODE_TRANSALPHA )
|
||||
GhostEntity:SetColor( 255, 255, 255, 150 )
|
||||
|
||||
GhostEntity:SetAngles(Phys.Angle)
|
||||
GhostEntity:SetPos(self:GetPos() + Phys.Pos - self.Offset)
|
||||
self:SetAngles(self.EntAngle)
|
||||
GhostEntity:SetParent( self )
|
||||
self:SetAngles(self.DupeAngle)
|
||||
self.Ghosts[EntIndex] = GhostEntity
|
||||
end
|
||||
end
|
||||
self:SetAngles(self.DupeAngle)
|
||||
self:GetPhysicsObject():EnableMotion(moveable)
|
||||
end
|
||||
|
||||
function ENT:GetCreationDelay() return self.delay end
|
||||
function ENT:GetDeletionDelay() return self.undo_delay end
|
||||
|
||||
function ENT:OnTakeDamage( dmginfo ) self.Entity:TakePhysicsDamage( dmginfo ) end
|
||||
|
||||
function ENT:SetDupeInfo( HeadEnt, EntityTable, ConstraintTable )
|
||||
|
||||
self.HeadEnt = HeadEnt
|
||||
self.EntityTable = EntityTable
|
||||
self.ConstraintTable = ConstraintTable
|
||||
if(!self.DupeAngle)then self.DupeAngle = self:GetAngles() end
|
||||
if(!self.EntAngle)then self.EntAngle = EntityTable[HeadEnt].PhysicsObjects[0].Angle end
|
||||
if(!self.Offset)then self.Offset = self.EntityTable[HeadEnt].PhysicsObjects[0].Pos end
|
||||
self.EntityTable[HeadEnt].PhysicsObjects[0].Pos = Vector(0,0,0)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
function ENT:DoSpawn( ply )
|
||||
|
||||
self.EntityTable[self.HeadEnt].PhysicsObjects[0].Pos = self:GetPos()
|
||||
self.EntityTable[self.HeadEnt].PhysicsObjects[0].Angle = self:GetAngles()
|
||||
for k,v in pairs(self.Ghosts)do
|
||||
self.EntityTable[k].PhysicsObjects[0].Pos = v:GetPos()
|
||||
self.EntityTable[k].PhysicsObjects[0].Angle = v:GetAngles()
|
||||
end
|
||||
|
||||
/*local AngleOffset = self.EntAngle
|
||||
AngleOffset = self:GetAngles() - AngleOffset
|
||||
local AngleOffset2 = Angle(0,0,0)
|
||||
//AngleOffset2.y = AngleOffset.y
|
||||
AngleOffset2:RotateAroundAxis(self:GetUp(), AngleOffset.y)
|
||||
AngleOffset2:RotateAroundAxis(self:GetRight(),AngleOffset.p)
|
||||
AngleOffset2:RotateAroundAxis(self:GetForward(),AngleOffset.r)*/
|
||||
|
||||
local Ents, Constrs = AdvDupe2.duplicator.Paste(ply, table.Copy(self.EntityTable), table.Copy(self.ConstraintTable), nil, nil, Vector(0,0,0), true)
|
||||
local i = #self.UndoList+1
|
||||
self.UndoList[i] = Ents
|
||||
|
||||
undo.Create("contraption_spawns")
|
||||
local phys
|
||||
for k,ent in pairs(Ents)do
|
||||
phys = ent:GetPhysicsObject()
|
||||
if IsValid(phys) then
|
||||
phys:Wake()
|
||||
if(self.DisableGravity==1)then phys:EnableGravity(false) end
|
||||
if(self.DisableDrag==1)then phys:EnableDrag(false) end
|
||||
phys:EnableMotion(true)
|
||||
if(self.AddVelocity==1)then
|
||||
phys:SetVelocity( self:GetVelocity() )
|
||||
phys:AddAngleVelocity( self:GetPhysicsObject():GetAngleVelocity() )
|
||||
end
|
||||
end
|
||||
|
||||
undo.AddEntity(ent)
|
||||
end
|
||||
|
||||
undo.SetPlayer(ply)
|
||||
undo.Finish()
|
||||
|
||||
if(self.undo_delay>0)then
|
||||
timer.Simple(self.undo_delay, function()
|
||||
if(self.UndoList && self.UndoList[i])then
|
||||
for k,ent in pairs(self.UndoList[i]) do
|
||||
if(IsValid(ent)) then
|
||||
ent:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
function ENT:DoUndo( ply )
|
||||
|
||||
if(!self.UndoList || #self.UndoList == 0)then return end
|
||||
|
||||
local entities = self.UndoList[ #self.UndoList ]
|
||||
self.UndoList[ #self.UndoList ] = nil
|
||||
for _,ent in pairs(entities) do
|
||||
if (IsValid(ent)) then
|
||||
ent:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:TriggerInput(iname, value)
|
||||
local ply = self:GetPlayer()
|
||||
|
||||
if(iname == "Spawn")then
|
||||
if ((value > 0) == self.SpawnLastValue) then return end
|
||||
self.SpawnLastValue = (value > 0)
|
||||
|
||||
if(self.SpawnLastValue)then
|
||||
local delay = self:GetCreationDelay()
|
||||
if (delay == 0) then self:DoSpawn( ply ) return end
|
||||
if(CurTime() < self.LastSpawnTime)then return end
|
||||
self:DoSpawn( ply )
|
||||
self.LastSpawnTime=CurTime()+delay
|
||||
end
|
||||
elseif (iname == "Undo") then
|
||||
// Same here
|
||||
if((value > 0) == self.UndoLastValue)then return end
|
||||
self.UndoLastValue = (value > 0)
|
||||
|
||||
if(self.UndoLastValue)then self:DoUndo(ply) end
|
||||
end
|
||||
end
|
||||
|
||||
local text2 = {"Enabled", "Disabled"}
|
||||
function ENT:ShowOutput()
|
||||
local text = "\nGravity: "
|
||||
if(self.DisableGravity==1)then text=text.."Enabled" else text=text.."Disabled" end
|
||||
text=text.."\nDrag: "
|
||||
if(self.DisableDrag==1)then text=text.."Enabled" else text=text.."Disabled" end
|
||||
text=text.."\nVelocity: "
|
||||
if(self.AddVelocity==1)then text=text.."Enabled" else text=text.."Disabled" end
|
||||
|
||||
self.Entity:SetOverlayText(
|
||||
"Spawn Delay: " .. tostring(self:GetCreationDelay()) ..
|
||||
"\nUndo Delay: ".. tostring(self:GetDeletionDelay()) ..
|
||||
text
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------*
|
||||
* Handler for spawn keypad input
|
||||
*-----------------------------------------------------------------------*/
|
||||
function SpawnContrSpawner( ply, ent )
|
||||
|
||||
if (!ent || !ent:IsValid()) then return end
|
||||
|
||||
local delay = ent:GetTable():GetCreationDelay()
|
||||
|
||||
if(delay == 0) then
|
||||
ent:DoSpawn( ply )
|
||||
return
|
||||
end
|
||||
|
||||
if(CurTime() < ent.LastSpawnTime)then return end
|
||||
ent:DoSpawn( ply )
|
||||
ent.LastSpawnTime=CurTime()+delay
|
||||
end
|
||||
|
||||
/*-----------------------------------------------------------------------*
|
||||
* Handler for undo keypad input
|
||||
*-----------------------------------------------------------------------*/
|
||||
function UndoContrSpawner( ply, ent )
|
||||
if (!ent || !ent:IsValid()) then return end
|
||||
ent:DoUndo( ply, true )
|
||||
end
|
||||
|
||||
numpad.Register( "ContrSpawnerCreate", SpawnContrSpawner )
|
||||
numpad.Register( "ContrSpawnerUndo", UndoContrSpawner )
|
10
lua/entities/gmod_contr_spawner/shared.lua
Normal file
10
lua/entities/gmod_contr_spawner/shared.lua
Normal file
@ -0,0 +1,10 @@
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = WireLib and "base_wire_entity" or "base_gmodentity"
|
||||
ENT.PrintName = "Contraption Spawner"
|
||||
ENT.Author = "TB"
|
||||
ENT.Contact = ""
|
||||
ENT.Purpose = ""
|
||||
ENT.Instructions = ""
|
||||
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = false
|
2051
lua/weapons/gmod_tool/stools/advdupe2.lua
Normal file
2051
lua/weapons/gmod_tool/stools/advdupe2.lua
Normal file
File diff suppressed because it is too large
Load Diff
BIN
materials/gui/ad2logo.tga
Normal file
BIN
materials/gui/ad2logo.tga
Normal file
Binary file not shown.
8
materials/gui/silkicons/help.vmt
Normal file
8
materials/gui/silkicons/help.vmt
Normal file
@ -0,0 +1,8 @@
|
||||
"UnlitGeneric"
|
||||
{
|
||||
"$basetexture" "gui/silkicons/help"
|
||||
"$ignorez" 1
|
||||
"$vertexcolor" 1
|
||||
"$vertexalpha" 1
|
||||
"$nolod" 1
|
||||
}
|
BIN
materials/gui/silkicons/help.vtf
Normal file
BIN
materials/gui/silkicons/help.vtf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user