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

AddCSLuaFile()

ENT.Base 			= "base_gmodentity"
ENT.PrintName			= "Particle Controller - Projectile"
ENT.Author			= ""

ENT.Spawnable			= false
ENT.AdminSpawnable		= false
ENT.RenderGroup			= RENDERGROUP_NONE




function ENT:SetupDataTables()

	self:NetworkVar( "Entity", 0, 	"TargetEnt" );


	self:NetworkVar( "String", 0, 	"ProjFX_EffectName" );
	self:NetworkVar( "Vector", 0, 	"ProjFX_UtilEffectInfo" );  //x = scale, y = magnitude, x = radius
	self:NetworkVar( "Vector", 1, 	"ProjFX_ColorInfo" );

	self:NetworkVar( "String", 1, 	"ImpactFX_EffectName" );
	self:NetworkVar( "Vector", 2, 	"ImpactFX_UtilEffectInfo" );  //x = scale, y = magnitude, x = radius
	self:NetworkVar( "Vector", 3, 	"ImpactFX_ColorInfo" );

	self:NetworkVar( "Int", 0, 	"AttachNum" );
	self:NetworkVar( "Float", 0, 	"RepeatRate" );

	self:NetworkVar( "String", 2, 	"ProjModel" );
	self:NetworkVar( "Int", 1, 	"ProjModel_AttachNum" );
	self:NetworkVar( "Bool", 0, 	"ProjModel_Invis" );
	self:NetworkVar( "Float", 1, 	"ImpactFX_EffectLifetime" );

	self:NetworkVar( "Float", 2, 	"ProjEnt_Spread" );
	self:NetworkVar( "Int", 2, 	"ProjEnt_Velocity" );
	self:NetworkVar( "Bool", 1, 	"ProjEnt_Gravity" );
	self:NetworkVar( "Int", 3, 	"ProjEnt_Angle" );
	self:NetworkVar( "Int", 4, 	"ProjEnt_Spin" );
	self:NetworkVar( "Bool", 2, 	"ProjEnt_DemomanFix" );
	self:NetworkVar( "Float", 3, 	"ProjEnt_Lifetime_PreHit" );
	self:NetworkVar( "Float", 4, 	"ProjEnt_Lifetime_PostHit" );
	self:NetworkVar( "Bool", 3, 	"ProjEnt_Serverside" );


	self:NetworkVar( "Bool", 4, 	"Active" );
	self:NetworkVar( "Bool", 5, 	"Toggle" );
	self:NetworkVar( "Int", 5, 	"NumpadKey" );
	self:NetworkVar( "String", 3, 	"NumpadState" );

end




function ENT:Initialize()

	util.PrecacheModel(self:GetProjModel())
	local target = self:GetTargetEnt()


	if SERVER then

		//Use ourselves as the target - we'll fire projectiles from our own position and angles
		local attachment1 = target:GetAttachment( self:GetAttachNum() )
		if attachment1 != nil then
			self:SetPos( attachment1.Pos )
			self:SetAngles( attachment1.Ang )
			self:Fire("setparentattachment", target:GetAttachments()[self:GetAttachNum()].name, 0.01)
			self:SetTargetEnt(self)
		end

	else

		//Set things up for a particlesystem effect

		local projfx_effectname = self:GetProjFX_EffectName()
		if projfx_effectname != "" and !string.StartWith( projfx_effectname, "!UTILEFFECT!" ) then
			PrecacheParticleSystem(projfx_effectname)
		end

		local impactfx_effectname = self:GetImpactFX_EffectName()
		if impactfx_effectname != "" and !string.StartWith( impactfx_effectname, "!UTILEFFECT!" ) then
			PrecacheParticleSystem(impactfx_effectname)
		end

	end
	//super niche bug fix: if we're in multiplayer and our model has any attachment points, then we'll cause visual jittering in the buildbonepositions callback of 
	//the entity we're parented to. originally we were using our own model to store the projectile model, but that was causing the aforementioned bug, so now we're 
	//storing the projectile model separately and having this entity use a neutral model that won't cause problems.
	self:SetModel("models/hunter/plates/plate.mdl")
	self:SetNoDraw(true)


	//Don't let players set the repeat rate to a really low amount that lets them spam tons of props every second
	if self:GetRepeatRate() > 0 and self:GetRepeatRate() < 0.1 then self:SetRepeatRate(0.1) end

	if self:GetRepeatRate() > 0 then
		//we're going to be repeating the effect, so let's set that up and let think do the rest
		self.NextRepeat = 0
	else
		//we won't be repeating the effect, so just attach it right now
		if self:GetActive() then self:AttachParticle() end
	end

