Fools Rush In - Part Eight

The next event to build is the artillery barrage.

Quick recap: We want to encourage the player to stay around the bunker. Instead of breaking the context and blocking him in with invisible walls we're going to try and achieve this by blowing the hell out of the surrounding desert with high-explosives, because that's much more subtle.

This article is going to fiddle with code quite heavily and might be a bit dry for people who don't follow that kind of thing. What's worse is that I'm going to skip bits that are so dull even I can't stand it, so you won't even be able to replicate what I'm doing. Before you head off to read something more interesting let me just sum up and then you won't feel left behind next time.

I want to get an explosion to appear in-game on demand. Then I want to figure out how to spawn many of these explosions over time, get them to appear in a variety of places and then stop when the barrage is over. If you like you can scroll to the last few lines and I'll tell you if I managed to do all that or not.

First off we need a bang. The biggest bangs I recall from the original game come from the gas cylinder decoration (mesh objects use to add extra details to a level), so we'll use that as a starting point. Question is, can we Trigger the cannister to explode on cue?

Yes, we can. Good.

The next thing we need to do is get the triggered bang but make the actual cannister model go away. Again let's pull the same trick of duplicating the decorations class so we can edit our own copy without corrupting the original game.

Here's the code:

//=============================================================================
// ExplosiveCannister.uc
//=============================================================================
class ExplosiveCannister extends BreakableItem;

#exec OBJ LOAD File=..\Textures\VertexT.utx
#exec OBJ LOAD File=..\StaticMeshes\Terran_DecoM.usx
#exec OBJ LOAD File=..\System\ParticleRes\CannisterFX.u

defaultproperties
{
	HitPoints=25
	BrokenStaticMesh=StaticMesh'Terran_DecoM.Misc.gas_tank_destroyed'
	BreakSound=Sound'U2AmbientA.Explosions.Xplode_15'
	BreakParticleEffectsRefs(0)=ParticleSalamander'CannisterFX.ParticleSalamander0'
	DamageAmount=350.000000
	DamageRadius=256.000000
	DamageType=Class'U2.DamageTypeThermal'
	Momentum=10000.000000
	bShakeView=true
	Duration=0.900000
	ShakeMagnitude=5.000000
	bTossNPCs=false
	bTossPCs=false
	TimeBetweenShakes=0.100000
	DrawType=DT_StaticMesh
	StaticMesh=StaticMesh'Terran_DecoM.Misc.gas_tank'
	bNetDirty=true
	DrawScale=0.900000
	SoundRadius=1200.000000
	SoundVolume=240
	TransientSoundVolume=2.000000
	TransientSoundRadius=1200.000000
	CollisionRadius=13.000000
	CollisionHeight=61.000000
	bProjTarget=true
	bNoStaticMeshCollide=true
     UseReticleOnEvents(0)="UseReticleText"
     UseReticleOnEvents(1)="UseReticleCorners"
     UseReticleOnEvents(2)="UseReticleTopBars"
     ProximityReticleOnEvents(0)="ProximityReticleCorners"
     ProximityReticleOnEvents(1)="ProximityReticleTopBars"
}

These two lines deal with the display of the cannister:

	DrawType=DT_StaticMesh
	StaticMesh=StaticMesh'Terran_DecoM.Misc.gas_tank'

...so we'll delete the mesh line and replace the DrawType with a default icon:


	DrawType=DT_Sprite

We'll also take the opportunity to change the property bHidden to false, so we can see it in the editor but not in the game. A few other minor details to do with collision detection we can tweak and nothing can make our invisible cannister explode apart from an Event, just as the spider was triggered in the previous article. Addtionally, there are parameters to do with shake the screen around to various degrees and tossing near-by players and NPCs onto their bottoms. Sounds good. The finished artilleryshell class looks like this:


//=============================================================================
// ArtilleryShell.uc
//=============================================================================
class ArtilleryShell extends BreakableItem;

#exec OBJ LOAD File=..\Textures\VertexT.utx
#exec OBJ LOAD File=..\StaticMeshes\Terran_DecoM.usx
#exec OBJ LOAD File=..\System\ParticleRes\CannisterFX.u
#exec OBJ LOAD File=..\System\ParticleRes\concussion_grenade_FX.u

