Jump to content

[LE] How to scan for actors around player?


xyz3lt

Recommended Posts

Naturally since i am looking for a way to get actors from around player any way of scanning for ObjectRefrences around some ObjectReference will do.

Ideally what i'd like is a Scan() function that returns array of Actors or ObjectReferences.

Roughly what i am using right now.

elt_Scanner.psc:

 

Scriptname elt_Scanner extends Quest 

ReferenceAlias Property playerRef Auto

Spell Property ScanSpell Auto

Actor[] found_arr
Actor[] return_arr

float fScanStart
Int found_C

bool bRunning = false
bool bInitialized = false



Event OnInit()
	found_arr = new Actor[20]
	bInitialized = true
endEvent



Actor[] Function Scan()	;EP for Scripts that want to scan actors around player
	bRunning = true
	ScanSpell.Cast(playerRef.GetRef() as Actor)
	fScanStart = Utility.GetCurrentRealTime()
	RegisterForSingleUpdate(1)
	
	While bRunning == true	;bRunning will be set to false after 5 seconds
		Utility.Wait(1)		;Within those 5 seconds ActiveMagicEffect will push(akTarget) into found_arr
	endWhile
	
	return_arr = PapyrusUtil.ResizeActorArray(found_arr, found_C)	;New resized array filled with all found actors
	
	int i	;Clean found_arr
	While i < found_C
		found_arr[i] = none
		i+=1
	endWhile
	
	found_C = 0
	
	return return_arr	;Return newly created array filled with found actors
endFunction


Event onUpdate()
	if (fScanStart + 5) < Utility.GetCurrentRealTime()	;If 5 seconds since ScanStart have passed set end flag
		Display()
		bRunning = false
	else	;Else check in a second, now that i think one RegisterForSingleUpdate(5) would work better...
		RegisterForSingleUpdate(1)
	endif
endEvent



Function Push(Actor obj)
	if found_C < 20
		found_arr[found_C] = obj
		found_C += 1
	endif
endFunction



Function Display()
	Debug.Notification("Number of found actors: " + found_C)
endFunction
 

 



elt_ScannerEffect.psc:


Scriptname elt_ScannerEffect extends activemagiceffect  

elt_Scanner Property Scanner auto

Event OnEffectStart(Actor akTarget, Actor akCaster)
	Scanner.Push(akTarget)
EndEvent

 



Script above casts AOE on player and retrives actors this way, but it does take a little while, mostly because i don't know how to safely handle parallelism differently.

I tried to use PapyrusUtils>MiscUtils>ScanCellNPCs but exactly as it says it only seems to scan a single cell, which is a problem in worldspace since cells are divided into squares.
Also thought of getting 4 closest cells to player then GetNthForm for actors and handle distance manually with GetDistance. I didn't try it though.
And also tried PowersOfThree>PO3_SKSEfunctions>FindAllReferencesOfFormType but that function seems to returns references as you would see them in CK render window. It worked very oddly, sometimes returning nothing and sometimes a lot.

Some mod that i checked made use of a scriptCloak that add's keywords, then Actors are retrived trough ReferenceAlias'es. It's a bit too confusing for me to recreate from just looking.

 

Here is PO3 script incase i used it incorrectly.

 

elt_QuestScanner.psc:

 

 

Scriptname elt_QuestScanner extends Quest  

import PO3_SKSEfunctions

ObjectReference PlayerRef

Event OnInit()
	PlayerRef = Game.GetPlayer() as ObjectReference
	RegisterForKey(62)
	Debug.Notification("F4 key Registered")
endEvent

;PO3_SKSEfunctions.ObjectReference[] Function FindAllReferencesOfFormType(ObjectReference akRef, Int formType, float radius)

