Modify Mutator Tutorial
This page is one of a series of UnrealScript Lessons.
UTute 2: A Modify Mute (Cooler)
Now that you've been warned about OO and have created your own package and simple mutator, let's take a closer look at what a mutator really is and how to use them. For this, we're going to rip open the Cooler mutator from the original XPak. This little mute cools off adrenaline even if you aren't using it, forcing players to keep on their killing sprees to be able to really pull off those combos.
Quicknote on that last tutorial – you might want to completely delete your MyPackage directory, .u and .int file. Why? Because not everyone can have a package of the same name. In fact, nobody can – one MyPackage would simply overwrite over the other. Now would be a good time to create a new package with your own unique name.
The first question one should be asking is, "How did Reg know how to make this mute?" I don't work for Epic, never had any advanced look at the code, no classes, no manuals. Instead, the most common way you will find to learn how to do things in the Unreal engine is to compare it to something similar that is already functioning. In this case, I wanted a mutator that would lower the player's adrenaline about every second. That sounded like our old friend the Regeneration mutator, which raises the player's health about every second. So I hunted around the packages until I found the likely suspect of MutRegen.
I don't want this to sound to rudimentary. Sure, for a really simple mutator like this, it seems obvious - but later on when you are trying to figure out how the trace function works to precisely land a shot with the InstantFire class, it might not seem so clear. In fact, if you ask questions on the forums and newsgroups, expect to get answers like "Look in the RocketLauncherPickUp class".
Now, here is the cooler class in full:
//============================================================================= // MutCooler - Lowers Adrenaline //============================================================================= class MutCooler extends Mutator; #exec OBJ LOAD File=MutatorArt.utx var() int AdrenalineDecrease; // Don't call Actor PreBeginPlay() for Mutator event PreBeginPlay() { SetTimer(1.0,true); } function Timer() { local Controller C; for (C = Level.ControllerList; C != None; C = C.NextController) { if (C.Pawn != None && C.Adrenaline > 5 ) { C.Adrenaline -= AdrenalineDecrease; } } } defaultproperties { AdrenalineDecrease=2 GroupName="Cooler" FriendlyName="Cooler" Description="Cools off adrenaline" }
The top part is simply a comment block for describing things to the poor saps who might look at the code. The first real line of code is:
class MutCooler extends Mutator;
Which says that MutCooler is a child of Mutator, and inherits all its properties and methods (see Class syntax for more). Now, in order to make our specific mutator work, we simply extend Mutator's functionality by modifying or adding to those traits.
#exec OBJ LOAD File=MutatorArt.utx
Exec directives like this one tell the engine to interact with real files, like models and textures and the like. This one is here pretty much only because it was in MutRegen. They become more important when you start trying to retexture existing models and the like.
var() int AdrenalineDecrease;
In UnrealScript, you define global Variables before you define any methods or functions. While this is pretty standard for many languages, what may be weird to some is how you automatically define values (or instantiate) these variables
// Don't call Actor PreBeginPlay() for Mutator event PreBeginPlay() { SetTimer(1.0,true); }
Ah, one of my favorite parts of the mutator - because it looks like I was good and commented stuff. In reality, this was just something I copied from MutRegen. This redefines the event (events differ slightly from functions in how the native code sees them, but for the most part you can treat them the same) PreBeginPlay to start a timer. PreBeginPlay is called right after the object is created, but before gameplay begins. It and its similar function PostBeginPlay have a lot of usage when changing existing objects in the game. SetTimer has two variables - the first is the interval time for the timer, or how long to wait. The second is a boolean which sets the timer to repeat or not. Here, we set the timer to repeat about every second.
function Timer() { local Controller C; for (C = Level.ControllerList; C != None; C = C.NextController) { if (C.Pawn != None && C.Adrenaline > 5 ) { C.Adrenaline -= AdrenalineDecrease; } } }
The function Timer is what is actually called when the timer is triggered. This creates a local variable to hold a Controller. Controllers are new to UT2003, but could be easily described as the code which actually controls the pawn (player or bot) in the game. Pawns and Controllers work tightly together, allowing modders to modify distinct parts of them individually. Here, it runs through the current list of controllers (Level.ControllerList) and if that controller has a certain amount of adrenaline, it decreases it.
Quicknote about the "C.Pawn != None" test in the innermost if statement. For those familiar with java and similar languages, None in UScript is similar to null. It means that it won't perform the logic if that object isn't there. This is one of the most vital parts of scripting Unreal, because not catching instances where an object doesn't exist can not only break your code, but also slow down the entire game as the game logs the "Accessed None" error.
defaultproperties { AdrenalineDecrease=2 GroupName="Cooler" FriendlyName="Cooler" Description="Cools off adrenaline" }
Finally, the default properties section. This defines some of the basic variables for the mutator. AdrenalineDecrease here is used in the timer to determine the amount of adrenaline to decrease, and if this mutator were configurable - that number could be changed to alter the mutator's effects without having to alter any code. The other three define the mutator for the game. The "GroupName" is the group the mutator belongs to. There can only be one mutator of a group brought into the game at one time. "FriendlyName" is the title placed into the mutator list and "Description" is what will go into the description block when the mutator is highlighted.
Quicknote about structuring the defaultproperties section - notice the lack of semicolons
There's the cooler class. Pretty simplistic, really. Now the only thing left is to open up the int file and add:
[Public] Object=(Class=Class,MetaClass=Engine.Mutator,Name=XPak.MutCooler,Description="Cooler.")
So that the game will recognize it. MetaClass here is the kind of object you are describing. In the next tute, you'll see how adding a weapon can be made to show on the Weapons Database dropdown. Name is the full name of the class, or Package.Class and description is simply a text description. Currently, I don't think UT2003 really uses description for mutes, but it's best to put one in.
This tutorial was originally part of RegularX's UTutes? series.
Related Topics
- Weapon Mutator Tutorial: – your recommended next step
- UnrealScript Lessons – all the UnrealScript tutorials
- UnrealScript – all the reference pages
- Mutator Topics – more on mutators
- Making Mods – more on the organizational and social aspects
Comments
RegularX: It should be noted that the cooler class above is from the original xpak, and the code in the XXXPak has been updated (I think).
CorDharel: Question: Can I use the content of this tutorial for my own tutorial? I did one in german, and I think this way is really easy to understand. Would be great if i can.
RegularX: Certainly, just make all the usual acknowledgements/pointers back to the wiki.
CorDharel: I thank you, I did the acknowledgements and pointers. You can see the result on www.guc.ch.vu (sorry for propaganda), but I can't assure that you will understand it
CH3Z: Again, what if you are trying to mod for UT and don't have UT2003? This note extended from comment on Regen Mutator.
Heinz57: Hello all. This is my first edit at the Unreal Wiki. Regarding the mutators in XpakII, I had problems getting the SlipSuit.uc class to compile. The culprit seems to be the following code from the Tick function:
if (OldAmmo > RepAmmo) AmmoCharge -= 1.0; OldAmmo = RepAmmo;
The variable OldAmmo is not declared locally for this method or at the class level. And, I followed the class hierarchy up through TransLauncher, Weapon, Inventory and Actor... no declaration of this variable could be found. Perhaps I have an out of date version of the XpakII classes? If so, could someone please post a link within these tutes to the most up to date packages?
Thx
RegularX: Heya Heinz. The copy of the Translauncher code I have here declares var int RepAmmo, OldAmmo in the same line. That copy is a few patches old however and so Epic might have pulled a fast one and changed the class. In fact, my non-photographic memory (more like a grease drawing) is thinking that I've seen something about OldAmmo in the logs recently - so I think that's likely. If that's true (I can't confirm it on this computer), try replacing the Tick function with:
simulated function Tick(float dt) { if ( Bot(Pawn(Owner).Controller) != none ){ if(FireMode[0].AllowFire()){ SlipSuitActivate(FireMode[0]).DoFireEffect(); } } Super.Tick(dt);
And see if that compiles. The SlipSuit uses the tick to trick the bots into triggering the Suit whenever they can, and that's probably the only portion of the function which needs to alter from the parent class.
I'm working on finalizing the next version of the xxxpak, but it might not be out for another "two weeks" or so. If this continues to be a problem, email me at inkless@inkless.com.
Heinz57: Thanks for the quick response RegularX. I've replaced the Tick() method code as you've shown (with the addition of a closing brace after the call to Super.Tick() - assuming all other code after was to be ignored), and this, of course, works great. I'm afraid I can't test the change however (because I'm at work, and I have a feeling it would be frowned upon to fire up UT2K3 when I'm supposed to be happily coding ). Is this a fix then, or just a workaround?
To confirm your suspicion re: variable declaration in the TransLauncher class, see the following snippet from the v2225 source:
var TransBeacon TransBeacon; var() float MaxCamDist; var() float AmmoCharge; var int RepAmmo; var() float AmmoChargeMax; var() float AmmoChargeRate; var globalconfig bool bPrevWeaponSwitch;
By the way, this was the only problem stopping me from compiling, so you should be golden, now. Nice work, btw!
Cheers.
RegularX: Having the same work issue I can't confirm, but looking at the code it should be a fix, an improvement even, from the earlier SS class (because it won't let this happen again). If it flies, I'll incorporate into the upcoming version.
DarthDevilous: Hi, only my second post here (forgot to put a name here). Anyway, what would happen if AdrenalineDecrease were bigger than 5?
RegularX: ... well, try it and find out Nothing will - in fact in a later version both Heater and Cooler had configs to alter that to anything. So a larger number will just decrease the adrenaline that much faster.
T1: Just making sure, but does this code only work in UT2003? Because when I tried some of the XPak things on UT2004 they didn't work (GPFs)... It's been a while, so I don't remember if this mutator was one of the mutators UT2004 broke.
RegularX: I haven't tried these in UT2004 as I had always planned to recode them completely, so there might well be incompatibilities. I would think this one would work. The weapon ones won't. I have new code for UXL which combines heater, cooler and the other adrenaline mutes into one.