Overview
This guide is gonna teach you everything Zombie Panic! Source related when it comes to creating maps. This guide is aimed for ZPS 3.0 and above, you shouldn’t use this guide for the “legacy” 2.4 versions and below./! THIS IS A WORK IN PROGRESS GUIDE /!
Introduction
Welcome to this guide that is gonna teach you everything Zombie Panic! Source related when it comes to map creation.
This guide will not teach you how to create maps from scratch, in other words, we assume you already know how to use Valve Hammer Editor 4 and you can make the basic stuff like geometry, compiling and that kind of thing. If it’s not the case, we recommend you to read or watch a tutorial about Hammer for another Source engine game like Half-Life 2: Deathmatch, Counter-Strike: Source…
Name of the map
In order to ensure that your map work properly, you will have to respect the following standards:
- Your map name must start by the proper game mode prefix, see the table below.
- Your map name must not be the same as a map that already exists.
- Your map name must be in lowercase characters, uppercase characters will likely cause problems on non-Windows operating systems where case sensitivity matters (like Linux).
- Your map name must not have special characters such as “&, $, !” because they will cause issues, however, it’s fine to use “-“, “_” and “.”.
- Your map name must only contains ASCII characters, avoid UTF-8 characters like accents (“é”, “è”, “ô”…) and non-Latin alphabets (cyrillic, kanji…)
- Your map name must not be too long, less than 32 characters is the prefered limit.
The name of your map must start by the game mode prefix you wish to map for:
The lobby (or “ready room”)
Biotec’s lobby (or “ready room”) in Hammer
The lobby also known as “ready room” is the area where the players choose their team before the round begins. This room must NOT interfere with the rest of the map (aka playable part) otherwise there will be serious issues.
That room must contain the following entities:
- x24 “info_player_commons” point entities which are the lobby spawn points.
- x1 “trigger_joinhumanteam” brush entity to join the Survivors team.
- x1 “trigger_joinzombieteam” brush entity to join the Zombies team.
- x1 “trigger_joinspectatorteam” brush entity to join the Spectators team when the round has started.
- There are some textures with their names starting by “zp_join” that allow you to indicate where the Survivors/Zombies/Spectators triggers are, use them. You can also make your own if you desire.
- You can also use a blue light for Survivors, a red one for Zombies and a white one for Spectators.
- Have the lobby match the theme of your map: it would be weird to have a spaceship as lobby but the map is about skyscraper on Earth.
- Players should not have to guess which way they have to go to pickup their team, don’t turn the lobby into a maze or puzzle!
- Be creative! Use blood/ragdolls to represent the Zombies, an escape vehicle for Survivors in objective maps. There are many ways to create an interesting lobby for your map!
[Angelscript] Using scripts in your map
Angelscript is a built-in alternative to SourceMod since ZPS 3.0 which is used by servers operators for their servers but by level designers for maps as well.
Biotec’s helipad lights, it’s objectives management and it’s objectives are all managed by Angelscript. Same goes for Zombie spawns and the nuclear nuke’s logic from Shreddingfield.
Angelscript isn’t a mandatory requirement, you have the freedom of not using it, mix it with Source I/O (input/output) system or going full Angelscript. The choice is entirely up to you.
Back in the early days of ZPS v3.0 development, we (the development team) had issues when we were updating Shreddingfield, one of ZPS’s big objective maps. Biotec suffered from the same problem late in v3.0 development as well.
The reason is because the maps were exceeding the “entdata” limit even if the entity limit hasn’t been reached, here’s an explanation from Valve Developer Community if you don’t know about this stuff already:
Originally posted by Valve Developer Community:For reasons of memory allocation, there is a limit to the number of entities Source can manage at once.
The combined size (in bytes) of a map’s entity data should also be considered, even if the number of entities is within safe limits. Large amounts of entdata can take a noticeably long time to transmit from server to client, and may lead to crashes
We had multiple choices:
- Sacrifice map’s quality by removing a lot of entities non-related to gameplay (props, decals, overlays…)
- Delegate the complex I/O entities setups/logics inside the game’s code.
- Add a scripting engine and delegate the complex I/O entities setups/logics to it.
First option has been tested and we were not happy with the results.
Second option would have required the level designers to ask a programmer to add/update/remove the code for his/her needs. Community couldn’t benefit from that “privilege” as well and programmers aren’t robots available 24h/24 7d/7.
Third option is the same as the second one but without the need for a programmer and the community can benefit from it as well!
Angelscript allowed us to save that precious “entdata” to prevent the crashes from happening. For some maps, we managed to save a lot of that is now used to add more props (decorative/physics), lights, decals and such.
Don’t worry if you are not a programmer/script kiddy, the level designers in our team picked up Angelscript fast just by looking at programmers samples and some help from them.
Speaking of help, remember that the Steam forums and our Discord server in the event you seek assistance. You could also help other fellows level designers, teamwork is OP!
In order to use an Angelscript script in your map, you need to tell the game to load a main script, this can be done by going in the “maps” folder and creating a file with the same name as your map with the “.cfg” extension. The file can be opened in a text editor (like Notepad or Notepad++) and contain the following:
Of course, replace “zpo_my_map” by the name of your map. Now you will need to create the script itself, go in the “data/scripts/maps” folder, create a new file with the same name you used in the “map_script” field and make sure it has the “.as” file extension.
Once everything is done, the game will load your Angelscript main script, all you have to do is writing the script itself, you may consult the [link] and/or other maps scripts for reference.
The process is simplier: every map has a main Angelscript script that the game tries to load by detecting if there is a “<mapname>.as” file located in the “data/scripts/maps” folder.
Since the API changed between v3.0.x and v3.1, you will need to use the v3.1 API[betaapi.zombiepanicsource.com] instead of the one above.
Here are a few things you need to be aware when using Angelscript:
- The Angelscript scripting engine will log in the console and log files whenever it tries to load/compile a script file.
- It will also tell you as an information if the script compilation is successful, it will however NOT tell you that the script works and do the job as you expect (it doesn’t replace testing your map/scripts ^^)
- It will also tell you when and why the compilation fails as a warning, it will also tell you which line/column that is impacted by the error.
- Sometimes, the answer to fix a compilation error is inside the error’s description itself.
- You can not save changes to scripts if the Angelscript scripting engine uses them, there is a locking mechanism for safety reasons. You need to restart ZPS or change to another map to save your changes.
- Comment your code if needed, indent it so that it’s readable, use explicit variables names, create functions/procedures if you need to the same thing multiple times instead of copying/pasting.
- Don’t forget that you can include other script files. Some maps have a script dedicated to objectives, another one for effects and all of them are included in the main script.
[Angelscript] Calling procedures from the map
There is one point entity that allow you to communicate from the map to Angelscript: “logic_script“. This is heavily used in official objective maps to call procedures like “OnObjectiveCompleted” to mark an objective as completed and move on to the next one.
You only need a single copy in your map, just give it a name. Then whenever you need to call a procedure, you just need to fire the “FireScript” output where the value is “void ProcedureName(ParametersIfNeeded)“. Don’t forget to program the procedure if not done already.
Have the server print “Hello World from Angelscript!” to everyone in the chat.
Angelscript:
Hammer setup:
OnSomething -> FireScript (value = “void SayHelloWorld()”)
Have the server print the result an addition to everyone in the chat.
Angelscript:
Hammer setup:
OnSomething -> FireScript (value = “void SayAdditionResult(2, 3)”)
[Angelscript] Custom badges rewards (ZPS v3.1 and higher)
You can add custom badges rewards for your map for the After Action Report panel at the end of the round.
Here’s how the DJ badge for Clubzombo is done: whenever the match begin, the DJ badge with it’s identifier (ID), name, description (optional) and path to it’s icon (relative to the “materials” folder without the extension). This is done by overriding the “OnMatchBegin()” procedure and calling the “CreateCustomBadge” procedure from the “BadgeManager” class like this:
The “#ZP_ClubZombo_Badge_DJ” is a localized string, meaning that it will be translated to the player’s language.
In the map, there is a “func_button” called “sndb” that toggles the club’s music through Source I/O. In the script, that button is registered for “use” events by using the “RegisterUse” function in the “Entities” namespace, this is done whenever the match begins as well. So far the code would look like this:
Since we registered an “use” event, we need to override the “OnEntityUsed” procedure, it takes two parameters: the player as an instance of the “CZP_Player” class and the used entity as an instance of the “CBaseEntity” class, the code would look like this:
Now we need to detect that the used entity is the “sndb” button. First we need to query the entity’s name, this is done using the “GetEntityName()” function on our “pEntity” instance. To test if the latter is equals to “sndb“, we can use the “StrEql” function from the “Utils” class. Combining the previous code and these explanations would give this:
The only thing left to do is to reward the player that used our button. The “IncreaseBadgeState” function from the “BadgeManager” class handles that, you need to pass 2 parameters: the player and the ID of the badge (“DJ” in our case). Here’s the result:
Now you might think: “phew, done” but it’s not over yet. What happens if the same player use the button again? What happens if another player use it? For the first question, it’s gonna increment the badge state again. For the second question, that means that many players can get the badge at the same time.
Since we want the badge to be earned by a single player per match, some extra work is needed. One way would be to lock the button after it has been used once either through Source I/O or Angelscript.
Another way would be to have in the script a global boolean variable that defines if the badge can be earned or not. False would mean no, true would mean yes. That variable would be true whenever the match begins then test if the variable is true at the same time we’re testing it’s name equality and switch the variable to false at the same time we increase the badge state. If you are doing this, the final script would look like this:
Player spawn points
ZPS has several features when it comes to controling the spawn points.
When testing your map, keep an eye out on the console whenever you respawn. If you get a warning involving a spawn entity/spawning location, then something is wrong with your spawn points and/or setup and you should fix the problem.
All maps requires one “info_player_start” as “fallback” solution, this is mandatory in all Source engine games/mods.
All spawn entities are point entities.
For ZPS, you will need one “info_player_observer” which is the spawn point for Spectators but also when players read the MOTD (Message Of The Day) after you connect to the server.
You will need 24 enabled “info_player_human” for the Survivors team. 24 being the max. players count on ZPS. More on the meaning of “enabled” below:
Every spawn point can be enabled/disabled through Source’s I/O system with the “EnableSpawn“, “DisableSpawn” and “ToggleSpawn” outputs. This is also do-able through Angelscript using the “Ent_Fire” function from the “Engine” class.
You will also need multiple enabled “info_player_zombie” depending on your map’s layout and depending if you want static/dynamic Zombie spawn points. Notice that you can set if a Zombie spawn is reserved for the Carrier(s) or for everyone (or you can use “info_player_carrier” as well).
If you enable the PVS mode on a spawn, it will automatically be disabled when at least one player from the opposite team look at it. It will be re-enabled when the previous condition isn’t met.
Player management and counting
All entities mentioned in this section are point entities.
The “logic_player_manager” entity allow you to change (or even remove) the starting gear of Survivors as well as changing the respawn timer for the Zombies. The settings, spawnflags as well as outputs should be explicit.
WARNING: You can NOT strip/remove the arms from Carriers and Zombies! The same applies to the Survivors hands for punching Zombies and pushing props and their phone for the objectives list/I.E.D.! Even if you do try (by map or script), the game will give them back!
Sometimes, you might want to do certain stuff when there is a certain amount of players. The “logic_playercounter” has been created for that purpose. The settings and the outputs are explicit so there is no need to explain what they do.
You can also handle this through Angelscript without using any entity (good for saving some precious “entdata”). Here’s an example: let’s suppose that you have 3 “light” entities that are turned off by default named “light_blue”, “light_green” and “light_red” where the colors are blue, green and red respectively. Imagine that you want to turn on the blue light if you have less than 8 starting Survivors, the green one if you have less than 12 starting Survivors and the red one if you have 12 starting Survivors or more. The Angelscript code would look similar to this one:
Blocking players depending on their team
All entities mentioned in this section are brush entities.
This is highly used to block Survivors from going inside the Zombies spawns and spawnkill them. There are other scenarios where these blockers are used.
The entities are “func_humanclip” to block the Survivors and “func_zombieclip” to block the Zombies. They are textured with the clip/player clip/trigger texture depending on your preference.
You can give them a name if you wish to disable/enable dynamically the blockers by firing the appropriate input through Angelscript or Source I/O.
Trigger entities on round start/end
The “logic_rounds” point entity has been designed for that purpose with the “OnRoundStart” and “OnRoundEnd” outputs.
“OnRoundStart” is called whenever a new round is starting which is when the first Zombie(s) has (have) been selected and Survivor(s) got his (their) starting gear.
“OnRoundEnd” is called when the round is ending, at this moment, the “Survivors Win”/”Zombies Win”/”Stalemate” jingle is playing and the message is shown. On ZPS v3.1, the After Action Report is shown there as well.
There are 2 functions of the same names as the entity’s outputs to achieve the same thing, here’s a code snippet:
In test mode, the round start/end outputs/Angelscript hooks will never be triggered. If you want to test them, there are multiple ways:
- Turn off test mode, enable bots and spawn a Survivor/Zombie bot.
- Make a temporary trigger that “mimic” the output/hook.
Force the round to end (Survivors/Zombies win)
There are multiple reasons you need to force the round in progress to end with either a Survivors or Zombies win.
Objective maps for example need to force a Survivors win when they completed all objectives or they could force a Zombies win if the last objective is something like “escape” and they didn’t reach the escape zone in time (also used to prevent players from delaying the round by standing near and killing Zombies for frags).
If you want to trigger the Survivors win (required for objective maps), you need to place a “game_win_human” point entity and fire it’s “EndGame” output.
If you need to force a Zombies win, you would use a “game_win_zombie” instead.
The “RoundManager” class has a “SetWinState” procedure that has only one parameter: one of the values of the “RoundWinState” enumeration: “ws_Stalemate“, “ws_ZombieWin” or “ws_HumanWin“.
In test mode, you won’t be able to end the round so the mentioned entities and Angelscript functions above will not work. Do not panic tho! They will work in normal gameplay.
MP3 system
The “logic_music” point entity allow you to manipulate the MP3 system. Frozenheart uses that entity to play the “Time Coming to a Crawl” music when survivors defend the restaurant while waiting for the military chopper to come.
FMOD Studio Low Level API (v3.0.x)/FMOD Studio Core API (v3.1 and higher) is the middleware we use to implement the MP3 system, it supports the following formats: WAV, MP3, OGG, WMA.
Even if FMOD allow you to use WMA, it’s highly discouraged to use it. WMA is a format by Microsoft and specific to Windows, this will give trouble for any other operating system such as Linux to read it.
The development team uses MP3 and highly recommend you do the same.
The MP3 system should NOT be used to play sounds and musics with a very small duration! The musics are opened as “streams” instead of “sounds” in FMOD. Their documentation states that “streams” are best for musics and not looping sounds whereas it’s the contrary for “sounds”.
If you intend to play a sound or a very short music (just a few seconds), consider using an “ambient_generic” instead.
The entity’s settings are obvious and don’t need explanation, you just need to fire the “ForceTrack” output.
Keep in mind that players won’t hear musics if their music volume is set to 0!
You can use the “PlayMusic” function from the “Globals” class, see the API[betaapi.zombiepanicsource.com] for details.
Ammo, items, weapons and balancing
All the entities mentioned in this section are point entities.
Like most of all Source engine games/mods, ZPS follow the “standard” entity naming convention, all ammo entities starts by “ammo_“, all weapons entities starts by “weapon_“, etc…
ZPS also has some “random_” entities that allow you to randomly spawn any kind of supply or a supply from a specific category (assault rifle, pistol…) Using the entity’s settings, you can control what can randomly spawn or not.
ZPS 3.0 and higher has a built-in balancing system which will automatically limit the number of ammo/items and weapons depending on the amount of players on the server. If you want to force an ammo box, an item or a weapon to always spawn no matter what, just set the “Ignore GameMode Rules” property to “Yes“.
Likewise, you can see that you can set the minimum amount of Survivors required for that ammo, item or weapon to spawn.
However the default values might not work for your map. Some maps would require even more supplies at high players count or less, same goes for medium and low players count. To change that, you will have to do this through Angelscript.
The following paragraphs assume that you have already setup your map to use Angelscript, see the concerned chapter above if this isn’t the case. A script sample is provided at the end of the chapter but do NOT skipping reading the next paragraphs as some actions from you will be required.
In your main script, it is highly recommended that you include the base scripts that every official map use, those are “overridelimits/enable_override” and “overridelimits/override_randomdef“.
To save time and effort, make a new folder with the name of your map inside the “datascriptsmaps” folder then copy/paste an official map’s “random_def.as” script inside it.
Open that copy of “random_def.as” file, inside the function “CheckRandomDefLimits“, you will see that kind of code, the comments prefixed by two forward slashes (“//”) were added to explain what it does exactly:
You will spend most of the time on this script changing the values of the conditions (if/else if/else) and values of “iReturn” depending on how you want to balance your map. You can also more “else if” conditions if you want more control over the “iReturn” value.
When you have finished changing the values, you will need to go back to your main script and include your “map_name/random_as.def” (change “map_name” by the name of your map) so that the Angelscript scripting engine will take your hard work into consideration.
There is a final step to do in the main script: tell the game to use your values. You do that by creating a new procedure called “OverrideLimits()” and you simply call the “EnableOverride()” and “SetupRandomDef()” procedures inside it.
If you followed everything above correctly, your main script file would look similar to this snippet:
[Objective] Definition of “state”
An objective in ZPS has a “state” to notify players of it’s progression. Keep in mind that you can manipulate one objective at a time, in other words, if you have an objective about finding a gas can and a hose in order to progress further, you would merge these two objectives into one: “Find supplies” or “Find the gas can and the hose”.
Here’s a table that contains all the states with their description, their Angelscript enumeration value if you plan on using Angelscript as well as their output to trigger it if you prefer to use Source I/O system instead:
[Objective] Setting up using Angelscript and phone messages
The principle is somewhat identical to the “map entities” version except that you are doing it through Angelscript.
Each objective is an instance of the “CASObjective” class, to create that instance, you use the “Create” function from the “Objectives” namespace. It’s best to do that in the “OnMapInit” procedure which is called automatically by the game whenever your map is loaded. That function takes one mandatory parameter: the objective’s text (find the supplies, defend the truck…), there is an optional parameter which is the name of a procedure that Angelscript will automatically call when the objective is failed or completed. The following example is from Shreddingfield:
Since we added that optional parameter, we need to declare the appropriate procedures:
Now you need to add these objectives to the list, this is done using the “AddToObjectiveList” function from the “Objectives” namespace, 2 parameters are required: an index that represent the order starting from 0 not from 1 and the objective instance. All of that inside the “OnMapInit” procedure, the code would look like this now:
You just need to call the “SetState” function with the appropriate state (see the concerned chapter) as parameter on the concerned objective’s instance. Taking our Shreddingfield example from before, here’s how it would look like:
Simply use the “SendPhoneMessage” from the “Utils” class passing the message as parameter, here’s an example:
[Objective] Setting up using map entities
The list of objectives as well as their states are controlled by the “info_objective_list” point entity. This entity is also responsible for showing the list of objectives and their status when players press the “Show list of objectives” button.
In this entity’s properties, you fill the objectives texts that will appear on the HUD. When you want the state of an objective to change, you fire an output to your “info_objective_list” entity with the state you want (see the chapter about objectives and states if you missed it), you will also need to set the objective ID in the value field.
[Objective] Beacons
The “info_beacon” point entity is used to show beacons on the HUD, the entity settings as well as it’s inputs are self explanatory and Hammer should give you the details if you need further explanations.
The location of the “info_beacon” will determine where the beacon will be shown, if you want the beacon to move with an object, you will need to parent it to that object.
[Objective] Grab an item (or more) and use it in specific area(s)
An example of this kind of objective in Biotec : survivors have to take the keys, go to the basement and use them on the basement’s door.
The area that defines where the item is useable is a “trigger_useable” brush entity (textured with “toolstrigger”). You will need to set an item ID which represent which item(s) this trigger will accept (let’s say “keys” for example). You also might want to set a tutor message for players that play with the Game Instructor feature.
Important change since v3.0.9a: the “trigger_useable” brush must NOT collide with any object and/or the world. If that happens, the line of sight check will always fail and you won’t be able to use the item!
This entity has 2 outputs, “OnUsed” will be triggered when the player uses the right item and “OnUsedFailed” will be triggered when the player uses the wrong item (thanks Captain Obvious).
The keys themselves is an “item_deliver” point entity. You will need to set the same item ID you defined previously (in our case: “keys”). Since those keys will be used, you will need to set the item state to “Useable”. The rest of settings are self explanatory.
Notice you can also use the “OnItemTaken”, “OnItemDropped”, “OnItemCaptured1” and “OnItemLost1” outputs to trigger various stuff when the item is being manipulated. You can also change the item’s glows, visibility and constraint with the “SetGlow”, “OverrideVisibility” and “ToggleConstraint” inputs which all of them required a boolean value (0 = false, 1 = true).
[Objective] Hold an area for a fixed amount of time
Exemple: Hacking the computer in Zomboeing’s tower.
- 1x “func_button” which will start the capture, set the filter to the designated team and a delay of reset of one second. For this guide, it’s gonna be called “capture_button“.
- 1x “game_timer” which is the timer itself, set the amount of time to capture and tick the “Ignore Team filter” spawn flag. I’m gonna name it “capture_timer“.
- 1x “logic_branch” which will disable/enable the capture’s progress. I’m gonna call it “capture_watcher“.
- 1x “logic_timer” which is disabled at start and refire every 2 seconds which will toggle the timer. I’m gonna name it “capture_timer_toggle“.
- 1x “trigger_multiple” which is the “capture area” with the filter set to the designated team and a reset delay of 0 seconds. Assuming it’s called “capture_area“.
For the “func_button” entity (“capture_button“), fire the “Test” input on the “logic_branch” (aka “capture_watcher“) to start the capture process.
For the “logic_branch” entity, use the “OnFalse” in order to make the capture resumable. In our scenario, this means you need to re-enable the “func_button” (aka “capture_button“), disable the “trigger_multiple” (aka “capture_area“), disable the “logic_timer” (aka “capture_timer_toggle“) and pause the “game_timer” (aka “capture_timer“). If you haven’t guessed it already, you will need to do the opposite as well to mark the capture as “in progress”. In other words, use the “OnTrue” output to disable the “func_button” (aka “capture_button“), enable the “trigger_multiple” (aka “capture_area“), enable the “logic_timer” (aka “capture_timer_toggle“) and resume the “game_timer” (aka “capture_timer“).
The “logic_timer” (“capture_timer_toggle“) will need to query the touch status of the “trigger_multiple” (“capture_area“) to see if we are still capturing or we stopped. This is a simple “OnTimer” output to the “trigger_multiple” (“capture_area“) with the “TestTouch” input.
The “trigger_multiple” (“capture_area“) will have to signal if there is still players, you will need to add an “OnStartTouchAll” output to the “logic_branch” (“capture_watcher“) with the “SetValue” input with a value of “1“. You will need another output on the same entity but with “OnEndTouchAll” and with the input “SetValueTest” where the value is “0“.
For the “game_timer” entity, use the “OnTimerEnd” output to trigger entities when the capture has been completed (marking the objective as done, move to the next one or end the game…)
[Objective] Hold an area (capture point version)
Exemple: sealing Zomboeing’s doors in the plane.
The concerned entity is a “trigger_zp_capturepoint” (brush entity). The settings and outputs should be explicit so there is no need to detail those.
If you want to have “scaleable” capture points (aka capture faster when there is more players), you will need to use multiple “logic_player_counter” (make sure to count dead players as well) and use the “SetCaptureSpeed” output on the capture point.
Testing your map
So you compiled your map and did everything ZPS related, now is the time to test!
There are multiple ways to test your map, one way is through “test mode” which you will spend a lot of time in. Test mode will disable the notion of “rounds” and disable the “awaiting for players” constraint so you can navigate freely inside the map. You will also have the liberty of choosing between the Survivor/Zombie/Spectator team, switch in-between them and respawn in whatever team you desire.
A reminder that “test mode” is designed to test maps. It is NOT designed for actual/real gameplay!
To use “test mode”, just start the game and create a local server, in the server’s properties, make sure to tick “Test mode” (or set the console CVAR “sv_testmode” to “1” for true) and start your local server.
Another reminder that round start/end hooks and game end hooks from map entities and Angelscript will not be triggered in “test mode”. If you are creating an objective map, and the “game win” triggers don’t work, do not panic! They will work as intended in normal gameplay.
A second way to test your map in “normal” circumstances is to not enable “test mode” but to “enable bots” instead: start a server with the max. players limit being 2 or more and then spawn at least one bot on a team using the console commands: “bot_zps_add_human” and “bot_zps_add_zombie“. You can kick the bot with “bot_zps_kick” or “bot_zps_kick_all“. It is worth mentioning that this way is also good to test stuff like balancing at different players count.
You can also ask some community members to test your map for feedback, some community servers operators might also offer you a way to test your map on their server(s)!
Custom assets packaging inside the BSP
The Source engine allow you through community made external programs to package custom assets (materials, models, sounds…) inside the final BSP file itself. Some level designers will tell you it’s a good practise to do so and some others think the opposite.
We recommend you to NOT package custom assets inside the BSP.
Sure, it will make your publishing task easier but every time you need to update your map, you will need to repeat the packaging process and ship again the assets even if you didn’t modified them. Clients will have to redownload them all again.
This will also increase the size of the BSP file itself. Some maps used to have a file size over 100 Mb due to custom assets inside the BSP and since not everyone has the privilege of having fast and stable Internet connection, they will hit the disconnect button fairly fast and this can be a motive for servers operators to throw your map away from their servers.
You are wrong, there are ways to grab the assets even if it’s packaged within the BSP.
No matter how hard you try, a Source engine BSP will always be decompiled and/or reversed engineered. That is something that no one can stop, not even the developers, not even Valve. If you are so afraid of this, you shouldn’t be publishing the map at all.
Just don’t package your custom assets inside the BSP file. You can provide a RES file for servers that still rely on the “legacy” FastDL system rather than the Steam Workshop.
This is because Angelscript works best with the standard C++ library rather than Valve’s filesystem library. In other words, Angelscript don’t know how to peek inside BSPs and VPKs as well.
The reason is similar to Angelscript: FMOD works with it’s own filesystem and we had to manually teach him how to peek inside our content, custom content and downloaded content. Teaching the implementation to read inside the BSPs and VPKs is gonna unneeded complexity to the system.
Packaging in the VPK
VPK packaging is entirely optional, you are free to do it if you desire and if you can but it is not a required step.
Angelscript scripts and musics played by FMOD should not be packaged inside the VPKs, see the related section in “Custom assets packaging in the BSP” as to why.
Publishing/Updating on the Steam Workshop
So you’ve finished your map and you are ready to publish it on the Workshop? Now it’s time to publish!
Most players won’t subscribe to workshop maps if they can’t see what it looks like before they push the “Subscribe” button. This is why you need to make multiple screenshots of your map. You will also need a “presentation image” that can be a modified screenshot and/or an image made from scratch by yourself.
If you can and if it’s not already done, set ZPS’s graphics settings to the highest possible. You can revert back to your previous once the “screenshoting” is done.
Start a local server with test mode and cheats on your map and join the survivors. Drop your weapons and hide everything HUD related by typing “cl_drawhud 0” in the console. Use the “noclip” cheat to navigate freely in your map.
All you have to do, is to choose the appropriate positions and angles of yourself then press the Steam’s “Take screenshot” (F12 by default) to take a screenshot. Remember that you need multiple screenshots, too many is likely spoil your map, too few is likely not enough.
Remember that if you want to use a screenshot as “presentation image” to make the screenshot itself.
Once you have done your “screenshoting” session, you can turn the HUD back on using “cl_drawhud 1” in the console.
When you will close ZPS, Steam will open the screenshots window, you can press the “Show on disk” button to order Steam to open your Windows Explorer in the folder where the screenshots are.
If your “presentation image” is a screenshot, you can open it with your favorite image editor (Paint, Photofiltre, Photoshop, GIMP, Paint .NET…) and add the map’s name in the center. If you wish to use the “Requiem” font which is used for the “Zombie Panic! Source” title, you can install it on your system. The font file is located in “<ZPS install dir>zpsresourcefontsRequiem.ttf”.
Videos aren’t mandatory, but if you want to make one, go ahead! But you will still need that “presentation image” explained earlier.
At any location you want, create a new folder. In that folder, create another folder called “maps” (case sensitivity matters). In that folder, place the final BSP file of your map.
If your map depends on custom assets (Angelscript plugin(s), materials, models…), you may add them in the folder, just make sure to respect the folders structures (materials goes in a “materials” folder, sounds goes in the “sound” folder…). Again, case-sensitivity matters.
Also make sure that there are NO “junk”, “personal” file(s) that isn’t related to the map because the Workshopper will publish EVERYTHING in that folder.
People who are familiar with Left 4 Dead 1/2 modding will get familiar with this already. The addon information is an in-game presentation of your addon to the players. At the root folder of your map which is the first folder you created in the “Preparations” section, you need to create a text file called “addoninfo.txt” which contains the following:
You will obviously need to change the title, description, author and Steam ID 64, you can retrieve your Steam ID 64 on this website[steamid.io].
To change the icon of your Workshop addon in the addons browser in-game. You need to add the following line in your “addoninfo.txt” file:
The value of this key is the relative path to the icon, empty means the same folder as “addoninfo.txt”.
The icon itself has to be in Targa (TGA) format, it’s dimensions must be 512×256 and it’s name must be “addonimage.tga”.
Place the TGA file in folder you specified in the “addoninfo.txt”. The game will handle the TGA to Source engine conversion process automatically.
Remember to increase the version value every time you update!.
Start the ZPS Mod Tools and double click on the Workshopper, when you will launch the Workshopper for the first time, you MUST read and ACCEPT the terms of the Workshopper and ZPS Workshop. If you don’t accept them, you may not publish anything on the Workshop. If those terms aren’t respected, we (the developers) will take the liberty of removing your workshop submissions (with a warning).
You may now click the “Create” button to create a new workshop item. Click on “Select Image” to select your “presentation image” which will act as the thumbnail as well as first screenshot of your map.
Click on “Select File Dir” and select the first folder that you created in the “Preparations” section, it’s very important that this folder contains the “maps” folder (and others folders if your map uses custom assets).
Click on “Select Workshop type” and choose the “Game Mode” category. Tick the game mode that matches your map (if your map is objective then you will tick “Objective”).
The “Title” field is the name of your map, players will see this first when they are browsing the workshop.
The “Description” field should contain extra information about your map. This is the perfect place to write some kind of story/lore as well as the map’s credits. You can change the description later in the Steam workshop if you prefer.
The 3 radio buttons defines the visibility of your item. “Public” means that your map will be available to everyone (aka released), “Private” means that only you and ZPS developers can access the map. “Hidden” means that only people who has the link and ZPS developers can access the map.
If the ZPS Development Team is running a map contest, you can define if your map is part of that contest or not.
Once everything is done, you can press the “Upload” button. If everything goes fine, your map should be available on the Steam Workshop. You may close the Workshopper and the Mod Tools once the job is done.
Updating the map is very simple, you just update the files in the folder used in the “Preparations” section, don’t forget that since ZPS 3.0.7 you will also need to update the “version” value in the addon information file as well. You start the Mod Tools and the Workshopper but you don’t click on the “Create” button, you click on the “Update” button of your map instead. You will have to fill the “Changelog” field (aka what changed between the previous and new versions) and press the “Upload” button to finally trigger the update.
[Upgrade] From ZPS v3.0.X to v3.1
This chapter contains all the changes between ZPS v3.0.X and ZPS v3.1 that you need to take into account if you are upgrading an existing map:
ZPS v3.0.x game movement code was based from Half-Life 2: Deathmatch which is based from Half-Life 2, the latter received “special treatment” when it comes to navigation with ladders. However, starting from ZPS v3.1, the game movement code is based directly from the base Source SDK 2013 multiplayer code meaning that “special treatment” is gone.
In HL2(:DM), you make ladders by using the the “func_useableladder“, “func_ladderendpoint” and “info_ladder_dismount” point entities. You will need to delete those and replace them by a brush where the face towards the player is textured with the “tools/toolsinvisibleladder” texture. The rest of the faces can be textured with the “tools/toolsnodraw” texture. If you are familiar with Counter-Strike: Source, Day of Defeat: Source and/or Garry’s Mod mapping, this is the same way.
Decals in the past could be named for various reasons, we removed that ability to save “entdata” and “edicts” when we were overhauling official maps. We had no use for them anyways.
The way of loading the main script changed, it no longer needs a config. file, the main script just need to be named as the map’s name.
The API changed between v3.0.X and v3.1 and the script might no longer work, consult the v3.1 API[betaapi.zombiepanicsource.com] there.
The balancing system received some changes between ZPS v3.0.X and ZPS v3.1, you might want to check if the balancing still works as intended or if you need to perform changes.
Some gameplay related entities changed class names and therefore require an update:
- Pills used to be “item_healthvial“, they are now “item_pills“.
- Barricades and hammers are now split into 2: “item_ammo_barricade” are the planks and “weapon_barricade” is the hammer itself, you should reduce the amount of those.
- You should place “random_smg” entities to have Glock 18c and MP5 spawning. The Glock 18c is no longer part of the “pistol” roster, same goes for the MP5 that is no longer on the “rifle” roster.
- Double check the “weapons_limit” and “random_limit” entities if you plan on keeping that balancing system.
- Inoculators can spawn at “random_misc” entities, there are 3 class names: “weapon_inoculator“, “weapon_inoculator_delay” and “weapon_inoculator_full” for white, green and red inoculators respectively.
TODO
As mentioned in the summary, this guide is being written and updated, here’s a summary of what’s left to be done to be considered “complete”:
- Objective – Push the cart (à la Corpsington’s generator).
- Objective – Escape.
- Angelscript – Code snippets.
- Zombies ladder.
- Zombie Vision highlighting.
- Doors as props.
- Hints & tips.
- Final words.
- Check if everything is “ZPS v3.1 ready”.
- Update if needed.
- Tutorial/sample map.
- More screenshots.