AddCSLuaFile( "cl_init.lua" ) AddCSLuaFile( "shared.lua" ) ENT.WireDebugName = "clutch" include('shared.lua') function ENT:Initialize() self:PhysicsInit( SOLID_VPHYSICS ) self:SetMoveType( MOVETYPE_VPHYSICS ) self:SetSolid( SOLID_VPHYSICS ) self.Inputs = Wire_CreateInputs( self, { "Friction" } ) //self.Outputs = Wire_CreateOutputs( self, { "Welded" } ) self.clutch_friction = 0 self.clutch_ballsockets = {} -- Table of constraints as keys self:UpdateOverlay() end function ENT:UpdateOverlay() local text = "Clutch Controller\n" text = text .. "Friction: " .. tostring( self.clutch_friction ) .. "\n" local num_constraints = table.Count( self.clutch_ballsockets ) if num_constraints > 0 then text = text .. "Links: " .. tostring( num_constraints ) else text = text .. "Unlinked" end self:SetOverlayText( text ) end /*--------------------------------------------------------- -- Constraint functions -- Functions for handling clutch constraints ---------------------------------------------------------*/ function ENT:ClutchExists( Ent1, Ent2 ) for k, v in pairs( self.clutch_ballsockets ) do if ( Ent1 == k.Ent1 and Ent2 == k.Ent2 ) or ( Ent1 == k.Ent2 and Ent2 == k.Ent1 ) then return true end end return false end // Returns an array with each entry as a table containing Ent1, Ent2 function ENT:GetConstrainedPairs() local ConstrainedPairs = {} for k, v in pairs( self.clutch_ballsockets ) do if IsValid( k ) then table.insert( ConstrainedPairs, {Ent1 = k.Ent1, Ent2 = k.Ent2} ) else self.clutch_ballsockets[k] = nil end end return ConstrainedPairs end function NewBallSocket( Ent1, Ent2, friction ) if !IsValid( Ent1 ) then return false end local ballsocket = constraint.AdvBallsocket( Ent1, Ent2, 0, 0, Vector(0,0,0), Vector(0,0,0), 0, 0, -180, -180, -180, 180, 180, 180, friction, friction, friction, 1, 0 ) if ballsocket then // Prevent ball socket from being affected by dupe/remove functions ballsocket.Type = "" end return ballsocket end // Register a new clutch association with the controller function ENT:AddClutch( Ent1, Ent2, friction ) local ballsocket = NewBallSocket( Ent1, Ent2, friction or self.clutch_friction ) if ballsocket then self:DeleteOnRemove( ballsocket ) self.clutch_ballsockets[ballsocket] = true end self:UpdateOverlay() return ballsocket end // Remove a new clutch association from the controller function ENT:RemoveClutch( const ) self.clutch_ballsockets[const] = nil if IsValid( const ) then const:Remove() end self:UpdateOverlay() end function ENT:SetClutchFriction( const, friction ) // There seems to be no direct way to edit constraint friction, so we must create a new ball socket constraint self.clutch_ballsockets[const] = nil if IsValid( const ) then local Ent1 = const.Ent1 local Ent2 = const.Ent2 const:Remove() local newconst = NewBallSocket( Ent1, Ent2, friction ) self:DeleteOnRemove( newconst ) self.clutch_ballsockets[newconst] = true else print("Wire Clutch: Attempted to set friction on invalid constraint") end return true end /*--------------------------------------------------------- -- Main controller functions -- Handle controller tables, wire input ---------------------------------------------------------*/ // Used for setting/restoring entity mass when creating the clutch constraint local function SaveMass( MassTable, ent ) if IsValid( ent ) and !MassTable[ent] then local Phys = ent:GetPhysicsObject() if IsValid( Phys ) then MassTable[ent] = Phys:GetMass() Phys:SetMass(1) end end end local function RestoreMass( MassTable ) for k, v in pairs( MassTable ) do k:GetPhysicsObject():SetMass( v ) end end // Set friction on all constrained ents, called by input or timer (if delayed) function ENT:UpdateFriction() // Set masses to 1 - this will prevents friction from varying depending on mass local MassTable = {} // Create a table copy so when we start ammending self.clutch_ballsockets, it won't affect this loop local clutch_ballsockets = table.Copy( self.clutch_ballsockets ) // Update all registered ball socket constraints local numconstraints = 0 -- Used to calculate the delay between inputs for k, v in pairs( clutch_ballsockets ) do if !IsValid( k ) then self:RemoveClutch( k ) else SaveMass( MassTable, k.Ent1 ) SaveMass( MassTable, k.Ent2 ) self:SetClutchFriction( k, self.clutch_friction ) numconstraints = numconstraints + 1 end end RestoreMass( MassTable ) self:UpdateOverlay() return numconstraints end // Called when the clutch input delay timer finishes local function ClutchDelayEnd( ent ) ent.ClutchDelay = nil if ent.delayed_clutch_friction then ent:TriggerInput( "Friction", ent.delayed_clutch_friction ) ent.delayed_clutch_friction = nil end end function ENT:TriggerInput( iname, value ) if iname == "Friction" then if !self.ClutchDelay then self.clutch_friction = value // Create a delay to avoid server lag local numconstraints = self:UpdateFriction() local maxrate = math.max( GetConVarNumber( "wire_clutch_maxrate", 20 ), 1 ) local Delay = numconstraints / maxrate self.ClutchDelay = true timer.Create( "wire_clutch_delay_" .. tostring(self:EntIndex()), Delay, 0, ClutchDelayEnd, self ) else // This should only happen if an error prevents the ClutchDelayEnd function from being called if !timer.IsTimer( "wire_clutch_delay_" .. tostring(self:EntIndex())) then self.ClutchDelay = false end // Store new friction value so it can be updated after the delay self.delayed_clutch_friction = value end end end /*--------------------------------------------------------- -- Adv Duplicator Support -- Linked entities are stored and recalled by their EntIndexes ---------------------------------------------------------*/ function ENT:BuildDupeInfo() local info = self.BaseClass.BuildDupeInfo(self) or {} info.constrained_pairs = {} for k, v in pairs( self:GetConstrainedPairs() ) do info.constrained_pairs[k] = {} info.constrained_pairs[k].Ent1 = v.Ent1:EntIndex() or 0 info.constrained_pairs[k].Ent2 = v.Ent2:EntIndex() or 0 end return info end function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) self.BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) for k, v in pairs( info.constrained_pairs ) do Ent1 = GetEntByID(v.Ent1) or ents.GetByIndex( v.Ent1 ) if v.Ent2 == 0 then Ent2 = GetWorldEntity() else Ent2 = GetEntByID(v.Ent2) or ents.GetByIndex( v.Ent2 ) if !IsValid( Ent2 ) then Ent2 = GetWorldEntity() end end if IsValid(Ent1) and Ent1 ~= Ent2 then if CPPI then if Ent1:CPPICanTool( ply, "ballsocket_adv" ) and Ent2:CPPICanTool( ply, "ballsocket_adv" ) then self:AddClutch( Ent1, Ent2 ) end else self:AddClutch( Ent1, Ent2 ) end end end end