/*
	Asb -> A simple bot [ Inspoir ]
	
	Revision	: 1.00.0
	Description	: Provides Asb with an Aimbot & ViewCorrection
*/

local MODULE = {
	Name = "Aimbot & ViewCorrection",
	Hooks = {},
	Help = {
		"\n  -> Aimbot :",
		"    asb_bot_bone	: What bone to aim at			: 0-4",
		"    			NECK, SHOULDERS, SPINE, ABDOMIN\n",
		"    asb_bot_filter	: How the bot filters targets		: 0-2",
		"    			LOWEST HEALTH, HIGHEST HEALTH, CLOSEST\n",
		"    asb_bot_fov		: The bot's field of view		: 0-180",
		"    asb_bot_los		: If the bot loses Line of Sight	: BOOL",
		"    asb_bot_predict	: Whether the bot will predict shots	:BOOL",
		"    asb_bot_shoot	: Whether the bot will Autoshoot	: BOOL\n",
		"\n  -> View Correction :",
		"    asb_viewcorrect	: Enables View Correction		: BOOL\n",
	}
}

// Local vars & functions {
	local self, target, targetlist = LocalPlayer, nil, {}
	local aiming, nextshot, update = false, CurTime(), CurTime()
	// convars {
		local _bone		= CreateClientConVar("asb_bot_bone", "2", true, false)
		local _filter	= CreateClientConVar("asb_bot_filter", "0", true, false)
		local _fov		= CreateClientConVar("asb_bot_fov", "5", true, false)
		local _los		= CreateClientConVar("asb_bot_los", "1", true, false)
		local _predict	= CreateClientConVar("asb_bot_predict", "1", true, false)
		local _shoot	= CreateClientConVar("asb_bot_shoot", "1", true, false)
		local _view		= CreateClientConVar("asb_viewcorrect", "1", true, false)
		local _team		= CreateClientConVar("asb_team", "1", true, false)
	// }
	local bones = {
		"ValveBiped.Bip01_Neck1",
		"ValveBiped.Bip01_Spine4",
		"ValveBiped.Bip01_Spine2",
		"ValveBiped.Bip01_Spine1"
	}
	local seizefire = {
		weapon_crowbar = true,	
		weapon_phycannon = true,
		weapon_physgun = true,
		weapon_pistol = false,
		weapon_357 = false,
		weapon_smg = false,
		weapon_ar2 = false,
		weapon_shotgun = false,
		weapon_crossbow = false,
		weapon_rpg = false,
		weapon_grenade = true,
		gmod_tool = true,
	}
	local delay = {
		weapon_pistol = 0.25
	}
	local prediction = {
		weapon_pistol = 40000,
		weapon_357 = 20500,
		weapon_smg = 39000,
		weapon_ar2 = 39000,
		weapon_shotgun = 35000,
		weapon_crossbow = 3485,
		weapon_rpg = 0
	}
	
	// Functions {
		// Math {
			local function Distance( a, b )
				/* Returns the distance between 2 entities */
				return a:GetPos():Distance(b:GetPos())
			end
			
			local function Normal( a )
				/* Returns a normalized Angle */
				return math.abs(math.NormalizeAngle(a))
			end
			
			local function InRange( v, a, z )
				/* Returns whether a Value is between a Min & Max */
				return ( v >= a && v <= z )
			end
		// }
		
		// Entity stuff {
			local function GetBone( e )
				/* Returns the position of a bone */
				if ( !ValidEntity(e) ) then return end
				if ( !_bone:GetBool() ) then return e:LocalToWorld(e:OBBCenter()) end
				return e:GetBonePosition(e:LookupBone(bones[math.Clamp(_bone:GetInt(), 1, 4)]))
			end
			
			local function GetShootTrace( e )
				/* Traces from the player's shoot position out 163840 units */
				local t = {
					start	= self():GetShootPos(),
					endpos		= self():GetShootPos() + (self():GetAimVector() * 163840),
					filter		= { self() }
				}
				
				return util.TraceLine(t)
			end
			
			local function GetTrace( e, i )
				/* Returns whether the entity(e) is visible
					i: #0, Entity == e; #1 HitWorld
				*/
				local t = {
					start	= self():GetShootPos(),
					endpos		= GetBone(e),
					filter		= { self() }
				}
				
				return ( util.TraceLine(t).Entity == e && i == 0 || util.TraceLine(t).HitWorld && i == 1  )
			end
			
			local function ValidTarget( e )
				/* ValidTarget returns whether an Entity is considered an Enemy */
				if ( !ValidEntity(e) ) || ( e == self() ) || ( !e:IsNPC() && !e:IsPlayer() ) then return false end

				m = e:GetMoveType()	
				
				if ( m == MOVETYPE_NONE ) then return false end
				if ( e:IsPlayer() ) then
					if ( !e:Alive() ) || ( m == MOVETYPE_OBSERVER ) || ( e:Team() == self():Team() && !_team:GetBool() ) then return false end
				end
					
				return true
			end
		// }
		
		local function GetFilter( e, c )
			/* This returns whether c meets the filter determined by _filter */
			if ( !ValidEntity(e) || !ValidEntity(c) ) then return false end
			local f = math.Clamp(_filter:GetInt(), 0, 2)
			
			if ( f == 0 && e:Health() < c:Health() ) || ( f == 1 && e:Health() > c:Health() ) 
			|| ( f == 2 && Distance(self(), e) < Distance(self(), c) ) then return true end
			
			return false
		end
		
		local function SimulateShot()
			/* Simulates the shooting of a weapon */
			local w = self():GetActiveWeapon()
			if ( CurTime() < nextshot || !w:IsWeapon() || seizefire[w:GetClass()] ) then return end
			
			local n = (delay[w:GetClass()] || 0.025); if ( w.Primary && w.Primary.Delay ) then n = w.Primary.Delay end
			self():ConCommand("+attack; wait 2; -attack;"); nextshot = CurTime() + n
		end
		
	// }