end




function ENT:Think()

	if self:GetProjEnt_Serverside() then
		if CLIENT then return end
	else
		if SERVER then return end
	end

	if self:GetNumpadState() == "off" then
		//MsgN("turn off")
		self:SetNumpadState("")
	end

	if self:GetNumpadState() == "on" then
		//MsgN("turn on")
		self:SetNumpadState("")
		if self:GetRepeatRate() > 0 then
			//we're going to be repeating the effect, so let's set that up and let think do the rest
			self.NextRepeat = 0
		else
			//we won't be repeating the effect, so just attach it right now
			if self:GetActive() then self:AttachParticle() end
		end
	end


	if self:GetActive() == true and self:GetRepeatRate() > 0 then
		if !( self.NextRepeat > CurTime() ) then
			self:AttachParticle()
			self.NextRepeat = CurTime() + self:GetRepeatRate()
		end
	end
	
	self:NextThink(CurTime())
	return true

end




function ENT:AttachParticle()

	if !self then return end

	if self:GetProjEnt_Serverside() then
		if CLIENT then return end
	else
		if SERVER then return end
	end

	local projent_spread		= math.Clamp( self:GetProjEnt_Spread(), 0, 4)
	local projent_velocity		= self:GetProjEnt_Velocity()
	local projent_gravity		= self:GetProjEnt_Gravity()
	local projent_angle		= self:GetProjEnt_Angle()
	local projent_spin		= self:GetProjEnt_Spin()
	local projent_demomanfix	= self:GetProjEnt_DemomanFix()
	local projent_lifetime_prehit	= math.Clamp( self:GetProjEnt_Lifetime_PreHit(), 0, 10)
	local projent_lifetime_posthit	= math.Clamp( self:GetProjEnt_Lifetime_PostHit(), 0, 10)

	local projfx_effectname		= self:GetProjFX_EffectName()
	local projmodel_attachnum	= self:GetProjModel_AttachNum()

	local impactfx_effectname	= self:GetImpactFX_EffectName()
	local impactfx_effectlifetime	= math.Clamp( self:GetImpactFX_EffectLifetime(), 0, 10)
	local impactfx_utileffectinfo	= self:GetImpactFX_UtilEffectInfo()
	local impactfx_colorinfo	= self:GetImpactFX_ColorInfo()



	//Create the projectile entity
	local proj = nil
	if SERVER then
		proj = ents.Create("parctrl_dummyent")
		proj:SetModel(self:GetProjModel())
	end
	if CLIENT then	
		proj = ClientsideModel(self:GetProjModel())
	end

	if !util.IsValidProp(self:GetProjModel()) then
		proj:PhysicsInitBox(proj:GetModelBounds())
	else
		proj:PhysicsInit(SOLID_VPHYSICS)
	end
	proj:GetPhysicsObject():Wake()

	proj:SetNoDraw(self:GetProjModel_Invis())
	proj:SetSkin(self:GetSkin())
	proj:SetMaterial(self:GetMaterial())
	proj:SetPos(self:GetPos())


	proj:Spawn()
	proj:Activate()
	local projphys = proj:GetPhysicsObject()

	local selfang = self:GetAngles()
	//a lot of attachment points are oriented at an angle on the roll axis - correct this, we want the default projectile angle to be upright
	if self:GetParent() != NULL then  //this returns a null entity if the player is outside of the visleaf, be careful
		if self:GetParent():GetAttachment( self:GetAttachNum() ) then
			local _, attachang = WorldToLocal(self:GetPos(), selfang, self:GetParent():GetPos(), self:GetParent():GetAngles())
			selfang = Angle(selfang.p,selfang.y,selfang.r - attachang.r)
		end
	end
	//muzzle attachments on demoman weapons are oriented 90 degrees to the side for some reason - give players an option to fix this
	if projent_demomanfix == true then selfang:RotateAroundAxis( selfang:Up(), -90 ) end

	//spread
	local projang = Angle(selfang.p,selfang.y,selfang.r)  //self:GetAngles()
	local randang = AngleRand()
	projang:RotateAroundAxis( projang:Forward(), randang.r )
	projang:RotateAroundAxis( projang:Right(), randang.p * (projent_spread / 2) )
	projang:RotateAroundAxis( projang:Up(), randang.y * (projent_spread / 4) )
	//set the velocity
	projphys:SetVelocity(projang:Forward() * projent_velocity)
	//now de-randomize the roll - we only needed to do that for the velocity
	projang:RotateAroundAxis( projang:Forward(), -randang.r )

	//change the angle of the projectile
	local rotationang = Angle(0,0,0)
	//0 = forward, don't change it
	//1 = left
	if projent_angle == 1 then projang:RotateAroundAxis( projang:Up(), 90 ) rotationang = Angle(0,-90,0)
	//2 = right
	elseif projent_angle == 2 then projang:RotateAroundAxis( projang:Up(), -90 ) rotationang = Angle(0,90,0)
	//3 = up
	elseif projent_angle == 3 then projang:RotateAroundAxis( projang:Right(), 90 ) rotationang = Angle(90,0,0)
	//4 = down
	elseif projent_angle == 4 then projang:RotateAroundAxis( projang:Right(), -90 ) rotationang = Angle(-90,0,0)
	//5 = back
	elseif projent_angle == 5 then projang:RotateAroundAxis( projang:Up(), 180 ) rotationang = Angle(0,180,0) end
	proj:SetAngles(projang)

	//add spin - spin should be the same regardless of which way we've oriented the prop with projent_angle
	//0 - don't spin
	//1 - spin pitch
	if projent_spin == 1 then projphys:AddAngleVelocity( rotationang:Right() * -350 )
	//2 - spin yaw
	elseif projent_spin == 2 then projphys:AddAngleVelocity( rotationang:Forward() * 350 )
	//3 - spin roll
	elseif projent_spin == 3 then projphys:AddAngleVelocity( rotationang:Up() * -350 )
	//4 - spin random
	elseif projent_spin == 4 then projphys:AddAngleVelocity( VectorRand() * -350 ) end

	projphys:EnableGravity(projent_gravity)
	projphys:SetMaterial("gmod_silent")   //don't play physics sounds
	proj:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)   //don't collide with other projectiles
	proj:SetOwner(self:GetParent())   //don't collide with the prop that the effect ent is parented to
	if SERVER then self:DeleteOnRemove(proj) end   //delete the proj if the effect ent is removed - i'd love to do this for clientside projs too but this function is serverside only



	//Create the projectile effect, if enabled
	if projfx_effectname != "" then

		local projfxent = proj

		if SERVER then
			//since we can't call util.Effect or CreateParticleEffect on the server, we have to get the ent to do it itself
			projfxent:SetEffectName(projfx_effectname)
			projfxent:SetColorInfo(self:GetProjFX_ColorInfo())
			projfxent:SetUtilEffectInfo(self:GetProjFX_UtilEffectInfo())
			projfxent:SetAttachNum(projmodel_attachnum)
		end
		if CLIENT then
			if string.StartWith( projfx_effectname, "!UTILEFFECT!" ) then

				//Create a util effect

				//Unfortunately, we have to do all of this every single time the effect repeats, because if we do it in Initialize instead, a whole bunch of stuff doesn't work properly

				local effectscale = self:GetProjFX_UtilEffectInfo().x
				local effectmagnitude = self:GetProjFX_UtilEffectInfo().y
				local effectradius = self:GetProjFX_UtilEffectInfo().z

				local projeffectdata = EffectData()
				projeffectdata:SetEntity( projfxent )
				//if ( string.find(projfx_effectname, "Tracer", 0, true) != nil ) then projeffectdata:SetScale(5000) else projeffectdata:SetScale( effectscale ) end  //for tracer effects, scale is the speed of the bullet, so we need to keep this high; useless for a projectile effect
				projeffectdata:SetScale( effectscale )
				projeffectdata:SetMagnitude( effectmagnitude )
				projeffectdata:SetRadius(effectradius )

				//flags can be set by typing !FLAG#! at the end of the effect name
				projeffectdata:SetFlags( 0 )
				if string.EndsWith( projfx_effectname, "!" ) then
					if string.find( projfx_effectname, "!FLAG1!" ) then projeffectdata:SetFlags( 1 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG1!", "" ) end
					if string.find( projfx_effectname, "!FLAG2!" ) then projeffectdata:SetFlags( 2 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG2!", "" ) end
					if string.find( projfx_effectname, "!FLAG3!" ) then projeffectdata:SetFlags( 3 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG3!", "" ) end
					if string.find( projfx_effectname, "!FLAG4!" ) then projeffectdata:SetFlags( 4 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG4!", "" ) end
					if string.find( projfx_effectname, "!FLAG5!" ) then projeffectdata:SetFlags( 5 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG5!", "" ) end
					if string.find( projfx_effectname, "!FLAG6!" ) then projeffectdata:SetFlags( 6 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG6!", "" ) end
					if string.find( projfx_effectname, "!FLAG7!" ) then projeffectdata:SetFlags( 7 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG7!", "" ) end
					if string.find( projfx_effectname, "!FLAG8!" ) then projeffectdata:SetFlags( 8 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG8!", "" ) end
					if string.find( projfx_effectname, "!FLAG9!" ) then projeffectdata:SetFlags( 9 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG9!", "" ) end
				end

				//colors can also be set the same way
				projeffectdata:SetColor(0)
				if string.EndsWith( projfx_effectname, "!" ) then
					if string.find( projfx_effectname, "!COLOR1!" ) then projeffectdata:SetColor( 1 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR1!", "" ) end
					if string.find( projfx_effectname, "!COLOR2!" ) then projeffectdata:SetColor( 2 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR2!", "" ) end
					if string.find( projfx_effectname, "!COLOR3!" ) then projeffectdata:SetColor( 3 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR3!", "" ) end
					if string.find( projfx_effectname, "!COLOR4!" ) then projeffectdata:SetColor( 4 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR4!", "" ) end
					if string.find( projfx_effectname, "!COLOR5!" ) then projeffectdata:SetColor( 5 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR5!", "" ) end
					if string.find( projfx_effectname, "!COLOR6!" ) then projeffectdata:SetColor( 6 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR6!", "" ) end
					if string.find( projfx_effectname, "!COLOR7!" ) then projeffectdata:SetColor( 7 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR7!", "" ) end
					if string.find( projfx_effectname, "!COLOR8!" ) then projeffectdata:SetColor( 8 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR8!", "" ) end
					if string.find( projfx_effectname, "!COLOR9!" ) then projeffectdata:SetColor( 9 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR9!", "" ) end
				end
	
				//dumb situational crap
				if string.find( string.lower(projfx_effectname), "shakeropes" ) then projeffectdata:SetMagnitude( effectmagnitude * 20 ) end
				if string.find( string.lower(projfx_effectname), "thumperdust" ) then projeffectdata:SetScale( effectscale * 50 ) end
				if string.find( string.lower(projfx_effectname), "bloodspray" ) then projeffectdata:SetScale( effectscale * 4 ) end

				//just in case someone makes a utileffect that works as a projectile effect
				if projfxent:GetAttachment(projmodel_attachnum) != nil then
					projeffectdata:SetStart( projfxent:GetAttachment( projmodel_attachnum ).Pos )
					projeffectdata:SetOrigin( projfxent:GetAttachment( projmodel_attachnum ).Pos )
					projeffectdata:SetAngles( projfxent:GetAttachment( projmodel_attachnum ).Ang )
					projeffectdata:SetNormal( projfxent:GetAttachment( projmodel_attachnum ).Ang:Forward() )
				else
					projeffectdata:SetStart( projfxent:GetPos() )
					projeffectdata:SetOrigin( projfxent:GetPos() )
					projeffectdata:SetAngles( projfxent:GetAngles() )
					projeffectdata:SetNormal( projfxent:GetAngles():Forward() )
				end

				util.Effect( string.Replace( projfx_effectname, "!UTILEFFECT!", "" ), projeffectdata )

			else

				//Create a particlesystem effect
	
				//Since we can't specify attachment points with Entity:CreateParticleEffect(), create an entity there to use as a target
				local attachment1 = proj:GetAttachment( projmodel_attachnum )
				if attachment1 != nil then
					//if SERVER then
					//	projfxent = ents.Create("parctrl_dummyent")
					//	projfxent:SetModel("models/hunter/plates/plate.mdl")
					//end
					if CLIENT then
						projfxent = ClientsideModel("models/hunter/plates/plate.mdl")
					end

					projfxent:SetParent(proj, projmodel_attachnum - 1)
					projfxent:SetPos( attachment1.Pos )
					projfxent:SetAngles( attachment1.Ang )
					projfxent:SetNoDraw(true)

					projfxent:Spawn()
					projfxent:Activate()

					proj:CallOnRemove("RemoveProjFX", function() if IsValid(projfxent) then projfxent:Remove() end end)
				end


				local clrtb = nil
				if self:GetProjFX_ColorInfo() == Vector(0,0,0) then
					//MsgN("color = false")
				else
					//MsgN("color = true")
					clrtb = { position = self:GetProjFX_ColorInfo() }
				end
				local projcpointtable = {}
					projcpointtable[1] = { entity = projfxent, attachtype = PATTACH_ABSORIGIN_FOLLOW }

					for i = 2, 64 do					
 						if clrtb then
							projcpointtable[i] = clrtb
						else
							projcpointtable[i] = projcpointtable[1]
						end
					end

				projfxent:CreateParticleEffect(projfx_effectname,projcpointtable)

			end
		end
	end



	local function projexpire(ent, pos, ang)

		//Create the impact effect, if enabled
		if impactfx_effectname != "" then

			//Create a new entity to attach the impact effect to - we can't use proj since we're removing it immediately after this
			local impactfxent = nil
			if SERVER then
				impactfxent = ents.Create("parctrl_dummyent")
				impactfxent:SetModel("models/hunter/plates/plate.mdl")
			end
			if CLIENT then
				impactfxent = ClientsideModel("models/hunter/plates/plate.mdl")
			end
			impactfxent:SetPos( pos or proj:GetPos() )
			impactfxent:SetAngles( ang or proj:GetAngles() )
			impactfxent:SetNoDraw(true)
			timer.Simple(impactfx_effectlifetime, function() if IsValid(impactfxent) then impactfxent:Remove() end; end)
			impactfxent:Spawn()
			impactfxent:Activate()

			if SERVER then
				//since we can't call util.Effect or CreateParticleEffect on the server, we have to get the ent to do it itself
				impactfxent:SetEffectName(impactfx_effectname)
				impactfxent:SetColorInfo(impactfx_colorinfo)
				impactfxent:SetUtilEffectInfo(impactfx_utileffectinfo)
				impactfxent:SetAttachNum(0)
			end
			if CLIENT then
				if string.StartWith( impactfx_effectname, "!UTILEFFECT!" ) then

					//Create a util effect

					//Unfortunately, we have to do all of this every single time the effect repeats, because if we do it in Initialize instead, a whole bunch of stuff doesn't work properly
	
					local effectscale = impactfx_utileffectinfo.x
					local effectmagnitude = impactfx_utileffectinfo.y
					local effectradius = impactfx_utileffectinfo.z

					local impacteffectdata = EffectData()
					impacteffectdata:SetEntity(impactfxent)
					//if ( string.find(impactfx_effectname, "Tracer", 0, true) != nil ) then impacteffectdata:SetScale(5000) else impacteffectdata:SetScale(effectscale) end  //for tracer effects, scale is the speed of the bullet, so we need to keep this high; useless for an impact effect
					impacteffectdata:SetScale(effectscale)
					impacteffectdata:SetMagnitude(effectmagnitude)
					impacteffectdata:SetRadius(effectradius)

					//flags can be set by typing !FLAG#! at the end of the effect name
					impacteffectdata:SetFlags( 0 )
					if string.EndsWith( impactfx_effectname, "!" ) then
						if string.find( impactfx_effectname, "!FLAG1!" ) then impacteffectdata:SetFlags( 1 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG1!", "" ) end
						if string.find( impactfx_effectname, "!FLAG2!" ) then impacteffectdata:SetFlags( 2 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG2!", "" ) end
						if string.find( impactfx_effectname, "!FLAG3!" ) then impacteffectdata:SetFlags( 3 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG3!", "" ) end
						if string.find( impactfx_effectname, "!FLAG4!" ) then impacteffectdata:SetFlags( 4 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG4!", "" ) end
						if string.find( impactfx_effectname, "!FLAG5!" ) then impacteffectdata:SetFlags( 5 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG5!", "" ) end
						if string.find( impactfx_effectname, "!FLAG6!" ) then impacteffectdata:SetFlags( 6 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG6!", "" ) end
						if string.find( impactfx_effectname, "!FLAG7!" ) then impacteffectdata:SetFlags( 7 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG7!", "" ) end
						if string.find( impactfx_effectname, "!FLAG8!" ) then impacteffectdata:SetFlags( 8 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG8!", "" ) end
						if string.find( impactfx_effectname, "!FLAG9!" ) then impacteffectdata:SetFlags( 9 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG9!", "" ) end
					end

					//colors can also be set the same way
					impacteffectdata:SetColor(0)
					if string.EndsWith( impactfx_effectname, "!" ) then
						if string.find( impactfx_effectname, "!COLOR1!" ) then impacteffectdata:SetColor( 1 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR1!", "" ) end
						if string.find( impactfx_effectname, "!COLOR2!" ) then impacteffectdata:SetColor( 2 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR2!", "" ) end
						if string.find( impactfx_effectname, "!COLOR3!" ) then impacteffectdata:SetColor( 3 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR3!", "" ) end
						if string.find( impactfx_effectname, "!COLOR4!" ) then impacteffectdata:SetColor( 4 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR4!", "" ) end
						if string.find( impactfx_effectname, "!COLOR5!" ) then impacteffectdata:SetColor( 5 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR5!", "" ) end
						if string.find( impactfx_effectname, "!COLOR6!" ) then impacteffectdata:SetColor( 6 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR6!", "" ) end
						if string.find( impactfx_effectname, "!COLOR7!" ) then impacteffectdata:SetColor( 7 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR7!", "" ) end
						if string.find( impactfx_effectname, "!COLOR8!" ) then impacteffectdata:SetColor( 8 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR8!", "" ) end
						if string.find( impactfx_effectname, "!COLOR9!" ) then impacteffectdata:SetColor( 9 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR9!", "" ) end
					end

					//dumb situational crap
					if string.find( string.lower(impactfx_effectname), "shakeropes" ) then impacteffectdata:SetMagnitude( effectmagnitude * 20 ) end
					if string.find( string.lower(impactfx_effectname), "thumperdust" ) then impacteffectdata:SetScale( effectscale * 50 ) end
					if string.find( string.lower(impactfx_effectname), "bloodspray" ) then impacteffectdata:SetScale( effectscale * 4 ) end

					impacteffectdata:SetStart( impactfxent:GetPos() )
					impacteffectdata:SetOrigin( impactfxent:GetPos() )
					impacteffectdata:SetAngles( impactfxent:GetAngles() )
					impacteffectdata:SetNormal( impactfxent:GetAngles():Forward() )

					util.Effect( string.Replace( impactfx_effectname, "!UTILEFFECT!", "" ), impacteffectdata )

				else

					//Create a particlesystem effect

					local clrtb = nil
					if impactfx_colorinfo == Vector(0,0,0) then
						//MsgN("color = false")
					else
						//MsgN("color = true")
						clrtb = { position = impactfx_colorinfo }
					end
					local impactcpointtable = {}
						impactcpointtable[1] = { entity = impactfxent, attachtype = PATTACH_ABSORIGIN_FOLLOW }

						for i = 2, 64 do					
 							if clrtb then
								impactcpointtable[i] = clrtb
							else
								impactcpointtable[i] = impactcpointtable[1]
							end
						end

					impactfxent:CreateParticleEffect(impactfx_effectname,impactcpointtable)

				end
			end
		end

		ent:Remove()

	end

	timer.Simple(projent_lifetime_prehit, function() if IsValid(proj) then projexpire(proj) end; end)

	local function projcollide(entity, data)
		if IsValid(entity) then
			if entity.HasHitSomething then return end   //there's no reason to call this more than once
			entity.HasHitSomething = true

			if projent_lifetime_posthit == 0 then
				//if lifetime_posthit is 0, then move the impactfx to the pos and angle of impact - 
				//we still need to use a timer because directly calling ent:Remove() in a PhysicsCollide callback crashes the game
				timer.Simple(0, function() if IsValid(proj) then projexpire(proj, data.HitPos, -data.HitNormal:Angle()) end; end) 
			else
				timer.Simple(projent_lifetime_posthit, function() if IsValid(proj) then projexpire(proj) end; end) 
			end
		end
	end
	if CLIENT then
		proj:AddCallback("PhysicsCollide", projcollide)
	else
		proj.PhysicsCollide = projcollide  //AddCallback inexplicably won't work on our sent we're using serverside, but overriding its physicscollide function seems to do it
	end

end




//numpad functions
if SERVER then

local function NumpadPress( pl, ent )

	if ( !ent || ent == NULL ) then return end
	
	if ( ent:GetToggle() ) then
		if ent:GetActive() == false then
			ent:SetActive(true)
			ent:SetNumpadState("on")
		else
			ent:SetActive(false)
			ent:SetNumpadState("off")
		end
	else
		ent:SetActive( true )
		ent:SetNumpadState("on")
	end

end

local function NumpadRelease( pl, ent )

	if ( !ent || ent == NULL ) then return end
	
	if ( ent:GetToggle() ) then return end
	
	ent:SetActive(false)
	ent:SetNumpadState("off")
	
end

numpad.Register( "Particle_Press", NumpadPress )
numpad.Register( "Particle_Release", NumpadRelease )

end




//don't duplicate this
duplicator.RegisterEntityClass( "particlecontroller_proj", function( ply, data )
end, "Data" )