Accessing an Actor without a loop

Discussions about Coding and Scripting
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Accessing an Actor without a loop

Post by Barbie »

To avoid changing maps I can do some hacks on server side by accessing map specific Actors and their properties, for example setting "Mover7.MoverEncroachType = ME_IgnoreWhenEncroach" in map "MH-Gekokujou". By now I do this by looping over all movers, comparing the name and break if the name is equal. More general a FindActor function can be used:

Code: Select all

function bool FindActor(name ActorName, out Actor A) {
local Actor A4loop;
	foreach AllActors(class 'Actor', A4loop)
		if (A4loop.Name == ActorName)
		{
			A = A4loop;
			return true;
		}
	return false;
}
But isn't there a simple way to access an instance of an Actor within a map? In a way like "Level.Actors('Mover7')" known from other programming languages? Looping over n/2 Actors to find only one seems a bit inefficient.
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Accessing an Actor without a loop

Post by sektor2111 »

I'm not sure if takes 2 months to do changes in PostBeginPlay - a single time and... fire action. Clock Iterator... I'll bet will do the job before to see the first player entered.
I've never been interested to build a long names list with movers from 1000+ Levels, or other bad set actors.
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Accessing an Actor without a loop

Post by Barbie »

It just hurts me to see so many CPU cycles wasted... ;o)
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Accessing an Actor without a loop

Post by sektor2111 »

Trust me, you won't affect performance from game if you are wrapping things at beginning of action. You also can call some function to fix stuff even after 1 second (if needs a delayed check) controlled by a boolean (bJobDone).

To mention that I have changed almost all PawnList with Foreach AllActors as long as PawnList in high loaded levels is really slow, I was clocking that.
Higor
Godlike
Posts: 1866
Joined: Sun Mar 04, 2012 6:47 pm

Re: Accessing an Actor without a loop

Post by Higor »

It's fine, my CacusCoop edits Unreal maps like this as well... I bet the mutator chain is slower than that by many orders.
BTW you 'CAN' speedup processing by finding out the actor's Tag.

EDIT2: For greater speedup change the function call opcode as well (final)
EDIT3: False return is automatic, let's shrink the function by 2 bytes by removing it.
EDIT4: Turn this into a major utilitary function by changing return type (return NONE automatic return).
Also, if you're trying to find actors YOU spawned withing the game session, remember to spawn these actors with very distinctive tags or by simply have the actor set the tag itself after spawning on the Net client.
Believe it or not it can be extremely useful... when paired with functions like this (SiegeIV does this all over the place).

Code: Select all

final function Actor FindActor(name ActorName, out Actor A, optional name ActorTag)
{
   local Actor A;
   foreach AllActors( class 'Actor', A, ActorTag)
      if (A4loop.Name == ActorName)
      {
         A = A4loop;
         return A4loop;
      }
}
Reason:

Code: Select all

void AActor::execAllActors( FFrame& Stack, RESULT_DECL )
{
	P_GET_OBJECT(UClass,ActorClass);
	P_GET_ACTOR_REF(OutActor);
	P_GET_NAME_OPTX(ActorTag,NAME_None);
	P_FINISH;

	BaseClass = ActorClass ? ActorClass : AActor::StaticClass();
	INT i=0;

	PRE_ITERATOR;
		*OutActor = NULL;
		while( (i < XLevel->Actors.Num()) && *OutActor==NULL )
		{
			AActor* TestActor = XLevel->Actors(i++);
			//Tag check is done at native level, therefore faster
			if ( TestActor && TestActor->IsA(ActorClass) && (ActorTag==NAME_None || TestActor->Tag==ActorTag) )
				*OutActor = TestActor;
		}
		if( *OutActor == NULL )
		{
			Stack.Code = &Stack.Node->Script(wEndOffset + 1);
			break;
		}
	POST_ITERATOR;
}
Last edited by Higor on Thu Dec 24, 2015 5:58 pm, edited 1 time in total.
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Accessing an Actor without a loop

Post by JackGriffin »

If you know the actor's name in a specific map you can do fixes like this:

Code: Select all

     if ( Level.Title == "Map 6: Gore Mine Crossing " && (Trigger(Other).Name == 'Trigger60') )
      {
	 		Trigger(Other).InitialState = 'NormalTrigger';
	 		Trigger(Other).bInitiallyActive = True;
      }
I do a ton of these in my coop controller. There's well over a thousand lines of just map fixes for various this-n-that.

