Because this function is crap, like the lot of vanilla âlibraryâ functions.
To calculate a planar angle you need 2 vectors: start (v1
) and end (v2
) or 3 points: v1
, v2
and o
, which is typically the origin ([0, 0, 0]
). v1
and v2
grow from o
, forming a triangle. If o
is not at the origin, you need to do some shifting. The angle is then returned as an unsigned real number between 0 and 180, which means that you get the same value if you switch v1
and v2
. The function you posted returns angle between v1
and x
axis, with tip of the angle located at v2
. It is not generic. And it is why you only get values between 0 and 90.
See image for comparison (correct on the left).

In my scripts I use a function that returns signed angle between vectors (-180 to 180) so you also get the direction, i.e. to determine whether the target is to the left or right of source. When working with creatures, v1
is often the facing vector of the source (i.e. PC) and v2
is difference between location of source and target (facing vector towards the target from source).
In short: dot product to get the angle, cross product to get the sign.
Script below does the calculation and runs a short demo. This code is robust.
//::////////////////////////////////////////////////////////////////////////////
//:: By NWShacker, 2022-01-11, licence: CC BY-SA 4.0
//::////////////////////////////////////////////////////////////////////////////
// By NWShacker, 2022-01-11, licence: CC BY-SA 4.0
// =============================================================================
// Calculates signed angle (-180 to 180) between vSource and vTarget within XY
// plane (ignoring Z components). This can be interpreted as by how many degrees
// vSource must be rotated around origin ([0, 0, 0]) and in which direction:
// negative - clockwise, positive - counter-clockwise to obtain vTarget (right
// hand rule). 0.0 is returned if either of the vectors has length 0.
// =============================================================================
// Parameters:
// * vSource: start vector
// * vTarget: end vector
// =============================================================================
// Return value:
// * 0.0 - vTarget points exactly in the same direction as vSource
// * 180.0 - vTarget points exactly in the opposite direction to vSource
// * between 0.0 and +180.0 - vTarget points somewhere to the left of vSource
// * between 0.0 and -180.0 - vTarget points somewhere to the right of vSource
float NWSH_GetSignedAngleBetweenVectors(vector vSource, vector vTarget);
float NWSH_GetSignedAngleBetweenVectors(vector vSource, vector vTarget)
{
float fAngle;
// normalize vSource
vSource = VectorNormalize(vSource);
// normalize vTarget
vTarget = VectorNormalize(vTarget);
// return 0 if vSource or vTarget has zero length
if(VectorMagnitude(vSource) == 0.0 || VectorMagnitude(vTarget) == 0.0)
{
return 0.0;
}
// calculate vector dot product to determine the angle
fAngle = acos(vTarget.x * vSource.x + vTarget.y * vSource.y);
// calculate vector cross product to determine the sign
if(vTarget.x * vSource.y - vTarget.y * vSource.x > 0.0)
{
return -fAngle;
}
else
{
return fAngle;
}
}
// By NWShacker, 2022-01-11, licence: CC BY-SA 4.0
// =============================================================================
// Uses NWSH_GetSignedAngleBetweenVectors() to obtain signed angle (-180 to 180)
// describing position of lTarget within XY plane in respect to lSource. If
// lTarget and lSource are in different areas this function returns 0.0.
// =============================================================================
// Parameters:
// * lSource: start location
// * lTarget: end location
// * iUseFacing:
// * TRUE: calculate angle between facing vector of lSource and vector from
// lSource to lTarget (both vectors starting from lSource)
// * FALSE: calculate angle between vectors pointing to lSource and lTarget
// (both vectors starting from origin, [0, 0, 0])
// =============================================================================
// Return value:
// +135 +90 +45
// \ | /
// \ | /
// +/-180 -- lSource == facing of lSource => 0
// / | \
// / | \
// -135 -90 -45
float NWSH_GetSignedAngleBetweenLocations(location lSource, location lTarget, int iUseFacing=FALSE);
float NWSH_GetSignedAngleBetweenLocations(location lSource, location lTarget, int iUseFacing=FALSE)
{
vector vSource;
vector vTarget;
float fAngle;
// check if lTarget and lSource are valid and in the same area
if(GetAreaFromLocation(lTarget) != GetAreaFromLocation(lSource) ||
!GetIsObjectValid(GetAreaFromLocation(lTarget)))
{
return 0.0;
}
if(iUseFacing)
{
// if iUseFacing is TRUE, calculate angle relative to lSource,
// between vector of its facing and vector towards lTarget
vSource = AngleToVector(GetFacingFromLocation(lSource));
vTarget = GetPositionFromLocation(lTarget) - GetPositionFromLocation(lSource);
}
else
{
// otherwise calculate angle between vectors of positions
// of lSource and lTarget attached at the origin
vSource = GetPositionFromLocation(lSource);
vTarget = GetPositionFromLocation(lTarget);
}
return NWSH_GetSignedAngleBetweenVectors(vSource, vTarget);
}
// =============================================================================
// DEMO: iterates all objects in the area of the caller and makes them speak
// their relative angle in respect to the caller.
// =============================================================================
void main()
{
object oObject;
float fAngle;
oObject = GetFirstObjectInArea();
while(GetIsObjectValid(oObject))
{
fAngle = NWSH_GetSignedAngleBetweenLocations(
GetLocation(OBJECT_SELF),
GetLocation(oObject),
TRUE);
AssignCommand(oObject, SpeakString(FloatToString(fAngle, 0, 1)));
oObject = GetNextObjectInArea();
}
}
Your case: vPC
is PCâs position (o
), vTrgt
is position of primary target (v1
), vHit
is position of the secondary target (v2
). So you simply check if:
fabs(NWSH_GetSignedAngleBetweenVectors(vTrgt-vPC, vHit-vPC)) <= 10.0