Event OnKeyDown(Int KeyCode)
	;Actor[] Scanned = MiscUtil.ScanCellNPCs(PlayerRef, 1000.0)
	;ObjectReference[] Scanned = FindAllReferencesOfFormType(Game.GetPlayer() as ObjectReference, 43, 1000.0)
	ObjectReference[] Scanned = FindAllReferencesOfFormType(PlayerRef, 43, 1000.0)
	
	Debug.Notification("Scanned length: " + Scanned.Length)
	
	if Scanned	;not empty, notification with a string containing all found form43 objects
		Debug.Trace("Array not empty")
		int i = 0
		string dbgString
		while i < Scanned.Length
			;Debug.Notification(Scanned[i].GetBaseObject().GetName() + " detected")
			dbgString += Scanned[i].GetBaseObject().GetName() + " "
			i+=1
		endwhile
		Debug.Notification(dbgString)
	else	;empty
		Debug.Trace("Array empty")
	endif
EndEvent
 

 

 

Link to comment
Share on other sites

You can use SKSE to do something similar. I wrote a quick and dirty example:

Actor akCenter = player ; player variable containing Game.GetPlayer() isnt visible in this example
Actor[] kActors = new Actor[128]
Cell kCell = akCenter.GetParentCell()
Int cellActors = kCell.GetNumRefs(43)
Int i


while i < cellActors
    Actor akActors = kCell.GetNthRef(i, 43) as actor
    if akActors != akCenter ; exclude the center of the search
        kActors[i] = akActors
    endif
    i += 1
endwhile

Problems: Could be slow since it searches the entire cell checking every type that matches, so highly populated cells might slow it down.

You will need to resize the array if the number of actors is over 128. I think, for example if you're near the edge of a cell and actors are in the next cell, this method will ignore them and catch only the ones in the parentcell. This works great for interiors though.


You can modify this to make your own FindReferenceofXXXXX, like restricting the search to the radius around the player, rather than the full size of the cell if you want as well.

Edit: I believe that FindAllReferencesOfFormType is actually using GetNumRefs underneath because the behavior you described is exactly how GetNumRefs operates. Cells might have 20 actors, and then some cells might have no actors. Now if you use the formtype to find objectreferences, it will return everything in the cell.

Edited by AnishaDawn
Link to comment
Share on other sites

Papyrus Extender has some functions that can help with this: https://www.nexusmods.com/skyrim/mods/95017

ObjectReference[] function FindAllReferencesOfType(ObjectReference akRef, Form formOrList, float radius) global native
ObjectReference[] function FindAllReferencesWithKeyword(ObjectReference akRef, Keyword[] keywordArray, float radius, bool matchAll) global native
Actor[] function GetActorsByProcessingLevel(int Level) global native

Use 0 for the actor function to get all loaded actors in your area.

Link to comment
Share on other sites

Big problem with GetNthRef / GetNumRefs is how cells work, inside a big cell like a ruin you can get a lot of hits, but add some GetDistance processing and you are good, meanwhile actors standing next to you inside worldspace like tamriel or whiterun may not be detected since they are technically in a different cell. For interiors i could just use ScanCellNPCs from MiscUtils as it does have built in distance, but for exteriors i'd have to check roughly 4 cells closest to player to get rid of "standing next to but not detected" issue. If it's faster then spell approach i'll try doing it anyways.

 

As for PO3 it just acts strangely, maybe i misunderstood how it works, using script above, outside of jorrvaskr i can get at most 3 detections (player included) but usually 0 not even player, but inside even though there were only 2 npcs there i got 13 detections, including strange things like bandits and silver hand.

Link to comment
Share on other sites

Test script i am using.

 

elt_QuestScanner.psc:

 

 

Scriptname elt_QuestScanner extends Quest  

import PO3_SKSEfunctions

ReferenceAlias Property playerRef Auto

elt_Scanner Property Scanner Auto

Event OnInit()
	RegisterForKey(62)
	RegisterForKey(64)
	RegisterForKey(65)
	RegisterForKey(66)
	Debug.Notification("F4 F6 F7 F8 keys Registered")
endEvent

;PO3_SKSEfunctions.ObjectReference[] Function FindAllReferencesOfFormType(ObjectReference akRef, Int formType, float radius)

Event OnKeyDown(Int KeyCode)
	if KeyCode == 62
		Test62()
	elseif KeyCode == 64
		Test64()
	elseif KeyCode == 65
		Test65()
	elseif KeyCode == 66
		Test66()
	endif
EndEvent