Oh, and you can do a replacement across different map versions too like this:

Code: Select all

 if(InStr ( Level.Title, "Andromeda" ) == -1)  //Stupid hack to let the people play with the everlasting jump boots
            {
                      ReplaceWith(Other,"HitmanModv8.HittyJumpBoots");
				return false;
            }
        }
That code checks all the Andromeda maps and doesn't replace the specific ones in them (because the players liked those ones).
Last edited by JackGriffin on Thu Dec 24, 2015 5:59 pm, edited 1 time in total.
So long, and thanks for all the fish
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Accessing an Actor without a loop

Post by sektor2111 »

Then you'll need to check more tags...

But this is some good info about AllActors, hehe...
Higor
Godlike
Posts: 1866
Joined: Sun Mar 04, 2012 6:47 pm

Re: Accessing an Actor without a loop

Post by Higor »

JackGriffin wrote:If you know the actor's name in a specific map you can do fixes like this:

Code: Select all

     if ( Level.Title == "Map 6: Gore Mine Crossing " && (Trigger(Other).Name == 'Trigger60') )
      {
	 		Trigger(Other).InitialState = 'NormalTrigger';
	 		Trigger(Other).bInitiallyActive = True;
      }
I do a ton of these in my coop controller. There's well over a thousand lines of just map fixes for various this-n-that.
That looks like something coming straight ouf ot IsRelevant()
I used to do that in my Coop, but as MrLoathsome pointed out, doing this here is a huge waste of CPU cycles.
The ideal way to proceed in these cases is (writing hypothetical code):

Code: Select all

local string LevelName;
LevelName = string(self);
LevelName = Left( LevelName, InStr(LevelName,".") );

//Evaluate levels here.
//We only need to fix level once, so no matter if map name changes if loaded from savegame
if ( LevelName ~= "Bluff" )
    FixBluff();
else if ( LevelName ~= "ExtremeDGen" )
    FixDGen();
else....
So

Code: Select all

function FixBluff()
{
    //Spam Barbie's FindActor to find specific actors and apply changes (my edit).
    //Also spawn your actors here if you wish.
}
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Accessing an Actor without a loop

Post by JackGriffin »

Above stuff of mine was from CheckReplacement.

I did what you described in the RYS fixes mod. It looks like this:

Code: Select all

