1
\$\begingroup\$

The scripting engine of Skyrim (and Fallout 4) has an interesting function called FindClosestActor(x,y,z,radius). If you call it with x,y,z center coordinates and a radius, it returns the closest Actor (entity) in the given radius and if it fails to find any, it returns nothing.

The most common use-case would be for such a well-named function is to find Actors around the Player in a specific radius. Lo and behold, Player is also an Actor therefore you always get Player as the result of the function, making it completely impractical.

There are exactly two functions that can be used for finding an Actor in a given radius. Their (pseudo-)signature is the following:

FindClosestActor(x,y,z,radius) -> Actor | None
FindRandomActor(x,y,z,radius) -> Actor | None

Question: How can I find the closest Actor to the player in a close to optimal (least function calls) way using FindClosestActor and optionally FindRandomActor? How can we overcome the defective behavior of FindClosestActor?

Method I refuse to utilize

It relies on random too much. What if it was badly written internally? I think the only useful property of this algorithm is that if there are more than 1 Actor most of the time, it returns a non-Player Actor with 2 function calls with a 75+% reliability.

closestActor = FindRandomActor(playerX,playerY,playerZ,radius)
for i in 0..iterationLimit
  tmp = FindRandomActor(playerX,playerY,playerZ,radius))
  if distance(tmp, player) < distance(closestActor, player)
    closestActor = tmp
Idea #1:

Legend:
A1, A2: Actors where A2 is closer to the Player than A1
R: position where we find the closest actor
RP: Distance between R and P
P: Player
B: circle around P at distance "base radius"

enter image description here

  1. Let R be an arbitrary midpoint between P and (circle) B and RP is the distance between R and P.
  2. We run FindClosestActor(R, RP) and rotate R by some degrees (50° increments seem reasonably round) around P and repeat the process until we arrive to the original R position. During this process, we collect Actors and select the one that is the closest to P (which is A2).
  3. If we reached the minimum Actor-Player distance limit, we return with the closest Actor we have at the moment.
  4. Let R be the midpoint between A2 and P and repeat from step 2.

There are two issues with this algorithm.

  • The degree increment means I can't perfectly cover the whole circle
  • It looks pretty brute force. Even for 2 Actors, you might need 8 calls which is a lot
Idea #2:

Still not convinced that such a nondeterministic algorithm would be reliable but at least this one spends calculation time on really close Actors first.

closestActor = None
for i in MinRadius..MaxRadius
  for j in 0..iterationLimit
    tmp = FindRandomActor(playerX,playerY,playerZ, i))
    if tmp is not player
      closestActor = tmp
      break "for i..."
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I'm not familiar with CreationKit, but it looks like FindAllReferencesWithKeyword might be a better way to solve the problem. The documentation gives an example that returns all of the actors within a certain distance of the player & then removes the player from the results:

Actor Player = Game.GetPlayer()
ObjectReference[] kActors = Player.FindAllReferencesWithKeyword(ActorTypeNPC, 2048.0)
 
int playerIndex = kActors.Find(Player as Actor)
kActors.Remove(playerIndex)

After that, it should just be a matter of searching through the array for the closest element:

int closestElement = 0
float closestDistance = Game.GetPlayer().GetDistance(kActors[0])
int currentElement = 1
while (currentElement < kActors.Length)
  if(Game.GetPlayer().GetDistance(kActors[currentElement]) < closestDistance)
    closestElement = currentElement
    closestDistance = Game.GetPlayer().GetDistance(kActors[currentElement]
  endIf
  currentElement += 1
endWhile

Again, I'm not familiar with this scripting language, so there might be some syntax errors in the code above. I checked against examples in the reference, but I'm if it looks like I made a mistake on something, feel free to comment &/or edit in a correction.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you for taking the time to skim through the api. "...WithKeyword" sounded like something that wouldn't work for me but apparently it does. It works flawlessly. \$\endgroup\$ Jan 19, 2022 at 20:09
  • \$\begingroup\$ Personally, I found the API to be somewhat unintuitive & would have expected FindClosestActor to provide what you were looking for. I'm glad it worked out & good luck with your project! \$\endgroup\$
    – Pikalek
    Jan 19, 2022 at 20:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .