--[[
| 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/
--]]

TOOL.Category = "Particle Controller"
TOOL.Name = "Adv. Particle Control"
TOOL.Command = nil
TOOL.ConfigName = "" 

TOOL.HighlightedEnt = nil
 
TOOL.ClientConVar[ "effectname" ] = "env_fire_large"
TOOL.ClientConVar[ "mode_beam" ] = "0"
TOOL.beamattach1 = 0
TOOL.beamattach2 = 0
TOOL.ClientConVar[ "utileffect_scale" ] = "1"
TOOL.ClientConVar[ "utileffect_magnitude" ] = "1"
TOOL.ClientConVar[ "utileffect_radius" ] = "10"
TOOL.ClientConVar[ "color_enabled" ] = "0"
TOOL.ClientConVar[ "color_r" ] = "255"
TOOL.ClientConVar[ "color_g" ] = "20"
TOOL.ClientConVar[ "color_b" ] = "0"
TOOL.ClientConVar[ "color_outofone" ] = "0"

TOOL.ClientConVar[ "attachnum" ] = "0"
TOOL.ClientConVar[ "repeatrate" ] = "0"
TOOL.ClientConVar[ "repeatsafety" ] = "1"

TOOL.ClientConVar[ "propmodel" ] = "models/hunter/plates/plate.mdl"
TOOL.ClientConVar[ "propangle" ] = "1"
TOOL.ClientConVar[ "propinvis" ] = "0"

TOOL.ClientConVar[ "numpadkey" ] = "52"
TOOL.ClientConVar[ "toggle" ] = "1"
TOOL.ClientConVar[ "starton" ] = "1"

TOOL.Information = {
	{ name = "info1", stage = 1, icon = "gui/info.png" },
	{ name = "left0", stage = 0, icon = "gui/lmb.png" },
	{ name = "left1", stage = 1, icon = "gui/lmb.png" },
	{ name = "middle01", icon = "gui/mmb.png" },
	{ name = "right0", stage = 0, icon = "gui/rmb.png" },
	{ name = "right1", stage = 1, icon = "gui/rmb.png" },
	{ name = "reload0", stage = 0, icon = "gui/r.png" },
	{ name = "reload1", stage = 1, icon = "gui/r.png" },
}

if ( CLIENT ) then
	language.Add( "tool.particlecontrol.name", "Advanced Particle Controller" )
	language.Add( "tool.particlecontrol.desc", "Attach particle effects to things" )
	language.Add( "tool.particlecontrol.help", "Particles are used for all sorts of different special effects. You can attach them to models and turn them on and off with a key." )

	language.Add( "tool.particlecontrol.info1", "BEAM EFFECT: Attaches to two points" )
	language.Add( "tool.particlecontrol.left0", "Add an effect to an object" )
	language.Add( "tool.particlecontrol.left1", "Add the other end to an object" )
	language.Add( "tool.particlecontrol.middle01", "Scroll through an object's attachments" )
	language.Add( "tool.particlecontrol.right0", "Attach a new prop with the effect on it" )
	language.Add( "tool.particlecontrol.right1", "Attach a new prop and add the other end to it" )
	language.Add( "tool.particlecontrol.reload0", "Remove all effects from an object" )
	language.Add( "tool.particlecontrol.reload1", "Cancel beam effect" )
end

util.PrecacheSound("weapons/pistol/pistol_empty.wav")




function TOOL:LeftClick( trace )

	local effectname = self:GetClientInfo( "effectname", 0 )
	local attachnum = self:GetClientNumber( "attachnum", 0 )

	local repeatrate = self:GetClientNumber( "repeatrate", 0 )
	local repeatsafety = self:GetClientNumber( "repeatsafety", 0 )

	local numpadkey = self:GetClientNumber( "numpadkey", 0 )
	local toggle = self:GetClientNumber( "toggle", 0 )
	local starton = self:GetClientNumber( "starton", 0 )

	local utileffectinfo = Vector( self:GetClientNumber( "utileffect_scale", 0 ), self:GetClientNumber( "utileffect_magnitude", 0 ), self:GetClientNumber( "utileffect_radius", 0 ) )
	local colorinfo = nil
	if self:GetClientNumber( "color_enabled", 0 ) == 1 then
		if self:GetClientNumber( "color_outofone", 0 ) == 1 then
			colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 1 )  //we're using the alpha value to store color_outofone
		else
			colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 0 )
		end
	end

	local ply = self:GetOwner()



	if self:GetClientNumber( "mode_beam", 0 ) == 0 then
		//Not a beam, attach the effect to one entity
		if ( trace.Entity:IsValid() ) then
			if CLIENT then return true end
			if trace.Entity:GetClass() == "prop_effect" and trace.Entity.AttachedEntity then trace.Entity = trace.Entity.AttachedEntity end
			AttachParticleControllerNormal( ply, trace.Entity, { NewTable = { 
				EffectName = effectname, 
				AttachNum = attachnum, 

				RepeatRate = repeatrate, 
				RepeatSafety = repeatsafety, 

				Toggle = toggle, 
				StartOn = starton, 
				NumpadKey = numpadkey, 

				UtilEffectInfo = utileffectinfo, 
				ColorInfo = colorinfo 
			} } )
			return true
		end
	else
		//It's a beam, attach the effect between two entities
		local iNum = self:NumObjects()

		if ( trace.Entity:IsValid() ) then
			//if CLIENT then return true end
			if trace.Entity:GetClass() == "prop_effect" and trace.Entity.AttachedEntity then trace.Entity = trace.Entity.AttachedEntity end

			self:SetObject( iNum + 1, trace.Entity, trace.HitPos, nil, nil, trace.HitNormal )
			if iNum == 0 then
				self.beamattach1 = attachnum
			else
				self.beamattach2 = attachnum
			end

			if ( iNum > 0 ) then
				if ( CLIENT ) then
					self:ClearObjects()
					return true
				end
				
				local Ent1, Ent2 = self:GetEnt(1), self:GetEnt(2)
				local constraint = constraint.AttachParticleControllerBeam( Ent1, Ent2, { 
					EffectName = effectname, 
					AttachNum = self.beamattach1, 
					AttachNum2 = self.beamattach2, 

					RepeatRate = repeatrate, 
					RepeatSafety = repeatsafety, 

					Toggle = toggle, 
					StartOn = starton, 
					NumpadKey = numpadkey, 

					UtilEffectInfo = utileffectinfo, 
					ColorInfo = colorinfo 
				}, ply )	
		
				self:ClearObjects()
			else
				self:SetStage( iNum+1 )
			end

			return true
		end
	end

end




