Contents
Existing Lists in Unreal Tournament
This part of the tutorial explains existing linked lists in Unreal Tournament.
Actor List
Type and Scope
Type: All actors in Unreal are connected though a gigantic list (well it is actually a dynamic array, but to make life easier think of it as a list) that exists only in the native C++ code. Without this list, actors would be just plain old objects. The base of this "list" is actually the native level object (all actors have a pointer to this: Xlevel).
Scope: Exists on both client and server. When a client "receives" an actor, it is added and when its connection is closed (except with bNetTemporary), it is removed.
Adding and Removing
Adding: Spawning an actor automatically does this. It adds at the end of the list. Note that LevelInfo is always the first actor.
Removing: Destroying an actor automatically does this.
In the Actor class, various "iterators" exist. These allow you read the list in different ways. Simply use ForEach Iterator_Function(params)
to use them. ForEach acts much like a For Loop.
- AllActors ( class<actor> BaseClass, out actor Actor, optional name MatchTag )
- BaseClass is the parent of classes you wish to look for. The actor pointer passed in the second parameter becomes the actor outputted. As the iterator functions are hard-coded into the compiler, you can have the actor pointer be of the BaseClass or any of its parents. Matchtag, if given, will only return actors, which have that tag. This will go through every actor in the level and is thus somewhat slow (this is not noticeable however unless it is called multiple times in one frame), and thus use it sparingly. If a more class specific list exists, such as that for pawns and navigation points (see below), use those instead. Note that if you wish to terminate the loop when a certain actor is returned, simply use
break;
.
- ChildActors ( class<actor> BaseClass, out actor Actor )
- The parameters work the same as AllActors'. However, this only returns actors with their owner set to the class this is run on. It has the same speed hit as AllActors.
- BasedActors ( class<actor> BaseClass, out actor Actor )
- Returns all actors that have this as a base. Same speed hit as AllActors.
- TraceActors ( class<actor> BaseClass, out actor Actor, out vector HitLoc, out vector HitNorm, vector End, optional vector Start, optional vector Extent )
- This is supposed to trace a line from Start to End and return all actors hit (executes as fast as a normal trace). However, this is not ever used by Epic in code and I recall of no one ever getting it to work right. I do not recommend using it.
- RadiusActors ( class<actor> BaseClass, out actor Actor, float Radius, optional vector Loc )
- This returns all actors that are within radius units of Loc (or if it isn't given; then the actor's location). Same speed hit as AllActors().
- VisibleActors ( class<actor> BaseClass, out actor Actor, optional float Radius, optional vector Loc )
- This is much like RadiusActors() only it also checks if an actor is visible from Loc. Same speed hit as AllActors.
- VisibleCollidingActors ( class<actor> BaseClass, out actor Actor, optional float Radius, optional vector Loc, optional bool bIgnoreHidden )
- This only returns actors with bCollideActors as true. It uses the collision hash to check, and thus it is much faster than Allactors, in normal circumstances. If a very high radius is used (900 or so), then it becomes very slow, and VisibleActors should be used instead.
- ZoneInfo.ZoneActors( class<actor> BaseClass, out actor Actor )
- Returns all actors inside that zone. Same speed hit as AllActors(). Go figure.
Inventory List
Type and Scope
Type: Standard one way linked list. Notice how it is defined in actor, and not in inventory. This is designed so other actors (pawns) can have a link to the base, while keeping the same pointer name.
Scope: Server. On the client side however, it will only exist for the player being controlled and within his inventory. Other player's will not have an inventory list. Note that in very rare events, replication can come at a weird order and cause the inventory pointer of one item to point to an older one in the list. Thus when using this list client-side, you should have a check so that no more than 100 or so loops occur (to avoid infinite iterators).
Adding and Removing
Adding: Call AddInventory() on the pawn you wish to add that inventory item to with that item in the parameters. It is then added to the beginning of the inventory list. This should only be done server-side. Note that inventory's GiveTo() automatically does this.
Removing: Class DeleteInventory() on the pawn(owner) with the item you wish to delete in the parameters. Again, this should only be performed server-side. When an inventory item is destroyed, this automatically occurs (see Inventory.Destroyed()).
If you are only searching for one item of a certain class (not child classes!), you can simply call FindInventoryType on the pawn who owns the items. Yet to search through the entire list do the following:
Local Inventory Inv; For (Inv=somepawn.Inventory;Inv!=none;Inv=Inv.Inventory) //do stuff here
Pawn List
This list is the reason I am writing this tutorial. I tire of seeing code that uses ForEach AllActors(class'pawn',p), when there is a much faster way this can be done.
Type and Scope
Type: Native controlled standard one way list linked by LevelInfo.PawnList.
Scope: Server Side only at BeginPlay() in gameinfo. Thus, on a client or in a mutator (post/pre)BeginPlay(), you need to stick with AllActors(class'pawn',p)
Adding and Removing
Adding: Simply call AddPawn() on the pawn. Note that pawn automatically does this in its PreBeginPlay() so you really do not need to worry about calling this. Adds to the end of the list.
Removing: Call RemovePawn() on the pawn. This is automatically taken care of in Destroyed().
The list base is located in levelinfo. Simply call (in any actor):
Local pawn p; For (p=level.pawnlist;p!=none;p=p.nextpawn) //do stuff
Type and Scope
Type: Constant standard one way list linked by LevelInfo.NavigationPointList.
Scope: Server-side only.
Adding and Removing
Cannot be done. The list is generated upon building the paths in UnrealEd.
Most likely you will use the navigation point list to find path nodes (a simple navpoint added by level authors). This allows you to spawn actors at many different locations in the map, and still have bots touch them. For instance, the relics mutator searches the Navigation Point list for pathnodes. Relics are then spawned at a random node. Like the pawn list, the base is in LevelInfo.
Local NavigationPoint np; For (np=level.NavigationPointList;np!=none;np=np.nextnavigationpoint) //do stuff
Note that many other lists exist within navigation point. They are all used exclusively for AI navigation. Unless your name is Pfhoenix, they have no meaning.
Mutator List
The introduction of mutators in Unreal 2.24 was one of the best things ever done for mod makers. What makes mutators so powerful? The fact that they are linked lists. This allows many different mods to be run at once and change many different game elements.
There are in fact 4 mutator lists in Unreal Tournament; the primary mutator list, damage mutators that only affect (Mutator)TakeDamage calls, message mutators that only affect messages (BroadCast, BroadCastLocalized, and player's talking), and finally HUD mutators that allow mutators to draw on the HUD.
Type and Scope
Type: All are standard one-way linked lists. HUD mutators are linked to by a HUD, and all others are linked by the GameInfo.
Scope: Mutators normally (default) only exist server-side. However, the HUD mutators must be on the client (see below).
Adding
Normal Mutators
When a user selects start, UT opens a map with a special parameter:
?mutator=MutatorPackage.MutatorClass,AnotherMutatorPackage.AnotherMutatorClass
The GameInfo's InitGame() is called when it is first spawned. First the base mutator (generally DMMutator) is spawned. From there the mutator options are parsed (this tutorial is not about string manipulation and Unreal URLs!), the mutator class dynamically loaded, then spawned, then finally added to the list:
log("Add mutator "$LeftOpt); MClass = class<Mutator>(DynamicLoadObject(LeftOpt, class'Class')); BaseMutator.AddMutator(Spawn(MClass));
AddMutator() adds to the end of the list with the "1337 function" method described earlier in this tutorial. Now the mutator list is ready to receive notification of actor spawning, mutate commands, player respawnings, death notifications, and all those other wonderful events mutators receive from pawns and, more commonly, the gameinfo.
Damage Mutators
To make a mutator be used as a damage mutator (it still has normal mutator functions!), simply call Level.Game.RegisterDamageMutator(MutatorToBeRegistered). The mutator will now receive notification whenever a pawn takes damage and can modify certain values.
Message Mutators
This is another specialized mutator, not unlike a damage mutator. Simply call Level.Game.RegisterMessageMutator(MutatorToBeRegistered) to be able to edit all of the wonderful messages. Incidentally, message mutators did not exist until UT version 4.13. They were created simply for support of Rocket Arena. They have also been beneficial in stopping message hacking. :p
HUD Mutators
I cannot even count the amount of times people have asked why their HUD mutator will not work in network play. What is the reason for this problem? It is quite simple; mutators exist only on the server, yet HUDs are client-side only. Epic's code makes it appear that you can simply call RegisterHUDMutator, which is untrue. The following is one way of adding a HUD mutator on the client:
Step 1: In the default properties of your mutator, place the following:
bAlwaysRelevant=True bNetTemporary=True RemoteRole=ROLE_SimulatedProxy
These values cause the mutator to be replicated to all clients, but the connection only stays open until the client has received the mutator. The mutator must also be a Simulated Proxy on the client for step 2 to work.
Step 2: Now add the following code to your mutator:
Simulated Function Tick(float delta) { If (Level.NetMode == NM_DedicatedServer || bHUDMutator) Disable('Tick'); Else RegisterHUDMutator(); }
Each frame, RegisterHUDMutator() is called. If it finds a PlayerPawn with a HUD (local player!), it will set that HUD's HUDMutator to this mutator (as well as preserve the list) and set bHUDMutator to true. If the mutator has already been registered or this is a dedicated server (no HUDs!), then tick () can be safely disabled.
On a final note, I ask that all mod makers who make use of HUD mutators to do the following: At the end of PostRender() in your HUD mutator, please add:
If (NextHUDMutator != None) NextHUDMutator.PostRender(Canvas);
For some dumb reason, Epic did not add that support to begin with. Without those lines, your mutator will be the only HUD mutator used. Other HUD mutators will not receive PostRender() calls, and thus not be able to display anything on screen.
Removing
No specialized functions exist to remove elements from mutator lists. Simply follow the removal model at the beginning of this tutorial.
Rarely will you ever need to iterate through the mutator list. All functions of mutators simply call that same function on the next mutator, returning a result if necessary. Yet if you wish to go through all mutators simply use:
Local mutator m; For (m = ListStart; m != None; m = m.ListNext) //do stuff
Replace ListStart
and ListNext
as follows:
Mutator Type | ListStart | ListNext |
Normal | Level.Game.BaseMutator |
NextMutator |
Damage | Level.Game.DamageMutator |
NextDamageMutator |
Message | Level.Game.MessageMutator |
NextMessageMutator |
HUD | MyPlayerPawn.myHUD.HUDMutator |
NextHUDMutator |
Spawn Notification List
This is a list of very powerful actors; SpawnNotifies. For more information read http://unreal.epicgames.com/UTMagic.html. Whenever an actor is spawned/received on a client, native code calls SpawnNotification(actor) on the LevelInfo.SpawnNotify.
Type and Scope
Type: Standard one-way list linked by LevelInfo.SpawnNotify.
Scope: Server and client-side. Note however that SpawnNotifies manually add themselves on both server and client, rather than having the list replicated, as is the case with inventory.
Adding and Removing
Adding: SpawnNotify.PostBeginPlay() automatically adds to the beginning of the list.
Removing: SpawnNotify.Destroyed() automatically removes that SpawnNotify. Feel free to call Destroyed() yourself to remove a SpawnNotify from a list, for this does not actually destroy the actor.
Note: The entire spawn notification system can be temporary deactivated as follows:
Local SpawnNotify Sn; Sn=level.SpawnNotify; Level.SpawnNotify=None; //do stuff here without worrying about Spawn Notifies. Level.SpawnNotify=Sn;
Other Cases of Linked Lists
- InterpolationPoint
- Uses a two-way list for interpolation of player locations and other properties between each point. While the list is created in unrealscript, that is as far as unrealscript handles it. Everything else is handled by native code.
- Pbolt
- Uses a standard one-way list that you will probably never need to touch. The plasma beam you see when alt firing the Pulse Gun is actually a linked list of Pbolt's. Unfortunately, the system wasn't written in the best way possible. Ideally, there should be a pointer to the StarterBolt in all beams. Variables only important to the beam as a whole (AccumulatedDamage, LastHitTime) are controlled in each beam, rather than only the list base. Thus when beams are destroyed, some of those values are lost, resulting in odd damage patterns.
- Mover
- Uses a one-way linked list that also has a pointer to the base. This is used for movers that follow other movers. Feel free to explore this code on your own.
- Actor.Deleted
- A list of actors that have been deleted and are awaiting garbage collection. This occurs when 128 actors are in the list. This is only used in Epic's native C++ code.
- SavedMove
- For player movement in a network environment, the client must save old moves to be sent later (to avoid overflowing the network and in cases of packet loss). The actual list is the standard one-way one, which carries move information. A playerpawn has pointers to the SavedMoves (the beginning of the SaveMove list), FreeMoves (a move that is "free"), and PendingMove (the next move to be sent to the server). Yet, this is a tutorial on lists, not something else. Study the SavedMove class and PlayerPawn.ReplicateMove() to learn more.
- Menu.ParentMenu
- This is simply a pointer to the previous green menu that showed. These "green menus" were used in Unreal I only. There is little need to understand how they work if you script in UT.
- ListElement
- This is a two-way list that was set up for use with the WebAdmin. It is a quite powerful method of storing data.
- UWindowList
- This is a very advanced list, which stores various data for the uWindow system.
- UWindowWindow
- All the menus seen in UT are actually stored with this complex list system.
Related Topics
Discussion
Daid303: How about a UT2003 version of this page?
Foxpaw: UT2003 uses the same lists. New lists of items have mostly been implemented as dynamic arrays instead. There is also one new linked list to my knowledge, the inventory list of an actor.
Wormbo: The linked list of Inventory actors was declared in Actor back in Unreal 1 v205 already. ;) We should probably create a page for all useful lists (not only linked lists) both in UT and UT2003. Things like the list of deleted actors can hardly be called useful in everyday UnrealScript coding...
Daid303: In UT2003 you also have the GUI, linked list? (didn't look) and controller ofcouse, is pawn still linked? And it would be nice to know what does and doesn't apply to UT2003. Oh, and how about a 'base' for linked list, 1 way, and 2 way. It would be alot easier of you could just copy a basic then work from that. I made a mistake in a 2 way linked list, and it took me quite a while to fix the remove function.
Wormbo: The GUI stuff is a set of dynamic arrays in the GUIController. Pawns aren't "listed" anymore, the Level.PawnList has been replaced by the Level.ControllerList.
Tarquin: I've rearranged other pages on linked lists. What should be done with this one?