This tutorial will cover the basics of making Assault maps for UT2004. This tutorial assumes you have a basic knowledge of the editor, if not you should read my Basic Tutorial before continuing.
Assault is a very complex gametype, and it is easy to get lost in ScriptedTriggers and player spawn managers. It would be good to come up with a standard way of doing things that works for you, an easy way to keep everything organized so you know exactly where to look if something isn't working right or you want to add more to what's already there.
Let's take a look at my example layout.
We will start at the end of a hallway and work our way into the first room, where we will pick up an energy core and deliver it to the other end of the room, like the first objective in Junkyard. Next we will make our way to the second room where we will have to destroy a door to continue. Then we will proceed to a small room on the side and hit a trigger objective, and finally go to the last room and use a Hold Objective, like the Nexus missile objective in Convoy.
The first thing we need to do is change the default gametype of our level to Assault:
For our first objective, let's add a NavigationPoint >> JumpDest >> JumpSpot >> GameObjective >> ASOBJ_EnergyCore_Spawn to the first room. These always use the Hellbender engine, to change it you will need to subclass it as well as the Decoration >> GameObject >> GameObject_EnergyCore and change the default static mesh there, and the FlagType of the spawner (under None, type "editdefault class=myenergycorespawn to access it). In addition, if this objective isn't the first in your map, the icon will still show on the HUD. To fix this I've created a subclass that fixes the problem. Just follow the instructions in the zip to use it.
On the far side, add a NavigationPoint >> JumpDest >> JumpSpot >> GameObjective >>ProximityObjective >> ASOBJ_EnergyCore_Delivery actor. This actor uses its CollisionRadius as the boundary to complete the objective, meaning you have to bring the Energy Core within its radius to complete the objective. If you right click the top of the 3D viewport and hit Actors >> Radii View, you can select it and see the boundary, and adjust it with the Collision properties.
For our second objective, we need to add the door to be destroyed. Add a NavigationPoint >> JumpDest >> JumpSpot >> GameObjective >> DestroyableObjective >> DestroyableObjective_SM to the room. For this example I will be using the default static mesh that it uses, but you can change it in its Display >> StaticMesh property. Fit it to the door and let's take a look at some of its properties.
Announcer_ - These properties change the voice announcements associated with this objective.
bPlayCriticalAssaultAlarm - Wether or not to play the alarm sound when someone is attacking this objective, or is getting close to it for hold/trigger objectives.
DrawDistThreshold - This is the distance at which the effects for this objective take place, for instance the hand that shoots from you to a HoldObjective. The default, 0, means infinite.
EndCameraTag - By default, if a match ends while this objective is still being defended, the camera will lock onto the objective at a fixed distance. Putting a camera tag here will make the view go here instead.
Objective_ - These three properties are what the HUD will display for the attackers and defenders, so it's good to make them describe the objective so people will know what they are.
DamageCapacity - This is the health for a destroyable objective.
AntiPortalTag - This is the antiportal associated with this objective. This is useful for hiding stuff behind doors/walls until the objective has been destroyed.
DestroyedStaticMesh - Once destroyed, the objective will use this static mesh.
Event - The objective will trigger this event when it has been completed. Useful for triggering effects, and for changing player starts as we will do later.
For our third objective we will place a NavigationPoint >> JumpDest >> JumpSpot >> GameObjective >> TriggeredObjective in our small room. These aren't actually triggerable by players, so we will have to add a TeamTrigger to use.
To get this objective working, simply make the Tag of the objective the same as the Event of the TeamTrigger.
Now for our last objective. Place a NavigationPoint >> JumpDest >> JumpSpot >> GameObjective >> ProximityObjective >> HoldObjective in our last room. For these to work, they must have a Mover associated with them. For this example I will be using a simple cube that moves from the middle of the room to the bottom in 5 seconds.
If you don't know how to make movers, I would suggest reading a tutorial to familiarize yourself with them. For this case, I will just run you through the settings to change:
Mover >> MoveTime = 5
Mover >> MoverEncroachType = ME_IgnoreWhenEncroach
Collision = All False
The major difference between this and other movers is the InitialState:
This makes it so the trigger will move as long as it is being triggered, but won't reset once it stops being triggered. To associate this mover with our HoldObjective, lets take a look at the properties.
Pretty simple, just the Tag and MoverTag. Now lets look at the Mover properties.
It's important to set the Mover's Event to the Tag of the hold objective. Once the mover has reached its final keyframe, it will trigger the objective to let it know that it's done. If the mover and objective aren't properly set up, this objective will not work at all and you will get an error in your log file when you play the level letting you know about it.
Putting Our Objectives In Sequence And Ending The Match
Now we have to put our objectives in order so they aren't all displayed at once. There is only one property in each objective that we need to adjust for this.
Contrary to what you would think, these actually have to be labelled backwards, starting from 0 with the last objective and working your way up through 1,2 etc going towards the first objective. It is important not to skip any numbers or the objective list will cut off. More than one objective can have the same number, for instance the objective in FallenCity where you have to destroy the two door controls for the door to open.
Now that that's all good, we still need to tell the game when the match is over. Add a Trigger >> Trigger_ASRoundEnd to your map, preferably near the last objective to keep it easy to find, then set the Tag of the trigger to the same thing as the Event of the last objective.
Player Spawn Managers
This is easily the most complicated aspect of Assault maps. As players progress through the maps you'll want to have the start locations for the Attackers and Defenders change so they won't end up walking across the entire map to get to the next objective. Let's take a look at my plan for our example map.
We will need 5 sets of player starts, 0 through 4. At the beginning of the match the attackers will start at 0 and the defenders at 1. After the energy core has been delivered, the attackers will move to 1 and the defenders to 2. After the door has been destroyed the attackers will move to 2 and the defenders to 3. And finally, after the trigger has been triggered, the defenders will move to 4 and the attackers will remain at 2.
First, to add the player starts.
The only property there we are concerned about is the TeamNumber. In general you'll want to add plenty of player starts to each area, so I'm going to add 12 to each of my numbered places, and set their TeamNumber to the numbers I have in my plan.
Now to manage them. Lets start by adding an Info >> PlayerSpawnManager to each set of player starts. Since points 1 and 2 will be used by both the attackers and the defenders, I'm going to add another one to those places.
Now lets take a look at the properties of the spawn managers.
AssaultTeam - Lets you choose between the Attackers or Defenders.
bAllowTeleporting - Allows players to teleport to these spawn points once they become active.
bEnabled - Wether this PlayerSpawnManager starts out active or not.
PlayerStartTeam - This should be the same number as the TeamNumber of the player starts you want this manager to control.
Lets start by setting the PlayerStartTeam of each of the PlayerSpawnManagers to the TeamNumber of the player starts they're going to control. Once that is done, we need to set all of the defense ones to Defenders. Finally, to start with only the Attacker manager at point 0 and the Defender manager at point 1 are going to be active initially, so set all of the other ones to bEnabled = False.
Once that is done, we need to trigger the changes for each objective. To do this we're going to use KeyPoint >> AIScript >> ScriptedSequence >> ScriptedTriggers. Let's start by setting one near the EnergyCoreDelivery objective. Open its properties and hit Add 5 times next to AIScript >> Actions. Set the first one to ACTION_WaitForEvent, and set the ExternalEvent in that to the Event of the EnergyCoreDelivery objective. That way, when the objective is completed, this scripted trigger will continue through the rest of its actions. The other 4 actions should all be set to ACTION_TriggerEvent, we'll use 2 for each team. For the first two, we want to enable the defender starts at point 2 and disable them at point 1.
It's generally a good idea to make a connection before you break one, so for the first TriggerEvent, set the Event to the Tag of the defender PlayerSpawnManager at point 2. For the second TriggerEvent, set the Event to the Tag of the defender PlayerSpawnManager at point 1. Now we need to take care of the attackers. We want to enable the starts at point 1 and disable them at point 0.
For the third TriggerEvent, set the Event to the Tag of the attacker PlayerSpawnManager at point 1, and for the last TriggerEvent set it to the Tag of the attacker PlayerSpawnManager at point 0. In the end, your script should look something like this:
In my example I have set the Tags of the PlayerSpawnManagers to Defender1, Attacker0 etc.
Miscellaneous Final Bits
In the official maps you'll notice there is a cinematic sequence at the start and end of the match. While I will not be covering Matinees in this tutorial, making the intro scene is essentially the same except that the name of the first scene must be IntroScene. To make the Ending scene, you will use an Info >> ASCinematic_SceneManager instead of a normal SceneManager, and ASCinematicCameras instead of InterpolationPoints. To trigger it, simply set its Tag to OutroScene.
That's it for the basics of Assault. There is a lot that isn't covered in this tutorial, such as adding effects, that are covered under other tutorials. Assault combines all of the mapping skills of other gametypes and adds a whole lot more, and they are easily the most difficult maps to make. Good luck!