Maphack Fundamentals

From Valve Developer Community
Jump to: navigation, search

Maphack is a system implemented into both No More Room in Hell and Pirates, Vikings, and Knights II by Felis that allows the creation and manipulation of entities at runtime. Using this system you can create alternative versions of maps as well as patch them without the need to recompile the map. Maphack uses the .txt file extension.

Maphack is NOT a scripting language and it should not be considered a VScript alternative or a fully fledged language.

Maphack scripts are data driven and the scripts are nodes of keys and values that the system traverses through recursively. The function keys starting with $ tell the parser that a function should be run and sends the nodes to the function that should handle it.

Examples from No More Room in Hell can be found here and examples for community maps can be found here.

The structure of a Maphack

All Maphack scripts must begin with a root key named "Maphack" (case insensitive)

"MapHack"
{
  // Rest of script goes in here
}

Includes

Directly following this is the includes key which contains all other Maphack scripts you wish for your Maphack script to use. This allows for easy reuse of commonly used scripts as well as to build on top of other scripts created for a map.

All file locations are started in the gamedir location (gameinfo.txt) and there is no support for relative pathing. The standard is to keep your maphacks in maps/maphacks/ but you can store them anywhere technically under the main gamedir.

"MapHack"
{
	"includes"
	{
		"file"	"maps/maphacks/challenges/include/nmo_broadway_blitz_shared.txt"
		"file"	"maps/maphacks/challenges/nmo_broadway_challenge_1.txt"
	}
}

Precache

If you are introducing new assets to the map you should precache them. Available keys are:

  • model
Accepts a file path to a model (.mdl)
  • material
Accepts a file path to a material(.vmt)
  • sound
Accepts a file path to a wav/mp3 or a soundscript name (Pain.FemaleDefault)
  • particle
Accepts a name that is defined in the PCF
  • entity
Accepts a classname and will call both CBaseEntity::SetModel() and CBaseEntity:Precache() on the provided classname. Normally you do not need this entry as Maphack will precache them for you when added by normal means.
"MapHack"
{
	"precache"
	{
		"model" "models/props_wasteland/dockplank01b.mdl"
		"model" "models/props/street/police_barricade_4.mdl"
		"model" "models/weapons/tool_barricade/w_barricadeboard.mdl"
		"model" "models/props_wasteland/cargo_container01.mdl"
                "sound" "ambient/radio/radio_internet_blava.wav"
                "sound"	 "Pain.FemaleDefault"
	}
}

Variables

Maphack allows you to create variables that you can both set and call in your scripts. Variables are declared near the top of the Maphack script in the "vars" section. Valid Types are:

  • int
  • float
  • color
  • string
"vars"
{
    "<VariableName>" { "type" "<type>" "value" "<DefaultValue>" }
}

Variables can be accessed in your script using %<VariableName>.

$setpos { "targetname" "test_melon" "value" "%vecPos"}

Variables can also be later $set, $increment, $decrement, and $rand

"entities"
{
  // Set variable
  // Keys:
  // "var" - Variable to set
  // "value" - Value to set
  $set { "var" "iTest" "value" "2" }

  // Increment variable
  // Keys:
  // "var" - Variable to increment
  $increment { "var" "iTest" }

  // Decrement variable
  // Keys:
  // "var" - Variable to decrement
  $decrement { "var" "iTest" }

  // Set a variable to a random value (also test maphack var ref)
  // Keys:
  // "var" - Chosen variable
  // "rand_min" - Min random value
  // "rand_max" - Max random value
  $rand { "var" "iTest" "rand_min" "%randMin" "rand_max" "%randMax" }
}

Events

Events are used to create inputs and outputs and go in the "events" section.

Note.pngNote:Note you can't name events using reserved keynames

Event Types can be:

  • EVENT_TRIGGER
  • EVENT_TIMED
  • EVENT_OUTPUT
  • EVENT_GAMEEVENT

Events are structured like:

"events"
{
  "<EventName>"
  {
    "type"	        "<EventType>"
    "delay"             "<Seconds in Float>
    "repeat"	        "<1 or 0>"
    "startDisabled"	"<1 or 0>"
    "targetname"	"<TargetName>"
    "output"            "<OutputEvent>"
  }
}

Example:

"events"
{
  // MAPHACK_EVENT_TRIGGER
  "EventTrigger"
  {
    "type"  "EVENT_TRIGGER"
  }
  
  // MAPHACK_EVENT_TIMED
  "EventTimed"
  {
    "type"  "EVENT_TIMED"
    "delay"  "2.0"
    "repeat"  "1"
    "startDisabled"  "1"
  }
  
  // MAPHACK_EVENT_OUTPUT
  "EventOutput"
  {
    "type"  "EVENT_OUTPUT"
    "targetname"  "test_melon"
    "output"  "OnAwakened"
  }
  
  // MAPHACK_EVENT_GAMEEVENT
  "EventGameEvent"
  {
    "type"  "EVENT_GAMEEVENT"
    "eventname"  "player_spawn"
  }
  
  // You can't name events using reserved keynames!
  "entities"
  {
    "type"  "EVENT_TRIGGER"
  }
}

"entities"
{
  // Trigger a MapHack event
  // Keys:
  // "event" - Event name
  $trigger { "event" "EventTrigger" }
  
  // Start a timed MapHack event (stops in EventTimed)
  // Keys:
  // "event" - Event name
  $start { "event" "EventTimed" }
}

// Events go here
"entities:EventTrigger" // Can specify a label type with a prefix...
{
  $console { "msg" "EventTrigger" }
  $trigger { "event" "EventUnregistered" }
}

"EventTimed" // ...or have none (defaults to entities field)
{
  $console { "msg" "%timedEventTriggered" }
  $increment { "var" "timedEventTriggered" }
  $if { "cond" "timedEventTriggered == 5"
    "entities"
    {
      // Stop a timed MapHack event
      $console { "msg" "EventTimed: Stop" }
      $stop { "event" "EventTimed" }
    }
  }
}

"EventOutput"
{
  $console { "msg" "EventOutput" }
}

"EventGameEvent"
{
  $console { "msg" "EventGameEvent" }
}

"EventUnregistered" // MapHack allows unregistered events, uses default properties (trigger)
{
  $console { "msg" "EventUnregistered" }
}

"EventRecursiveTest" // We got a safety net for this, don't go too far
{
  $console { "msg" "%recursion" }
  $increment { "var" "recursion" }
  $trigger { "event" "EventRecursiveTest" }
}

Example from nmo_toxeth_blitz_shared.txt

"MapHack"
{
  "events"
  {
    "SpawnSecondTelephone"
    {
      "type" "EVENT_OUTPUT"
      "targetname" "boundary_objI"
      "output" "OnObjectiveBegin"
    }
  }
  
  // Recreate second telephone to skip the speech
  "SpawnSecondTelephone"
  {
    "func_button"
    {
      "origin" "-850 -2049 139.51"
      "targetname" "toxteth_telephone_2nd"
      "keyvalues"
      {
        "health" "0"
        "lip" "0"
        "movedir" "0 0 0"
        "sounds" "0"
        "spawnflags" "1025"
        "speed" "0"
        "wait" "-1"
        
        "connections"
        {
          "OnPressed" "toxteth_objb_phonesound,StopSound,,0,1"
          "OnPressed" "boundary_objI,ObjectiveComplete,,0,-1"
          "OnPressed" "toxteth_phoneline_obji,PickRandom,,0,-1"
        }
      }
    }
  }
}

Pre-Entities

The Pre-Entity section runs on level initialization (LevelInit) and directly modifies the entdata buffer. Use this for stripping out entities before the map loads or to change a classname.

Note.pngNote:this is completely seperate from maphack's runtime entities field, events can not be triggered, started or stopped here

The only available function keys are: $edit, $edit_all, $modify, $remove, $remove_all, $if, $set, $increment, $decrement, $rand

"pre_entities"
  {
    // Creating an entity here plops it at the back of entdata buffer
    "prop_physics"
    {
      "targetname"  "test_melon"
      "origin"  "0 0 0"
      "angles"  "0 0 0"
      "keyvalues"
      {
        "model"      "models/props_junk/watermelon01.mdl"
        "rendercolor"  "%clrTest"
      }
    }
    
    // These edit entdata directly
    $edit
    {
      "targetname"  "test_melon"
      "keyvalues"
      {
        "modelscale"    "4.0"
      }
    }
    
    // Example, change sky name
    $edit_all
    {
      "classname" "worldspawn"
      "keyvalues"
      {
        "skyname" "sky_borealis01"
      }
    }
    
    // Another example, turn all props into zombies
    $edit_all
    {
      "classname" "prop_physics"
      "keyvalues"
      {
        "classname"  "npc_nmrih_shamblerzombie"
      }
    }
    
    // Test $modify
    $modify
    {
      "insert"
      {
        "disableshadows" "1"
      }
      
      "match"
      {
        "classname" "npc_nmrih_shamblerzombie"
      }
      
      "replace"
      {
        "rendercolor" "0 0 0"
      }
      
      "delete"
      {
        "rendercolor" "0 0 0"
      }
    }
    
    $remove_all
    {
      "classname" "random_spawner"
    }
    
    // Control flow also works in pre-entity
    $if
    {
      "cond" "pre_iTest == 69"
      "entities" // bit ambiguous, entities field here is run here as if it's "pre_entities"
      {
        $edit_all
        {
          "classname" "prop_physics"
          "keyvalues"
          {
            "modelscale" "4.0"
          }
        }
      }
    }
    
    $set { "var" "pre_iTest" "value" "2" }
    $increment { "var" "pre_iTest" }
    $decrement { "var" "pre_iTest" }
    $rand { "var" "pre_iTest" "rand_min" "%randMin" "rand_max" "%randMax" }
  }

