Skip to content

Mapping

Hammer

Hammer is the map editing tool used for most source maps. There is also Hammer++, an unofficialy community-supported version of Hammer. You can read an indepth guide here. Important takeaway notes for general mapping is understanding entities, input / output, and scripts.

Entities and Brushes

Entities and Brushes are similar in that entities interact with brushes. Entities are mobile objects such as players, props, chickens, etc. Brushes are objects such as ladders, triggers, and water. Both entities and brushes support input / output. Targets such as !self target the object itself, !activator targets whoever triggered the object, etc. Hammer1

Input / Output (I/O)

Inputs and Outputs are a key function of maps. They can be used to make BHops, Triggers, Minigames, and many other possibilities. The vast majority of maps use inputs and outputs. An important note is that every output that an entity has counts as an input for another entity. Hammer2

Scripts

While not as used as Inputs and Outputs, scripts are a significantly more powerful version of inputs and outputs. Scripts can be used to better automate and control entities and brushes. You will generally find scripts in more complex maps, which are generally exclusive to Jailbreak. The language that scripts use depends on the platform, with the large majority of them using Squirrel.

Example Script
Damage<-0;
MAX_DAMAGE<-20;
MIN_DAMAGE<-0;
DamageSignTT<-null;
DAMAGE_TO_TEXTURE <- {[0] = 0, [5] = 1, [10] = 2, [15] = 3, [20] = 4, [25] = 5, [50] = 6, [75] = 7, [100] = 8};

MAX_WINNERS<-32;
MIN_WINNERS<-3;
WinnersSettings<-4;
Winners<-0;
WinnersSettingTT<-null;

DEBUG_PRINT<-false
function debugprint(text)
{
  if (!DEBUG_PRINT || GetDeveloperLevel()   == 0) return;
  printl("*************" + text + "*************")
}

function OnPostSpawn() //Called after the logic_script spawns
{
  EntFireByHandle(self, "RunScriptCode", "Setup()", 0.5, null, null);
}

function Setup()
{
  DamageSignTT = Entities.FindByName(null, "decathlon_damage_sign_tt");

  Damage = 0;
  EntFireByHandle(DamageSignTT, "SetTextureIndex", DAMAGE_TO_TEXTURE[Damage].tostring(), 0.0, null, self);
}

function AddToDamage(toAdd)
{
  Damage += toAdd;
  if (Damage > MAX_DAMAGE) Damage = MAX_DAMAGE;
  if (Damage < MIN_DAMAGE) Damage = MIN_DAMAGE;

  EntFireByHandle(DamageSignTT, "SetTextureIndex", DAMAGE_TO_TEXTURE[Damage].tostring(), 0.0, null, self);
  if (Damage == 0) {
    EntFire("decathlon_damage", "Disable", "", 0.0);
  } else {
    EntFire("decathlon_damage", "Enable", "", 0.0);
  }
  EntFire("decathlon_damage", "SetDamage", (Damage*2).tostring(), 0.0);
}


function UpdateTeleports()
{
  local teleportDesinationEntities = [EntityGroup[0], EntityGroup[1], EntityGroup[2]];
  local winnersDestination = EntityGroup[3];
  local losersDestination = EntityGroup[4];
  local teleportDestination = losersDestination;
  local winTeleport = EntityGroup[5];

  if (Winners < 3)
  {
    debugprint("Setting teleportDestination to podium " + (Winners+1));
    teleportDestination = teleportDesinationEntities[Winners];
  }
  else if (Winners < WinnersSettings)
  {
    debugprint("Setting teleportDestination to winners podium");
    teleportDestination = winnersDestination;
  }
  else
  {
    debugprint("Setting teleportDestination to losers podium");
  }
  debugprint("podium name: " + teleportDestination.GetName());
  EntFireByHandle(winTeleport, "SetRemoteDestination", teleportDestination.GetName(), 0.0, this, this);
}

function AddToWinners()
{
  Winners++;
  UpdateTeleports();
}

function ResetWinners()
{
  Winners = 0;
  UpdateTeleports();
}

