--[[
	This is an expanded lua library made by wacev
	I do not care if you use this, credit me if you want :)
	I made this because I found myself copying common functions from other files or they just didn't exist in the base raflua/lua lib
	Some of the function here were made by other people, I just included them here to have them all in one space
	
	Last Updated: 9/21/25
	
	For Documentation: 
	
		Returns tells you what type of data it returns, if there isn't a Returns field then it doesn't return anything.
		Param has 2 parts, the first part is the name is the example function, the second part is the type of data it uses
		Vararg mean that the function supports unlimited amount of args, indicated by " ... "
	
	How To Use:
	
		At the start of your popfile, in WaveSchedule put: LuaScriptFile "scripts/wacev_expanded_lib.lua" [$SIGSEGV]
	
	Documentation:
		
		----CVector----
		
		
			--@Retuns angle (vector but on a range of 0-360)
			--@Param start vector
			--@Param goal vector
			--Returns the angle that a forward movement from the start vector would need to get to the goal vector
			--Can be used with CEntity:SnapEyeAngles() to make them look at a point in space
			--Can also be used with util.Trace for the angle param
				start:FaceVector( goal )
				
			--@Returns x, y, and z in 3 different vars
				x,y,z = CVector:Unpack()
				
				
		----Table----
		
		
		--@Return the index of the chance that passed (say if 0.05 passed then it would return 2)
		--@Vararg table of numbers or a table within the table contain the index "chance"
			table.RandomChance( { 0.95, 0.05, ... } )
			table.RandomChance( { { name = "Hello", chance = 0.95 }, { name = "World", chance = 0.05 }, ... } )
		
	
		----Ents----
		
		
			--@Returns entity
			--@Param index number
			--@Param filter string, entity classname ( optional )
			--Returns the ent with the matching handle index
				ents.FindByHandleIndex( index, filter )
				
			--@Returns entity
			--@Param index number
			--@Param filter string, entity classname ( optional )
			--Returns the ent with the matching net index
				ents.FindByNetIndex( index, filter )
				
			--@Returns table (players)
				ents.GetAllRealPlayers()
				
			--@Returns table (players)
				ents.GetAllAlivePlayers()
				
			--@Returns table (players)
				ents.GetAllAliveRealPlayers()
			
			
		----CEntity----
		
		
			--@Returns entity
				CEntity:GetOwner()
				
			--@Returns entity
				CEntity:GetBuilder()
				
			--@Param scale number
			--@Param over_time number (optional)
			--@Param increment number (optional)
			--If over_time is left out, then instantly scale
				CEntity:SetModelScale( scale, over_time, increment )
				
			--@Returns number
			--Returns the number index for a team. 0 = unassigned, 1 = spec, 2 = red, 3 = blu, 5 = halloween bosses
				CEntity:GetTeam()
				
			--@Returns boolean
			--@Param team number
			--The number index for a team. 0 = unassigned, 1 = spec, 2 = red, 3 = blu, 5 = halloween bosses
				CEntity:OnTeam( team )
				
			--@Param particle string
			--@Param offset Vector
			--@Param lifetime number
			--@Param removeFunc function ( (optional) fire this function on remove, no args are passed to the function )
				CEntity:PlayParentedParticle( particle, offset, lifetime, removeFunc )
				
			--@Returns boolean
			--@Param target entity
				CEntity:HasLineOfSight( target )
				
			--@Returns string
				CEntity:GetModel()
		
		
		----Player----
		
			
			--@Returns boolean
				player:IsMidair()
			
			--@Returns Vector
			--Gets a player's eye pos
				player:GetEyePos()
				
			--@Returns Vector
			--Gets a player's eye angles, third vector is always 0
				player:GetEyeAngles()
			
			--@Returns Vector
			--Gets a player's eye angles, third vector isn't always 0, most of the time is the same as player:GetEyeAngles()
				player:GetEyeAnglesExact()
			
			--@Returns Vector
			--Gets a player's abs velocity in all three directions
				player:GetPlayerVelocity()
			
			--@Param velo Vector
				player:SetPlayerVelocity( velo )
				
			--@Returns entity
				player:GetActivePlayerWeapon()
				player:GetActiveWeapon()
			
			--@Param weapon entity
			--if the weapon entity does not exist then nothing will happen
			--bug: certain weapons like the thermal thruster become unusable when forced to
			--note: calling this every tick will force the player to use an item but the draw anim of other weapons will still play
				player:SetActivePlayerWeapon( weapon )
				player:SetActiveWeapon( weapon )
				
			--@Returns boolean
				player:IsInvuln()
				
			--@Returns boolean
			--Doesn't check for minicrit boost conds
				player:IsCritBoosted()
				
			--@Returns boolean
			--Doesn't check for critboost conds
				player:IsMiniCritBoosted()
			
			
		----Math----
			
			
			--@Returns number
			--@Param value number
			--@Param min number
			--@Param max number
				math.clamp( value, min, max )
			
			--@Returns number
			--@Param value number
			--@Param decimals number
			--Set decimals to 0 to have decimals removed
				math.round( value, decimals )
				
			--@Returns number
			--@Param min number
			--@Param max number
			--Returns a pseudorandom float between the given values ( normal math.random() only returns ints )
				math.randomfloat( min, max )
				
			--@Returns number
			--@Param value number
			--@Param startMin number
			--@Param startMax number
			--@Param endMin number
			--@Param endMax number
			--remaps the value from one range to another
				math.remap( value, startMin, startMax, endMin, endMax )
				
			--@Returns number
			--@Vararg numbers
				math.average( ... )
				
			--@Returns number
			--@Param start number
			--@Param goal number
			--@Param percent float (0-1)
			--percent values greater than 1 work, the return will just be greater than the goal
			--Returns a value between the start and the goal based on the percent
				lerp( start, goal, percent )
				
			--@Returns vector
			--@Param start vector
			--@Param end vector
			--@Param percent float (0-1)
			--percent values greater than 1 work, the return will just be greater than the goal
			--Returns a vector with each point being between the start and the goal based on the percent
				lerpvector( start, goal, percent )
			
		
		----Misc/Global Functions----
		
		
			--@Returns boolean
			--@Param player player
				IsValidAlivePlayer(player)
			
			--@Returns boolean
			--@Param player player
				IsValidAliveRealPlayer(player)
				
			--@Returns boolean
			--@Param player player
				IsValidRealPlayer(player)
			
			--@Returns boolean
			--@Param player player
				IsValidPlayer(player)
				
			--@Returns boolean
			--@Param damagetype number/global
			--@Param filter number/global
			--Filters one damagetype from the mess that is in dmginfo.DamageType from the ON_DAMAGE_RECIEVED* callbacks
				IsDamageType( damagetype, filter )
			
			--@Returns boolean
			--@Param player entity
			--@Param weapon entity or string
				IsValidWeapon( player, weapon )
				
			--@Returns boolean
			--@Param val any
				tobool( val )
				
		
		----Hooks/Functions----
		
		
			--@Param player entity
			--@Param text string
			--Add this function block as if it were OnGameTick()
				function OnPlayerChat( player, text ) end
			
			--@Param player entity
			--@Param cash number
			--Add this function block as if it were OnGameTick()
				function OnPickupCurrency( player, cash ) end
				
			--Add this function block as if it were OnGameTick()
			--No Params
				function OnTankDestroyed() end 
				
			--@Param player entity
			--@Param attacker entity
			--@Param weapon string ( internal name of the weapon that killed the player EX: back_scatter )
			--@Param dmgtype number ( combined number of all damagetypes )
			--@Param assister entity
			--@Param crit_type number ( 0 = no crit, 1 = mini-crit, 2 = full crit )
			--@Param silent_kill numberbool ( 0-1 )
			--Add this function block as if it were OnGameTick()
				function OnPlayerDeath(player, attacker, weapon, dmgtype, assister, crit_type, silent_kill) end
				
			--@Param sound string 
			--@Param pos Vector
				function PlaySound( sound, pos ) end
			
			--@Return entity ( the entity that was created )
			--@Param class string ( classname of the entity to crate )
			--@Param { ent_info } table ( entity info ( such as model and pos ) )
			--@Param client entity ( player to show it to )
				function CreateClientSidedProp( class, { ent_info }, client )
				
			--@Return Vector
			--@Param radius number
			--Make sure to add an offset otherwise it will get a point around the origin
				function GetRandomPointInCircle( radius ) end
				
			--@Return boolean ( did it hit anything )
			--@Param pos Vector
			--@Param mins Vector
			--@Param maxs Vector
			--@Param filter entity or table ( what to ignore )
			--This function can be intesive, do not overuse
				function CustomHitbox( pos, mins, maxs, filter )
	
	
	TODO/Roadmap:
	
	Disable buildings function
	Enable buildings function
	IsStunned() func
	IsDisabled() func (buildings only)
	GetPlayerClass() func
	util.BlastDamage() func
	GetParent() func
	Vector:Abs()
	
--]]