Entities

The entity section is where most changes are done.

Available actions are:

  • $set
Sets a variable
  • $increment
Increments a variable
  • $decrement
Decrements a variable
  • $rand
Sets a variable to a random value between the min and max provided
  • classname
You can pass in a classname to create an entity of that class
  • $if
Checks a variable condition and if it passes then executes the fields as if it was another entities field
  • $console
Send a command to console, send a message to the console, or send a warning to the console
  • $fire
Fires an input
  • $edit
Set the KeyValues of an existing entity, can target using targetname or id
  • $edit_all
Set the KeyValues for all existing entities of the given classname
  • $edit_field
Edit the entity DATADESC fields
  • $modify
Advanced KeyValue manipulation, allows you to do searches based on a provided KeyValue pair and then replace/insert/delete the matched entities. You can also use keyvalues to set KeyValues the same way you would with $edit
  • $getpos
Gets the entity original and assigns it to a variable
  • $getang
Gets the entity angle and assigns it to a variable
  • $trigger
Triggers a Maphack event
  • $start
Starts a timed Maphack event
  • $respawn
Respawns an entity
  • $remove_connections
Removes all IO from an entity based on targetname or ID
  • $remove
Removes an entity based on targetname or ID
  • $remove_all
Removes all entities for the provided classname
  • $playsound
