Worst-case scenario: the UEd Goblin wipes the map and burns down your house.

Legacy:Super Arena Mutator

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

This tutorial was created for an Unreal Script class at Bellevue Community College

Sam Beirne & Jesse

By the end of this tutorial you should understand how to create a mutator called “Super Arena.” This mutator lowers gravity, regenerates players’ health, and increases the number of jumps that may be linked together. It also starts players off with every weapon complete with a full clip of ammo.

//=============================================================================
// SuperArenaMut:
// Decreases Gravity
// Increases Maximum Number of Linked Jumps
// Players Regenerate Health
// Players Begin With All Weapons and Full Ammo
//=============================================================================

The class begins with a few comments describing the function of this mutator. A comment is any line that begins with the // character. These lines are only important to other coders and have no effect on the functionality of our code.

class SuperArenaMut extends Mutator;

In the first real line of code after the beginning block comment we declare a new class called SuperArenaMut that exends the Mutator class. Because SuperArenaMut is a child of Mutator, it inherits all of the methods and properties found in the Mutator class.

#exec OBJ LOAD File=MutatorArt.utx

Exec directives like the one above tell the engine how it interact with real files, like models and textures. For the moment this is not important and this line had been copied only because it appears in the Regen mutator which is a basis for the regeneration portion of our mutator.

var() float RegenPerSecond;
var float GravityZ;

Before any of our functions are declared we create two global variables to hold the amount by which players will increase health and the decrease in gravity. For greater precision, floating point values (real numbers, ex. 1.234) are used. These variables will be used later on in the code.

event PreBeginPlay()
{
    SetTimer(1.0,true);
}

Once the global variables have been declared (remember, global variables must be declared before functions), we create our first function, PreBeginPlay(). PreBeginPlay is called right after the object is created, but before gameplay begins. We use the function to start a timer by calling SetTimer. The first parameter of SetTimer is the interval time, or how long to wait. The second tells the timer whether or not to repeat.

function Timer()
{
    local Controller C;
 
    for (C = Level.ControllerList; C != None; C = C.NextController)
    {
		if (C.Pawn != None && C.Pawn.Health < C.Pawn.HealthMax )
        {
            C.Pawn.Health = Min( C.Pawn.Health+RegenPerSecond, C.Pawn.HealthMax );
        }
    }
}

The Timer() function is the code called each time the interval from SetTimer is reached. It creates a local Controller variable and checks every controller until it finds a Pawn (i.e. Player). When a Pawn is found its health is set to the minimum of its current health plus the regen rate or the max health. This allows the player to regenerate without ever going over the maximum health value.

function ModifyPlayer(Pawn Other)
{
    local xPawn x;   
    local Weapon m_wTemp;
 
    x = xPawn(Other);
    if(x != None)
    {
    	x.MaxMultiJump = 15; 
        x.MultiJumpBoost = 50;
    }
 
    Other.Health = 199;
 
    Other.GiveWeapon("XWeapons.BioRifle");
    Other.GiveWeapon("XWeapons.FlakCannon");
    Other.GiveWeapon("XWeapons.LinkGun");
    Other.GiveWeapon("XWeapons.Minigun");
    Other.GiveWeapon("XWeapons.RocketLauncher");
    Other.GiveWeapon("XWeapons.ShockRifle");
    Other.GiveWeapon("XWeapons.SniperRifle");
 
    m_wTemp = Weapon(Other.FindInventoryType(class'BioRifle'));
    m_wTemp.Ammo[0].AmmoAmount = m_wTemp.Ammo[0].MaxAmmo;       // in UT2004, instead use:  m_wTemp.MaxOutAmmo();
 
    m_wTemp = Weapon(Other.FindInventoryType(class'FlakCannon'));
    m_wTemp.Ammo[0].AmmoAmount = m_wTemp.Ammo[0].MaxAmmo;       // in UT2004, instead use:  m_wTemp.MaxOutAmmo();
 
    m_wTemp = Weapon(Other.FindInventoryType(class'LinkGun'));
    m_wTemp.Ammo[0].AmmoAmount = m_wTemp.Ammo[0].MaxAmmo;       // in UT2004, instead use:  m_wTemp.MaxOutAmmo();
 
    m_wTemp = Weapon(Other.FindInventoryType(class'MiniGun'));
    m_wTemp.Ammo[0].AmmoAmount = m_wTemp.Ammo[0].MaxAmmo;       // in UT2004, instead use:  m_wTemp.MaxOutAmmo();
 
    m_wTemp = Weapon(Other.FindInventoryType(class'RocketLauncher'));
    m_wTemp.Ammo[0].AmmoAmount = m_wTemp.Ammo[0].MaxAmmo;       // in UT2004, instead use:  m_wTemp.MaxOutAmmo();
 
    m_wTemp = Weapon(Other.FindInventoryType(class'ShockRifle'));
    m_wTemp.Ammo[0].AmmoAmount = m_wTemp.Ammo[0].MaxAmmo;       // in UT2004, instead use:  m_wTemp.MaxOutAmmo();
 
    m_wTemp = Weapon(Other.FindInventoryType(class'SniperRifle'));
    m_wTemp.Ammo[0].AmmoAmount = m_wTemp.Ammo[0].MaxAmmo;       // in UT2004, instead use:  m_wTemp.MaxOutAmmo();
 
    Super.ModifyPlayer(Other);
}

