Call of Duty 5: Zombie Map Tutorial: Verruckt
Overview
If you have not gotten Mod Tools 1.2 patch yet, be sure to make backups if you've modified zombie mode specific files for your mods so you don't lose your work as there are updates to scripts, zone source, sounds, etc... Once you've downloaded the 1.2 update and installed it, you'll be ready to make a Nazi Zombie Verrückt-style map.
A lot of the script examples below are taken from the Verrückt nazi_zombie_asylum.GSC that can be found in raw\maps. It is the best source to look at if you have questions regarding implementation of any of the script examples below as they are all taken from Verrückt.
Implementation
Map Name
Custom zombie maps will need to have a prefix of nazi_zombie_ in order to work correctly with the game. Your scripts/ zone source/ soundalias/ etc should follow the same naming convention too.
Introscreen
An introscreen isn't required in terms of displaying a title, time, and date, but there are important zombie function calls needed at the beginning of the map that are called during the introscreen for Verruckt.
Strings
In \raw\english\localizedstrings you'll want to make a new string file for your map if you haven't done so already. Inside add a title, date, and place string. Save the file as Mapname.STR (changing Mapname to your level's name):
VERSION "1" CONFIG "G:\CoD5\cod\cod5\bin\StringEd.cfg" FILENOTES "" REFERENCE TITLE LANG_ENGLISH "'My Zombie Map'" REFERENCE DATE LANG_ENGLISH "April 10, 1945" REFERENCE PLACE LANG_ENGLISH "The Reichstag, Berlin" ENDMARKER
Precache
In your map script file, before you call maps/_zombiemode::main();, you'll want to precache the strings by adding (changing Mapname to your level's name):
#include common_scripts\utility; #include maps\_utility; #include maps\_zombiemode_utility; #using_animtree("generic_human"); main() { // Snippet precachestring(&"MAPNAME_TITLE"); precachestring(&"MAPNAME_DATE"); precachestring(&"MAPNAME_PLACE"); // Snippet maps\_zombiemode::main(); }
The notation is actually &"STRINGFILENAME_REFERENCE", so if your strings aren't in Mapname.STR be sure to make appropriate changes to the script.
Script
In your level script, you'll want to create a function for your introscreen and call it after maps/_zombiemode::main();. An example function take from Verruckt is (changing Mapname to your level's name):
intro_screen() { flag_wait( "all_players_connected" ); wait(2); level.intro_hud = []; for(i = 0; i < 3; i++) { level.intro_hud[i] = newHudElem(); level.intro_hud[i].x = 0; level.intro_hud[i].y = 0; level.intro_hud[i].alignX = "left"; level.intro_hud[i].alignY = "bottom"; level.intro_hud[i].horzAlign = "left"; level.intro_hud[i].vertAlign = "bottom"; level.intro_hud[i].foreground = true; if ( level.splitscreen && !level.hidef ) { level.intro_hud[i].fontScale = 2.75; } else { level.intro_hud[i].fontScale = 1.75; } level.intro_hud[i].alpha = 0.0; level.intro_hud[i].color = (1, 1, 1); level.intro_hud[i].inuse = false; } level.intro_hud[0].y = -110; level.intro_hud[1].y = -90; level.intro_hud[2].y = -70; level.intro_hud[0] settext(&"MAPNAME_TITLE"); level.intro_hud[1] settext(&"MAPNAME_DATE"); level.intro_hud[2] settext(&"MAPNAME_PLACE"); for(i = 0 ; i < 3; i++) { level.intro_hud[i] FadeOverTime( 1.5 ); level.intro_hud[i].alpha = 1; wait(1.5); } wait(1.5); for(i = 0 ; i < 3; i++) { level.intro_hud[i] FadeOverTime( 1.5 ); level.intro_hud[i].alpha = 0; wait(1.5); } //wait(1.5); for(i = 0 ; i < 3; i++) { level.intro_hud[i] destroy(); } level thread magic_box_limit_location_init(); }
The notation is actually &"STRINGFILENAME_REFERENCE", so if your strings aren't in Mapname.STR be sure to make appropriate changes to the script.
Zone Source
In your Mapname.CSV in \zone_source, you'll have to add (changing Mapname to your level's name):
localize,mapname,,
The above example is assuming you've made your strings in your Mapname.STR, so if your strings are elsewhere be sure to make appropriate changes to the zone source.
Players
In Verruckt players are split into two pairs. You can do this following the extra steps below, or can choose to spawn your players together like in Prototype by following the first instruction outline.
Spawn Point Set Up
Add in an info_player_start found in the right click menu under the info sub menu. You'll need just one, but you'll have to add in four (4) script_structs to support four players. Place these anywhere you wish in your map and give them the KVP:
"targetname" "initial_spawn_points"
Normally you would then have to script a function to move the players around to each but the zombie mode utility already takes care of that for you if you use the above targetname.
Paired Players
If you want to split players into pairs, you'll need to set a flag in your level script shortly after maps/_zomebiemode::main(); is called.
#include common_scripts\utility; #include maps\_utility; #include maps\_zombiemode_utility; #using_animtree("generic_human"); main() { // Snippet maps\_zombiemode::main(); // Snippet flag_set( "spawn_point_override" ); }
This will tell _zombiemode.gsc to not automatically move your players around. So you will need to create a function in your level script to do this. You can use the example below taken from Verruckt.
Spawn Points
You'll need to add an extra KVP to your player spawn script_structs in Radiant. For two (2) of them you'll need to add:
"script_noteworthy" "north_spawn"
And for the other two (2):
"script_noteworthy" "south_spawn"
Script
You'll want to make create an override function in your level script and make a call to the function shortly after maps/_zomebiemode::main(); is called.
#include common_scripts\utility; #include maps\_utility; #include maps\_zombiemode_utility; #using_animtree("generic_human"); main() { // Snippet maps\_zombiemode::main(); // Snippet level thread spawn_point_override(); }
The actual override function would look like:
spawn_point_override() { flag_wait( "all_players_connected" ); players = get_players(); //spawn points are split, so grab them both seperately north_structs = getstructarray("north_spawn","script_noteworthy"); south_structs = getstructarray("south_spawn","script_noteworthy"); side1 = north_structs; side2 = south_structs; if(randomint(100)>50) { side1 = south_structs; side2 = north_structs; } //spawn players on a specific side, but randomize it up a bit for( i = 0; i < players.size; i++ ) { //track zombies for sounds players[i] thread player_zombie_awareness(); players[i] thread player_killstreak_timer(); if(i<2) { players[i] setorigin( side1[i].origin ); players[i] setplayerangles( side1[i].angles ); players[i].respawn_point = side1[i]; players[i].spawn_side = side1[i].script_noteworthy; } else { players[i] setorigin( side2[i].origin); players[i] setplayerangles( side2[i].angles); players[i].respawn_point = side2[i]; players[i].spawn_side = side2[i].script_noteworthy; } } }
Voice Overs
To get player dialogue working in your map such as lines like "Great, power's out.", "Low on ammo.", or "Need another clip!", you'll need to just add the player sound alias entries to your map sound alias. Please see the SoundAlias section under the Compiling & Distribution section for more information.
We have provided some of the new sounds from Verruckt. We encourage you to record your own sounds to enhance your mod.
Cameras
This isn't required but if you want a camera view pan at the end of the match add in two script_structs to your map. Make the first struct target the second one, the start and end points for the camera view respectively.
For the start point struct, you will need to give it two KVPs
"targetname" "intermission" "speed" "20"
20 is a good number and the one used for the stock zombie map, feel free to tweak it.
You can repeat the process to have multiple camera path so the game will cycle randomly through them during the end of the game.
Zombies
For placing zombies, right cick in the ortographic view and use Actors> Axis> Zombie_ger_ber_sshonor. While you have the spawner selected, press N (by default) to open the entity window to give the zombie some KVPs
"script_noteworthy" "zombie_spawner" "count" "9999" "script_forcespawn" "1" "targetname" "zombie_spawner_init" "spawnflags" "3"
Nodes
Your map will need nodes placed based on where you want the Zombies to walk.
For Zombie Mode you will more than likely only need path nodes and traversals since Zombies won't be using action/cover nodes.
Traversals
Ideally, if you have Zombies breaking through windows, they will need to make their way through the window. This requires placing in negotiation nodes with a traverse animation, but these are already done for you in the form of prefabs. The one you will need is: _prefabs/traverse/wall_hop_zombie.map
For better positioning of the prefab, use the traverse brush in the prefab as a guide. The brush represents the lower end of the wall so you can match it up to the geometry in your map so the AI plays the wall hop animation nicely onto your geo.
As with the window barriers, the direction of the angle on the prefab is the direction the Zombies will wall hop through.
Blockers
Blockers are buyable furniture in the map. These aren't prefabbed because they aren't always the same and can differ depending on how a mapper wants to set them up.
Model
Start off by making a script_model, and assigning any model you want to block the path. Then you'll need to give it some KVPs.
Required:
"script_linkTo" "" "spawnflags" "1" "targetname" ""
Optional:
"script_firefx" "poltergeist" "script_fxid" "large_ceiling_dust" "script_noteworthy" "jiggle"
script_linkTo and targetname are left blank up above because it will be different for each furniture piece (hence a reason why there are no prefabs). They will be referenced in the Move Location section right below on how to properly set them up.
Move Location
You'll need to make a script_struct on where you want the blocker to move when triggered. You'll have to give a KVP:
"script_linkName" ""
The script_linkName should match up to the number as the script_linkTo on the blocker. You'll see a red line connected from the blocker to the struct when both are entered.
Buy Trigger
You'll then need to make triggers for players to buy the blocker, depending on how your map is laid out, you typically need to make two trigger_uses on both sides (in case the players unlock a different part of the map and come from the other side, depends on your map). Any triggers you want to use for that particular blocker will need a few KVPs:
"targetname" "zombie_debris" "zombie_cost" "" "target" ""
The target should match up to the targetname on the blocker. Once the triggers are set, you should see red lines connecting to the blocker.
zombie_cost you define on your own based on how much money you want required. These are the defined values (if you want a different price, you'll have to edit a string into the the zombie.STR in raw\english\localizedstrings):
100 | 200 | 250 | 500 | 750 | 1000 | 1250 | 1500 | 1750 | 2000 |
Weapons
Buyable weapons are like blockers in that they are unique and are left up to the user to determine price cost. Although, there are a few prefabs of buyable weapons that can be included in your map or used to learn from in \map_source\_prefabs\zombiemode\ -- they are the weapon_*.MAP prefabs.
Decal
You'll want to add the weapon chalk decals to your map, these can be found in Radiant under Locale>Decals.
Model
Add in a script_model.
Assign it a model of the weapon you want players to buy and place it by the decal.
Buy Trigger
Make a trigger_use.
Select the script_model you just made while the trigger is still selected and press W (by default) to make the trigger target the script_model weapon.
The trigger will need some KVPs:
"targetname" "weapon_upgrade" "zombie_weapon_upgrade" "" "zombie_cost" ""
The zombie_weapon_upgrade value should be the weapon name.
zombie_cost you define on your own based on how much money you want required. These are the stock defined values (if you want a different price, you'll have to edit a string into the the zombie.STR in raw\english\localizedstrings):
100 | 200 | 250 | 500 | 750 | 1000 | 1250 | 1500 | 1750 | 2000 |
Boards
The boards Zombies rip through can easily be placed in your map using the prefabs: \map_source\_prefabs\zombiemode\window_med.map or \map_source\_prefabs\zombiemode\factory_large_window.map
The direction of the angle on the prefab is the direction the Zombie will approach it and break through.
Toilet Easter Egg
Radiant
Place a misc_model using the xmodel/static_berlin_toilet model.
Create a trigger_multiple near the chair. Give it a KVP of:
"targetname" "toilet"
Script
You'll need to call a toilet usage function after maps\_zombiemode::main();
#include common_scripts\utility; #include maps\_utility; #include maps\_zombiemode_utility; #using_animtree("generic_human"); main() { // Snippet maps\_zombiemode::main(); // Snippet level thread toilet_useage(); }
The actual toilet function will look like:
toilet_useage() { toilet_counter = 0; toilet_trig = getent("toilet", "targetname"); toilet_trig SetCursorHint( "HINT_NOICON" ); toilet_trig UseTriggerRequireLookAt(); players = getplayers(); if (!IsDefined (level.eggs)) { level.eggs = 0; } while (1) { wait(0.5); for(i=0;i<players.size;i++) { toilet_trig waittill( "trigger", players); toilet_trig playsound ("toilet_flush", "sound_done"); toilet_trig waittill ("sound_done"); toilet_counter ++; if(toilet_counter == 3) { playsoundatposition ("cha_ching", toilet_trig.origin); level.eggs = 1; setmusicstate("eggs"); wait(245); setmusicstate("WAVE_1"); level.eggs = 0; } } } }
Sound
Dentist Chair Easter Egg
Radiant
Place a misc_model using the xmodel/zombie_asylum_chair (or xmodel/zombie_asylum_chair_on) model.
Create a trigger_multiple near the chair. Give it a KVP of:
"targetname" "dentist_chair"
Script
You'll need to call a chair usage function after maps\_zombiemode::main();
#include common_scripts\utility; #include maps\_utility; #include maps\_zombiemode_utility; #using_animtree("generic_human"); main() { // Snippet maps\_zombiemode::main(); // Snippet level thread chair_useage(); }
The actual chair function will look like:
chair_useage() { chair_counter = 0; chair_trig = getent("dentist_chair", "targetname"); chair_trig SetCursorHint( "HINT_NOICON" ); chair_trig UseTriggerRequireLookAt(); players = getplayers(); while (1) { wait(0.05); for(i=0;i<players.size;i++) { chair_trig waittill( "trigger", players); chair_counter ++; if(chair_counter == 3) { playsoundatposition ("chair", chair_trig.origin); chair_counter = 0; } } } }
Sound
Water Sheeting
Water sheeting isn't required, but you can feel free to add it by following the steps below.
Triggers
Make trigger_multiples in your map where you have water FX dripping down and give each trigger the KVP:
"targetname" "waterfall"
Script
In your level script, after you call maps/_zombiemode::main();, you'll want to activate your water triggers by getting them and threading them to a water sheet function:
#include common_scripts\utility; #include maps\_utility; #include maps\_zombiemode_utility; #using_animtree("generic_human"); main() { // Snippet maps\_zombiemode::main(); // Snippet water_trigs = getentarray( "waterfall","targetname" ); array_thread( water_trigs,::watersheet_on_trigger ); }
The actual watersheet function is:
watersheet_on_trigger( ) { while( 1 ) { self waittill( "trigger", who ); if( isDefined(who) && isplayer(who) && isAlive(who) ) { if( !who maps\_laststand::player_is_in_laststand() ) { who setwatersheeting(true, 3); wait( 0.1 ); } } } }
Bouncing Betties
Decal
You'll want to add the betty chalk decals to your map, these can be found in Radiant under Locale>Decals.
Model
Add in a script_model.
Assign it a bouncing betty model (xmodel/weapon_bbetty_mine) and place it by the decal.
Buy Trigger
Make a trigger_use.
Select the script_model you just made while the trigger is still selected and press W (by default) to make the trigger target the script_model weapon.
The trigger will need a KVP:
"targetname" "betty_purchase"
Script
In your level script, after you call maps/_zombiemode::main();, you'll want to activate your betty triggers by getting them and threading them to a series of bouncing betties functions:
#include common_scripts\utility; #include maps\_utility; #include maps\_zombiemode_utility; #using_animtree("generic_human"); main() { // Snippet maps\_zombiemode::main(); // Snippet level thread purchase_bouncing_betties(); level thread give_betties_after_rounds(); }
The actual functions would look like:
purchase_bouncing_betties() { trigs = getentarray("betty_purchase","targetname"); array_thread(trigs,::buy_bouncing_betties); } buy_bouncing_betties() { self.zombie_cost = 1000; betty_model = getent(self.target, "targetname"); betty_model hide(); self sethintstring( &"ZOMBIE_BETTY_PURCHASE" ); level thread set_betty_visible(); while(1) { self waittill("trigger",who); if( who in_revive_trigger() ) { continue; } if( is_player_valid( who ) ) { if( who.score >= self.zombie_cost ) { if(!isDefined(who.has_betties)) { who.has_betties = 1; play_sound_at_pos( "purchase", self.origin ); betty_model show(); //set the score who maps\_zombiemode_score::minus_to_player_score( self.zombie_cost ); who thread bouncing_betty_setup(); //who thread show_betty_hint("betty_purchased"); trigs = getentarray("betty_purchase","targetname"); for(i = 0; i < trigs.size; i++) { trigs[i] SetInvisibleToPlayer(who); } } } } } } set_betty_visible() { players = getplayers(); trigs = getentarray("betty_purchase","targetname"); while(1) { for(j = 0; j < players.size; j++) { if( !isdefined(players[j].has_betties)) { for(i = 0; i < trigs.size; i++) { trigs[i] SetInvisibleToPlayer(players[j], false); } } } wait(1); } } bouncing_betty_watch() { while(1) { self waittill("grenade_fire",betty,weapname); if(weapname == "mine_bouncing_betty") { betty.owner = self; betty thread betty_think(); self thread betty_death_think(); } } } betty_death_think() { self waittill("death"); if(isDefined(self.trigger)) { self.trigger delete(); } self delete(); } bouncing_betty_setup() { self thread bouncing_betty_watch(); self giveweapon("mine_bouncing_betty"); self setactionslot(4,"weapon","mine_bouncing_betty"); self setweaponammostock("mine_bouncing_betty",5); } betty_loadout() { flag_wait("all_players_connected"); //players = get_players(); //array_thread(players,::bouncing_betty_setup); } betty_think() { wait(2); trigger = spawn("trigger_radius",self.origin,9,80,64); trigger waittill( "trigger" ); trigger = trigger; self playsound("betty_activated"); wait(.1); fake_model = spawn("script_model",self.origin); fake_model setmodel(self.model); self hide(); tag_origin = spawn("script_model",self.origin); tag_origin setmodel("tag_origin"); tag_origin linkto(fake_model); temp_origin = self.origin; playfxontag(level._effect["betty_trail"],tag_origin,"tag_origin"); fake_model moveto (self.origin + (0,0,32),.2); fake_model waittill("movedone"); playfx(level._effect["betty_explode"],fake_model.origin); earthquake(1,.4, temp_origin, 512); //CHris_P - betties do no damage to the players zombs = getaiarray("axis"); for(i=0;i<zombs.size;i++) { if(DistanceSquared(zombs[i].origin, temp_origin) < 200 * 200) { zombs[i] thread maps\_zombiemode_spawner::zombie_damage( "MOD_EXPLOSIVE", "none", zombs[i].origin, self.owner ); } } //radiusdamage(self.origin,128,1000,75,self.owner); trigger delete(); fake_model delete(); tag_origin delete(); if(isdefined(self)) { self delete(); } } betty_smoke_trail() { self.tag_origin = spawn("script_model",self.origin); self.tag_origin setmodel("tag_origin"); playfxontag(level._effect["betty_trail"],self.tag_origin,"tag_origin"); self.tag_origin moveto(self.tag_origin.origin + (0,0,100),.15); } give_betties_after_rounds() { while(1) { level waittill( "between_round_over" ); { players = get_players(); for(i=0;i<players.size;i++) { if(isDefined(players[i].has_betties)) { players[i] giveweapon("mine_bouncing_betty"); players[i] setactionslot(4,"weapon","mine_bouncing_betty"); players[i] setweaponammoclip("mine_bouncing_betty",2); } } } } }
Perks-a-Cola
Radiant
You can find four (4) easy to drop prefabs for the Perks-a-Cola machines in \map_source\_prefabs\zombiemode\.
You can only have one (1) of each, but you do not need to have all four (4) in your map.
vending_doubletap.MAP | Double Tap | Faster Fire Rate. |
vending_juggernaut.MAP | Juggernaut | More Health. |
vending_revive.MAP | Revive | Faster Reviving. |
vending_sleight.MAP | Sleight of Hand | Faster Reloading. |
Script
If you have Perks-a-Cola machines in your map you will need to initialize them in script. You can do this by making a call to the perks GSC before _zombiemode::main();
#include common_scripts\utility; #include maps\_utility; #include maps\_zombiemode_utility; #using_animtree("generic_human"); main() { // Snippet maps\_zombiemode_perks::init(); maps\_zombiemode::main(); // Snippet }
Electric System
Power Switch
Radiant
Make a trigger_multiple and give it a KVP of:
"use_master_switch" "targetname"
Script
You'll want to call a master electric switch function shortly after maps/_zombiemode::main();:
#include common_scripts\utility; #include maps\_utility; #include maps\_zombiemode_utility; #using_animtree("generic_human"); main() { // Snippet maps\_zombiemode::main(); // Snippet level thread master_electric_switch(); }
The actual master electric switch function will look like:
master_electric_switch() { trig = getent("use_master_switch","targetname"); master_switch = getent("master_switch","targetname"); master_switch notsolid(); //master_switch rotatepitch(90,1); trig sethintstring(&"ZOMBIE_ELECTRIC_SWITCH"); //turn off the buyable door triggers downstairs door_trigs = getentarray("electric_door","script_noteworthy"); //door_trigs[0] sethintstring(&"ZOMBIE_FLAMES_UNAVAILABLE"); //door_trigs[0] UseTriggerRequireLookAt(); array_thread(door_trigs,::set_door_unusable); array_thread(door_trigs,::play_door_dialog); fx_org = spawn("script_model", (-674.922, -300.473, 284.125)); fx_org setmodel("tag_origin"); fx_org.angles = (0, 90, 0); playfxontag(level._effect["electric_power_gen_idle"], fx_org, "tag_origin"); cheat = false; /# if( GetDvarInt( "zombie_cheat" ) >= 3 ) { wait( 5 ); cheat = true; } #/ if ( cheat != true ) { trig waittill("trigger",user); } array_thread(door_trigs,::trigger_off); master_switch rotateroll(-90,.3); //TO DO (TUEY) - kick off a 'switch' on client script here that operates similiarly to Berlin2 subway. master_switch playsound("switch_flip"); //level thread electric_current_open_middle_door(); //level thread electric_current_revive_machine(); //level thread electric_current_reload_machine(); //level thread electric_current_doubletap_machine(); //level thread electric_current_juggernog_machine(); clientnotify("revive_on"); clientnotify("middle_door_open"); clientnotify("fast_reload_on"); clientnotify("doubletap_on"); clientnotify("jugger_on"); level notify("switch_flipped"); maps\_audio::disable_bump_trigger("switch_door_trig"); level thread play_the_numbers(); left_org = getent("audio_swtch_left", "targetname"); right_org = getent("audio_swtch_right", "targetname"); left_org_b = getent("audio_swtch_b_left", "targetname"); right_org_b = getent("audio_swtch_b_right", "targetname"); if( isdefined (left_org)) { left_org playsound("amb_sparks_l"); } if( isdefined (left_org_b)) { left_org playsound("amb_sparks_l_b"); } if( isdefined (right_org)) { right_org playsound("amb_sparks_r"); } if( isdefined (right_org_b)) { right_org playsound("amb_sparks_r_b"); } // TUEY - Sets the "ON" state for all electrical systems via client scripts SetClientSysState("levelNotify","start_lights"); level thread play_pa_system(); //cut for now :( //enable the MG fountain //level thread fountain_mg42_activate(); //set the trigger hint on the fountain //getent("trig_courtyard_mg","targetname") sethintstring(&"ZOMBIE_FLAMES_UNAVAILABLE"); flag_set("electric_switch_used"); trig delete(); //enable the electric traps traps = getentarray("gas_access","targetname"); for(i=0;i<traps.size;i++) { traps[i] sethintstring(&"ZOMBIE_BUTTON_NORTH_FLAMES"); traps[i].is_available = true; } master_switch waittill("rotatedone"); playfx(level._effect["switch_sparks"] ,getstruct("switch_fx","targetname").origin); //activate perks-a-cola level notify( "master_switch_activated" ); fx_org delete(); fx_org = spawn("script_model", (-675.021, -300.906, 283.724)); fx_org setmodel("tag_origin"); fx_org.angles = (0, 90, 0); playfxontag(level._effect["electric_power_gen_on"], fx_org, "tag_origin"); fx_org playloopsound("elec_current_loop"); //elec room fx on //playfx(level._effect["elec_room_on"], (-440, -208, 8)); //turn on green lights above the zapper trap doors north_zapper_light_green(); south_zapper_light_green(); wait(6); fx_org stoploopsound(); level notify ("sleight_on"); level notify("revive_on"); level notify ("electric_on_middle_door"); level notify ("doubletap_on"); level notify ("juggernog_on"); //level waittill("electric_on_middle_door"); doors = getentarray(door_trigs[0].target,"targetname"); open_bottom_doors(doors); exploder(101); //exploder(201); //This wait is to time out the SFX properly wait(8); playsoundatposition ("amb_sparks_l_end", left_org.origin); playsoundatposition ("amb_sparks_r_end", right_org.origin); }
Doors
Traps
Compiling & Distribution
Script
Sound Alias
You'll want to copy the nazi_zombie_asylum.CSV from \raw\soundaiases\ and rename it your Nazi Zombie map. This will allow all the Zombie Verruckt sounds to be usable in your map. You can of course go into the CSV and remove any sounds you aren't using or do not wish to use.
Load Screen
Mission Screen
String
In \raw\english\localizedstrings you'll want to make a new string file for your map mod if you haven't done so already. Inside add a title for your map that will appear on the Coop map selection list. Save the file as MyMod.STR (Feel free to change the STR filename to whatever you like, just be sure to make the appropiate change in your ARENA file and Zone Source):
VERSION "1" CONFIG "G:\CoD5\cod\cod5\bin\StringEd.cfg" FILENOTES "" REFERENCE YOURMAPNAME LANG_ENGLISH "My First Verruckt Map" ENDMARKER
Arena File
The arena file does not go into an IWD or FastFile, so you'll want to make one in your map's mod folder called mod.arena. Below is an example of what it should contain, making the appropriate change for your map:
{ map "mak" longname "MENU_LEVEL_MAK" gametype "cmp arc" } { map "nazi_zombie_prototype" longname "MOD_LEVEL_ZOMBIE_PROTOTYPE" gametype "zom" } { map "nazi_zombie_asylum" longname "MENU_LEVEL_ZOMBIE_ASYLUM" gametype "zom" } { map "nazi_zombie_YOURMAPNAME" longname "MYMOD_YOURMAPNAME" gametype "zom" }
Zone Source
Map
You can use the nazi_zombie_asylum.CSV from \zone_source\ as a base by coping it and renaming it your Nazi Zombie map. You'll have to go in and make changes as noted below (you do not need all of them unless you have made the files for your map). Please note again, the example below is just a fragment from the Asylum CSV on where you would need to make changes specific to your map -- the example below alone would not work properly.
rawfile,clientscripts/nazi_zombie_YOURMAPNAME.csc rawfile,clientscripts/nazi_zombie_YOURMAPNAME_amb.csc rawfile,clientscripts/nazi_zombie_YOURMAPNAME_fx.csc rawfile,clientscripts/createfx/nazi_zombie_YOURMAPNAME_fx.csc rawfile,maps/nazi_zombie_YOURMAPNAME.gsc rawfile,maps/nazi_zombie_YOURMAPNAME_fx.gsc rawfile,maps/createfx/nazi_zombie_YOURMAPNAME_fx.gsc // Snippet col_map_sp,maps/nazi_zombie_YOURMAPNAME.d3dbsp sound,common,nazi_zombie_YOURMAPNAME,all_sp sound,generic,nazi_zombie_YOURMAPNAME,all_sp sound,voiceovers,nazi_zombie_YOURMAPNAME,all_sp sound,requests,nazi_zombie_YOURMAPNAME,all_sp sound,character,nazi_zombie_YOURMAPNAME,all_sp sound,projectiles,nazi_zombie_YOURMAPNAME,all_sp sound,nazi_zombie_asylum,nazi_zombie_YOURMAPNAME,all_sp sound,physics,nazi_zombie_YOURMAPNAME,all_sp sound,destructibles,nazi_zombie_YOURMAPNAME,all_sp // Snippet // Fog and vision, rawfile,maps/createart/nazi_zombie_YOURMAPNAME_art.gsc
You'll also want to remove the FX raw asset inclusion section at the bottom of the Asylum CSV unless you plan on using them in your map, they are not Zombie Mode specific.
Load
In \zone_source\ you'll want to make a nazi_zombie_YOURMAPNAME_load.CSV. This will store information for your Coop loadscreen for Fast File creation. It should look along the lines of (with necessary changes for your map):
ignore,code_post_gfx ignore,common ui_map,maps/nazi_zombie_YOURMAPNAME.csv
maps/nazi_zombie_YOURMAPNAME.csv refers to a CSV that you should have made for your loadscreen. If it does not exist please see the Loadscreen creation segment above.
Compile
Once you have everything done, go into Launcher and select your map in the map list. Check off:
Compile BSP Compile Lights Connect Paths Compile Reflections (If you added reflection nodes) Build Fast Files Run Map After Compile (If you want to play it right away)
You may want to make your map a Mod Specific Map. Make a folder in your [root]\mods folder with the same name as your map for organization, then in Launcher check the Mod Specific Map box and select your mod on the drop down list.
Distribution
Once you've completed the above steps, you'll want to grab a few files to ZIP them up to share with your friends and the PC Community. The list of things are:
- Map FastFile
- Map _Load FastFile
- Map IWD
- Map Arena
You'll probably want to create a ReadMe text file with your information, how to install your map, how to launch the map, and any other relevant information.
Once you have all these things, ZIP them together and give that ZIP to others. You'll need to let them know (in your ReadMe) that they must go to the Mods menu when they launch the game and launch your map's mod before attempting to play the map in Solo or Coop.