Mostly Harmless

Legacy:Mod Authoring/Making A Mutator

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search

Mutators are a great place to cut your teeth on UnrealScript because you are exposed to a limited, but powerful subset of the engine. As I said above, Mutators should only modify the game code in a relatively slight way. This increases the chances your mutator will work well when mixed with other mutators. (For example, you can play FatBoy, InstaGib, No Powerups deathmatch. A mix of 3 mutators).

All mutators descend from the Mutator base class either directly or indirectly. Let's make a Vampire mutator and see how it all works. Create a new file in your package classes directory called Vampire.uc. Drop the following code in there:

class Vampire extends Mutator;
 
var bool Initialized;
 
function PostBeginPlay()
{
	if (Initialized)
		return;
	Initialized = True;
 
	Level.Game.RegisterDamageMutator( Self );
}
 
function MutatorTakeDamage( out int ActualDamage, Pawn Victim, Pawn InstigatedBy, out Vector HitLocation,
	out Vector Momentum, name DamageType)
{
	if (InstigatedBy.IsA('Bot') || InstigatedBy.IsA('PlayerPawn'))
	{
		InstigatedBy.Health += ActualDamage;
		if (InstigatedBy.Health > 199)
			InstigatedBy.Health = 199;
	}
	if ( NextDamageMutator != None )
		NextDamageMutator.MutatorTakeDamage( ActualDamage, Victim, InstigatedBy, HitLocation, Momentum, DamageType );
}

The first line declares the class contained in this file. UnrealScript is like Java in that each file contains a separate class definition. We are saying that our class, Vampire, is a Mutator. It "inherits" all the properties of Mutator.

Next, we declare a class member. In UnrealScript, all class member variables must be declared before any functions (also called methods) are declared. The var keyword tells the compiler what we are doing. Here we have a boolean (true/false) value called Intialized.

Next we have a function called PostBeginPlay. To someone who isn't experienced with object oriented programming, we have a bit of a puzzle. This object just declares functions, it doesn't seem to have any entry point! The "entry point" is inherited. Vampire is a Mutator, so it does everything Mutators can. Mutator is an Info, Info is an Actor, and an Actor is managed by the engine. In our case, we are overriding the function PostBeginPlay. PreBeginPlay, BeginPlay, and PostBeginPlay are called on every Actor in the level when the game starts. They are used to initialize the world.

Our PostBeginPlay function starts by setting initialized to true. Notice it'll return if initialized is already true. What's the point of that? Well, the problem is that the BeginPlay suite of functions get called twice on Mutators. Unfortunately, this is somewhat unavoidable. BeginPlay and its friends are called on Actors when they are spawned and when the game starts. Mutators, unlike other actors, are spawned before the game starts... so you get two calls. The Initialized check is to prevent the rest of PostBeginPlay from being executed twice.

The second part of PostBeginPlay is called RegisterDamageMutator, a method located in the GameInfo (UT) class. Here we are accessing the Level property that all Actors inherit. Then we access the Game property of the LevelInfo class that Level points to. Finally, we call the function from that reference, passing our Self as the parameter.

RegisterDamageMutator is a special method that tells the GameInfo to call MutatorTakeDamage on this mutator whenever a pawn takes damage. Because pawns take a lot of damage during the course of a normal game, we don't want to call this function on every mutator, that would be slow. RegisterDamageMutator allows us to limit the calls to only a subset of mutators. (By the way, in UT Pawn (UT)s consist primarily of Bot (UT)s and PlayerPawns).

Next we have our implementation of MutatorTakeDamage. This is the heart of our Mutator. We are making a Vampire mutator. The idea is simple: if a Pawn A does damage to another Pawn B, give the Pawn A health equal to the amount of damage. Players and bots will effectively suck life off of other players or bots.

As I mentioned above, RegisterDamageMutator is called on our mutator whenever a player takes damage from another one. The pawns in question are passed to us in the variables.

We start by making sure we are only dealing with bots and playerpawns. There are pawns that are neither bots nor players and we don't want to deal with them. InstigatedBy refers to the player who dealt the damage. We do our core logic by adding to that pawn's health life equal to the damage dealt. RegisterDamageMutator always passes in an amount of damage after armor, so this is raw final damage. Finally, we don't want a player gaining so much life he becomes unkillable, so we limit the total life gain to 199 points.

To finish the function off, we call MutatorTakeDamage on the next damage mutator in the list. It is critical that you pass along method calls like this. If you fail to, damage mutators loaded after your own won't work right. There are other functions that need to be passed along, which we'll look at below.

So now you can save this file and rebuild your package. We aren't done yet, though, because the mutator won't show up in the menus without some more work. Open your package's .int file and add the following line to the [Public] section (in a single line):

 Object=(Name=SemperFi.Vampire,Class=Class,MetaClass=Engine.Mutator,
   Description="Vampire,You gain life equal to the amount of damage you do to an enemy.")

When the game looks for mutators to list in the Add Mutators window, it searches all .int files for objects with a declared MetaClass of Engine.Mutator. We've also added a Description for the help bar. Now we are ready to run Unreal Tournament and load the mutator. Play around with it for a bit and you'll probably get ideas for your own mutators or ways of expanding Vampire.


Prev Page: Legacy:Mod Authoring/How To Build Your PackageSection 7 of 12 – Next Page: Legacy:Mod Authoring/The Anatomy Of Mutator