--[[
| 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 - Tracer"
ENT.Author			= ""

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




function ENT:SetupDataTables()

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

	self:NetworkVar( "String", 0, "EffectName" );
	self:NetworkVar( "String", 1, "NumpadState" );
	self:NetworkVar( "String", 2, "Impact_EffectName" );

	self:NetworkVar( "Float", 0, "RepeatRate" );
	self:NetworkVar( "Float", 1, "TracerSpread" );
	self:NetworkVar( "Float", 2, "EffectLifetime" );

	self:NetworkVar( "Int", 0, "AttachNum" );
	self:NetworkVar( "Int", 1, "NumpadKey" );
	self:NetworkVar( "Int", 2, "TracerCount" );

	self:NetworkVar( "Bool", 0, "Active" );
	self:NetworkVar( "Bool", 1, "Toggle" );
	self:NetworkVar( "Bool", 2, "LeaveBulletHoles" );

	//we have 3 pieces of information we need for util.effect, and they're not going to be changed after the ent's been spawned, so there's no reason we can't save space by storing them all in this vector
	//self:NetworkVar( "Vector", 0, "UtilEffectInfo" );  //x = scale, y = magnitude, x = radius
	self:NetworkVar( "Vector", 0, "Impact_UtilEffectInfo" );  //x = scale, y = magnitude, x = radius
	self:NetworkVar( "Vector", 1, "Impact_ColorInfo" );

end




function ENT:Initialize()

	local target = self:GetTargetEnt()


	if SERVER then

		//Use ourselves as the target for control point 1 - because of how we have tracer effects set up, we're going to do this for both util and .pcf effects
		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

		if !string.StartWith( self:GetEffectName(), "!UTILEFFECT!" ) then

			//Make a table of control point information, so we only have to do this once instead of every time AttachParticle runs.

			local clrtb = nil
			if self:GetColor().r == 0 and self:GetColor().g == 0 and self:GetColor().b == 0 then
				//MsgN("color = false")
			else
				//MsgN("color = true")
				if self:GetColor().a == 1 then
					clrtb = { position = Vector( self:GetColor().r / 255, self:GetColor().g / 255, self:GetColor().b / 255 )  }
				else
					clrtb = { position = Vector( self:GetColor().r, self:GetColor().g, self:GetColor().b )  }
				end
			end
			self.cpointtable = {}
				self.cpointtable[1] = { entity = target, attachtype = PATTACH_ABSORIGIN_FOLLOW }

				//this entry is a placeholder - we want the second controlpoint to be at the endpos of a trace we haven't performed yet
				self.cpointtable[2] = true

				for i = 3, 64 do					
 					if clrtb then
						self.cpointtable[i] = clrtb
					else
						self.cpointtable[i] = self.cpointtable[1]
					end
				end

			PrecacheParticleSystem(self:GetEffectName())

		end

	end
	self:SetModel("models/hunter/plates/plate.mdl") //it's a tiny block so that model-covering fx show up at a single point instead of all over an invisible error model
	self:SetNoDraw(true)


	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 SERVER then return 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 SERVER then return end

	local target		= self:GetTargetEnt()
	//local attachnum	= self:GetAttachNum()  //this isn't used anywhere
	local effectname	= self:GetEffectName()

	local tracerspread	= math.Clamp( self:GetTracerSpread(), 0, 4)
	local tracercount	= self:GetTracerCount()
	local leavebulletholes	= self:GetLeaveBulletHoles()
	local effectlifetime	= math.Clamp( self:GetEffectLifetime(), 0.5, 5)

	local impact_effectname	= self:GetImpact_EffectName()

	if effectname == nil or !target:IsValid() then return end


	for i = 1, tracercount do

		//Do a trace to find the endpoint of the bullet	
		local tracedata = {}
		if self:GetParent() != NULL then tracedata.filter = {self:GetParent(), self:GetParent():GetParent()} end  //this returns a null entity if the player is outside of the visleaf, be careful
		local tracepos = target:GetPos()
		local traceang = target:GetAngles()

		//spread
		local randang = AngleRand()
		traceang:RotateAroundAxis( traceang:Forward(), randang.r )
		traceang:RotateAroundAxis( traceang:Right(), randang.p * (tracerspread / 2) )
		traceang:RotateAroundAxis( traceang:Up(), randang.y * (tracerspread / 4) )

		tracedata.start = tracepos
		tracedata.endpos = tracepos+(traceang:Forward()*20000)
		local trace = util.TraceLine(tracedata)


		//we're going to tie the tracer effect and impact effect to a clientside ent that expires quickly, so people can't create a million permanent medigun beams or anything like that
		local tracerent = ClientsideModel( "models/hunter/plates/plate.mdl", RENDERGROUP_OTHER )
		tracerent:SetNoDraw(true)
		tracerent:SetPos(trace.HitPos)
		tracerent:SetAngles(trace.HitNormal:Angle())
		timer.Simple(effectlifetime, function() if IsValid(tracerent) then tracerent:Remove() end; end)
		tracerent:Spawn()
		tracerent:Activate()

		if self.cpointtable then self.cpointtable[2] = { entity = tracerent, attachtype = PATTACH_ABSORIGIN_FOLLOW } end


		//Create a bullet hole effect, if enabled
		if leavebulletholes == true then
			local impacteffectdata = EffectData()
			impacteffectdata:SetStart( tracepos )
			impacteffectdata:SetOrigin( trace.HitPos )
			impacteffectdata:SetNormal( trace.HitNormal )
			impacteffectdata:SetEntity( trace.Entity )
			impacteffectdata:SetSurfaceProp( trace.SurfaceProps )
			impacteffectdata:SetHitBox( trace.HitBox )
			util.Effect( "Impact", impacteffectdata )
		end


		//Create the tracer effect
		if string.StartWith( 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:GetUtilEffectInfo().x
			//local effectmagnitude = self:GetUtilEffectInfo().y
			//local effectradius = self:GetUtilEffectInfo().z

			local luaeffectdata = EffectData()
			luaeffectdata:SetEntity( tracerent )

			luaeffectdata:SetScale(5000) //for tracer effects, scale is the speed of the bullet, so we need to keep this high
			//luaeffectdata:SetMagnitude( 20 )
			//luaeffectdata:SetRadius( 10 )

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

			//colors can also be set the same way - i don't think there are any tracers that use these
			luaeffectdata:SetColor(0)
			if string.EndsWith( effectname, "!" ) then
				if string.find( effectname, "!COLOR1!" ) then luaeffectdata:SetColor( 1 ) effectname = string.Replace( effectname, "!COLOR1!", "" ) end
				if string.find( effectname, "!COLOR2!" ) then luaeffectdata:SetColor( 2 ) effectname = string.Replace( effectname, "!COLOR2!", "" ) end
				if string.find( effectname, "!COLOR3!" ) then luaeffectdata:SetColor( 3 ) effectname = string.Replace( effectname, "!COLOR3!", "" ) end
				if string.find( effectname, "!COLOR4!" ) then luaeffectdata:SetColor( 4 ) effectname = string.Replace( effectname, "!COLOR4!", "" ) end
				if string.find( effectname, "!COLOR5!" ) then luaeffectdata:SetColor( 5 ) effectname = string.Replace( effectname, "!COLOR5!", "" ) end
				if string.find( effectname, "!COLOR6!" ) then luaeffectdata:SetColor( 6 ) effectname = string.Replace( effectname, "!COLOR6!", "" ) end
				if string.find( effectname, "!COLOR7!" ) then luaeffectdata:SetColor( 7 ) effectname = string.Replace( effectname, "!COLOR7!", "" ) end
				if string.find( effectname, "!COLOR8!" ) then luaeffectdata:SetColor( 8 ) effectname = string.Replace( effectname, "!COLOR8!", "" ) end
				if string.find( effectname, "!COLOR9!" ) then luaeffectdata:SetColor( 9 ) effectname = string.Replace( effectname, "!COLOR9!", "" ) end
			end

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

			luaeffectdata:SetOrigin( trace.HitPos )
			luaeffectdata:SetStart( tracepos )
			//luaeffectdata:SetAngles( traceang )
			luaeffectdata:SetNormal( trace.HitNormal:Angle():Forward() )
		
			util.Effect( string.Replace( effectname, "!UTILEFFECT!", "" ), luaeffectdata )

		else

			//Create a particlesystem effect

			tracerent:CreateParticleEffect(effectname,self.cpointtable)

		end


		//Create the impact effect, if enabled
		if impact_effectname != "" then
			if string.StartWith( impact_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:GetImpact_UtilEffectInfo().x
				local effectmagnitude = self:GetImpact_UtilEffectInfo().y
				local effectradius = self:GetImpact_UtilEffectInfo().z

				local impacteffectdata = EffectData()
				impacteffectdata:SetEntity( tracerent )
				if ( string.find(impact_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
				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( impact_effectname, "!" ) then
					if string.find( impact_effectname, "!FLAG1!" ) then impacteffectdata:SetFlags( 1 ) impact_effectname = string.Replace( impact_effectname, "!FLAG1!", "" ) end
					if string.find( impact_effectname, "!FLAG2!" ) then impacteffectdata:SetFlags( 2 ) impact_effectname = string.Replace( impact_effectname, "!FLAG2!", "" ) end
					if string.find( impact_effectname, "!FLAG3!" ) then impacteffectdata:SetFlags( 3 ) impact_effectname = string.Replace( impact_effectname, "!FLAG3!", "" ) end
					if string.find( impact_effectname, "!FLAG4!" ) then impacteffectdata:SetFlags( 4 ) impact_effectname = string.Replace( impact_effectname, "!FLAG4!", "" ) end
					if string.find( impact_effectname, "!FLAG5!" ) then impacteffectdata:SetFlags( 5 ) impact_effectname = string.Replace( impact_effectname, "!FLAG5!", "" ) end
					if string.find( impact_effectname, "!FLAG6!" ) then impacteffectdata:SetFlags( 6 ) impact_effectname = string.Replace( impact_effectname, "!FLAG6!", "" ) end
					if string.find( impact_effectname, "!FLAG7!" ) then impacteffectdata:SetFlags( 7 ) impact_effectname = string.Replace( impact_effectname, "!FLAG7!", "" ) end
					if string.find( impact_effectname, "!FLAG8!" ) then impacteffectdata:SetFlags( 8 ) impact_effectname = string.Replace( impact_effectname, "!FLAG8!", "" ) end
					if string.find( impact_effectname, "!FLAG9!" ) then impacteffectdata:SetFlags( 9 ) impact_effectname = string.Replace( impact_effectname, "!FLAG9!", "" ) end
				end

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

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

				impacteffectdata:SetStart( tracerent:GetPos() )
				impacteffectdata:SetOrigin( tracerent:GetPos() )
				impacteffectdata:SetAngles( tracerent:GetAngles() )
				impacteffectdata:SetNormal( trace.HitNormal )

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

			else

				//Create a particlesystem effect

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

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

				tracerent:CreateParticleEffect(impact_effectname,impactcpointtable)

			end
		end

	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_tracer", function( ply, data )
end, "Data" )