AddCSLuaFile( "vgui/duplicator_gui.lua" ) AddCSLuaFile( "vgui/duplicator_gui_button.lua" ) TOOL.Category = "Construction" TOOL.Name = "#Duplicator" TOOL.Command = nil TOOL.ConfigName = "" TOOL.Stored = true TOOL.ClientConVar[ "simple" ] = 0 cleanup.Register( "duplicates" ) // // Converts to world so that the entities will be spawned in the correct positions // local function ConvertEntityPositionsToWorld( EntTable, Offset, HoldAngle ) for k, Ent in pairs( EntTable ) do local NewPos, NewAngle = LocalToWorld( Ent.LocalPos, Ent.LocalAngle, Offset, HoldAngle ) Ent.Pos = NewPos Ent.Angle = NewAngle // And for physics objects if ( Ent.PhysicsObjects ) then for Num, Object in pairs( Ent.PhysicsObjects ) do local NewPos, NewAngle = LocalToWorld( Object.LocalPos, Object.LocalAngle, Offset, HoldAngle ) Object.Pos = NewPos Object.Angle = NewAngle end end end end // // Move the world positions // local function ConvertConstraintPositionsToWorld( Constraints, Offset, HoldAngle ) for k, Constraint in pairs( Constraints ) do if ( Constraint.Entity ) then for k, Entity in pairs( Constraint.Entity ) do if (Entity.World && Entity.LPos) then local NewPos, NewAngle = LocalToWorld( Entity.LPos, Angle(0,0,0), Offset, HoldAngle ) Entity.LPosOld = Entity.LPos Entity.LPos = NewPos end end end end end // // Resets the positions of all the entities in the table // local function ResetPositions( EntTable, Constraints ) for k, Ent in pairs( EntTable ) do Ent.Pos = Ent.LocalPos * 1 Ent.Angle = Ent.LocalAngle * 1 // And for physics objects if ( Ent.PhysicsObjects ) then for Num, Object in pairs( Ent.PhysicsObjects ) do Object.Pos = Object.LocalPos * 1 Object.Angle = Object.LocalAngle * 1 end end end for k, Constraint in pairs( Constraints ) do if ( Constraint.Entity ) then for k, Entity in pairs( Constraint.Entity ) do if (Entity.LPosOld) then Entity.LPos = Entity.LPosOld Entity.LPosOld = nil end end end end end // // Converts the positions from world positions to positions local to Offset // local function ConvertPositionsToLocal( EntTable, Constraints, Offset, HoldAngle ) for k, Ent in pairs( EntTable ) do Ent.Pos = Ent.Pos - Offset Ent.LocalPos = Ent.Pos * 1 Ent.LocalAngle = Ent.Angle * 1 if ( Ent.PhysicsObjects ) then for Num, Object in pairs(Ent.PhysicsObjects) do Object.Pos = Object.Pos - Offset Object.LocalPos = Object.Pos * 1 Object.LocalAngle = Object.Angle * 1 end end end // If the entity is constrained to the world we want to move the points to be // relative to where we're clicking for k, Constraint in pairs( Constraints ) do if ( Constraint.Entity ) then for k, Entity in pairs( Constraint.Entity ) do if (Entity.World && Entity.LPos) then Entity.LPos = Entity.LPos - Offset end end end end end // // PASTE // function TOOL:LeftClick( trace ) if ( CLIENT ) then return true end local DupeTable = self:GetOwner():UniqueIDTable( "Duplicator" ) if (!DupeTable.Entities) then return false end local angle = self:GetOwner():GetAngles() angle.pitch = 0 angle.roll = 0 // Create the entities at the clicked position at the angle we're facing right now ConvertEntityPositionsToWorld( DupeTable.Entities, trace.HitPos, angle - DupeTable.HoldAngle ) ConvertConstraintPositionsToWorld( DupeTable.Constraints, trace.HitPos, angle - DupeTable.HoldAngle ) local Ents, Constraints = duplicator.Paste( self:GetOwner(), DupeTable.Entities, DupeTable.Constraints ) ResetPositions( DupeTable.Entities, DupeTable.Constraints ) // Add all of the created entities // to the undo system under one undo. undo.Create( "Duplicator" ) for k, ent in pairs( Ents ) do undo.AddEntity( ent ) end for k, ent in pairs( Ents ) do self:GetOwner():AddCleanup( "duplicates", ent ) end undo.SetPlayer( self:GetOwner() ) undo.Finish() return true end // // Copy // function TOOL:RightClick( trace ) if (!trace.Entity || !trace.Entity:IsValid() || trace.Entity:IsPlayer() ) then self:ReleaseGhostEntity() local DupeTable = self:GetOwner():UniqueIDTable( "Duplicator" ) DupeTable.HeadEntityIdx = nil DupeTable.HoldAngle = nil DupeTable.HoldPos = nil DupeTable.Entities = nil DupeTable.Constraints = nil self.Stored = true return true end local StartPos = trace.HitPos self:ReleaseGhostEntity() if ( CLIENT ) then return true end // Get the distance from the floor local tr = {} tr.start = StartPos tr.endpos = StartPos + Vector(0,0,-1024) tr.mask = MASK_NPCSOLID_BRUSHONLY local tr_floor = util.TraceLine( tr ) if (tr_floor.Hit) then StartPos = StartPos + Vector(0,0,-1) * tr_floor.Fraction * 1024 end // Copy the entities local Entities, Constraints = duplicator.Copy( trace.Entity ) local angle = self:GetOwner():GetAngles() angle.pitch = 0 angle.roll = 0 // Convert the positions to local ConvertPositionsToLocal( Entities, Constraints, StartPos, angle ) // Store stuff for pasting/ghosting // Save to a UniqueID table so the object will exist after the player dies/leaves the server local DupeTable = self:GetOwner():UniqueIDTable( "Duplicator" ) DupeTable.HeadEntityIdx = trace.Entity:EntIndex() DupeTable.HoldAngle = angle DupeTable.HoldPos = trace.Entity:WorldToLocal( StartPos ) DupeTable.Entities = Entities DupeTable.Constraints = Constraints self:StartGhostEntities( DupeTable.Entities, DupeTable.HeadEntityIdx, DupeTable.HoldPos, DupeTable.HoldAngle ) self.Stored = false return true end // // Think // function TOOL:Think() self:UpdateGhostEntities() end // // Make the ghost entities // function TOOL:MakeGhostFromTable( EntTable, pParent, HoldPos, HoldAngle ) local GhostEntity = nil 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.Pos = EntTable.Pos GhostEntity.Angle = EntTable.Angle - HoldAngle if ( pParent ) then GhostEntity:SetParent( pParent ) end // If we're a ragdoll send our bone positions if ( EntTable.Class == "prop_ragdoll" ) then for k, v in pairs( EntTable.PhysicsObjects ) do local lPos = v.Pos // The physics object positions are stored relative to the head entity if ( pParent ) then lPos = pParent:LocalToWorld( v.LocalPos ) lPos = GhostEntity:WorldToLocal( v.Pos ) else lPos = lPos + HoldPos end GhostEntity:SetNetworkedBonePosition( k, lPos, v.LocalAngle ) end end return GhostEntity end /*--------------------------------------------------------- Starts up the ghost entities ---------------------------------------------------------*/ function TOOL:Deploy() if ( CLIENT ) then return end local DupeTable = self:GetOwner():UniqueIDTable( "Duplicator" ) if ( DupeTable.Entities ) then self:StartGhostEntities( DupeTable.Entities, DupeTable.HeadEntityIdx, DupeTable.HoldPos, DupeTable.HoldAngle ) end end /*--------------------------------------------------------- Starts up the ghost entities ---------------------------------------------------------*/ function TOOL:StartGhostEntities( EntityTable, Head, HoldPos, HoldAngle ) self:ReleaseGhostEntity() self.GhostEntities = {} // Make the head entity first self.GhostEntities[ Head ] = self:MakeGhostFromTable( EntityTable[ Head ], self.GhostEntities[ Head ], HoldPos, HoldAngle ) // Set NW vars for clientside self.Weapon:SetNetworkedEntity( "GhostEntity", self.GhostEntities[ Head ] ) self.Weapon:SetNetworkedVector( "HeadPos", self.GhostEntities[ Head ].Pos ) self.Weapon:SetNetworkedAngle( "HeadAngle", self.GhostEntities[ Head ].Angle ) self.Weapon:SetNetworkedVector( "HoldPos", HoldPos ) if ( !self.GhostEntities[ Head ] || !self.GhostEntities[ Head ]:IsValid() ) then self.GhostEntities = nil return end for k, entTable in pairs( EntityTable ) do if ( !self.GhostEntities[ k ] ) then self.GhostEntities[ k ] = self:MakeGhostFromTable( entTable, self.GhostEntities[ Head ], HoldPos, HoldAngle ) end end end /*--------------------------------------------------------- Update the ghost entity positions ---------------------------------------------------------*/ function TOOL:UpdateGhostEntities() if (SERVER && !self.GhostEntities) then return end if ( CLIENT && SinglePlayer() ) then return end local Owner = self:GetOwner() local tr = utilx.GetPlayerTrace( Owner, Owner:GetCursorAimVector() ) local trace = util.TraceLine( tr ) if (!trace.Hit) then return end local GhostEnt = nil local HoldPos = nil if ( SERVER ) then local DupeTable = Owner:UniqueIDTable( "Duplicator" ) GhostEnt = self.GhostEntities[ DupeTable.HeadEntityIdx ] HoldPos = DupeTable.HoldPos else GhostEnt = self.Weapon:GetNetworkedEntity( "GhostEntity", nil ) GhostEnt.Pos = self.Weapon:GetNetworkedVector( "HeadPos", Vector(0,0,0) ) GhostEnt.Angle = self.Weapon:GetNetworkedAngle( "HeadAngle", Angle(0,0,0) ) HoldPos = self.Weapon:GetNetworkedVector( "HoldPos", Vector(0,0,0) ) end if (!GhostEnt || !GhostEnt:IsValid()) then self.GhostEntities = nil return end GhostEnt:SetMoveType( MOVETYPE_VPHYSICS ) GhostEnt:SetNotSolid( true ) local angle = self:GetOwner():GetAngles() angle.pitch = 0 angle.roll = 0 local TargetPos = GhostEnt:GetPos() - GhostEnt:LocalToWorld( HoldPos ) local PhysObj = GhostEnt:GetPhysicsObject() if ( PhysObj && PhysObj:IsValid() ) then PhysObj:EnableMotion( false ) PhysObj:SetPos( TargetPos + trace.HitPos ) PhysObj:SetAngle( GhostEnt.Angle + angle ) 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 if (SERVER) then local function CC_Store( pl, command, arguments ) local tool = pl:GetTool( "duplicator" ) if (!tool) then Msg("No Tool!\n") return end tool:StoreCurrent( arguments[1] ) end concommand.Add( "tool_duplicator_store", CC_Store ) local function CC_Select( pl, command, arguments ) local tool = pl:GetTool( "duplicator" ) if (!tool) then return end tool:Select( math.Clamp( tonumber(arguments[1]), 0, 1000 ) ) end concommand.Add( "tool_duplicator_select", CC_Select ) local function CC_Remove( pl, command, arguments ) local tool = pl:GetTool( "duplicator" ) if (!tool) then return end tool:Remove( math.Clamp( tonumber(arguments[1]), 0, 1000 ) ) end concommand.Add( "tool_duplicator_remove", CC_Remove ) /*--------------------------------------------------------- Store the current duplication under name ---------------------------------------------------------*/ function TOOL:StoreCurrent( name ) // Don't store the same copy more than once if ( self.Stored ) then return end self.Stored = true local Owner = self:GetOwner() local DupeTable = Owner:UniqueIDTable( "Duplicator" ) if ( name == nil || name == "" ) then name = "Rubbish #" .. ( #Owner:UniqueIDTable( "StoredDuplications" ) + 1 ) end local SaveTab = {} SaveTab.Name = name SaveTab.Entities = DupeTable.Entities SaveTab.Constraints = DupeTable.Constraints SaveTab.HeadEntityIdx = DupeTable.HeadEntityIdx SaveTab.HoldAngle = DupeTable.HoldAngle * 1 SaveTab.HoldPos = DupeTable.HoldPos * 1 table.insert( Owner:UniqueIDTable( "StoredDuplications" ), SaveTab ) self:RebuildStoredDuplications() end /*--------------------------------------------------------- Select stored duplication ---------------------------------------------------------*/ function TOOL:Select( id ) local Owner = self:GetOwner() local DupeTable = Owner:UniqueIDTable( "Duplicator" ) local Dupe = Owner:UniqueIDTable( "StoredDuplications" )[ id ] if (!Dupe) then return end // Release the old entities self:ReleaseGhostEntity() // Store stuff for pasting/ghosting DupeTable.HeadEntityIdx = Dupe.HeadEntityIdx DupeTable.HoldAngle = Dupe.HoldAngle * 1 DupeTable.HoldPos = Dupe.HoldPos * 1 DupeTable.Entities = Dupe.Entities DupeTable.Constraints = Dupe.Constraints // Start the entities! self:StartGhostEntities( DupeTable.Entities, DupeTable.HeadEntityIdx, DupeTable.HoldPos, DupeTable.HoldAngle ) end /*--------------------------------------------------------- Select stored duplication ---------------------------------------------------------*/ function TOOL:Remove( id ) local Owner = self:GetOwner() if ( Owner:UniqueIDTable( "StoredDuplications" )[ id ] == nil ) then return end Owner:UniqueIDTable( "StoredDuplications" )[ id ] = nil self:RebuildStoredDuplications() end /*--------------------------------------------------------- Send the stored duplication to user ---------------------------------------------------------*/ function TOOL:RebuildStoredDuplications() local Owner = self:GetOwner() local Saved = Owner:UniqueIDTable( "StoredDuplications" ) if (!Saved) then return end // Clear the list umsg.Start( "Duplications", Owner ) umsg.Short( 0 ) umsg.End() // Send each item to the client for k, v in pairs( Saved ) do umsg.Start( "Duplications", Owner ) umsg.Short( k ) umsg.String( v.Name ) umsg.End() end umsg.Start( "Duplications", Owner ) umsg.Short( 1000 ) umsg.End() end end if (CLIENT) then local DuplicatorControl = nil local pnldef_Duplicator = vgui.RegisterFile( "vgui/duplicator_gui.lua" ) /*--------------------------------------------------------- Builds the context menu ---------------------------------------------------------*/ function TOOL.BuildCPanel( CPanel ) CPanel:AddControl( "Header", { Text = "#Tool_duplicator_name", Description = "#Tool_duplicator_desc" } ) DuplicatorControl = vgui.CreateFromTable( pnldef_Duplicator ) CPanel:AddPanel( DuplicatorControl ) end /*--------------------------------------------------------- Receives the usermessages ---------------------------------------------------------*/ local function RecvFunc( m ) local id = m:ReadShort() if ( id == 0 ) then DuplicatorControl:Clear() return end if ( id == 1000 ) then DuplicatorControl:Populate() return end DuplicatorControl:Add( id, m:ReadString() ) end usermessage.Hook( "Duplications", RecvFunc ) end