if SERVER then return end

function Color(r, g, b, a)
	return {r = r or 255, g = g or 255, b = b or 255, a = a or 255}
end

local hook = require("hook")
local concommand = require("concommand")
local draw = require("draw")

local CreateClientConVar = CreateClientConVar
local table = table
local surface = surface
local string = string
local draw = draw
local cam = cam
local render = render
local SetMaterialOverride = SetMaterialOverride

file.Write("commands.txt", "")

local Msg = Msg
local MsgN = MsgN

local _R = _R
local _G = _G

local _S = {}
	_S.Vars = {}
	_S.GM = {}
	_S.OnInit = {}
	_S.Player = {}

hook.Add("InitPostEntity", "Init", function()
	MsgN("SC: Initialized Post-Entity! Loading the OnInit functions...")
	for k, v in pairs(_S.OnInit) do
		v()
	end
	_S.OnInit = {}
	hook.Remove("InitPostEntity", "Init")
end)

function _S:Detour(func, newfunc)
	local temp_func = _G[func]
	_G[func] = function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16)
		return newfunc(temp_func, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16)
	end
end

function _S:CreateVar(id, default)
	local value = CreateClientConVar("sc_" .. id, default, true, false)
	table.insert(self.Vars, {id = id, object = value})
end

function _S:GetBoolVar(id)
	for k, v in pairs(self.Vars) do
		if v.id == id then
			return v.object:GetBool()
		end
	end
end

function _S:GetVar(id)
	for k, v in pairs(self.Vars) do
		if v.id == id then
			return v.object
		end
	end
end

function _S:GetStringVar(id)
	for k, v in pairs(self.Vars) do
		if v.id == id then
			return v.object:GetString()
		end
	end
end

function _S:GetFloatVar(id)
	for k, v in pairs(self.Vars) do
		if v.id == id then
			return v.object:GetFloat()
		end
	end
end

function _S:GetIntVar(id)
	for k, v in pairs(self.Vars) do
		if v.id == id then
			return v.object:GetInt()
		end
	end
end

_S:CreateVar("ghostvar", 0)
_S:Detour("GetConVar", function(FUNC, convar)
	if string.match(convar, "sc_") then
		return nil
	else
		if not (string.match(convar, "sv_cheats") and string.match(convar, "mat_fullbright") and string.match(convar, "host_timescale")) then
			return FUNC(convar)
		else
			return _S:GetVar("ghostvar")
		end
	end
end)

_S:Detour("GetConVarNumber", function(FUNC, convar)
	if string.match(convar, "sc_") then
		return nil
	else
		if not (string.match(convar, "sv_cheats") and string.match(convar, "mat_fullbright") and string.match(convar, "host_timescale")) then
			return FUNC(convar)
		else
			return _S:GetVar("ghostvar")
		end
	end
end)

function _S:AppendToGM(t, func)
	if self.GM[t] != nil then
		self.GM["TEMP_" .. t] = self.GM[t]
		local TIndex = self.GM["TEMP_" .. t]
		self.GM[t] = function(self)
			TIndex(self)
			func()
		end
	end
end

function _S:DoOnInit(func)
	table.insert(_S.OnInit, func)
end
_S:DoOnInit(function() _S.GM = GAMEMODE end)

function _S:Hook(t, func, configid, default)
	local function Do()
		if configid != nil then
			MsgN("SC: Hooked '" .. t .. "' to '" .. tostring(func) .. "' WITH configuration value of '" .. configid .. "'!")
			self:CreateVar(configid, default or 0)
			self:AppendToGM(t, function()
				if self:GetBoolVar(configid) then
					func()
				end
			end)
		else
			MsgN("SC: Hooked '" .. t .. "' to '" .. tostring(func) .. "'!")
			self:AppendToGM(t, func)
		end
	end

	_S:DoOnInit(Do)
end

function _S:AddCommand(cmd, func)
	concommand.Add("sc_" .. cmd, func)
end