Function Test62()
	ObjectReference[] Scanned = FindAllReferencesOfFormType(PlayerRef.GetRef(), 43, 1000.0)
	dbgOutbput(Scanned)
endFunction

Function Test64()
	Actor[] Scanned = Scanner.Scan()
	dbgOutbput(Cast_ActorArray_to_ObjectReferenceArray(Scanned))
endFunction

Function Test65()
	ObjectReference[] Scanned = FindAllReferencesOfType(PlayerRef.GetRef(), PlayerRef.GetRef(), 1000)
	dbgOutbput(Scanned)
endFunction

Function Test66()
	Actor[] Scanned = GetActorsByProcessingLevel(0)
	dbgOutbput(Cast_ActorArray_to_ObjectReferenceArray(Scanned))
endFunction

ObjectReference[] Function Cast_ActorArray_to_ObjectReferenceArray(Actor[] aArr)
	ObjectReference[] retArr = PapyrusUtil.ObjRefArray(aArr.Length)
	int i
	while i < aArr.Length
		retArr[i] = aArr[i] as ObjectReference
		i+=1
	endwhile
	return retArr
endFunction

Function dbgOutbput(ObjectReference[] dbgArr)

	Debug.Notification("Scanned length: " + dbgArr.Length)
	if dbgArr
		int i = 0
		string dbgString
		while i < dbgArr.Length
			dbgString += dbgArr[i].GetBaseObject().GetName() + " "
			i+=1
		endwhile
		Debug.Notification(dbgString)
	else	;empty
		Debug.Notification("Array empty")
	endif
	
endFunction

 

 



Test62 FindAllReferencesOfFormType works erratically detecting invisible things and sometimes nothing.

Test64 using my spell scanner, works fine, but it does take 5 seconds (i could probably cut it down a bit).

Test65 FindAllReferencesOfType doesn't ever return anything, clearly i am using it wrong and after a bit of testing i still don't have any idea how to make it return anything.

Test66 GetActorsByProcessingLevel, it does work, returns 50 actors on average, with a bit of processing it coud easily be cut down to ~15, any idea how does it perform in comparison to spell?

 

As for keywords search i didn't try it yet, i am not sure what keyword to look for, i could make a script cloak that adds/removes keywords but that couldn't possibly be performance friendly.

 

Edit: As for MiscUtils ScanCellNPCs it only scans around you in current cell, works great for interiors but no so much for worldspace (square grid of cells).

Edit2: somewhat more readable script

Edited by xyz3lt
Link to comment
Share on other sites

Here is an explanation of processing level: https://geck.bethsoft.com/index.php?title=GetActorsByProcessingLevel

0 gets all loaded actors in the area.

 

For FindAllReferencesOfType, it only finds object references of a specific form. So if you used:

 

MiscObject Property Lockpick Auto

Event OnInit() 
    Form[] MyFormArray = PO3_SKSEfunctions.FindAllReferencesOfType(Game.GetPlayer(), Lockpick, 1000)
EndEvent

It would only find all of the lockpicks in 1000 units from the player. So if using an actorbase, it would only find that specific actor. Using a formlist instead there for me caused CTD. I would suggest using the keyword function. You can use ActorTypeNPC to get all humanoid actors. There's also a bunch of other ActorType keywords you can check for.

 

Keyword Property ActorTypeNPC Auto 

Event OnInit() 
    ObjectReference[] NPcs = PO3_SKSEfunctions.FindAllReferencesWithKeyword(Game.GetPlayer(), ActorTypeNPC, 1000, false)
EndEvent

Or, if you're using Skyrim SE, you can maybe use SkyPal: https://www.nexusmods.com/skyrimspecialedition/mods/43925?tab=description which has a bunch of functions for getting and filtering object references.

 

Link to comment
Share on other sites

For now i'll try to improve SpellScanner, implement proper FindAllReferencesWithKeyword search keyword being ActorTypeNPC, implement skypal Grid() then filter it for actors then distance, and finally GetActorsByProcessingLevel with a bit of postprocessing, also using skypal filter for distance. Then test for performace impact of those 4 methods and that would be it.

 

Thanks for all the help thats probably all i needed to know.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...