This next function is a little more complicated than the previous Timer() function. ModifyPlayer is called by GameInfo.RestartPlayer(), a function that executes each time a player enters the game or respawns. We start by creating a local xPawn variable to hold our player xPawn object. The local Weapon variable that is declared next will be used in a moment. First, after checking to see that our xPawn variable contains a valid object, we increase the MaxMultiJump from 2 to 15. We also increase the MultiJumpBoost to 50. This allows the players to jump higher and to link a greater number of jumps in succession. Next, using the Other object passed into the function we set the player's Health to 199.

Once all of the player's abilities have been increased, it's time to equip all of the non-super weapons. Using the GiveWeapon function that is a part of the Pawn class, each weapon is added to the player's inventory by passing in the weapon name as a string. Now that every weapon has been equipped they need to be filled with ammo. Using the local weapon variable we created earlier we retrieve the weapons from the player's inventory using FindInventoryType, another function found in the Pawn class. However, this function returns an object of type Inventory when we need a Weapon. The type is changed by casting the returned value to a weapon by wrapping the function like so: Weapon( function );. Once the weapons has been retrieved and is stored in the Weapon variable (m_wTemp) we can change the AmmoAmount to the MaxAmmo. It is important to use the max ammo of each particular class because every weapon has a different maximum ammo value.

On the final line of the function we call Super.ModifyPlayer(Other) to execute everything in the original ModifyPlayer class. This is an easier method to simulate copying the other lines right into our new function.

function bool CheckReplacement(Actor Other, out byte bSuperRelevant)
{
    local PhysicsVolume PV;
    local xPickupBase B;
    local Pickup P;
 
    PV = PhysicsVolume(Other);
    if ( PV != none )
        PV.Gravity.Z = GravityZ;
 
    B = xPickupBase(Other);
    if ( B != none )
        B.bHidden = true;
 
    P = Pickup(Other);
    if ( P != none )
        P.Destroy();
 
    return true;
}

The CheckReplacement() function iterates through all of the actors in a game so that they may be modified. First we create three local varibles to hold our actors. Just like in the previous function, we use casting to set these actors to the appropriate types. The first actor modified is the PhysicsVolume. This is a bounding volume that affects actor physics. We change the Gravity.Z component in order to lower the vertical gravity. Next all of the xPickupBases (the pads that sit under weapons, health, etc) are set to hidden by flipping the bHidden flag. Finally all of the Pickups (anything on the map that affects the player or changes inventory when walked over) are destroyed using the Destroy() function.

defaultproperties
{
     GravityZ=-150.000000
     RegenPerSecond=1.000000
     FriendlyName="Super Arena"
     Description="Regeneration, high jumps and all weapons."
}

In the last part of our class we set the defaultproperties of our new class. GravityZ and RegenPerSecond are the two gloabal floating point values created at the beginning of the class. The FriendlyName is the name that will be listed in the mutators menu in UT2003 and the description is what will be seen when the mutator is selected.

The new class file is completed, but we need to create a new package folder before it can be used. In the base directory of the UT2003 folder (usually C:\UT2003, but may be different depending on where the program was installed) create the following folders:

..\UT2003\SuperArena\
..\UT2003\SuperArena\Classes\
Put the new class file (named SuperArena.uc) into the new Classes folder you just created.

There is one final step to complete before running the mutator. We need to tell the engine about our new class. This is done using a .int file and modifying UT2003.ini. Create a file called SuperArena.int in your ..\UT2003\System directory and place the following code into it:

[Public]
Object=(Class=Class,MetaClass=Engine.Mutator,Name=SuperArena.SuperArenaMut,Description="SuperArena")

