Fixing and tweaking maps with Stripper

Take note, this does not go into significant detail. Stripper is VERY powerful, and can be used to do amazing things. This is just an example of how to accomplish a few things in Team Fortress 2 in particular, but may be easily adaptable to other Source Engine games.

If you find any errors feel free to contact CmptrWz on the forums.

Before you begin, follow instructions for installing Metamod:Source and Stripper:Source. For testing, I recommend installing on your local machine, it will work fine if you are using the latest versions.

The Basics

Everything in the source engine you will be able to tweak are entities. Each entity has a class, and each class has options that can be applied. Some entities you can't do much about, others you can do a lot with.

Stripper allows you to filter entities out based on their name, their class, what is set, and so on. It also lets you add new ones, and edit the ones that are already there.

Stripper is, as a result, VERY powerful. And if you screw up, chances are your server will crash. ALWAYS TEST BEFORE PUTTING ON A PRODUCTION SERVER.

I am not going to cover every entitiy here. The Valve Developer Wiki is a good place to start for that. In addition, being able to recognize things in a text file is probably a must here, as well as regular expressions when changing things.

Profiling a map

Sometimes you need to know what is already in a map to decide how to change it. The stripper_dump command is your friend here. Load up a map, you don't even have to spawn, and enter stripper_dump into your console. A file will be placed into addons/stripper/dumps named after the map you loaded containing all entities. Whenever I refer to checking a map for something, chances are I mean by opening up the dump file.

Problems/Tweaks and Solutions

Most of the time, you will want to mess with a map to fix a problem you think the map has. Sometimes you want to modify a map to make it more friendly for a modified game mode you are running, but don't want to make people download an entirely new map. If you need to alter so-called "World Brushes" then you are looking at the wrong page, and should go find a Hammer tutorial.

Below you will find a number of problems maps might have, and how to fix them with stripper.

No Round End

Sometimes a CP map, or a map with no defined gamemode to speak of, does not play nice with map cycling. The round never ends, and the timelimit counts down to 0 and stays there.

On a CP map, this usually means someone has to win, no stalemates. On a deathmatch or similar map, this means the map basically never ends, ever.

This is caused by a lack of calling a game_round_win entity from a team_round_timer entity. Sometimes due to the lack of one, or both, in the map.

Before going and trying to add them, however, we should check to see if the entities exist in the map already. How we proceed will vary based on this.

Both exist

Before you continue, make sure there IS a problem. Maps go into overtime if a point is in the middle of being captured, for example. Alternatively, see if there is an "OnFinished" in the team_round_timer referring to the "targetname" of the game_round_win. Note it may go through one or more relays, complicating things.

If there is no "OnFinished" or you are sure it doesn't trigger the game_round_win you will need to note the "targetname" of the game_round_win, and if present the team_round_timer. For the purposes of example, lets assume that we have targetnames of game_win and team_timer.

The only things we need to do here are ensure that the game_round_win is set to team 0, and then tell it the round is won. That is accomplished with two additions to the team_round_timer, like so:

modify:
{
	match:
	{
		"targetname" "round_timer" ; Note, you could use "classname" "team_round_timer" here instead, if there is only one
	}
	insert:
	{
		"OnFinished" "game_win,SetTeam,0,0,-1"
		"OnFinished" "game_win,RoundWin,,0.1,-1"
	}
}

That basically tells the team_round_timer to tell the game_round_win to stalemate if the timer hits 0. It will take a moment to register the stalemate, though.

No game_round_win

Basically, we have to do what we did above, to modify the timer, but we don't have a game_round_win to target. This isn't much more complicated.

Lets assume that our team_round_timer is still round_timer:

add:
{
	"classname" "game_round_win"
	"targetname" "game_win"
	"TeamNum" "0"
}
modify:
{
	match:
	{
		"targetname" "round_timer" ; Note, you could use "classname" "team_round_timer" here instead, if there is only one
	}
	insert:
	{
		"OnFinished" "game_win,RoundWin,,0,-1"
	}

}

Basically, we had to create the game_round_win, rather than assume it was there. BUT, since we created it, we were able to set the team in advance, negating any need to ensure the team was set correctly later.

No team_round timer

We might also have a game_round_win, but no timer. In this case, we just need to add a timer that refers to the existing game_round_win. Once again, assuming that the game_round_win has a targetname of game_win:

