--[[ | This file was obtained through the combined efforts | of Madbluntz & Plymouth Antiquarian Society. | | Credits: lifestorm, Gregory Wayne Rossel JR., | Maloy, DrPepper10 @ RIP, Atle! | | Visit for more: https://plymouth.thetwilightzone.ru/ --]] --[[ 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.JobManager = {} AdvDupe2.JobManager.PastingHook = false AdvDupe2.JobManager.Queue = {} local debugConvar = GetConVar("AdvDupe2_DebugInfo") local gtSetupTable = { SERIAL = { [TYPE_BOOL] = true, [TYPE_ANGLE] = true, [TYPE_TABLE] = true, [TYPE_NUMBER] = true, [TYPE_VECTOR] = true, [TYPE_STRING] = true }, CONSTRAINT = { Weld = true, Axis = true, Rope = true, Motor = true, Winch = true, Muscle = true, Pulley = true, Slider = true, Elastic = true, Hydraulic = true, Ballsocket = true }, COMPARE = { V1 = Vector(1, 1, 1), A0 = Angle (0, 0, 0), V0 = Vector(0, 0, 0) }, POS = { pos = true, Pos = true, position = true, Position = true }, ANG = { ang = true, Ang = true, angle = true, Angle = true }, MODEL = { model = true, Model = true }, PLAYER = { pl = true, ply = true }, ENT1 = { Ent = true, Ent1 = true, }, TVEHICLE = { VehicleTable = true }, SPECIAL = { Data = true } } local function CopyClassArgTable(tab) local done = {} local function recursiveCopy(oldtable) local newtable = {} done[oldtable] = newtable for k, v in pairs(oldtable) do local varType = TypeID(v) if gtSetupTable.SERIAL[varType] then if varType == TYPE_TABLE then if done[v] then newtable[k] = done[v] else newtable[k] = recursiveCopy(v) end else newtable[k] = v end else if debugConvar:GetBool() then print("[AdvDupe2] ClassArg table with key \"" .. tostring(k) .. "\" has unsupported value of type \"".. type(v) .."\"!") end end end return newtable end return recursiveCopy(tab) end --[[ 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 ---------------------------------------------------------]] function AdvDupe2.duplicator.IsCopyable(Ent) return not Ent.DoNotDuplicate and duplicator.IsAllowed(Ent:GetClass()) and IsValid(Ent:GetPhysicsObject()) end local function CopyEntTable(Ent, Offset) -- Filter duplicator blocked entities out. if not AdvDupe2.duplicator.IsCopyable(Ent) then return nil end local Tab = {} if Ent.PreEntityCopy then local status, valid = pcall(Ent.PreEntityCopy, Ent) if (not status) then print("AD2 PreEntityCopy Error: " .. tostring(valid)) end end local EntityClass = duplicator.FindEntityClass(Ent:GetClass()) local EntTable = Ent:GetTable() if EntityClass then for iNumber, Key in pairs(EntityClass.Args) do if gtSetupTable.SPECIAL[Key] then Tab = CopyClassArgTable(EntTable) end -- Ignore keys from old system if (not gtSetupTable.POS[Key] and not gtSetupTable.ANG[Key] and not gtSetupTable.MODEL[Key]) then local varType = TypeID(EntTable[Key]) if gtSetupTable.SERIAL[varType] then if varType == TYPE_TABLE then Tab[Key] = CopyClassArgTable(EntTable[Key]) else Tab[Key] = EntTable[Key] end elseif varType ~= TYPE_NIL and debugConvar:GetBool() then print("[AdvDupe2] Entity ClassArg \"" .. Key .. "\" of type \"" .. Ent:GetClass() .. "\" has unsupported value of type \"" .. type(EntTable[Key]) .. "\"!\n") end end end end Tab.BoneMods = table.Copy(Ent.BoneMods) if(Ent.EntityMods)then Tab.EntityMods = table.Copy(Ent.EntityMods) end if Ent.PostEntityCopy then local status, valid = pcall(Ent.PostEntityCopy, Ent) if(not status)then print("AD2 PostEntityCopy Error: "..tostring(valid)) end end Tab.Pos = Ent:GetPos() Tab.Class = Ent:GetClass() Tab.Model = Ent:GetModel() Tab.Skin = Ent:GetSkin() Tab.CollisionGroup = Ent:GetCollisionGroup() Tab.ModelScale = Ent:GetModelScale() if (Tab.Skin == 0) then Tab.Skin = nil end if (Tab.ModelScale == 1) then Tab.ModelScale = 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 PhysObj for Bone = 0, Ent:GetPhysicsObjectCount() - 1 do PhysObj = Ent:GetPhysicsObjectNum(Bone) if IsValid(PhysObj) 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:GetAngles() end end Tab.PhysicsObjects[0].Pos = Tab.Pos - Offset Tab.Pos = nil if (Tab.Class ~= "prop_physics") then if (not Tab.BuildDupeInfo) then Tab.BuildDupeInfo = {} end Tab.BuildDupeInfo.IsNPC = Ent:IsNPC() Tab.BuildDupeInfo.IsVehicle = Ent:IsVehicle() end if (IsValid(Ent:GetParent())) then if (not Tab.BuildDupeInfo) then Tab.BuildDupeInfo = {} end Tab.PhysicsObjects[0].Angle = Ent:GetAngles() Tab.BuildDupeInfo.DupeParentID = Ent:GetParent():EntIndex() end -- Flexes local FlexNum = Ent:GetFlexNum() Tab.Flex = Tab.Flex or {} local weight local flexes for i = 0, FlexNum do weight = Ent:GetFlexWeight(i) if (weight ~= 0) then Tab.Flex[i] = weight flexes = true end end if (flexes or Ent:GetFlexScale() ~= 1) then Tab.FlexScale = Ent:GetFlexScale() else Tab.Flex = nil end -- Body Groups Tab.BodyG = {} for k, v in pairs(Ent:GetBodyGroups()) do if ( Ent:GetBodygroup( v.id ) > 0 ) then Tab.BodyG[ v.id ] = Ent:GetBodygroup( v.id ) end end if(next(Tab.BodyG) == nil)then Tab.BodyG = nil end -- Bone Manipulator if (Ent:HasBoneManipulations()) then Tab.BoneManip = {} local c = gtSetupTable.COMPARE for i = 0, Ent:GetBoneCount() do local s = Ent:GetManipulateBoneScale(i) s = ((s ~= c.V1) and s or nil) local a = Ent:GetManipulateBoneAngles(i) a = ((a ~= c.A0) and a or nil) local p = Ent:GetManipulateBonePosition(i) p = ((p ~= c.V0) and p or nil) -- Avoid making a vector just to compare it if (s or a or p) then Tab.BoneManip[i] = {s = s, a = a, p = p} end end end if Ent.GetNetworkVars then Tab.DT = Ent:GetNetworkVars() end -- Make this function on your SENT if you want to modify the -- returned table specifically for your entity. if Ent.OnEntityCopyTableFinish then local status, valid = pcall(Ent.OnEntityCopyTableFinish, Ent, Tab) if (not status) then print("AD2 OnEntityCopyTableFinish Error: " .. tostring(valid)) end end return Tab end --[[ Name: CopyConstraintTable Desc: Create a table for constraints Params:
Constraints Returns:
Constraints,
Entities ]] local function CopyConstraintTable(Const, Offset) if (Const == nil) then return nil, {} end -- Filter duplicator blocked constraints out. if Const.DoNotDuplicate then return nil, {} end local Type = duplicator.ConstraintType[Const.Type] if (not Type) then return nil, {} end local Constraint = {} local Entities = {} Const.Constraint = nil Const.OnDieFunctions = nil Constraint.Entity = {} for k, key in pairs(Type.Args) do if (key ~= "pl" and not string.find(key, "Ent") and not string.find(key, "Bone")) then Constraint[key] = Const[key] end end if ((Const["Ent"] and Const["Ent"]:IsWorld()) or IsValid(Const["Ent"])) then Constraint.Entity[1] = {} Constraint.Entity[1].Index = Const["Ent"]:EntIndex() if (not Const["Ent"]:IsWorld()) then table.insert(Entities, Const["Ent"]) end Constraint.Type = Const.Type if (Const.BuildDupeInfo) then Constraint.BuildDupeInfo = table.Copy(Const.BuildDupeInfo) end else for i = 1, 4 do local ent = "Ent" .. i local lpos = "LPos" .. i local wpos = "WPos" .. i if ((Const[ent] and Const[ent]:IsWorld()) or 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]) then if (i ~= 4 and i ~= 2) then if (Const["Ent2"]) then Constraint.Entity[i].LPos = Const[lpos] - Const["Ent2"]:GetPos() Constraint[lpos] = Const[lpos] - Const["Ent2"]:GetPos() elseif (Const["Ent4"]) then Constraint.Entity[i].LPos = Const[lpos] - Const["Ent4"]:GetPos() Constraint[lpos] = Const[lpos] - Const["Ent4"]:GetPos() end elseif (Const["Ent1"]) then Constraint.Entity[i].LPos = Const[lpos] - Const["Ent1"]:GetPos() Constraint[lpos] = Const[lpos] - Const["Ent1"]:GetPos() end else Constraint.Entity[i].LPos = Offset Constraint[lpos] = Offset end else Constraint.Entity[i].LPos = Const[lpos] Constraint.Entity[i].WPos = Const[wpos] end if (not Const[ent]:IsWorld()) then table.insert(Entities, Const[ent]) end end if (Const[wpos]) then if (not Const["Ent1"]:IsWorld()) then Constraint[wpos] = Const[wpos] - Const["Ent1"]:GetPos() else Constraint[wpos] = Const[wpos] - Const["Ent4"]:GetPos() end end end Constraint.Type = Const.Type if (Const.BuildDupeInfo) then Constraint.BuildDupeInfo = table.Copy(Const.BuildDupeInfo) end end return Constraint, Entities end --[[ Name: Copy Desc: Copy an entity and all entities constrained Params: Entity Returns:
Entities,
Constraints ]] local function Copy(ply, Ent, EntTable, ConstraintTable, Offset) local function RecursiveCopy(Ent) local index = Ent:EntIndex() if EntTable[index] then return end local cantool = Ent.CPPICanTool if cantool and not cantool(Ent, ply, "advdupe2") then return end local EntData = CopyEntTable(Ent, Offset) if EntData == nil then return end EntTable[index] = EntData if Ent.Constraints then for k, Constraint in pairs(Ent.Constraints) do if Constraint:IsValid() then index = Constraint:GetCreationID() if index and not ConstraintTable[index] then local ConstTable, EntTab = CopyConstraintTable(table.Copy(Constraint:GetTable()), Offset) ConstraintTable[index] = ConstTable for j, e in pairs(EntTab) do if e and (e:IsWorld() or e:IsValid()) then RecursiveCopy(e) end end end end end end do -- Wiremod Wire Connections if istable(Ent.Inputs) then for k, v in pairs(Ent.Inputs) do if isentity(v.Src) and v.Src:IsValid() then RecursiveCopy(v.Src) end end end if istable(Ent.Outputs) then for k, v in pairs(Ent.Outputs) do if istable(v.Connected) then for k, v in pairs(v.Connected) do if isentity(v.Entity) and v.Entity:IsValid() then RecursiveCopy(v.Entity) end end end end end end do -- Parented stuff local parent = Ent:GetParent() if IsValid(parent) then RecursiveCopy(parent) end for k, child in pairs(Ent:GetChildren()) do RecursiveCopy(child) end end for k, v in pairs(EntData.PhysicsObjects) do Ent:GetPhysicsObjectNum(k):EnableMotion(v.Frozen) end end RecursiveCopy(Ent) return EntTable, ConstraintTable end AdvDupe2.duplicator.Copy = Copy --[[ Name: AreaCopy Desc: Copy based on a box Returns:
Entities,
Constraints ]] function AdvDupe2.duplicator.AreaCopy(ply, Entities, Offset, CopyOutside) local Constraints, EntTable, ConstraintTable = {}, {}, {} local index, add, AddEnts, AddConstrs, ConstTable, EntTab for _, Ent in pairs(Entities) do index = Ent:EntIndex() EntTable[index] = CopyEntTable(Ent, Offset) if (EntTable[index] ~= nil) then if (not constraint.HasConstraints(Ent)) then for k, v in pairs(EntTable[Ent:EntIndex()].PhysicsObjects) do Ent:GetPhysicsObjectNum(k):EnableMotion(v.Frozen) end else for k, v in pairs(Ent.Constraints) do -- Filter duplicator blocked constraints out. if not v.DoNotDuplicate then index = v:GetCreationID() if (index and not Constraints[index]) then Constraints[index] = v end end end for k, v in pairs(EntTable[Ent:EntIndex()].PhysicsObjects) do Ent:GetPhysicsObjectNum(k):EnableMotion(v.Frozen) end end end end for _, Constraint in pairs(Constraints) do ConstTable, EntTab = 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 (not CopyOutside) then add = true for k, v in pairs(EntTab) do if (not Entities[v:EntIndex()]) then add = false end end if (add) then ConstraintTable[_] = ConstTable end else -- Copy entities and constraints outside of the box that are constrained to entities inside the box ConstraintTable[_] = ConstTable for k, v in pairs(EntTab) do Copy(ply, v, EntTable, ConstraintTable, Offset) 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, DontEnable) local Factory = duplicator.ConstraintType[Constraint.Type] if not Factory then return end local first, firstindex -- Ent1 or Ent in the constraint's table local second, secondindex -- Any other Ent that is not Ent1 or Ent local Args = {} -- Build the argument list for the Constraint's spawn function for k, Key in ipairs(Factory.Args) do local Val = Constraint[Key] if gtSetupTable.PLAYER[Key] then Val = Player end for i = 1, 4 do if (Constraint.Entity and Constraint.Entity[i]) then if Key == "Ent" .. i or Key == "Ent" then if (Constraint.Entity[i].World) then Val = game.GetWorld() else Val = EntityList[Constraint.Entity[i].Index] if not IsValid(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 if (IsValid(Val:GetPhysicsObject())) then Val:GetPhysicsObject():EnableMotion(false) end -- Important for perfect duplication -- Get which entity is which so we can reposition them before constraining if (gtSetupTable.ENT1[Key]) 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 or 0 end if Key == "LPos" .. i then if (Constraint.Entity[i].World and Constraint.Entity[i].LPos) then if (i == 2 or 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 (not Constraint.Entity[1].World) then Val = Constraint[Key] + EntityList[Constraint.Entity[1].Index]:GetPos() else Val = Constraint[Key] + EntityList[Constraint.Entity[4].Index]:GetPos() end end end Args[k] = Val end local Bone1, Bone1Index, ReEnableFirst local Bone2, Bone2Index, ReEnableSecond local buildInfo = Constraint.BuildDupeInfo if (buildInfo) then if first ~= nil and second ~= nil and not second:IsWorld() and buildInfo.EntityPos ~= nil then local SecondPhys = second:GetPhysicsObject() if IsValid(SecondPhys) then if not DontEnable then ReEnableSecond = SecondPhys:IsMoveable() end SecondPhys:EnableMotion(false) second:SetPos(first:GetPos() - buildInfo.EntityPos) if (buildInfo.Bone2) then Bone2Index = buildInfo.Bone2 Bone2 = second:GetPhysicsObjectNum(Bone2Index) if IsValid(Bone2) then Bone2:EnableMotion(false) Bone2:SetPos(second:GetPos() + buildInfo.Bone2Pos) Bone2:SetAngles(buildInfo.Bone2Angle) end end end end if first ~= nil and not first:IsWorld() and buildInfo.Ent1Ang ~= nil then local FirstPhys = first:GetPhysicsObject() if IsValid(FirstPhys) then if not DontEnable then ReEnableFirst = FirstPhys:IsMoveable() end FirstPhys:EnableMotion(false) first:SetAngles(buildInfo.Ent1Ang) if (buildInfo.Bone1) then Bone1Index = buildInfo.Bone1 Bone1 = first:GetPhysicsObjectNum(Bone1Index) if IsValid(Bone1) then Bone1:EnableMotion(false) Bone1:SetPos(first:GetPos() + buildInfo.Bone1Pos) Bone1:SetAngles(buildInfo.Bone1Angle) end end end end if second ~= nil and not second:IsWorld() then if buildInfo.Ent2Ang ~= nil then second:SetAngles(buildInfo.Ent2Ang) elseif buildInfo.Ent4Ang ~= nil then second:SetAngles(buildInfo.Ent4Ang) end end end local ok, Ent = pcall(Factory.Func, unpack(Args, 1, #Factory.Args)) if not ok or not Ent then if (Player) then AdvDupe2.Notify(Player, "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(buildInfo) -- Move the entities back after constraining them. No point in moving the world though. if (EntityTable) then local fEnt = EntityTable[firstindex] local sEnt = EntityTable[secondindex] if (first ~= nil and not first:IsWorld()) then first:SetPos(fEnt.BuildDupeInfo.PosReset) first:SetAngles(fEnt.BuildDupeInfo.AngleReset) if (IsValid(Bone1) and Bone1Index ~= 0) then Bone1:SetPos(fEnt.BuildDupeInfo.PosReset + fEnt.BuildDupeInfo.PhysicsObjects[Bone1Index].Pos) Bone1:SetAngles(fEnt.PhysicsObjects[Bone1Index].Angle) end local FirstPhys = first:GetPhysicsObject() if IsValid(FirstPhys) then if ReEnableFirst then FirstPhys:EnableMotion(true) end end end if (second ~= nil and not second:IsWorld()) then second:SetPos(sEnt.BuildDupeInfo.PosReset) second:SetAngles(sEnt.BuildDupeInfo.AngleReset) if (IsValid(Bone2) and Bone2Index ~= 0) then Bone2:SetPos(sEnt.BuildDupeInfo.PosReset + sEnt.BuildDupeInfo.PhysicsObjects[Bone2Index].Pos) Bone2:SetAngles(sEnt.PhysicsObjects[Bone2Index].Angle) end local SecondPhys = second:GetPhysicsObject() if IsValid(SecondPhys) then if ReEnableSecond then SecondPhys:EnableMotion(true) end end end end if (Ent and Ent.length) then Ent.length = Constraint["length"] end -- Fix for weird bug with ropes return Ent end local function ApplyEntityModifiers(Player, Ent) if not Ent.EntityMods then return end if Ent.EntityMods.trail then Ent.EntityMods.trail.EndSize = math.Clamp(tonumber(Ent.EntityMods.trail.EndSize) or 0, 0, 1024) Ent.EntityMods.trail.StartSize = math.Clamp(tonumber(Ent.EntityMods.trail.StartSize) or 0, 0, 1024) end for Type, Data in SortedPairs(Ent.EntityMods) do local ModFunction = duplicator.EntityModifiers[Type] if (ModFunction) then local ok, err = pcall(ModFunction, Player, Ent, Data) if (not ok) then if (Player) then Player:ChatPrint('Error applying entity modifer, "' .. tostring(Type) .. '". ERROR: ' .. err) else print('Error applying entity modifer, "' .. tostring(Type) .. '". ERROR: ' .. err) end end end end if (Ent.EntityMods["mass"] and duplicator.EntityModifiers["mass"]) then local ok, err = pcall(duplicator.EntityModifiers["mass"], Player, Ent, Ent.EntityMods["mass"]) if (not ok) then if (Player) then Player:ChatPrint('Error applying entity modifer, "mass". ERROR: ' .. err) else print('Error applying entity modifer, "' .. tostring(Type) .. '". ERROR: ' .. err) end end end if(Ent.EntityMods["buoyancy"] and duplicator.EntityModifiers["buoyancy"]) then local ok, err = pcall(duplicator.EntityModifiers["buoyancy"], Player, Ent, Ent.EntityMods["buoyancy"]) if (not ok) then if (Player) then Player:ChatPrint('Error applying entity modifer, "buoyancy". ERROR: ' .. err) else print('Error applying entity modifer, "' .. tostring(Type) .. '". ERROR: ' .. err) end end end end local function ApplyBoneModifiers(Player, Ent) if (not Ent.BoneMods or not Ent.PhysicsObjects) then return end for Type, ModFunction in pairs(duplicator.BoneModifiers) do for Bone, Args in pairs(Ent.PhysicsObjects) do if (Ent.BoneMods[Bone] and Ent.BoneMods[Bone][Type]) then local PhysObj = Ent:GetPhysicsObjectNum(Bone) if (Ent.PhysicsObjects[Bone]) then local ok, err = pcall(ModFunction, Player, Ent, Bone, PhysObj, Ent.BoneMods[Bone][Type]) if (not ok) then Player:ChatPrint('Error applying bone modifer, "' .. tostring(Type) .. '". ERROR: ' .. err) end end end end end end --[[ Name: DoGenericPhysics Desc: Applies bone data, generically. Params: Player,
data Returns: Entity,
data ]] local function DoGenericPhysics(Entity, data, Player) if (not data) then return end if (not data.PhysicsObjects) then return end local Phys if (Player) then for Bone, Args in pairs(data.PhysicsObjects) do Phys = Entity:GetPhysicsObjectNum(Bone) if (IsValid(Phys)) then Phys:SetPos(Args.Pos) Phys:SetAngles(Args.Angle) Phys:EnableMotion(false) Player:AddFrozenPhysicsObject(Entity, Phys) end end else for Bone, Args in pairs(data.PhysicsObjects) do Phys = Entity:GetPhysicsObjectNum(Bone) if (IsValid(Phys)) then Phys:SetPos(Args.Pos) Phys:SetAngles(Args.Angle) Phys:EnableMotion(false) end end end end local function reportclass(ply, class) net.Start("AdvDupe2_ReportClass") net.WriteString(class) net.Send(ply) end local function reportmodel(ply, model) net.Start("AdvDupe2_ReportModel") net.WriteString(model) net.Send(ply) end --[[ Name: GenericDuplicatorFunction Desc: Override the default duplicator's GenericDuplicatorFunction function Params:
data, Player Returns: Entity ]] local function GenericDuplicatorFunction(data, Player) local Entity = ents.Create(data.Class) if (not IsValid(Entity)) then if (Player) then reportclass(Player, data.Class) else print("Advanced Duplicator 2 Invalid Class: " .. data.Class) end return nil end if (not util.IsValidModel(data.Model) and not file.Exists(data.Model, "GAME")) 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) if (Player) then Entity:SetCreator(Player) end 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 Data.ModelScale then Data.ModelScale = math.Clamp(Data.ModelScale, 1e-5, 1e5) end if (not util.IsValidModel(Model) and not file.Exists(Data.Model, "GAME")) 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 Data.Frozen = true -- Make sure this is allowed if (Player) then if (not gamemode.Call("PlayerSpawnProp", Player, Model)) then return false end end local Prop = ents.Create("prop_physics") if not IsValid(Prop) then return false end duplicator.DoGeneric(Prop, Data) if (Player) then Prop:SetCreator(Player) end Prop:Spawn() Prop:Activate() DoGenericPhysics(Prop, Data, Player) if (Data.Flex) then duplicator.DoFlex(Prop, Data.Flex, Data.FlexScale) end return Prop end local function RestoreBodyGroups(ent, BodyG) for k, v in pairs(BodyG) do ent:SetBodygroup(k, v) end end --[[ Name: CreateEntityFromTable Desc: Creates an entity from a given table Params:
EntTable, Player Returns: nil ]] local function IsAllowed(Player, Class, EntityClass) if (scripted_ents.GetMember(Class, "DoNotDuplicate")) then return false end if (IsValid(Player) and not Player:IsAdmin()) then if not duplicator.IsAllowed(Class) then return false end if (not scripted_ents.GetMember(Class, "Spawnable") and not EntityClass) then return false end if (scripted_ents.GetMember(Class, "AdminOnly")) then return false end end return true end local function CreateEntityFromTable(EntTable, Player) hook.Run("AdvDupe2_PreCreateEntity", EntTable, Player) local EntityClass = duplicator.FindEntityClass(EntTable.Class) if not IsAllowed(Player, EntTable.Class, EntityClass) then Player:ChatPrint([[Entity Class Black listed, "]] .. EntTable.Class .. [["]]) return nil end local sent = false local status, valid local GENERIC = false local CreatedEntities = {} -- This class is unregistered. Instead of failing try using a generic -- Duplication function to make a new copy. if (not EntityClass) then GENERIC = true sent = true if Player then if(EntTable.Class=="prop_effect")then sent = gamemode.Call( "PlayerSpawnEffect", Player, EntTable.Model) else sent = gamemode.Call( "PlayerSpawnSENT", Player, EntTable.Class) end else sent = true end if (sent == false) then print("Advanced Duplicator 2: Creation rejected for class, : " .. EntTable.Class) return nil else sent = true end if IsAllowed(Player, EntTable.Class, EntityClass) 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 (not GENERIC) then -- Build the argument list for the Entitie's spawn function local ArgList, Arg = {} for iNumber, Key in pairs(EntityClass.Args) do Arg = nil -- Translate keys from old system if (gtSetupTable.POS[Key]) then Key = "Pos" end if (gtSetupTable.ANG[Key]) then Key = "Angle" end if (gtSetupTable.MODEL[Key]) then Key = "Model" end if (gtSetupTable.TVEHICLE[Key] and EntTable[Key] and EntTable[Key].KeyValues) then EntTable[Key].KeyValues = { limitview = EntTable[Key].KeyValues.limitview, vehiclescript = EntTable[Key].KeyValues.vehiclescript } end Arg = EntTable[Key] -- Special keys if (gtSetupTable.SPECIAL[Key]) then Arg = EntTable end ArgList[iNumber] = Arg end -- Create and return the entity if (EntTable.Class == "prop_physics") then valid = MakeProp(Player, unpack(ArgList, 1, #EntityClass.Args)) -- Create prop_physics like this because if the model doesn't exist it will cause elseif IsAllowed(Player, EntTable.Class, EntityClass) then -- Create sents using their spawn function with the arguments we stored earlier sent = true if Player then if (not EntTable.BuildDupeInfo.IsVehicle and not EntTable.BuildDupeInfo.IsNPC and EntTable.Class ~= "prop_ragdoll" and EntTable.Class ~= "prop_effect") then sent = hook.Call("PlayerSpawnSENT", nil, Player, EntTable.Class) end else sent = true end if (sent == false) then print("Advanced Duplicator 2: Creation rejected for class, : " .. EntTable.Class) return nil else sent = true end hook.Add( "OnEntityCreated", "AdvDupe2_GetLastEntitiesCreated", function( ent ) table.insert( CreatedEntities, ent ) end ) status, valid = xpcall(EntityClass.Func, ErrorNoHaltWithStack, Player, unpack(ArgList, 1, #EntityClass.Args)) hook.Remove( "OnEntityCreated", "AdvDupe2_GetLastEntitiesCreated" ) 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 if (Player) then for Bone = 0, iNumPhysObjects - 1 do PhysObj = valid:GetPhysicsObjectNum(Bone) if IsValid(PhysObj) then PhysObj:EnableMotion(false) Player:AddFrozenPhysicsObject(valid, PhysObj) end end else for Bone = 0, iNumPhysObjects - 1 do PhysObj = valid:GetPhysicsObjectNum(Bone) if IsValid(PhysObj) then PhysObj:EnableMotion(false) end end end if (EntTable.Skin) then valid:SetSkin(EntTable.Skin) end if (EntTable.BodyG) then RestoreBodyGroups(valid, EntTable.BodyG) end if valid.RestoreNetworkVars then valid:RestoreNetworkVars(EntTable.DT) end if GENERIC and Player then if(EntTable.Class=="prop_effect")then gamemode.Call("PlayerSpawnedEffect", Player, valid:GetModel(), valid) else gamemode.Call("PlayerSpawnedSENT", Player, valid) end end elseif (Player) then gamemode.Call("PlayerSpawnedProp", Player, valid:GetModel(), valid) end return valid else if (status == false) then print("Advanced Duplicator 2: Error creating entity, removing last created entities") for _, CreatedEntity in pairs(CreatedEntities) do SafeRemoveEntity(CreatedEntity) end end 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) local CreatedEntities = {} -- -- Create entities -- local proppos DisablePropCreateEffect = true for k, v in pairs(EntityList) do if (not 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 AdvDupe2.SpawningEntity = true local Ent = CreateEntityFromTable(v, Player) AdvDupe2.SpawningEntity = false if Ent then if (Player) then Player:AddCleanup("AdvDupe2", Ent) end Ent.BoneMods = table.Copy(v.BoneMods) Ent.EntityMods = table.Copy(v.EntityMods) Ent.PhysicsObjects = table.Copy(v.PhysicsObjects) if (v.CollisionGroup) then Ent:SetCollisionGroup(v.CollisionGroup) end if (Ent.OnDuplicated) then Ent:OnDuplicated(v) end ApplyEntityModifiers(Player, Ent) ApplyBoneModifiers(Player, Ent) Ent.SolidMod = not Ent:IsSolid() Ent:SetNotSolid(true) elseif (Ent == false) then Ent = nil -- ConstraintList = {} -- break else Ent = nil end CreatedEntities[k] = Ent end local CreatedConstraints, 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") for _, v in pairs(CreatedEntities) do -- If the entity has a PostEntityPaste function tell it to use it now if v.PostEntityPaste then local status, valid = pcall(v.PostEntityPaste, v, Player, v, CreatedEntities) if (not status) then print("AD2 PostEntityPaste Error: " .. tostring(valid)) end end v:GetPhysicsObject():EnableMotion(false) if (EntityList[_].BuildDupeInfo.DupeParentID and Parenting) then v:SetParent(CreatedEntities[EntityList[_].BuildDupeInfo.DupeParentID]) end v:SetNotSolid(v.SolidMod) 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 local status, valid = pcall(v.PostEntityPaste, v, Player, v, CreatedEntities) if (not status) then print("AD2 PostEntityPaste Error: " .. tostring(valid)) end end v:GetPhysicsObject():EnableMotion(false) if (EntityList[_].BuildDupeInfo.DupeParentID and Parenting) then v:SetParent(CreatedEntities[EntityList[_].BuildDupeInfo.DupeParentID]) end v:SetNotSolid(v.SolidMod) end end DisablePropCreateEffect = nil hook.Call("AdvDupe_FinishPasting", nil, { { EntityList = EntityList, CreatedEntities = CreatedEntities, ConstraintList = ConstraintList, CreatedConstraints = CreatedConstraints, HitPos = OrigPos or Position, Player = Player } }, 1) return CreatedEntities, CreatedConstraints end local function AdvDupe2_Spawn() local Queue = AdvDupe2.JobManager.Queue[AdvDupe2.JobManager.CurrentPlayer] if (not Queue or not IsValid(Queue.Player)) then if Queue then table.remove(AdvDupe2.JobManager.Queue, AdvDupe2.JobManager.CurrentPlayer) end if (#AdvDupe2.JobManager.Queue == 0) then hook.Remove("Tick", "AdvDupe2_Spawning") DisablePropCreateEffect = nil AdvDupe2.JobManager.PastingHook = false end return end if (Queue.Entity) then if (Queue.Current == 1) then AdvDupe2.InitProgressBar(Queue.Player, "Pasting:") Queue.Player.AdvDupe2.Queued = false end if (Queue.Current > #Queue.SortedEntities) then Queue.Entity = false Queue.Constraint = true Queue.Current = 1 return end if (not Queue.SortedEntities[Queue.Current]) then Queue.Current = Queue.Current + 1 return end local k = Queue.SortedEntities[Queue.Current] local v = Queue.EntityList[k] if (not v.BuildDupeInfo) then v.BuildDupeInfo = {} end if Queue.Revision < 1 and v.LocalPos then for i, _ in pairs(v.PhysicsObjects) do v.PhysicsObjects[i] = {Pos = v.LocalPos, Angle = v.LocalAngle} end end v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects) local 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 AdvDupe2.SpawningEntity = true local Ent = CreateEntityFromTable(v, Queue.Player) AdvDupe2.SpawningEntity = false if Ent then Queue.Player:AddCleanup("AdvDupe2", Ent) Ent.BoneMods = table.Copy(v.BoneMods) Ent.EntityMods = table.Copy(v.EntityMods) Ent.PhysicsObjects = table.Copy(v.PhysicsObjects) Ent.SolidMod = not Ent:IsSolid() local Phys = Ent:GetPhysicsObject() if (IsValid(Phys)) then Phys:EnableMotion(false) end if (not Queue.DisableProtection) then Ent:SetNotSolid(true) end if (v.CollisionGroup) then Ent:SetCollisionGroup(v.CollisionGroup) end if (Ent.OnDuplicated) then Ent:OnDuplicated(v) end elseif (Ent == false) then Ent = nil else Ent = nil end Queue.CreatedEntities[k] = Ent AdvDupe2.UpdateProgressBar(Queue.Player, math.floor((Queue.Percent * Queue.Current) * 100)) Queue.Current = Queue.Current + 1 if (Queue.Current > #Queue.SortedEntities) then for _, Ent in pairs(Queue.CreatedEntities) do ApplyEntityModifiers(Queue.Player, Ent) ApplyBoneModifiers(Queue.Player, Ent) -- If the entity has a PostEntityPaste function tell it to use it now if Ent.PostEntityPaste then local status, valid = pcall(Ent.PostEntityPaste, Ent, Queue.Player, Ent, Queue.CreatedEntities) if (not status) then print("AD2 PostEntityPaste Error: " .. tostring(valid)) end end end 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") DisablePropCreateEffect = nil AdvDupe2.JobManager.PastingHook = false end if (not 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, true) if IsValid(Entity) then table.insert(Queue.CreatedConstraints, Entity) end elseif (next(Queue.ConstraintList) ~= nil) then local tbl = {} for k, v in pairs(Queue.ConstraintList) do table.insert(tbl, v) end Queue.ConstraintList = tbl Queue.Current = 0 end AdvDupe2.UpdateProgressBar(Queue.Player, math.floor((Queue.Percent * (Queue.Current + Queue.Plus)) * 100)) 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 undotxt = Queue.Name and ("AdvDupe2 ("..Queue.Name..")") or "AdvDupe2" local undos = undo.GetTable()[Queue.Player:UniqueID()] for i = #undos, 1, -1 do if (undos[i] and undos[i].Name == undotxt) then undos[i] = nil -- Undo module netmessage net.Start("Undo_Undone") net.WriteInt(i, 16) net.Send(Queue.Player) break end end undo.Create(undotxt) local phys, edit, mass for k, v in pairs(Queue.CreatedEntities) do if (not IsValid(v)) then v = nil else edit = true if (Queue.EntityList[k].BuildDupeInfo.DupeParentID ~= nil and Queue.Parenting) then v:SetParent(Queue.CreatedEntities[Queue.EntityList[k].BuildDupeInfo.DupeParentID]) if (v.Constraints ~= nil) then for i, c in pairs(v.Constraints) do if (c and gtSetupTable.CONSTRAINT[c.Type]) then edit = false break end end end if (edit and IsValid(v:GetPhysicsObject())) then mass = v:GetPhysicsObject():GetMass() v:PhysicsInitShadow(false, false) v:SetCollisionGroup(COLLISION_GROUP_WORLD) v:GetPhysicsObject():EnableMotion(false) v:GetPhysicsObject():Sleep() v:GetPhysicsObject():SetMass(mass) end else edit = false 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 phys:Wake() end end elseif (preservefrozenstate) then for i = 0, v:GetPhysicsObjectCount() do phys = v:GetPhysicsObjectNum(i) if (IsValid(phys)) then if (Queue.EntityList[k].BuildDupeInfo.PhysicsObjects[i].Frozen) then phys:EnableMotion(true) -- Restore the entity and all of its objects to their original frozen state phys:Wake() else Queue.Player:AddFrozenPhysicsObject(v, phys) end end end else for i = 0, v:GetPhysicsObjectCount() do phys = v:GetPhysicsObjectNum(i) if (IsValid(phys)) then if (phys:IsMoveable()) then phys:EnableMotion(false) -- Freeze the entitiy and all of its objects Queue.Player:AddFrozenPhysicsObject(v, phys) end end end end if (not edit or not Queue.DisableParents) then v:SetNotSolid(v.SolidMod) end undo.AddEntity(v) end end undo.SetPlayer(Queue.Player) undo.Finish(undotxt) hook.Call("AdvDupe_FinishPasting", nil, { { EntityList = Queue.EntityList, CreatedEntities = Queue.CreatedEntities, ConstraintList = Queue.ConstraintList, CreatedConstraints = Queue.CreatedConstraints, HitPos = Queue.PositionOffset, Player = Queue.Player } }, 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") DisablePropCreateEffect = nil 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 end local ticktotal = 0 local function ErrorCatchSpawning() ticktotal = ticktotal + math.max(GetConVarNumber("AdvDupe2_SpawnRate"), 0.01) while ticktotal >= 1 do ticktotal = ticktotal - 1 local status, err = pcall(AdvDupe2_Spawn) if (not status) then -- PUT ERROR LOGGING HERE if (not AdvDupe2.JobManager.Queue) then print("[AdvDupe2Notify]\t" .. err) AdvDupe2.JobManager.Queue = {} return end local Queue = AdvDupe2.JobManager.Queue[AdvDupe2.JobManager.CurrentPlayer] if (not Queue) then print("[AdvDupe2Notify]\t" .. err) return end if (IsValid(Queue.Player)) then AdvDupe2.Notify(Queue.Player, err) local undos = undo.GetTable()[Queue.Player:UniqueID()] local undotxt = Queue.Name and ("AdvDupe2 ("..Queue.Name..")") or "AdvDupe2" for i = #undos, 1, -1 do if (undos[i] and undos[i].Name == undotxt) then undos[i] = nil -- Undo module netmessage net.Start("Undo_Undone") net.WriteInt(i, 16) net.Send(Queue.Player) break end end else print("[AdvDupe2Notify]\t" .. err) end for k, v in pairs(Queue.CreatedEntities) do if (IsValid(v)) then v:Remove() end end if (IsValid(Queue.Player)) then AdvDupe2.FinishPasting(Queue.Player, true) end table.remove(AdvDupe2.JobManager.Queue, AdvDupe2.JobManager.CurrentPlayer) if (#AdvDupe2.JobManager.Queue == 0) then hook.Remove("Tick", "AdvDupe2_Spawning") DisablePropCreateEffect = nil AdvDupe2.JobManager.PastingHook = false else if (#Queue < AdvDupe2.JobManager.CurrentPlayer) then AdvDupe2.JobManager.CurrentPlayer = 1 end end end end end local function RemoveSpawnedEntities(tbl, i) if (not AdvDupe2.JobManager.Queue[i]) then return end -- Without this some errors come up, double check the errors without this line for k, v in pairs(AdvDupe2.JobManager.Queue[i].CreatedEntities) do if (IsValid(v)) then v:Remove() end end AdvDupe2.FinishPasting(AdvDupe2.JobManager.Queue[i].Player, false) table.remove(AdvDupe2.JobManager.Queue, i) if (#AdvDupe2.JobManager.Queue == 0) then hook.Remove("Tick", "AdvDupe2_Spawning") DisablePropCreateEffect = nil AdvDupe2.JobManager.PastingHook = false end end function AdvDupe2.InitPastingQueue(Player, PositionOffset, AngleOffset, OrigPos, Constrs, Parenting, DisableParents, DisableProtection) local i = #AdvDupe2.JobManager.Queue + 1 local Queue = { Player = Player, SortedEntities = {}, EntityList = table.Copy(Player.AdvDupe2.Entities), Current = 1, Name = Player.AdvDupe2.Name, Entity = true, Constraint = false, Parenting = Parenting, DisableParents = DisableParents, DisableProtection = DisableProtection, CreatedEntities = {}, CreatedConstraints = {}, PositionOffset = PositionOffset or Vector(0, 0, 0), AngleOffset = AngleOffset or Angle(0, 0, 0), Revision = Player.AdvDupe2.Revision, } AdvDupe2.JobManager.Queue[i] = Queue if (Constrs) then Queue.ConstraintList = table.Copy(Player.AdvDupe2.Constraints) else Queue.ConstraintList = {} end Queue.OrigPos = OrigPos for k, v in pairs(Player.AdvDupe2.Entities) do table.insert(Queue.SortedEntities, k) end if (Player.AdvDupe2.Name) then print( "[AdvDupe2NotifyPaste]\t Player: " .. Player:Nick() .. " Pasted File, " .. Player.AdvDupe2.Name .. " with, " .. #Queue.SortedEntities .. " Entities and " .. #Player.AdvDupe2.Constraints .. " Constraints.") else print("[AdvDupe2NotifyPaste]\t Player: " .. Player:Nick() .. " Pasted, " .. #Queue.SortedEntities .. " Entities and " .. #Player.AdvDupe2.Constraints .. " Constraints.") end Queue.Plus = #Queue.SortedEntities Queue.Percent = 1 / (#Queue.SortedEntities + #Queue.ConstraintList) AdvDupe2.InitProgressBar(Player, "Queued:") Player.AdvDupe2.Queued = true if (not AdvDupe2.JobManager.PastingHook) then DisablePropCreateEffect = true hook.Add("Tick", "AdvDupe2_Spawning", ErrorCatchSpawning) AdvDupe2.JobManager.PastingHook = true AdvDupe2.JobManager.CurrentPlayer = 1 end local undotxt = Player.AdvDupe2.Name and ("AdvDupe2 ("..Player.AdvDupe2.Name..")") or "AdvDupe2" undo.Create(undotxt) undo.SetPlayer(Player) undo.AddFunction(RemoveSpawnedEntities, i) undo.Finish(undotxt) end