Left 4 Dead 2

Left 4 Dead 2

Not enough ratings
[Stripper:Source] How to modify a map
By 您🍂
A guide explaining how to modify a map using Stripper:Source and how to port a Stripper map to VScript format.
   
Award
Favorite
Favorited
Unfavorite
Add entities to the map
First, go to a map with the "map ..." command in the console. for example, map c1m1_hotel

If you don't know what is the "c1m1_hotel", you can go to a map through a lobby and then type "status" in the console to get the mapname that you want to modify:



and type sv_cheats 1;director_stop;god 1 to make sure that no one interferes with the progress of the modification.

Type /admin in the chatbox, Objects Spawner will add the (4)option to the admin menu, we will use this option to add new entities to the map:



We can spawn an entity with (3)Spawn Objects, adjust the angle(s) and position(origin) of the entity with (4)Edit Object, finally save the entity that we've spawned with (1)Save Object:



Entity Types

Objects Spawner can spawn four types of entities, they are:

(1)(2)Spawn Physics On Origin/Cursor: Spawn a prop_physics entity on the map;
  • prop_physics can be punched by a tank, shocked by a boomer or a charger, pushed by a player(depending on mass), and have physical effects.
(3)(4)Spawn Non-solid On Origin/Cursor: Spawn a prop_dynamic entity with solid 0 on the map, this type of entity has no hitbox effects;
(5)(6)Spawn Solid On Origin/Cursor: Spawn a prop_dynamic entity with solid 6(hitbox) on the map,;

(3,4,5,6)prop_dynamic has no physical effects, so this type of entity is negative
for cars, because ...:


(7)(..)Spawn Items & Weapons On Origin/Cursor.

We should use (5,6)Spawn Solid to spawn entities for obstacles and roads.
Stripper Format
Our entities will be saved in the \Left 4 Dead 2\left4dead2\addons\stripper\maps\ folder and the one will be named mapname.cfg(e.g. c1m1_hotel.cfg). Objects Spawner will not overwrite it the next time, but rather Objects Spawner will save entities incrementally.

Open it with a notepad(e.g. notepad++), let's see what format/code it is stored in:


solid is the entity's hitbox status. if this value is 0, the entity has no hitbox; if it's 6, the survivors and infected can't walk through it. (Non-solid vs Solid)

origin is the position of the entity. you can get the position of your feet using /pos in the chatbox.

angles is the angle of the entity, the value should be 45*N for navmesh editing (e.g. 45, 90, 135).

model
* Mass, hitbox, appearance of the entity determined by the mass(in QC), material(.vmt and .vtf), references.smd and physics.smd(hitbox) of the model.

The type of an entity and it's properties are determined by the classname. For example, physical entities are prop_physics, solid entities are prop_dynamic, invisible walls are env_physics_blocker or env_player_blocker.



We can add some properties to the entity's code to control it's function if the classname supports it.
For example, adding "rendermode" "10" to an entity can make it to be invisible:


adding "disableshadows" "1" will disable the shadows of an entity. this will increase the FPS of the clients and improve the client-side performance when we have a lot of entities on the map:


  • Above: using "disableshadows" "1";
  • Below: default.
You can find the properties&keyvalues of a classname at Valve Developer Community, and learn how to control function/feature by this way.
Input
Aim at the entity you just spawned, and type "ent_fire !picker kill" in the console:



Entities such as prop_dynamic and prop_physics accept various "commands", i.e. Input.
However, most entities on the map are prop_static, this type of entity can't accept any commands from all sources. in other words, we can't remove any entities that belong to prop_static classname.

We can use /ent in the chatbox to check the classname of an entity we are aimming, and if it doesn't accept the "input", it's probably a prop_static:


Command an entity using Stripper
We can provide kill to an entity in the console, but we need to port this console command to the code and make Stripper to be able to command.

For example, we'll remove a fence barricade in c2m4_barns to learn how to command an entity using Stripper:

Enter the map with map c2m4_barns, save an empty .cfg file for this map with Objects Spawner, and we need to create a logic_auto entity in the .cfg file as our commander:
  • add:
    {
    "classname" "logic_auto"
    "OnMapSpawn" "props_coop,kill,,0,-1"
    }
