--By Shadow Bonnie from GitHub

OUTLINE_MODE_BOTH		= 0		-- Render always
OUTLINE_MODE_NOTVISIBLE	= 1
OUTLINE_MODE_VISIBLE	= 2

OUTLINE_RENDERTYPE_BEFORE_VM	= 0		-- Render before drawing the view model
OUTLINE_RENDERTYPE_BEFORE_EF	= 1		-- Render before drawing all effects (after drawing the viewmodel)
OUTLINE_RENDERTYPE_AFTER_EF		= 2		-- Render after drawing all effects

local istable			= istable
local render			= render
local Material			= Material
local CreateMaterial	= CreateMaterial
local hook				= hook
local cam				= cam
local ScrW				= ScrW
local ScrH				= ScrH
local IsValid			= IsValid
local surface			= surface

module( "outline", package.seeall )

local List, ListSize		= {}, 0
local RenderEnt				= NULL
local RenderType			= OUTLINE_RENDERTYPE_AFTER_EF
local OutlineThickness		= 1

local StoreTexture			= render.GetScreenEffectTexture( 0 )
local DrawTexture			= render.GetScreenEffectTexture( 1 )

local OutlineMatSettings	= {
	[ "$basetexture" ]	= DrawTexture:GetName(),
	[ "$ignorez" ]		= 1,
	[ "$alphatest" ]	= 1
}

local CopyMat				= Material( "pp/copy" )
local OutlineMat			= CreateMaterial( "outline", "UnlitGeneric", OutlineMatSettings )

local ENTS, COLOR, MODE		= 1, 2, 3

function Add( ents, color, mode )
	
	if ( ListSize >= 255 ) then return end				--Maximum 255 reference values
	if ( !istable( ents ) ) then ents = { ents } end	--Support for passing Entity as first argument
	if ( ents[ 1 ] == nil ) then return end				--Do not pass empty tables
	
	if (
		mode != OUTLINE_MODE_BOTH &&
		mode != OUTLINE_MODE_NOTVISIBLE &&
		mode != OUTLINE_MODE_VISIBLE
	) then
		mode = OUTLINE_MODE_BOTH
	end
	
	local data = {
		[ ENTS ] = ents,
		[ COLOR ] = color,
		[ MODE ] = mode
	}
	
	ListSize = ListSize + 1
	List[ ListSize ] = data
	
end

function RenderedEntity()
	
	return RenderEnt
	
end

function SetRenderType( render_type )
	
	if (
		render_type != OUTLINE_RENDERTYPE_BEFORE_VM &&
		render_type != OUTLINE_RENDERTYPE_BEFORE_EF &&
		render_type != OUTLINE_RENDERTYPE_AFTER_EF
	) then
		return
	end

	local old_type = RenderType
	RenderType = render_type
	
	return old_type
	
end

function GetRenderType()
	
	return RenderType
	
end

function SetDoubleThickness( thickness )

	local old_thickness = OutlineThickness == 2
	OutlineThickness = thickness && 2 || 1
	
	return old_thickness
	
end

function IsDoubleThickness()
	
	return OutlineThickness == 2
	
end

local function Render()
	
	local scene = render.GetRenderTarget()
	render.CopyRenderTargetToTexture( StoreTexture )
	
	local w, h = ScrW(), ScrH()
	
	render.ClearStencil()
	
	render.SetStencilEnable( true )
		render.SuppressEngineLighting( true )
		
		render.SetStencilWriteMask( 0xFF )
		render.SetStencilTestMask( 0xFF )
		
		render.SetStencilCompareFunction( STENCIL_GREATER )
		render.SetStencilFailOperation( STENCIL_KEEP )
		
		cam.Start3D()
			render.SetBlend(1)
			
			for i = 1, ListSize do
				
				local reference = 0xFF - ( i - 1 )
				
				local data = List[ i ]
				local mode = data[ MODE ]
				local ents = data[ ENTS ]
				
				render.SetStencilReferenceValue( reference )
				
				if ( mode == OUTLINE_MODE_BOTH || mode == OUTLINE_MODE_VISIBLE ) then
					
					render.SetStencilZFailOperation( mode == OUTLINE_MODE_BOTH && STENCIL_REPLACE || STENCIL_KEEP )
					render.SetStencilPassOperation( STENCIL_REPLACE )
					
					for j = 1, #ents do
						
						local ent = ents[ j ]
						
						if ( IsValid( ent ) ) then
							
							RenderEnt = ent
							ent:DrawModel()
							
						end
						
					end
					
				elseif ( mode == OUTLINE_MODE_NOTVISIBLE ) then
					
					render.SetStencilZFailOperation( STENCIL_REPLACE )
					render.SetStencilPassOperation( STENCIL_KEEP )
					
					for j = 1, #ents do
						
						local ent = ents[ j ]
						
						if ( IsValid( ent ) ) then
							
							RenderEnt = ent
							ent:DrawModel()
							
						end
						
					end
					
					render.SetStencilCompareFunction( STENCIL_EQUAL )
					render.SetStencilZFailOperation( STENCIL_KEEP )
					render.SetStencilPassOperation( STENCIL_ZERO )
					
					for j = 1, #ents do
						
						local ent = ents[ j ]
						
						if ( IsValid( ent ) ) then
							
							RenderEnt = ent
							ent:DrawModel()
							
						end
						
					end
					
					render.SetStencilCompareFunction( STENCIL_GREATER )
					
				end
				
			end
			
			RenderEnt = NULL
			
			render.SetBlend(1)
		cam.End3D()
		
		render.SetStencilCompareFunction( STENCIL_EQUAL )
		render.SetStencilZFailOperation( STENCIL_KEEP )
		render.SetStencilPassOperation( STENCIL_KEEP )
		
		render.Clear( 0, 0, 0, 0, false, false )
		
		cam.Start2D()
			
			for i = 1, ListSize do
				
				local reference = 0xFF - ( i - 1 )
				
				render.SetStencilReferenceValue( reference )
				
				surface.SetDrawColor( List[ i ][ COLOR ] )
				surface.DrawRect( 0, 0, w, h )
				
			end
			
		cam.End2D()
		
		render.SuppressEngineLighting( false )
	render.SetStencilEnable( false )
	
	render.CopyRenderTargetToTexture( DrawTexture )
	
	render.SetRenderTarget( scene )
	CopyMat:SetTexture( "$basetexture", StoreTexture )
	render.SetMaterial( CopyMat )
	render.DrawScreenQuad()
	
	render.SetStencilEnable( true )
	
		render.SetStencilReferenceValue( 0 )
		
		render.SetStencilCompareFunction( STENCIL_EQUAL )
		
		OutlineMat:SetTexture( "$basetexture", DrawTexture )
		render.SetMaterial( OutlineMat )
		
		render.DrawScreenQuadEx( -OutlineThickness, -OutlineThickness, w ,h )
		render.DrawScreenQuadEx( -OutlineThickness, 0, w, h )
		render.DrawScreenQuadEx( -OutlineThickness, OutlineThickness, w, h )
		render.DrawScreenQuadEx( 0, -OutlineThickness, w, h )
		render.DrawScreenQuadEx( 0, OutlineThickness, w, h )
		render.DrawScreenQuadEx( OutlineThickness, -OutlineThickness, w, h )
		render.DrawScreenQuadEx( OutlineThickness, 0, w, h )
		render.DrawScreenQuadEx( OutlineThickness, OutlineThickness, w, h )
		
	render.SetStencilEnable( false )
	
	render.ClearDepth()		-- Allows to render view model and other stuff in front of outline