defaultproperties
{
	HitPoints=1
	BreakSound=Sound'U2AmbientA.Explosions.Xplode_15'
	BreakParticleEffectsRefs(0)=ParticleSalamander'CannisterFX.ParticleSalamander0'
	BreakParticleEffectsRefs(1)=ParticleSalamander'concussion_grenade_FX.ParticleSalamander0'
	DamageAmount=5000.000000
	DamageRadius=1024.000000
	DamageType=Class'U2.DamageTypeThermal'
	Momentum=10000.000000
	bShakeView=true
	Duration=0.900000
	ShakeMagnitude=20.000000
	bHidden=true
	bTossNPCs=true
	bTossPCs=true
	TimeBetweenShakes=0.100000
	DrawType=DT_Sprite
	bNetDirty=true
	DrawScale=20.000000
	SoundRadius=1200.000000
	SoundVolume=240
	TransientSoundVolume=10.000000
	TransientSoundRadius=2400.000000
	CollisionRadius=512.000000
	CollisionHeight=1.000000
	bBlockActors=false
	bBlockPlayers=false
	bCollideActors=false
	bProjTarget=true
	bDestroyCollision=false
	bNoStaticMeshCollide=true
     UseReticleOnEvents(0)="UseReticleText"
     UseReticleOnEvents(1)="UseReticleCorners"
     UseReticleOnEvents(2)="UseReticleTopBars"
     ProximityReticleOnEvents(0)="ProximityReticleCorners"
     ProximityReticleOnEvents(1)="ProximityReticleTopBars"
}

Viola. One bang to order.

The last thing to do is to change the class name (from 'ExplosiveCannister' to 'artilleryshell') and build it into our custom package.

Now then, let's put a bunch of them around the place and set them off. We want to set them off sequentially over time and the best way to generate lots of events over time is the Dispatcher.

The Dispatcher works just like the Trigger but can fire up to eight Events one after the other and if the last Event is another Dispatcher you can daisy-chain them forever. It looks a bit like this:

So let's set up seven artillery shells and name them all consecutively, Shell1, Shell2, Shell3 etc. Set the Events up in the dispatcher with about a 2 second delay and see what happens.

Hmm. Well, that was alright I suppose. A bit small, the blast radius was hardly artillery-like. And the damage is weedy considering we're supposed to be terrified of instant and bloody death from above. We can fix those easily enough, the properties are right there in DamageAmount and DamageRadius but the biggest problem is that our Dispatcher handled a barrage that lasted a little over 14 seconds. We want it to go on for around ten minutes so, quickly whipping my socks off so I can also count on my toes, that means we need, um, 300 artillery shell objects and 43 Dispatchers to land a shell every 2 seconds.