OnMapSpawn will give an input(command) to all entities that called "props_coop" when the map is loaded; this will remove "props_coop".

props_coop is the targetname of the entity we want to remove. to get the targetname of an entity, we can aim at it and use /ent to print the target's information in the chatbox.

We will find that there are still some invisible walls blocking the way after we remove the fence barricade, and /ent won't work for them, but we can use ent_fire !picker addoutput "targetname test01" and ent_dump test01 or ent_dump !picker to get the information about this entity.
5 Types of invisible walls
There are five types of normal invisible walls:
  1. The first is env_physics_blocker;
  2. The second is env_player_blocker;
  3. The third is func_simpleladder, which is also used as a ladder;
  4. The fourth is prop_dynamic with "rendermode" "10";
  5. The fifth is the func_brush, which cannot be specified with /ent.
The first time of using ent_dump, you may see an entity with classname info_remarkable, which is not an invisible wall blocking us, so we need to remove it and other similar distractions with the ent_fire info_remarkable kill.

Repeatedly get the entity information and remove them with ent_fire until we have found and removed all the entities blocking the path.




The entity information printed by the console during this process shows that there is a func_simpleladder and a func_brush blocking the path.

* If you can't get information about entities that are blocking the path this way, then you can use /find func_brush to find nearby func_brush-like entities, and then search for the relevant entities in the list of entities exported by stripper_dump based on the origin given by this command.
* Note: Most invisible walls are not func_brush-like entities. Please consider using this add-on to remove invisible walls.

Reload the map and type stripper_dump in the console to export information about the interactable entities(vanilla entities only) on the map; we need to find their targetname in the exported entity information to remove the invisible walls with kill from OnMapSpawn.




Search for hammerid (the unique ID of the vanilla entity) to find both entities. For the func_brush, we can use kill to remove it from the map.

However, since func_simpleladder doesn't have a target name, there is no way we can remove it with OnMapSpawn's kill.
In this case we need to use another feature of Stripper to remove the entity.
Remove entities with filter
Stripper's filter can be used to remove entities with hammerid; however, it cannot remove various entities added by TLS update[forums.alliedmods.net].
Add the following code to the bottom of c2m4_barns.cfg:
  • filter:
    {
    "hammerid" "2018662"
    }
    {
    "hammerid" "1864501"
    }


Save and reload the map. Entities matching the criteria (specified by hammerid) will be removed by Stripper's filter.
* If you want to continue adding entities below the filter, you'll also need to add another add: line before the code that adds the entity.

Now there should be no annoying invisible walls in the way, but that's not enough.

If we type nav_edit 1 in the console, we'll see a square with an embedded sky-blue box at that location, and the blue box means that the bots will not be able to move to that location. like this:



The func_nav_blocker class entity blocks bots from entering a navmesh area(s) by adding a sky-blue box to the area.

Aim at the square with the sky-blue box embedded, use ent_dump !picker to find the func_nav_blocker that's causing the problem, and use filter to remove it from the map. In this way, bots won't have psychological problems because of the func_nav_blocker:



Create your first event: button, glowing prop and visual & auditory effects
The filter can be used for relatively simple event modifications, for example, you can use it to remove the CEDA doors and the alarm station button at c5m2 (The Parish Level 2), thus disabling the events associated with them.

We can also use add to add a new event to the map.

First enter the first level of Dead Center (c1m1_hotel) and create an empty .cfg file with /admin, then add the following code to this file:
  • add:
    {
    "solid" "6"
    "origin" "390 5635 2818"
    "angles" "0 180 0"
    "model" "models/props_fortifications/concrete_barrier01.mdl"
    "classname" "prop_dynamic"
    }
    {
    "solid" "6"
    "origin" "402 5579 2909"
    "angles" "0 0 0"
    "model" "models/props_unique/generator_switch_01.mdl"
    "classname" "prop_dynamic"
    }



Our goal is to remove the wall on the right when the button on the left is pressed.

prop_dynamic does not have the functionality of a button; instead, the functionality of a button is actually implemented by two other abstract entities, which are
First, enable through-the-wall mode with the noclip console command, then move to the outside of the center point of the button, and use /pos to get the position of your feet.