--Misc/Global Functions

timer.Simple(1, function()
	print( "Running Expanded Lib Version 1.5" )
end)

function IsValidAlivePlayer(ent)
	return IsValid(ent) and ent:IsPlayer() and ent:IsAlive();
end

function IsValidAliveRealPlayer(ent)
	return IsValid(ent) and ent:IsRealPlayer() and ent:IsAlive();
end

function IsValidRealPlayer(ent)
	return IsValid(ent) and ent:IsRealPlayer();
end

function IsValidPlayer(ent)
	return IsValid(ent) and ent:IsPlayer();
end

function IsDamageType( damagetype, filter )
	return ( damagetype & filter ) == filter
end

function IsValidWeapon( ply, wep )
	return wep and ( ply:GetPlayerItemByName( wep:GetItemName() ):IsValid() or ply:GetPlayerItemByName( wep ):IsValid() or ply:GetPlayerItemBySlot( wep ):IsValid() )
end

function tobool( val )
	if ( ( val == nil ) or ( val == false ) or ( val == 0 ) or ( val == "0" ) or ( val == "false" ) ) then return false end
	return true
end

function PlaySound(sound, position)
	local ent = ents.CreateWithKeys("info_target", {}, true, true);

	ent:SetAbsOrigin(position);
	ent:PlaySound(sound);
	ent:Remove();