Bollocks to that. (For non-British readers who don't know what 'bollocks' are, they are small green vegetables often found in the more rural areas of the British Isles).

No, we're going to have to be smarter than that. Let's think about the ideal sequence of events:

  1. Create an artilleryshell class.
  2. Trigger the created shell and get a bang.
  3. Wait for 2 secs.
  4. Start over until the ten minutes is up then quit.

Okay, well we know how to create actors out of fresh air, because we once spent weeks rooting through the Unreal engine actorlist looking for useful things. Didn't we? One of us did, anyway.

This is an ActorFactory:

ActorFactory make actors of a specified class on recieving an Event. Useful, eh? Even more useful is that in order to work they need to have another object associated with them called a SpawnPoint, or several even. All you have to do is give the SpawnPoints the same name as the ActorFactory and any actors (in our case the custom artilleryshell class) that it creates appear at one of the SpawnPoints. We can even decide whether or not we want the SpawnPoints to be used sequentially or randomly. That's inadvertently solved a problem for us before I even got chance to notice it. Now we can make the shells land in roughly the right area (by grouping the SpawnPoints) but also randomly so running out into the open will contain quite a bit of chance. We might call it a 'sporting chance' but only in the same way a an old and be-moustached British peer might say 'heh, sporting chance' whilst sighting along the barrels of a shotgun at a defenceless grouse.

So we set up the Actorfactory to do the following things:

  1. Wait for the Event 'Barrage'.
  2. On the Event, spawn an object of the class 'artilleryshell'.
  3. Do this randomly at one of several possible SpawnPoints with the name 'Barrage'.
  4. Name the artilleryshell 'shell' so we can trigger it.
  5. Wait until the event 'Barrage' comes again then start over.

All well and good.

Setting up the actor factory like this saves us from having to place 300 individual artilleryshell objects in our map and then referencing them individually, but we'd still have to trigger the created shell. We need to find a way of triggering more shells on a loop.

These articles are always written with the benefit of hindsight, where I can neatly lay out the methods for the end result in a logical way. In reality, the process involves an awful lot of trial and error and retrial and test runs in a messy and sometimes confused way. I'll save you from that by describing the next coherent stage I managed to get to: four actorfactories and four sets of spawnpoints each creating a shell per 'barrage' that's triggered by an intermediate Dispatcher.

So, like this:

  1. A Barrage Event is sent ...
  2. ActorFactory One creates Shell 1 at a random SpawnPoint.
  3. ActorFactory Two creates Shell 2 at a random SpawnPoint.
  4. ActorFactory Three creates Shell 3 at a random SpawnPoint.
  5. ActorFactory Four creates Shell 4 at a random SpawnPoint.
  6. A Dispatcher explodes Shell 1, then Shell 2, then Shell 3, then Shell 4 at two second intervals.

This barrage lasts six seconds and an array of daisy-chained Dispatchers that do nothing but send out the Event called Barrage at intervals gives us our ten minute assault.

But testing the chaos reveals an interesting (and slightly irritating) side-effect of our automation: it's not so chaotic. The Dispatcher controlling the explosion of the four shells is fixed, each barrage lands with exactly the same pattern and listening to it for the full ten minutes soon becomes tedious and, well, repetitive. It proves the age old adage that it's not the size of the bang that counts, it's the rhythm. Heh.

What we need to do is add a short but random delay before the triggering of each shell, to break up the sequence on each barrage. Scanning through the actorlist reveals that Unreal2 doesn't include a trigger-object capable of generating a random delay, so if that's what we want then we'll have to write one.

This is normally closer to the code than I like to get, but I remember that Col wrote a random trigger to control the pop-up targets in Cassandra's shooting range and maybe I can emulate his sterling efforts.

First thing to find is a basic class, preferably something with most of the functionality already in the code. This turns out to be the Dispatcher again, as it has all the code to fire an event after a delay (in the Dispatcher's case the delay is defined in the editor by the mapmaker). We can rip that class off and replace the delay value with one generated randomly.

The procedure is the same as always, duplicate the Dispatcher's code file, give it a new name, edit the code and build it into our custom package.

This is the original code from the Dispatcher:

//=============================================================================
// Dispatcher: receives one trigger (corresponding to its name) as input, 
// then triggers a set of specifid events with optional delays.
//=============================================================================
class Dispatcher extends Triggers;

#exec Texture Import File=Textures\S_Dispatcher.pcx Mips=Off Masked=1

//-----------------------------------------------------------------------------
// Dispatcher variables.

var() name  OutEvents[8]; // Events to generate.
var() float OutDelays[8]; // Relative delays before generating events.
var int i;                // Internal counter.

//=============================================================================
// Dispatcher logic.

//
// When dispatcher is triggered...
//
function Trigger( Actor Other, Pawn EventInstigator, optional name EventName )
{
	Instigator = EventInstigator;
	gotostate('Dispatch');
}

//
// Dispatch events.
//
state Dispatch
{
	ignores trigger;

Begin:
	for( i=0; i 0 )
				Sleep( OutDelays[i] );
			TriggerEvent(OutEvents[i],self,Instigator);
		}
	}
	GotoState('');
}

defaultproperties
{
	Texture=Texture'Legend.S_Dispatcher'
     UseReticleOnEvents(0)="UseReticleText"
     UseReticleOnEvents(1)="UseReticleCorners"
     UseReticleOnEvents(2)="UseReticleTopBars"
     ProximityReticleOnEvents(0)="ProximityReticleCorners"
     ProximityReticleOnEvents(1)="ProximityReticleTopBars"
}