Then create a func_button entity that uses the origin obtained with /pos:
  • add:
    {
    "classname" "func_button"
    "origin" "..."
    "spawnflags" "1024"
    }
If the origin is inside the entity, there's no way for the player to touch the button; by the same token, another common beginner's mistake is to put the button in the ground, since the height of the origin obtained with /pos is at the feet, not at the eye
Where "spawnflags" "1024" means that pressing E will activate the button.

Then we need to give the wall on the right a targetname so we can kill it. For example "targetname" "c1m1_barricade_test01".

In order for the button to remove the wall on the right when pressed, we also need to design what outputs will be triggered when the button is pressed.
The process of adding outputs is similar to logic_auto's OnMapSpawn: first add an "OnPressed", and then add code that can remove entities.



Players also need to know that the left button is pressable, so we need to add a little special effect to the left button (prop_dynamic):
glowcolor is the color of the glowing (rgb);
glowrange is how close the player has to be to the entity to make it glow;
and glowstate is the state in which the entity is glowing (3 is glowing OnMapSpawn, 0 is not glowing OnMapSpawn).



The visual effects of prop_dynamic should be disabled when the player presses the button. Adding a targetname to this entity is necessary to disable its visual effects, and func_button also needs the OnPressed and stopglowing to disable the glowing effect of prop_dyanmic when the func_button is pressed.

*We can use startglowing(input) to enable the glowing effect of an entity that with glowstart 0; or use stopglowing to disable the glowing effect of something.

To make this button play a sound when pressed (the sound is also generated by another abstract entity; its origin should be the same as the location of the prop_dynamic button), add:
  • add:
    {
    "classname" "ambient_generic"
    "message" "ambient/atmosphere/arena_lights_off_01.wav"
    "pitch" "100"
    "health" "10"
    "pitchstart" "100"
    "spawnflags" "48"
    "radius" "5555"
    "targetname" "c1m1_barricade_test03"
    "origin" "402.00 5579.00 2909.00"
    }
* You can learn a lot about a classname in Valve Developer Community.

To make ambient_generic to play a sound, provide PlaySound input to the entity.

Also need to add visual effects to the barricade when it is destroyed:
  • add:
    {
    "classname" "info_particle_system"
    "effect_name" "gas_explosion_main"
    "targetname" "sky_c1m3_CME_tank_particles"
    "origin" "390 5635 2818"
    }
* Trigger the visual effect with Start input; we also need to stop the visual effect with Stop after 0.8 seconds. The 0 means a delay of N seconds.

and the sound of the barricade exploding:
  • add:
    {
    "classname" "ambient_generic"
    "health" "10"
    "message" "weapons/hegrenade/explode5.wav"
    "pitch" "100"
    "pitchstart" "100"
    "radius" "7000"
    "spawnflags" "49"
    "targetname" "sky_c1m3_CME_tank_sound"
    "origin" "390 5635 2818"
    }
The fact that the barricade explodes immediately after the button is pressed can feel a bit anticlimactic, so we also need to delay the timing of the barricade's explosion:
  • add:
    {
    "classname" "func_button"
    "origin" "411.181060 5580.661132 2898.300292"
    "spawnflags" "1024"
    "OnPressed" "c1m1_barricade_test01,kill,,2,-1"
    "OnPressed" "c1m1_barricade_test02,stopglowing,,0,-1"
    "OnPressed" "c1m1_barricade_test03,PlaySound,,0,-1"
    "OnPressed" "sky_c1m3_CME_tank_particles,start,,2,-1"
    "OnPressed" "sky_c1m3_CME_tank_particles,stop,,2.8,-1"
    "OnPressed" "sky_c1m3_CME_tank_sound,PlaySound,,2,-1"
    }

Where [2] and [2.8] means delay the execution for N seconds and [0] means immediately. For example, [kill,,0] deletes the entity immediately, while [kill,,2] means that the entity will be deleted two seconds later.

The whole event will probably look like this when it's done:
Have you abadoned learned that how to make an event?

If you want to call a panic when the barricade explodes, you can provide ForcePanicEvent to an entity that called director or @director (you can find this entity in the output of stripper_dump).
See also: https://developer.valvesoftware.com/wiki/L4D2_Director_Scripts
trigger_once and modify: Remove annoying TLS entities
TLS entities are added after Stripper has finished entities, and Stripper cannot remove "non-existent" entities that have not yet been generated, so we have to find a way to delay the finishing of stripper's filter.