function AddToWinnersSetting(toAdd)
{
  WinnersSettings += toAdd;
  if (WinnersSettings > MAX_WINNERS) WinnersSettings = MAX_WINNERS;
  if (WinnersSettings < MIN_WINNERS) WinnersSettings = MIN_WINNERS;

  if (WinnersSettingTT == null)
  {
    WinnersSettingTT = Entities.FindByName(null, "decathlon_winners_sign_tt");
  }

  EntFireByHandle(WinnersSettingTT, "SetTextureIndex", WinnersSettings.tostring(), 0.0, null, self);

  if (Winners > 0)
  {
    UpdateTeleports();
  }
}

Map Security

Both I/O and Scripts can be abused. It is the responsibility of each Game's Leadership to vet maps and ensure they do not contain malicious content.

Hammer Inaccuracies

The Hammer editor has some inaccuracies, specifically with coordinates. If you are using Stripper to target entities, you may need to adjust the coordinates. Use VIDE to get more accurate coordinates for entities. Similarly, if you are using Hammer to place brushes / entities, they will be offset by a few units.

Setting up Hammer

Pre-requisite

You must have either CS:Source 1.6 or CS:GO installed. (You may be able to run Hammer without both if you have a Source game installed).

  1. In Steam, type "SDK". Counter-Strike: Global Offensive - SDK (or Source SDK), should show up as a result, click Install and wait for it to download.
  2. Once installed, click "Play", you should see a window similar to the one below. Hammer3
  3. Click "Hammer World Editor", and Hammer will open. Hammer4
  4. To open a BSP file, click File -> Open (Ctrl + O) -> Navigate to your BSP file -> Open.
  5. Hammer should open the BSP file.

Decompiling

Source maps come in two formats, BSPs and VMFs. BSPs are compiled VMFs. You can use tools to decompile BSPs into VMFs, though there are some limitations and complications. The recommended tool to use to decompile BSPs is BSPSource.

Decompiling
  1. Download BSPSource from here.
  2. Unzip the contents, place them somewhere you want to keep the program. Screenshot
  3. Run bspsrc.bat, this should open up a BSPSource window. Screenshot
  4. Click "Add", this will open up a File Selection dialog.
  5. Navigate to the BSP you want to decompile, and then click "Open". Screenshot
  6. If you require the resources (images, scripts, sounds, etc.) in the BSP, click "Other". Screenshot
  7. Enable the "Extract embedded files" option, and then click "Decompile".
  8. Set the target destination where you wish, make sure it is an easy to access location. Screenshot
  9. If you extracted the embedded files, there will be a folder in the same location as the VMF.
  10. To make sure that Hammer can find these resources, you will want to copy them into your CS folder.

VIDE

VIDE is a separate decompiling tool that is used in conjunction with BSPSource. VIDE is more specific to entities and packing/unpacking resources.

Setting up VIDE

Pre-requisite

VIDE only works with BSPs. See Decompiling to generate one from a VMF.

  1. Download VIDE from here.
  2. Extract the files into a folder you want to keep the program.
  3. Run VIDE.exe, this should open up a VIDE window. VIDE1
  4. To decompile a BSP and get accurate entity coordinates, click "Entity Lump Editor". VIDE2
  5. A new smaller window will open within VIDE itself. Click File -> Open -> Navigate to your BSP -> Open. VIDE3
  6. Here, we have a list of all entities and brushes in the BSP. This list is what should be used for Stripper.

Stripper

Stripper is a tool used by EdgeGamers to quickly modify maps without having to decompile and recompile them. While limited, stripping maps is generally recommended over re-compiling them. You can view an in-depth Stripper guide here.

Stripper Seperators

Stripper uses invisible characters in I/O to separate parameters. If you are copying straight from VIDE (eg below), then this character will be automatically copied. If you are not copying, you will need to manually copy this character.

VIDE4

Removing Entities

To remove an entity, you can use the filter block.

filter: {
  classname: "func_button"
  targetname: "button_1"
}
If you do not have the targetname, you will need to find the coordinates using VIDE.
filter: {
  classname: "func_button"
  origin: "51 987 -50"
}

Adding Entities