First of all we don't need the Dispatcher's ability to fire more than one Event (although we could leave it be, I'd prefer to focus our new trigger on one task) so we can shorten the arrays called OutEvents[8] and OutDelays[8] to a single variable.

Here's what the edited code looks like:

// Trigger variables.

var() name  OutEvent; // Event to generate.
var float OutDelay; // Final delay before generating events.
var int i;                // Internal counter.

The next thing we need to do is to find the variable that deals with the length of time the Dispatcher waits before triggering the Event. It's OutDelay. Now we need to replace the user input for this variable with a randomly generated value.

It's worth mentioning at this point that I really don't know much about 'proper' programming. Everything I can achieve here is done scavenger-style, rooting through the scripts and code files in the game looking for functionality that's similar to what we want and begging, borrowing and stealing bits of it, cobbling it together and trying it out to see if it works. It helps that UnrealScript is what they call 'high-level' which basically means it's closer to 'real' language than it is to machine code and it helps that there are many references, tutorials and help files available on the internet but we mustn't lose sight of the fact that we're essentially raiding the scrap-yard to make a vehicle to get us where we're going.

There's a line of code in another file that shows us how to generate random numbers from between a specified range. The line is:

RandRange( MinTime, MaxTime )

...in a class that looks like it's supposed to make random lightning strikes somewhere. We can steal that and also, because the original line takes two variables we can ask the user which range he wants random values from, an added bit of functionality that's going to make us look like we know what we're doing. Don't tell anyone, we'll bluff it.

Here's the finished RandomDelayTrigger:

//=============================================================================
// Hacked Dispatcher: this Randomly Delayed Trigger is
// Shameless Bodgeware courtesy of Always_Black
//=============================================================================
class RandomDelayedTrigger extends Triggers;

//-----------------------------------------------------------------------------
// Trigger variables.

var() name  OutEvent; // Event to generate.
var float OutDelay; // Final delay before generating events.
var int i;                // Internal counter.

var() float MinTime; //Minimum delay time
var() float MaxTime; //Maximum delay time

//=============================================================================
// Trigger logic.

//
// When triger is triggered...
//
function Trigger( Actor Other, Pawn EventInstigator, optional name EventName )
{
	Instigator = EventInstigator;
	gotostate('Dispatch');
}

//
// Trigger event.
//
state Dispatch
{
	ignores trigger;

Begin:
		OutDelay = RandRange( MinTime, MaxTime );
		if( (OutEvent != '') && (OutEvent != 'None') )
		{
			if( OutDelay > 0 )
				Sleep( OutDelay );
			TriggerEvent(OutEvent,self,Instigator);
		}
	GotoState('');
}

defaultproperties
{
	MinTime=0
	MaxTime=1
	Texture=Texture'Legend.S_Dispatcher'
     UseReticleOnEvents(0)="UseReticleText"
     UseReticleOnEvents(1)="UseReticleCorners"
     UseReticleOnEvents(2)="UseReticleTopBars"
     ProximityReticleOnEvents(0)="ProximityReticleCorners"
     ProximityReticleOnEvents(1)="ProximityReticleTopBars"
}

Let's build it into the code and see what it looks like in the map editor.

So instead of waiting for exactly 2 seconds between each shell explosion, we'll generate a random value /up to/ two seconds. Instead of triggering the shells directly from the 'Barrage' Dispatcher, we'll trigger these randomifiers (trademark!) and let them trigger the shells when they feel like it.

In case you got lost in all that (hell, I did) here's a diagram of how the final artillery strike system operates. This is useful, because in two weeks we'll have mostly forgotten how it all hangs together and we can refer back to this when we need to adjust it instead of sobbing like children.

...as you can see we can kick the whole thing off simply by sending a single event 'artillerystrike' to the system. Job done.

If you've just skimmed down from the top to find out how things went, it went FINE, okay? We didn't need you.

Here, make yourself useful and update the plan.

Next time we're going to kick up some dust and make a sandstorm.

Comments: on the _blackbored

Next: Part Nine