Emits a sound
"entities"
  {
    // Spawn a test melon somewhere
    "prop_physics"
    {
      "targetname"  "test_melon"
      "origin"  "0 0 0"
      "angles"  "0 0 0"
      "keyvalues"
      {
        "model"      "models/props_junk/watermelon01.mdl"
        "rendercolor"  "%clrTest" // '%' = reference a MapHacks variable
      }
    }
    
    // Spawn another test melon somewhere but this time in multiplayer
    "prop_physics_multiplayer"
    {
      "targetname"  "test_melon2"
      "origin"  "0 0 0"
      "angles"  "0 0 0"
      "keyvalues"
      {
        "model"      "models/props_junk/watermelon01.mdl"
        "rendercolor"  "%clrTest"
      }
    }
    
    // Input test
    "env_explosion"
    {
      "targetname" "explosion"
      "origin" "0 0 0"
      "angles" "0 0 0"
      "keyvalues"
      {
        "magnitude"  "1337"
      }
    }

    // Check for variable condition
    // Keys:
    // "cond" - Condition to test, uses C style operators
    // "entities" - Entities field to run if test passes
    $if
    {
      "cond" "iTest == 42"
      "entities" // this field will be run as if it was another entities field, it can be used for control flow eg. by nesting more $ifs
      {
        // all function keys will also work here!
        $console { "warning" "$if test passed" }
      }
    }
    
    // Set variable
    // Keys:
    // "var" - Variable to set
    // "value" - Value to set
    $set { "var" "iTest" "value" "2" }
    
    // Increment variable
    // Keys:
    // "var" - Variable to increment
    $increment { "var" "iTest" }
    
    // Decrement variable
    // Keys:
    // "var" - Variable to decrement
    $decrement { "var" "iTest" }
    
    // Set a variable to a random value (also test maphack var ref)
    // Keys:
    // "var" - Chosen variable
    // "rand_min" - Min random value
    // "rand_max" - Max random value
    $rand { "var" "iTest" "rand_min" "%randMin" "rand_max" "%randMax" }
    
    // Send a command to console, or debug spew
    // Keys:
    // "cmd" - Send a console command
    // "msg" - Send console spew
    // "warning" - Send console warning
    $console { "cmd" "echo cmd" }
    $console { "msg" "$console msg" }
    $console { "warning" "$console warning" }
    
    // Fire an input
    // Keys:
    // "targetname" - Target an entity by name
    // "id" - Target an entity by Hammer ID
    // "input" - Input to fire
    // "value" - Value to set
    // "type" - Type override (available types: int, float, string)
    $fire { "targetname" "test_melon" "input" "Sleep" }
    $fire { "targetname" "test_melon" "input" "Wake" }
    $fire { "targetname" "explosion" "input" "Explode" }
    
    // Set KeyValues for existing entity
    // Keys:
    // "targetname" - Target an entity by name
    // "id" - Target an entity by Hammer ID
    // "keyvalues" - KeyValues to set, you can insert new pairs
    $edit
    {
      "targetname"  "test_melon"
      "keyvalues"
      {
        "modelscale"    "4.0"
      }
    }
    
    // Set KeyValues for ALL existing entities
    // Keys:
    // "classname" - Target all entities by classname
    // "keyvalues" - KeyValues to set, you can insert new pairs
    $edit_all
    {
      "classname"  "prop_physics"
      "keyvalues"
      {
        "modelscale"    "4.0"
      }
    }
    
    // Edit entity datadesc fields
    // Keys:
    // "targetname" - Target an entity by name
    // "id" - Target an entity by Hammer ID
    // "fieldname" - Field to change
    // "value" - Value to change on targeted field (type is determined automatically)
    $edit_field
    {
      "targetname"  "test_melon"
      "fieldname"  "m_clrRender"
      "value"  "123 123 123"
    }
    
    // Advanced key value manipulation ($edit_all on crack), inspired by Stripper:Source's "modify"
    // Keys:
    // "match" - Match entities by these keys and values
    // "replace" - Replace values on matched entities
    // "insert" - Insert new keyvalues on matched entities
    // "delete" - Remove existing keyvalues on matched entities
    // "keyvalues" - Traditional keyvalues block ($edit) on matched entities
    $modify
    {
      "match"
      {
        "classname" "npc_nmrih_shamblerzombie"
      }
      
      "replace"
      {
        "rendercolor" "0 0 0"
      }
      
      "insert"
      {
        "modelscale" "4.0"
      }
      
      "delete"
      {
        "modelscale" "4.0"
      }
      
      "keyvalues"
      {
        "rendercolor" "0 0 0"
        "modelscale" "4.0"
      }
    }

    // Get entity origin & angles, assigns it to variables
    // Keys:
    // "targetname" - Target an entity by name
    // "id" - Target an entity by Hammer ID
    // "var" - Variable to assign
    $getpos { "targetname" "test_melon" "var" "vecPos"}
    $getang { "targetname" "test_melon" "var" "vecAng"}
    
    // Set entity origin & angles
    // Keys:
    // "targetname" - Target an entity by name
    // "id" - Target an entity by Hammer ID
    // "value" - Value to set
    $setpos { "targetname" "test_melon" "value" "%vecPos"}
    $setang { "targetname" "test_melon" "value" "%vecAng"}
    
    // Trigger a MapHack event
    // Keys:
    // "event" - Event name
    $trigger { "event" "EventTrigger" }
    
    // Start a timed MapHack event (stops in EventTimed)
    // Keys:
    // "event" - Event name
    $start { "event" "EventTimed" }
    
    // Respawn an entity
    // Keys:
    // "targetname" - Target an entity by name
    // "id" - Target an entity by Hammer ID
    $respawn { "targetname" "test_melon" }
    
    // Remove connections
    // Keys:
    // "targetname" - Target an entity by name
    // "id" - Target an entity by Hammer ID
    $remove_connections { "targetname" "test_melon" }
    
    // Remove an entity
    // Keys:
    // "targetname" - Target an entity by name
    // "id" - Target an entity by Hammer ID
    $remove { "targetname" "test_melon" }
    
    // Remove all named entities
    // Keys:
    // "classname" - Target all entities by classname
    $remove_all { "classname" "prop_physics" }
    
    // Emits a sound
    // Keys:
    // "name" - Sound name
    // "source" - Entity sound source (plays globally by default)
    $playsound { "name" "Pain.FemaleDefault" }
  }

Commands

  • maphack_load <string>
Load Maphack file by name
  • maphack_include <string>
Include a file by name into existing Maphack
  • maphack_reload
Reload current Maphack
  • maphack_trigger <string>
Trigger a Maphack event
  • maphack_dump_vars
Dump Maphack variables to console
  • sv_maphack <1 or 0>
Enable Maphack
  • sv_maphack_filename <string>
If not empty, load this file for Maphack
  • sv_maphack_allow_servercommand <1 or 0>
Allow $console function to execute server commands