Tribes Vengeance Tutorial: your own explosion class

Making fireworks

index

This tutorial shows how to make your own explosion class, with that you can have lots of fun ;-). When the explosion is triggered, it spawns some projectiles that spread it random directions.

First we make a new folder in 'Source\Game' called for example 'MyFireworkExplosion'. We create 'Classes' dir in there and a new file named 'PyroExplosion.uc'. Like in every class we put the following line in there:

Class PyroExplosion extends Gameplay.Explosion;

Because our new class is a explosion, we extend the explosion class, found in Gameplay. Then we add the variable block:
var (Explosion) class<Gameplay.Projectile>	projectileClass 	"The class of the projectiles spread in random directions. hehehe...";
var (Explosion) int 						projectileCount 	"The number of projectiles to spawn";
var (Explosion) float						projectileVelocity	"Speed of the projectile when it is fired"

Now you have to know that explosions have certain function called 'Trigger', that is called when the explosion is explode. You can look in the explosion class to see that.
We replace this function:
function Trigger(Actor Other, Pawn EventInstigator)
{
	local int i;
	
	StoredEventInstigator = EventInstigator;
	
	for(i = 0; i < projectileCount; i++)
	{
		makeProjectile(Other, RotRand (true), Location);
	}

	GotoState('Exploding');
}

The for-loop calls the function 'makeProjectile' 'projectileCount'-times, with a random rotation. Then we goto state 'Exploding'. Its time to add our functions:
function initialiseVelocity(Gameplay.Projectile p, Vector InitialVelocity)
{
	p.InitialVelocity = InitialVelocity;
	p.Velocity = InitialVelocity;
}

protected function makeProjectile(Actor A, Rotator fireRot, Vector fireLoc)
{
	local Gameplay.Projectile p;

	p = spawn(projectileClass, A.Owner, , fireLoc);
	if (p == None)
	{
		LOG("Warning: Explosion "$Name$" spawn projectile "$projectileClass$" failed.");
		return;
	}
	p.SpawnTick = LastTick;
	p.SetRotation(fireRot);
	initialiseVelocity(p, (Vector(fireRot) * projectileVelocity));
	p.Acceleration = Normal(p.Velocity) * (p.AccelerationMagtitude);
}

'initialiseVelocity' is just for setting the projectile's velocity, while 'makeProjectile' spawns the projectile and set the right speed and rotation.
In the end we add our defaultproperties:
defaultproperties
{
	damageTypeClass = Class'ExplosionClasses.DamageTypeExplosionDefault'
}

That's it. Our explosion class is done. Here you can donwload the file, if you dont like copy & paste.

Ok, now we can compile it. Add 'EditPackages=MyFireworkExplosion' to UCC.ini at the right place and run 'ucc make -nobind'.
Everything should be fine.
You can use your new class in TVed: change the extension to '.pkg', goto class browser and open you package. Then duplicate the class (right click, New...) and store it into another package. Then you can doubleclick the copy and edit it's properties. You can also set defaultproperties directly in the code.

The projectiles are spawned into a random position, so when the explosion is triggered on the ground (a projectile hits the ground), half of them hit it directly. To prevent this, we can make the explosion aligning to the floor and only spawning away from it. Make a new class in our package called 'DirectedPyroExplosion', that extends our 'PyroExplosion':

Class DirectedPyroExplosion extends PyroExplosion;

We add another varibale:
var (Explosion) float						projectileSpread	"Max angular deviation from ideal line of fire in degrees";

...and replace the trigger function again:
function Trigger(Actor Other, Pawn EventInstigator)
{
	local int i;
	local rotator fireRot, r;
	local float spreadInRotUnits;

	StoredEventInstigator = EventInstigator;

	fireRot = Rotator(getHitNormal(Other));
	spreadInRotUnits = projectileSpread * 65536 / 360;
	
	for(i = 0; i < projectileCount; i++)
	{
		r.Yaw = spreadInRotUnits * (2.0f * FRand() - 1);
		r.Pitch = spreadInRotUnits * (2.0f * FRand() - 1);
		makeProjectile(Other, fireRot + r, Location);
	}

	GotoState('Exploding');
}

The new function 'getHitNormal':
function vector getHitNormal(Actor A) {
	local vector HitLocation, HitNormal;
	if( A != None && A.Trace(HitLocation, HitNormal, Location + 100 * Vector(Rotation), Location - 10 * Vector(Rotation), true) != None )
		return HitNormal;
	else
		return Normal(Vector(Rotation) * -1);
}

When the explosion is spawned, it's rotation is the same like the actor that spawned it. Because we want to align the explosion to floor, we make a shor trace into the direction and get the 'HitNormal', a vector that we can convert into a rotator. If the trace failed, we reverse the rotation of the explosion.
We set projectileSpread to 60 in defaultproperties:
defaultproperties
{
	projectileSpread = 60
}

Now you can compile again. Download the source file.

Heres an example how to use this new explosion classes:
Our Mutator class:

class Mutator extends Gameplay.Mutator;


function string MutateSpawnCombatRoleClass(Character c)
{
	local int i, j;
	
	for(i = 0; i < c.team().combatRoleData.length; i++)
		for(j = 0; j < c.team().combatRoleData[i].role.default.armorClass.default.AllowedWeapons.length; j++)
			if(c.team().combatRoleData[i].role.default.armorClass.default.AllowedWeapons[j].typeClass == Class'EquipmentClasses.WeaponSpinfusor')
			{
				c.team().combatRoleData[i].role.default.armorClass.default.AllowedWeapons[j].quantity = 12;
			}
				
	return Super.MutateSpawnCombatRoleClass(c);
}

function Actor ReplaceActor(Actor Other)
{
	if(Other.IsA('Spinfusor'))
	{
		Gameplay.Spinfusor(Other).projectileClass = Class'UltraDisc';
		Gameplay.Spinfusor(Other).roundsPerSecond = 0.1;		
	}
		
	return Super.ReplaceActor(Other);
}

defaultproperties
{
}

The new 'UltraDisc':
class UltraDisc extends EquipmentClasses.ProjectileSpinfusor;

defaultproperties
{
	ExplosionClass = Class'DirectedPyroExplosionMortar'
	LifeSpan = 6
}

The explosion:
Class DirectedPyroExplosionMortar extends DirectedPyroExplosion;

defaultproperties
{
	projectileClass = Class'ProjMortar'
	projectileCount = 10
	projectileVelocity = 4000
}

New projectile:
class ProjMortar extends EquipmentClasses.ProjectileMortar;

defaultproperties
{
	ExplosionClass = Class'PyroExplosionBurner'
}

and the 2nd explosion:
Class PyroExplosionBurner extends PyroExplosion;

defaultproperties
{
	projectileClass = Class'EquipmentClasses.ProjectileBurner'
	projectileCount = 4
	projectileVelocity = 6000
}

Put this classes all in the same package and run tribes like this: 'TV_CD_DVD.exe mp-emerald?mutator=MyFireworkExplosion.Mutator' (after you have compiled and copied the package into your 'Bin' folder).