// }

// Functions {
	function MODULE.Add( e )
		/* This adds entities to the targetlist */
		if ( ValidTarget(e) && !table.HasValue(targetlist, e) ) then table.insert(targetlist, e) end
	end
	
	function MODULE.BuildList()
		/* This attempts to add entities to targetlist when it is empty */
		for _, e in pairs( ents.GetAll() ) do
			MODULE.Add(e)
		end
	end
	
	function MODULE.Bot( u )
		/* This compensates to aim at the current target */
		viewangle = u:GetViewAngles()
		if ( !aiming || !ValidTarget(target) || _los:GetBool() && GetTrace(target, 1) ) then target = nil; return end
		local vector = GetBone(target) - self():GetShootPos()
		if ( _predict:GetBool() ) then
			local position, weapon = self():GetPos(), self():GetActiveWeapon()
			local speed, distance = position:Distance(position + self():GetVelocity()), Distance(self(), target)
			
			/* Player & Target prediction */
			local p, t = Vector(0, 0, 0), Vector(0, 0, 0)
			if ( distance < 4000 && speed > 0 ) then p = self():GetVelocity() * distance / (speed * distance * 0.125) end
			if ( weapon:IsWeapon() && prediction[weapon:GetClass()] != 0 ) then t = target:GetVelocity() * distance / (prediction[weapon:GetClass()] || 150000) end
			
			vector = vector - p + t
		end

		u:SetViewAngles(vector:Angle()); viewangle = vector:Angle()
		
		// Shoot {
			if ( !_shoot:GetBool() || u:KeyDown(IN_ATTACK) ) then return end
			
			local t = GetShootTrace(target)
			if ( t.Entity == target && InRange(t.HitGroup, 0, 3) || _predict:GetBool() && GetTrace(target, 0) ) then SimulateShot() end
		// }
	end
	
	function MODULE.Select()
		/* This makes sure the current target is valid, but only when aiming */
		if ( !aiming || ValidTarget(target) ) then return end

		target = nil
		local a = self():GetPos()
		if ( #targetlist == 0 || CurTime() >= update ) then MODULE.BuildList(); update = CurTime() + 0.1 end
		for _, e in pairs( targetlist ) do
			if ( !ValidEntity(e) ) then 
				table.remove(targetlist, _)
			elseif ( !target || GetFilter(e, target) ) then 
				a = self():GetAimVector():Angle() - (GetBone(e) - self():GetShootPos()):Angle()
				if ( Normal(a.y) < _fov:GetInt() && Normal(a.p) < _fov:GetInt() && GetTrace(e, 0) ) then target = e end 
			end
		end
	end
	
	function MODULE.View( u, o, a, f )
		/* This Corrects the player's view */
		if ( !_view:GetBool() ) then return end
		
		local w = self():GetActiveWeapon()
		if ( w.Primary ) then w.Primary.Recoil = 0 end
		
		return { origin = o, angles = viewangle, fov = f }
	end
	
	function MODULE.On()
		aiming = true
	end
	
	function MODULE.Off()
		aiming = false
		target = nil
	end
// }

// Register Hooks {
	concommand.Add("+asb_bot", MODULE.On)
	concommand.Add("-asb_bot", MODULE.Off)

	MODULE.Hooks[MODULE.View] = "CalcView"
	MODULE.Hooks[MODULE.Bot] = "CreateMove"
	MODULE.Hooks[MODULE.Add] = "OnEntityCreated"
	MODULE.Hooks[MODULE.Select] = "Think"
	Asb:Register(MODULE)
// }
