Finding Players in an Arc Cone

SNIPPET TITLE: FindPlayersInArcCone
SKILL LEVEL: Just call the function.

Taken straight from the Rangefinder API which I intend to release later, FindPlayersInArcCone finds players in an arc cone, which is defined by a maximum distance from the origin point and a maximum angle from the origin's forward facing. If you've got something that's got an angle and range to it, such as a vision cone or a shotgun spread, then this has got you covered--and it also covers angles from the central axis between 90 and 180 degrees too, just in case. Any more and you've got a sphere with some possible overlap.

This code can be quite useful with Multicast Beam VFX, as while it can't detect what the VFX is currently zapping, it can tell you what players are within its area of effect. To make sure the two line up, the max range of the VFX (in meters) should be the function's range/100, the max angle of the VFX should be the function's angle, and the origin should either be the VFX or an object located and facing the same way as it. Also, setting the VFX's horizontal spread, vertical spread, and max distance variation to 0 is recommended for this application.

Likewise, it can also be useful for shotgun style weapons or any other damage application with a similar spread pattern. I used it with the Watersplash Large VFX in my water shotgun to make the impressive plume of water actually deal damage to enemy players caught within it, for example.

And of course, this function is a simple way of detecting players within the field of view of an NPC, for example.


--[[ Origin should be an Core Object or Transform positioned at the point of the arc cone
and facing its central axis. Range is how far the arc cone extends in centimeters. 
Angle is the maximum angle in degrees from the central axis included in the arc cone.
Parameters are the same as those used in Game.FindPlayersInSphere().
function FindPlayersInArcCone(origin, range, angle, parameters)
	-- Find players in sphere, then reduce to just those within the angle.
	local playersInRange = nil
	if parameters and Object.IsValid(parameters) then --Is this needed? No idea. It's here just in case.
		playersInRange = Game.FindPlayersInSphere(origin:GetWorldPosition(), range, parameters)
		playersInRange = Game.FindPlayersInSphere(origin:GetWorldPosition(), range)
	local playersInAngle = {}
	-- Add only those within the angle, then return that table.
    for _, player in ipairs(playersInRange) do
    	if IsObjectInAngle(origin, player:GetWorldPosition(), angle) then
        	table.insert(playersInAngle, player)
    return playersInAngle

-- Located separately because it's a useful function on its own.
-- Also, it's used many times in the Rangefinder API.
function IsObjectInAngle(origin, object, angle)
	local originPos = origin:GetWorldPosition()
	local originAim = origin:GetWorldTransform():GetForwardVector()
	local objectPos = object:GetWorldPosition()
	local originToObject = (objectPos - originPos):GetNormalized()
	local checkForward = angle <= 90
	if angle >= 180 then
		error("Angle must be less than 180 degrees to prevent overlap.")
	elseif angle < 0 then
		error("Angle cannot be negative.")
	-- Based off the Vector3^Vector3 example on Core Documentation, but expanded to accept any angle.
	-- If angle is <= 90 then check for players in the forward direction. 
    -- Otherwise, check it's not in the backward direction.
	if (originToObject .. originAim > 0) == checkForward then
		-- If the angle is exactly 90, return true because the object is in front. 
		if angle == 90 then return true end
		if ((originToObject ^ originAim).size <= math.sin(math.rad(angle))) then
            --If checking forward, we want objects within that angle. 
            --If checking backwards, we don't want objects within that angle.
            return checkForward
	--If we check forwards, anything behind is not a part of the angle. 
	--If we check backwards, anything in front is part of the angle.
	return not checkForward

If you want to find the closest player in an arc cone, here's a way to make that work:

function FindClosestPlayerInArcCone(object, range, angle, parameters)
	local playersInRange = FindPlayersInArcCone(object, range, angle, parameters)
	local lowestDistance = math.huge
	local closestPlayer = nil
	local distance = 0
	for _, player in ipairs(playersInRange) do
		distance = (object:GetWorldPosition() - player:GetWorldPosition()).size
		if distance < lowestDistance then
			closestPlayer = player
			lowestDistance = distance
	return closestPlayer

The new World.SpherecastAll() function has great potential for making this work with objects, but I have not yet coded or devised an implementation for it yet.

If you have NPCs or AIs you'd also like to find along with players, you just need to substitute CombatWrapAPI's FindInSphere function (from Combat Dependencies in community content) in the place of Game.FindPlayersInSphere function.

Thank you.