Maphack Fundamentals
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.
Contents
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()
andCBaseEntity: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.
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.
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
orid
- $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 usekeyvalues
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