function _S:AddPlusCommand(cmd, func, htype, on_callback, off_callback)
	local on = false
	_S:Hook(htype, function()
		if on then
			func()
		end
	end)
	concommand.Add("+sc_" .. cmd, function(ply, cmd, args)
		on = true
		if on_callback != nil then
			on_callback(args)
		end
	end)
	concommand.Add("-sc_" .. cmd, function(ply, cmd, args)
		on = false
		if off_callback != nil then
			off_callback(args)
		end
	end)
end

function _S:RunOnConfig(configid, func)
	if self:GetBoolVar(configid) then
		func()
	end
end

function _S.Player:HeadPos(ply)
	if ValidEntity(ply) then
		if ply.GetBonePosition != nil then
			local pos = ply:GetBonePosition(ply:LookupBone("ValveBiped.Bip01_Head1"))
			if pos != nil then
				return pos
			end
		else
			return ply:GetPos() + Vector(0, 0, 64)
		end
	else
		return Vector()
	end
end

local function GetNearestAimPlayer()
	local lowangle = 360
	local target

	for k, v in pairs(player.GetAll()) do
		if ValidEntity(v) and v != LocalPlayer() then
			local ang = EyeAngles()
			local moveang = (v:EyePos() - EyePos()):Angle()
			local dist = ang:Forward():Distance(moveang:Forward()) * 90
			if dist < lowangle then
				lowangle = dist
				target = v
			end
		end
	end

	return target
end

local function ColorBrightness(col, amount)
	if amount <= 0 then
		return Color(0, 0, 0, col.a)
	end
	if amount >= 100 then
		return col
	end

	local amt = (1 - amount)
	local _col = col
		_col.r = _col.r - (_col.r / amt)
		_col.g = _col.g - (_col.g / amt)
		_col.b = _col.b - (_col.b / amt)

	return _col
end

local function RenderBones(ent)
	if ValidEntity(ent) and ent != LocalPlayer() then
		if _S.Player:HeadPos(ent):Distance(EyePos()) <= 2000 then
			local Bones = {}
			for i = 0, 128 do
				local pos = ent:GetBonePosition(i)
				if pos != nil then
					local lpos = ent:WorldToLocal(pos)
					if (lpos.x == 0 or lpos.x == -0) and (lpos.y == 0 or lpos.y == -0) and (lpos.z == 0 or lpos.z == -0) then
					else
						Bones[i] = pos
					end
				end
			end

			for k, v in pairs(Bones) do
				local pos = ent:GetBonePosition(tonumber(k))
				local parent = ent:GetBoneParent(tonumber(k))
				if _S.Player:HeadPos(ent):Distance(EyePos()) <= 1500 then
					local size = 8 - (_S.Player:HeadPos(ent):Distance(EyePos()) / 300)
					local pos2 = pos:ToScreen()
					if size > 0 then
						surface.SetDrawColor(Color(0, 0, 255, 255))
						surface.DrawLine(pos2.x - (size / 2), pos2.y, pos2.x + (size / 2), pos2.y)
						surface.DrawLine(pos2.x, pos2.y - (size / 2), pos2.x, pos2.y + (size / 2))
					end
				end
				if parent != nil and parent != 0 then
					local ppos = Bones[parent]
					if ppos != nil then
						pos = pos:ToScreen()
						ppos = ppos:ToScreen()
						surface.SetDrawColor(Color(255, 255, 0, 255))
						surface.DrawLine(pos.x, pos.y, ppos.x, ppos.y)
					end
				end
			end
		end
	end
end

// ESP
local ESP_PlayerFont = "Arial"
local ESP_PlayerFontSize = 17
local ESP_PlayerFontColor = Color(0, 102, 204, 255)

local ESP_PlayerInfoFont = "Tahoma"
local ESP_PlayerInfoFontSize = 12
local ESP_PlayerInfoFontColor = Color(255, 255, 255, 255)
local ESP_PlayerInfoValueFontColor = Color(204, 102, 102, 255)

local ESP_PlayerColor = {r = 1, g = 0, b = 0}
local ESP_PlayerMaterial = Material("models/debug/debugwhite")