end

function CreateClientSidedProp( classname, table, client )
	local prop = ents.CreateWithKeys( classname, table, true, true )
	
	prop:Enable()
	prop:HideToAll()
	prop:ShowTo( client )
	
	return prop
end

function GetRandomPointInCircle( Radius ) --welcome to fucked up math
	
	local pi = math.pi
	
	local theta = math.random() * Radius * pi
	local r = math.random() + math.random()
	
	if r >= 1 then
		r = 2 - r
	end
	
	--local dist = ( r * math.cos( theta ) )^2 + ( r * math.sin( theta ) )^2
	--dist = math.sqrt( dist )
	
	--print( dist * Radius )
	--print( ( r * math.cos( theta ) ) * Radius, ( r * math.sin( theta ) ) * Radius )
	
	return Vector( ( r * math.cos( theta ) ) * Radius, ( r * math.sin( theta ) ) * Radius, 0 )

end 

function CustomHitbox( origin, maxs, mins, filt )
	
	local st1 = origin + Vector( maxs[1], maxs[2], maxs[3] )
	local ed1 = origin + Vector( mins[1], mins[2], mins[3] )
	
	local st2 = origin + Vector( maxs[1], maxs[2], mins[3] )
	local ed2 = origin + Vector( mins[1], mins[2], maxs[3] )
	
	local st3 = origin + Vector( mins[1], maxs[2], maxs[3] )
	local ed3 = origin + Vector( maxs[1], mins[2], mins[3] )
	
	local st4 = origin + Vector( mins[1], maxs[2], mins[3] )
	local ed4 = origin + Vector( maxs[1], mins[2], maxs[3] )
	
	local tr1 = util.Trace( { start = st1, endpos = ed1, filter = filt } )
	local tr2 = util.Trace( { start = st2, endpos = ed2, filter = filt } )
	local tr3 = util.Trace( { start = st3, endpos = ed3, filter = filt } )
	local tr4 = util.Trace( { start = st4, endpos = ed4, filter = filt } )
	
	if tr1.Hit or tr2.Hit or tr3.Hit or tr4.Hit then 
		return false
	else
		return true
	end
	
end

--Ents

ents.FindByHandleIndex = function( filter, class )
	if not filter then return nil end
	if not class then
		for _, ent in pairs( ents.GetAll( ) ) do
			if ent:GetHandleIndex() == filter then 
				return ent 
			end
		end
		return nil
	else
		for _, ent in pairs( ents.FindAllByClass( class ) ) do
			if ent:GetHandleIndex() == filter then 
				return ent 
			end
		end
		return nil
	end