function TOOL:RightClick( trace )

	local effectname = self:GetClientInfo( "effectname", 0 )
	local attachnum  = self:GetClientNumber( "attachnum", 0 )

	local repeatrate  = self:GetClientNumber( "repeatrate", 0 )
	local repeatsafety  = self:GetClientNumber( "repeatsafety", 0 )

	local numpadkey  = self:GetClientNumber( "numpadkey", 0 )
	local toggle  = self:GetClientNumber( "toggle", 0 )
	local starton  = self:GetClientNumber( "starton", 0 )

	local utileffectinfo = Vector( self:GetClientNumber( "utileffect_scale", 0 ), self:GetClientNumber( "utileffect_magnitude", 0 ), self:GetClientNumber( "utileffect_radius", 0 ) )
	local colorinfo = nil
	if self:GetClientNumber( "color_enabled", 0 ) == 1 then
		if self:GetClientNumber( "color_outofone", 0 ) == 1 then
			colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 1 )  //we're using the alpha value to store color_outofone
		else
			colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 0 )
		end
	end

	local ply = self:GetOwner()



	local propmodel = self:GetClientInfo( "propmodel", 0 )
	local propangle = self:GetClientNumber( "propangle", 0 )
	//propangle 1: spawn upright
	//propangle 2: spawn at surface angle

	if !util.IsValidModel(propmodel) then return false end
	if !util.IsValidProp(propmodel) then return false end
	if CLIENT then return true end

	prop = ents.Create( "prop_physics" )
		prop:SetModel( propmodel )
		prop:SetPos( trace.HitPos - trace.HitNormal * prop:OBBMins().z )
		if propangle == 1 then prop:SetAngles(Angle(0,trace.HitNormal:Angle().y,0)) else prop:SetAngles(trace.HitNormal:Angle()) end
		prop:SetCollisionGroup(20) //COLLISION_GROUP_NONE, nocollide with everything except world
	prop:Spawn()

	local shouldweweld = true									    //don't weld if...
	if ( !util.IsValidPhysicsObject(prop, 0) ) then shouldweweld = false end			    //the prop doesn't have a phys object
	if ( !trace.Entity:IsValid() ) then shouldweweld = false end					    //the thing we clicked on doesn't exist/is the world
	if ( trace.Entity && trace.Entity:IsPlayer() ) then shouldweweld = false end			    //the thing we clicked on is a player
	if ( !util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) ) then shouldweweld = false end  //the thing we clicked on doesn't have a phys object
	if shouldweweld == true then
		local const = constraint.Weld( prop, trace.Entity, 0, trace.PhysicsBone, 0, true, true )
	else
		if util.IsValidPhysicsObject(prop, 0) then prop:GetPhysicsObject():EnableMotion(false) end
	end

	if self:GetClientNumber( "propinvis", 0 ) == 1 then
		prop:SetRenderMode(1)  //we need to change the render mode so the transparency actually shows up
		prop:SetColor( Color(255,255,255,0) )
		duplicator.StoreEntityModifier( prop, "colour", { Color = Color(255,255,255,0), RenderMode = 1, RenderFX = 0 } )
	end

	undo.Create( "prop" )
		undo.AddEntity( prop )
		undo.SetPlayer( ply )
	undo.Finish( "Prop ("..tostring(propmodel)..")" )



	if ( !prop:IsValid() ) then return false end
	if self:GetClientNumber( "mode_beam", 0 ) == 0 then
		//Not a beam, attach the effect to one entity.
		AttachParticleControllerNormal( ply, prop, { NewTable = { 
			EffectName = effectname, 
			AttachNum = attachnum, 

			RepeatRate = repeatrate, 
			RepeatSafety = repeatsafety, 

			Toggle = toggle, 
			StartOn = starton, 
			NumpadKey = numpadkey, 

			UtilEffectInfo = utileffectinfo, 
			ColorInfo = colorinfo 
		} } )
	else
		//It's a beam, attach the effect between two entities
		local iNum = self:NumObjects()

		self:SetObject( iNum + 1, prop, trace.HitPos, nil, nil, trace.HitNormal )
		if iNum == 0 then
			self.beamattach1 = attachnum
		else
			self.beamattach2 = attachnum
		end

		if ( iNum > 0 ) then
			if ( CLIENT ) then
				self:ClearObjects()
				return true
			end
			
			local Ent1, Ent2 = self:GetEnt(1), self:GetEnt(2)
			local constraint = constraint.AttachParticleControllerBeam( Ent1, Ent2, { 
				EffectName = effectname, 
				AttachNum = self.beamattach1, 
				AttachNum2 = self.beamattach2, 

				RepeatRate = repeatrate, 
				RepeatSafety = repeatsafety, 

				Toggle = toggle, 
				StartOn = starton, 
				NumpadKey = numpadkey, 

				UtilEffectInfo = utileffectinfo, 
				ColorInfo = colorinfo 
			}, ply )	
	
			self:ClearObjects()
		else
			self:SetStage( iNum+1 )
		end

		return true
	end

	return true

end