function postbeginplay()
{
	FixMaps();
and later

Code: Select all

function FixMaps()
{
	if (outer.name == '00Intro')
		FixMap_00Intro();
	else if (outer.name == '01Escape')
		FixMap_01Escape();
	else if (outer.name == '02Village')
		FixMap_02Village();
	else if (outer.name == '03Temple')
		FixMap_03Temple();
	else if (outer.name == '04Mountains')
		FixMap_04Mountains();
which leads to

Code: Select all

function FixMap_00Intro()
{
	SetSpecialEventMessage("SpecialEvent1", "In the 21st century tension between world powers was rising...");
	SetSpecialEventMessage("SpecialEvent2", "Until the outbursts of hatred got out of control.");
	SetSpecialEventMessage("SpecialEvent3", "In 2048 the 3rd World War broke out.");
I'll be honest, it's been my experience that as long as you aren't banging tick, running seventy timers, and you pay attention to proper destroying of unused actors then UT is very solid. There are certainly better ways of doing things but the real world impact is not usually noticeable.
So long, and thanks for all the fish
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Accessing an Actor without a loop

Post by sektor2111 »

That old way with CheckReplacement was probably a very expensive thing, when something has been spawned replacement did a lot of checks if if if if.
Let me guess, Replacement is good to be wrapped - if something belongs to our package simply allow it TRUE and move to next section Only if needs. Using checks category-based will prevent a lot of checks for the same actor, code enter in stage only if category is a hunted one. Example if we want to replace a weapon we don't check if weapon > Other.IsA('Skaarj'). Also if we have a Skaarj child in our package using primary package content check will prevent all next useless checks speeding up CheckReplacement and IsRelevant deal.
For Level changes I prefer PostBeginPlay - happens once and doesn't affect the rest of next action. Right ?
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Accessing an Actor without a loop

Post by Barbie »

Higor wrote:That looks like something coming straight ouf ot IsRelevant()
That was my first thought (because of other), too. Then the only-once-applied-map-changing-stuff is called on nearly every spawned item...
If on the other side done in any PostBeginPlay() I run into problems for several modifications (maybe the FixTeleportersWithURL-patch, I don't remember clearly).
Spoiler

Code: Select all

function bool FixTeleporterWithURL(Teleporter T) {
/*******************************************************************************
Returns TRUE if *T* has a travelling URL.

Todo: if an URL is given it may direct to the map itself.
*******************************************************************************/
local MonsterEnd MonsterEnd;

	if (InStr(T.URL, "#") >= 0) // URL=LevelName#TeleporterName: Teleports to a different level. Found in MH-FissionSmelter, MH-NP04Hyperion.
	{
		log(T @ "has travelling URL '" $ T.URL $ "', disabling teleporter");
		T.URL = "";
		T.SetCollision(False, False, False);// don't replace to avoid error messages "destination not found" ingame
		T.bHidden = True;
		// put a MHEnd also at the same place:
		//ExMHEnd = Spawn(class'exmhend',,,T.location + vect(-1,-1,-1));
		MonsterEnd = Spawn(class'MonsterEnd',,,T.location);
		//MonsterEnd = MonsterEnd(SpawnAndPasteActor(T, "MonsterHunt.MonsterEnd"));
		if (MonsterEnd != None)
		{
			MonsterEnd.SetCollisionSize(FMax(t.CollisionRadius, MonsterEnd.CollisionRadius), FMax(t.CollisionHeight, MonsterEnd.CollisionHeight));
			MonsterEnd.bHidden = False;
		}
		else
			warn("MonsterEnd could not be spawned");
		Return True;
	}
	else
		Return False;
}
So I call these map modifications when all Mutator's operations are done and the Auto State is entered. There it works fine.

Code: Select all

class MyMutator expands Mutator;
...
auto state WhatEverYouNamedIt {
Begin:
	MonsterReplicationInfoSB(Level.Game.GameReplicationInfo).MaxAmmoScale = MaxAmmoScale;
	FixCTFFlags();
	MapSpecificFixes();
	if(bFixTeleportersWithURL)
		FixTeleportersWithURL();
}
EDIT: I've seen another solution for this by running it once in timer after ? seconds.
The topic has driven a bit :omfg:
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Accessing an Actor without a loop

Post by sektor2111 »

That spawning trigger problem indeed is a bit sensitive. Let's see what I found in my first days of MH battling.

I was trying to spawn new end in BaseMutator - did not passed relevancy check and after some lag to almost crash it didn't spawn. Later in Game Controller it worked. But finally I did that even in Base after figuring how relevance works - simply End need to be powerful by default bGameRelevant. After spawning will self calm down relevance. It passed test and later can be wick without problems. This race was in purpose to solve MH-Miam types. But I have figured another solution too. So I can spawn end even after 100 seconds and it will be almost like original. Actually it is needed to end Level is not a need to have it right in first second. The rest of tweaks can be also called once after 3 seconds or 2 seconds or delayed - if they don't need an self autostate setup... If you want to move faster than AutoState you have to attack them in PostBeginPlay, here if Relevance start to mock things, make a default bGameRelevant, or hack IsRelevant() to accept stuff spawned by package. Simply stuff which is part of our package won't be checked if has relevance or not because we have changed rules.
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Accessing an Actor without a loop

Post by Barbie »

Higor wrote:EDIT3: False return is automatic, let's shrink the function by 2 bytes by removing it.
:D
That's just my style of coding: sometimes I write code not for the computer but for a human reader (maybe myself in later years) to clarify what I intended to do there. Furthermore it can be a kind of defensive programming - if someone (maybe myself) adds some code later to a routine, variables might not have the initial value any more. Example:

Code: Select all

Function WhatEver(string s) {
local int i;
	// <- if some code is added here and changes i, the next line keeps the functionality
	i = 0; // not necessary because compiler sets intial value of 0
	While (i < len(s))
		DoSomething(s, i++);
}
Higor wrote:EDIT4: Turn this into a major utilitary function by changing return type (return NONE automatic return).
Also my coding style: I prefer functions with error code as result and eventually return values as parameter. Then such a function can smoothly be used as

Code: Select all

if (FunctionNameLikeAQuestion(MyActor)) DoSomeThingWith(MyActor)
Over decades of coding I found that readable code is much more important than performance (exceptions exist, yes).
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Accessing an Actor without a loop

Post by JackGriffin »

For some reason (I can't remember what) the teleporter will crash the server if you try to use it. I scripted a solution years back. I can dig that up if you need it? It's tested and working, I do remember that.
So long, and thanks for all the fish
User avatar
Wormbo
Adept
Posts: 258
Joined: Sat Aug 24, 2013 6:04 pm
Contact:

Re: Accessing an Actor without a loop

Post by Wormbo »

Since we're already into optimization:
Higor wrote:

Code: Select all

local string LevelName;
LevelName = string(self);
LevelName = Left( LevelName, InStr(LevelName,".") );
Few people seem to realize that actors are always directly contained in the map package itself. As a result you can extract the package name without any explicit string manipulation operations:

Code: Select all

local string LevelName;
LevelName = string(Outer); // assuming code is in an actor subclass, otherwise access Outer through an actor reference
Or just use Outer.Name directly to skip the string conversion altogether. This should work really well in combination with a Switch statement. Here's a piece of code we used in JB2004c's "map fixes" to apply map-specific fixes so we didn't need to re-release fixed versions of those maps:

Code: Select all

// in PreBeginPlay() of a Mutator-like class (UT200x differentiates between Mutator and GameRules)
// SetInitialState will later pick up the InitialState value set here
// we opted for Locs() (opposite of Caps()) to prevent polluting the name table and ensure the actual string representation matches
  switch (Locs(Level.Outer.Name)) {
    case "jb-arlon-gold":         InitialState = 'Arlon';         break;
    case "jb-aswan-v2":           InitialState = 'Aswan';         break;
    case "jb-atlantis-gold":      InitialState = 'Atlantis';      break;
    case "jb-babylontemple-gold": InitialState = 'BabylonTemple'; break;
    case "jb-collateral":         InitialState = 'Collateral';    break;
    case "jb-cosmos":             InitialState = 'Cosmos';        break;
    case "jb-frostbite":          InitialState = 'Frostbite';     break;
    case "jb-heights-gold-v2":    InitialState = 'Heights';       break;
    case "jb-indusrage2-gold":    InitialState = 'IndusRage';     break;
    case "jb-poseidon-gold":      InitialState = 'Poseidon';      break;
    case "jb-thedecks":           InitialState = 'TheDecks';       break;
  }
(Most of the fixes spawned or modified actors in the various states' BeginState(). Some of the fixes involved more complex logic, so states were needed.)

Later, for my UT2004 Christmas Decorations server actor, I used code like this piece:

Code: Select all

					switch (Locs(A.StaticMesh.Name))
					{
						case "idomatakkenmid":
						case "idomatakkenend":
							SpawnEmitter(A, class'ChristmasTreeLightsEmitterF');
							break;
						case "miscstatue01ba": // for some reason myLevel'd in ONS-Crossfire
							SpawnDecoHat(A, vect(-4,0,111), rot(0,16384,-2048), 0.47, vect(1,1,2));
							SpawnDecoBag(A, vect(24,16,60), rot(0,-16384,3072), 2.0, vect(1,1,1));
							break;
						case "colWallFirePot2AR": // face deco on Bridge of Fate
							SpawnDecoHat(A, vect(0,3,-4), rot(0,0,0), 0.25, vect(1.5,0.5,1.05));
							break;
						case "bannersconce": // myLevel'd in BR-Serenity, but also in sc_volcano_m package
							SpawnDecoHat(A, vect(0,2,144), rot(0,32768,3072), 0.225, vect(1,1,1));
							break;
						case "hourekandelaarkemtelelcitilicht": // myLevel'd
						case "kandelaarkemtelelcitilicht": // from HourmeshstuffX3, but possibly also myLevel'd
							SpawnDecoHat(A, vect(456.4,48.95,94), rot(0,0,0), 0.2, vect(1,1,1));
							break;
						case "phonepole": // don't want spiffingrad left out
							SpawnDecoHat(A, vect(-89,11,-12), rot(0,32768,0), 0.46, vect(1,1,1.5));
							break;
						case "StatueCon1":
							SpawnDecoHat(A, vect(-56,-64,536), rot(0,16384,-1024), 0.8, vect(0.85,1,1));
							SpawnDecoBag(A, vect(-16,-64,480), rot(0,-16384,0), 2.0, vect(0.9,1,1.5));
							break;
					}
There's nothing wrong with the question style bool function returning the value through an out parameter, it's actually a relatively common pattern in e.g. C# as well. (A common pattern there is something like "Foo value; if (dict.TryGetValue(key, out value)) DoSomethingWith(value);")
I also don't think (without explicitly profiling it) the assingment + null check approach will provide any significant performance changes over the bool return + out parameter approach.
Post Reply