end

ents.FindByNetIndex = function( filter, class )
	if not filter then return nil end
	if not class then
		for _, ent in pairs( ents.GetAll( ) ) do
			if ent:GetNetIndex() == filter then 
				return ent 
			end
		end
		return nil
	else
		for _, ent in pairs( ents.FindAllByClass( class ) ) do
			if ent:GetNetIndex() == filter then 
				return ent 
			end
		end
		return nil
	end
end

ents.GetAllBots = function()
	local players = {}
	for index, ply in pairs(ents.GetAllPlayers()) do
		if ply:IsBot() then 
			table.insert( players, ply )
		end
	end
	return players
end

ents.GetAllRealPlayers = function()
	local players = {}
	for index, ply in pairs(ents.GetAllPlayers()) do
		if ply:IsRealPlayer() then 
			table.insert( players, ply )
		end
	end
	return players
end

ents.GetAllAlivePlayers = function()
	local ply = {}
	for _, player in pairs( ents.GetAllPlayers() ) do
		if player:IsPlayer() and player:IsAlive() then 
			table.insert( ply, player )
		end
	end
	return ply
end

ents.GetAllAliveRealPlayers = function()
	local ply = {}
	for _, player in pairs( ents.GetAllPlayers() ) do
		if player:IsRealPlayer() and player:IsAlive() then 
			table.insert( ply, player )
		end
	end
	return ply
end

--Table

table.RandomChance = function(t)
	local rand = math.random()
	local cumulativeProbability = 0

	for key, item in pairs(t) do
		if (type(item) == "table") then
			cumulativeProbability = cumulativeProbability + item.chance;
		else
			cumulativeProbability = cumulativeProbability + item;
		end

		if (rand <= cumulativeProbability) then
			return key;
		end
	end
end

--CVector

CVector.FaceVector = function(self, target)
	if target then
		local targetAngles = ( target - self ):ToAngles()
		targetAngles = Vector(targetAngles[1], targetAngles[2], 0)
		return targetAngles
	end
end

CVector.Unpack = function(self)
	return self[1], self[2], self[3]
end

--CEntity

CEntity.GetModel = function(self)
	if self:IsValid() then
		return self.m_ModelName
	end
end

CEntity.HasLineOfSight = function( self, tar )
	if self and tar then
		local ignore_tbl = { self, tar }
		for _, ply in pairs(ents.GetAllPlayers()) do
			table.insert(ignore_tbl, ply)
		end
		local Trace = util.Trace({
			start = self:GetAbsOrigin() + Vector(0,0,16),
			endpos = tar:GetEyePos(),
			mask = MASK_SOLID,
			filter = ents.GetAllPlayers()
		})
		local Trace2 = util.Trace({
			start = self:GetAbsOrigin(),
			endpos = tar:GetAbsOrigin(),
			mask = MASK_SOLID,
			filter = ents.GetAllPlayers()
		})
		if ( not Trace.Hit ) or ( not Trace2.Hit ) then
			return true
		else
			return false
		end
	end
end

CEntity.PlayParentedParticle = function(self, particle, offset, remove_after, RemoveFunction)
	if (not IsValid(self)) then return; end

	particle = ents.CreateWithKeys("info_particle_system", {
		effect_name=particle, start_active=1,
		["$modules"]="fakeparent", ["$positiononly"]=1,
	}, true, true)

	local entity_origin = self:GetAbsOrigin();
	if (offset) then entity_origin = entity_origin + offset; end

	particle.m_vecOrigin = entity_origin;
	particle["$SetFakeParent"](particle, self);
	particle["Start"](particle);

	timer.Create(remove_after, function()
		pcall(particle["Remove"], particle);
		if (RemoveFunction) then pcall(RemoveFunction); end
	end, 1);

	return particle;
end

CEntity.GetOwner = function(self)
	if self:IsValid() then
		return self.m_hOwnerEntity
	end
end

CEntity.GetBuilder = function(self)
	if self:IsValid() then
		return self.m_hBuilder
	end
end

CEntity.GetTeam = function(self)
	if self:IsValid() then
		return self.m_iTeamNum
	end
end