Then add the new package to your UT2003.ini file (located in the same folder) by adding the following line:

EditPackages=SuperArena

The new class is now ready to compile and run. Open a command prompt (Start-> Run-> "cmd") and navigate to your system directory. Type "ucc make" without the quotes and the packages should compile. Assuming there are no errors, fire up the game and the new mutator should be availiable.

Firestorm: HI This is MY FIRST POST.

HI Iv'e just completed the MOD Super Arena Mutator, compiled fine but it dose not showup in the Mutator list in game.

Im running PATCH 2199 and compiled with UDE with lattest Patch,HELP PLEASE..

LuckyStrike: Experiencing some issues compiling this for UT2004 - "Error, Can't access protected variable 'Ammo' in 'Weapon'" for the attempts to max out ammo in ModifyPlayer. Replacing each "m_wTemp.Ammo[0].AmmoAmount = m_wTemp.Ammo[0].MaxAmmo;" with "m_wTemp.AddAmmo(m_wTemp.MaxAmmo(0) , 0);" appears to fix the problem.

poorsod: I looked in xLastManStandingGame.uc and found

function AddGameSpecificInventory(Pawn p)
{
	local Inventory Inv;
 
	Super.AddGameSpecificInventory(p);
 
	p.CreateInventory("XWeapons.BioRifle");
	p.CreateInventory("XWeapons.FlakCannon");
	p.CreateInventory("XWeapons.LinkGun");
	p.CreateInventory("XWeapons.Minigun");
	p.CreateInventory("XWeapons.RocketLauncher");
	p.CreateInventory("XWeapons.ShockRifle");
	p.CreateInventory("XWeapons.SniperRifle");
 
	if ( bFullAmmo )
	{
		For ( Inv=P.Inventory; Inv!=None; Inv=Inv.Inventory )
		{
			if ( Weapon(Inv) != None )
				Weapon(Inv).MaxOutAmmo();
		}
	}
}

So I copied it across and it works fine for me :)

Geist: Yep, or you could also just use MaxOutAmmo() (for full ammo) or SuperMaxOutAmmo() (for 999 ammo) Weapon functions, instead of AddAmmo(). (UT2004)

AngryPanda: With the code as written, the log file (i.e., "UT2004.log") is spammed with warning messages (e.g., "Warning: SuperArenaMut DM-Antalus.SuperArenaMut (Function: SuperArenaMut.CheckReplacement:004B) Accessed None 'B'".) Adding the lines "if (B != None)" and "if (P != None)" before "B.bHidden = true;" and "P.Destroy();" respectively fixes this (similar to what was done with PV). I should note that this probably isn't that big a deal, since it would only happen once, at the loading of each level.

Geist: Good point, AngryPanda. I've adjusted the script (above) to fix this. I also added (UT2004-specific) comments to the max-ammo script lines as well.

Oh, and I also changed the timer interval from 2 to 1 sec in PreBeginPlay(). If we want to restore RegenPerSecond health points per second, then Timer() needs to execute each second (or compensate by doubling the health increase if you keep the timer to every 2 seconds). I figured setting the timer back to 1 second was the easiest solution, esp. for readibility in a tutorial. :)

DaWrecka: There's also another way that would let us keep the same average rate regardless of the timer rate; and it involves using just that, TimerRate. It's declared in Actor and, as you might expect, defines the timer's repeat rate in seconds. A simple (RegenPerSecond * TimerRate) in the Timer() function's Min() would allow us to vary the timer rate to whatever we liked. We could even add another variable for the server admin to define their own rate if we like. Slightly more elegant would be a RegenPeriod and RegenAmount; with RegenPeriod being the timer rate and RegenAmount just being how much it restores every 'RegenPeriod' seconds.

Also, in UT2004 the Pawn class has a function GiveHealth. It's defined as:

function bool GiveHealth(int HealAmount, int HealMax)
{
	if (Health < HealMax)
	{
		Health = Min(HealMax, Health + HealAmount);
        return true;
	}
    return false;
}

which would simplify matters a little. Using that, we could define Timer() as

function Timer()
{
    local Controller C;
 
    for (C = Level.ControllerList; C != None; C = C.NextController)
    {
		if (C.Pawn != None )
        {
            C.Pawn.GiveHealth(RegenPerSecond, C.Pawn.HealthMax);
        }
    }
}

That said, the code as it is should work fine, and I should probably be bearing the old "If it ain't broke don't fix it" adage in mind. Suffice it to say I have a habit of picking nits.