end

local function RenderOutlines()
	
	hook.Run( "SetupOutlines", Add )
	
	if ( ListSize == 0 ) then return end
	
	Render()
	
	List, ListSize = {}, 0
	
end

hook.Add( "PreDrawViewModels", "RenderOutlines", function()
	
	if ( RenderType == OUTLINE_RENDERTYPE_BEFORE_VM ) then
		
		RenderOutlines()
		
	end
	
end )

hook.Add( "PreDrawEffects", "RenderOutlines", function()
	
	if ( RenderType == OUTLINE_RENDERTYPE_BEFORE_EF ) then
		
		RenderOutlines()
		
	end
	
end )

hook.Add( "PostDrawEffects", "RenderOutlines", function()
	
	if ( RenderType == OUTLINE_RENDERTYPE_AFTER_EF ) then
		
		RenderOutlines()
		
	end
	
end )

-----------------------------------------------------------------------------------

hook.Add("SetupOutlines", "SuR_ScoutAbility", function()
	local ply = LocalPlayer()

	--------------------------

	if ply:Alive() and ply:IsSurvivor() then
		local tab1 = {}
		local tab2 = {}
		local tab3 = {}
		local tab4 = {}
		for _, ent in pairs(player.GetAll()) do
			if ent:IsZombie() or !ent:Alive() or ply:IsLineOfSightClear(ent) then continue end
			local alpha = 255/(ent:EyePos():Distance(EyePos())/500)
			if string.match(ent:GetSVAnim(), "executions") or string.match(ent:GetSVAnim(), "killmove") or string.match(ent:GetSVAnim(), "grappleloop") then
				tab4[#tab4+1] = ent
			elseif ent:IsDowned() then
				tab3[#tab3+1] = ent
			elseif alpha > 200 then
				tab1[#tab1+1] = ent
			else
				tab2[#tab2+1] = ent
			end
		end
		outline.Add(tab1, Color(200,200,200), 1)
		outline.Add(tab2, Color(120,120,120,180), 1)
		outline.Add(tab3, Color(200,20,20), 1)
		outline.Add(tab4, Color(200,120,20), 1)

		local tr = ply:GetEyeTrace().Entity
		if !ply:IsLockedAbilities() and IsValid(tr) and string.match(tr:GetClass(), "vj") and tr:GetPos():DistToSqr(ply:GetPos()) < 10000 and tr:GetNWBool('flinching', false) and tr:Health() <= 200 then
			outline.Add({tr}, Color(180,0,0), 2)
		end
	end

	--------------------------

	if not SuR.Config.Objective_Mode and ply:GetPlayerClass() == "scout" and ply:GetLevelState() >= 1 and ply:Alive() then
		local tab = {}
		for _, ent in pairs(ents.FindInSphere(ply:GetPos(), 400)) do
			if ent:GetClass() == "sur_loot" or ent:GetClass() == "sur_resupply" then
				tab[#tab+1] = ent
			end
		end
		outline.Add(tab, Color(150,200,150), 1)
	end

	--------------------------

	if SuR.Config.Objective_Mode and ply:Alive() then
		local wep = ply:GetWeapon("sur_fuel")
		if IsValid(wep) then
			local tab = {}
			local ftab = ents.FindByClass("sur_generator")
			for i = 1, #ftab do
				local v = ftab[i]
				if !v:GetNWBool('HaveFuel', false) then
					tab[#tab+1] = v
				end
			end
			outline.Add(tab, Color(220,200,0), 1)
		else
			local tab = {}
			local ftab = ents.FindByClass("sur_loot")
			for i = 1, #ftab do
				local v = ftab[i]
				if v:GetModel() == "models/surrounded/obj/w_jerrycanblue.mdl" then
					tab[#tab+1] = v
				end
			end
			outline.Add(tab, Color(240,160,0), 1)
		end
	end
end)