CEntity.OnTeam = function(self, team)
	if self:IsValid() then
		return self.m_iTeamNum == team
	end
end

CEntity.SetModelScale = function(self, scale, over_time, increment)
    if (not IsValid(self)) then return; end

    if (over_time and over_time > 0) then
        if (not increment or increment <= 0) then increment = 0.01; end
        local current_scale = self.m_flModelScale;

        if (scale - current_scale == 0) then
            return;
        elseif (scale - current_scale < 0) then
            increment = -increment;
        end

        local exec_times    = math.floor(math.abs((scale - current_scale) / increment));
        local counter       = 0;
        timer.Create(over_time / exec_times, function()
            if (not IsValid(self)) then return; end

            counter = counter + 1;
            current_scale = current_scale + increment;

            if (counter == exec_times and current_scale ~= scale) then
                self.m_flModelScale = scale;
            else
                self.m_flModelScale = current_scale;
            end
        end, exec_times);
    else
        self.m_flModelScale = scale;
    end
end

--Players

CEntity.IsInvuln = function(self)
    if (not IsValidPlayer(self)) then return end

    if (self:InCond(TF_COND_INVULNERABLE) or
        self:InCond(TF_COND_INVULNERABLE_CARD_EFFECT) or
        self:InCond(TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED) or
        self:InCond(TF_COND_INVULNERABLE_USER_BUFF)) then

		return true
    end

    return false
end

CEntity.IsCritBoosted = function(self)
    if (not IsValidPlayer(self)) then return end

    if (self:InCond(TF_COND_CRITBOOSTED) or
        self:InCond(TF_COND_CRITBOOSTED_PUMPKIN) or
        self:InCond(TF_COND_CRITBOOSTED_USER_BUFF) or
        self:InCond(TF_COND_CRITBOOSTED_BONUS_TIME) or
        self:InCond(TF_COND_CRITBOOSTED_CTF_CAPTURE) or
        self:InCond(TF_COND_CRITBOOSTED_ON_KILL) or
        self:InCond(TF_COND_CRITBOOSTED_RAGE_BUFF) or
        self:InCond(TF_COND_CRITBOOSTED_CARD_EFFECT) or
        self:InCond(TF_COND_CRITBOOSTED_RUNE_TEMP) or
        self:InCond(TF_COND_CRITBOOSTED_FIRST_BLOOD)) then

		return true
    end

    return false
end

CEntity.IsMiniCritBoosted = function(self)
    if (not IsValidPlayer(self)) then return end

    if (self:InCond(TF_COND_OFFENSEBUFF) or
        self:InCond(TF_COND_NOHEALINGDAMAGEBUFF) or
        self:InCond(TF_COND_ENERGY_BUFF) or
        self:InCond(TF_COND_MINICRITBOOSTED_ON_KILL)) then

		return true
    end

    return false
end

CEntity.IsMidair = function(self)
	if (IsValidAlivePlayer(self)) then
		return not (self.movetype == MOVETYPE_WALK and (self.m_fFlags & FL_ONGROUND ~= 0));
	end
end

CEntity.GetEyePos = function(self)
	if (not IsValidPlayer(self)) then return; end
	
	local eyepos = self:GetAbsOrigin();
	eyepos.z = eyepos.z + self["m_vecViewOffset[2]"];
	
	return eyepos
end

CEntity.GetEyeAngles = function(self)
	if (IsValidPlayer(self)) then
		return Vector( self.m_angEyeAngles[1], self.m_angEyeAngles[2], 0 )
	end
end

CEntity.GetEyeAnglesExact = function(self)
	if (IsValidPlayer(self)) then
		return Vector( self.m_angEyeAngles[1], self.m_angEyeAngles[2], self.m_angEyeAngles[3] )
	end
end

CEntity.GetActivePlayerWeapon = function(self)
	if (IsValidPlayer(self)) then
		return self.m_hActiveWeapon
	end
end

CEntity.GetActiveWeapon = function(self)
	if (IsValidPlayer(self)) then
		return self.m_hActiveWeapon
	end
end

CEntity.SetActivePlayerWeapon = function(self, wep)
	if ( IsValidPlayer(self) ) and ( self.m_hActiveWeapon ) and ( self.m_hActiveWeapon:IsValid() ) then
		if IsValidWeapon( self, wep ) and ( wep.m_iViewModelIndex ~= nil ) and ( wep.m_iViewModelIndex ~= 0 ) then
			self.m_hActiveWeapon = wep
		end
	end