To add an entity (eg. Spawnpoint) you can use the add block.

add: {
  classname: "info_player_terrorist"
  origin: "584 10 -56"
  angles: "180 270 0" 
}

Changing Entities

To modify an entity, you use the modify block combined with match, insert, delete, and replace.

Changing button's name
modify: {
  match: {
    classname: "func_button"
    targetname: "dk_s"
  }

  replace: {
    targetname: "deathcrate_start"
  }
}

Here we use replace because the entity already has a targetname, and we simply want to change it.

Adding an output to an entity
modify: {
  match: {
    origin: "587 -88 12"
    classname: "trigger_once"
  }

  insert: {
    OnStartTouch: "SLP_Relay7CancelPending0-1"
  }
}

Here we use insert to insert an additional output to the entity. Note that it is hard to separate the targetname, method, delay, and max times due to the invisible character.

Changing the outpot of an entity
modify: {
  match: {
  "origin" "-1768 864 344"
  "classname" "logic_auto"
  }
  delete: { 
  "OnMapSpawn" "cell_button1Unlock20-1"
  "OnMapSpawn" "cell_button2Enable20-1"
  }
  insert: {
  "OnMultiNewRound" "cell_button1Lock5-1"
  "OnMultiNewRound" "cell_button2Disable5-1"
  "OnMultiNewRound" "cell_button1Unlock20-1"
  "OnMultiNewRound" "cell_button2Enable20-1"
  }
}

Since there can be multiple outputs to the same input, we need to delete all the OnMapSpawns and insert multiple OnMultiNewRounds.

Changing where a player is looking when teleported
modify: {
  match: {
  "classname" "info_player_terrorist"
  }
  replace: {
  "angles" "0 270 0"
  }
}

Depending on the type of teleport, you may want to keep where the player was looking at before being teleported. You would want to set UseLandmarkAngles to 1 to achieve this behavior.

Modifying the size of a trigger

Trigger

Minimap (TODO)

Testing Maps

Map testing is an important part of most servers development. Testing a map both locally and on a development server is important to ensure server stability and fun gameplay.

Testing Locally

  1. Download the map or subscribe to it from the Steam Workshop
  2. If you downloaded the map, move it to your csgo/maps folder.
  3. Open CS:GO
  4. If you downloaded the map, open up console and then type map [map name]
  5. Otherwise, click on Play > Workshop Maps > Select the map.

Testing on Dev

  1. The map must be on the Steam Workshop
  2. Open the Steam Workshop link (eg: https://steamcommunity.com/sharedfiles/filedetails/?id=252961216)
  3. Copy the ID that is at the end of the URL (252961216)
  4. Go into the dev server
  5. Open up console
  6. Type sm_rcon host_workshop_map [ID] (eg: sm_rcon host_workshop_map 252961216)

Things to look for

  • Basics
    • Minimap - All maps should have a minimap.
    • HDR - All maps should have HDR enabled (type mat_hdr_enabled in console to check).
  • Gameplay
    • Make sure the gameplay is sufficient. Things like balance, appeal, player spawns, decor, etc.
    • If relevant, validate any secrets work as intended (and do not cause server issues).
  • Malicious Content
    • Decompiling the map is strongly encouraged, as it is very easy to hide inappropriate or malicious content in a map.
    • Ensure all map images, VScripts, brushes, messages, etc. follow our Code of Conduct and are not malicious.

Adding Maps

Adding maps involves uploading the map (unless the map is already hosted publicly), making an issue on our GitLab, and updating a few config files.

  1. Upload the BSP of the map (or get a link to the map). We do not need a NAV or VMF.
  2. Create an issue in the corresponding server's gitlab. (Eg: Jailbreak Maps go in the Jailbreak repo, Surf goes in surf, etc.)
    1. Make sure you include the map's download URL.
  3. Once the map has been uploaded (tech will respond):
    1. Update the mapcycle.txt that can be found in the cfg folder (eg: jailbreak/cfg/mapcycle.txt).
    2. If you are on surf or KZ, add the map (and its tier) to setup/umc_mapcycle.txt
  4. Make sure you create a stripper file for the map, following the basic format.