trigger_once is a bounding-box-like thing, where a player entering it (or it could be something else, as specified by filtername) triggers the output of OnTrigger, this is not the same as logic_auto and stripper's fliter.

For example, if you want to delete an entity, then using trigger_once will delete the target entity by triggering OnTrigger when a qualified object enters its bounding box.
trigger_once will run after the TLS entities have been generated(if you don't put it on the spawnpoint), and thus avoiding the associated load order problem.

This is similar to OnPressed, but it doesn't require the player to press a button.
We can use trigger_once and place its bounding box on the escape route(e.g. the outside of a door) to delay the command without any unnatural or conscious behavior.

Here is some example code:
https://forums.alliedmods.net/showpost.php?p=2799367&postcount=181

The trigger_once's trigger area range is added by logic_auto's AddOutput of OnMapSpawn, and its location is specified by origin.
* You can create a logic_relay entity, put the commands in that entity(OnTrigger) instead of trigger_once and add a trigger code associated with that entity to trigger_once. this can be useful for debugging.

or simply modify the door of a saferoom to make it be a commander, where in the example code:
  • match: Specifies the entity to be modified by hammerid or similar;
  • insert: Adds an attribute and its keyvalue to the entity as an insert, e.g., add output (OnFullyOpen) and its effect (e.g., kill an entity);
  • replace: Replaces an attribute and it's keyvalue of an entity that you specify, e.g. "targetname" "new_targetname" replaces the targetname of the target entity with the specified keyvalue;
  • delete: Removes an attribute from the target entity.
We can use modify the door of a start saferoom to delay the command(s) by inserting OnFullyOpen.
This way is easy-to-use, but it's impossible on the first level of a map because there is no start saferoom.

For more information about modify, see: https://www.bailopan.net/stripper/#newconf
Modify navmesh to allow the bots to recognize entities and avoid or walk on them
Preview:

Type nav_edit 1 in the console to enter the navmesh editing, and type nav_save to save the navmesh. this way will make our modification will be based on TLS update.

Go to \Left 4 Dead 2\update\maps\ and rename the current map's .nav file to invalidate the TLS .nav file, that overwrites the navmesh we modified and saved.[forums.alliedmods.net]
* For example, rename c1m1_hotel.nav to c1m1_hotel#.nav

Reload the map and disable special infected & common generation of director with director_stop, use ent_fire infected kill to remove all common, and use kick ... to kick all survivor bots to prevent such objects from crashing the game to crash while editing in navmesh.
* The kick command may be case sensitive, though I'm not sure: if kick nick doesn't work, try kick Nick.

Enter navmesh editing with nav_edit 1, and type...:
  • bind "kp_ins" "nav_split";
  • bind "del" "nav_delete";
  • bind "f5" "nav_save";
  • bind "'" "nav_mark";
  • bind "/" "nav_connect";
Starting with the white line on the navmesh areas, press keypad 0 to split the navmesh area(nav_split), then press del to remove the navmesh area(s) under the entity(nav_delete), and finally press F5 to save the changes(nav_save).
The process looks like this:

To create a new navmesh area, see below.
If the angle(s) of the entity is not a multiple of 45, then there will be a misalignment between the entity and the navmesh areas.

To make common and tank to be able to climb our entity, we also need to type:
  1. bind "HOME" "nav_create_area_at_feet 1";
  2. bind "END" "nav_create_area_at_feet 0";
  3. bind "[" "nav_begin_area";
  4. bind "]" "nav_end_area";

Press HOME to change nav_edit's cursor positioning point (height) to feet, because nav_edit's cursor does not recognize prop_dynamic.

Move to the top of the entity, aim at the top left corner of the entity and press the { key, then aim at the bottom right corner of the top of the entity and press the } key to create the navigation network area.
* I can't type out the square brackets due to formatting issues, so I used the curly brackets instead.

When you are finished, press END to change the cursor positioning point to the crosshair.

Aim at the navmesh area we just created, press ' (nav_mark) to select it, then aim at the navmesh area around the bottom and press / (nav_connect) in order to create a unidirectional connection so that the common(and tank) located in the top navmesh area will jump down according to this line;
And vice versa, we will also need to create several navmesh connections from the bottom to the top.
  • If you accidentally create a wrong connection, you will need to remove it with nav_mark( ' ) and nav_disconnect.

Finally, spawn some zombies with z_spawn to test if the navmesh areas can be recognized by the bots. Remember to kill the zombies with ent_fire infected kill before editing the navmesh, so that they don't crash the game.

For example:


Once the modifications are complete, we will also need to use nav_analyze to allow the game engine to analyze the entire navmesh.

The next step is also to manually determine if there is a problem with the navmesh; if there is, the director will not generate special infected and common on all of navmesh areas. use director_start to re-enable the special infected & common generation of the director.

Navmesh debugging
First, Type ... in the console:
z_debug 1;z_show_flow_delta 2;z_show_flow_distance 1

If the navmesh areas within the saferoom (or spawnpoint) display Flow: -9999 / -9999, this indicates there is a problem with the navmesh area(s):



Go to the saferoom at the end of the level and we'll notice that the numbers in Flow are different:



Go back until you find the -9999/-9999 navmesh area(s) nearest to the "normal navmesh area(s)":
  • If there are problems connecting navmesh areas, use '(nav_mark) and /(nav_connect) to connect them;
  • If there is a lack of navmesh areas between navmesh areas, mark one navmesh area with '(nav_mark), then aim at another navmesh area and fill the gaps between them with nav_splice;
  • If the problem with the navmesh areas is caused by the navmesh attribute of PLAYER_CLIP, aim at the navigation area(s) and type clear_attribute PLAYERCLIP to remove the attribute.


*an example of PLAYER_CLIP & BLOCKED_SURVIVOR.

After fixing the connection/lack/attribute problems of the navmesh, update the navmesh with nav_analyze; if the navmesh areas in the start zone are still -9999 / -9999, repeat this process until the navmesh areas(in the start zone) are free of such problems.

*Flow: -9999 / -9999 or similar navmesh problems that cause common generation to not work, possibly rare problems caused by removing func_nav_blocker[forums.alliedmods.net].

See also:
https://steamcommunity.com/sharedfiles/filedetails/?id=1981189248
https://developer.valvesoftware.com/wiki/Nav_Mesh/Console_commands#Editing_commands
https://developer.valvesoftware.com/wiki/Sorting_out_navigation_flow
How to port a Stripper map to VScript format
It's best to make a new Stripper map with some event(s) and ladders before you port a Stripper map to VScript format. I won't explain the basics of Stripper in this section because they have been explained before.

Download the example code from here: https://forums.alliedmods.net/showpost.php?p=2816742&postcount=230

In \Left 4 Dead 2\left4dead2\scripts\vscripts\ create a script file called director_base_addon.nut that will run when the map is loaded (regardless of what map is loaded).

if(whatisthemapname == "c11m1_greenhouse") is used to detect the map name;
When it detects that a player is playing the map we're modifying, its code will run another vscript file that will be used to generate entities.

Since director_base_addon.nut and the script file it runs from are executed twice (see the message printed to the console), it is also necessary to perform duplicate detection (determine if the script has been executed before by creating an entity and checking if it exists):



which:
IncludeScript("modified_dead_air_c11m1_ladder_controller.nut", this); is used to create ladders or modify ladder properties.

The ladder related .nut scripts must be run inside director_base_addon.nut or there will be navmesh problems.

VScript: add

Now, open modified_dead_air_c11m1.nut the script used to generate entities.

Stripper's add is implemented in VScript using SpawnEntityFromTable(); it looks a lot like Stripper's entity format, but it's different:


  1. origin and angles use Vector(123,456,789);
  2. Common parameters have keyvalue without double quotes (""), but parameters such as model and targetname require double quotes for their keyvalue.

It looks like double quotes are needed for the keyvalue of the string.

Use this plugin to generate VScript entities: https://forums.alliedmods.net/showpost.php?p=2814521&postcount=228

SpawnEntityFromTable doesn't seem to support adding output like OnTrigger to entities.

Download modified the parish vscript(experimental)[forums.alliedmods.net], open modified_the_parish_c5m1.nut and scroll down to the end.

We can use EntityOutputs.AddOutput to add outputs(OnTrigger / OnMapSpawn) to an entity like logic_relay / logic_auto, this way requires the entity to have a targetname, e.g. relay_intro_survivor_cameras.



VScript: modify

Use EntFire to modify the properties of an entity:



If the entity class does not support EntFire, or does not have a target name, we need to delete the entity and rebuild the entity with its attributes using SpawnEntityFromTable.

VScript: filter

There are three ways to remove an entity: 1) EntFire; 2) CBaseEntity.Kill(); 3) trigger_remove