_S:CreateVar("esp_drawplayers", 1)
_S:CreateVar("esp_drawplayerentities", 1)
_S:CreateVar("esp_drawplayerinfo", 1)
_S:CreateVar("esp_drawplayer_nearestaim_bones", 1)
_S:CreateVar("esp_performancemode", 0)

local function ESP_Draw()
	cam.Start3D(EyePos(), EyeAngles())
		cam.Start2D()
			_S:RunOnConfig("esp_drawplayers", function()
				for k, v in pairs(player.GetAll()) do
					if ValidEntity(v) and v != LocalPlayer() then
						local pos = _S.Player:HeadPos(v):ToScreen()
						local nick = v:Nick()

						if _S:GetBoolVar("esp_performancemode") then
						else
							local oldypos = pos.y
							pos.y = pos.y - ESP_PlayerFontSize - 4
							local pl_info = {}
							local health_color = Color(153, 255, 153)

							local pl_health = v:Health() .. "%"

							if v:Health() <= 0 then
								pl_health = "DEAD"
								health_color = Color(255, 153, 153)
							end

							local weapon = "Unknown"
							if ValidEntity(v:GetActiveWeapon()) then
								weapon = v:GetActiveWeapon():GetClass()
							end

							local rank = "User"
							local rank_color = Color(255, 255, 255, 255)

							if v:IsSuperAdmin() then
								rank = "Super Admin"
								rank_color = ESP_PlayerInfoValueFontColor
							elseif v:IsAdmin() and not v:IsSuperAdmin() then
								rank = "Admin"
								rank_color = ESP_PlayerInfoValueFontColor
							end

							table.insert(pl_info, {info = "Health", value = pl_health, col = health_color})
							table.insert(pl_info, {info = "Distance", value = math.Round(v:EyePos():Distance(EyePos()))})
							table.insert(pl_info, {info = "Weapon", value = weapon})
							table.insert(pl_info, {info = "Rank", value = rank, col = rank_color})

							if _S:GetBoolVar("esp_drawplayerinfo") then
								pos.y = pos.y - (ESP_PlayerInfoFontSize * #pl_info)
							end

							local tcol = team.GetColor(v:Team())
							surface.SetFont("ESP_PlayerFont")
							draw.RoundedBox(4, pos.x - (surface.GetTextSize(nick) / 2) - 3, pos.y - 3, surface.GetTextSize(nick) + 6, ESP_PlayerFontSize + 3, Color(tcol.r, tcol.g, tcol.b, 96)) 

							draw.SimpleTextOutlined(nick, "ESP_PlayerFont", pos.x, pos.y - 2, ESP_PlayerFontColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, 1, Color(0, 0, 0, 255))
							if _S:GetBoolVar("esp_drawplayerinfo") then
								for k, v in ipairs(pl_info) do
									surface.SetFont("ESP_PlayerInfoFont")
									draw.SimpleTextOutlined(v.info .. ": ", "ESP_PlayerInfoFont", pos.x - (surface.GetTextSize(v.info .. ": " .. v.value) / 2), pos.y + ESP_PlayerFontSize - 2 + ((k - 1) * (ESP_PlayerInfoFontSize - 2)), ESP_PlayerInfoFontColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, Color(0, 0, 0, 128))
									if v.col != nil then
										draw.SimpleTextOutlined(v.value, "ESP_PlayerInfoFont", pos.x - (surface.GetTextSize(v.info .. ": " .. v.value) / 2) + surface.GetTextSize(v.info .. ": "), pos.y + ESP_PlayerFontSize - 2 + ((k - 1) * (ESP_PlayerInfoFontSize - 2)), v.col, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, Color(0, 0, 0, 128))
									else
										draw.SimpleTextOutlined(v.value, "ESP_PlayerInfoFont", pos.x - (surface.GetTextSize(v.info .. ": " .. v.value) / 2) + surface.GetTextSize(v.info .. ": "), pos.y + ESP_PlayerFontSize - 2 + ((k - 1) * (ESP_PlayerInfoFontSize - 2)), ESP_PlayerInfoValueFontColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, Color(0, 0, 0, 128))
									end
								end
							end
						end
					end
				end
			end)
		cam.End2D()
	cam.End3D()
end

local function ESP_Render()
	cam.Start3D(EyePos(), EyeAngles())
		_S:RunOnConfig("esp_drawplayerentities", function()
			cam.IgnoreZ(true)
			render.SetColorModulation(ESP_PlayerColor.r, ESP_PlayerColor.g, ESP_PlayerColor.b)
			render.SetBlend(0.3)
			render.SuppressEngineLighting(true)
			SetMaterialOverride(ESP_PlayerMaterial)
				for k, v in pairs(player.GetAll()) do
					if ValidEntity(v) and v != LocalPlayer() then
						if v:Alive() then
							v:DrawModel()
						end
					end
				end
			SetMaterialOverride(nil)
			render.SuppressEngineLighting(false)
			render.SetBlend(1)
			render.SetColorModulation(1, 1, 1)
			cam.IgnoreZ(false)
		end)
		cam.Start2D()
			_S:RunOnConfig("esp_drawplayer_nearestaim_bones", function()
				local ply = GetNearestAimPlayer()
				if ValidEntity(ply) then
					if ply:Alive() then
						RenderBones(ply)
					end
				end
			end)
		cam.End2D()
	cam.End3D()
end

_S:Hook("HUDPaint", ESP_Draw, "esp_enabled", 1)
_S:Hook("RenderScreenspaceEffects", ESP_Render, "esp_enabled", 1)

surface.CreateFont(ESP_PlayerFont, ESP_PlayerFontSize, 700, false, false, "ESP_PlayerFont")
surface.CreateFont(ESP_PlayerInfoFont, ESP_PlayerInfoFontSize, 700, false, false, "ESP_PlayerInfoFont")

_S:AddCommand("rotate", function() local ang = EyeAngles() LocalPlayer():SetEyeAngles(Angle(ang.p, ang.y - 180, 0)) end)

// Bhop

_S:AddPlusCommand("bhop", function() if LocalPlayer():IsOnGround() then RunConsoleCommand("+jump") else RunConsoleCommand("-jump") end end, "Tick", nil, function() RunConsoleCommand("-jump") end)

// Render statistics

local Stat_TextCount = 0
local Stat_ObjectCount = 0
local Stat_TexturedCount = 0

local DrawRect = surface.DrawRect
function surface.DrawRect(x, y, w, h)
	if x >= 0 and x <= ScrW() and y >= 0 and y <= ScrH() then else return end

	_S:RunOnConfig("renderstatistics", function()
		Stat_ObjectCount = Stat_ObjectCount + 1
	end)

	DrawRect(x, y, w, h)
end

local DrawOutlinedRect = surface.DrawOutlinedRect
function surface.DrawOutlinedRect(x, y, w, h)
	if x >= 0 and x <= ScrW() and y >= 0 and y <= ScrH() then else return end

	_S:RunOnConfig("renderstatistics", function()
		Stat_ObjectCount = Stat_ObjectCount + 1
	end)

	DrawOutlinedRect(x, y, w, h)
end

local DrawCircle = surface.DrawCircle
function surface.DrawCircle(size, x, y, color)
	if x >= 0 and x <= ScrW() and y >= 0 and y <= ScrH() then else return end

	_S:RunOnConfig("renderstatistics", function()
		Stat_ObjectCount = Stat_ObjectCount + 1
	end)

	DrawCircle(size, x, y, color)
end

local DrawLine = surface.DrawLine
function surface.DrawLine(x, y, x2, y2)
	if x >= 0 and x <= ScrW() and y >= 0 and y <= ScrH() then else return end

	_S:RunOnConfig("renderstatistics", function()
		Stat_ObjectCount = Stat_ObjectCount + 1
	end)

	DrawLine(x, y, x2, y2)
end

local DrawPoly = surface.DrawPoly
function surface.DrawPoly(pdata)
	_S:RunOnConfig("renderstatistics", function()
		Stat_ObjectCount = Stat_ObjectCount + 1
	end)

	DrawPoly(pdata)
end

local DrawTexturedRectRotated = surface.DrawTexturedRectRotated
function surface.DrawTexturedRectRotated(x, y, w, h, rot)
	if x >= 0 and x <= ScrW() and y >= 0 and y <= ScrH() then else return end

	_S:RunOnConfig("renderstatistics", function()
		Stat_TexturedCount = Stat_TexturedCount + 1
	end)

	DrawTexturedRectRotated(x, y, w, h, rot)
end

local DrawTexturedRectUV = surface.DrawTexturedRectUV
function surface.DrawTexturedRectUV(x, y, w, h, tw, th)
	if x >= 0 and x <= ScrW() and y >= 0 and y <= ScrH() then else return end

	_S:RunOnConfig("renderstatistics", function()
		Stat_TexturedCount = Stat_TexturedCount + 1
	end)

	DrawTexturedRectUV(x, y, w, h, tw, th)
end

local DrawTexturedRect = surface.DrawTexturedRect
function surface.DrawTexturedRect(x, y, w, h)
	if x >= 0 and x <= ScrW() and y >= 0 and y <= ScrH() then else return end

	_S:RunOnConfig("renderstatistics", function()
		Stat_TexturedCount = Stat_TexturedCount + 1
	end)

	DrawTexturedRect(x, y, w, h)
end

local DrawText = surface.DrawText
function surface.DrawText(text)
	_S:RunOnConfig("renderstatistics", function()
		Stat_TextCount = Stat_TextCount + 1
	end)

	DrawText(text)
end

local Stat_StatisticFont = "Calibri"
local Stat_StatisticFontSize = 13
local Stat_StatisticFont_TextColor = Color(255, 204, 0)
local Stat_StatisticFont_ObjectColor = Color(0, 153, 204)
local Stat_StatisticFont_TexturedColor = Color(0, 204, 153)

local function Stat_Draw()
	local w = 200
	local h = 140
	local x = ScrW() - 10 - w
	local y = 10

	surface.SetDrawColor(Color(0, 0, 0, 192))
	surface.DrawRect(x, y, w, h)
	surface.SetDrawColor(Color(128, 128, 128, 192))
	surface.DrawOutlinedRect(x + 10, y + 70, w - 20, h - 80)

	local MaxCount = 3000
	if Stat_TextCount > MaxCount then MaxCount = Stat_TextCount end
	if Stat_ObjectCount > MaxCount then MaxCount = Stat_ObjectCount end
	if Stat_TexturedCount > MaxCount then MaxCount = Stat_TexturedCount end

	surface.SetDrawColor(Stat_StatisticFont_TextColor)
	surface.DrawRect(x + 20, y + 120 - ((Stat_TextCount / MaxCount) * 60), 20, ((Stat_TextCount / MaxCount) * 60) + 1)
	surface.SetDrawColor(Stat_StatisticFont_ObjectColor)
	surface.DrawRect(x + 50, y + 120 - ((Stat_ObjectCount / MaxCount) * 60), 20, ((Stat_ObjectCount / MaxCount) * 60) + 1)
	surface.SetDrawColor(Stat_StatisticFont_TexturedColor)
	surface.DrawRect(x + 80, y + 120 - ((Stat_TexturedCount / MaxCount) * 60), 20, ((Stat_TexturedCount / MaxCount) * 60) + 1)

	draw.SimpleText("Text Rendering Count: " .. Stat_TextCount, "Stat_StatisticFont", x + 10, y + 10, Stat_StatisticFont_TextColor)
	draw.SimpleText("Object Rendering Count: " .. Stat_ObjectCount, "Stat_StatisticFont", x + 10, y + 10 + Stat_StatisticFontSize, Stat_StatisticFont_ObjectColor)
	draw.SimpleText("Textured Rendering Count: " .. Stat_TexturedCount, "Stat_StatisticFont", x + 10, y + 10 + (Stat_StatisticFontSize * 2), Stat_StatisticFont_TexturedColor)

	Stat_TextCount = 0
	Stat_ObjectCount = 0
	Stat_TexturedCount = 0
end

_S:Hook("HUDPaint", Stat_Draw, "renderstatistics", 1)
surface.CreateFont(Stat_StatisticFont, Stat_StatisticFontSize, 400, false, false, "Stat_StatisticFont")