end

CEntity.SetActiveWeapon = function(self, wep)
	if ( IsValidPlayer(self) ) and ( self.m_hActiveWeapon ) and ( self.m_hActiveWeapon:IsValid() ) then
		if IsValidWeapon( self, wep ) and ( wep.m_iViewModelIndex ~= nil ) and ( wep.m_iViewModelIndex ~= 0 ) then
			self.m_hActiveWeapon = wep
		end
	end
end

CEntity.GetPlayerVelocity = function(self)
	if (IsValidPlayer(self)) then
		return self.m_vecAbsVelocity
	end
end

CEntity.SetPlayerVelocity = function(self, vel)
	if (IsValidAlivePlayer(self)) then
		self.m_vecAbsVelocity = vel
	end
end

--Math

math.clamp = function( value, min, max )
	if min and max then
		if min > max then min, max = max, min end
		if value > max then
			value = max
		end
		if min > value then
			value = min
		end
		return value
	elseif min and ( not max ) then return math.min( value )
	elseif max and ( not min ) then return math.max( value ) 
	end
end

math.round = function(num, decimals)
	if (not decimals or decimals <= 0) then
		return math.floor(num + 0.5)
	else
		local mod = 10 ^ decimals
		return math.floor((num * mod) + 0.5) / mod
	end
end

math.randomfloat = function(m, n) 
	if (m) then
		if (n) then
			if (n == m) then return m;
			elseif (m > n) then m, n = n, m; end -- n should be greater than m
			
			local dif  = n - m;
			local mod  = dif * math.random();

			return m + mod;
			
		else
			return m * math.random();
		end
	else
		return math.random();
	end
end

math.remap = function( value, inMin, inMax, outMin, outMax )
	return outMin + ( ( ( value - inMin ) / ( inMax - inMin ) ) * ( outMax - outMin ) )
end

math.average = function( ... )
	local total = 0
	for _, val in pairs({ ... }) do
		total = total + val
	end
	
	total = total / #{ ... }
	return total
end

function lerp(start, goal, exp)
    return start + (goal - start) * exp
end

function lerpvector(start, goal, exp)
	local return_vec = Vector(0, 0, 0)
	
	return_vec[1] = start[1] + (goal[1] - start[1]) * exp
	return_vec[2] = start[2] + (goal[2] - start[2]) * exp
	return_vec[3] = start[3] + (goal[3] - start[3]) * exp
	
    return return_vec
end

--Hooks

local function PlayerChat(event_table)
	local text = event_table.text
	local userid = event_table.userid
	local player = ents.GetPlayerByUserId(userid)
	if ( player ) and ( player:IsValid() ) and ( player ~= nil ) then
		pcall( OnPlayerChat, player, text )
	end
end

local function PickupCurrency(event_table) --prints error
	-- local cash = event_table.currency
	-- local player = ents.FindByNetIndex( event_table.player, "player" )
	-- if ( player ) and ( player:IsValid() ) and ( player ~= nil ) then
		-- pcall( OnPickupCurrency, player, cash )
	-- end
end

local function TankDestroyed() 
	pcall( OnTankDestroyed )
end

local function PlayerDeath(event_table)
	
	local userid = event_table.userid
	local player = ents.GetPlayerByUserId( userid )
	local attacker_id = event_table.attacker
	local attacker = ents.GetPlayerByUserId( attacker_id )
	local weapon = event_table.weapon
	local damagetype = event_table.damagebits
	local assister_id = event_table.assister
	local assister = ents.GetPlayerByUserId( assister_id )
	local silent_kill = event_table.silent_kill
	local crit_type = event_table.crit_type
	
	if ( player ) and ( player:IsValid() ) and ( player ~= nil ) then
		pcall( OnPlayerDeath, player, attacker, weapon, damagetype, assister, crit_type, silent_kill )
	end
end

AddEventCallback("player_say", PlayerChat)
AddEventCallback("mvm_pickup_currency", PickupCurrency)
AddEventCallback("mvm_tank_destroyed_by_players", TankDestroyed)
AddEventCallback("player_death", PlayerDeath)