1)EntFire:
EntFire("targetname","kill");
For entities that have a targetname, easy-to-use.

2)CBaseEntity.Kill():
For entities that do not have a targetname, but only if the entity type is CBaseEntity; entities such as doors may not be removed in this way.



Use Entities.FindByClassnameNearest to get the entity(by classname) nearest to Vector; "3" specifies the size of the search range.

== null is the entity detection. If no entity is found, the code inside {} will be executed. No code means nothing is done.

If the entity is not null, create a variable named prop_physics_01(local ...) and specify the variable as the entity obtained by Entities.FindByClassnameNearest().

And then remove the entities associated with the variable. This is done by sending a .Kill() command to the entities via the variable name.

3)trigger_remove:
See: https://forums.alliedmods.net/showpost.php?p=2806050&postcount=193

Copy and paste the code from the "VScript version:" section of the post, replace the keyvalue of origin, that's all.



Adding ladders
Create or modify ladders with this plugin: https://forums.alliedmods.net/showpost.php?p=2814535&postcount=229



Since SpawnEntityFromTable doesn't support adding properties to a ladder, we need to use addoutput of EntFire to add normal.x / normal.y / normal.z



VScript has no function for hammerid (at least none that I could find), and the vanilla ladders have no targetname, and FindByClassnameNearest does not work for them, so there is no way to change the keyvalue of the team parameter to allow the survivor team to climb the ladder via addoutput.

