commit 23d7d5e9b27e4008c572e044dfb8ce1c13a4791a Author: emspike Date: Sun Oct 30 02:59:26 2011 +0000 Initial commit. diff --git a/icon.tga b/icon.tga new file mode 100644 index 0000000..6b9a7e6 Binary files /dev/null and b/icon.tga differ diff --git a/info.txt b/info.txt new file mode 100644 index 0000000..116967a --- /dev/null +++ b/info.txt @@ -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." +} diff --git a/lua/advdupe2/cl_browser.lua b/lua/advdupe2/cl_browser.lua new file mode 100644 index 0000000..82cb7ee --- /dev/null +++ b/lua/advdupe2/cl_browser.lua @@ -0,0 +1,1057 @@ +--[[ + Title: Adv. Dupe 2 File Browser + + Desc: Displays and interfaces with duplication files. + + Author: TB + + Version: 1.0 +]] + +local PANEL = {} +local ClientFolderCount = 1 + +function PANEL:PopulateUpload(Search, Folders, Files, parent) + //Search = string.Left(Search,#Search-1) + local NodeP + if(parent==0)then + NodeP = self.Upload + else + NodeP = self.CNodes[parent] + end + + local Folder + for k,v in pairs(Folders)do + Folder = NodeP:AddNode(v) + self.CNodes[ClientFolderCount] = Folder + Folder.IsFile = false + Folder.Name = v + Folder.SortName = "A"..string.lower(v) + Folder.ID = ClientFolderCount + //table.insert(FolderSearch, {ID=Folder.ID, Name=v}) + ClientFolderCount = ClientFolderCount + 1 + for k,v in pairs(file.Find(Search..v.."/*.txt"))do + name = string.sub(v, 1, -5) + File = Folder:AddNode(name) + File.IsFile = true + File.Name = name + File.SortName = "B"..string.lower(name) + File.Icon:SetImage("vgui/spawnmenu/file") + end + self:PopulateUpload(Search..v.."/", file.FindDir(Search..v.."/*"), nil, Folder.ID) + //file.TFind(Search..v.."/*", function(Search2, Folders2, Files2) self:PopulateUpload(Search2, Folders2, Files2, Folder.ID) end) + end + + /*local name = "" + local File + for k,v in pairs(Files)do + name = string.sub(v, 1, -5) + File = NodeP:AddNode(name) + File.IsFile = true + File.Name = name + File.SortName = "B"..string.lower(name) + File.Icon:SetImage("vgui/spawnmenu/file") + end + + for k,v in pairs(FolderSearch)do + file.TFind(Search..v.Name.."/*", function(Search2, Folders2, Files2) self:PopulateUpload(Search2, Folders2, Files2, v.ID) end) + end*/ + +end + +function PANEL:UpdateClientFiles() + ClientFolderCount = 2 + local Folder = self.Upload:AddNode("=Adv Duplicator=") + self.CNodes[1] = Folder + Folder.IsFile = false + Folder.Name = "=Adv Duplicator=" + Folder.SortName = "A=adv duplicator=" + Folder.ID = 1 + + self:PopulateUpload("adv_duplicator/", file.FindDir("adv_duplicator/*"), nil, 1) + local name = "" + local File + for k,v in pairs(file.Find("adv_duplicator/*.txt"))do + name = string.sub(v, 1, -5) + File = Folder:AddNode(name) + File.IsFile = true + File.Name = name + File.SortName = "B"..string.lower(name) + File.Icon:SetImage("vgui/spawnmenu/file") + end + self:PopulateUpload(AdvDupe2.DataFolder.."/", file.FindDir(AdvDupe2.DataFolder.."/*"), nil, 0) + for k,v in pairs(file.Find(AdvDupe2.DataFolder.."/*.txt"))do + name = string.sub(v, 1, -5) + File = self.Upload:AddNode(name) + File.IsFile = true + File.Name = name + File.SortName = "B"..string.lower(name) + File.Icon:SetImage("vgui/spawnmenu/file") + end + + //file.TFind("Data/adv_duplicator/*", function(Search, Folders, Files) self:PopulateUpload(Search, Folders, Files, 1) end) + //file.TFind("Data/"..AdvDupe2.DataFolder.."/*", function(Search, Folders, Files) self:PopulateUpload(Search, Folders, Files, 0) end) + + self.Upload.Populated = true +end + +local function SortChildren(NodeP) + if(NodeP.ChildNodes)then + NodeP.ChildNodes:SortByMember("SortName") + NodeP:SetExpanded(false, true) + NodeP:SetExpanded(true, true) + else + NodeP:SortByMember("SortName") + NodeP:InvalidateLayout() + end +end + +local function GetNodeRoot(Node) + + local ReturnNode = false + local function PurgeNodes( PNode ) + if(PNode.Name == nil)then return end + ReturnNode = PNode + PurgeNodes(PNode:GetParentNode()) + end + PurgeNodes(Node:GetParentNode()) + + return ReturnNode +end + +local function GetNodePath( Node ) + local path = Node.Name + + local function PurgeNodes( PNode ) + if(PNode.Name == nil)then return end + path = PNode.Name.."/"..path + PurgeNodes(PNode:GetParentNode()) + end + + PurgeNodes(Node:GetParentNode()) + + return path +end + +local function ParsePath(path) + local area = 0 + local Alt = string.Explode("/", path)[1] + if(Alt=="=Adv Duplicator=")then + path = string.sub(path, 18) + area = 2 + elseif(Alt=="=Public=")then + path = string.sub(path, 10) + area = 1 + end + + return path, area +end + +local function CheckFileNameCl(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 + return false + end + + return path..".txt" +end + +function PANEL:FolderSelect(func, name, path, ArgNode) + + local txt = "" + local todo + local Frame + local Fldbrw + local Tree + local AltTree = nil + local path, area = ParsePath(GetNodePath(ArgNode)) + + if(func==1)then + txt = "Select the folder you want to save the Upload to." + Tree = self.Tree + todo = function() + local area2 = 0 + if(area==2)then area2=2 end + if(!Fldbrw:GetSelectedItem())then + AdvDupe2.InitializeUpload(path, area, name, area2, 0) + Frame:Remove() + return + end + AdvDupe2.InitializeUpload(path, area, GetNodePath(Fldbrw:GetSelectedItem()).."/"..name, area2, Fldbrw:GetSelectedItem().ID) + Frame:Remove() + end + elseif(func==2)then + txt = "Select the folder you want to save the Download to." + if(!self.Upload.Populated)then self:UpdateClientFiles() end + Tree = self.Upload + local Root = GetNodeRoot(ArgNode) + todo = function() + if(!Fldbrw:GetSelectedItem())then + + if(area==0 || area==1)then + AdvDupe2.SavePath = AdvDupe2.DataFolder.."/"..name..".txt" + AdvDupe2.SaveNode = 0 + elseif(area==2)then + AdvDupe2.SavePath = "adv_duplicator/"..name..".txt" + AdvDupe2.SaveNode = Root.ID + end + RunConsoleCommand("AdvDupe2_DownloadFile", path, area) + Frame:Remove() + return + end + local SavePath + if(area==0 || area==1)then + SavePath = AdvDupe2.DataFolder.."/"..GetNodePath(Fldbrw:GetSelectedItem()) + else + SavePath = "adv_duplicator/"..GetNodePath(Fldbrw:GetSelectedItem()) + end + AdvDupe2.SavePath = SavePath.."/"..name..".txt" + AdvDupe2.SaveNode = Fldbrw:GetSelectedItem().ID + RunConsoleCommand("AdvDupe2_DownloadFile", path, area) + Frame:Remove() + end + elseif(func==3)then //Move File Client + txt = "Select the folder you want to move the File to." + if SinglePlayer() then + Tree = self.Tree + else + if(!self.Upload.Populated)then self:UpdateClientFiles() end + Tree = self.Upload + end + local Root = GetNodeRoot(ArgNode) + if(Root && Root.Name=="=Adv Duplicator=")then AltTree=Root end + todo = function() + local base = AdvDupe2.DataFolder + local ParentNode + local savepath = "" + if(AltTree)then base = "adv_duplicator" end + + if(!Fldbrw:GetSelectedItem())then + savepath = base.."/" + ParentNode = 0 + if(AltTree)then ParentNode = 1 end + else + local NodePath = GetNodePath(Fldbrw:GetSelectedItem()) + savepath = base.."/"..NodePath.."/" + ParentNode = Fldbrw:GetSelectedItem().ID + end + savepath = savepath..name + savepath = CheckFileNameCl(savepath) + if(AltTree)then path = string.sub(path, 18) end + local OldFile = base.."/"..path..".txt" + local ReFile = file.Read(OldFile) + file.Write( savepath, ReFile) + file.Delete(OldFile) + local name2 = string.Explode("/", savepath) + name2 = string.sub(name2[#name2], 1, -5) + if(SinglePlayer())then + self:AddFile(name2, ParentNode, true) + else + self:AddFileToClient(name2, ParentNode, true) + end + local NodeP = ArgNode:GetParentNode() + for k,v in pairs(NodeP.Items or NodeP.ChildNodes.Items)do + if(v==ArgNode.Panel)then + table.remove(NodeP.Items or NodeP.ChildNodes.Items, k) + v:Remove() + NodeP:InvalidateLayout() + break + end + end + if(NodeP.ChildNodes)then + if(NodeP.m_bExpanded)then + NodeP:SetExpanded(false) + NodeP:SetExpanded(true) + end + end + Tree.m_pSelectedItem = nil + Frame:Remove() + end + elseif(func==4)then //Move File Server + txt = "Select the folder you want to move the File to." + local Root = GetNodeRoot(ArgNode) + if(Root && Root.Name=="=Adv Duplicator=")then AltTree=Root else Tree = self.Tree end + todo = function() + local path1, area1 = ParsePath(GetNodePath(ArgNode)) + local path2, area2 + + if(Fldbrw:GetSelectedItem())then + path2, area2 = ParsePath(GetNodePath(Fldbrw:GetSelectedItem())) + self.MoveToNode = Fldbrw:GetSelectedItem().ID + if(area1==2)then area2 = 2 end + else + if(area1==2)then + self.MoveToNode = Root.ID + else + self.MoveToNode = 0 + end + path2 = "" + area2 = area1 + end + + if((area1==2 && area2!=2) || (area2==2 && area1!=2))then AdvDupe2.Notify("Cannot move files between these directories.", NOTIFY_ERROR) return end + self.NodeToMove = ArgNode + RunConsoleCommand("AdvDupe2_MoveFile", area1, area2, path1, path2) + Frame:Remove() + end + end + + + Frame = vgui.Create("DFrame") + Frame:SetTitle( txt ) + Frame:SetSize(280, 475) + Frame:Center() + Frame:ShowCloseButton(true) + Frame:MakePopup() + + Fldbrw = vgui.Create("DTree", Frame) + Fldbrw:SetPadding(5) + Fldbrw:SetPos(10,40) + Fldbrw:SetSize(260,400) + + local function PurgeChildren(Node, Parent) + if(!Node.ChildNodes)then return end + local child + for k,v in pairs(Node.ChildNodes:GetItems())do + if(v.IsFile)then continue end + child = Parent:AddNode(v.Name) + child.Name = v.Name + child.ID = v.ID + PurgeChildren(v, child) + end + return + end + + local child + for k,v in pairs(AltTree && AltTree.ChildNodes.Items or Tree:GetItems())do + if(v.IsFile || v.Name=="=Adv Duplicator=")then continue end + child = Fldbrw:AddNode(v.Name) + child.Name = v.Name + child.ID = v.ID + PurgeChildren(v, child) + end + + local confirm = vgui.Create("DButton", Frame) + confirm:SetSize(75,25) + confirm:SetText("Ok") + confirm:AlignBottom(5) + confirm:AlignLeft(10) + confirm.DoClick = todo + + local Cancel = vgui.Create("DButton", Frame) + Cancel:SetSize(75,25) + Cancel:SetText("Cancel") + Cancel:AlignBottom(5) + Cancel:AlignRight(10) + Cancel.DoClick = function() Frame:Remove() end + +end + +local function DeleteFilesInFolders(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.."/*", DeleteFilesInFolders) + end + +end + +local function RemoveFileNodes(Node) + for k,v in pairs(Node.ChildNodes.Items)do + if(v.IsFile)then + table.remove(Node.ChildNodes.Items, k) + v:Remove() + elseif(v.ChildNodes)then + RemoveFileNodes(v) + end + end + Node:InvalidateLayout() + Node:SetExpanded(false) +end + +local function Delete(Tree, Folder, Server) + + local Node = Tree:GetSelectedItem() + local msg + + if(Server)then + msg = '" from the SERVER?' + else + msg = '" from your CLIENT?' + end + + if(!Folder)then + msg = 'Are you sure you want to delete the FILE, "'..Node.Name..msg + else + msg = 'Are you sure you want to delete the ENTIRE FOLDER, "'..Node.Name..msg + end + + local path, area = ParsePath(GetNodePath(Node)) + + local Frame = vgui.Create("DFrame") + Frame:SetTitle( "Are You Sure?" ) + Frame:SetSize(250, 100) + Frame:Center() + Frame:ShowCloseButton(false) + + local label = vgui.Create("DLabel", Frame) + label:AlignLeft(5) + label:AlignTop(30) + label:SetText(msg) + label:SetWide(240) + label:SetTall(25) + label:SetWrap(true) + + local confirm = vgui.Create("DButton", Frame) + confirm:SetSize(75,25) + confirm:SetText("Delete") + confirm:AlignBottom(5) + confirm:AlignLeft(10) + confirm.DoClick = function() + if(!Folder)then + if(Server)then + Tree:GetParent().NodeToDelete = Node + Tree:GetParent().ParentToDelete = Node:GetParentNode() + RunConsoleCommand("AdvDupe2_DeleteFile", path, area, "false") + else + if(area==1)then path = "=Public=/"..path end + path = AdvDupe2.DataFolder.."/"..path..".txt" + local NodeP = Node:GetParentNode() + + for k,v in pairs(NodeP.Items or NodeP.ChildNodes.Items)do + if(v==Node.Panel)then + table.remove(NodeP.Items or NodeP.ChildNodes.Items, k) + v:Remove() + NodeP:InvalidateLayout() + break + end + end + if(NodeP.ChildNodes)then + if(NodeP.m_bExpanded)then + NodeP:SetExpanded(false) + NodeP:SetExpanded(true) + end + end + Tree.m_pSelectedItem = nil + end + file.Delete(path) + else + if(Server)then + Tree:GetParent().NodeToDelete = false + Tree:GetParent().ParentToDelete = Node + RunConsoleCommand("AdvDupe2_DeleteFile", path, area, "true") + else + if(area==1)then path = "=Public=/"..path end + path = "Data/"..AdvDupe2.DataFolder.."/"..path.."/*" + if(Node.ChildNodes)then + RemoveFileNodes(Node) + end + Node:SetExpanded(false) + Tree.m_pSelectedItem = nil + end + file.TFind(path, DeleteFilesInFolders) + end + Frame:Remove() + end + + local Cancel = vgui.Create("DButton", Frame) + Cancel:SetSize(75,25) + Cancel:SetText("Cancel") + Cancel:AlignBottom(5) + Cancel:AlignRight(10) + Cancel.DoClick = function() Frame:Remove() end + Frame:SetVisible(true) + Frame:MakePopup() + +end + +function PANEL:AddNewFolder(Tree, base) + + local Node + if(base)then + Node = Tree + else + Node = Tree:GetSelectedItem() + end + + local name = self.Textbox:GetValue() + if(name=="" || name=="File_Name...")then AdvDupe2.Notify("Name is blank!", NOTIFY_ERROR) return end + name = name:gsub("%W","") + + local path, area + if(base)then + path = AdvDupe2.DataFolder.."/"..name + else + path, area = ParsePath(GetNodePath(Node)) + if(area==0)then + path = AdvDupe2.DataFolder.."/"..path.."/"..name + elseif(area==1)then + path = AdvDupe2.DataFolder.."/=Public=/"..path.."/"..name + else + path = "adv_duplicator/"..path.."/"..name + end + end + + if(file.IsDir(path))then AdvDupe2.Notify("Folder name alreayd exists.", NOTIFY_ERROR) return end + + file.CreateDir(path) + + local Folder = Node:AddNode(name) + self.CNodes[ClientFolderCount] = Folder + Folder.IsFile = false + Folder.Name = name + Folder.SortName = "A"..string.lower(name) + Folder.ID = ClientFolderCount + ClientFolderCount = ClientFolderCount + 1 + + SortChildren(Node) + + if(!Node.m_bExpanded)then + Node:SetExpanded(true) + end + + Tree:SetSelectedItem(Folder) +end + +local function Incomplete() + AdvDupe2.Notify("This feature is not yet complete!",NOTIFY_GENERIC,10) +end + +function PANEL:DoClick(Node) + if(!Node || !Node.IsFile)then return end + if(CurTime()-self.LastClick<=0.25 && self.LastNode==Node)then + local path, area = ParsePath(GetNodePath(Node)) + RunConsoleCommand("AdvDupe2_OpenFile", path, area) + end + self.LastNode = Node + self.LastClick = CurTime() +end + +local function RenameFileCl(Node, name) + if(name=="" || name=="File_Name...")then AdvDupe2.Notify("Enter a file name to rename file.", NOTIFY_ERROR) return end + local path, area = ParsePath(GetNodePath(Node)) + local File, FilePath, tempFilePath = "", "", "" + if(area==0)then + tempFilePath = AdvDupe2.DataFolder.."/"..path + elseif(area==1)then + tempFilePath = AdvDupe2.DataFolder.."/=Public=/"..path + elseif(area==2)then + tempFilePath = "adv_duplicator/"..path + end + + File = file.Read(tempFilePath..".txt") + FilePath = CheckFileNameCl(string.sub(tempFilePath, 1, #tempFilePath-#Node.Name)..name) + + if(!FilePath)then AdvDupe2.Notify("Rename limit exceeded, could not rename.", NOTIFY_ERROR) return end + file.Write(FilePath, File) + if(file.Exists(FilePath))then + file.Delete(tempFilePath..".txt") + local NewName = string.Explode("/", FilePath) + NewName = string.sub(NewName[#NewName], 1, -5) + Node:SetText(NewName) + Node.Name = NewName + Node.SortName = "B"..string.lower(NewName) + AdvDupe2.Notify("File renamed to "..NewName) + else + AdvDupe2.Notify("File was not renamed.", NOTIFY_ERROR) + end + + local NodeP = Node:GetParentNode() + SortChildren(NodeP) +end + +function PANEL:DoRightClick(Node) + if(Node==nil)then return end + self:SetSelectedItem(Node) + local parent = self:GetParent() + local Menu = DermaMenu() + + if(SinglePlayer())then + if(Node.IsFile)then + Menu:AddOption("Open", function() + local path, area = ParsePath(GetNodePath(Node)) + RunConsoleCommand("AdvDupe2_OpenFile", path, area) + end) + Menu:AddOption("Rename", function() + RenameFileCl(Node, parent.Textbox:GetValue()) + parent.Textbox:SetValue("File_Name...") + parent.DescBox:SetValue("Description...") + end) + Menu:AddOption("Move File", function() parent:FolderSelect( 3, Node.Name, GetNodePath(Node), Node) end) + Menu:AddOption("Delete", function() Delete(self, false, false) end) + else + Menu:AddOption("Save", function() + local name = parent.Textbox:GetValue() + if(name=="" || name=="File_Name...")then AdvDupe2.Notify("Name field is blank.", NOTIFY_ERROR) return end + local path, area = ParsePath(GetNodePath(Node) ) + local desc = parent.DescBox:GetValue() + if(desc=="Description...")then desc="" end + RunConsoleCommand("AdvDupe2_SaveFile", parent.Textbox:GetValue(), path, area, desc, Node.ID) + parent.Textbox:SetValue("File_Name...") + parent.DescBox:SetValue("Description...") + end) + Menu:AddOption("New Folder", function() + parent:AddNewFolder(self, false) + parent.Textbox:SetValue("File_Name...") + parent.DescBox:SetValue("Description...") + end) + Menu:AddOption("Delete", function() Delete(self, true, false) end) + end + + elseif(parent.Change.Server)then + + if(Node.IsFile)then + Menu:AddOption("Open", function() + local path, area = ParsePath(GetNodePath(Node)) + RunConsoleCommand("AdvDupe2_OpenFile", path, area) + end) + Menu:AddOption("Download", function() + parent:FolderSelect(2, Node.Name, nil, Node) + end) + Menu:AddOption("Rename", function() + local name = parent.Textbox:GetValue() + if(name=="" || name=="File_Name...")then AdvDupe2.Notify("Name field is blank!", NOTIFY_ERROR) return end + local path, area = ParsePath(GetNodePath(Node)) + parent.NodeToRename = Node + RunConsoleCommand("AdvDupe2_RenameFile", area, name, path ) + parent.Textbox:SetValue("File_Name...") + parent.DescBox:SetValue("Description...") + end) + Menu:AddOption("Move File", function() + parent:FolderSelect(4, nil, nil, Node) + end) + Menu:AddOption("Delete", function() Delete(self, false, true) end ) + else + Menu:AddOption("Save", function() + local name = parent.Textbox:GetValue() + if(name=="" || name=="File_Name...")then AdvDupe2.Notify("Name field is blank!", NOTIFY_ERROR) return end + local path, area = ParsePath(GetNodePath(Node)) + local desc = parent.DescBox:GetValue() + if(desc=="Description...")then desc="" end + RunConsoleCommand("AdvDupe2_SaveFile", parent.Textbox:GetValue(), path, area, desc, Node.ID) + parent.Textbox:SetValue("File_Name...") + parent.DescBox:SetValue("Description...") + end) + Menu:AddOption("New Folder", function() + local name = parent.Textbox:GetValue() + if(name=="" || name=="File_Name...")then AdvDupe2.Notify("Name field is blank!", NOTIFY_ERROR) return end + name = name:gsub("%W","") + local path, area = ParsePath(GetNodePath(Node)) + RunConsoleCommand("AdvDupe2_NewFolder", name, path, area, Node.ID) + parent.Textbox:SetValue("File_Name...") + parent.DescBox:SetValue("Description...") + end) + Menu:AddOption("Delete", function() Delete(self, true, true) end ) + end + else + if(Node.IsFile)then + Menu:AddOption("Upload", function() parent:FolderSelect(1, Node.Name, GetNodePath(Node), Node) end)//function() AdvDupe2.InitializeUpload(GetNodePath(Tree:GetSelectedItem())) end ) + Menu:AddOption("Rename", function() + RenameFileCl(Node, parent.Textbox:GetValue()) + parent.Textbox:SetValue("File_Name...") + parent.DescBox:SetValue("Description...") + end) + Menu:AddOption("Move File", function() parent:FolderSelect( 3, Node.Name, GetNodePath(Node), Node) end) + Menu:AddOption("Delete", function() Delete(self, false, false) end) + else + Menu:AddOption("New Folder", function() + parent:AddNewFolder(self) + parent.Textbox:SetValue("File_Name...") + parent.DescBox:SetValue("Description...") + end) + Menu:AddOption("Delete", function() Delete(self, true, false) end) + end + end + Menu:Open() +end + + + +function PANEL:Init() + + PANEL.Panel = self + self.Nodes = {} + self.CNodes = {} + local Wide, Tall = self:GetSize() + local TreeOff = 60 + local UpOff = 40 + + self.FileParent = 0 + + self.Textbox = vgui.Create("DTextEntry", self) + self.Textbox:SetMultiline(false) + self.Textbox:SetKeyboardInputEnabled( true ) + self.Textbox:SetEnabled( true ) + self.Textbox:SetAllowNonAsciiCharacters( true ) + self.Textbox:SetValue("File_Name...") + self.Textbox.OnMousePressed = function() + self.Textbox:OnGetFocus() + if(self.Textbox:GetValue()=="File_Name...")then + self.Textbox:SelectAllOnFocus(true) + end + end + self.Textbox:SetUpdateOnType(true) + self.Textbox.OnTextChanged = function() + local new, changed = self.Textbox:GetValue():gsub("[?.:\"*<>|]","") + if changed > 0 then + self.Textbox:SetValue(new) + end + end + self.Textbox.OnValueChange = function() + if(self.Textbox:GetValue()!="File_Name...")then + local new,changed = self.Textbox:GetValue():gsub("[?.:\"*<>|]","") + if changed > 0 then + self.Textbox:SetValue(new) + end + end + end + /*function() + //if(self.Textbox:GetValue()!="File_Name...")then + local new,changed = self.Textbox:GetValue():gsub("[?.:\"*<>|]","") + if changed > 0 then + self.Textbox:SetValue(new) + end + //end + end*/ + + self.Save = vgui.Create("DButton", self) + self.Save:SetText("Save") + self.Save:SetToolTip("Save to base folder") + self.Save.DoClick = function() + if(self.Textbox:GetValue()=="" || self.Textbox:GetValue()=="File_Name...")then AdvDupe2.Notify("Name field is blank!", NOTIFY_ERROR) return end + --[[local _,changed = self.Textbox:GetValue():gsub("[?.:\"*<>|]","") + if changed > 0 then + AdvDupe2.Notify("Filenames cannot contain ?.:\"*<>|",NOTIFY_ERROR) + return + end]] + local desc = self.DescBox:GetValue() + if(desc=="Description...")then desc="" end + RunConsoleCommand("AdvDupe2_SaveFile", self.Textbox:GetValue(), "", 0, desc, 0) + self.Textbox:SetValue("File_Name...") + self.DescBox:SetValue("Description...") + end + self.Textbox.OnEnter = self.Save.DoClick + + self.DescBox = vgui.Create("DTextEntry", self) + self.DescBox:SetMultiline(false) + self.DescBox:SetKeyboardInputEnabled( true ) + self.DescBox:SetEnabled( true ) + self.DescBox:SetValue("Description...") + self.DescBox.OnEnter = self.Save.DoClick + self.DescBox.OnMousePressed = function() + self.DescBox:OnGetFocus() + if(self.DescBox:GetValue()=="Description...")then + self.DescBox:SelectAllOnFocus(true) + end + end + + if(!SinglePlayer())then + self.Upload = vgui.Create("DTree", self) + self.Upload:SetPadding(5) + self.Upload.Populated = false + self.Upload.DoRightClick = self.DoRightClick + self.Upload:SetVisible(false) + self.Upload.LastClick = 0 + end + + self.Tree = vgui.Create("DTree", self) + self.Tree:SetPadding(5) + self.Tree.LastClick = 0 + self.Tree.DoClick = self.DoClick + self.Tree.DoRightClick = self.DoRightClick + self.CurrentParent = self.Tree + + self.Help = vgui.Create("DButton", self) + //self.Help:SetText("?") + self.Help:SetImage("gui/silkicons/help") + self.Help.DoClick = function(btn) + local Menu = DermaMenu() + Menu:AddOption("Forum", Incomplete) + Menu:AddOption("Bug Reporting", function() gui.OpenURL("http://code.google.com/p/advdupe2/issues/list") end) + Menu:AddOption("About", AdvDupe2.ShowSplash) + Menu:Open() + end + + self.Update = vgui.Create("DButton", self) + self.Update:SetText("Update") + self.Update:SetToolTip("Update files") + self.Update.DoClick = function(button) + local Wide, Tall = self:GetSize() + if(self.Change.Server) then + RunConsoleCommand("AdvDupe2_SendFiles", 0) + else + for k,v in pairs(self.Upload.Items)do + v:Remove() + end + self.Upload.Items = {} + self.Upload:InvalidateLayout() + self.CNodes = {} + self.Upload.m_pSelectedItem = nil + self:UpdateClientFiles() + end + end + + self.NewFolder = vgui.Create("DButton", self) + self.NewFolder:SetText("New Folder") + self.NewFolder:SetToolTip("Create a new folder in the base folder") + self.NewFolder.DoClick = function() + if(self.Change.Server)then + local name = self.Textbox:GetValue() + if(name=="" || name=="File_Name...")then AdvDupe2.Notify("Name field is blank!", NOTIFY_ERROR) return end + name = name:gsub("%W","") + RunConsoleCommand("AdvDupe2_NewFolder", name, "", 0) + self.Textbox:SetValue("File_Name...") + else + self:AddNewFolder(self.Upload, true) + end + end + + self.Change = vgui.Create("DButton", self) + self.Change.Server = true + if(SinglePlayer())then + self.Change:SetText("Local Files") + else + self.Change:SetText("Server Files") + self.Change:SetToolTip("Change between server and client files") + self.Change.DoClick = function(button) + if(self.Change.Server)then + if(SinglePlayer())then return end + self.Change:SetText("Client Files") + self.Change.Server = false + self.Tree:SetVisible(false) + if(!self.Upload.Populated)then self:UpdateClientFiles() end + self.Upload:SetVisible(true) + else + self.Change:SetText("Server Files") + self.Change.Server = true + self.Upload:SetVisible(false) + self.Tree:SetVisible(true) + end + end + end + + self:SetPaintBackground(false) + +end + +function PANEL:AdjustFiller(Menu) + local Tab = self.Panel:GetTable() + if(Menu)then + if(g_ContextMenu:GetTall() MaxTall ) then Tall = MaxTall end + Tab.Filler:SetTall(Tall-49) + else + Tab.Filler:SetTall(g_ContextMenu:GetTall()-49) + end + else + Tab.Filler:SetTall(Tab.Panel:GetParent():GetParent():GetParent():GetParent():GetParent():GetTall()-45) + end +end + +local CMenu = false +hook.Add("OnContextMenuOpen", "AD2MenuFormat", + function() + if(!GetControlPanel("advdupe2"):IsVisible() || !PANEL || !PANEL.Panel)then return end + CMenu = true + PANEL:AdjustFiller(true) + end) + +hook.Add("SpawnMenuOpen", "AD2MenuFormat", + function() + if(CMenu)then CMenu = false return end + if(!GetControlPanel("advdupe2"):IsVisible() || !PANEL || !PANEL.Panel)then return end + PANEL:AdjustFiller(false) + end) + +function PANEL:PerformLayout() + + local w = self:GetWide() + self.Change:SetPos(0, 0) + self.Change:SetSize(w, 20) + if(self.Upload)then + self.Upload:SetPos(0, 20) + self.Upload:SetSize(w,360) + end + self.Tree:SetPos(0, 20) + self.Tree:SetSize(w,360) + self.Textbox:SetPos(0,380) + self.Textbox:SetSize(w*.75, 20) + self.Save:SetPos(w*.75,380) + self.Save:SetSize(w*.25, 20) + self.DescBox:SetPos(0,400) + self.DescBox:SetSize(w, 20) + self.Help:SetPos(0, 420) + self.Help:SetSize(22, 20) + self.Update:SetPos(22, 420) + self.Update:SetSize(w/2-22, 20) + self.NewFolder:SetPos(w/2, 420) + self.NewFolder:SetSize(w/2, 20) +end + +function PANEL:AddFolder(Name, ID, Parent, New) + + local NodeP + if(Parent!=0)then + NodeP = self.Nodes[Parent] + else + NodeP = self.Tree + end + if(!ValidPanel(NodeP))then return end + local Folder = NodeP:AddNode(Name) + + self.Nodes[ID]=Folder + Folder.IsFile = false + Folder.Name = Name + Folder.SortName = "A"..string.lower(Name) + Folder.ID = ID + + if(NodeP.ChildNodes)then + NodeP.ChildNodes.Items[#NodeP.ChildNodes.Items].Name = Folder.Name + end + if(New)then + SortChildren(NodeP) + end + +end + +function PANEL:AddFile(Name, Parent, New) + + local NodeP + if(Parent!=0)then + NodeP = self.Nodes[Parent] + else + NodeP = self.Tree + end + if(!ValidPanel(NodeP))then return end + + local File = NodeP:AddNode(Name) + File.IsFile = true + File.Name = Name + File.SortName = "B"..string.lower(Name) + File.Icon:SetImage("vgui/spawnmenu/file") + + if(NodeP.ChildNodes)then + NodeP.ChildNodes.Items[#NodeP.ChildNodes.Items].IsFile = true + end + + if(New)then + SortChildren(NodeP) + end + +end + +function PANEL:AddFileToClient(Name, Parent, New) + local NodeP + if(Parent!=0)then + NodeP = self.CNodes[Parent] + else + NodeP = self.Upload + end + if(!ValidPanel(NodeP))then return end + + local File = NodeP:AddNode(Name) + File.IsFile = true + File.Name = Name + File.SortName = "B"..string.lower(Name) + File.Icon:SetImage("vgui/spawnmenu/file") + + if(NodeP.ChildNodes)then + NodeP.ChildNodes.Items[#NodeP.ChildNodes.Items].IsFile = true + end + if(New)then + SortChildren(NodeP) + end +end + +function PANEL:ClearBrowser() + for k,v in pairs(self.Tree:GetItems())do + v:Remove() + end + self.Tree.Items = {} + self.Tree:InvalidateLayout() + self.Nodes = {} + self.Tree.m_pSelectedItem = nil + RunConsoleCommand("AdvDupe2_SendFiles", 1) +end + +function PANEL:RenameNode(Name) + local Node = self.NodeToRename + Node:SetText(Name) + Node.Name = Name + Node.SortName = "B"..string.lower(Name) + local NodeP = Node:GetParentNode() + SortChildren(NodeP) + AdvDupe2.Notify("File was renamed to "..Name) + self.NodeToRename = nil +end + +function PANEL:MoveNode(Name) + local Node = self.NodeToMove + local NodeP = Node:GetParentNode() + Node:GetRoot().m_pSelectedItem = nil + for k,v in pairs(NodeP.Items or NodeP.ChildNodes.Items)do + if(v==Node.Panel)then + table.remove(NodeP.Items or NodeP.ChildNodes.Items, k) + v:Remove() + NodeP:InvalidateLayout() + break + end + end + if(NodeP.m_bExpanded)then + NodeP:SetExpanded(false) + NodeP:SetExpanded(true) + end + if(self.MoveToNode==0)then + NodeP = self.Tree + else + NodeP = self.Nodes[self.MoveToNode] + end + + local NewNode = NodeP:AddNode(Name) + NewNode.Name = Name + NewNode.SortName = "B"..string.lower(Name) + NewNode.Icon:SetImage("vgui/spawnmenu/file") + NewNode.IsFile = true + if(NodeP.ChildNodes)then + NodeP.ChildNodes.Items[#NodeP.ChildNodes.Items].IsFile = true + end + SortChildren(NodeP) + +end + +function PANEL:DeleteNode() + if(self.NodeToDelete==false)then + RemoveFileNodes(self.ParentToDelete) + else + for k,v in pairs(self.ParentToDelete.Items or self.ParentToDelete.ChildNodes.Items)do + if(v==self.NodeToDelete)then + table.remove(self.ParentToDelete.Items or self.ParentToDelete.ChildNodes.Items, k) + v:Remove() + self.ParentToDelete:InvalidateLayout() + break + end + end + if(self.ParentToDelete.ChildNodes)then + if(self.ParentToDelete.m_bExpanded)then + self.ParentToDelete:SetExpanded(false) + self.ParentToDelete:SetExpanded(true) + end + end + self.Tree.m_pSelectedItem = nil + end + self.ParentToDelete=nil + self.NodeToDelete=nil +end + +vgui.Register("advdupe2_browser", PANEL, "DPanel") \ No newline at end of file diff --git a/lua/advdupe2/cl_file.lua b/lua/advdupe2/cl_file.lua new file mode 100644 index 0000000..5ab06a0 --- /dev/null +++ b/lua/advdupe2/cl_file.lua @@ -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: dupe, name + Return: success/ 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: name + Return: 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 \ No newline at end of file diff --git a/lua/advdupe2/cl_networking.lua b/lua/advdupe2/cl_networking.lua new file mode 100644 index 0000000..a0777c0 --- /dev/null +++ b/lua/advdupe2/cl_networking.lua @@ -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) \ No newline at end of file diff --git a/lua/advdupe2/nullesc.lua b/lua/advdupe2/nullesc.lua new file mode 100644 index 0000000..826e031 --- /dev/null +++ b/lua/advdupe2/nullesc.lua @@ -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 diff --git a/lua/advdupe2/sv_clipboard.lua b/lua/advdupe2/sv_clipboard.lua new file mode 100644 index 0000000..6fd9434 --- /dev/null +++ b/lua/advdupe2/sv_clipboard.lua @@ -0,0 +1,1283 @@ +--[[ + Title: Adv. Duplicator 2 Module + + Desc: Provides advanced duplication functionality for the Adv. Dupe 2 tool. + + Author: TB + + Version: 1.0 +]] + +require "duplicator" + +AdvDupe2.duplicator = {} +//AdvDupe2.AllowNPCs = false --Allow to paste NPCs +AdvDupe2.JobManager = {} +AdvDupe2.JobManager.PastingHook = false +AdvDupe2.JobManager.Queue = {} + +local constraints = {Weld=true, Axis=true, Ballsocket=true, Elastic=true, Hydraulic=true, Motor=true, Muscle=true, Pulley=true, Rope=true, Slider=true, Winch=true} + + +--[[ + Name: CopyEntTable + Desc: Returns a copy of the passed entity's table + Params: Ent + Returns: enttable +]] + +/*--------------------------------------------------------- + Returns a copy of the passed entity's table +---------------------------------------------------------*/ +local function CopyEntTable( Ent, Offset ) + + local Tab = {} + + if Ent.PreEntityCopy then + Ent:PreEntityCopy() + end + + local EntityClass = duplicator.FindEntityClass( Ent:GetClass() ) + + if EntityClass then + + local EntTable = table.Copy(Ent:GetTable()) + local Arg + for iNumber, Key in pairs( EntityClass.Args ) do + -- Translate keys from old system + if ( Key == "pos" or Key == "position" ) then Key = "Pos" end + if ( Key == "ang" or Key == "Ang" or Key == "angle" ) then Key = "Angle" end + if ( Key == "model" ) then Key = "Model" end + + Arg = EntTable[ Key ] + + -- Special keys + --if ( Key == "Data" ) then Arg = EntTable end + + -- If there's a missing argument then unpack will stop sending at that argument + if Arg == nil then Arg = false end + + Tab[ Key ] = Arg + + end + + end + + Tab.BoneMods = table.Copy( Ent.BoneMods ) + if(Ent.EntityMods)then + Tab.EntityMods = Ent.EntityMods + end + + if Ent.PostEntityCopy then + Ent:PostEntityCopy() + end + + Tab.Pos = Ent:GetPos() + Tab.Angle = nil + Tab.Class = Ent:GetClass() + Tab.Model = Ent:GetModel() + Tab.Skin = Ent:GetSkin() + if(Tab.Skin==0)then Tab.Skin = nil end + Tab.CollisionGroup = Ent:GetCollisionGroup() + if(Tab.CollisionGroup==0)then Tab.CollisionGroup = nil end + + if(Tab.Class == "gmod_cameraprop")then + Tab.key = Ent:GetNetworkedInt("key") + end + -- Allow the entity to override the class + -- This is a hack for the jeep, since it's real class is different from the one it reports as + -- (It reports a different class to avoid compatibility problems) + if Ent.ClassOverride then Tab.Class = Ent.ClassOverride end + + Tab.PhysicsObjects = {} + + -- Physics Objects + local iNumPhysObjects = Ent:GetPhysicsObjectCount() + local PhysObj + + for Bone = 0, iNumPhysObjects-1 do + PhysObj = Ent:GetPhysicsObjectNum( Bone ) + if PhysObj!=nil then + Tab.PhysicsObjects[ Bone ] = Tab.PhysicsObjects[ Bone ] or {} + if(PhysObj:IsMoveable())then Tab.PhysicsObjects[ Bone ].Frozen = true end + PhysObj:EnableMotion(false) + Tab.PhysicsObjects[ Bone ].Pos = PhysObj:GetPos() - Tab.Pos + Tab.PhysicsObjects[ Bone ].Angle = PhysObj:GetAngle() + end + end + + Tab.PhysicsObjects[0].Pos = Tab.Pos - Offset + + Tab.Pos = nil + if(Tab.Class!="prop_physics")then + if(!Tab.BuildDupeInfo)then Tab.BuildDupeInfo = {} end + Tab.BuildDupeInfo.IsNPC = Ent:IsNPC() + Tab.BuildDupeInfo.IsVehicle = Ent:IsVehicle() + end + if( IsValid(Ent:GetParent()) ) then + if(!Tab.BuildDupeInfo)then Tab.BuildDupeInfo = {} end + Tab.PhysicsObjects[ 0 ].Angle = Ent:GetAngles() + Tab.BuildDupeInfo.DupeParentID = Ent.Entity:GetParent():EntIndex() + end + + -- Flexes + local FlexNum = Ent:GetFlexNum() + Tab.Flex = Tab.Flex or {} + local weight + local flexes = false + for i = 0, FlexNum do + weight = Ent:GetFlexWeight( i ) + if(weight!=0)then + Tab.Flex[ i ] = Ent:GetFlexWeight( i ) + flexes = true + end + end + + if(flexes)then + Tab.FlexScale = Ent:GetFlexScale() + else + Tab.Flex = nil + end + + // Make this function on your SENT if you want to modify the + // returned table specifically for your entity. + if Ent.OnEntityCopyTableFinish then + Ent:OnEntityCopyTableFinish( Tab ) + end + + return Tab + +end + + +--[[ + Name: CopyConstraintTable + Desc: Create a table for constraints + Params:
Constraints + Returns:
Constraints,
Entities +]] + +/*Still not finished: + +*/ +local function CopyConstraintTable( Const, Offset ) + + local Constraint = {} + local Entities = {} + + if(Const!=nil)then + Const.Constraint = nil + Const.OnDieFunctions=nil + Constraint.Entity={} + local Type = duplicator.ConstraintType[ Const.Type ] + + if ( Type ) then + for k, key in pairs( Type.Args ) do + if(!string.find(key, "Ent") and !string.find(key, "Bone"))then + Constraint[key] = Const[ key ] + end + end + + if((Const["Ent"] && Const["Ent"]:IsWorld()) || IsValid(Const["Ent"]))then + Constraint.Entity[ 1 ] = {} + Constraint.Entity[ 1 ].Index = Const["Ent"]:EntIndex() + if(!Const["Ent"]:IsWorld())then table.insert( Entities, Const["Ent"] ) end + else + local ent + for i=1,4 do + ent = "Ent"..i + + if((Const[ent] && Const[ent]:IsWorld()) || IsValid(Const[ent]))then + Constraint.Entity[ i ] = {} + Constraint.Entity[ i ].Index = Const[ent]:EntIndex() + Constraint.Entity[ i ].Bone = Const[ "Bone"..i ] + Constraint.Entity[ i ].Length = Const[ "Length"..i ] + Constraint.Entity[ i ].World = Const[ "World"..i ] + + if Const[ ent ]:IsWorld() then + Constraint.Entity[ i ].World = true + if ( Const[ "LPos"..i ] ) then + if(i!= 4 and i!=2)then + if(Const["Ent2"])then + Constraint.Entity[ i ].LPos = Const[ "LPos"..i ] - Const["Ent2"]:GetPos() + Constraint[ "LPos"..i ] = Const[ "LPos"..i ] - Const["Ent2"]:GetPos() + elseif(Const["Ent4"])then + Constraint.Entity[ i ].LPos = Const[ "LPos"..i ] - Const["Ent4"]:GetPos() + Constraint[ "LPos"..i ] = Const[ "LPos"..i ] - Const["Ent4"]:GetPos() + end + elseif(Const["Ent1"])then + Constraint.Entity[ i ].LPos = Const[ "LPos"..i ] - Const["Ent1"]:GetPos() + Constraint[ "LPos"..i ] = Const[ "LPos"..i ] - Const["Ent1"]:GetPos() + end + else + Constraint.Entity[ i ].LPos = Offset + Constraint[ "LPos"..i ] = Offset + end + else + Constraint.Entity[ i ].LPos = Const[ "LPos"..i ] + Constraint.Entity[ i ].WPos = Const[ "WPos"..i ] + end + + if(!Const[ent]:IsWorld())then table.insert( Entities, Const[ent] ) end + end + + if(Const["WPos"..i])then + if(!Const["Ent1"]:IsWorld())then + Constraint["WPos"..i] = Const[ "WPos"..i ] - Const["Ent1"]:GetPos() + else + Constraint["WPos"..i] = Const[ "WPos"..i ] - Const["Ent4"]:GetPos() + end + end + end + end + + Constraint.Type = Const.Type + Constraint.Identity = Const.Identity + if(Const.BuildDupeInfo)then Constraint.BuildDupeInfo = table.Copy(Const.BuildDupeInfo) end + end + + end + + return Constraint, Entities + +end + +--[[ + Name: Copy + Desc: Copy an entity and all entities constrained + Params: Entity + Returns:
Entities,
Constraints +]] +local function Copy( Ent, EntTable, ConstraintTable, Offset ) + + + local phys + + local index = Ent:EntIndex() + + EntTable[index] = CopyEntTable(Ent, Offset) + + if ( !constraint.HasConstraints( Ent ) ) then + local PhysObjs = EntTable[Ent:EntIndex()].PhysicsObjects + for i=0, Ent:GetPhysicsObjectCount() do + phys = Ent:GetPhysicsObjectNum(i) + if(IsValid(phys))then + phys:EnableMotion(PhysObjs[i].Frozen) + end + end + return EntTable, ConstraintTable + end + + local index + for k, Constraint in pairs( Ent.Constraints ) do + + /*if(!Constraint.BuildDupeInfo)then + Constraint.BuildDupeInfo = {} + end*/ + + if(!Constraint.Identity)then + index = Constraint:GetCreationID() + Constraint.Identity = Constraint:GetCreationID() + else + index = Constraint.Identity + end + + if ( index and !ConstraintTable[ index ] ) then + local ConstTable, ents = CopyConstraintTable( table.Copy(Constraint:GetTable()), Offset ) + ConstraintTable[ index ] = ConstTable + + for j,e in pairs(ents) do + if ( e and ( e:IsWorld() or e:IsValid() ) ) and ( !EntTable[ e:EntIndex() ] ) then + Copy( e, EntTable, ConstraintTable, Offset ) + end + end + end + + end + + local PhysObjs = EntTable[Ent:EntIndex()].PhysicsObjects + for i=0, Ent:GetPhysicsObjectCount() do + phys = Ent:GetPhysicsObjectNum(i) + if(IsValid(phys))then + phys:EnableMotion(PhysObjs[i].Frozen) + end + end + + return EntTable, ConstraintTable +end +AdvDupe2.duplicator.Copy = Copy + +--[[ + Name: LoadSents + Desc: Loads the entities list and the whitelist for spawning props + Params: + Returns: +]] +local function LoadSents() + AdvDupe2.duplicator.EntityList = {prop_physics=true, prop_ragdoll=true, prop_vehicle_prisoner_pod=true, prop_vehicle_airboat=true, prop_vehicle_jeep=true, prop_vehicle_jeep_old=true, phys_magnet=true, prop_effect=true} + AdvDupe2.duplicator.WhiteList = {prop_physics=true, prop_ragdoll=true, prop_vehicle_prisoner_pod=true, prop_vehicle_airboat=true, prop_vehicle_jeep=true, prop_vehicle_jeep_old=true, phys_magnet=true, prop_effect=true} + local exclusion = {prop_effect= true, gmod_player_start=true, gmod_ghost=true, lua_run=true}//, gmod_wire_hologram=true} + for _,v in pairs(scripted_ents.GetList( )) do + if _:sub(1,4) == "base" then continue end + if _:sub(1,4) == "info" then continue end + if _:sub(1,4) == "func" then continue end + if exclusion[_] then continue end + if v.t.AdminSpawnable and !v.t.Spawnable then + AdvDupe2.duplicator.EntityList[_] = false + else + AdvDupe2.duplicator.EntityList[_] = true + end + AdvDupe2.duplicator.WhiteList[_] = true + end +end +concommand.Add("advdupe2_reloadwhitelist", LoadSents) +hook.Add( "InitPostEntity", "LoadDuplicatingEntities", LoadSents) + +--[[ + Name: AreaCopy + Desc: Copy based on a box + Params: Entity + Returns:
Entities,
Constraints +]] +//Ghosts are a problem and entities need to be returned by there index, not seqeuntial +//Also need to make a get entities function for constraints +function AdvDupe2.duplicator.AreaCopy( Entities, Offset, CopyOutside ) + local EntTable = {} + local ConstraintTable = {} + + for _,Ent in pairs(Entities)do + + local phys + + local index = Ent:EntIndex() + EntTable[index] = CopyEntTable(Ent, Offset) + + if ( !constraint.HasConstraints( Ent ) ) then + local PhysObjs = EntTable[Ent:EntIndex()].PhysicsObjects + for i=0, Ent:GetPhysicsObjectCount() do + phys = Ent:GetPhysicsObjectNum(i) + if(IsValid(phys))then + phys:EnableMotion(PhysObjs[i].Frozen) //Restore the frozen state of the entity and all of its objects + end + end + continue + end + + local index + local add + for k, Constraint in pairs( Ent.Constraints ) do + + /*if(!Constraint.BuildDupeInfo)then + Constraint.BuildDupeInfo = {} + end*/ + + if(!Constraint.Identity)then + index = Constraint:GetCreationID() + Constraint.Identity = Constraint:GetCreationID() + else + index = Constraint.Identity + end + + if ( index and !ConstraintTable[ index ] ) then + local ConstTable, ents = CopyConstraintTable( table.Copy(Constraint:GetTable()), Offset ) + //If the entity is constrained to an entity outside of the area box, don't copy the constraint. + if(!CopyOutside)then + add = true + for j,e in pairs(ents)do + if(!Entities[e:EntIndex()])then add=false end + end + if(add)then ConstraintTable[ index ] = ConstTable end + else //Copy entities and constraints outside of the box that are constrained to entities inside the box + for k,v in pairs(ents)do + ConstraintTable[ index ] = ConstTable + if(v:EntIndex()!=_)then + local AddEnts, AddConstrs = Copy(v, {}, {}, Offset) + for j,e in pairs(AddEnts)do + if(!EntTable[j])then EntTable[j] = e end + end + + for j,e in pairs(AddConstrs)do + if(!ConstraintTable[j])then ConstraintTable[j] = e end + end + end + end + end + end + end + + local PhysObjs = EntTable[Ent:EntIndex()].PhysicsObjects + for i=0, Ent:GetPhysicsObjectCount() do + phys = Ent:GetPhysicsObjectNum(i) + if(IsValid(phys))then + phys:EnableMotion(PhysObjs[i].Frozen) //Restore the frozen state of the entity and all of its objects + end + end + + end + + return EntTable, ConstraintTable +end + +--[[ + Name: CreateConstraintFromTable + Desc: Creates a constraint from a given table + Params:
Constraint,
EntityList,
EntityTable + Returns: CreatedConstraint +]] +local function CreateConstraintFromTable(Constraint, EntityList, EntityTable, Player) + + local Factory = duplicator.ConstraintType[ Constraint.Type ] + + if not Factory then return end + + local first --Ent1 or Ent in the constraint's table + local second --Any other Ent that is not Ent1 or Ent + + -- Build the argument list for the Constraint's spawn function + local Args = {} + local Val + for k, Key in pairs( Factory.Args ) do + + Val = Constraint[ Key ] + + if Key == "pl" or Key == "ply" then + Val = Player + end + + for i=1, 4 do + -- if(!ValidEntity(Constraint.Entity[i])) then Player:ChatPrint("DUPLICATOR: ERROR, Invalid constraints, maybe wrong file version.")return end + if ( Constraint.Entity and Constraint.Entity[ i ] ) then + if Key == "Ent"..i or Key == "Ent" then + if ( Constraint.Entity[ i ].World ) then + Val = GetWorldEntity() + else + Val = EntityList[ Constraint.Entity[ i ].Index ] + + if not ValidEntity(Val) then + if(Player)then + Player:ChatPrint("DUPLICATOR: ERROR, "..Constraint.Type.." Constraint could not find an entity!") + else + print("DUPLICATOR: ERROR, "..Constraint.Type.." Constraint could not find an entity!") + end + return + else + --Important for perfect duplication + --Get which entity is which so we can reposition them before constraining + if(Key== "Ent" || Key == "Ent1")then + first=Val + firstindex = Constraint.Entity[ i ].Index + else + second=Val + secondindex = Constraint.Entity[ i ].Index + end + + end + end + + end + + if Key == "Bone"..i or Key == "Bone" then Val = Constraint.Entity[ i ].Bone end + + if Key == "LPos"..i then + if (Constraint.Entity[i].World && Constraint.Entity[i].LPos)then + if(i==2 || i==4)then + Val = Constraint.Entity[i].LPos + EntityList[Constraint.Entity[1].Index]:GetPos() + elseif(i==1)then + if(Constraint.Entity[2])then + Val = Constraint.Entity[i].LPos + EntityList[Constraint.Entity[2].Index]:GetPos() + else + Val = Constraint.Entity[i].LPos + EntityList[Constraint.Entity[4].Index]:GetPos() + end + end + elseif( Constraint.Entity[i].LPos ) then + Val = Constraint.Entity[ i ].LPos + end + end + + if Key == "Length"..i then Val = Constraint.Entity[ i ].Length end + end + if Key == "WPos"..i then + if(!Constraint.Entity[1].World)then + Val = Constraint["WPos"..i] + EntityList[Constraint.Entity[1].Index]:GetPos() + else + Val = Constraint["WPos"..i] + EntityList[Constraint.Entity[4].Index]:GetPos() + end + end + + end + -- If there's a missing argument then unpack will stop sending at that argument + Val = Val or false + table.insert( Args, Val ) + + end + + local Bone1 + local Bone1Index + local ReEnableFirst + local Bone2 + local Bone2Index + local ReEnableSecond + if(Constraint.BuildDupeInfo)then + + if second ~= nil and not second:IsWorld() and Constraint.BuildDupeInfo.EntityPos ~= nil then + ReEnableSecond = second:GetPhysicsObject():IsMoveable() + second:GetPhysicsObject():EnableMotion(false) + second:SetPos(first:GetPos()-Constraint.BuildDupeInfo.EntityPos) + if(Constraint.BuildDupeInfo.Bone2) then + Bone2Index = Constraint.BuildDupeInfo.Bone2 + Bone2 = second:GetPhysicsObjectNum(Bone2Index) + Bone2:EnableMotion(false) + Bone2:SetPos(second:GetPos() + Constraint.BuildDupeInfo.Bone2Pos) + Bone2:SetAngle(Constraint.BuildDupeInfo.Bone2Angle) + end + end + + if first ~= nil and Constraint.BuildDupeInfo.Ent1Ang ~= nil then + ReEnableFirst = first:GetPhysicsObject():IsMoveable() + first:GetPhysicsObject():EnableMotion(false) + first:SetAngles(Constraint.BuildDupeInfo.Ent1Ang) + if(Constraint.BuildDupeInfo.Bone1) then + Bone1Index = Constraint.BuildDupeInfo.Bone1 + Bone1 = first:GetPhysicsObjectNum(Bone1Index) + Bone1:EnableMotion(false) + Bone1:SetPos(first:GetPos() + Constraint.BuildDupeInfo.Bone1Pos) + Bone1:SetAngle(Constraint.BuildDupeInfo.Bone1Angle) + end + end + + if second ~= nil and Constraint.BuildDupeInfo.Ent2Ang ~= nil then + second:SetAngles(Constraint.BuildDupeInfo.Ent2Ang) + end + + if second ~= nil and Constraint.BuildDupeInfo.Ent4Ang ~= nil then + second:SetAngles(Constraint.BuildDupeInfo.Ent4Ang) + end + end + + local Ent + local status = pcall( function() Ent = Factory.Func( unpack(Args) ) end ) + if not status then + if(Player)then + AdvDupe2.Notify(ply, "ERROR, Failed to create "..Constraint.Type.." Constraint!", NOTIFY_ERROR) + else + print("DUPLICATOR: ERROR, Failed to create "..Constraint.Type.." Constraint!") + end + return + end + + Ent.BuildDupeInfo = table.Copy(Constraint.BuildDupeInfo) + + //Move the entities back after constraining them + if(EntityTable)then + if(first!=nil)then + first:SetPos(EntityTable[firstindex].BuildDupeInfo.PosReset) + first:SetAngles(EntityTable[firstindex].BuildDupeInfo.AngleReset) + if(ReEnableFirst)then first:GetPhysicsObject():EnableMotion(true) end + if(Bone1)then + Bone1:SetPos(EntityTable[firstindex].BuildDupeInfo.PosReset + EntityTable[firstindex].BuildDupeInfo.PhysicsObjects[Bone1Index].Pos) + Bone1:SetAngle(EntityTable[firstindex].PhysicsObjects[Bone1Index].Angle) + end + end + if(second!=nil)then + second:SetPos(EntityTable[secondindex].BuildDupeInfo.PosReset) + second:SetAngles(EntityTable[secondindex].BuildDupeInfo.AngleReset) + if(ReEnableSecond)then second:GetPhysicsObject():EnableMotion(true) end + if(Bone2)then + Bone2:SetPos(EntityTable[secondindex].BuildDupeInfo.PosReset + EntityTable[secondindex].BuildDupeInfo.PhysicsObjects[Bone2Index].Pos) + Bone2:SetAngle(EntityTable[secondindex].PhysicsObjects[Bone2Index].Angle) + end + end + end + + if(Ent and Ent.length)then Ent.length = Constraint["length"] end //Fix for weird bug with ropes + + return Ent +end + +--[[ + Name: DoGenericPhysics + Desc: Applies bone data, generically. + Params: Player,
data + Returns: Entity,
data +]] +local function DoGenericPhysics( Entity, data, Player ) + + if (!data) then return end + if (!data.PhysicsObjects) then return end + local Phys + for Bone, Args in pairs( data.PhysicsObjects ) do + Phys = Entity:GetPhysicsObjectNum(Bone) + if ( IsValid(Phys) ) then + Phys:SetPos( Args.Pos ) + Phys:SetAngle( Args.Angle ) + if ( Args.Frozen == true ) then + Phys:EnableMotion( false ) + if(Player)then Player:AddFrozenPhysicsObject( Entity, Phys ) end + end + end + end +end + +local function reportclass(ply,class) + umsg.Start("AdvDupe2_ReportClass", ply) + umsg.String(class) + umsg.End() +end + +local function reportmodel(ply,model) + umsg.Start("AdvDupe2_ReportModel", ply) + umsg.String(model) + umsg.End() +end + +--[[ + Name: GenericDuplicatorFunction + Desc: Override the default duplicator's GenericDuplicatorFunction function + Params: Player,
data + Returns: Entity +]] +local function GenericDuplicatorFunction( data, Player ) + + local Entity = ents.Create( data.Class ) + if ( !ValidEntity(Entity) ) then + if(Player)then + reportclass(Player,data.Class) + else + print("Advanced Duplicator 2 Invalid Class: "..data.Class) + end + return nil + end + + if( !util.IsValidModel(data.Model) )then + if(Player)then + reportmodel(Player,data.Model) + else + print("Advanced Duplicator 2 Invalid Model: "..data.Model) + end + return nil + end + + duplicator.DoGeneric( Entity, data ) + Entity:Spawn() + Entity:Activate() + DoGenericPhysics( Entity, data, Player ) + + table.Add( Entity:GetTable(), data ) + + return Entity + +end + +--[[ + Name: MakeProp + Desc: Make prop without spawn effects + Params: Player, Pos, Ang, Model,
PhysicsObject,
Data + Returns: Prop +]] +local function MakeProp(Player, Pos, Ang, Model, PhysicsObject, Data) + + if( !util.IsValidModel(Model) )then + if(Player)then + reportmodel(Player,Data.Model) + else + print("Advanced Duplicator 2 Invalid Model: "..Model) + end + return nil + end + + Data.Pos = Pos + Data.Angle = Ang + Data.Model = Model + + // Make sure this is allowed + if( Player )then + if ( !gamemode.Call( "PlayerSpawnProp", Player, Model ) ) then return false end + end + + local Prop = ents.Create( "prop_physics" ) + if !IsValid(Prop) then return false end + + duplicator.DoGeneric( Prop, Data ) + Prop:Spawn() + Prop:Activate() + DoGenericPhysics( Prop, Data, Player ) + duplicator.DoFlex( Prop, Data.Flex, Data.FlexScale ) + + return Prop +end + +--[[ + Name: CreateEntityFromTable + Desc: Creates an entity from a given table + Params:
EntTable, Player + Returns: nil +]] +local function CreateEntityFromTable(EntTable, Player) + + local EntityClass = duplicator.FindEntityClass( EntTable.Class ) + if ( !Player:IsAdmin( ) && !Player:IsSuperAdmin() && !SinglePlayer())then + if(!AdvDupe2.duplicator.EntityList[EntTable.Class])then + Player:ChatPrint([[Entity Class Black listed, "]]..EntTable.Class..[["]]) + return nil + end + end + + local sent = false + local status, valid + local GENERIC = false + // This class is unregistered. Instead of failing try using a generic + // Duplication function to make a new copy. + if (!EntityClass) then + GENERIC = true + sent = true + + if(EntTable.Class=="prop_effect")then + sent = gamemode.Call( "PlayerSpawnEffect", Player, EntTable.Model) + else + sent = gamemode.Call( "PlayerSpawnSENT", Player, EntTable.Class) + end + + if(!sent)then + print("Advanced Duplicator 2: Creation rejected for class, : "..EntTable.Class) + return nil + end + + if(AdvDupe2.duplicator.WhiteList[EntTable.Class] || (EntTable.BuildDupeInfo.IsNPC && ((tobool(GetConVarString("AdvDupe2_AllowNPCPasting")) && string.sub(EntTable.Class, 1, 4)=="npc_") || SinglePlayer())))then + status, valid = pcall(GenericDuplicatorFunction, EntTable, Player ) + else + print("Advanced Duplicator 2: ENTITY CLASS IS BLACKLISTED, CLASS NAME: "..EntTable.Class) + return nil + end + end + + if(!GENERIC)then + + // Build the argument list for the Entitie's spawn function + local ArgList = {} + local Arg + for iNumber, Key in pairs( EntityClass.Args ) do + + Arg = nil + + // Translate keys from old system + if ( Key == "pos" || Key == "position" ) then Key = "Pos" end + if ( Key == "ang" || Key == "Ang" || Key == "angle" ) then Key = "Angle" end + if ( Key == "model" ) then Key = "Model" end + if ( Key == "VehicleTable" )then //Exploit fix, not sure if its still an exploit + EntTable[Key] = {vehiclescript=EntTable[Key].vehiclescript, limitview=EntTable[Key].limitview} + end + Arg = EntTable[ Key ] + + // Special keys + if ( Key == "Data" ) then Arg = EntTable end + + // If there's a missing argument then unpack will stop sending at that argument + if ( Arg == nil ) then Arg = false end + + ArgList[ iNumber ] = Arg + + end + // Create and return the entity + if(EntTable.Class=="prop_physics")then + valid = MakeProp(Player, unpack(ArgList)) //Create prop_physics like this because if the model doesn't exist it will cause + elseif(AdvDupe2.duplicator.WhiteList[EntTable.Class] || (EntTable.BuildDupeInfo.IsNPC && ((tobool(GetConVarString("AdvDupe2_AllowNPCPasting")) && string.sub(EntTable.Class, 1, 4)=="npc_") || SinglePlayer())))then + //Create sents using their spawn function with the arguments we stored earlier + sent = true + + if(!EntTable.BuildDupeInfo.IsVehicle || !EntTable.BuildDupeInfo.IsNPC || EntTable.Class!="prop_ragdoll")then //These three are auto done + sent = gamemode.Call( "PlayerSpawnSENT", Player, EntTable.Class) + end + + if(!sent)then + print("Advanced Duplicator 2: Creation rejected for class, : "..EntTable.Class) + return nil + end + + status,valid = pcall( EntityClass.Func, Player, unpack(ArgList) ) + else + print("Advanced Duplicator 2: ENTITY CLASS IS BLACKLISTED, CLASS NAME: "..EntTable.Class) + return nil + end + end + + //If its a valid entity send it back to the entities list so we can constrain it + if( status!=false and IsValid(valid) )then + if(sent)then + local iNumPhysObjects = valid:GetPhysicsObjectCount() + local PhysObj + for Bone = 0, iNumPhysObjects-1 do + PhysObj = valid:GetPhysicsObjectNum( Bone ) + if IsValid(PhysObj) then + PhysObj:EnableMotion(false) + end + end + if(Player)then + if(!valid:IsVehicle() && EntTable.Class!="prop_ragdoll" && !valid:IsNPC())then //These three get called automatically + if(EntTable.Class=="prop_effect")then + gamemode.Call("PlayerSpawnedEffect", Player, valid:GetModel(), valid) + else + gamemode.Call("PlayerSpawnedSENT", Player, valid) + end + end + end + else + gamemode.Call( "PlayerSpawnedProp", Player, valid:GetModel(), valid ) + end + + return valid + else + if(valid==false)then + return false + else + return nil + end + end + +end + +--[[ + Name: Paste + Desc: Override the default duplicator's paste function + Params: Player,
Entities,
Constraints + Returns:
Entities,
Constraints +]] + +function AdvDupe2.duplicator.Paste( Player, EntityList, ConstraintList, Position, AngleOffset, OrigPos, Parenting ) + + table.SortByMember(ConstraintList, "Identity", function(a, b) return a > b end) + + local CreatedEntities = {} + -- + -- Create entities + -- + local proppos + for k, v in pairs( EntityList ) do + if(!v.BuildDupeInfo)then v.BuildDupeInfo={} end + v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects) + proppos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PhysicsObjects[0].Pos = Vector(0,0,0) + if( OrigPos )then + for i,p in pairs(v.BuildDupeInfo.PhysicsObjects) do + v.PhysicsObjects[i].Pos = p.Pos + proppos + OrigPos + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.PosReset = v.Pos + v.BuildDupeInfo.AngleReset = v.Angle + else + for i,p in pairs(v.BuildDupeInfo.PhysicsObjects)do + v.PhysicsObjects[i].Pos, v.PhysicsObjects[i].Angle = LocalToWorld(p.Pos + proppos, p.Angle, Position, AngleOffset) + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PosReset = v.Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.AngleReset = v.Angle + end + + CreatedEntities[k] = CreateEntityFromTable(v, Player) + + if CreatedEntities[ k ] then + if(Player)then Player:AddCleanup( "AdvDupe2", CreatedEntities[ k ] ) end + CreatedEntities[ k ].BoneMods = table.Copy( v.BoneMods ) + CreatedEntities[ k ].EntityMods = table.Copy( v.EntityMods ) + CreatedEntities[ k ].PhysicsObjects = table.Copy( v.PhysicsObjects ) + if(v.CollisionGroup)then CreatedEntities[ k ]:SetCollisionGroup(v.CollisionGroup) end + duplicator.ApplyEntityModifiers ( Player, CreatedEntities[ k ] ) + duplicator.ApplyBoneModifiers ( Player, CreatedEntities[ k ] ) + CreatedEntities[k]:SetNotSolid(true) + elseif(CreatedEntities[ k ]==false)then + CreatedEntities[ k ] = nil + ConstraintList = {} + break + else + CreatedEntities[ k ] = nil + end + + end + + local CreatedConstraints = {} + local Entity + -- + -- Create constraints + -- + + for k, Constraint in pairs( ConstraintList ) do + Entity = CreateConstraintFromTable( Constraint, CreatedEntities, EntityList, Player ) + if(IsValid(Entity))then + table.insert( CreatedConstraints, Entity ) + end + end + + if(Player)then + + undo.Create "AdvDupe2_Paste" + + for _,v in pairs( CreatedEntities ) do + --If the entity has a PostEntityPaste function tell it to use it now + if v.PostEntityPaste then + v:PostEntityPaste( Player, v, CreatedEntities ) + end + v:GetPhysicsObject():EnableMotion(false) + + if(EntityList[_].BuildDupeInfo.DupeParentID!=nil)then + v:SetParent(CreatedEntities[EntityList[_].BuildDupeInfo.DupeParentID]) + end + + v:SetNotSolid(false) + undo.AddEntity( v ) + end + + undo.SetPlayer( Player ) + + undo.Finish() + + //if(Tool)then AdvDupe2.FinishPasting(Player, true) end + + else + + for _,v in pairs( CreatedEntities ) do + --If the entity has a PostEntityPaste function tell it to use it now + if v.PostEntityPaste then + v:PostEntityPaste( Player, v, CreatedEntities ) + end + v:GetPhysicsObject():EnableMotion(false) + + if(EntityList[_].BuildDupeInfo.DupeParentID!=nil && Parenting)then + v:SetParent(CreatedEntities[EntityList[_].BuildDupeInfo.DupeParentID]) + end + + v:SetNotSolid(false) + end + end + + return CreatedEntities, CreatedConstraints + +end + + +local function AdvDupe2_Spawn() + + local Queue = AdvDupe2.JobManager.Queue[AdvDupe2.JobManager.CurrentPlayer] + + if(IsValid(Queue.Player))then + if(Queue.Entity)then + if(Queue.Current==1)then + + AdvDupe2.InitProgressBar(Queue.Player,"Pasting:") + Queue.Player.AdvDupe2.Queued = false + end + local newpos + if(Queue.Current>#Queue.SortedEntities)then + Queue.Entity = false + Queue.Constraint = true + Queue.Current = 1 + return + end + if(!Queue.SortedEntities[Queue.Current])then Queue.Current = Queue.Current+1 return end + + local k = Queue.SortedEntities[Queue.Current] + local v = Queue.EntityList[k] + + if(!v.BuildDupeInfo)then v.BuildDupeInfo={} end + if(v.LocalPos)then + for i,p in pairs(v.PhysicsObjects) do + v.PhysicsObjects[i] = {Pos=v.LocalPos, Angle=v.LocalAngle} + end + end + + v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects) + proppos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PhysicsObjects[0].Pos = Vector(0,0,0) + if( Queue.OrigPos )then + for i,p in pairs(v.BuildDupeInfo.PhysicsObjects) do + v.PhysicsObjects[i].Pos = p.Pos + proppos + Queue.OrigPos + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.PosReset = v.Pos + v.BuildDupeInfo.AngleReset = v.Angle + else + for i,p in pairs(v.BuildDupeInfo.PhysicsObjects)do + + v.PhysicsObjects[i].Pos, v.PhysicsObjects[i].Angle = LocalToWorld(p.Pos + proppos, p.Angle, Queue.PositionOffset, Queue.AngleOffset) + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PosReset = v.Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.AngleReset = v.Angle + end + + //if(v.SavedParentIdx)then v.BuildDupeInfo.DupeParentID = v.SavedParentIdx end + Queue.CreatedEntities[k] = CreateEntityFromTable(v, Queue.Player) + + if Queue.CreatedEntities[ k ] then + Queue.Player:AddCleanup( "AdvDupe2", Queue.CreatedEntities[ k ] ) + Queue.CreatedEntities[ k ].BoneMods = table.Copy( v.BoneMods ) + Queue.CreatedEntities[ k ].EntityMods = table.Copy( v.EntityMods ) + Queue.CreatedEntities[ k ].PhysicsObjects = table.Copy( v.PhysicsObjects ) + if(v.CollisionGroup)then Queue.CreatedEntities[ k ]:SetCollisionGroup(v.CollisionGroup) end + duplicator.ApplyEntityModifiers ( Queue.Player, Queue.CreatedEntities[ k ] ) + duplicator.ApplyBoneModifiers ( Queue.Player, Queue.CreatedEntities[ k ] ) + Queue.CreatedEntities[k]:SetNotSolid(true) + elseif(Queue.CreatedEntities[ k ]==false)then + Queue.CreatedEntities[ k ] = nil + Queue.Entity = false + Queue.Constraint = true + Queue.Current = 1 + Queue.ConstraintList = {} + else + Queue.CreatedEntities[ k ] = nil + end + + local perc = math.floor((Queue.Percent*Queue.Current)*100) + AdvDupe2.UpdateProgressBar(Queue.Player,perc) + Queue.Current = Queue.Current+1 + if(Queue.Current>#Queue.SortedEntities)then + Queue.Entity = false + Queue.Constraint = true + Queue.Current = 1 + end + + if(#AdvDupe2.JobManager.Queue>=AdvDupe2.JobManager.CurrentPlayer+1)then + AdvDupe2.JobManager.CurrentPlayer = AdvDupe2.JobManager.CurrentPlayer+1 + else + AdvDupe2.JobManager.CurrentPlayer = 1 + end + else + if(#Queue.ConstraintList>0)then + + if(#AdvDupe2.JobManager.Queue==0)then + hook.Remove("Tick", "AdvDupe2_Spawning") + AdvDupe2.JobManager.PastingHook = false + end + if(!Queue.ConstraintList[Queue.Current])then Queue.Current = Queue.Current+1 return end + + local Entity = CreateConstraintFromTable( Queue.ConstraintList[Queue.Current], Queue.CreatedEntities, Queue.EntityList, Queue.Player ) + + if IsValid(Entity) then + table.insert( Queue.CreatedConstraints, Entity ) + end + elseif(Queue.ConstraintList && Queue.ConstraintList!={})then + local tbl = {} + for k,v in pairs(Queue.ConstraintList)do + table.insert(tbl, v) + end + Queue.ConstraintList = tbl + Queue.Current=0 + end + local perc = math.floor((Queue.Percent*(Queue.Current+Queue.Plus))*100) + AdvDupe2.UpdateProgressBar(Queue.Player,perc) + Queue.Current = Queue.Current+1 + + + if(Queue.Current>#Queue.ConstraintList)then + + local unfreeze = tobool(Queue.Player:GetInfo("advdupe2_paste_unfreeze")) or false + local preservefrozenstate = tobool(Queue.Player:GetInfo("advdupe2_preserve_freeze")) or false + + //Remove the undo for stopping pasting + local undos = undo.GetTable()[Queue.Player:UniqueID()] + local str = "AdvDupe2_"..Queue.Player:UniqueID() + for i=#undos, 1, -1 do + if(undos[i] && undos[i].Name == str)then + undos[i] = nil + umsg.Start( "Undone", Queue.Player ) + umsg.Long( i ) + umsg.End() + break + end + end + + undo.Create "AdvDupe2" + local phys + local edit + for _,v in pairs( Queue.CreatedEntities ) do + + edit = true + if(Queue.EntityList[_].BuildDupeInfo.DupeParentID!=nil && Queue.Parenting)then + v:SetParent(Queue.CreatedEntities[Queue.EntityList[_].BuildDupeInfo.DupeParentID]) + if(v.Constraints!=nil)then + for i,c in pairs(v.Constraints)do + if(c && constraints[c.Type])then + edit=false + break + end + end + end + if(edit)then + v:SetCollisionGroup(COLLISION_GROUP_WORLD) + v:GetPhysicsObject():EnableMotion(false) + v:GetPhysicsObject():Sleep() + end + else + edit=false + end + + --If the entity has a PostEntityPaste function tell it to use it now + if v.PostEntityPaste then + v:PostEntityPaste( Queue.Player, v, Queue.CreatedEntities ) + end + + if(unfreeze)then + for i=0, v:GetPhysicsObjectCount() do + phys = v:GetPhysicsObjectNum(i) + if(IsValid(phys))then + phys:EnableMotion(true) //Unfreeze the entitiy and all of its objects + end + end + elseif(preservefrozenstate)then + for i=0, v:GetPhysicsObjectCount() do + phys = v:GetPhysicsObjectNum(i) + if(IsValid(phys))then + phys:EnableMotion(Queue.EntityList[_].BuildDupeInfo.PhysicsObjects[i].Frozen) //Restore the entity and all of its objects to their original frozen state + end + end + else + for i=0, v:GetPhysicsObjectCount() do + phys = v:GetPhysicsObjectNum(i) + if(IsValid(phys) && phys:IsMoveable())then + phys:EnableMotion(false) //Freeze the entitiy and all of its objects + Queue.Player:AddFrozenPhysicsObject( v, phys ) + end + end + end + + if(!edit || !Queue.DisableParents)then + v:SetNotSolid(false) + end + undo.AddEntity( v ) + end + undo.SetPlayer( Queue.Player ) + undo.Finish() + + hook.Call("AdvDupe_FinishPasting", nil, {{EntityList=Queue.EntityList, CreatedEntities=Queue.CreatedEntities, ConstraintList=Queue.ConstraintList, CreatedConstraints=Queue.CreatedConstraints, HitPos=Queue.PositionOffset}}, 1) + AdvDupe2.FinishPasting(Queue.Player, true) + + table.remove(AdvDupe2.JobManager.Queue, AdvDupe2.JobManager.CurrentPlayer) + if(#AdvDupe2.JobManager.Queue==0)then + hook.Remove("Tick", "AdvDupe2_Spawning") + AdvDupe2.JobManager.PastingHook = false + end + end + if(#AdvDupe2.JobManager.Queue>=AdvDupe2.JobManager.CurrentPlayer+1)then + AdvDupe2.JobManager.CurrentPlayer = AdvDupe2.JobManager.CurrentPlayer+1 + else + AdvDupe2.JobManager.CurrentPlayer = 1 + end + + end + else + table.remove(AdvDupe2.JobManager.Queue, AdvDupe2.JobManager.CurrentPlayer) + if(#AdvDupe2.JobManager.Queue==0)then + hook.Remove("Tick", "AdvDupe2_Spawning") + AdvDupe2.JobManager.PastingHook = false + end + end +end + +local function ErrorCatchSpawning() + + local status, error = pcall(AdvDupe2_Spawn) + if(!status)then + //PUT ERROR LOGGING HERE + + local Queue = AdvDupe2.JobManager.Queue[AdvDupe2.JobManager.CurrentPlayer] + + local undos = undo.GetTable()[Queue.Player:UniqueID()] + local str = "AdvDupe2_"..Queue.Player:UniqueID() + for i=#undos, 1, -1 do + if(undos[i] && undos[i].Name == str)then + undos[i] = nil + umsg.Start( "Undone", Queue.Player ) + umsg.Long( i ) + umsg.End() + break + end + end + + for k,v in pairs(Queue.CreatedEntities)do + v:Remove() + end + Queue.Player:ChatPrint([[Error spawning your contraptions, "]]..error..[["]]) + AdvDupe2.FinishPasting(Queue.Player, true) + + table.remove(AdvDupe2.JobManager.Queue, AdvDupe2.JobManager.CurrentPlayer) + + + if(#AdvDupe2.JobManager.Queue==0)then + hook.Remove("Tick", "AdvDupe2_Spawning") + AdvDupe2.JobManager.PastingHook = false + else + if(#Queue ply + Return:
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(" 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:
dupe,
info, callback, <...> args + Return: runs callback( 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: encodedDupe, callback, <...> args + Return: runs callback( success,
tbl,
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 \ No newline at end of file diff --git a/lua/advdupe2/sv_file.lua b/lua/advdupe2/sv_file.lua new file mode 100644 index 0000000..c8f2301 --- /dev/null +++ b/lua/advdupe2/sv_file.lua @@ -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: dupe, name + Return: success/ 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: name + Return: 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 diff --git a/lua/advdupe2/sv_misc.lua b/lua/advdupe2/sv_misc.lua new file mode 100644 index 0000000..b29aa29 --- /dev/null +++ b/lua/advdupe2/sv_misc.lua @@ -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: 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") \ No newline at end of file diff --git a/lua/advdupe2/sv_networking.lua b/lua/advdupe2/sv_networking.lua new file mode 100644 index 0000000..2ea03ec --- /dev/null +++ b/lua/advdupe2/sv_networking.lua @@ -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) diff --git a/lua/autorun/client/advdupe2_cl_init.lua b/lua/autorun/client/advdupe2_cl_init.lua new file mode 100644 index 0000000..c2bf11f --- /dev/null +++ b/lua/autorun/client/advdupe2_cl_init.lua @@ -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) \ No newline at end of file diff --git a/lua/autorun/server/advdupe2_sv_init.lua b/lua/autorun/server/advdupe2_sv_init.lua new file mode 100644 index 0000000..d9f7d4d --- /dev/null +++ b/lua/autorun/server/advdupe2_sv_init.lua @@ -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) diff --git a/lua/entities/gmod_contr_spawner/cl_init.lua b/lua/entities/gmod_contr_spawner/cl_init.lua new file mode 100644 index 0000000..a2eab73 --- /dev/null +++ b/lua/entities/gmod_contr_spawner/cl_init.lua @@ -0,0 +1,6 @@ +include("shared.lua") + +function ENT:Draw() + self.BaseClass.Draw(self) + self.Entity:DrawModel() +end diff --git a/lua/entities/gmod_contr_spawner/init.lua b/lua/entities/gmod_contr_spawner/init.lua new file mode 100644 index 0000000..c484460 --- /dev/null +++ b/lua/entities/gmod_contr_spawner/init.lua @@ -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 ) \ No newline at end of file diff --git a/lua/entities/gmod_contr_spawner/shared.lua b/lua/entities/gmod_contr_spawner/shared.lua new file mode 100644 index 0000000..bfc1947 --- /dev/null +++ b/lua/entities/gmod_contr_spawner/shared.lua @@ -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 diff --git a/lua/weapons/gmod_tool/stools/advdupe2.lua b/lua/weapons/gmod_tool/stools/advdupe2.lua new file mode 100644 index 0000000..18ca461 --- /dev/null +++ b/lua/weapons/gmod_tool/stools/advdupe2.lua @@ -0,0 +1,2051 @@ +--[[ + Title: Adv. Dupe 2 Tool + + Desc: Defines the AD2 tool and assorted functionalities. + + Author: TB + + Version: 1.0 +]] + + +TOOL.Category = "Construction" +TOOL.Name = "#Advanced Duplicator 2" +cleanup.Register( "AdvDupe2" ) + + + +--[[ + Name: LeftClick + Desc: Defines the tool's behavior when the player left-clicks. + Params: trace + Returns: success +]] +function TOOL:LeftClick( trace ) + + if(!trace)then return false end + if(CLIENT)then return true end + + local ply = self:GetOwner() + + if(!ply.AdvDupe2 || !ply.AdvDupe2.Entities)then return false end + + if(ply.AdvDupe2.Pasting || ply.AdvDupe2.Downloading)then + AdvDupe2.Notify(ply,"Advanced Duplicator 2 is busy.",NOTIFY_ERROR) + return false + end + + local z = tonumber(ply:GetInfo("advdupe2_offset_z")) + ply.AdvDupe2.HeadEnt.Z + ply.AdvDupe2.Position = trace.HitPos + Vector(0, 0, z) + ply.AdvDupe2.Angle = Angle(tonumber(ply:GetInfo("advdupe2_offset_pitch")), tonumber(ply:GetInfo("advdupe2_offset_yaw")), tonumber(ply:GetInfo("advdupe2_offset_roll"))) + + if(tobool(ply:GetInfo("advdupe2_offset_world")))then ply.AdvDupe2.Angle = ply.AdvDupe2.Angle - ply.AdvDupe2.Entities[ply.AdvDupe2.HeadEnt.Index].PhysicsObjects[0].Angle end + ply.AdvDupe2.Pasting = true + umsg.Start("AdvDupe2_NotGhosting", ply) + umsg.End() + AdvDupe2.Notify(ply,"Pasting...") + local origin + if(tobool(ply:GetInfo("advdupe2_original_origin")))then + origin = ply.AdvDupe2.HeadEnt.Pos + end + AdvDupe2.InitPastingQueue(ply, ply.AdvDupe2.Position, ply.AdvDupe2.Angle, origin, tobool(ply:GetInfo("advdupe2_paste_constraints")), tobool(ply:GetInfo("advdupe2_paste_parents")), tobool(ply:GetInfo("advdupe2_paste_disparents"))) + //AdvDupe2.duplicator.Paste(ply, table.Copy(ply.AdvDupe2.Entities), table.Copy(ply.AdvDupe2.Constraints), ply.AdvDupe2.Position, ply.AdvDupe2.Angle, nil, true) + return true +end + + +//Thanks to Donovan for fixing the table +//Turns a table into a numerically indexed table +local function CollapseTableToArray( t ) + + local array = {} + local q = {} + local min, max = 0, 0 + --get the bounds + for k in pairs(t) do + if not min and not max then min,max = k,k end + min = (k < min) and k or min + max = (k > max) and k or max + end + for i=min, max do + if t[i] then + array[#array+1] = t[i] + end + end + return array +end + +//Find all the entities in a box, given the adjacent corners and the player +local function FindInBox(min, max, ply) + + local Entities = ents.GetAll() + local EntTable = {} + local pos + for _,ent in pairs(Entities) do + pos = ent:GetPos() + if (pos.X>=min.X) and (pos.X<=max.X) and (pos.Y>=min.Y) and (pos.Y<=max.Y) and (pos.Z>=min.Z) and (pos.Z<=max.Z) and (AdvDupe2.duplicator.EntityList[ent:GetClass()] ~= nil) then + if CPPI then + if ent:CPPICanTool(ply, "advdupe2") then + EntTable[ent:EntIndex()] = ent + end + else + EntTable[ent:EntIndex()] = ent + end + end + end + + return EntTable +end + +//Start drawing the area copy box +function AdvDupe2.DrawSelectBox(ply) + umsg.Start("AdvDupe2_DrawSelectBox", ply) + umsg.End() +end + +//Removes the area copy box +function AdvDupe2.RemoveSelectBox(ply) + umsg.Start("AdvDupe2_RemoveSelectBox", ply) + umsg.End() +end + +//Reset the offsets of height, pitch, yaw, and roll back to default +function AdvDupe2.ResetOffsets(ply) + umsg.Start("AdvDupe2_ResetOffsets", ply) + umsg.End() +end + +//Remove player's ghosts and tell the client to stop updating ghosts +local function RemoveGhosts(ply) + ply.AdvDupe2.Ghosting = false + if(ply.AdvDupe2.GhostEntities)then + for k,v in pairs(ply.AdvDupe2.GhostEntities)do + if(IsValid(v))then + v:Remove() + end + end + end + ply.AdvDupe2.GhostEntities = nil + ply.AdvDupe2.GhostToSpawn = nil + ply.AdvDupe2.CurrentGhost = 1 + umsg.Start("AdvDupe2_NotGhosting", ply) + umsg.End() +end + +--[[ + Name: RightClick + Desc: Defines the tool's behavior when the player right-clicks. + Params: trace + Returns: success +]] +function TOOL:RightClick( trace ) + + if CLIENT then return true end + + local ply = self:GetOwner() + + if(!ply.AdvDupe2)then ply.AdvDupe2 = {} end + if(ply.AdvDupe2.Pasting || ply.AdvDupe2.Downloading)then + AdvDupe2.Notify(ply,"Advanced Duplicator 2 is busy.",NOTIFY_ERROR) + return false + end + + //Set Area Copy on or off + if( ply:KeyDown(IN_SPEED) && !ply:KeyDown(IN_WALK) )then + if( self:GetStage()==0)then + AdvDupe2.DrawSelectBox(ply) + self:SetStage(1) + return false + elseif(self:GetStage()==1)then + AdvDupe2.RemoveSelectBox(ply) + self:SetStage(0) + return false + end + end + + if(!trace or !trace.Hit)then return false end + //If area copy is on and an ent was not right clicked, do an area copy and pick an ent + if( self:GetStage()==1 and !IsValid(trace.Entity) )then + if( !SinglePlayer() && (tonumber(ply:GetInfo("advdupe2_area_copy_size"))or 50) > tonumber(GetConVarString("AdvDupe2_MaxAreaCopySize")))then + AdvDupe2.Notify(ply,"Area copy size exceeds limit of "..GetConVarString("AdvDupe2_MaxAreaCopySize")..".",NOTIFY_ERROR) + return false + end + local i = tonumber(ply:GetInfo("advdupe2_area_copy_size")) or 50 + local Pos = trace.HitPos + local T = (Vector(i,i,i)+Pos) + local B = (Vector(-i,-i,-i)+Pos) + local timecheck = SysTime() + local Entities = FindInBox(B,T, ply) + + if(table.Count(Entities)==0)then + self:SetStage(0) + AdvDupe2.RemoveSelectBox(ply) + return true + end + + RemoveGhosts(ply) + ply.AdvDupe2.HeadEnt = {} + ply.AdvDupe2.Entities = {} + ply.AdvDupe2.Constraints = {} + + ply.AdvDupe2.HeadEnt.Index = table.GetFirstKey(Entities) + ply.AdvDupe2.HeadEnt.Pos = Entities[ply.AdvDupe2.HeadEnt.Index]:GetPos() + + local Outside = false + if((tonumber(ply:GetInfo("advdupe2_copy_outside")) or 0)==1)then + Outside = true + end + + ply.AdvDupe2.Entities, ply.AdvDupe2.Constraints = AdvDupe2.duplicator.AreaCopy(Entities, ply.AdvDupe2.HeadEnt.Pos, Outside) + + local tracedata = {} + tracedata.mask = MASK_NPCWORLDSTATIC + tracedata.start = ply.AdvDupe2.HeadEnt.Pos+Vector(0,0,1) + tracedata.endpos = ply.AdvDupe2.HeadEnt.Pos-Vector(0,0,50000) + local WorldTrace = util.TraceLine( tracedata ) + if(WorldTrace.Hit)then ply.AdvDupe2.HeadEnt.Z = math.abs(ply.AdvDupe2.HeadEnt.Pos.Z-WorldTrace.HitPos.Z) else ply.AdvDupe2.HeadEnt.Z = 0 end + + AdvDupe2.RemoveSelectBox(ply) + else //Area Copy is off or the ent is valid + + //Non valid entity or clicked the world + if(!IsValid(trace.Entity))then + + //If shift and alt are being held, clear the dupe + if(ply:KeyDown(IN_WALK) && ply:KeyDown(IN_SPEED))then + + RemoveGhosts(ply) + ply.AdvDupe2.Entities = nil + ply.AdvDupe2.Constraints = nil + umsg.Start("AdvDupe2_ResetDupeInfo", ply) + umsg.End() + AdvDupe2.ResetOffsets(ply) + end + return false + end + + //If Alt is being held, add a prop to the dupe + if(self:GetStage()==0 and ply:KeyDown(IN_WALK) and ply.AdvDupe2.Entities!=nil and table.Count(ply.AdvDupe2.Entities)>0)then + AdvDupe2.duplicator.Copy( trace.Entity, ply.AdvDupe2.Entities, ply.AdvDupe2.Constraints, ply.AdvDupe2.HeadEnt.Pos) + + ply.AdvDupe2.Constraints = CollapseTableToArray(ply.AdvDupe2.Constraints) + + umsg.Start("AdvDupe2_SetDupeInfo", ply) + umsg.String("") + umsg.String("") + umsg.String("") + umsg.String(os.date("%I:%M %p")) + umsg.String("") + umsg.String("") + umsg.String(table.Count(ply.AdvDupe2.Entities)) + umsg.String(#ply.AdvDupe2.Constraints) + umsg.End() + + //Only add the one ghost + local index = trace.Entity:EntIndex() + if(ply.AdvDupe2.Entities[index] && !ply.AdvDupe2.GhostEntities[index])then + if(!ply.AdvDupe2.GhostToSpawn)then ply.AdvDupe2.GhostToSpawn={} end + ply.AdvDupe2.GhostToSpawn[#ply.AdvDupe2.GhostToSpawn] = index + ply.AdvDupe2.LastGhost = CurTime()+0.02 + ply.AdvDupe2.Ghosting = true + end + + else + RemoveGhosts(ply) + + ply.AdvDupe2.HeadEnt = {} + ply.AdvDupe2.HeadEnt.Index = trace.Entity:EntIndex() + ply.AdvDupe2.Entities = {} + ply.AdvDupe2.Constraints = {} + ply.AdvDupe2.HeadEnt.Pos = trace.HitPos //trace.Entity:GetPos() + + local tracedata = {} + tracedata.mask = MASK_NPCWORLDSTATIC + tracedata.start = ply.AdvDupe2.HeadEnt.Pos + tracedata.endpos = ply.AdvDupe2.HeadEnt.Pos-Vector(0,0,50000) + local WorldTrace = util.TraceLine( tracedata ) + if WorldTrace.Hit then ply.AdvDupe2.HeadEnt.Z = math.abs(ply.AdvDupe2.HeadEnt.Pos.Z-WorldTrace.HitPos.Z) else ply.AdvDupe2.HeadEnt.Z=0 end + + //Area Copy is off, do a regular copy + if(self:GetStage()==0)then + AdvDupe2.duplicator.Copy( trace.Entity, ply.AdvDupe2.Entities, ply.AdvDupe2.Constraints, trace.HitPos ) //ply.AdvDupe2.HeadEnt.Pos ) + else //Area copy is on and an ent was clicked, do an area copy + if( !SinglePlayer() && (tonumber(ply:GetInfo("advdupe2_area_copy_size"))or 50) > tonumber(GetConVarString("AdvDupe2_MaxAreaCopySize")))then + AdvDupe2.Notify(ply,"Area copy size exceeds limit of "..GetConVarString("AdvDupe2_MaxAreaCopySize")..".",NOTIFY_ERROR) + return false + end + local i = tonumber(ply:GetInfo("advdupe2_area_copy_size")) or 50 + local Pos = ply.AdvDupe2.HeadEnt.Pos + local T = (Vector(i,i,i)+Pos) + local B = (Vector(-i,-i,-i)+Pos) + + local Outside = false + if((tonumber(ply:GetInfo("advdupe2_copy_outside")) or 0)==1)then + Outside = true + end + + local Entities = FindInBox(B,T, ply) + + ply.AdvDupe2.Entities, ply.AdvDupe2.Constraints = AdvDupe2.duplicator.AreaCopy(Entities, Pos, Outside) + + self:SetStage(0) + AdvDupe2.RemoveSelectBox(ply) + end + end + end + + ply.AdvDupe2.Constraints = CollapseTableToArray(ply.AdvDupe2.Constraints) + + umsg.Start("AdvDupe2_SetDupeInfo", ply) + umsg.String("") + umsg.String(ply:Nick()) + umsg.String(os.date("%d %B %Y")) + umsg.String(os.date("%I:%M %p")) + umsg.String("") + umsg.String("") + umsg.String(table.Count(ply.AdvDupe2.Entities)) + umsg.String(#ply.AdvDupe2.Constraints) + umsg.End() + + AdvDupe2.StartGhosting(ply) + + if(self:GetStage()==1)then + self:SetStage(0) + AdvDupe2.RemoveSelectBox(ply) + end + + AdvDupe2.ResetOffsets(ply) + + return true +end + +//Called to clean up the tool when pasting is finished or undo during pasting +function AdvDupe2.FinishPasting(Player, Paste) + Player.AdvDupe2.Pasting=false + AdvDupe2.RemoveProgressBar(Player) + + if(Paste)then AdvDupe2.Notify(Player,"Finished Pasting!") end + + local tool = Player:GetTool() + if(tool && Player:GetActiveWeapon():GetClass()=="gmod_tool" && tool.Mode=="advdupe2")then + if(Player.AdvDupe2.Ghosting)then AdvDupe2.InitProgressBar(Player, "Ghosting: ") end + umsg.Start("AdvDupe2_Ghosting", Player) + umsg.End() + return + else + RemoveGhosts(Player) + end + +end + +//Update the ghost's postion and angles based on where the player is looking and the offsets +local function UpdateGhost(ply, toolWep) + + local trace = util.TraceLine(util.GetPlayerTrace(ply, ply:GetCursorAimVector())) + if (!trace.Hit) then return end + + local GhostEnt = toolWep:GetNetworkedEntity("GhostEntity", nil) + + if(!IsValid(GhostEnt))then + if SERVER then RemoveGhosts(ply) end + return + end + + GhostEnt:SetMoveType(MOVETYPE_VPHYSICS) + GhostEnt:SetNotSolid(true) + + local PhysObj = GhostEnt:GetPhysicsObject() + if ( IsValid(PhysObj) ) then + PhysObj:EnableMotion( false ) + if(tobool(ply:GetInfo("advdupe2_original_origin")))then + PhysObj:SetPos(toolWep:GetNetworkedVector("HeadPos", Vector(0,0,0)) + toolWep:GetNetworkedVector( "HeadOffset", Vector(0,0,0) )) + PhysObj:SetAngle(toolWep:GetNetworkedAngle("HeadAngle", Angle(0,0,0))) + else + local EntAngle = toolWep:GetNetworkedAngle("HeadAngle", Angle(0,0,0)) + if(tobool(ply:GetInfo("advdupe2_offset_world")))then EntAngle = Angle(0,0,0) end + trace.HitPos.Z = trace.HitPos.Z + toolWep:GetNetworkedFloat("HeadZPos", 0) + tonumber(ply:GetInfo("advdupe2_offset_z")) or 0 + local Pos, Angle = LocalToWorld(toolWep:GetNetworkedVector("HeadOffset", Vector(0,0,0)), EntAngle, trace.HitPos, Angle(math.Clamp(tonumber(ply:GetInfo("advdupe2_offset_pitch")) or 0,-180,180), math.Clamp(tonumber(ply:GetInfo("advdupe2_offset_yaw")) or 0,-180,180), math.Clamp(tonumber(ply:GetInfo("advdupe2_offset_roll")) or 0,-180,180))) + PhysObj:SetPos(Pos) + PhysObj:SetAngle(Angle) + end + PhysObj:Wake() + else + // Give the head ghost entity a physics object + // This way the movement will be predicted on the client + if(CLIENT)then + GhostEnt:PhysicsInit(SOLID_VPHYSICS) + end + end +end + +//Add a folder to the clients file browser +local function AddFolder(ply,name,id,parent,new) + umsg.Start("AdvDupe2_AddFolder",ply) + umsg.String(name) + umsg.Short(id) + umsg.Short(parent) + umsg.Bool(new) + umsg.End() +end + +//Add a file to the clients file browser +local function AddFile(ply,name,parent,new) + umsg.Start("AdvDupe2_AddFile",ply) + umsg.String(name) + umsg.Short(parent) + umsg.Bool(new) + umsg.End() +end + +//Take control of the mouse wheel bind so the player can modify the height of the dupe +local function MouseWheelScrolled(ply, bind, pressed) + + if(bind=="invprev")then + local Z = tonumber(ply:GetInfo("advdupe2_offset_z")) + 5 + RunConsoleCommand("advdupe2_offset_z",Z) + return true + elseif(bind=="invnext")then + local Z = tonumber(ply:GetInfo("advdupe2_offset_z")) - 5 + RunConsoleCommand("advdupe2_offset_z",Z) + return true + end + + GAMEMODE:PlayerBindPress(ply, bind, pressed) +end + + +//Creates a ghost from the given entity's table +local function MakeGhostsFromTable( toolWep, gParent, EntTable, Player) + + if(!EntTable)then return end + + if(!EntTable.Model || !util.IsValidModel(EntTable.Model)) then EntTable.Model="models/error.mdl" end + + local GhostEntity + 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 !IsValid(GhostEntity) then + RemoveGhosts(Player) + AdvDupe2.RemoveProgressBar(Player) + AdvDupe2.Notify(Player, "To many entities to spawn ghosts", NOTIFY_ERROR) + return + end + + local Phys = EntTable.PhysicsObjects[0] + EntTable.Pos = Phys.Pos + EntTable.Angle = Phys.Angle + 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 ) + + // If we're a ragdoll send our bone positions + if ( EntTable.Class == "prop_ragdoll" ) then + for k, v in pairs( EntTable.PhysicsObjects ) do + if(k==0)then + GhostEntity:SetNetworkedBonePosition( k, Vector(0,0,0), v.Angle ) + else + GhostEntity:SetNetworkedBonePosition( k, v.Pos, v.Angle ) + end + end + Phys.Angle = Angle(0,0,0) + end + + if ( gParent ) then + local Parent = toolWep:GetNetworkedEntity("GhostEntity", nil) + local temp = Parent:GetAngles() + GhostEntity:SetPos(Parent:GetPos() + Phys.Pos - toolWep:GetNetworkedAngle("HeadOffset", Angle(0,0,0))) + GhostEntity:SetAngles(Phys.Angle) + Parent:SetAngles(toolWep:GetNetworkedAngle("HeadAngle", Angle(0,0,0))) + GhostEntity:SetParent(Parent) + Parent:SetAngles(temp) + else + GhostEntity:SetAngles(Phys.Angle) + toolWep:SetNetworkedEntity("GhostEntity", GhostEntity) + toolWep:SetNetworkedVector("HeadPos", Player.AdvDupe2.HeadEnt.Pos) + toolWep:SetNetworkedVector("HeadOffset", EntTable.Pos) + toolWep:SetNetworkedFloat("HeadZPos", Player.AdvDupe2.HeadEnt.Z) + toolWep:SetNetworkedAngle("HeadAngle", Phys.Angle) + + umsg.Start("AdvDupe2_Ghosting", Player) + umsg.End() + end + + return GhostEntity +end + + +local XTotal = 0 +local YTotal = 0 +local LastXDegree = 0 +//Retrieves the players files for the file browser, creates and updates ghosts, checks binds to modify dupes position and angles +function TOOL:Think() + + local ply = self:GetOwner() + + if(SERVER && ply.AdvDupe2)then + + if(ply.AdvDupe2.GhostEntities && !ply.AdvDupe2.Pasting)then + UpdateGhost(ply, self.Weapon) + end + + if(ply.AdvDupe2.Ghosting && CurTime()>=ply.AdvDupe2.LastGhost && !ply.AdvDupe2.Pasting)then + + local i = ply.AdvDupe2.GhostToSpawn[ply.AdvDupe2.CurrentGhost] + if(i!=nil)then + + local total = math.Round((math.Clamp( tonumber(ply:GetInfo("advdupe2_limit_ghost")) or 100, 1, 100 )/100)*#ply.AdvDupe2.GhostToSpawn) + if(ply.AdvDupe2.CurrentGhost >= total)then + + AdvDupe2.RemoveProgressBar(ply) + ply.AdvDupe2.Ghosting = false + ply.AdvDupe2.CurrentGhost=1 + end + + ply.AdvDupe2.GhostEntities[i] = MakeGhostsFromTable( self.Weapon, ply.AdvDupe2.HeadEnt.Index, table.Copy(ply.AdvDupe2.Entities[i]), ply) + ply.AdvDupe2.CurrentGhost = ply.AdvDupe2.CurrentGhost+1 + local barperc = math.floor((ply.AdvDupe2.CurrentGhost/total)*100) + if(!ply.AdvDupe2.Downloading)then + AdvDupe2.UpdateProgressBar(ply, barperc) + end + ply.AdvDupe2.LastGhost=CurTime()+0.02 + else + AdvDupe2.RemoveProgressBar(ply) + ply.AdvDupe2.Ghosting = false + ply.AdvDupe2.CurrentGhost=1 + end + + end + + if(ply.AdvDupe2.SendFiles && CurTime()>= ply.AdvDupe2.LastFile)then + if(ply.AdvDupe2.Folders[1])then + local Folder = ply.AdvDupe2.Folders[1] + AddFolder(ply, Folder.Name, Folder.ID, Folder.Parent, false) + table.remove(ply.AdvDupe2.Folders, 1) + elseif(ply.AdvDupe2.Files[1])then + local File = ply.AdvDupe2.Files[1] + AddFile(ply, File.Name, File.Parent, false) + table.remove(ply.AdvDupe2.Files, 1) + else + ply.AdvDupe2.SendFiles = false + ply.AdvDupe2.LastFile = 0 + end + + ply.AdvDupe2.LastFile = CurTime()+0.02 + end + + else + if(!AdvDupe2.GhostEntity)then return end + + UpdateGhost(ply, self.Weapon) + + local cmd = ply:GetCurrentCommand() + + if(ply:KeyDown(IN_USE))then + if(!AdvDupe2.Rotation)then + hook.Add("PlayerBindPress", "AdvDupe2_BindPress", MouseWheelScrolled) + AdvDupe2.Rotation = true + end + else + if(AdvDupe2.Rotation)then + AdvDupe2.Rotation = false + hook.Remove("PlayerBindPress", "AdvDupe2_BindPress") + end + + XTotal = 0 + YTotal = 0 + LastXDegree = 0 + + return + end + + local X = -cmd:GetMouseX()/-20 + local Y = cmd:GetMouseY()/-20 + + local X2 = 0 + local Y2 = 0 + + if(X!=0)then + + X2 = tonumber(ply:GetInfo("advdupe2_offset_yaw")) + + if(ply:KeyDown(IN_SPEED))then + XTotal = XTotal + X + local temp = XTotal + X2 + + local degree = math.Round(temp/45)*45 + if(degree>=225)then + degree = -135 + elseif(degree<=-225)then + degree = 135 + end + if(degree!=LastXDegree)then + XTotal = 0 + LastXDegree = degree + end + + X2 = degree + + else + + X2 = X2 + X + + if(X2<-180)then + X2 = X2+360 + elseif(X2>180)then + X2 = X2-360 + end + + end + + RunConsoleCommand("advdupe2_offset_yaw", X2) + end + + /*if(Y!=0)then + Y2 = tonumber(ply:GetInfo("advdupe2_offset_pitch")) + local Y3 = tonumber(ply:GetInfo("advdupe2_offset_roll")) + if(ply:KeyDown(IN_SPEED))then + YTotal = YTotal + Y + local temp = YTotal + Y2 + + local degree = math.Round(temp/45)*45 + if(degree>=225)then + degree = -135 + elseif(degree<=-225)then + degree = 135 + end + if(degree!=LastYDegree)then + YTotal = 0 + LastYDegree = degree + end + + Y2 = degree + else + local dir = LocalPlayer():GetForward() + + Y2 = Y2 + Y*dir.X + Y3 = Y3 + Y*dir.Y + + if(Y2<-180)then + Y2 = Y2+360 + elseif(Y2>180)then + Y2 = Y2-360 + end + end + + + + RunConsoleCommand("advdupe2_offset_pitch",Y2) + RunConsoleCommand("advdupe2_offset_roll",Y3) + end*/ + + cmd:SetMouseX(0) + cmd:SetMouseY(0) + end + +end + +//Hinder the player from looking to modify offsets with the mouse +function TOOL:FreezeMovement() + return AdvDupe2.Rotation +end + +//Checks table, re-draws loading bar, and recreates ghosts when tool is pulled out +function TOOL:Deploy() + if ( CLIENT ) then return end + local ply = self:GetOwner() + if ( !ply.AdvDupe2 ) then ply.AdvDupe2={} end + + if(!ply.AdvDupe2.Entities)then return end + if(ply.AdvDupe2.Queued)then + AdvDupe2.InitProgressBar(ply, "Queued: ") + return + end + + if(ply.AdvDupe2.Pasting)then + AdvDupe2.InitProgressBar(ply, "Pasting: ") + return + elseif(ply.AdvDupe2.Uploading)then + AdvDupe2.InitProgressBar(ply, "Uploading: ") + return + elseif(ply.AdvDupe2.Downloading)then + AdvDupe2.InitProgressBar(ply, "Downloading: ") + return + end + + AdvDupe2.StartGhosting(ply) +end + +//Removes progress bar and removes ghosts when tool is put away +function TOOL:Holster() + if( CLIENT ) then return end + + local ply = self:GetOwner() + if(self:GetStage()==1)then + AdvDupe2.RemoveSelectBox(ply) + end + + AdvDupe2.RemoveProgressBar(ply) + + if ( !ply.AdvDupe2 || !ply.AdvDupe2.GhostEntities || ply.AdvDupe2.Pasting ) then return end + + RemoveGhosts(ply) + +end + +//function for creating a contraption spawner +function MakeContraptionSpawner( ply, Pos, Ang, HeadEnt, EntityTable, ConstraintTable, delay, undo_delay, model, key, undo_key, disgrav, disdrag, addvel) + + if !ply:CheckLimit("gmod_contr_spawners") then return nil end + + if(!SinglePlayer())then + if(table.Count(EntityTable)>tonumber(GetConVarString("AdvDupe2_MaxContraptionEntities")))then + AdvDupe2.Notify(ply,"Contraption Spawner exceeds the maximum amount of "..GetConVarString("AdvDupe2_MaxContraptionEntities").." entities for a spawner!",NOTIFY_ERROR) + return false + end + if(#ConstraintTable>tonumber(GetConVarString("AdvDupe2_MaxContraptionConstraints")))then + AdvDupe2.Notify(ply,"Contraption Spawner exceeds the maximum amount of "..GetConVarString("AdvDupe2_MaxContraptionConstraints").." constraints for a spawner!",NOTIFY_ERROR) + return false + end + end + + local spawner = ents.Create("gmod_contr_spawner") + if !IsValid(spawner) then return end + + spawner:SetPos(Pos) + spawner:SetAngles(Ang) + spawner:SetModel(model) + spawner:SetRenderMode(RENDERMODE_TRANSALPHA) + spawner:Spawn() + + duplicator.ApplyEntityModifiers(ply, spawner) + + if IsValid(spawner:GetPhysicsObject()) then + spawner:GetPhysicsObject():EnableMotion(false) + end + + if !SinglePlayer() and delay < 0.2 then + delay = 0.33 + end + + if !SinglePlayer() and (undo_delay <=0 or undo_delay>=60) then + undo_delay = 15 + end + + // Set options + spawner:SetPlayer(ply) + spawner:GetTable():SetOptions(ply, delay, undo_delay, key, undo_key, disgrav, disdrag, addvel) + + local tbl = { + ply = ply, + delay = delay, + undo_delay = undo_delay, + disgrav = disgrav, + disdrag = disdrag, + addvel = addvel; + } + table.Merge(spawner:GetTable(), tbl) + + spawner:SetDupeInfo(HeadEnt, EntityTable, ConstraintTable) + spawner:AddGhosts() + + ply:AddCount("gmod_contr_spawners", spawner) + ply:AddCleanup("gmod_contr_spawner", spawner) + return spawner +end +duplicator.RegisterEntityClass("gmod_contr_spawner", MakeContraptionSpawner, "Pos", "Ang", "HeadEnt", "EntityTable", "ConstraintTable", "delay", "undo_delay", "model", "key", "undo_key", "disgrav", "disdrag", "addvel") + + +--[[ + Name: Reload + Desc: Creates an Advance Contraption Spawner. + Params: trace + Returns: success +]] +function TOOL:Reload( trace ) + if CLIENT then return true end + local ply = self:GetOwner() + + //If a contraption spawner was clicked then update it with the current settings + if(trace.Entity:GetClass()=="gmod_contr_spawner")then + trace.Entity:GetTable():SetOptions(ply, tonumber(ply:GetInfo("advdupe2_contr_spawner_delay")) or .33,tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_delay")) or 0, tonumber(ply:GetInfo("advdupe2_contr_spawner_key")), tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_key")), tonumber(ply:GetInfo("advdupe2_contr_spawner_disgrav")) or 0, tonumber(ply:GetInfo("advdupe2_contr_spawner_disdrag")) or 0, tonumber(ply:GetInfo("advdupe2_contr_spawner_addvel")) or 1 ) + return true + end + + //Create a contration spawner + if ply.AdvDupe2 and ply.AdvDupe2.Entities then + + local headent = ply.AdvDupe2.Entities[ply.AdvDupe2.HeadEnt.Index] + local ghostent = ply.AdvDupe2.GhostEntities[ply.AdvDupe2.HeadEnt.Index] + if(headent.Class=="gmod_contr_spawner") then return false end + local spawner = MakeContraptionSpawner( ply, ghostent:GetPos(), ghostent:GetAngles(), ply.AdvDupe2.HeadEnt.Index, table.Copy(ply.AdvDupe2.Entities), table.Copy(ply.AdvDupe2.Constraints), tonumber(ply:GetInfo("advdupe2_contr_spawner_delay")) or .33,tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_delay")) or 0, headent.Model, tonumber(ply:GetInfo("advdupe2_contr_spawner_key")), tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_key")), tonumber(ply:GetInfo("advdupe2_contr_spawner_disgrav")) or 0, tonumber(ply:GetInfo("advdupe2_contr_spawner_disdrag")) or 0, tonumber(ply:GetInfo("advdupe2_contr_spawner_addvel")) or 1 ) + ply:AddCleanup( "AdvDupe2", spawner ) + undo.Create("gmod_contr_spawner") + undo.AddEntity( spawner ) + undo.SetPlayer( ply ) + undo.Finish() + + return true + end +end + + + +if SERVER then + + CreateConVar("sbox_maxgmod_contr_spawners",5) + + function AdvDupe2.StartGhosting(ply) + RemoveGhosts(ply) + + if(!ply.AdvDupe2.Entities)then return end + + local index = ply.AdvDupe2.HeadEnt.Index + + local tool = ply:GetTool() + if(!tool || ply:GetActiveWeapon():GetClass()!="gmod_tool" || tool.Mode!="advdupe2")then return end + + ply.AdvDupe2.GhostEntities = {} + ply.AdvDupe2.GhostEntities[index] = MakeGhostsFromTable( tool.Weapon, nil, table.Copy(ply.AdvDupe2.Entities[index]), ply) + + if !IsValid(ply.AdvDupe2.GhostEntities[index]) then + ply.AdvDupe2.GhostEntities = nil + AdvDupe2.Notify(ply, "Parent ghost is invalid, not creating ghosts", NOTIFY_ERROR) + return + end + + ply.AdvDupe2.GhostToSpawn = {} + local total = 1 + for k,v in pairs(ply.AdvDupe2.Entities)do + if(k!=index)then + ply.AdvDupe2.GhostToSpawn[total] = k + total = total + 1 + end + end + ply.AdvDupe2.LastGhost = CurTime()+0.02 + AdvDupe2.InitProgressBar(ply, "Ghosting: ") + ply.AdvDupe2.Ghosting = true + end + + local function RenameNode(ply, newname) + umsg.Start("AdvDupe2_RenameNode", ply) + umsg.String(newname) + umsg.End() + end + + --[[==============]]-- + --[[FILE FUNCTIONS]]-- + --[[==============]]-- + + //Download a file from the server + local function DownloadFile(ply, cmd, args) + + if(ply.AdvDupe2.Pasting || ply.AdvDupe2.Downloading)then + AdvDupe2.Notify(ply,"Advanced Duplicator 2 is busy.",NOTIFY_ERROR) + return false + end + if(!tobool(GetConVarString("AdvDupe2_AllowDownloading")))then + AdvDupe2.Notify(ply,"Downloading is not allowed.",NOTIFY_ERROR) + return false + end + + local path = args[1] + local area = tonumber(args[2]) + + local newfile + if(area==0)then //AD2 folder in client's folder + newfile = ply:GetAdvDupe2Folder().."/"..path..".txt" + elseif(area==1)then //Public folder + if(!tobool(GetConVarString("AdvDupe2_AllowPublicFolder")))then + AdvDupe2.Notify(ply,"Public Folder is disabled.",NOTIFY_ERROR) + return + end + newfile = AdvDupe2.DataFolder.."/=Public=/"..path..".txt" + else //AD1 folder in client's folder + newfile = "adv_duplicator/"..ply:GetAdvDupe2Folder().."/"..path..".txt" + end + + if(!file.Exists(newfile))then return end + + AdvDupe2.EstablishNetwork(ply, file.Read(newfile)) + end + concommand.Add("AdvDupe2_DownloadFile", DownloadFile) + + //Open a file on the server + local function OpenFile(ply, cmd, args) + if(args[1]=="" || args[1]==nil || args[2]=="" || args[2]==nil)then return end + + if(ply.AdvDupe2.Pasting || ply.AdvDupe2.Downloading)then + AdvDupe2.Notify(ply,"Advanced Duplicator 2 is busy.",NOTIFY_ERROR) + return false + end + + if(!SinglePlayer() && CurTime()-(ply.AdvDupe2.FileMod or 0) < 0)then + AdvDupe2.Notify(ply,"Cannot open at the moment.", NOTIFY_ERROR) + return + end + ply.AdvDupe2.FileMod = CurTime()+tonumber(GetConVarString("AdvDupe2_FileModificationDelay")) + + local path, area = args[1], tonumber(args[2]) + local name = string.Explode("/", args[1]) + name = name[#name] + + if(area==0)then + data = ply:ReadAdvDupe2File(path) + elseif(area==1)then + if(SinglePlayer())then path = "=Public=/"..path end + data = AdvDupe2.ReadFile(nil, path) + else + data = AdvDupe2.ReadFile(ply, path, "adv_duplicator") + end + if(data==false)then + AdvDupe2.Notify(ply,"File size is greater than "..GetConVarString("AdvDupe2_MaxFileSize"), NOTIFY_ERROR) + return + end + + AdvDupe2.Decode(data, function(success,dupe,info,moreinfo) + + if(!IsValid(ply))then return end + + if(!SinglePlayer())then + if(#dupe["Constraints"]>tonumber(GetConVarString("AdvDupe2_MaxConstraints")))then + AdvDupe2.Notify(ply,"Amount of constraints is greater than "..GetConVarString("AdvDupe2_MaxConstraints"),NOTIFY_ERROR) + return false + end + + if(tonumber(GetConVarString("AdvDupe2_MaxEntities"))>0)then + if(table.Count(dupe["Entities"])>tonumber(GetConVarString("AdvDupe2_MaxEntities")))then + AdvDupe2.Notify(ply,"Amount of entities is greater than "..GetConVarString("AdvDupe2_MaxEntities"),NOTIFY_ERROR) + return false + end + else + if(table.Count(dupe["Entities"])>tonumber(GetConVarString("sbox_maxprops")))then + AdvDupe2.Notify(ply,"Amount of entities is greater than "..GetConVarString("sbox_maxprops"),NOTIFY_ERROR) + return false + end + end + end + + if not success then + AdvDupe2.Notify(ply,"Could not open "..dupe,NOTIFY_ERROR) + return + end + + ply.AdvDupe2.Entities = {} + ply.AdvDupe2.Constraints = {} + ply.AdvDupe2.HeadEnt={} + local time = "" + local desc = "" + local date = "" + local creator = "" + + if(info.ad1)then + time = moreinfo["Time"] + desc = info["Description"] + date = info["Date"] + creator = info["Creator"] + + ply.AdvDupe2.HeadEnt.Index = tonumber(moreinfo.Head) + local startpos = string.Explode(",", moreinfo.StartPos) + ply.AdvDupe2.HeadEnt.Pos = Vector(tonumber(startpos[1]), tonumber(startpos[2]), tonumber(startpos[3])) + ply.AdvDupe2.HeadEnt.Z = tonumber(string.Explode(",", moreinfo.HoldPos)[3])*-1 + local z = ply.AdvDupe2.HeadEnt.Z + local Pos = nil + local Ang = nil + for k,v in pairs(dupe["Entities"])do + Pos = nil + Ang = nil + if(v.SavedParentIdx)then + if(!v.BuildDupeInfo)then v.BuildDupeInfo = {} end + v.BuildDupeInfo.DupeParentID = v.SavedParentIdx + Pos = v.LocalPos*1 + Ang = v.LocalAngle*1 + end + for i,p in pairs(v.PhysicsObjects)do + p.Pos = Pos or (p.LocalPos*1) + p.Pos.Z = p.Pos.Z - z + p.Angle = Ang or (p.LocalAngle*1) + p.LocalPos = nil + p.LocalAngle = nil + end + v.LocalPos = nil + v.LocalAngle = nil + end + + ply.AdvDupe2.Entities = dupe["Entities"] + ply.AdvDupe2.Constraints = dupe["Constraints"] + + else + time = info["time"] + desc = dupe["Description"] + date = info["date"] + creator = info["name"] + + ply.AdvDupe2.Entities = dupe["Entities"] + ply.AdvDupe2.Constraints = dupe["Constraints"] + ply.AdvDupe2.HeadEnt = dupe["HeadEnt"] + end + + //ply.AdvDupe2.Name = name[#name] + + umsg.Start("AdvDupe2_SetDupeInfo", ply) + umsg.String(name) + umsg.String(creator) + umsg.String(date) + umsg.String(time) + umsg.String(string.NiceSize(tonumber(info.size) or 0)) + umsg.String(desc) + umsg.String(table.Count(ply.AdvDupe2.Entities)) + umsg.String(#ply.AdvDupe2.Constraints) + umsg.End() + + AdvDupe2.ResetOffsets(ply) + AdvDupe2.StartGhosting(ply) + end) + end + concommand.Add("AdvDupe2_OpenFile", OpenFile) + + //Save a file to the server + local function SaveFile(ply, cmd, args) + if(!ply.AdvDupe2 || !ply.AdvDupe2.Entities || ply.AdvDupe2.Entities == {})then return end + if(args[1]=="" || args[1]==nil || args[3]=="" || args[3]==nil)then return end + + if(!SinglePlayer() && CurTime()-(ply.AdvDupe2.FileMod or 0) < 0)then + AdvDupe2.Notify(ply,"Cannot save at the moment.", NOTIFY_ERROR) + return + end + ply.AdvDupe2.FileMod = CurTime()+tonumber(GetConVarString("AdvDupe2_FileModificationDelay")) + + local path, area = args[1], tonumber(args[3]) + local public = false + + if(args[2]!="")then + path = args[2].."/"..path + end + + if(area==1)then + if(!tobool(GetConVarString("AdvDupe2_AllowPublicFolder")))then + AdvDupe2.Notify(ply,"Public Folder is disabled.",NOTIFY_ERROR) + return + end + if(SinglePlayer())then path = "=Public=/"..path end + public = true + elseif(area==2)then + AdvDupe2.Notify(ply,"Cannot save into this directory.",NOTIFY_ERROR) + return + end + + umsg.Start("AdvDupe2_SetDupeInfo", ply) + umsg.String(args[1]) + umsg.String(ply:Nick()) + umsg.String(os.date("%d %B %Y")) + umsg.String(os.date("%I:%M %p")) + umsg.String("") + umsg.String(args[4]) + umsg.String(table.Count(ply.AdvDupe2.Entities)) + umsg.String(#ply.AdvDupe2.Constraints) + umsg.End() + + local Tab = {Entities = ply.AdvDupe2.Entities, Constraints = ply.AdvDupe2.Constraints, HeadEnt = ply.AdvDupe2.HeadEnt, Description=args[4]} + + AdvDupe2.Encode( + Tab, + AdvDupe2.GenerateDupeStamp(ply), + function(data) + local dir, name = "", "" + if(!public)then + dir, name = ply:WriteAdvDupe2File(path, data) + else + dir, name = AdvDupe2.WriteFile(nil, path, data) + end + AddFile(ply,name,args[5],true) + end) + + if(!SinglePlayer() && tobool(GetConVarString("AdvDupe2_RemoveFilesOnDisconnect")))then + AdvDupe2.Notify(ply, "Your saved files will be deleted when you disconnect!", NOTIFY_CLEANUP, 10) + end + end + concommand.Add("AdvDupe2_SaveFile", SaveFile) + + //Add a new folder to the server + local function NewFolder(ply, cmd, args) + + if(!SinglePlayer() && CurTime()-(ply.AdvDupe2.FileMod or 0) < 0)then + AdvDupe2.Notify(ply,"Cannot create a new folder at the moment.", NOTIFY_ERROR) + return + end + ply.AdvDupe2.FileMod = CurTime()+tonumber(GetConVarString("AdvDupe2_FileModificationDelay")) + + local path, area = args[1], tonumber(args[3]) + local public = false + if path:find("%W") then AdvDupe2.Notify(ply,"Invalid folder name.",NOTIFY_ERROR) return false end + + if(args[2]!="")then + path = args[2].."/"..path + end + + if(area==0)then + path = ply:GetAdvDupe2Folder().."/"..path + elseif(area==1)then + if(!tobool(GetConVarString("AdvDupe2_AllowPublicFolder")))then + AdvDupe2.Notify(ply,"Public Folder is disabled.",NOTIFY_ERROR) + return + end + path = AdvDupe2.DataFolder.."/=Public=/"..path + else + path = "adv_duplicator/"..ply:SteamIDSafe().."/"..path + end + + + if(file.IsDir(path))then + AdvDupe2.Notify(ply,"Folder name already exists.",NOTIFY_ERROR) + return + end + file.CreateDir(path) + ply.AdvDupe2.FolderID = ply.AdvDupe2.FolderID+1 + AddFolder(ply, args[1], ply.AdvDupe2.FolderID, args[4], true) + end + concommand.Add("AdvDupe2_NewFolder", NewFolder) + + local function TFindDelete(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.."/*", + function(Search2, Folders2, Files2) + TFindDelete(Search2, Folders2, Files2) + end) + end + end + + //Delete a file on the server + local function DeleteFile(ply, cmd, args) + + if(!SinglePlayer() && CurTime()-(ply.AdvDupe2.FileMod or 0) < 0)then + AdvDupe2.Notify(ply,"Cannot delete at the moment.", NOTIFY_ERROR) + return + end + ply.AdvDupe2.FileMod = CurTime()+tonumber(GetConVarString("AdvDupe2_FileModificationDelay")) + + local path, area = args[1], tonumber(args[2]) + local folder = tobool(args[3]) + + + if(area==0)then + if(folder)then + path = ply:GetAdvDupe2Folder().."/"..path + else + path = ply:GetAdvDupe2Folder().."/"..path..".txt" + end + elseif(area==1)then + if(!ply:IsAdmin())then + AdvDupe2.Notify(ply,"You are not an admin.",NOTIFY_ERROR) + return + end + if(folder)then + path = AdvDupe2.DataFolder.."/=Public=/"..path + else + path = AdvDupe2.DataFolder.."/=Public=/"..path..".txt" + end + else + if(folder)then + path = "adv_duplicator/"..ply:SteamIDSafe().."/"..path + else + path = "adv_duplicator/"..ply:SteamIDSafe().."/"..path..".txt" + end + end + if(!folder && file.Exists(path))then + file.Delete(path) + end + + if(folder && file.IsDir(path))then + file.TFind("Data/"..path.."/*", + function(Search, Folders, Files) + TFindDelete(Search, Folders, Files) + end) + end + umsg.Start("AdvDupe2_DeleteNode") + umsg.End() + + end + concommand.Add("AdvDupe2_DeleteFile", DeleteFile) + + local function RenameFile(ply, cmd, args) + + if(!SinglePlayer() && CurTime()-(ply.AdvDupe2.FileMod or 0) < 0)then + AdvDupe2.Notify(ply,"Cannot rename at the moment.", NOTIFY_ERROR) + return + end + ply.AdvDupe2.FileMod = CurTime()+tonumber(GetConVarString("AdvDupe2_FileModificationDelay")) + + local Alt = tonumber(args[1]) or nil + if(Alt==nil)then return end + local NewName = args[2] + local Path = args[3] + + if(Alt==0)then + Path = ply:GetAdvDupe2Folder().."/"..Path + elseif(Alt==1)then + AdvDupe2.Notify(ply, "Public folder modification not allowed", NOTIFY_ERROR) + //Path = AdvDupe2.DataFolder.."/"..Path + else + Path = "adv_duplicator/"..ply:SteamIDSafe().."/"..Path + end + + local OldName = string.Explode("/", Path) + OldName = OldName[#OldName] + + local NewPath = string.sub(Path, 1, -#OldName-1)..NewName + + if file.Exists(NewPath..".txt") then + local found = false + for i = 1, AdvDupe2.FileRenameTryLimit do + if not file.Exists(NewPath.."_"..i..".txt") then + NewPath = NewPath.."_"..i + found = true + break + end + end + if(!found)then AdvDupe2.Notify(ply, "File could not be renamed.", NOTIFY_ERROR) return end + end + local File = file.Read(Path..".txt") + file.Write(NewPath..".txt", File) + + if(file.Exists(NewPath..".txt"))then + file.Delete(Path..".txt") + NewName = string.Explode("/", NewPath) + RenameNode(ply, NewName[#NewName]) + else + AdvDupe2.Notify(ply, "File rename failed.", NOTIFY_ERROR) + end + + end + concommand.Add("AdvDupe2_RenameFile", RenameFile) + + local function MoveFile(ply, cmd, args) + + if(!SinglePlayer() && CurTime()-(ply.AdvDupe2.FileMod or 0) < 0)then + AdvDupe2.Notify(ply,"Cannot move file at the moment.", NOTIFY_ERROR) + return + end + ply.AdvDupe2.FileMod = CurTime()+tonumber(GetConVarString("AdvDupe2_FileModificationDelay")) + + local area1, area2 = tonumber(args[1]) or nil, tonumber(args[2]) or nil + local path1, path2 = args[3], args[4] + + if(area1==nil || area2==nil)then return end + if((area1==2 && area2!=2) || (area2==2 && area1!=2))then return end + + local name = string.Explode("/", path1) + name = name[#name] + + path1 = ply:SteamIDSafe().."/"..path1 + path2 = ply:SteamIDSafe().."/"..path2.."/"..name + + if(area1==0)then + path1 = AdvDupe2.DataFolder.."/"..path1 + elseif(area1==1)then + AdvDupe2.Notify(ply, "Public folder modification not allowed", NOTIFY_ERROR) + //path1 = AdvDupe2.DataFolder.."/".."=Public=/"..path1 + return + else + path1 = "adv_duplicator/"..path1 + end + + if(area2==0)then + path2 = AdvDupe2.DataFolder.."/"..path2 + elseif(area2==1)then + AdvDupe2.Notify(ply, "Public folder modification not allowed", NOTIFY_ERROR) + //path2 = AdvDupe2.DataFolder.."/".."=Public=/"..path2 + return + else + path2 = "adv_duplicator/"..path2 + end + + local File = file.Read(path1..".txt") + if(!File)then return end + + if file.Exists(path2..".txt") then + local found = false + for i = 1, AdvDupe2.FileRenameTryLimit do + if not file.Exists(path2.."_"..i..".txt") then + path2 = path2.."_"..i + found = true + break + end + end + if(!found)then AdvDupe2.Notify(ply, "File could not be renamed.", NOTIFY_ERROR) return end + end + + file.Write(path2..".txt", File) + if(file.Exists(path2..".txt"))then + file.Delete(path1..".txt") + local name = string.Explode("/", path2) + + umsg.Start("AdvDupe2_MoveNode", ply) + umsg.String(name[#name]) + umsg.End() + else + AdvDupe2.Notify(ply, "File could not be moved.", NOTIFY_ERROR) + end + + end + concommand.Add("AdvDupe2_MoveFile", MoveFile) + + //TFind files and folders on the server + local function TFind(ply, Search, Folders, Files, parent) + + for k,v in pairs(Files)do + local File = {} + File.Name = string.Left(v, #v-4) + File.IsFolder = 0 + File.Parent = parent + table.insert(ply.AdvDupe2.Files, File) + end + for k,v in pairs(Folders)do + ply.AdvDupe2.FolderID=ply.AdvDupe2.FolderID+1 + local Folder = {} + Folder.Name = v + Folder.Parent = parent + Folder.ID = ply.AdvDupe2.FolderID + table.insert(ply.AdvDupe2.Folders, Folder) + file.TFind(string.Left(Search,#Search-1)..v.."/*", function(Search2, Folders2, Files2) TFind(ply, Search2, Folders2, Files2, Folder.ID) end) + end + ply.AdvDupe2.SendFiles = true + end + + concommand.Add("AdvDupe2_SendFiles", function(ply, cmd, args) + + if(ply.AdvDupe2 && !SinglePlayer() && CurTime()-(ply.AdvDupe2.NextSend or 0) < 0)then + AdvDupe2.Notify(ply,"Cannot update at the moment.",NOTIFY_ERROR) + return + elseif(tonumber(args[1])==0)then + umsg.Start("AdvDupe2_ClearBrowser", ply) + umsg.End() + return + end + + if(!ply.AdvDupe2)then ply.AdvDupe2 = {} end + if(ply.AdvDupe2.SendFiles)then return end + + file.TFind("Data/"..ply:GetAdvDupe2Folder().."/*", + function(Search, Folders, Files) + if(!ply.AdvDupe2)then ply.AdvDupe2 = {} end + ply.AdvDupe2.Folders = {} + ply.AdvDupe2.Files = {} + ply.AdvDupe2.FolderID = 0 + ply.AdvDupe2.NextSend = CurTime() + tonumber(GetConVarString("AdvDupe2_UpdateFilesDelay")) + + local AD1 = "adv_duplicator" + if(!SinglePlayer())then + AD1 = AD1.."/"..ply:SteamIDSafe() + end + ply.AdvDupe2.FolderID=ply.AdvDupe2.FolderID+1 + local AD1Folder = {} + AD1Folder.Name = "=Adv Duplicator=" + AD1Folder.Parent = 0 + AD1Folder.ID = ply.AdvDupe2.FolderID + table.insert(ply.AdvDupe2.Folders, AD1Folder) + + if(!SinglePlayer() && tobool(GetConVarString("AdvDupe2_AllowPublicFolder")))then + ply.AdvDupe2.FolderID=ply.AdvDupe2.FolderID+1 + local Folder = {} + Folder.Name = "=Public=" + Folder.Parent = 0 + Folder.ID = ply.AdvDupe2.FolderID + Folder.Public = true + table.insert(ply.AdvDupe2.Folders, Folder) + file.TFind("Data/advdupe2/=Public=/*", function(Search, Folders, Files) TFind(ply, Search, Folders, Files, Folder.ID) end) + end + + file.TFind("Data/"..AD1.."/*", + function(Search2, Folders2, Files2) + TFind(ply, Search2, Folders2, Files2, AD1Folder.ID) + end) + + TFind(ply, Search, Folders, Files, 0) + ply.AdvDupe2.LastFile = 0 + ply.AdvDupe2.SendFiles = true + end) + + + end) + + --[[=====================]]-- + --[[END OF FILE FUNCTIONS]]-- + --[[=====================]]-- + + function AdvDupe2.InitProgressBar(ply,label) + umsg.Start("AdvDupe2_InitProgressBar",ply) + umsg.String(label) + umsg.End() + end + + concommand.Add("AdvDupe2_RemakeGhosts", function(ply, cmd, args) + RemoveGhosts(ply) + AdvDupe2.StartGhosting(ply) + AdvDupe2.ResetOffsets(ply) + end) +end + + +concommand.Add( "SaveDupe", SaveDupe ) +concommand.Add( "ReadDupe", ReadDupe ) +if CLIENT then + + language.Add( "Tool_advdupe2_name", "Advanced Duplicator 2" ) + language.Add( "Tool_advdupe2_desc", "Duplicate things." ) + language.Add( "Tool_advdupe2_0", "Primary: Paste, Secondary: Copy." ) + language.Add( "Tool_advdupe2_1", "Primary: Paste, Secondary: Copy an area." ) + language.Add( "Undone_AdvDupe2", "Undone AdvDupe2 paste" ) + language.Add( "Cleanup_AdvDupe2", "Adv. Duplications" ) + language.Add( "Cleaned_AdvDupe2", "Cleaned up all Adv. Duplications" ) + language.Add( "SBoxLimit_AdvDupe2", "You've reached the Adv. Duplicator limit!" ) + + CreateClientConVar("advdupe2_offset_world", 0, false, true) + CreateClientConVar("advdupe2_offset_z", 0, false, true) + CreateClientConVar("advdupe2_offset_pitch", 0, false, true) + CreateClientConVar("advdupe2_offset_yaw", 0, false, true) + CreateClientConVar("advdupe2_offset_roll", 0, false, true) + CreateClientConVar("advdupe2_original_origin", 0, false, true) + CreateClientConVar("advdupe2_paste_constraints", 1, false, true) + CreateClientConVar("advdupe2_paste_parents", 1, false, true) + CreateClientConVar("advdupe2_paste_unfreeze", 0, false, true) + CreateClientConVar("advdupe2_preserve_freeze", 0, false, true) + CreateClientConVar("advdupe2_copy_outside", 0, false, true) + CreateClientConVar("advdupe2_limit_ghost", 100, false, true) + CreateClientConVar("advdupe2_area_copy_size", 300, false, true) + + //Contraption Spawner + CreateClientConVar("advdupe2_contr_spawner_key", -1, false, true) + CreateClientConVar("advdupe2_contr_spawner_undo_key", -1, false, true) + CreateClientConVar("advdupe2_contr_spawner_delay", 0, false, true) + CreateClientConVar("advdupe2_contr_spawner_undo_delay", 0, false, true) + CreateClientConVar("advdupe2_contr_spawner_disgrav", 0, false, true) + CreateClientConVar("advdupe2_contr_spawner_disdrag", 0, false, true) + CreateClientConVar("advdupe2_contr_spawner_addvel", 1, false, true) + + //Experimental + CreateClientConVar("advdupe2_paste_disparents", 0, false, true) + + local function BuildCPanel() + local CPanel = GetControlPanel("advdupe2") + + if not CPanel then return end + CPanel:ClearControls() + local Fill = vgui.Create( "DPanel" ) + CPanel:AddPanel(Fill) + Fill:SetTall(CPanel:GetParent():GetParent():GetTall()-45) + local List = vgui.Create( "DPanelList", CPanel ) + List:EnableVerticalScrollbar( true ) + List:Dock( FILL ) + List:SetSpacing( 2 ) + List:SetPadding( 2 ) + + local FileBrowser = vgui.Create("advdupe2_browser") + AdvDupe2.FileBrowser = FileBrowser + List:AddItem(FileBrowser) + FileBrowser:SetSize(235,450) + FileBrowser.Filler = Fill + FileBrowser.Initialized = true + RunConsoleCommand("AdvDupe2_SendFiles") + + local Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Paste at original position" ) + Check:SetConVar( "advdupe2_original_origin" ) + Check:SetValue( 0 ) + Check:SetToolTip("Paste at the coords originally copied") + List:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Paste with constraints" ) + Check:SetConVar( "advdupe2_paste_constraints" ) + Check:SetValue( 1 ) + Check:SetToolTip("Paste with or without constraints") + List:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Paste with parenting" ) + Check:SetConVar( "advdupe2_paste_parents" ) + Check:SetValue( 1 ) + Check:SetToolTip("Paste with or without parenting") + List:AddItem(Check) + + local Check_1 = vgui.Create("DCheckBoxLabel") + local Check_2 = vgui.Create("DCheckBoxLabel") + + Check_1:SetText( "Unfreeze all after paste" ) + Check_1:SetConVar( "advdupe2_paste_unfreeze" ) + Check_1:SetValue( 0 ) + Check_1.OnChange = function() + if(Check_1:GetChecked() and Check_2:GetChecked())then + Check_2:SetValue(0) + end + end + Check_1:SetToolTip("Unfreeze all props after pasting") + List:AddItem(Check_1) + + Check_2:SetText( "Preserve frozen state after paste" ) + Check_2:SetConVar( "advdupe2_preserve_freeze" ) + Check_2:SetValue( 0 ) + Check_2.OnChange = function() + if(Check_2:GetChecked() and Check_1:GetChecked())then + Check_1:SetValue(0) + end + end + Check_2:SetToolTip("Makes props have the same frozen state as when they were copied") + List:AddItem(Check_2) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Area copy constrained props outside of box" ) + Check:SetConVar( "advdupe2_copy_outside" ) + Check:SetValue( 0 ) + Check:SetToolTip("Copy entities outside of the area copy that are constrained to entities insde") + List:AddItem(Check) + + local NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Percent of ghost to create" ) + NumSlider:SetMin( 0 ) + NumSlider:SetMax( 100 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar( "advdupe2_limit_ghost" ) + NumSlider:SetToolTip("Change the percent of ghosts to spawn") + //If these funcs are not here, problems occur for each + local func = NumSlider.Slider.OnMouseReleased + NumSlider.Slider.OnMouseReleased = function(mcode) func(mcode) RunConsoleCommand("AdvDupe2_RemakeGhosts") end + local func2 = NumSlider.Wang.OnMouseReleased //Hacky way to make it work + NumSlider.Wang.OnMouseReleased = function(mousecode) func2(mousecode) RunConsoleCommand("AdvDupe2_RemakeGhosts") end + local func3 = NumSlider.Wang.TextEntry.OnLoseFocus + NumSlider.Wang.TextEntry.OnLoseFocus = function(txtBox) func3(txtBox) RunConsoleCommand("AdvDupe2_RemakeGhosts") end + List:AddItem(NumSlider) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Area Copy Size" ) + NumSlider:SetMin( 0 ) + NumSlider:SetMax( 2500 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar( "advdupe2_area_copy_size" ) + NumSlider:SetToolTip("Change the size of the area copy") + List:AddItem(NumSlider) + + local Category1 = vgui.Create("DCollapsibleCategory") + List:AddItem(Category1) + Category1:SetLabel("Offsets") + Category1:SetExpanded(0) + + --[[Offsets]]-- + local CategoryContent1 = vgui.Create( "DPanelList" ) + CategoryContent1:SetAutoSize( true ) + CategoryContent1:SetDrawBackground( false ) + CategoryContent1:SetSpacing( 1 ) + CategoryContent1:SetPadding( 2 ) + + Category1:SetContents( CategoryContent1 ) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Height Offset" ) + NumSlider:SetMin( 0 ) + NumSlider:SetMax( 2500 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar("advdupe2_offset_z") + NumSlider:SetToolTip("Change the Z offset of the dupe") + CategoryContent1:AddItem(NumSlider) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Use World Angles" ) + Check:SetConVar( "advdupe2_offset_world" ) + Check:SetValue( 0 ) + Check:SetToolTip("Use world angles for the offset instead of the main entity") + CategoryContent1:AddItem(Check) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Pitch Offset" ) + NumSlider:SetMin( -180 ) + NumSlider:SetMax( 180 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar("advdupe2_offset_pitch") + CategoryContent1:AddItem(NumSlider) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Yaw Offset" ) + NumSlider:SetMin( -180 ) + NumSlider:SetMax( 180 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar("advdupe2_offset_yaw") + CategoryContent1:AddItem(NumSlider) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Roll Offset" ) + NumSlider:SetMin( -180 ) + NumSlider:SetMax( 180 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar("advdupe2_offset_roll") + CategoryContent1:AddItem(NumSlider) + + + --[[Dupe Information]]-- + local Category2 = vgui.Create("DCollapsibleCategory") + List:AddItem(Category2) + Category2:SetLabel("Dupe Information") + Category2:SetExpanded(0) + + local CategoryContent2 = vgui.Create( "DPanelList" ) + CategoryContent2:SetAutoSize( true ) + CategoryContent2:SetDrawBackground( false ) + CategoryContent2:SetSpacing( 3 ) + CategoryContent2:SetPadding( 2 ) + Category2:SetContents( CategoryContent2 ) + + AdvDupe2.Info = {} + + local lbl = vgui.Create( "DLabel" ) + lbl:SetText("File: ") + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.File = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText("Creator:") + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Creator = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText("Date:") + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Date = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText("Time:") + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Time = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText("Size:") + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Size = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText("Desc:") + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Desc = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText("Entities:") + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Entities = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText("Constraints:") + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Constraints = lbl + + --[[Contraption Spawner]]-- + local Category3 = vgui.Create("DCollapsibleCategory") + List:AddItem(Category3) + Category3:SetLabel("Contraption Spawner") + Category3:SetExpanded(0) + + local CategoryContent3 = vgui.Create( "DPanelList" ) + CategoryContent3:SetAutoSize( true ) + CategoryContent3:SetDrawBackground( false ) + CategoryContent3:SetSpacing( 3 ) + CategoryContent3:SetPadding( 2 ) + Category3:SetContents( CategoryContent3 ) + + local ctrl = vgui.Create( "CtrlNumPad" ) + ctrl:SetConVar1( "advdupe2_contr_spawner_key" ) + ctrl:SetConVar2( "advdupe2_contr_spawner_undo_key" ) + ctrl:SetLabel1( "Spawn Key") + ctrl:SetLabel2( "Undo Key" ) + CategoryContent3:AddItem(ctrl) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Spawn Delay" ) + NumSlider:SetMin( 0 ) + NumSlider:SetMax( 256 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar("advdupe2_contr_spawner_delay") + CategoryContent3:AddItem(NumSlider) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Undo Delay" ) + NumSlider:SetMin( 0 ) + NumSlider:SetMax( 256 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar("advdupe2_contr_spawner_undo_delay") + CategoryContent3:AddItem(NumSlider) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable gravity for all spawned props" ) + Check:SetConVar( "advdupe2_contr_spawner_disgrav" ) + Check:SetValue( 0 ) + CategoryContent3:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable drag for all spawned props" ) + Check:SetConVar( "advdupe2_contr_spawner_disdrag" ) + Check:SetValue( 0 ) + CategoryContent3:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Add spawner's velocity to contraption" ) + Check:SetConVar( "advdupe2_contr_spawner_addvel" ) + Check:SetValue( 1 ) + CategoryContent3:AddItem(Check) + + --[[Experimental Section]]-- + local Category4 = vgui.Create("DCollapsibleCategory") + List:AddItem(Category4) + Category4:SetLabel("Experimental Section") + Category4:SetExpanded(0) + + local CategoryContent4 = vgui.Create( "DPanelList" ) + CategoryContent4:SetAutoSize( true ) + CategoryContent4:SetDrawBackground( false ) + CategoryContent4:SetSpacing( 3 ) + CategoryContent4:SetPadding( 2 ) + Category4:SetContents( CategoryContent4 ) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable parented props physics interaction" ) + Check:SetConVar( "advdupe2_paste_disparents" ) + Check:SetValue( 0 ) + CategoryContent4:AddItem(Check) + end + + function TOOL.BuildCPanel(panel) + panel:AddControl("Header", { + Text = "Advanced Duplicator 2", + Description = "Duplicate stuff." + }) + timer.Simple(0, BuildCPanel) + end + + local state = 0 + local ToColor = {r=25, g=100, b=40, a=255} + local CurColor = {r=25, g=100, b=40, a=255} + local rate + surface.CreateFont ("Arial", 40, 1000, true, false, "AD2Font") ---Remember to use gm_clearfonts + surface.CreateFont ("Arial", 24, 1000, true, false, "AD2TitleFont") + //local spacing = {" "," "," "," "," "," "} + function TOOL:RenderToolScreen() + if(!AdvDupe2)then return true end + + local text = "Ready" + state=0 + if(AdvDupe2.ProgressBar.Text)then + state=1 + text = AdvDupe2.ProgressBar.Text + end + + cam.Start2D() + + surface.SetDrawColor(32, 32, 32, 255) + surface.DrawRect(0, 0, 256, 256) + + if(state==0)then + ToColor = {r=25, g=100, b=40, a=255} + else + ToColor = {r=130, g=25, b=40, a=255} + end + + rate = FrameTime()*160 + CurColor.r = math.Approach( CurColor.r, ToColor.r, rate ) + CurColor.g = math.Approach( CurColor.g, ToColor.g, rate ) + + surface.SetDrawColor(CurColor) + surface.DrawRect(13, 13, 230, 230) + + surface.SetTextColor( 255, 255, 255, 255 ) + + draw.SimpleText("Advanced Duplicator 2", "AD2TitleFont", 128, 50, Color(255,255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText(text, "AD2Font", 128, 128, Color(255,255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + if(state!=0)then + draw.RoundedBox( 6, 32, 178, 192, 28, Color( 255, 255, 255, 150 ) ) + draw.RoundedBox( 6, 36, 182, 188*(AdvDupe2.ProgressBar.Percent/100), 24, Color( 0, 255, 0, 255 ) ) + elseif(LocalPlayer():KeyDown(IN_USE))then + //draw.SimpleText("Height: Pitch: Roll:", "AD2TitleFont", 128, 206, Color(255,255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM) + //local str_space1 = spacing[7-string.len(height)] or "" + //local str_space2 = spacing[7-string.len(pitch)] or "" + //draw.SimpleText(height..str_space1..pitch..str_space2..LocalPlayer():GetInfo("advdupe2_offset_roll"), "AD2TitleFont", 25, 226, Color(255,255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + draw.SimpleText("Height: "..LocalPlayer():GetInfo("advdupe2_offset_z"), "AD2TitleFont", 25, 180, Color(255,255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + draw.SimpleText("Pitch: "..LocalPlayer():GetInfo("advdupe2_offset_pitch"), "AD2TitleFont", 25, 210, Color(255,255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + draw.SimpleText("Yaw: "..LocalPlayer():GetInfo("advdupe2_offset_yaw"), "AD2TitleFont", 25, 240, Color(255,255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + end + + cam.End2D() + end + + + local function FindInBox(min, max, ply) + + local Entities = ents.GetAll() + local EntTable = {} + for _,ent in pairs(Entities) do + local pos = ent:GetPos() + if (pos.X>=min.X) and (pos.X<=max.X) and (pos.Y>=min.Y) and (pos.Y<=max.Y) and (pos.Z>=min.Z) and (pos.Z<=max.Z) then + if(ent:GetClass()!="gmod_ghost")then + EntTable[ent:EntIndex()] = ent + end + end + end + + return EntTable + end + + local function DrawSelectionBox() + + local trace = util.GetPlayerTrace(LocalPlayer()) + local TraceRes = util.TraceLine(trace) + local i = tonumber(LocalPlayer():GetInfo("advdupe2_area_copy_size")) or 50 + + //Bottom Points + local B1 = (Vector(-i,-i,-i)+TraceRes.HitPos) + local B2 = (Vector(-i,i,-i)+TraceRes.HitPos) + local B3 = (Vector(i,i,-i)+TraceRes.HitPos) + local B4 = (Vector(i,-i,-i)+TraceRes.HitPos) + + //Top Points + local T1 = (Vector(-i,-i,i)+TraceRes.HitPos):ToScreen() + local T2 = (Vector(-i,i,i)+TraceRes.HitPos):ToScreen() + local T3 = (Vector(i,i,i)+TraceRes.HitPos):ToScreen() + local T4 = (Vector(i,-i,i)+TraceRes.HitPos):ToScreen() + + //Version 1 Constantly resets the color of all the props that have entered the box and changes all the props color in the it. + //Version 2 Only Colors if the prop is new or has left the box, but if a prop is moved it will change back...gmod bug. + + //Version 1 of prop coloring + if(!AdvDupe2.LastUpdate || CurTime()>=AdvDupe2.LastUpdate)then + + if AdvDupe2.ColorEntities then + for k,v in pairs(AdvDupe2.EntityColors)do + local ent = AdvDupe2.ColorEntities[k] + if(IsValid(ent))then + AdvDupe2.ColorEntities[k]:SetColor(v.r,v.g,v.b,v.a) + end + end + end + + local Entities = FindInBox(B1, (Vector(i,i,i)+TraceRes.HitPos), LocalPlayer()) + AdvDupe2.ColorEntities = Entities + AdvDupe2.EntityColors = {} + for k,v in pairs(Entities)do + local r,g,b,a = v:GetColor() + AdvDupe2.EntityColors[k] = {r = r, g = g,b = b,a = a} + v:SetColor(0,255,0,255) + end + AdvDupe2.LastUpdate = CurTime()+0.25 + + end + + /* Version 2 of prop coloring(this version needs some stuff uncommented in the hook) + if(!AdvDupe2.LastUpdate || CurTime()<=AdvDupe2.LastUpdate)then + + AdvDupe2.TempEntities = {} + local Entities = ents.FindInBox(B1, (Vector(i,i,i)+TraceRes.HitPos)) + + for k,v in pairs(Entities)do + local i = v:EntIndex() + if(!AdvDupe2.ColorEntities[i])then + local r,g,b,a = v:GetColor() + AdvDupe2.EntityColors[i] = {r = r, g = g,b = b,a = a} + v:SetColor(0,255,0,255) + AdvDupe2.ColorEntities[i] = v + end + AdvDupe2.TempEntities[i] = v + end + + if AdvDupe2.ColorEntities then + for k,v in pairs(AdvDupe2.EntityColors)do + if(!AdvDupe2.TempEntities[k])then + local ent = AdvDupe2.ColorEntities[k] + if(ent:IsValid())then + AdvDupe2.ColorEntities[k]:SetColor(v.r,v.g,v.b,v.a) + AdvDupe2.ColorEntities[k] = nil + AdvDupe2.EntityColors[k] = nil + + end + end + end + end + + AdvDupe2.LastUpdate = CurTime()+0.5 + end + */ + + local tracedata = {} + tracedata.mask = MASK_NPCWORLDSTATIC + local WorldTrace + + tracedata.start = B1+Vector(0,0,i*2) + tracedata.endpos = B1 + WorldTrace = util.TraceLine( tracedata ) + B1 = WorldTrace.HitPos:ToScreen() + tracedata.start = B2+Vector(0,0,i*2) + tracedata.endpos = B2 + WorldTrace = util.TraceLine( tracedata ) + B2 = WorldTrace.HitPos:ToScreen() + tracedata.start = B3+Vector(0,0,i*2) + tracedata.endpos = B3 + WorldTrace = util.TraceLine( tracedata ) + B3 = WorldTrace.HitPos:ToScreen() + tracedata.start = B4+Vector(0,0,i*2) + tracedata.endpos = B4 + WorldTrace = util.TraceLine( tracedata ) + B4 = WorldTrace.HitPos:ToScreen() + + surface.SetDrawColor( 0, 255, 0, 255 ) + + //Draw Sides + surface.DrawLine(B1.x, B1.y, T1.x, T1.y) + surface.DrawLine(B2.x, B2.y, T2.x, T2.y) + surface.DrawLine(B3.x, B3.y, T3.x, T3.y) + surface.DrawLine(B4.x, B4.y, T4.x, T4.y) + + //Draw Bottom + surface.DrawLine(B1.x, B1.y, B2.x, B2.y) + surface.DrawLine(B2.x, B2.y, B3.x, B3.y) + surface.DrawLine(B3.x, B3.y, B4.x, B4.y) + surface.DrawLine(B4.x, B4.y, B1.x, B1.y) + + //Draw Top + surface.DrawLine(T1.x, T1.y, T2.x, T2.y) + surface.DrawLine(T2.x, T2.y, T3.x, T3.y) + surface.DrawLine(T3.x, T3.y, T4.x, T4.y) + surface.DrawLine(T4.x, T4.y, T1.x, T1.y) + + end + + usermessage.Hook("AdvDupe2_DrawSelectBox",function() + hook.Add("HUDPaint", "AdvDupe2_DrawSelectionBox", DrawSelectionBox) + if !AdvDupe2 then AdvDupe2={} AdvDupe2.ProgressBar={} end + /*Version 2 Prop coloring + AdvDupe2.ColorEntities = {} + AdvDupe2.EntityColors = {} + */ + end) + + usermessage.Hook("AdvDupe2_RemoveSelectBox",function() + hook.Remove("HUDPaint", "AdvDupe2_DrawSelectionBox") + if AdvDupe2.ColorEntities then + for k,v in pairs(AdvDupe2.EntityColors)do + if(!IsValid(AdvDupe2.ColorEntities[k]))then AdvDupe2.ColorEntities[k]=nil continue end + local r,g,b,a = v.r, v.g, v.b, v.a + AdvDupe2.ColorEntities[k]:SetColor(r,g,b,a) + end + AdvDupe2.ColorEntities={} + AdvDupe2.EntityColors={} + end + end) + + function AdvDupe2.InitProgressBar(label) + if !AdvDupe2 then AdvDupe2={} end + AdvDupe2.ProgressBar = {} + AdvDupe2.ProgressBar.Text = label + AdvDupe2.ProgressBar.Percent = 0 + end + + usermessage.Hook("AdvDupe2_InitProgressBar",function(um) + AdvDupe2.InitProgressBar(um:ReadString()) + end) + + usermessage.Hook("AdvDupe2_UpdateProgressBar",function(um) + AdvDupe2.ProgressBar.Percent = um:ReadChar() + end) + + usermessage.Hook("AdvDupe2_RemoveProgressBar",function(um) + if !AdvDupe2 then AdvDupe2={} end + AdvDupe2.ProgressBar = {} + end) + + usermessage.Hook("AdvDupe2_ResetOffsets",function(um) + RunConsoleCommand("advdupe2_original_origin", "0") + RunConsoleCommand("advdupe2_paste_constraints","1") + RunConsoleCommand("advdupe2_offset_z","0") + RunConsoleCommand("advdupe2_offset_pitch","0") + RunConsoleCommand("advdupe2_offset_yaw","0") + RunConsoleCommand("advdupe2_offset_roll","0") + RunConsoleCommand("advdupe2_paste_parents","1") + RunConsoleCommand("advdupe2_paste_disparents","0") + end) + + usermessage.Hook("AdvDupe2_ReportModel",function(um) + print("Advanced Duplicator 2: Invalid Model: "..um:ReadString()) + end) + + usermessage.Hook("AdvDupe2_ReportClass",function(um) + print("Advanced Duplicator 2: Invalid Class: "..um:ReadString()) + end) + + usermessage.Hook("AdvDupe2_AddFile",function(um) + AdvDupe2.FileBrowser:AddFile(um:ReadString(), um:ReadShort(), um:ReadBool()) + end) + + usermessage.Hook("AdvDupe2_AddFolder",function(um) + AdvDupe2.FileBrowser:AddFolder(um:ReadString(), um:ReadShort(), um:ReadShort(), um:ReadBool()) + end) + + usermessage.Hook("AdvDupe2_ClearBrowser",function(um) + AdvDupe2.FileBrowser:ClearBrowser() + end) + + usermessage.Hook("AdvDupe2_SetDupeInfo",function(um) + if(!AdvDupe2.Info)then return end + + AdvDupe2.Info.File:SetText('File: "'..um:ReadString()..'"') + AdvDupe2.Info.Creator:SetText("Creator: "..um:ReadString()) + AdvDupe2.Info.Date:SetText("Date: "..um:ReadString()) + AdvDupe2.Info.Time:SetText("Time: "..um:ReadString()) + AdvDupe2.Info.Size:SetText("Size : "..um:ReadString()) + AdvDupe2.Info.Desc:SetText("Desc: "..um:ReadString()) + AdvDupe2.Info.Entities:SetText("Entities: "..um:ReadString()) + AdvDupe2.Info.Constraints:SetText("Constraints: "..um:ReadString()) + end) + + usermessage.Hook("AdvDupe2_ResetDupeInfo",function(um) + if(!AdvDupe2.Info)then return end + AdvDupe2.Info.File:SetText("File:") + AdvDupe2.Info.Creator:SetText("Creator:") + AdvDupe2.Info.Date:SetText("Date:") + AdvDupe2.Info.Time:SetText("Time:") + AdvDupe2.Info.Size:SetText("Size:") + AdvDupe2.Info.Desc:SetText("Desc:") + AdvDupe2.Info.Entities:SetText("Entities:") + AdvDupe2.Info.Constraints:SetText("Constraints:") + end) + + usermessage.Hook("AdvDupe2_Ghosting", function(um) + AdvDupe2.GhostEntity = true + end) + + usermessage.Hook("AdvDupe2_NotGhosting", function(um) + AdvDupe2.GhostEntity = nil + AdvDupe2.Rotation = false + end) + + usermessage.Hook("AdvDupe2_RenameNode", function(um) + AdvDupe2.FileBrowser:RenameNode(um:ReadString()) + end) + + usermessage.Hook("AdvDupe2_MoveNode", function(um) + AdvDupe2.FileBrowser:MoveNode(um:ReadString()) + end) + + usermessage.Hook("AdvDupe2_DeleteNode", function(um) + AdvDupe2.FileBrowser:DeleteNode() + end) + +end \ No newline at end of file diff --git a/materials/gui/ad2logo.tga b/materials/gui/ad2logo.tga new file mode 100644 index 0000000..0b5b467 Binary files /dev/null and b/materials/gui/ad2logo.tga differ diff --git a/materials/gui/silkicons/help.vmt b/materials/gui/silkicons/help.vmt new file mode 100644 index 0000000..be84886 --- /dev/null +++ b/materials/gui/silkicons/help.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "gui/silkicons/help" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/materials/gui/silkicons/help.vtf b/materials/gui/silkicons/help.vtf new file mode 100644 index 0000000..bee020e Binary files /dev/null and b/materials/gui/silkicons/help.vtf differ