function TOOL:Reload( trace )

	//if we've selected something for a beam effect, then let us deselect it with reload
	if self:GetClientNumber( "mode_beam", 0 ) != 0 and self:NumObjects() > 0 then
		//self:GetEnt(1)
		self:ClearObjects()
		self:SetStage(0)
		return true
	end

	if ( trace.Entity:IsValid() ) then
		local fx = false

		if trace.Entity:GetClass() == "prop_effect" and trace.Entity.AttachedEntity then trace.Entity = trace.Entity.AttachedEntity end

		for _, asdf in pairs( ents:GetAll() ) do
			if asdf:GetClass() == "particlecontroller_normal" then 
				if asdf:GetParent() == trace.Entity then 
					if SERVER then asdf:Remove() end
					fx = true
				end
				if IsValid(asdf:GetTargetEnt2()) then
					if asdf:GetTargetEnt2() == trace.Entity or asdf:GetTargetEnt2():GetParent() == trace.Entity then 
						if SERVER then asdf:Remove() end
						fx = true
					end
				end
			end
		end
		if SERVER then
			duplicator.ClearEntityModifier( trace.Entity, "DupeParticleControllerNormal" )
			constraint.RemoveConstraints( trace.Entity, "AttachParticleControllerBeam" )
		end

		return fx
	end
	
end




if CLIENT then

	local colorborder   = Color(0,0,0,255)
	local colorselect   = Color(0,255,0,255)
	local colorunselect = Color(255,255,255,255)

	function TOOL:DrawHUD()
		local pl = LocalPlayer()
		local tr = pl:GetEyeTrace()
		local attachnum = self:GetClientNumber( "attachnum", 0 )

		local function DrawHighlightAttachments(ent)

			//If there aren't any attachments, then draw the model origin as selected and stop here:
			if !ent:GetAttachments() or !ent:GetAttachments()[1] then
				local _pos,_ang = ent:GetPos(), ent:GetAngles()
				local _pos = _pos:ToScreen()
				local textpos = {x = _pos.x+5,y = _pos.y-5}

				draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder)
				draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect)
				draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder)

				return
			end


			//Draw the unselected model origin, if applicable:
			if ent:GetAttachments()[attachnum] then
				local _pos,_ang = ent:GetPos(), ent:GetAngles()
				local _pos = _pos:ToScreen()
				local textpos = {x = _pos.x+5,y = _pos.y-5}

				draw.RoundedBox(0,_pos.x - 2,_pos.y - 2,4,4,colorborder)
				draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorunselect)
				draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorunselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,1,colorborder)
			end

			//Draw the unselected attachment points:
			for _, table in pairs(ent:GetAttachments()) do
				local _pos,_ang = ent:GetAttachment(table.id).Pos,ent:GetAttachment(table.id).Ang
				local _pos = _pos:ToScreen()
				local textpos = {x = _pos.x+5,y = _pos.y-5}

				if table.id != attachnum then
					draw.RoundedBox(0,_pos.x - 2,_pos.y - 2,4,4,colorborder)
					draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorunselect)
					draw.SimpleTextOutlined(table.id ..": ".. table.name,"Default",textpos.x,textpos.y,colorunselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,1,colorborder)
				end
			end
			
			//Draw the selected attachment point or model origin last, so it renders above all the others:
			if !ent:GetAttachments()[attachnum] then
				//Model origin
				local _pos,_ang = ent:GetPos(), ent:GetAngles()
				local _pos = _pos:ToScreen()
				local textpos = {x = _pos.x+5,y = _pos.y-5}

				draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder)
				draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect)
				draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder)
			else
				//Attachment
				local _pos,_ang = ent:GetAttachment(attachnum).Pos,ent:GetAttachment(attachnum).Ang
				local _pos = _pos:ToScreen()
				local textpos = {x = _pos.x+5,y = _pos.y-5}

				draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder)
				draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect)
				draw.SimpleTextOutlined(attachnum ..": ".. ent:GetAttachments()[attachnum].name,"Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder)
			end
		end

		if IsValid(tr.Entity) and tr.Entity == self.HighlightedEnt then
			DrawHighlightAttachments(self.HighlightedEnt) 
		return end

		if IsValid(tr.Entity) and tr.Entity != self.HighlightedEnt then
			//unhighlight the old ent if it exists
			if self.HighlightedEnt != nil then
				self.HighlightedEnt = nil
			end

			//highlight the new ent
			self.HighlightedEnt = tr.Entity
		end

		if !IsValid(tr.Entity) and self.HighlightedEnt != nil then
			self.HighlightedEnt = nil
		end
	end

	function TOOL:Holster()
		if self.HighlightedEnt != nil then
			self.HighlightedEnt = nil
		end
	end




	//All credit for the toolgun scroll wheel code goes to the Wiremod devs. You guys are the best.
		local function get_active_tool(ply, tool)
			-- find toolgun
			local activeWep = ply:GetActiveWeapon()
			if not IsValid(activeWep) or activeWep:GetClass() ~= "gmod_tool" or activeWep.Mode ~= tool then return end

			return activeWep:GetToolObject(tool)
		end

		local function hookfunc( ply, bind, pressed )
			if not pressed then return end
			if bind == "invnext" then
				local self = get_active_tool(ply, "particlecontrol")
				if not self then return end
			
				return self:ScrollDown(ply:GetEyeTraceNoCursor())
			elseif bind == "invprev" then
				local self = get_active_tool(ply, "particlecontrol")
				if not self then return end

				return self:ScrollUp(ply:GetEyeTraceNoCursor())
			end
		end
	
		if game.SinglePlayer() then -- wtfgarry (have to have a delay in single player or the hook won't get added)
			timer.Simple(5,function() hook.Add( "PlayerBindPress", "particlecontrol_playerbindpress", hookfunc ) end)
		else
			hook.Add( "PlayerBindPress", "particlecontrol_playerbindpress", hookfunc )
		end
	//End shamefully copied code here.

	function TOOL:Scroll(trace,dir)
		if !IsValid(self.HighlightedEnt) then return end

		local attachcount = 0
		if self.HighlightedEnt:GetAttachments() then attachcount = table.Count(self.HighlightedEnt:GetAttachments()) end
		local oldattachnum = self:GetClientNumber( "attachnum", 0 )
		if oldattachnum > attachcount then oldattachnum = 0 end
		local attachnum = oldattachnum + dir

		if attachnum < 0 then attachnum = attachcount end
		if attachnum > attachcount then attachnum = 0 end
		RunConsoleCommand("particlecontrol_attachnum", tostring(attachnum))
		self:GetOwner():EmitSound("weapons/pistol/pistol_empty.wav")
		return true
	end
	function TOOL:ScrollUp(trace) return self:Scroll(trace,-1) end
	function TOOL:ScrollDown(trace) return self:Scroll(trace,1) end

end




if SERVER then

	local function SpawnParticleControllerNormal(ply, ent, DataTable)

		if DataTable == nil or DataTable == {} or DataTable.EffectName == nil or ent == nil or !IsValid(ent) then return end


		local ParticleControlNormal = ents.Create( "particlecontroller_normal" )
		ParticleControlNormal:SetPos(ent:GetPos())
		ParticleControlNormal:SetAngles(ent:GetAngles())
		ParticleControlNormal:SetParent(ent)
		ent:DeleteOnRemove(ParticleControlNormal)

		ParticleControlNormal:SetTargetEnt(ent)
		ParticleControlNormal:SetEffectName(DataTable.EffectName)
		ParticleControlNormal:SetAttachNum(DataTable.AttachNum)
		ParticleControlNormal:SetUtilEffectInfo(DataTable.UtilEffectInfo)
		if DataTable.ColorInfo != nil then ParticleControlNormal:SetColor(DataTable.ColorInfo) else ParticleControlNormal:SetColor( Color(0,0,0,0) ) end

		ParticleControlNormal:SetRepeatRate(DataTable.RepeatRate)
		if DataTable.RepeatSafety == 1 or DataTable.RepeatSafety == true then ParticleControlNormal:SetRepeatSafety(true) else ParticleControlNormal:SetRepeatSafety(false) end


		if DataTable.StartOn == 1 or DataTable.StartOn == true then ParticleControlNormal:SetActive(true) else ParticleControlNormal:SetActive(false) end
		if DataTable.Toggle == 1 or DataTable.Toggle == true then ParticleControlNormal:SetToggle(true) else ParticleControlNormal:SetToggle(false) end
		ParticleControlNormal:SetNumpadKey(DataTable.NumpadKey)

		numpad.OnDown( 	 ply, 	DataTable.NumpadKey, 	"Particle_Press", 	ParticleControlNormal )
		numpad.OnUp( 	 ply, 	DataTable.NumpadKey, 	"Particle_Release", 	ParticleControlNormal )
		ParticleControlNormal:SetNumpadState("")


		ParticleControlNormal:Spawn()
		ParticleControlNormal:Activate()

	end


	function AttachParticleControllerNormal( ply, ent, Data )

		if Data.NewTable then
			SpawnParticleControllerNormal(ply, ent, Data.NewTable)

			local dupetable = {}
			if ent.EntityMods and ent.EntityMods.DupeParticleControllerNormal then dupetable = ent.EntityMods.DupeParticleControllerNormal end
			table.insert(dupetable, Data.NewTable)
			duplicator.StoreEntityModifier( ent, "DupeParticleControllerNormal", dupetable )
		return end

	end


	function DupeParticleControllerNormal( ply, ent, Data )

		//due to a problem with the easy bonemerge tool that causes entity modifiers to be applied TWICE, we need to remove the effects that were added the first time
		for _, asdf in pairs( ents:GetAll() ) do
			if asdf:GetClass() == "particlecontroller_normal" and asdf:GetParent() == ent then
				asdf:Remove()
			end
		end

		for _, DataTable in pairs (Data) do
			SpawnParticleControllerNormal(ply, ent, DataTable)
		end

	end
	duplicator.RegisterEntityModifier( "DupeParticleControllerNormal", DupeParticleControllerNormal )




	//we have to redefine some of the constraint functions here because they're local functions that don't exist outside of constraints.lua
	//not sure how well these'll work, one of them is ripped straight from the nocollide world tool which uses the same trick for its custom constraints
		local MAX_CONSTRAINTS_PER_SYSTEM = 100
		local function CreateConstraintSystem()
			local System = ents.Create("phys_constraintsystem")
			if !IsValid(System) then return end
			System:SetKeyValue("additionaliterations", GetConVarNumber("gmod_physiterations"))
			System:Spawn()
			System:Activate()
			return System
		end
		local function FindOrCreateConstraintSystem( Ent1, Ent2 )
			local System = nil
			Ent2 = Ent2 or Ent1
			-- Does Ent1 have a constraint system?
			if ( !Ent1:IsWorld() && Ent1:GetTable().ConstraintSystem && Ent1:GetTable().ConstraintSystem:IsValid() ) then 
				System = Ent1:GetTable().ConstraintSystem
			end
			-- Don't add to this system - we have too many constraints on it already.
			if ( System && System:IsValid() && System:GetVar( "constraints", 0 ) > MAX_CONSTRAINTS_PER_SYSTEM ) then System = nil end
			-- Does Ent2 have a constraint system?
			if ( !System && !Ent2:IsWorld() && Ent2:GetTable().ConstraintSystem && Ent2:GetTable().ConstraintSystem:IsValid() ) then 
				System = Ent2:GetTable().ConstraintSystem
			end
			-- Don't add to this system - we have too many constraints on it already.
			if ( System && System:IsValid() && System:GetVar( "constraints", 0 ) > MAX_CONSTRAINTS_PER_SYSTEM ) then System = nil end
			-- No constraint system yet (Or they're both full) - make a new one
			if ( !System || !System:IsValid() ) then
				--Msg("New Constrant System\n")
				System = CreateConstraintSystem()
			end
			Ent1.ConstraintSystem = System
			Ent2.ConstraintSystem = System
			System.UsedEntities = System.UsedEntities or {}
			table.insert( System.UsedEntities, Ent1 )
			table.insert( System.UsedEntities, Ent2 )
			local ConstraintNum = System:GetVar( "constraints", 0 )
			System:SetVar( "constraints", ConstraintNum + 1 )
			--Msg("System has "..tostring( System:GetVar( "constraints", 0 ) ).." constraints\n")
			return System
		end
	//end ripped constraint functions here.

	//multiple-point "beam" effects use a constraint so the duplicator can group the two entities together
	function constraint.AttachParticleControllerBeam( Ent1, Ent2, Data, ply )
		if !Ent1 or !Ent2 then return end

		//onStartConstraint( Ent1, Ent2 )
		local system = FindOrCreateConstraintSystem( Ent1, Ent2 )
		SetPhysConstraintSystem( system )
		
		//create a dummy ent for the constraint functions to use
		local Constraint = ents.Create("logic_collision_pair")
		Constraint:Spawn()
		Constraint:Activate()



		local ParticleControlBeam = ents.Create( "particlecontroller_normal" )
		ParticleControlBeam:SetPos(Ent1:GetPos())
		ParticleControlBeam:SetAngles(Ent1:GetAngles())
		ParticleControlBeam:SetParent(Ent1)
		Ent1:DeleteOnRemove(ParticleControlBeam)
		Ent2:DeleteOnRemove(ParticleControlBeam)

		ParticleControlBeam:SetTargetEnt(Ent1)
		ParticleControlBeam:SetTargetEnt2(Ent2)
		ParticleControlBeam:SetEffectName(Data.EffectName)
		ParticleControlBeam:SetAttachNum(Data.AttachNum)
		ParticleControlBeam:SetAttachNum2(Data.AttachNum2)
		ParticleControlBeam:SetUtilEffectInfo(Data.UtilEffectInfo)
		if Data.ColorInfo != nil then ParticleControlBeam:SetColor(Data.ColorInfo) else ParticleControlBeam:SetColor( Color(0,0,0,0) ) end

		ParticleControlBeam:SetRepeatRate(Data.RepeatRate)
		if Data.RepeatSafety == 1 or Data.RepeatSafety == true then ParticleControlBeam:SetRepeatSafety(true) else ParticleControlBeam:SetRepeatSafety(false) end

		if Data.StartOn == 1 or Data.StartOn == true then ParticleControlBeam:SetActive(true) else ParticleControlBeam:SetActive(false) end
		if Data.Toggle == 1 or Data.Toggle ==  true then ParticleControlBeam:SetToggle(true) else ParticleControlBeam:SetToggle(false) end
		ParticleControlBeam:SetNumpadKey(Data.NumpadKey)

		numpad.OnDown( 	 ply, 	Data.NumpadKey, 	"Particle_Press", 	ParticleControlBeam )
		numpad.OnUp( 	 ply, 	Data.NumpadKey, 	"Particle_Release", 	ParticleControlBeam )
		ParticleControlBeam:SetNumpadState("")

		ParticleControlBeam:Spawn()
		ParticleControlBeam:Activate()
		


		//onFinishConstraint( Ent1, Ent2 )
		SetPhysConstraintSystem( NULL )

		constraint.AddConstraintTable( Ent1, Constraint, Ent2 )
		
		local ctable  = 
		{
			Type  = "AttachParticleControllerBeam",
			Ent1  = Ent1,
			Ent2  = Ent2,
			Data  = Data,
			ply   = ply,
		}
	
		Constraint:SetTable( ctable )
	
		return Constraint
	end
	duplicator.RegisterConstraint( "AttachParticleControllerBeam", constraint.AttachParticleControllerBeam, "Ent1", "Ent2", "Data", "ply" )

end




//we're still testing out a lot of stuff with the cpanel, so let's add a way to refresh it by reselecting the tool
--[[
TOOL.ClientConVar[ "refresh" ] = 1
function TOOL:Think()
	if SERVER then return end
	if self:GetClientNumber("refresh") == 1 then
		RunConsoleCommand("particlecontrol_refresh", "0");
		//refresh the cpanel
		local panel = controlpanel.Get( "particlecontrol" )
		if ( !panel ) then return end
		panel:ClearControls()
		self.BuildCPanel(panel)
	end
end
function TOOL:Deploy()
	RunConsoleCommand("particlecontrol_refresh", "1");
end
]]

local ConVarsDefault = TOOL:BuildConVarList()
ConVarsDefault["particlecontrol_attachnum"] = nil  //don't save the attachnum in presets, it's used by the other tools too

function TOOL.BuildCPanel(panel)

	panel:AddControl( "Header", { Description = "#tool.particlecontrol.help" } )

	//Presets
	panel:AddControl( "ComboBox", { 
		MenuButton = 1, 
		Folder = "particlecontrol", 
		Options = { 
			[ "#preset.default" ] = ConVarsDefault
		}, 
		CVars = table.GetKeys( ConVarsDefault ) 
	} )



	AddParticleBrowser(panel, { 
		name = "Effect", 
		commands = { 
			effectname = "particlecontrol_effectname", 
			mode_beam = "particlecontrol_mode_beam",
			color = "particlecontrol_color",
			utileffect = "particlecontrol_utileffect",
		}, 
	})



	//panel:AddControl( "Label", { Text = "" }  )
	panel:AddControl( "Label", { Text = "" }  )



	panel:AddControl("Slider", {
		Label = "Attachment",
	 	Type = "Integer",
		Min = "0",
		Max = "10",
		Command = "particlecontrol_attachnum",
	})
	panel:ControlHelp( "Attachment point on the model to use. Set to 0 to attach to the model origin or to attach model-covering effects to the entire model." )

	panel:AddControl("Slider", {
		Label = "Repeat Rate",
	 	Type = "Float",
		Min = "0",
		Max = "5",
		Command = "particlecontrol_repeatrate",
	})
	panel:ControlHelp( "How often the effect plays. Set to 0 to not repeat." )

	panel:AddControl( "CheckBox", { Label = "Repeat Safety", Command = "particlecontrol_repeatsafety" } )
	panel:ControlHelp( "If on, effects are removed before being repeated. This stops them from piling up endlessly, but can also cause small problems, like effects being cut off. You should probably keep this on unless you know what you're doing." )



	panel:AddControl( "Label", { Text = "" }  )
	panel:AddControl( "Label", { Text = "" }  )



	local modellist = { Label = "Prop", ConVar = "particlecontrol_propmodel", Category = "Prop", Height = 1, Models = {} }
	modellist.Models["models/hunter/plates/plate025x025.mdl"] = {}
	modellist.Models["models/hunter/plates/plate.mdl"] = {}
	modellist.Models["models/weapons/w_smg1.mdl"] = {}
	modellist.Models["models/props_junk/popcan01a.mdl"] = {}
	panel:AddControl( "PropSelect", modellist )

	panel:AddControl( "ComboBox",  {
		Label = "Prop Angle", 
		MenuButton = "0", 
		Options = {
			["Spawn upright"] = { particlecontrol_propangle = "1" },
			["Spawn at surface angle"] = { particlecontrol_propangle = "2" }
		}
	})

	panel:AddControl( "CheckBox", { Label = "Invisible prop (particles only)", Command = "particlecontrol_propinvis" } )



	panel:AddControl( "Label", { Text = "" }  )
	panel:AddControl( "Label", { Text = "" }  )



	panel:AddControl( "Numpad", {
		Label = "Effect Key",
		Command = "particlecontrol_numpadkey",
	})

	panel:AddControl( "CheckBox", { Label = "Toggle", Command = "particlecontrol_toggle" } )

	panel:AddControl( "CheckBox", { Label = "Start on?", Command = "particlecontrol_starton" } )

end
