Basic Objective Tutorial
January 2024
Zombie Panic! Source Level Creation
--Ratboy601 02:21, 16 March 2012 (PDT)
Contents
Intro
First of all, don't forget to name your map starting with "zpo_" i.e. "zpo_biotec"
I've created an example map which can be downloaded @[1]. These files (a .bsp and a .vmf) are much more up to date than the following tutorial and use more techniques than are laid out below. Nevertheless I recommend trying to reference the .vmf in the editor if you're ever confused. Also, its a good idea to play the .bsp in game before reading the tutorial or looking at the .vmf. This way you will understand what each objective is supposed to do and therefore what you are trying to accomplish.
I'll begin by breaking it down into types of objectives.
Types of objectives:
1. Press a button (i.e. turn off the power, activate the generator, pick up static item, open a door, etc.)
2. Hold and Hack (i.e. hack the computer, a timed and/or "don't leave this small area for a specific amount of time" kind of objective)
3. Find a randomly spawning item
4. Pushable
5. Find multiple items, or activate multiple machines/objects/buttons (to accomplish a single objective) (this one is not included in the map pack yet).
6. Advanced Hold n' Hack w/timer (All credit to zp:s level designer EArkham for scripting of the timer).
The most important thing to remember is that objectives are an art, not a science. There is no right or wrong way to do your objectives. There are only efficient and in-efficient ways to do them. And after I teach you the basics of manipulating the inputs and outputs to do these staples of mapping objectives, you should be able to use your imagination to do almost anything you want with them in future projects.
Press a button
This is the easiest type of objective to accomplish, so lets start here. Whether you want to use a pre-existing model as your button, such as a big panel with a lever, or the elevator button, or the keypad, you'll need to create a brush based entity called "func_button." This entity will fire any output when a player presses "use" on it. You can make it invisible and stick it in front of your chosen model, OR you can apply a texture to it and use it as the visual AND functional button.
Let's say you want an objective of your map to be: enter the code into the keypad. You've chosen a model (prop_dynamic) to be your button. Give it a name, like "keypad." Create the brush button (func_button), make it roughly the same size as the keypad prop, apply the invisible texture, and give it a name also, like "b2". Depending on if this is your first objective, you might want the button to start locked. All you need to do this is open the buttons properties, go to "flags" and click "starts locked." The previous objective will have to fire an output at this button to unlock it. An entity involved with the previous objective will have an output that looks like this:
My output named: Onpressed
Target entities named: b2
Via this input: unlock
So bear in mind that that's all an objective is: a series of inputs and outputs. When one objective is activated, it enables the next, usually by unlocking the next button, which is generally the beginning in a chain of events (usually tiny details) that make up the objective. You'll be surprised how many little thigns pop up that need to happen to set up the following objective. They pile on quickly.
Back to our button "b2". Let's say this button opens a door that makes way for the next section. You'll have to add an output to open the door, based on the name of the door (lets call it "door1"). You'll add an output to "b2" that looks like this:
onpressed; door1; open
Another very important output to add to this button is whatever activates the next objective, assuming this is what you want to trigger it, and not another event. Almost any objective is going to involve a button for player interaction purposes as you'll see as we move on, so lets say the next objective is activated by a button named "b3". You need to add an output to "b2" like this:
onpressed; b3; unlock
Since the 2.0 update you don't need to worry about filtering as much since buttons have built in filters now. All you have to do is choose which team you want to be able to press the button in the object properties "class info" tab. Now lets discuss details.
You'll probably want to tell players what the next objective is, or let them know what their amazing button pushing skills have won them via some screen text. So lets say you have a "game_text" entity all set up and named "txt1". Add an output to "b2" like this:
onpressed; txt1; display
Also, you want the keypad to give a visual response to let the player know they've done well. The model "keypad.mdl" found in the directory ".../models/props_lab/keypad.mdl" has two alternate skins that are identical except one makes the screen read "access" and the other "error" while the default skin is blank. To make it read "access" upon pressing the button, add this output to "b2":
onpressed; b2; skin
with a parameter override of: 1
I think you get the idea at this point. You need to be sure to cover ALL your bases. Don't forget things like beacons or hint textures need to be enabled and disabled at the appropriate times. Only you as the mapper can decide what that time is and simply use your guile to ensure it functions properly. Always test in every way you can think of. If you can imagine it can go wrong, then it probably will. So fix it! Lets move on to a more complicated type of objective.
Hold and Hack
You'll need several entities to work in symphony for this type of objective.
a. logic_timer (I'll name it: timer)
b. trigger_multiple (name: trigger)
c. func_button (name: button)
d. func_brush and/or info_beacon (name: hint)
e. game_text x3 (names: hacking, reads: "hacking"; failed, reads: "failed"; successful, reads: "successful")
You'll notice that the basic concept is the same as a plain-jane button objective, it just has some middle man functions and is a little more dynamic. So once you've created the entities listed above we're ready to move on.
Shape your func_brush to whatever area you want the player to notice and attempt to use (i.e. a computer console). Apply the zp_redbox to it, and open the texture editor (SHIFT + A) and fit the texture. Or simply place your info_beacon in the area. You can do both if you wish, but it seems a bit redundant don't you think? This entity must either start enabled, or be enabled by the previous objective.
Place your func_button in front of this func_brush. Give it the invisible or nodraw texture. Doesn't matter, but you can see through the invisible texture in the hammer editor.
Very important is placement and size of your trigger_multiple. This will determine how small or large the area is that the player can move in while "hacking" the objective. Also, it MUST be almost touching the button (or even totally encompassing it), so that the player is already inside this field when they press the button. Also, be sure to select the option for this entity to "start disabled" in the object properties box.
It doesn't matter where you put the logic_timer in-world. It will not be visible, but I recommend placing it RIGHT NEXT to the system it uses for easy access. This entity must also start disabled. Also, be sure to set the amount of time it must be active before it fires its outputs, via the "class info" tab.
Now I'm going to list the outputs you're going to want to add to each entity:
--func_button outputs:
onpressed; button; lock
onpressed; trigger; enable—trigger_multiple outputs:
onStartTouchAll; timer; enable
onStartTouchAll; hint; disable
onStartTouchAll; hacking; display
onEndTouchAll; trigger; disable
onEndTouchAll; button; unlock
onEndTouchAll; timer; disable
onEndTouchAll; hint; enable
onEndTouchAll; failed; display—logic_timer
ontimer; button; kill
ontimer; trigger; kill
ontimer; timer; kill
ontimer; successful; display
And voila! What this does is: when you press the button, the hint will disappear, the trigger area enables, and the timer begins. But if you leave that area before the timer finishes, then the system completely resets and the player must start over by pressing the button again.
And add another output here that will enable your next objective if necessary. In once instance of this objective type that I've used personally, I used two "hold and hack" systems which each add one numerical value to a "math_counter" entity (its max value set to "2"). Then the math_counter entity's outputs unlock the following objective. This way each hold and hack system only functions as one half of the objective, and it doesn't matter what order they are completed in
Randomly spawning items
You will need:
a. func_button (name: button)
b. prop_dynamic (name: keys)
c. logic_case (name: case)
d. info_beacon (you CAN use a func_brush w/redbox texture, but its more complicated) (name: hint)
e. point_teleport (one for each possible item spawn point) (name: T1, T2, T3, etc.) Set the "entity to teleport" in the "class info" tab to "keys" for each of these.
Place your keys somewhere within the map that is unreachable by players. Wrap the button brush around them, shaped to roughly the same size, but keep it rectangular/square in shape. Place the logic_case entity with these two entities just for easy access. Place the info beacon on top of the button (if you want people to find this item VERY easily). Place each of your teleport entities in the map where you would like the item to possibly spawn. Let's say your previous objective, a hold and hack, is what enables this objective. That logic timer would need an output which reads:
ontimer; case; pickrandom
Now fill out the outputs accordingly:
--logic_case:
onCase01; T1; teleport
onCase02; T2; teleport
onCase03; T3; teleport
etc.
--func_button:
onpressed; button; kill
onpressed; nextobjectivebutton; unlock
onpressed; nextobjectivehint; enable
onpressed; keys; kill
onpressed; hint; TurnOff
And that's really all there is to it. The prop_dynamic, the beacons/hints, and the teleports are all passive elements to these objectives. They are controlled by the aggressive entity's: func_button and logic_case. They function in response to an output, and don't need to fire any direct outputs, unless of course you want them to for your own purposes. Again, these are the nuts and bolts of basic objective system elements, and with creativity and thought can be integrated into more dynamic and complicated systems.
Pushable Objectives
You will need:
a. func_tracktrain (name: train)
b. path_track (at least two, but you can use as many as you want) (name: P1, P2, P3...)
c. trigger_multiple (name: trigger)
d. info_beacon (name: hint)
This is a deceivingly simple type of objective. This basic system is used for any objective where you push an object to another location (not a physics prop, but one that clearly moves along a predetermined path).
In a nutshell, you wrap the trigger multiple around the object you want players to "push". When they enter it, the object moves, when they exit it, the object stops. When the object reaches the end of its path, the objective is complete.
You can make your object to be pushed a brush based object of your design, OR it can be a dynamic prop that is parented to an invisible func_tracktrain entity. For simplicity sake, lets say we're using a simple brush based box. you'll want to set the first path_track entity where you would like the box to start. Place the box anywhere in editor, it will automatically spawn on top of the first path_track in-game. You just need to tell the box which one to start at in the "class info" tab of the object properties screen. You can quickly set up a whole system of path_tracks after creating just one. Hold SHIFT, click and drag the path_track entity to where you would like the second path track. It will automatically create a new one, while adding the "next stop target" info to the first and also naming the new one. Now lets say you've only got three path_tracks in this system. Fill out the objectives thusly:
--trigger_multiple:
onstarttouchall; train; startforward
onendtouchall; train; stop
onstarttouchall; hint; disable
onendtouchall; hint; enable—final path_track:
onpass; trigger; disable
onpass; hint; kill
onpass; nextobjectivebutton; unlock/enable
Simple as that. Whew.... It's been a long road, I know, but we're almost finished. I recommend you set the path_tracks AND the tracktrain to have a "fixed orientation" so that it doesn't rotate. Rotation is a tricky animal that I have not yet mastered. If you can, you probably don't need my tutorials.
Multiple actions for a single objective
I briefly covered this in the "hack and hold" section. The only entity I guarantee you'll need for this type of objective is a "math_counter" entity. You can set a maximum value of any integer for this entity. Then tell any number of other entities to fire an output at this counter that looks like this:
onpressed; counter; add; parameter override: 1
For example, lets say you have one of every type of objective I've just listed, and they are all available to the player to complete in any order they see fit. Your math counter's max value will be set to 4.
Your door button (and your random object button) will fire an output:
onpressed; counter; add; paramter override: 1
The timer in your hack n'hold will read:
ontimer; counter; add; parameter override: 1
And the final path_track in your pushable will read:
onpass; counter; add; parameter override: 1
And lastly, your math_counter will fire an output similar to:
on hit max; nextobjectivebutton; unlock
Advanced Hold n' Hack w/timer
This functions much like the regular hold n' hack, however it does not completely reset when the player leaves the trigger area. It will retain the progress that the player has made, and restart from where it left off when the player can get back into the trigger area. Please look at the downloadable map pack to see how this is done. This also includes a timer, the scripting for which was originally created (and made public for use) by official zps level designer EArkham. Thanks!