So the only way to allow survivors to climb the ladder is to delete the ladder and rebuild it with team 0 in place.

Since the removing ladder code finds and removes all ladders based on the model, we must put the removing ladder code at the top of the .nut file or the ladders we added will be removed.

Why VScript?

The advantage of using VScript is that dedicated and local servers do not rely on Stripper and its requirements, and do not need to use -insecure; in other words, it can be used on VAC-enabled servers.

On the other hand, it does work on VAC-enabled servers. When used on local servers, since -insecure is not used, the host can join VAC-enabled servers without having to remove the non-existent -insecure and restart the game.

In addition, VScript's .nut script files can be packaged with .nav and .lmp for use as a separate .vpk; it is also possible to release it on Workshop, for example:
https://steamcommunity.com/sharedfiles/filedetails/?id=3113799031
The advantage of using VScript is that it delays the timing of entity generation, thus mitigating the problems associated with ED_Alloc: no free edicts:

First remove unnecessary vanilla entities and then generate our entities using SpawnEntityFromTable(VScript), thus avoiding or mitigating game crashes caused by generating a large number of entities.

Some games that use Source engine also support VScript, such as Gmod, so L4D2's VScript could theoretically be used in other games as well

For example, it might be possible to use VScript to spawn enemies (and other classes of entities) in L4D2 maps in Gmod; I haven't tried it that way, but it's theoretically possible

4 Comments
您🍂  [author] Feb 12 @ 6:07pm 
😂
Sandy Feb 12 @ 1:38am 
you're wrong, your work is just fantastic, I especially liked watching it on sky alliedmods, but after deleting all the videos it became problematic, because I couldn't estimate the "volume" of your work
Anyway, thank you, you taught me and I think many other people to create something more. :p2cube:
您🍂  [author] Feb 11 @ 7:31pm 
Youtube pushes other videos from the same channel to those who have already watched videos from that channel, which is not a big problem if the channel has similar types of videos. However, I often upload messy videos to Youtube, so I try not to make my uploads public so as not to bother others.

TL;DR, I guess it's not worth being public xd
Sandy Feb 11 @ 3:42pm 
Wow!!? Good work men! :spirallove:
n why you removed your videos from YouTube? :lunar2019deadpanpig: