mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
1035 lines
25 KiB
Lua
1035 lines
25 KiB
Lua
--[[
|
|
| 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/
|
|
--]]
|
|
|
|
|
|
|
|
--[[---------------------------------------------------------
|
|
Duplicator module,
|
|
to add new constraints or entity classes use...
|
|
|
|
duplicator.RegisterConstraint( "name", funct, ... )
|
|
duplicator.RegisterEntityClass( "class", funct, ... )
|
|
|
|
-----------------------------------------------------------]]
|
|
|
|
module( "duplicator", package.seeall )
|
|
|
|
--
|
|
-- When saving or loading all coordinates are saved relative to these
|
|
--
|
|
local LocalPos = Vector( 0, 0, 0 )
|
|
local LocalAng = Angle( 0, 0, 0 )
|
|
|
|
--
|
|
-- Should be set to the player that is creating/copying stuff. Can be nil.
|
|
--
|
|
local ActionPlayer = nil
|
|
|
|
--
|
|
-- The physics object Saver/Loader
|
|
--
|
|
local PhysicsObject =
|
|
{
|
|
Save = function( data, phys )
|
|
|
|
data.Pos = phys:GetPos()
|
|
data.Angle = phys:GetAngles()
|
|
data.Frozen = !phys:IsMoveable()
|
|
if ( !phys:IsGravityEnabled() ) then data.NoGrav = true end
|
|
if ( phys:IsAsleep() ) then data.Sleep = true end
|
|
|
|
data.Pos, data.Angle = WorldToLocal( data.Pos, data.Angle, LocalPos, LocalAng )
|
|
|
|
end,
|
|
|
|
Load = function( data, phys )
|
|
|
|
if ( isvector( data.Pos ) and isangle( data.Angle ) ) then
|
|
|
|
local pos, ang = LocalToWorld( data.Pos, data.Angle, LocalPos, LocalAng )
|
|
phys:SetPos( pos )
|
|
phys:SetAngles( ang )
|
|
|
|
end
|
|
|
|
-- Let's not Wake or put anything to sleep for now
|
|
--[[
|
|
if ( data.Sleep ) then
|
|
if ( IsValid( phys ) ) then phys:Sleep() end
|
|
else
|
|
phys:Wake()
|
|
end
|
|
]]
|
|
|
|
if ( data.Frozen ) then
|
|
|
|
phys:EnableMotion( false )
|
|
|
|
-- If we're being created by a player then add these to their frozen list so they can unfreeze them all
|
|
if ( IsValid( ActionPlayer ) ) then
|
|
ActionPlayer:AddFrozenPhysicsObject( phys:GetEntity(), phys )
|
|
end
|
|
|
|
end
|
|
|
|
if ( data.NoGrav ) then phys:EnableGravity( false ) end
|
|
|
|
end,
|
|
}
|
|
|
|
--
|
|
-- Entity physics saver
|
|
--
|
|
local EntityPhysics =
|
|
{
|
|
--
|
|
-- Loop each bone, calling PhysicsObject.Save
|
|
--
|
|
Save = function( data, Entity )
|
|
|
|
local num = Entity:GetPhysicsObjectCount()
|
|
|
|
for objectid = 0, num-1 do
|
|
|
|
local obj = Entity:GetPhysicsObjectNum( objectid )
|
|
if ( !IsValid( obj ) ) then continue end
|
|
|
|
data[ objectid ] = {}
|
|
PhysicsObject.Save( data[ objectid ], obj )
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
--
|
|
-- Loop each bone, calling PhysicsObject.Load
|
|
--
|
|
Load = function( data, Entity )
|
|
|
|
if ( !istable( data ) ) then return end
|
|
|
|
for objectid, objectdata in pairs( data ) do
|
|
|
|
local Phys = Entity:GetPhysicsObjectNum( objectid )
|
|
if ( !IsValid( Phys ) ) then continue end
|
|
|
|
PhysicsObject.Load( objectdata, Phys )
|
|
|
|
end
|
|
|
|
end,
|
|
}
|
|
|
|
--
|
|
-- Entity saver
|
|
--
|
|
local EntitySaver =
|
|
{
|
|
--
|
|
-- Called on each entity when saving
|
|
--
|
|
Save = function( data, ent )
|
|
|
|
--
|
|
-- Merge the entities actual table with the table we're saving
|
|
-- this is terrible behaviour - but it's what we've always done.
|
|
--
|
|
if ( ent.PreEntityCopy ) then ent:PreEntityCopy() end
|
|
table.Merge( data, ent:GetTable() )
|
|
if ( ent.PostEntityCopy ) then ent:PostEntityCopy() end
|
|
|
|
--
|
|
-- Set so me generic variables that pretty much all entities
|
|
-- would like to save.
|
|
--
|
|
data.Pos = ent:GetPos()
|
|
data.Angle = ent:GetAngles()
|
|
data.Class = ent:GetClass()
|
|
data.Model = ent:GetModel()
|
|
data.Skin = ent:GetSkin()
|
|
data.Mins, data.Maxs = ent:GetCollisionBounds()
|
|
data.ColGroup = ent:GetCollisionGroup()
|
|
data.Name = ent:GetName()
|
|
data.WorkshopID = ent:GetWorkshopID()
|
|
data.CurHealth = ent:Health()
|
|
data.MaxHealth = ent:GetMaxHealth()
|
|
data.Persistent = ent:GetPersistent()
|
|
|
|
data.Pos, data.Angle = WorldToLocal( data.Pos, data.Angle, LocalPos, LocalAng )
|
|
|
|
data.ModelScale = ent:GetModelScale()
|
|
if ( data.ModelScale == 1 ) then data.ModelScale = nil end
|
|
|
|
-- This is useful for addons to determine if the entity was map spawned or not
|
|
if ( ent:CreatedByMap() ) then
|
|
data.MapCreationID = ent:MapCreationID()
|
|
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)
|
|
if ( ent.ClassOverride ) then data.Class = ent.ClassOverride end
|
|
|
|
-- Save the physics
|
|
data.PhysicsObjects = data.PhysicsObjects or {}
|
|
EntityPhysics.Save( data.PhysicsObjects, ent )
|
|
|
|
|
|
-- Flexes
|
|
data.FlexScale = ent:GetFlexScale()
|
|
for i = 0, ent:GetFlexNum() do
|
|
|
|
local w = ent:GetFlexWeight( i )
|
|
if ( w != 0 ) then
|
|
data.Flex = data.Flex or {}
|
|
data.Flex[ i ] = w
|
|
end
|
|
|
|
end
|
|
|
|
-- Body Groups
|
|
local bg = ent:GetBodyGroups()
|
|
if ( bg ) then
|
|
|
|
for k, v in pairs( bg ) do
|
|
|
|
--
|
|
-- If it has a non default setting, save it.
|
|
--
|
|
if ( ent:GetBodygroup( v.id ) > 0 ) then
|
|
|
|
data.BodyG = data.BodyG or {}
|
|
data.BodyG[ v.id ] = ent:GetBodygroup( v.id )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- Non Sandbox tool set color and materials
|
|
if ( ent:GetColor() != color_white ) then data._DuplicatedColor = ent:GetColor() end
|
|
if ( ent:GetMaterial() != "" ) then data._DuplicatedMaterial = ent:GetMaterial() end
|
|
|
|
-- Sub materials
|
|
local subMaterials = {}
|
|
|
|
for i = 0, 31 do
|
|
|
|
local mat = ent:GetSubMaterial( i )
|
|
|
|
if ( mat:len() > 0 ) then
|
|
subMaterials[ i ] = mat
|
|
end
|
|
|
|
end
|
|
|
|
if ( !table.IsEmpty( subMaterials ) ) then data._DuplicatedSubMaterials = subMaterials end
|
|
|
|
-- Bone Manipulations
|
|
if ( ent:HasBoneManipulations() ) then
|
|
|
|
data.BoneManip = {}
|
|
|
|
for i = 0, ent:GetBoneCount() do
|
|
|
|
local t = {}
|
|
|
|
local s = ent:GetManipulateBoneScale( i )
|
|
local a = ent:GetManipulateBoneAngles( i )
|
|
local p = ent:GetManipulateBonePosition( i )
|
|
|
|
if ( s != Vector( 1, 1, 1 ) ) then t[ 's' ] = s end -- scale
|
|
if ( a != angle_zero ) then t[ 'a' ] = a end -- angle
|
|
if ( p != vector_origin ) then t[ 'p' ] = p end -- position
|
|
|
|
if ( !table.IsEmpty( t ) ) then
|
|
data.BoneManip[ i ] = t
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
--
|
|
-- Store networks vars/DT vars (assigned using SetupDataTables)
|
|
--
|
|
if ( ent.GetNetworkVars ) then
|
|
data.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
|
|
ent:OnEntityCopyTableFinish( data )
|
|
end
|
|
|
|
--
|
|
-- Exclude this crap
|
|
--
|
|
for k, v in pairs( data ) do
|
|
|
|
if ( isfunction( v ) ) then
|
|
data[k] = nil
|
|
end
|
|
|
|
end
|
|
|
|
data.OnDieFunctions = nil
|
|
data.AutomaticFrameAdvance = nil
|
|
data.BaseClass = nil
|
|
|
|
end,
|
|
|
|
--
|
|
-- Fill in the data!
|
|
--
|
|
Load = function( data, ent )
|
|
|
|
if ( !data ) then return end
|
|
|
|
-- We do the second check for models because apparently setting the model on an NPC causes some position changes
|
|
-- And to prevent NPCs going into T-pose briefly upon duplicating
|
|
if ( data.Model and data.Model != ent:GetModel() ) then ent:SetModel( data.Model ) end
|
|
if ( data.Angle ) then ent:SetAngles( data.Angle ) end
|
|
if ( data.Pos ) then ent:SetPos( data.Pos ) end
|
|
if ( data.Skin ) then ent:SetSkin( data.Skin ) end
|
|
if ( data.Flex ) then DoFlex( ent, data.Flex, data.FlexScale ) end
|
|
if ( data.BoneManip ) then DoBoneManipulator( ent, data.BoneManip ) end
|
|
if ( data.ModelScale ) then ent:SetModelScale( data.ModelScale, 0 ) end
|
|
if ( data.ColGroup ) then ent:SetCollisionGroup( data.ColGroup ) end
|
|
if ( data.Name ) then ent:SetName( data.Name ) end
|
|
if ( data.Persistent ) then ent:SetPersistent( data.Persistent ) end
|
|
if ( data._DuplicatedColor ) then ent:SetColor( data._DuplicatedColor ) end
|
|
if ( data._DuplicatedMaterial ) then ent:SetMaterial( data._DuplicatedMaterial ) end
|
|
|
|
-- Sub materials
|
|
if ( data._DuplicatedSubMaterials ) then
|
|
|
|
for id, mat in pairs( data._DuplicatedSubMaterials ) do
|
|
|
|
ent:SetSubMaterial( id, mat )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- Body Groups
|
|
if ( data.BodyG ) then
|
|
for k, v in pairs( data.BodyG ) do
|
|
ent:SetBodygroup( k, v )
|
|
end
|
|
end
|
|
|
|
--
|
|
-- Restore NetworkVars/DataTable variables (the SetupDataTables values)
|
|
--
|
|
if ( ent.RestoreNetworkVars ) then
|
|
ent:RestoreNetworkVars( data.DT )
|
|
end
|
|
|
|
end,
|
|
}
|
|
|
|
local DuplicateAllowed = {}
|
|
|
|
--
|
|
-- Allow this entity to be duplicated
|
|
--
|
|
function Allow( classname )
|
|
|
|
DuplicateAllowed[ classname ] = true
|
|
|
|
end
|
|
|
|
--
|
|
-- Disallow this entity to be duplicated
|
|
--
|
|
function Disallow( classname )
|
|
|
|
DuplicateAllowed[ classname ] = false
|
|
|
|
end
|
|
|
|
--
|
|
-- Returns true if we can copy/paste this entity
|
|
--
|
|
function IsAllowed( classname )
|
|
|
|
return DuplicateAllowed[ classname ]
|
|
|
|
end
|
|
|
|
ConstraintType = ConstraintType or {}
|
|
|
|
--
|
|
-- When a copy is copied it will be translated according to these
|
|
-- If you set them - make sure to set them back to 0 0 0!
|
|
--
|
|
function SetLocalPos( v ) LocalPos = v * 1 end
|
|
function SetLocalAng( v ) LocalAng = v * 1 end
|
|
|
|
--[[---------------------------------------------------------
|
|
Register a constraint to be duplicated
|
|
-----------------------------------------------------------]]
|
|
function RegisterConstraint( _name_, _function_, ... )
|
|
|
|
ConstraintType[ _name_ ] = {}
|
|
|
|
ConstraintType[ _name_ ].Func = _function_
|
|
ConstraintType[ _name_ ].Args = { ... }
|
|
|
|
end
|
|
|
|
EntityClasses = EntityClasses or {}
|
|
|
|
--[[---------------------------------------------------------
|
|
Register an entity's class, to allow it to be duplicated
|
|
-----------------------------------------------------------]]
|
|
function RegisterEntityClass( _name_, _function_, ... )
|
|
|
|
EntityClasses[ _name_ ] = {}
|
|
|
|
EntityClasses[ _name_ ].Func = _function_
|
|
EntityClasses[ _name_ ].Args = {...}
|
|
|
|
Allow( _name_ )
|
|
|
|
end
|
|
|
|
--[[---------------------------------------------------------
|
|
Returns an entity class factory
|
|
-----------------------------------------------------------]]
|
|
function FindEntityClass( _name_ )
|
|
|
|
if ( !_name_ ) then return end
|
|
return EntityClasses[ _name_ ]
|
|
|
|
end
|
|
|
|
BoneModifiers = BoneModifiers or {}
|
|
EntityModifiers = EntityModifiers or {}
|
|
|
|
function RegisterBoneModifier( _name_, _function_ ) BoneModifiers[ _name_ ] = _function_ end
|
|
function RegisterEntityModifier( _name_, _function_ ) EntityModifiers[ _name_ ] = _function_ end
|
|
|
|
--
|
|
-- Try to work out which workshop addons are used by this dupe. This is far from perfect.
|
|
--
|
|
function FigureOutRequiredAddons( Dupe )
|
|
|
|
local addons = {}
|
|
for _, ent in pairs( Dupe.Entities ) do
|
|
for id, addon in pairs( engine.GetAddons() ) do
|
|
-- Model
|
|
if ( ent.Model and file.Exists( ent.Model, addon.title ) ) then
|
|
addons[ addon.wsid ] = true
|
|
end
|
|
|
|
-- Material override
|
|
if ( ent._DuplicatedMaterial and file.Exists( "materials/" .. ent._DuplicatedMaterial .. ".vmt", addon.title ) ) then
|
|
addons[ addon.wsid ] = true
|
|
end
|
|
end
|
|
end
|
|
Dupe.RequiredAddons = table.GetKeys( addons )
|
|
|
|
end
|
|
|
|
if ( CLIENT ) then return end
|
|
|
|
--[[---------------------------------------------------------
|
|
Restore's the flex data
|
|
-----------------------------------------------------------]]
|
|
function DoFlex( ent, Flex, Scale )
|
|
|
|
if ( !Flex ) then return end
|
|
if ( !IsValid( ent ) ) then return end
|
|
|
|
for k, v in pairs( Flex ) do
|
|
ent:SetFlexWeight( k, v )
|
|
end
|
|
|
|
if ( Scale ) then
|
|
ent:SetFlexScale( Scale )
|
|
end
|
|
|
|
end
|
|
|
|
--[[---------------------------------------------------------
|
|
Restore's the bone's data
|
|
-----------------------------------------------------------]]
|
|
function DoBoneManipulator( ent, Bones )
|
|
|
|
if ( !Bones ) then return end
|
|
if ( !IsValid( ent ) ) then return end
|
|
|
|
for k, v in pairs( Bones ) do
|
|
|
|
if ( v.s ) then ent:ManipulateBoneScale( k, v.s ) end
|
|
if ( v.a ) then ent:ManipulateBoneAngles( k, v.a ) end
|
|
if ( v.p ) then ent:ManipulateBonePosition( k, v.p ) end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
--[[---------------------------------------------------------
|
|
Generic function for duplicating stuff
|
|
-----------------------------------------------------------]]
|
|
function GenericDuplicatorFunction( Player, data )
|
|
|
|
if ( !IsAllowed( data.Class ) ) then
|
|
-- MsgN( "duplicator: ", data.Class, " isn't allowed to be duplicated!" )
|
|
return
|
|
end
|
|
|
|
--
|
|
-- Is this entity 'admin only'?
|
|
--
|
|
if ( IsValid( Player ) and !Player:IsAdmin() ) then
|
|
|
|
if ( !scripted_ents.GetMember( data.Class, "Spawnable" ) ) then return end
|
|
if ( scripted_ents.GetMember( data.Class, "AdminOnly" ) ) then return end
|
|
|
|
end
|
|
|
|
local Entity = ents.Create( data.Class )
|
|
if ( !IsValid( Entity ) ) then return end
|
|
|
|
-- TODO: Entity not found - maybe spawn a prop_physics with their model?
|
|
|
|
DoGeneric( Entity, data )
|
|
|
|
Entity:Spawn()
|
|
Entity:Activate()
|
|
|
|
EntityPhysics.Load( data.PhysicsObjects, Entity )
|
|
|
|
table.Merge( Entity:GetTable(), data )
|
|
|
|
return Entity
|
|
|
|
end
|
|
|
|
--[[---------------------------------------------------------
|
|
Automates the process of adding crap the EntityMods table
|
|
-----------------------------------------------------------]]
|
|
function StoreEntityModifier( Entity, Type, Data )
|
|
|
|
if ( !IsValid( Entity ) ) then return end
|
|
|
|
Entity.EntityMods = Entity.EntityMods or {}
|
|
|
|
-- Copy the data
|
|
local NewData = Entity.EntityMods[ Type ] or {}
|
|
table.Merge( NewData, Data )
|
|
|
|
Entity.EntityMods[ Type ] = NewData
|
|
|
|
end
|
|
|
|
--[[---------------------------------------------------------
|
|
Clear entity modification
|
|
-----------------------------------------------------------]]
|
|
function ClearEntityModifier( Entity, Type )
|
|
|
|
if ( !IsValid( Entity ) ) then return end
|
|
|
|
Entity.EntityMods = Entity.EntityMods or {}
|
|
Entity.EntityMods[ Type ] = nil
|
|
|
|
end
|
|
|
|
--[[---------------------------------------------------------
|
|
Automates the process of adding crap the BoneMods table
|
|
-----------------------------------------------------------]]
|
|
function StoreBoneModifier( Entity, BoneID, Type, Data )
|
|
|
|
if ( !IsValid( Entity ) ) then return end
|
|
|
|
-- Copy the data
|
|
NewData = {}
|
|
table.Merge( NewData, Data )
|
|
|
|
-- Add it to the entity
|
|
Entity.BoneMods = Entity.BoneMods or {}
|
|
Entity.BoneMods[ BoneID ] = Entity.BoneMods[ BoneID ] or {}
|
|
|
|
Entity.BoneMods[ BoneID ][ Type ] = NewData
|
|
|
|
end
|
|
|
|
--[[---------------------------------------------------------
|
|
Returns a copy of the passed entity's table
|
|
-----------------------------------------------------------]]
|
|
function CopyEntTable( Ent )
|
|
|
|
local output = {}
|
|
EntitySaver.Save( output, Ent )
|
|
return output
|
|
|
|
end
|
|
|
|
--
|
|
-- Work out the AABB size
|
|
--
|
|
function WorkoutSize( Ents )
|
|
|
|
local mins = Vector( -1, -1, -1 )
|
|
local maxs = Vector( 1, 1, 1 )
|
|
|
|
|
|
for k, v in pairs( Ents ) do
|
|
|
|
if ( !v.Mins or !v.Maxs ) then continue end
|
|
if ( !v.Angle or !v.Pos ) then continue end
|
|
|
|
--
|
|
-- Rotate according to the entity!
|
|
--
|
|
local mi = v.Mins
|
|
local ma = v.Maxs
|
|
|
|
-- There has to be a better way
|
|
local t1 = LocalToWorld( Vector( mi.x, mi.y, mi.z ), Angle( 0, 0, 0 ), v.Pos, v.Angle )
|
|
local t2 = LocalToWorld( Vector( ma.x, mi.y, mi.z ), Angle( 0, 0, 0 ), v.Pos, v.Angle )
|
|
local t3 = LocalToWorld( Vector( mi.x, ma.y, mi.z ), Angle( 0, 0, 0 ), v.Pos, v.Angle )
|
|
local t4 = LocalToWorld( Vector( ma.x, ma.y, mi.z ), Angle( 0, 0, 0 ), v.Pos, v.Angle )
|
|
|
|
local b1 = LocalToWorld( Vector( mi.x, mi.y, ma.z ), Angle( 0, 0, 0 ), v.Pos, v.Angle )
|
|
local b2 = LocalToWorld( Vector( ma.x, mi.y, ma.z ), Angle( 0, 0, 0 ), v.Pos, v.Angle )
|
|
local b3 = LocalToWorld( Vector( mi.x, ma.y, ma.z ), Angle( 0, 0, 0 ), v.Pos, v.Angle )
|
|
local b4 = LocalToWorld( Vector( ma.x, ma.y, ma.z ), Angle( 0, 0, 0 ), v.Pos, v.Angle )
|
|
|
|
mins.x = math.min( mins.x, t1.x, t2.x, t3.x, t4.x, b1.x, b2.x, b3.x, b4.x )
|
|
mins.y = math.min( mins.y, t1.y, t2.y, t3.y, t4.y, b1.y, b2.y, b3.y, b4.y )
|
|
mins.z = math.min( mins.z, t1.z, t2.z, t3.z, t4.z, b1.z, b2.z, b3.z, b4.z )
|
|
|
|
maxs.x = math.max( maxs.x, t1.x, t2.x, t3.x, t4.x, b1.x, b2.x, b3.x, b4.x )
|
|
maxs.y = math.max( maxs.y, t1.y, t2.y, t3.y, t4.y, b1.y, b2.y, b3.y, b4.y )
|
|
maxs.z = math.max( maxs.z, t1.z, t2.z, t3.z, t4.z, b1.z, b2.z, b3.z, b4.z )
|
|
|
|
end
|
|
|
|
return mins, maxs
|
|
|
|
end
|
|
|
|
--[[---------------------------------------------------------
|
|
Copy this entity, and all of its constraints and entities
|
|
and put them in a table.
|
|
-----------------------------------------------------------]]
|
|
function Copy( Ent, AddToTable )
|
|
|
|
local Ents = {}
|
|
local Constraints = {}
|
|
|
|
GetAllConstrainedEntitiesAndConstraints( Ent, Ents, Constraints )
|
|
|
|
local EntTables = {}
|
|
if ( AddToTable != nil ) then EntTables = AddToTable.Entities or {} end
|
|
|
|
for k, v in pairs( Ents ) do
|
|
EntTables[ k ] = CopyEntTable( v )
|
|
end
|
|
|
|
local ConstraintTables = {}
|
|
if ( AddToTable != nil ) then ConstraintTables = AddToTable.Constraints or {} end
|
|
|
|
for k, v in pairs( Constraints ) do
|
|
ConstraintTables[ k ] = v
|
|
end
|
|
|
|
local mins, maxs = WorkoutSize( EntTables )
|
|
|
|
return {
|
|
Entities = EntTables,
|
|
Constraints = ConstraintTables,
|
|
Mins = mins,
|
|
Maxs = maxs
|
|
}
|
|
|
|
end
|
|
|
|
function CopyEnts( Ents )
|
|
|
|
local Ret = { Entities = {}, Constraints = {} }
|
|
|
|
for k, v in pairs( Ents ) do
|
|
|
|
Ret = Copy( v, Ret )
|
|
|
|
end
|
|
|
|
return Ret
|
|
|
|
end
|
|
|
|
--[[---------------------------------------------------------
|
|
Create an entity from a table.
|
|
-----------------------------------------------------------]]
|
|
function CreateEntityFromTable( Player, EntTable )
|
|
|
|
-- Get rid of stored outputs, they are being abused
|
|
-- Do it here, so that entities can store new ones on creation
|
|
EntTable.m_tOutputs = nil
|
|
|
|
--
|
|
-- Convert position/angle to `local`
|
|
--
|
|
if ( EntTable.Pos and EntTable.Angle ) then
|
|
|
|
EntTable.Pos, EntTable.Angle = LocalToWorld( EntTable.Pos, EntTable.Angle, LocalPos, LocalAng )
|
|
|
|
end
|
|
|
|
local EntityClass = FindEntityClass( EntTable.Class )
|
|
|
|
-- This class is unregistered. Instead of failing try using a generic
|
|
-- Duplication function to make a new copy..
|
|
if ( !EntityClass ) then
|
|
|
|
return GenericDuplicatorFunction( Player, EntTable )
|
|
|
|
end
|
|
|
|
-- Build the argument list
|
|
local ArgList = {}
|
|
|
|
for iNumber, Key in pairs( EntityClass.Args ) do
|
|
|
|
local Arg = nil
|
|
|
|
-- 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 so send it as `false`
|
|
if ( Arg == nil ) then Arg = false end
|
|
|
|
ArgList[ iNumber ] = Arg
|
|
|
|
end
|
|
|
|
-- Create and return the entity
|
|
return EntityClass.Func( Player, unpack( ArgList ) )
|
|
|
|
end
|
|
|
|
|
|
--[[---------------------------------------------------------
|
|
Make a constraint from a constraint table
|
|
-----------------------------------------------------------]]
|
|
function CreateConstraintFromTable( Constraint, EntityList, Player )
|
|
|
|
local Factory = ConstraintType[ Constraint.Type ]
|
|
if ( !Factory ) then return end
|
|
|
|
local Args = {}
|
|
for k, Key in pairs( Factory.Args ) do
|
|
|
|
local Val = Constraint[ Key ]
|
|
|
|
for i = 1, 6 do
|
|
|
|
if ( Constraint.Entity[ i ] ) then
|
|
|
|
if ( Key == "Ent" .. i ) then
|
|
Val = EntityList[ Constraint.Entity[ i ].Index ]
|
|
if ( Constraint.Entity[ i ].World ) then
|
|
Val = game.GetWorld()
|
|
end
|
|
end
|
|
|
|
if ( Key == "Bone" .. i ) then Val = Constraint.Entity[ i ].Bone or 0 end
|
|
if ( Key == "LPos" .. i ) then Val = Constraint.Entity[ i ].LPos end
|
|
if ( Key == "WPos" .. i ) then Val = Constraint.Entity[ i ].WPos end
|
|
if ( Key == "Length" .. i ) then Val = Constraint.Entity[ i ].Length or 0 end
|
|
|
|
end
|
|
end
|
|
|
|
-- A little hack to give the duped constraints the correct player object
|
|
if ( Key:lower() == "pl" or Key:lower() == "ply" or Key:lower() == "player" ) then Val = Player end
|
|
|
|
-- If there's a missing argument then unpack will stop sending at that argument
|
|
if ( Val == nil ) then Val = false end
|
|
|
|
table.insert( Args, Val )
|
|
|
|
end
|
|
|
|
return Factory.Func( unpack( Args ) )
|
|
|
|
end
|
|
|
|
--[[---------------------------------------------------------
|
|
Given entity list and constranit list, create all entities
|
|
and return their tables
|
|
-----------------------------------------------------------]]
|
|
function Paste( Player, entityList, constraintList )
|
|
|
|
--
|
|
-- Store the player
|
|
--
|
|
local oldplayer = ActionPlayer
|
|
ActionPlayer = Player
|
|
|
|
--
|
|
-- Copy the table - because we're gonna be changing some stuff on it.
|
|
--
|
|
local EntityList = table.Copy( entityList )
|
|
local ConstraintList = table.Copy( constraintList )
|
|
|
|
local CreatedEntities = {}
|
|
|
|
--
|
|
-- Create the Entities
|
|
--
|
|
for k, v in pairs( EntityList ) do
|
|
|
|
local e = nil
|
|
local b = ProtectedCall( function() e = CreateEntityFromTable( Player, v ) end )
|
|
if ( !b ) then continue end
|
|
|
|
if ( IsValid( e ) ) then
|
|
|
|
--
|
|
-- Call this here ( as well as before :Spawn) because Spawn/Init might have stomped the values
|
|
--
|
|
if ( e.RestoreNetworkVars ) then
|
|
e:RestoreNetworkVars( v.DT )
|
|
end
|
|
|
|
if ( e.OnDuplicated ) then
|
|
e:OnDuplicated( v )
|
|
end
|
|
|
|
end
|
|
|
|
CreatedEntities[ k ] = e
|
|
|
|
if ( CreatedEntities[ k ] ) then
|
|
|
|
CreatedEntities[ k ].BoneMods = table.Copy( v.BoneMods )
|
|
CreatedEntities[ k ].EntityMods = table.Copy( v.EntityMods )
|
|
CreatedEntities[ k ].PhysicsObjects = table.Copy( v.PhysicsObjects )
|
|
|
|
else
|
|
|
|
CreatedEntities[ k ] = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
--
|
|
-- Apply modifiers to the created entities
|
|
--
|
|
for EntID, Ent in pairs( CreatedEntities ) do
|
|
|
|
ApplyEntityModifiers( Player, Ent )
|
|
ApplyBoneModifiers( Player, Ent )
|
|
|
|
if ( Ent.PostEntityPaste ) then
|
|
Ent:PostEntityPaste( Player or NULL, Ent, CreatedEntities )
|
|
end
|
|
|
|
end
|
|
|
|
local CreatedConstraints = {}
|
|
|
|
--
|
|
-- Create constraints
|
|
--
|
|
for k, Constraint in pairs( ConstraintList ) do
|
|
|
|
local Entity = nil
|
|
ProtectedCall( function() Entity = CreateConstraintFromTable( Constraint, CreatedEntities, Player ) end )
|
|
|
|
if ( IsValid( Entity ) ) then
|
|
table.insert( CreatedConstraints, Entity )
|
|
end
|
|
|
|
end
|
|
|
|
ActionPlayer = oldplayer
|
|
|
|
return CreatedEntities, CreatedConstraints
|
|
|
|
end
|
|
|
|
|
|
--[[---------------------------------------------------------
|
|
Applies entity modifiers
|
|
-----------------------------------------------------------]]
|
|
function ApplyEntityModifiers( Player, Ent )
|
|
|
|
if ( !Ent ) then return end
|
|
if ( !Ent.EntityMods ) then return end
|
|
|
|
for Type, ModFunction in pairs( EntityModifiers ) do
|
|
|
|
if ( Ent.EntityMods[ Type ] ) then
|
|
|
|
ModFunction( Player, Ent, Ent.EntityMods[ Type ] )
|
|
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
|
|
--[[---------------------------------------------------------
|
|
Applies Bone Modifiers
|
|
-----------------------------------------------------------]]
|
|
function ApplyBoneModifiers( Player, Ent )
|
|
|
|
if ( !Ent ) then return end
|
|
if ( !Ent.PhysicsObjects ) then return end
|
|
if ( !Ent.BoneMods ) then return end
|
|
|
|
--
|
|
-- Loop every Bone on the entity
|
|
--
|
|
for Bone, Types in pairs( Ent.BoneMods ) do
|
|
|
|
-- The physics object isn't valid, skip it.
|
|
if ( !Ent.PhysicsObjects[ Bone ] ) then continue end
|
|
|
|
-- Loop through each modifier on this bone
|
|
for Type, Data in pairs( Types ) do
|
|
|
|
-- Find and all the function
|
|
local ModFunction = BoneModifiers[ Type ]
|
|
if ( ModFunction ) then
|
|
ModFunction( Player, Ent, Bone, Ent:GetPhysicsObjectNum( Bone ), Data )
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
--
|
|
-- Returns all constrained Entities and constraints
|
|
-- This is kind of in the wrong place.
|
|
--
|
|
-- This function will accept the world entity to save constrains, but will not actually save the world entity itself
|
|
--
|
|
function GetAllConstrainedEntitiesAndConstraints( ent, EntTable, ConstraintTable )
|
|
|
|
if ( !IsValid( ent ) and !ent:IsWorld() ) then return end
|
|
|
|
-- Translate the class name
|
|
local classname = ent:GetClass()
|
|
if ( ent.ClassOverride ) then classname = ent.ClassOverride end
|
|
|
|
-- Is the entity in the dupe whitelist?
|
|
if ( !IsAllowed( classname ) and !ent:IsWorld() ) then
|
|
-- MsgN( "duplicator: ", classname, " isn't allowed to be duplicated!" )
|
|
return
|
|
end
|
|
|
|
-- Entity doesn't want to be duplicated.
|
|
if ( ent.DoNotDuplicate ) then return end
|
|
|
|
if ( !ent:IsWorld() ) then EntTable[ ent:EntIndex() ] = ent end
|
|
|
|
if ( !constraint.HasConstraints( ent ) ) then return end
|
|
|
|
local ConTable = constraint.GetTable( ent )
|
|
|
|
for key, constr in pairs( ConTable ) do
|
|
|
|
local index = constr.Constraint:GetCreationID()
|
|
|
|
if ( !ConstraintTable[ index ] ) then
|
|
|
|
-- Add constraint to the constraints table
|
|
ConstraintTable[ index ] = constr
|
|
|
|
-- Run the Function for any ents attached to this constraint
|
|
for _, ConstrainedEnt in pairs( constr.Entity ) do
|
|
|
|
if ( !ConstrainedEnt.Entity:IsWorld() ) then
|
|
|
|
GetAllConstrainedEntitiesAndConstraints( ConstrainedEnt.Entity, EntTable, ConstraintTable )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
return EntTable, ConstraintTable
|
|
|
|
end
|
|
|
|
|
|
--
|
|
-- Return true if this entity should be removed when RemoveMapCreatedEntities is called
|
|
-- We don't want to remove all entities.
|
|
--
|
|
local function ShouldMapEntityBeRemoved( ent, classname )
|
|
|
|
if ( classname == "prop_physics" ) then return true end
|
|
if ( classname == "prop_physics_multiplayer" ) then return true end
|
|
if ( classname == "prop_ragdoll" ) then return true end
|
|
if ( ent:IsNPC() ) then return true end
|
|
if ( IsAllowed( classname ) ) then return true end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
--
|
|
-- Help to remove certain map created entities before creating the saved entities
|
|
-- This is obviously so we don't get duplicate props everywhere. It should be called
|
|
-- before calling Paste.
|
|
--
|
|
function RemoveMapCreatedEntities()
|
|
|
|
for k, v in ipairs( ents.GetAll() ) do
|
|
|
|
if ( v:CreatedByMap() and ShouldMapEntityBeRemoved( v, v:GetClass() ) ) then
|
|
v:Remove()
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
--
|
|
-- BACKWARDS COMPATIBILITY - PHASE OUT, RENAME?
|
|
--
|
|
function DoGenericPhysics( Entity, Player, data )
|
|
|
|
if ( !data or !data.PhysicsObjects ) then return end
|
|
|
|
EntityPhysics.Load( data.PhysicsObjects, Entity )
|
|
|
|
end
|
|
|
|
function DoGeneric( ent, data )
|
|
|
|
EntitySaver.Load( data, ent )
|
|
|
|
end
|