add:
{
	"targetname" "round_timer"
	"classname" "team_round_timer"
	"timer_length" "600" // Seconds for each round to last
	"max_length" "600" // Duplicate timer_length here
	"auto_countdown" "1"
	"start_paused" "0"
	"StartDisabled" "0"
	"show_in_hud" "1"
	"OnFinished" "game_win,SetTeam,0,0,-1"
	"OnFinished" "game_win,RoundWin,,0.1,-1"
}

There, added a timer, that will call the game_round_win that was already there.

No game_round_win OR team_round_timer

Lazy map maker, there is NOTHING to start with! Guess we have to do everything ourselves. Combining the above two properly, we get:

add:
{
	"classname" "game_round_win"
	"targetname" "game_win"
	"TeamNum" "0"
}
{
	"targetname" "round_timer"
	"classname" "team_round_timer"
	"timer_length" "600" // Seconds for each round to last
	"max_length" "600" // Duplicate timer_length here
	"auto_countdown" "1"
	"start_paused" "0"
	"StartDisabled" "0"
	"show_in_hud" "1"
	"OnFinished" "game_win,RoundWin,,0,-1"
}

Easy, right? Now we have a round timer, and we are all set for a stalemate.

Adding time on a CP map

Ok, you have a timer. Either you used the above and added one yourself, or the map came with one. It even stalemates properly.

But it doesn't add time for capturing a point. Easy enough to solve!

You need to know the targetname of the timer. The targetname of the capture points can be used for adjusting the amount of time added, but that I will leave up to you. We are going to add a generic amount of time per capture.

; Uncomment the following if the team_round_timer does not have a name.
;modify:
;{
;	match:
;	{
;		"classname" "team_round_timer"
;	}
;	insert:
;	{
;		"targetname" "round_timer"
;	}
;}
modify:
{
	match:
	{
		"classname" "team_control_point"
	}
	insert:
	{
		"OnCapTeam1" "round_timer,AddTime,300,0,-1" ; 300 is the number of seconds to add when Red caps the point
		"OnCapTeam2" "round_timer,AddTime,300,0,-1" ; Ditto, but for blue
	}
}

Now the map will add time when points are capped. Expand to a series of modify blocks with targetnames to set time per point in the map, though.

Skipping a round on a multi-round attack/defend map

Going to use Dustbowl as an example here, although in theory this will work for payload maps as well. Due to how most of these maps are designed, start skipping from the END.

On dustbowl, and I assume on most valve maps, the points are named "cap_red_#" where # is the point number. The easy solution to skipping a round is to set the last round's points to be owned by blue by default.

Assuming you know the names, you just modify each one. We are going to shorthand this, though, due to the predictable naming.

modify:
{
	match:
	{
		"targetname" "/cap_red_[56]/"
	}
	replace:
	{
		"point_default_owner" "3"
	}
}

Those '/' turn the check into a regular expression. You could change the [56] to [3456] or even [3-6] to only run through the first round as well. This won't fix the text messages, though!

Inverted CTF

Lets say you want to play any Capture the Flag map, but with a twist. Instead of playing normally, you want to play invade. As in, drag the intel into the enemy base, rather than out of.

The messages might all be wrong as to who's base you are in and such, but the easiest solution is to make the game think you are playing a normal CTF map. All you have to do is swap the red and blue intel and cap areas.

Thus, you might think you want something like this, where item_teamflag is a flag and func_capturezone is a capture area:

; WARNING: THIS WILL NOT WORK LIKE YOU EXPECT IT TO
; Blue to Red
modify:
{
	match:
	{
		"classname" "/(item_teamflag|func_capturezone)/"
		"TeamNum" "3"
	}
	replace:
	{
		"TeamNum" "2"
	}
}
; Red to Blue
{
	match:
	{
		"classname" "/(item_teamflag|func_capturezone)/"
		"TeamNum" "2"
	}
	replace:
	{
		"TeamNum" "3"
	}
}

The problem there is that the end result will be Blue owning everything, as each block can alter previous ones. Instead, we can borrow the spectator team to do the swap:

; Red to spectator
modify:
{
	match:
	{
		"classname" "/(item_teamflag|func_capturezone)/"
		"TeamNum" "2"
	}
	replace:
	{
		"TeamNum" "1"
	}
}
; Blue to Red
{
	match:
	{
		"classname" "/(item_teamflag|func_capturezone)/"
		"TeamNum" "3"
	}
	replace:
	{
		"TeamNum" "2"
	}
}
; Spectator to Blue
{
	match:
	{
		"classname" "/(item_teamflag|func_capturezone)/"
		"TeamNum" "1"
	}
	replace:
	{
		"TeamNum" "3"
	}
}

Everything else on the map will be identical, but the flags will be going the opposite directions.