dismhook Lua API

The complete reference for scripting dismhook: every function, every event, every field of every table, the rules the runtime enforces, and ready-to-run examples.

59functions
22events
927settings you can read and write
10tested examples
5.4.7Lua version
2.0API version
Nothing matches your search. Try a function name (draw.text), an event (player_hurt), a field (bones) or a setting (m_radar).

Overview#

dismhook runs Lua scripts next to the overlay. A script can read what the program already knows about the game (players, bones, grenades, the bomb, the camera), draw on the overlay, read and change the program's own settings, keep data between sessions, and, if you ask it to, press keys for you.

Everything a script can do goes through one global table called dismhook. This page documents every function, every event, every field of every table a script receives, the rules the runtime enforces, and ready-to-run examples.

LanguageLua 5.4.7, the one built into the program
API version2.0 (dismhook.api.major and dismhook.api.minor); a script folder's manifest may name api_version: 2
Where scripts liveDocuments\dh-<username>\scripts
Turned on withScripts tab: Enable Lua API, then Run on the script
Written inthe editor built into the Scripts tab (see The script editor), or any editor you like
Each script getsits own Lua state and its own thread, so scripts cannot see each other's variables and a slow one cannot stall the overlay

Quick start#

  1. Open the menu and go to the Scripts tab. Tick Enable Lua API. It is off by default, and nothing runs while it is off.
  2. Type hello in the Script name box and press Create (or Enter). The script is made in Documents\dh-<username>\scripts and opens in the editor.
  3. Select everything in the editor (Ctrl+A) and type or paste the script below. Press Save (or Ctrl+S).
  4. Press Run on hello in the list. The state next to it goes Starting, then Running. (Run is greyed out while Enable Lua API is off.)
  5. Start a game. A panel with a blue title bar appears at the left of the screen. (It stays there with the menu shut.)
  6. Change the text in the editor and press Save. The running script is replaced within about a quarter of a second, no need to stop it.

You can also write scripts in any other editor: a .lua file copied into Documents\dh-<username>\scripts shows up in the list after Refresh list.

hello.lua
-- hello.lua
dismhook.log("hello.lua loaded (API " .. dismhook.api.major .. "." .. dismhook.api.minor .. ")")

dismhook.events.on("frame", function(frame)
    local map = frame.map ~= "" and frame.map or "no map"
    dismhook.draw.panel(20, 200, 260, 64, "Hello from Lua", 90, 160, 255, 255)
    dismhook.draw.text(30, 226, string.format("%s  |  %d players", map, #frame.players), 235, 235, 240, 255)
end)

When something does not work, go to Troubleshooting. A script that raises an error shows Error in the list (hover it for the message), and the message is also written to Documents\dh-<username>\scripts\.logs\<script>.log.

The script editor#

The Scripts tab is laid out like the Configs tab: a name box with a button, and a list with buttons on every row. You do not need another program to write a script: Create makes the file and opens it in the editor that is built in.

ButtonWhat it does
Createmakes <name>.lua in Documents\dh-<username>\scripts from a small starter script (it logs a line and draws the number of players), then opens it in the editor. Enter in the name box does the same. A name that is already taken, or that Windows cannot use for a file, is refused with a message and nothing is overwritten
Run / Stopstarts or stops the script. Run is greyed out while Enable Lua API is off
Editopens the script in the editor
Deleteremoves the script after a confirmation (see Enabling, reloading, deleting)
Refresh listreads the folder again; use it after copying a .lua file in by hand

Names#

The name you type is the file name. Capitals, spaces, dots and letters of any language stay exactly as typed: only what Windows does not allow in a file name is refused as you type (\ / : * ? " < > |), and spaces at the start and spaces or dots at the end are dropped. A typed .lua is not doubled, and the names Windows keeps for devices (CON, NUL, COM1 ...) are refused. My Script.v2 becomes the file My Script.v2.lua, whose id is My_Script_v2 (see Single-file scripts). The box takes up to 63 bytes. Config names follow the same rules.

The editor window#

It is a code editor, not a text box: Lua is coloured as you type, every line is numbered, and the usual editing keys do what they do in any other code editor.

  • Save (or Ctrl+S) writes the file at once, in one step, so a reload never sees half a script. A running script reloads by itself within about a quarter of a second; a script that has stopped stays stopped. If the file cannot be written the editor says so and keeps your text.
  • The title shows a * while there is something to save. Save and Revert are greyed out until there is. Revert puts back the text in the file, and Close (or the window's X) shuts the editor; both ask first when they would throw changes away.
  • One script is open at a time. Save or close it before opening another.
  • A script in the Error state shows its message in red under the text. The line it names is marked in red (in the margin and across the line, with the message when you hover it), and Go to line N puts the cursor there. Fix it and save: it reloads by itself.
  • The bar under the text shows the line and column of the cursor, how many lines the script has, and how much is selected. If the script is deleted while it is open, the editor closes.

What you see#

ColourWhat
bluelocal, function, and, or, not, in, and nil, true, false
purpleif, then, else, elseif, for, while, do, repeat, until, return, break, goto, end
yellowthe name of a function that is defined or called
tealLua's own functions and libraries: print, pairs, math.floor, string.format ...
light bluedismhook and the names directly under it: dismhook.draw, dismhook.events, dismhook.api.major ...
orange, light green, greenstrings (long [[ ... ]] strings across lines too), numbers, comments (-- and --[[ ... ]])

The current line is lightly lit, the bracket next to the cursor and its partner are outlined, and the text you are looking for in the find bar is marked everywhere it appears.

Keys and mouse#

KeysWhat they do
Arrows, Home, End, Page Up, Page Downmove; with Shift they select. Ctrl with Left and Right moves a word at a time, with Home and End goes to the start or end of the script. Home goes to the first character of the line, and to the very start when pressed again
Ctrl+A, Ctrl+C, Ctrl+X, Ctrl+Vselect all, copy, cut and paste, through the Windows clipboard. With nothing selected, copy and cut take the whole line
Ctrl+Z, Ctrl+Y (or Ctrl+Shift+Z)undo and redo. Typing is undone a word at a time; every other edit is one step
Backspace, Deletedelete. With Ctrl they delete a word. Backspace in the indentation goes back a tab stop, and on an empty pair such as () takes both
Tab, Shift+TabTab types spaces up to the next multiple of four (never a tab character); with several lines selected it indents them all. Shift+Tab takes one level off
Enterkeeps the indentation of the line above, one level deeper after a line that opens a block (function, then, do, repeat, else, {), and splits a pair such as {|} over three lines
Ctrl+/comments or uncomments the lines touched
Ctrl+D, Alt+Up, Alt+Down, Ctrl+Shift+Kduplicate, move up, move down and delete the lines touched
Ctrl+F, Ctrl+H, F3, Shift+F3find; find and replace; next and previous match. In the find box, Enter and Shift+Enter do the same. Match case narrows it, Replace replaces the match and moves on, All replaces every match as one step you can undo
click, drag, double click, triple clickplace the cursor, select, select a word, select a line. A click on a line number selects the line. Shift+click extends the selection. The wheel scrolls, and dragging past an edge scrolls with it

Typing helps#

  • (, [, {, " and ' come with their partner, and typing the partner steps over it. They do not pair inside a string or a comment, after a letter (an apostrophe is not a quote), or in front of a word. Typed over a selection they wrap it.
  • end, else, elseif, until or } typed alone on an indented line goes back one level (and comes back if the word goes on: endpoint). Words inside quotes and comments never count as keywords.
  • None of it touches text that is pasted.

What the editor does not do#

  • No completion, no code folding, no word wrap, and no other tabs: one script at a time.
  • It opens files up to 1 MiB, and only text: a file with a NUL byte is refused. The text is read as UTF-8; a byte order mark is dropped and every line ending becomes \n when you save. Lua does not mind; keep using your own editor for a script that must keep \r\n.
  • Escape does nothing in the editor.
  • It only edits a script's entry file. A folder script's modules are edited in your own editor.

Scripts, folders and modules#

Everything lives under Documents\dh-<username>, where <username> is the name of the dismhook account you signed in with, in lowercase. It is inside the Windows Documents folder; if yours is redirected, for example to OneDrive, that is where it is.

folder layout
Documents\dh-<username>\
    scripts\
        hello.lua            a single-file script (id "hello")
        kill_tracker\        a folder script
            manifest.json
            main.lua
            lib\util.lua
        workspace\           shared modules, found by require() from every script
            common.lua
        .data\<id>\         a script's own data: store.json, pictures it exported
        .logs\<id>.log       the errors a script raised

Single-file scripts#

Any .lua file placed directly in scripts is a script. Its id is the file name without .lua, with every character that is not a letter, digit, _ or - replaced by _. Its name in the list is the file name. The script's own folder (dismhook.api.script_dir) is scripts itself.

Folder scripts and manifest.json#

A sub-folder of scripts that contains a manifest.json is one script. Sub-folders without a manifest are ignored. The manifest is plain JSON; every field is optional:

FieldDefaultMeaning
idthe folder nameunique id (cleaned like a file name); also names the .data folder and the log
namethe idthe name shown in the Scripts tab
version"1.0"free text
authoremptyfree text
descriptionemptyfree text
api_version2the API major version the script was written for. Any other value makes the script fail to start with "unsupported Lua API major version N"
entry"main.lua"the file that is run, relative to the folder. Must exist, otherwise the folder is ignored
manifest.json
{
    "id": "kill_tracker",
    "name": "Kill tracker",
    "version": "1.2.0",
    "author": "you",
    "description": "Counts the kills you confirm, and how long the map has been running",
    "api_version": 2,
    "entry": "main.lua"
}

If two scripts end up with the same id, the first one found is used and the other is ignored. The list is sorted by name.

Modules: require()#

require("name") looks for, in this order:

  1. <the script's folder>\name.lua
  2. <the script's folder>\name\init.lua
  3. scripts\workspace\name.lua
  4. scripts\workspace\name\init.lua

A dot in the name is a folder separator, so require("lib.util") loads lib\util.lua. C libraries cannot be loaded (package.cpath is empty and package.loadlib is removed). A module is loaded once per script and cached, like in any Lua program.

a module
-- kill_tracker\main.lua
local util = require("lib.util")            -- kill_tracker\lib\util.lua
print(util.clock(75.4))                       --> 1:15

-- kill_tracker\lib\util.lua
local util = {}
function util.clock(seconds)
    seconds = math.max(0, math.floor(seconds))
    return string.format("%d:%02d", seconds // 60, seconds % 60)
end
return util

Enabling, reloading, deleting#

  • Which scripts are running is not remembered. After a restart every script is off again. The master switch Enable Lua API is part of the config profile (misc.lua_enabled).
  • Reload happens by itself. While a script is running (or in Error), the runtime looks at its entry file every 250 ms; when its modification time changes it reloads. Saving in the built-in editor counts. Only the entry file is watched: after editing a module, save the entry file too (or press Stop and Run).
  • A bad edit never takes the script down. The new version is first run on a scratch Lua state. If that fails, the old version keeps running and the Scripts tab shows the error. If it works, unload runs in the old version, the states are swapped, and load runs in the new one.
  • While a reload is being validated, config.set, config.patch, helpers.start, every input.* call and storage.set do nothing (they return false or skip saving), so the trial run cannot have side effects.
  • Delete (in the Scripts tab, with a confirmation) removes the .lua file, or the whole folder for a folder script. The script's .data folder is kept.
  • Create (in the Scripts tab) makes a new .lua file in scripts and opens it in the editor; it never replaces a file that is there.

How scripts run#

The loop#

Each enabled script has one thread (at below-normal priority) and one Lua state. About every 16 ms it does this, in order:

  1. applies a pending reload;
  2. raises the tick event (argument 0.016);
  3. if the overlay has published a new frame since the last pass, raises the events that frame implies (map_changed, player_hurt, bomb_planted, ...), then frame;
  4. raises config_changed if a script changed the config in the meantime.

Your callbacks run one at a time on that thread, so a script never has to worry about two of its own handlers running together.

Frames and the game data#

The overlay's render thread publishes a new frame (see Frame) at most every 16 ms, and only while Lua is enabled and at least one script is running. A frame is a consistent snapshot: the camera, the local player, and the lists of players, items, projectiles and spectators, and the bomb.

The program does not collect data nobody wants. What it collects depends on what the running scripts asked for:

A script asks for it byThe program collects
events.on("frame", ...) or game.snapshot()players, items, projectiles, spectators, bomb
events.on("player_..."), "weapon_changed" or "confirmed_hit"players
events.on("projectile_...")projectiles
events.on("bomb_...")the bomb
game.radar_snapshot()players, items, projectiles, bomb

Drawing#

dismhook.draw.* calls do not paint anything by themselves. They append to a list. The list is cleared at the start of each frame pass, and published at the end of it; the overlay then draws the published list on every render frame until the next one replaces it. So:

  • Draw from the frame handler. Handlers of the events raised in the same pass just before it (player_hurt, bomb_planted, ...) also count.
  • Calls made from tick, load, or at the top of the file are discarded (or, if no frame handler exists at all, never shown).
  • Because the list is only replaced when a new frame arrives, what you draw moves at most about 60 times a second, however fast the overlay renders.
  • Each script may append up to 8192 commands per pass; the next one raises "draw command limit exceeded".

Time limits#

Every callback, including the top-level code of the file when the script starts, has 8 ms of wall-clock time. The runtime checks after every 10,000 Lua instructions and raises the error "dismhook Lua callback exceeded its execution budget". A single long call into the program (for example game.radar_overview, or a file read) is not interrupted, but the time it takes counts.

Errors and states#

StateMeaning
Stoppednot started, or stopped
Startingthe thread is starting and the file is being loaded
Runningevents are being delivered
Errorthe file did not load, or a callback raised an error (including the time limit). No more events are delivered. The state is shown in red, the message when you hover it (and under the text in the editor, with a Go to line button), and it is appended to .logs\<id>.log. Save the file to reload it, or press Stop and Run
Over budgetthe script tried to use more than 64 MiB of memory (or its Lua state could not be created)

An error in one handler stops the remaining handlers of that event and puts the whole script into Error. Wrap risky code in pcall if you would rather log it and carry on.

Stopping#

Pressing Stop, turning off Enable Lua API, deleting the script, or closing the program raises unload, lets go of every key and mouse button the script is holding, and saves the script's storage.

The Lua environment#

Each script sees standard Lua 5.4 plus the dismhook table.

LibraryAvailableNotes
baseyesprint, pairs, pcall, require, load, loadfile, dofile, ... print writes to standard output, which nobody sees in a normal run; use dismhook.log or draw text
packageyesrequire works for .lua files (see Modules); C loaders are removed
coroutine, table, string, math, utf8yesunchanged
io, osyes, unrestrictedfiles, os.execute, io.popen, os.remove ... all work with the rights of the program
debugnonot opened

Numbers#

  • Lua 5.4 has integers and floats. Counts, handles, ids and steam_id64 arrive as integers; positions, times and angles as floats.
  • Functions that ask for an integer (colour channels, virtual keys, mouse deltas, handles) accept 127 and 127.0, but raise an error for 127.5. Use math.floor or math.tointeger.
  • Colour channels outside 0 to 255 are clamped; missing ones default to 255.

Coordinates#

  • Screen coordinates are pixels of the overlay, which covers the game window: (0, 0) is the top-left, x grows to the right, y downwards. frame.screen has the size.
  • World coordinates are the game's units. Angles are degrees: x is pitch, y is yaw, z is roll.
  • dismhook.game.world_to_screen turns the second into the first.

Logging#

dismhook.log(text) writes [lua:<id>] text to the program's diagnostic console. That console only exists in the Debug build; in a Release build the message goes nowhere. Errors are different: they are always shown in the Scripts tab and written to .logs\<id>.log. If you need to keep messages in a Release build, append them to a file yourself with io.open(path, "a"), or draw them.

Limits#

WhatLimitWhat happens beyond it
Time per callback (and the file's top level)8 mserror "callback exceeded its execution budget"; the script goes to Error
Memory per script64 MiBallocation fails; the script goes to Over budget
Draw commands per frame pass8192error "draw command limit exceeded"
UI controls per script512error "UI control limit exceeded"
Frame rate of the data and of what you drawabout 60 per second (one per 16 ms)nothing is lost, you simply get the newest frame
Textures64 in total, across all scripts; 16 MiB per file; up to 4096 x 4096 pixelsdraw.load_texture returns nil and a message for the count, the file size and the format. A picture larger than 4096 x 4096 is accepted but never shown
Texture formatspng, jpg, jpeg, bmp"unsupported texture format"
Helper (PowerShell) arguments32 arguments, each up to 2048 charactersextra arguments are dropped; a longer one raises an error
Script opened in the editor1 MiB, and text onlythe editor refuses to open it; the script still runs
Nesting depth of tables passed to storage and config32 levelsthe call returns false
Update rate of the settings snapshotat least every 100 msconfig.get can be up to 100 ms behind a change made in the menu

Troubleshooting#

ProblemCause and fix
The script is not in the listIt must be a .lua file directly in scripts, or a folder with a manifest.json whose entry exists. Press Refresh list. A second script with the same id is ignored.
Run is greyed outEnable Lua API is off. Tick it first.
State says ErrorHover it for the message. Common ones: a syntax error (the line number is in the message), calling a function with the wrong kind of argument, or "callback exceeded its execution budget". Fix the file and save it: it reloads by itself.
Nothing is drawnDraw calls only work from the frame handler (see Drawing). Also check that a game is running, the overlay is visible, and that you registered a frame handler at all.
frame.players is emptyThe game is not in a match, or the data was only just requested (it fills in a frame later), or you are looking at a list that never includes you: players never contains the pawn you are viewing.
"number has no integer representation"A colour channel, virtual key or mouse delta was a fractional number. Wrap it in math.floor(...).
"draw command limit exceeded"More than 8192 draw calls in one pass. Draw less, or skip what is off-screen. Drawing from tick also fills the list with commands that never get published.
dismhook.log prints nothingOnly the Debug build has a console. See Logging.
player_killed never firesDead players are removed from frame.players, so the health never drops to 0 inside the list. Use player_left, or confirmed_hit with killed = true for your own kills.
config.set returned true but nothing changedtrue only means the change was queued. It is ignored, without an error, when the path does not exist or the new value has a different type than the old one (a boolean cannot become a number).
ui.get never changesThe script has to be running (Run in the Scripts tab): its controls appear under Script controls in the Scripts tab, and only there can they be changed. See dismhook.ui.
Nothing is drawn with the menu shutIt is drawn: a running script keeps the overlay awake by itself. If it is not, the script is stopped or in Error, or Enable Lua API is off.
Input calls return falseThe menu is open, or the game is not ready for input. Look at frame.menu_open and frame.input_ready.
A texture does not showCheck the return of load_texture for a message. The path must be relative to the script's folder and stay inside it.
The script keeps its old behaviour after I edited a moduleOnly the entry file is watched. Save the entry file (or press Stop and Run).

Known quirks in this version#

Things the runtime does today that you might not expect. They are collected here so a script can allow for them. If the runtime changes, this page changes with it.

QuirkWhat a script should do
config.set and config.patch return true when a change is queued, even if it is later ignored (unknown path, or a value of another kind)read the setting back with config.get; find paths with config.schema() or the config reference
Only the entry file is watched for changesafter editing a module, save the entry file too
Draw calls only count from a frame passdraw from frame handlers
A table stored with dismhook.storage.set that has a list part loses its other keysstore lists and objects in separate tables
storage.set and storage.remove batch writes to disk every 250ms instead of writing immediatelydon't rely on a value being on disk the instant you call set

dismhook.api and dismhook.log#

The two things that are always there.

dismhook.api#

A table describing the API and where this script lives.

Returns
ValueDescription
majorinteger, 2. A folder script whose manifest names a different api_version is refused
minorinteger, 0
script_dirstring: the folder that holds the script's entry file, without a trailing backslash
data_dirstring: this script's data folder, scripts\.data\<id>. It is created the first time something is saved there (storage, radar_overview). The last part of the path is the script's id
lua
print(dismhook.api.major, dismhook.api.minor)        --> 2   0
local id = dismhook.api.data_dir:match("[^\\/]+$")  -- the script's id, e.g. "hello"

dismhook.log(text)#

Writes a line to the program's diagnostic console as [lua:<id>] text.

Arguments
NameTypeDescription
textstringthe message; numbers are converted
lua
dismhook.log("players: " .. #dismhook.game.snapshot().players)

dismhook.events#

Scripts are event driven: you register functions, and the runtime calls them.

dismhook.events.on(event, callback, opts) -> token#

Registers a function to be called whenever event happens. Any number of functions can be registered for the same event; they run in the order they were registered.

Arguments
NameTypeDescription
eventstringthe event name, see the table below. An unknown name is accepted but never fires
callbackfunctioncalled with the event's argument (if it has one)
optstable (optional)for the frame event only: { bones = true, hitboxes = true } to opt into those fields on every player. New in API v2, both default to false
Returns

an integer token. Pass it to dismhook.events.off to remove exactly this registration.

lua
local token = dismhook.events.on("map_changed", function(map)
    dismhook.log("map: " .. map)
end)

dismhook.events.off(event [, token]) -> boolean#

Removes callbacks.

Arguments
NameTypeDescription
eventstringthe event name
tokeninteger (optional)the value on returned. Without it, all callbacks of that event are removed
Returns

true if something was removed, otherwise false.

lua
dismhook.events.off("map_changed", token)   -- just that one
dismhook.events.off("tick")                 -- every tick handler

Event reference#

EventRaised whenArgumentData it collects
loadthe script has been loaded (see the warning about a double load on first start)none-
unloadthe script is stopping, or is about to be replaced by a new version of the filenone-
tickevery 16 msdt: number, always 0.016-
config_changeda script's config.set or config.patch has been applied. Not raised for changes made in the menunonethe settings snapshot
framea new frame is available. The only place drawing worksFrameeverything
game_connectedframe.connected became truenone-
game_disconnectedframe.connected became false. The script's memory of players, weapons and projectiles is resetnone-
map_changedthe map name changed (also fired once when the first map becomes known, and with "" when you leave it)map name, string-
player_joinedFixed in v2: a controller became alive that was not alive a moment ago: a real join, or a respawn (not for the players already alive in the first frame)Playerplayers, roster
player_leftFixed in v2: a controller disconnected entirely — no longer confused with a death, which now raises player_killed instead{ handle = 0, name, team, last_health }players, roster
player_hurta player's health went down{ handle, damage, health }players
player_killedFixed in v2: raised when a player's controller goes from alive to not-alive, using the game's own alive flag instead of guessing from health{ handle, name, team, last_health }players, roster
weapon_changedthe weapon a player holds changed{ handle, weapon }, weapon is a Weaponplayers
projectile_createda grenade appeared (for every grenade already in the air in the first frame, too){ id, kind, origin, velocity }projectiles
projectile_detonateda grenade went off, or its smoke started{ id, kind, origin }projectiles
projectile_removeda grenade is goneprojectile id, integerprojectiles
bomb_plantedthe bomb was plantednonethe bomb
bomb_defuse_startedsomebody started defusingnonethe bomb
bomb_defuse_stoppedthe defuse was interrupted while the bomb is still plantednonethe bomb
bomb_defusedthe planted bomb is gone because it was defusednonethe bomb
bomb_resolvedthe planted bomb is gone and it was not a defuse: it exploded, or the round endednonethe bomb
confirmed_hitone of your shots was confirmed to have hit someone{ damage, killed }players
using events
dismhook.events.on("player_hurt", function(e)
    -- e.handle is the pawn handle, the same number as player.handle in a frame
    dismhook.log(string.format("%d lost %d hp, %d left", e.handle, e.damage, e.health))
end)

dismhook.events.on("projectile_detonated", function(e)
    if e.kind == 3 then dismhook.log("smoke went off at " .. e.origin.x .. ", " .. e.origin.y) end
end)

dismhook.game#

Reading the game and asking the program's simulation questions. Nothing here changes the game.

dismhook.game.snapshot() -> Frame | nil#

The newest frame, the same table the frame event receives. Handy in tick handlers or hotkey code that has no frame argument.

Returns

a Frame, or nil if no frame has been published yet (Lua must be enabled and at least one script running, and the first frame takes a moment).

lua
dismhook.events.on("tick", function()
    local frame = dismhook.game.snapshot()
    if frame and frame.local_player.alive then
        -- ...
    end
end)

dismhook.game.radar_snapshot() -> RadarFrame | nil#

A slimmer frame meant for radar-style scripts: fewer fields per player and projectile, no screen size, no camera matrix, no spectators.

Returns
ValueDescription
sequence, timestamp_us, connected, mapas in Frame
camera{ origin, angles, fov }
local_player{ alive, name, team, health, game_time }
playerseach: handle, name, health, armor, team, money, origin, velocity, eye_angles, weapon, loadout, and the flags invulnerable, helmet, defuser, scoped, visible, spotted
itemseach: kind, origin
projectileseach: id, kind, origin, velocity, initial_position, initial_velocity, bounces, effect_tick_begin, spawn_time, detonate_time, remaining_lifetime, expire_time, detonated, smoke_active, fire_points
bombactive, active_position, planted, time_remaining, being_defused, defuse_remaining, defuse_success, site, predicted_damage, position

dismhook.game.world_to_screen(position) -> {x, y} | nil#

Projects a point of the world onto the screen.

Arguments
NameTypeDescription
positionVec3a table { x = ..., y = ..., z = ... }. Anything that is not a table is read as (0, 0, 0); missing components as 0
Returns

a table { x, y } in screen pixels (the same space the draw functions use), or nil when the point cannot be shown: behind the camera, or with no camera yet.

lua
local head = player.bones[8]                         -- bone 7 is the head, tables are 1-based
local screen = dismhook.game.world_to_screen(head)
if screen then
    dismhook.draw.filled_circle(screen.x, screen.y, 3, 255, 0, 0, 255)
end

dismhook.game.trace_ray(from, to) -> Trace | nil#

Casts a ray through the map's solid geometry and reports what it hits first.

Arguments
NameTypeDescription
fromVec3start of the ray
toVec3end of the ray
Returns
ValueDescription
hitboolean: something solid is in the way
fractionnumber 0..1: how far along the ray the hit is (1 when nothing was hit)
distancenumber: units from from to the hit point (or to to when nothing was hit)
positionVec3: the hit point (or to)
normalVec3: the direction the surface faces at the hit (zero when nothing was hit)
penetrationnumber: how easily bullets go through the surface that was hit
surface_typeinteger: the surface material id
lua
local trace = dismhook.game.trace_ray(eye, target)
if trace and not trace.hit then
    -- a clear line from eye to target
end

dismhook.game.bomb_damage(position [, site [, angles [, ducked]]]) -> integer#

Predicts how much damage the planted bomb would do to a player standing at position, from the map's blast data.

Arguments
NameTypeDescription
positionVec3where the player stands
siteinteger (optional)the bomb site, 0 for A and 1 for B. By default the site whose blast area contains position
anglesVec3 (optional)the player's view angles; default (0, 0, 0)
duckedboolean (optional)whether the player is crouching; default false
Returns

an integer, the predicted damage in health points, or -1 when there is no blast data for the map or the site.

lua
local damage = dismhook.game.bomb_damage(player.origin, 1, player.eye_angles, player.ducked)
if damage >= player.health then dismhook.log(player.name .. " would not survive") end

dismhook.game.predict_grenade(origin, velocity, weapon [, lifetime]) -> Path | nil, message#

Simulates the flight of a grenade thrown from origin with velocity, including bounces off the map.

Arguments
NameTypeDescription
originVec3where the grenade starts (or is right now)
velocityVec3its velocity in units per second
weaponstring | integerwhich grenade: "he", "flash", "smoke", "molotov", "incendiary" or "decoy" (also "he_grenade", "flashbang", "smoke_grenade" and the "weapon_..." class names). An integer is taken as the weapon's internal id
lifetimenumber (optional)seconds the grenade lives; default: the grenade's normal life
Returns
ValueDescription
validboolean: the simulation produced a path
durationnumber: seconds from the start to the end of the path
end_tickinteger: the tick the path ends on
end_positionVec3: where it ends
pointsarray of Vec3 along the flight, in order
bouncesarray of Vec3, the points where it bounced
lua
local path = dismhook.game.predict_grenade(projectile.origin, projectile.velocity, "smoke")
if path and path.valid then
    print(("lands after %.1f s"):format(path.duration))
end

dismhook.game.radar_overview(map [, name]) -> Overview | nil, message#

Exports the game's own radar picture for a map to disk and tells you how to place world positions on it.

Arguments
NameTypeDescription
mapstringthe map name, e.g. "de_mirage" (cleaned to letters, digits, _ and -)
namestring (optional)base name of the exported files; default "overview"
Returns
ValueDescription
pos_x, pos_ynumbers: the world position of the picture's top-left corner
scalenumber: world units per picture pixel
width, heightintegers: the picture size in pixels
has_lowerboolean: the map has a second (lower) level picture
lower_altitude_maxnumber: heights below this belong to the lower level
primary_pathstring: the exported PNG, <data_dir>\<name>-primary.png
lower_pathstring, only when has_lower: <data_dir>\<name>-lower.png
lua
local info, err = dismhook.game.radar_overview("de_mirage")
if info then
    local x = (player.origin.x - info.pos_x) / info.scale
    local y = (info.pos_y - player.origin.y) / info.scale
end

dismhook.game.penetration_damage(start, finish [, target]) -> Result | nil#

Estimates what your current weapon would do along a line, through walls. It needs you to be alive and holding a weapon.

Arguments
NameTypeDescription
startVec3where the shot starts, usually your eye
finishVec3where it ends, or a point along the direction
targetinteger (optional)a player's pawn handle (player.handle). With it, the shot is run against that player's hitboxes
Returns
ValueDescription
with target{ damage, distance, hitbox, penetrated }: the damage that reaches the player, the distance to the hit, the hitbox index, and whether the shot went through something
without target{ damage, penetrated = true }: whether the weapon can shoot through what lies along that direction, and the damage estimate
lua
local result = dismhook.game.penetration_damage(eye, player.bones[8], player.handle)
if result then
    dismhook.draw.text(20, 300, ("wallbang: %d dmg"):format(result.damage), 255, 255, 255, 255)
end

dismhook.game.seed_check(mode, ticks) -> boolean | nil#

Checks whether the bullet spread on the current tick would hit an enemy, using the game's seed prediction. This is the same algorithm the built-in triggerbot uses, but exposed so scripts can make their own firing decisions.

Arguments
NameTypeDescription
modeinteger1 = restricted (the bullet must hit the enemy your crosshair is already on), 2 = unrestricted (any enemy)
ticksinteger (optional)how many consecutive ticks must all confirm a hit; 1 to 3, default 2
Returns
ValueDescription
trueevery sampled tick confirms a hit
falseat least one tick missed, or the check couldn't run at all (no weapon, dead, unreadable state)
nilonly for an invalid mode argument
lua
local ok = dismhook.game.seed_check(1, 2)
if ok then
    dismhook.input.mouse_button("primary", true)
end

dismhook.draw#

The drawing functions add a shape to the script's draw list. See Drawing for when that list is shown. All coordinates are screen pixels (top-left origin) unless the function says world.

dismhook.draw.line(x1, y1, x2, y2 [, r, g, b, a [, thickness]])#

A straight line.

Arguments
NameTypeDescription
x1, y1, x2, y2numberthe two end points
thicknessnumber (optional)line width in pixels; default 1
lua
dismhook.draw.line(0, 540, 1920, 540, 255, 255, 255, 60)

dismhook.draw.rect(x, y, w, h [, r, g, b, a [, thickness]])#

The outline of a rectangle.

Arguments
NameTypeDescription
x, ynumbertop-left corner
w, hnumberwidth and height
thicknessnumber (optional)default 1

dismhook.draw.filled_rect(x, y, w, h [, r, g, b, a])#

A filled rectangle.

Arguments
NameTypeDescription
x, ynumbertop-left corner
w, hnumberwidth and height
lua
dismhook.draw.filled_rect(20, 20, 200, 8, 0, 0, 0, 160)      -- a dark bar

dismhook.draw.circle(x, y, radius [, r, g, b, a [, thickness]])#

The outline of a circle.

Arguments
NameTypeDescription
x, ynumbercentre
radiusnumberin pixels
thicknessnumber (optional)default 1

dismhook.draw.filled_circle(x, y, radius [, r, g, b, a])#

A filled circle.

Arguments
NameTypeDescription
x, ynumbercentre
radiusnumberin pixels

dismhook.draw.text(x, y, text [, r, g, b, a])#

A line of text.

Arguments
NameTypeDescription
x, ynumberthe top-left corner of the text
textstringwhat to write; numbers are converted
lua
dismhook.draw.text(30, 226, "Hello", 235, 235, 240, 255)

dismhook.draw.world_line(from, to [, r, g, b, a [, thickness]])#

A line between two points of the world, projected onto the screen.

Arguments
NameTypeDescription
from, toVec3the end points in world coordinates
thicknessnumber (optional)default 1
lua
dismhook.draw.world_line(player.origin, frame.camera.origin, 255, 255, 255, 120)

dismhook.draw.world_text(position, text [, r, g, b, a])#

Text placed at a point of the world. The top-left of the text is at the projected point; nothing is drawn when the point is not on the projection.

Arguments
NameTypeDescription
positionVec3world position
textstringwhat to write

dismhook.draw.load_texture(path) -> key | nil, message#

Registers a picture file so it can be drawn with draw.image.

Arguments
NameTypeDescription
pathstringa relative path from the script's folder (dismhook.api.script_dir), that stays inside it. Forward or back slashes
Returns

a string key to pass to draw.image, or nil and a message: "texture path must be relative", "texture file not found", "texture path escapes script directory", "unsupported texture format", "texture exceeds 16 MiB" or "texture limit exceeded".

lua
local logo = dismhook.draw.load_texture("images/logo.png")   -- scripts\images\logo.png

dismhook.events.on("frame", function()
    if logo then dismhook.draw.image(logo, 20, 20, 64, 64) end
end)

dismhook.draw.image(key, x, y, w, h [, r, g, b, a])#

Draws a registered picture, scaled to w by h pixels.

Arguments
NameTypeDescription
keystringwhat load_texture returned
x, ynumbertop-left corner
w, hnumbersize on screen
r, g, b, aintegers (optional)a tint; opaque white leaves the picture unchanged

dismhook.draw.panel(x, y, w, h, title [, r, g, b, a])#

A window as ImGui draws one: window background, border and a title bar with a title, in the colours of the current ImGui style, so it looks like the menu and the on-screen panels the program shows itself.

Arguments
NameTypeDescription
x, ynumbertop-left corner
w, hnumbersize; at least 80 x 36 (smaller values are raised to that)
titlestringwritten in the title bar
r, g, b, aintegers (optional)accepted and ignored: a panel always has the menu's own look. They stay in the signature so older scripts keep working
lua
dismhook.draw.panel(20, 200, 260, 64, "Hello", 90, 160, 255, 255)

dismhook.ui#

A control is created by the first call with its id; calling the same function again with the same id later does nothing (except text and button, which update their text). That makes it safe to declare controls at the top of the file or on every frame. A script can have 512 controls.

dismhook.ui.text(id, label [, text])#

A line of read-only text. Later calls change what it says.

Arguments
NameTypeDescription
idstringunique within the script
labelstringshown next to it; defaults to the id
textstringthe content; default empty

dismhook.ui.button(id, label [, action_text])#

A button. Later calls change the button's text.

Arguments
NameTypeDescription
idstring
labelstring
action_textstringthe text on the button; default "Run"

dismhook.ui.toggle(id, label [, initial])#

An on/off switch.

Arguments
NameTypeDescription
initialbooleandefault false

dismhook.ui.slider(id, label [, initial [, min [, max [, step]]]])#

A number between min and max.

Arguments
NameTypeDescription
initialnumberdefault 0
minnumberdefault 0
maxnumberdefault 1
stepnumberdefault 0.01

dismhook.ui.select(id, label, options [, initial])#

A choice from a list.

Arguments
NameTypeDescription
optionstable of stringsthe choices, in order
initialintegerthe 0-based index of the choice that starts selected; default 0

dismhook.ui.input(id, label [, initial])#

A text box.

Arguments
NameTypeDescription
initialstringdefault empty

dismhook.ui.color(id, label [, initial])#

A colour picker.

Arguments
NameTypeDescription
initialtable{ r = 255, g = 255, b = 255, a = 255 }; missing channels default to 255

dismhook.ui.keybind(id, label [, initial])#

A key picker.

Arguments
NameTypeDescription
initialintegera virtual-key code, 0 to 255; default 0, which means not bound

dismhook.ui.separator(id)#

A divider line between groups of controls.

dismhook.ui.get(id) -> value | nil#

The current value of a control.

Returns
ValueDescription
toggleboolean
slidernumber
selectinteger, the 0-based index
input, textstring
colortable { r, g, b, a }
keybindinteger, 0 when not bound
buttonfalse
unknown idnil

dismhook.ui.consume(id) -> boolean#

Returns true once for every press of a button that has not been handled yet, and false when there is none.

lua
dismhook.ui.button("reset", "Reset counters", "Reset")

dismhook.events.on("tick", function()
    if dismhook.ui.consume("reset") then
        kills = 0
    end
end)

dismhook.ui.set(id, value) -> boolean#

New in API v2. Programmatically sets an existing control's value, the same way the user moving it would. Previously scripts could only read their own controls, never write them.

Arguments
NameTypeDescription
idstringthe control's id
valueanybool for toggle, number for slider, integer index for select/keybind, string for text/input, table for color
Returns

true if the control exists and the value was applied, false otherwise.

lua
dismhook.ui.set("flick_sens", 1.0) -- reset to default

dismhook.input#

Reading the keyboard, and pressing keys and mouse buttons for you. Keys are Windows virtual-key codes (1 to 255).

dismhook.input.is_down(key) -> boolean#

Whether a key or mouse button is held down right now.

Arguments
NameTypeDescription
keyintegera virtual-key code, 1 to 255; anything else returns false
lua
local F6 = 0x75
local was_down = false
dismhook.events.on("tick", function()
    local down = dismhook.input.is_down(F6)
    if down and not was_down then dismhook.log("F6 pressed") end   -- the press only, not while held
    was_down = down
end)

dismhook.input.binding(action) -> Binding | nil#

Which key the game has bound to an action, so a script follows the player's own settings.

Arguments
NameTypeDescription
actionstring"forward", "back", "left", "right", "walk", "duck", "jump", "attack" or "attack2"
Returns
ValueDescription
virtual_keyinteger: the Windows virtual-key code
deviceinteger: 0 none, 1 keyboard, 2 left mouse button, 3 right mouse button, 4 middle button, 5 and 6 the side buttons
namestring: the key's name as the game shows it
lua
local jump = dismhook.input.binding("jump")
if jump and jump.device == 1 then
    dismhook.log("jump is on key " .. jump.name)
end

dismhook.input.key(key, pressed) -> boolean#

Presses or releases a keyboard key.

Arguments
NameTypeDescription
keyintegera virtual-key code; values outside 1 to 255 are clamped into it
pressedbooleantrue to press and hold, false to release
Returns

true if the input was sent.

lua
dismhook.input.key(0x11, true)      -- hold Ctrl
dismhook.input.key(0x11, false)     -- let go

dismhook.input.tap(key) -> boolean#

Presses and releases a key straight away.

Arguments
NameTypeDescription
keyintegera virtual-key code
Returns

true if it was sent.

dismhook.input.mouse_move(dx, dy) -> boolean#

Moves the mouse by a relative amount, as if the mouse had been pushed.

Arguments
NameTypeDescription
dx, dyintegermovement in raw mouse units; positive is right and down
Returns

true if it was sent.

dismhook.input.mouse_button(button, pressed) -> boolean#

Presses or releases a mouse button.

Arguments
NameTypeDescription
buttonstring"primary" (left) or "secondary" (right); anything else returns false
pressedbooleantrue to press, false to release
Returns

true if it was sent.

dismhook.math#

New in API v2. Angle and mouse-count math scripts previously had to hand-roll, plus vector helpers. mouse_counts in particular reads your real sensitivity and the game's own scoped-sensitivity field, so scripts get correct sniper-scope math for free instead of guessing a FOV ratio.

dismhook.math.angle_to(from, to) -> pitch, yaw#

Returns the view angles that would point the camera from one position at another.

Arguments
NameTypeDescription
fromVector3the eye/camera position to aim from
toVector3the position to aim at
Returns

two numbers, pitch and yaw in degrees.

lua
local pitch, yaw = dismhook.math.angle_to(f.camera.origin, target_point)

dismhook.math.wrap_angle(degrees) -> degrees#

Normalizes an angle (typically a yaw delta) into the -180..180 range.

Arguments
NameTypeDescription
degreesnumberan angle, or an angle difference
Returns

the equivalent angle in -180..180.

lua
local delta = dismhook.math.wrap_angle(target_yaw - f.camera.angles.y)

dismhook.math.mouse_counts(delta_pitch, delta_yaw, sensitivity) -> dx, dy#

Converts an angle delta into relative mouse move counts, ready for input.mouse_move. Reads your real sensitivity convar and the game's scoped-sensitivity field automatically, so it stays correct while zoomed with a sniper.

Arguments
NameTypeDescription
delta_pitchnumberpitch change in degrees
delta_yawnumberyaw change in degrees
sensitivitynumber (optional)overrides the sensitivity convar read automatically
Returns

two integers, dx and dy, for input.mouse_move.

lua
local pitch, yaw = dismhook.math.angle_to(f.camera.origin, target)
local dx, dy = dismhook.math.mouse_counts(pitch - f.camera.angles.x, dismhook.math.wrap_angle(yaw - f.camera.angles.y))
dismhook.input.mouse_move(dx, dy)

dismhook.math.direction(pitch, yaw) -> forward, right, up#

Converts view angles into three unit direction vectors.

Arguments
NameTypeDescription
pitchnumberpitch in degrees
yawnumberyaw in degrees
Returns

three Vector3 unit vectors: forward, right, up.

lua
local forward = dismhook.math.direction(f.camera.angles.x, f.camera.angles.y)

dismhook.math.distance(a, b) -> number#

Euclidean distance between two points.

Arguments
NameTypeDescription
a, bVector3the two points
Returns

a number.

lua
local d = dismhook.math.distance(f.camera.origin, target)

dismhook.math.normalize(v) -> Vector3#

Returns v scaled to unit length.

Arguments
NameTypeDescription
vVector3the vector to normalize
Returns

a Vector3.

lua
local dir = dismhook.math.normalize(velocity)

dismhook.math.dot(a, b) -> number#

Dot product of two vectors.

Arguments
NameTypeDescription
a, bVector3the two vectors
Returns

a number.

lua
local facing = dismhook.math.dot(a_forward, b_forward)

dismhook.config#

Read and change the program's own settings, the same ones the menu edits. A path names a setting: keys joined by dots, starting at one of the four top-level parts esp, misc, combat_global and combat_overrides. A number in a path picks an element of a list (counting from 0). Every path is listed in the config reference.

paths
esp.m_player.enabled            -- player ESP on/off               (boolean)
misc.fps_limit                  -- the FPS limiter's target        (integer)
esp.m_bomb.show_timer           -- a bomb setting                  (boolean)
combat_overrides.2.aimbot_fov   -- the rifle group's setting       (a list index, 2 = rifle)

dismhook.config.get(path) -> value | nil#

Reads one setting.

Arguments
NameTypeDescription
pathstringdotted path
Returns

the value: a boolean, number, string, a colour as { r, g, b, a }, or a table for a group of settings; nil if the path does not exist.

lua
if dismhook.config.get("esp.m_player.enabled") then
    dismhook.log("player ESP is on")
end
local box_colour = dismhook.config.get("esp.m_player.m_box.visible_color")   -- { r = ..., g = ..., b = ..., a = ... }

dismhook.config.set(path, value) -> boolean#

Queues a change of one setting.

Arguments
NameTypeDescription
pathstringdotted path
valueboolean | number | string | tablethe new value. A colour is a table { r =, g =, b =, a = }
Returns

true if the change was queued, false if the value cannot be stored (a function, a table with keys that are neither text nor a list, nesting deeper than 32) or during a reload trial.

lua
dismhook.config.set("misc.limit_fps", true)
dismhook.config.set("misc.fps_limit", 240)
dismhook.config.set("esp.m_player.m_box.visible_color", { r = 255, g = 60, b = 60, a = 255 })

dismhook.config.patch(changes) -> boolean#

Queues several changes at once.

Arguments
NameTypeDescription
changestablea table whose keys are paths and whose values are the new values
Returns

true if every entry was queued. At the first entry that cannot be (a key that is not text, or a value that cannot be stored) it stops and returns false; the entries before it stay queued.

lua
dismhook.config.patch({
    ["esp.m_player.enabled"] = true,
    ["esp.m_radar.enabled"]  = true,
    ["misc.m_watermark.enabled"] = false,
})

dismhook.config.snapshot() -> table#

The whole settings document as nested tables. Large: use get for one value.

Returns

a table with the four parts esp, misc, combat_global and combat_overrides (a list of 6 groups).

dismhook.config.schema() -> table#

Describes the shape of the settings document, so a script can discover paths at run time.

Returns
ValueDescription
an object{ type = "object", properties = { name = <schema>, ... } }
a list{ type = "array", size = n, items = <schema of the first element> }
a value{ type = "boolean" }, "integer", "number", "string" or "null"
lua
local schema = dismhook.config.schema()
local misc_schema = schema.properties["misc"].properties
for name, entry in pairs(misc_schema) do
    print(name, entry.type)          --> auto_accept  boolean, fps_limit  integer, ...
end

dismhook.storage#

A small key/value store that belongs to one script and survives restarts. It is a JSON file, scripts\.data\<id>\store.json, loaded when the script starts. Scripts cannot see each other's stores, and deleting a script leaves its store behind.

dismhook.storage.get(key) -> value | nil#

Reads a value.

Arguments
NameTypeDescription
keystring
Returns

a copy of what was stored, or nil.

lua
local stats = dismhook.storage.get("stats") or { kills = 0 }

dismhook.storage.set(key, value) -> boolean#

Stores a value and writes the file immediately (to a temporary file that then replaces the old one, so a crash cannot leave half a file).

Arguments
NameTypeDescription
keystring
valuenil | boolean | number | string | tableanything JSON can hold
Returns

true when stored. false for a value that cannot be stored: a function, a thread, a table with a key that is neither text nor a number, or a table nested deeper than 32 levels.

lua
stats.kills = stats.kills + 1
dismhook.storage.set("stats", stats)

dismhook.storage.remove(key) -> boolean#

Deletes a value.

Arguments
NameTypeDescription
keystring
Returns

true if there was something to delete.

dismhook.helpers#

Two things a script cannot do with plain Lua: put text on the clipboard, and run a PowerShell helper that sits next to it.

dismhook.helpers.start(path [, args]) -> true | false, message#

Runs a PowerShell script that is part of your script, without waiting for it and without a window.

Arguments
NameTypeDescription
pathstringa relative path to a .ps1 file inside the script's folder
argstable of strings (optional)arguments for the helper, up to 32, each up to 2048 characters (numbers are converted)
Returns

true once it was started, or false and a message: "helper path must be relative", "PowerShell helper not found" (also when the file is not a .ps1), "helper path escapes script directory", "CreateProcess failed (n)", or "helper launch is unavailable during validation".

lua
local started, why = dismhook.helpers.start("copy_notify.ps1", { "Position copied" })
if not started then dismhook.log("helper did not start: " .. tostring(why)) end

dismhook.helpers.copy_text(text) -> boolean#

Replaces the clipboard's contents with text (Unicode).

Arguments
NameTypeDescription
textstringUTF-8 text
Returns

true if the clipboard took it, false if it could not be opened (another program is using it) or the text is not valid UTF-8.

lua
dismhook.helpers.copy_text(string.format("%.1f %.1f %.1f", o.x, o.y, o.z))

dismhook.assets#

Things the program carries that a script may want.

dismhook.assets.weapon_font() -> string#

The bytes of the weapon-icon font the program draws with (a TrueType font).

Returns

a Lua string with the raw font file. It is large, so it counts against the script's memory; do not keep calling it.

lua
local file = io.open(dismhook.api.data_dir .. "\\weapons.ttf", "wb")   -- the folder must exist
if file then file:write(dismhook.assets.weapon_font()); file:close() end

Data types#

These are the tables the API hands to a script. All of them are copies: change them freely. Vec3 is { x, y, z } (numbers) and Vec2 is { x, y }. Lists are Lua sequences starting at 1.

Frame#

What the frame event receives and dismhook.game.snapshot() returns.

FieldTypeMeaning
sequenceintegerthe number of this frame; goes up by one per published frame
timestamp_usintegermicroseconds on a monotonic clock. Only differences mean something; divide by 1000000 for seconds
connectedbooleana game with a local player is running
menu_openbooleanthe program's menu is open
input_readybooleanthe overlay is attached to a visible game window, so input can be sent to the game
mapstringthe current map, e.g. "de_mirage"; "" when there is none
screen{ width, height }the size of the overlay in pixels
cameraCamerathe view you are looking through
local_playerLocalPlayeryou
playerslist of Playerevery living player on a team, both teams, except the player you are viewing (you, or whoever you are watching when dead)
itemslist of Itemweapons and equipment on the ground
projectileslist of Projectilegrenades in the air, smokes and fires
spectatorslist of Spectatorwho is watching you; refreshed about five times a second
bombBombthe bomb

Camera#

FieldTypeMeaning
originVec3the position of the camera (your eyes)
anglesVec3where it looks: x pitch, y yaw, z roll, in degrees
fovnumberthe field of view in degrees
matrixlist of 16 numbersthe game's 4 x 4 view matrix, row by row: matrix[row * 4 + column + 1] with row and column from 0. You do not need it: world_to_screen uses it for you

LocalPlayer#

FieldTypeMeaning
aliveboolean
namestringyour name; may be empty for a moment after joining
teamintegersee teams
healthinteger
tick_baseintegeryour tick counter
game_timenumberthe game's clock in seconds. Compare it with last_fired_time and similar fields, which are on the same clock
flash_alphanumberhow strongly you are flashed, as the game reports it (0 when not flashed)
weapon_typeintegerthe kind of weapon you hold, see weapon types, or dismhook.enums.weapon_class
armorintegernew in API v2
moneyintegernew in API v2
ammointegernew in API v2. current magazine, from your active weapon (0 if you have none)
scopedbooleannew in API v2. true while zoomed in with a sniper rifle
duckedbooleannew in API v2
defusingbooleannew in API v2
flashedbooleannew in API v2. shorthand for flash_alpha > 0
aim_punchVector3new in API v2. current view-punch (recoil kick) angles, the same value seed_check uses internally
crosshair_idintegernew in API v2. the entity index your crosshair is over, or a non-positive value when it isn't over anyone

Player#

One player. handle is the number that identifies them between frames and in events.

FieldTypeMeaning
handleintegerthe player's pawn handle. Stable while they live; a new life is a new handle
controller_indexintegerthe player's slot number
steam_id64integerthe 64-bit Steam id
namestringthe player's name
health, armorintegerhealth is 1 to 100 (a dead player is not in the list)
teamintegersee teams
moneyintegerdollars
pingintegermilliseconds
rank, wins, rank_typeintegercompetitive rank data exactly as the game reports it
simulation_tick, simulation_timeinteger, numberwhen the game last simulated this player
last_fired_timenumberthe game time of their last shot
emit_sound_timenumberthe game time of the last sound they made
originVec3the position of their feet
velocityVec3units per second
collision_centerVec3the centre of their collision box, about waist high
eye_anglesVec3where they look: x pitch, y yaw
model_pathstringthe player model's resource path. Only filled in while Chams is on in the config, otherwise empty
weaponWeaponwhat they hold
loadoutlist of stringsnames of the weapons they carry; refreshed about four times a second, may be empty
invulnerablebooleanspawn or gun-game protection
helmet, defuserbooleanthey wear a helmet / carry a defuse kit
scoped, defusing, duckedbooleanzoomed in / defusing the bomb / crouching
flashedbooleanthey are currently blinded
visiblebooleanthe program's line-of-sight trace from your camera to their head is not blocked by the map. It needs the map's collision data and is refreshed on the trace interval (see the Performance settings). It says nothing about walls of smoke
spottedbooleanthe game marks them as spotted
spotted_by_maskintegera bit mask, one bit per player slot, of who has spotted them
boneslist of Boneeach entry has x, y, z (world position) plus, new in API v2, a rotation field ({x, y, z, w} quaternion). The list is 1-based: bone id n is bones[n + 1], so the head (bone 7, or dismhook.enums.bone.head) is bones[8]. Up to 128 entries; empty unless the frame callback asked for bones in its opts (see events.on), or the pose could not be read. See bone ids
hitboxeslist of Hitboxthe hit boxes of their model. Empty unless the frame callback asked for hitboxes in its opts (see events.on)

Weapon#

FieldTypeMeaning
handleintegerthe weapon entity's handle
item_definitionintegerthe game's item definition index (7 is the AK-47, 9 the AWP, ...)
namestringthe weapon's name without the prefix, such as "ak47" or "hegrenade". Empty when unknown
ammo, max_ammointegerbullets in the magazine and its size
pin_pulledbooleana grenade whose pin has been pulled

Hitbox#

FieldTypeMeaning
indexintegerthe hitbox's number in the model
boneintegerthe bone it is attached to (an index into bones, counting from 0)
namestringe.g. "head"
mins, maxsVec3the corners of the box, in the bone's own space
radiusnumberthe radius when the hitbox is a capsule, otherwise 0

Item#

A weapon or piece of equipment lying on the ground.

FieldTypeMeaning
kindintegersee item kinds
originVec3where it lies
ammo, max_ammointegerbullets in it and its magazine size

Projectile#

A grenade in the air, or the smoke or fire one left behind.

FieldTypeMeaning
idintegeran id that stays the same for the projectile's whole life
kindintegersee projectile kinds
origin, velocityVec3position now, and speed in units per second
initial_position, initial_velocityVec3where it was thrown from, and how fast
thrower_handleintegerthe pawn handle of whoever threw it
bouncesintegerhow often it has bounced
effect_tick_beginintegerthe tick its effect began, 0 before that
spawn_time, detonate_time, expire_timenumbergame-clock seconds
remaining_lifetimenumberseconds left, or -1 when unknown
detonatedbooleanit has gone off
smoke_activebooleana smoke cloud is up
smoke_positionVec3where the smoke went off
smoke_voxel_sizeintegerthe size of the smoke cloud's cells
smoke_volume_receivedbooleanthe game has sent the shape of the smoke cloud
fire_pointslist of Vec3the spots a molotov's fire covers

Spectator#

FieldTypeMeaning
namestring
steam_id64integer
modeintegerthe raw observer mode of that player (the game's own numbering); players with mode 0 are not listed

Bomb#

FieldTypeMeaning
activebooleana bomb that is not planted exists (carried or dropped)
active_positionVec3where that bomb is
plantedbooleanthe bomb is planted
time_remainingnumberseconds until it explodes
timer_lengthnumberthe full length of the timer in seconds (40 by default)
being_defusedboolean
defuse_remainingnumberseconds until the defuse completes
defuse_lengthnumberthe defuse time in seconds: 5 with a kit, 10 without
defuse_successbooleanthe last planted bomb ended in a defuse
siteinteger0 is site A, 1 is site B
predicted_damageintegerthe damage the explosion would do to you, or -1 when unknown
positionVec3where the planted bomb is

Enumerations#

Several fields are small integers that stand for a choice. The lists below are read from the program's source.

Teams#

ValueTeam
2Terrorists
3Counter-Terrorists
0, 1none / spectator (only seen on local_player.team)

Item kinds#

item.kind:

0 unknown12 mac1024 negev36 knife
1 ak4713 mp5sd25 deagle37 c4
2 m4a414 mp726 dual_berettas38 healthshot
3 m4a1s15 mp927 five_seven39 he_grenade
4 awp16 pp_bizon28 glock40 flashbang
5 aug17 p9029 p200041 smoke_grenade
6 famas18 ump4530 usps42 molotov
7 galil_ar19 nova31 p25043 incendiary
8 sg55320 sawed_off32 cz7544 decoy
9 g3sg121 xm101433 tec9
10 scar2022 mag734 r8_revolver
11 ssg0823 m24935 taser

Projectile kinds#

projectile.kind. molotov_fire is the fire a molotov leaves; it has no flight to predict.

ValueName
0unknown
1he_grenade
2flashbang
3smoke_grenade
4molotov
5molotov_fire
6decoy

Weapon types#

local_player.weapon_type. sidearm is the pistols, precision the sniper rifles, lmg the machine guns, objective the bomb, electroshock the Zeus, throwable the grenades:

ValueName
0knife
1sidearm
2smg
3rifle
4shotgun
5precision
6lmg
7objective
8electroshock
9throwable
10equipment
11healthshot

Bone ids#

The named bones of a player model. Remember that player.bones is 1-based, so bone id n is bones[n + 1]. Other ids exist (fingers, twist bones, ...) and are in the list too.

0 origin11 hand_l23 chest
1 pelvis12 clavicle_r24 gun
2 spine_013 shoulder_r25 eye_l
3 spine_114 elbow_r26 eye_r
4 spine_215 hand_r27 random_bone
5 spine_317 hip_l28 cvj_bone
6 neck18 knee_l74 foot_toes_l_t
7 head19 foot_heel_l77 foot_toes_r_t
8 clavicle_l20 hip_r81 foot_toes_l_ct
9 shoulder_l21 knee_r86 foot_toes_r_ct
10 elbow_l22 foot_heel_r

Input actions and devices#

The action names dismhook.input.binding accepts, and the device numbers it returns:

ActionDevice valueDevice
forward0none
back1keyboard
left2mouse_primary
right3mouse_secondary
walk4mouse_middle
duck5mouse_auxiliary1
jump6mouse_auxiliary2
attack
attack2

Bomb sites#

ValueSite
0A
1B

Script states#

The state shown next to a script in the Scripts tab: Stopped, Starting, Running, Error, Over budget. See How scripts run.

Virtual-key codes#

input.is_down, input.key, input.tap and ui.keybind use Windows virtual-key codes. Write them in hexadecimal, as in 0x75 for F6, or, for letters and digits, from the character: string.byte("X") is 88, the code of the X key.

KeyCode
Left mouse button0x01 (1)
Right mouse button0x02 (2)
Middle mouse button0x04 (4)
Mouse side button 1 (back)0x05 (5)
Mouse side button 2 (forward)0x06 (6)
Backspace0x08 (8)
Tab0x09 (9)
Enter0x0D (13)
Shift0x10 (16)
Ctrl0x11 (17)
Alt0x12 (18)
Pause0x13 (19)
Caps Lock0x14 (20)
Esc0x1B (27)
Space0x20 (32)
Page Up0x21 (33)
Page Down0x22 (34)
End0x23 (35)
Home0x24 (36)
Left arrow0x25 (37)
Up arrow0x26 (38)
Right arrow0x27 (39)
Down arrow0x28 (40)
Insert0x2D (45)
Delete0x2E (46)
0 to 9 (main row)0x30 to 0x39 (48 to 57)
A to Z0x41 to 0x5A (65 to 90)
Numpad 0 to 90x60 to 0x69 (96 to 105)
Numpad *0x6A (106)
Numpad +0x6B (107)
Numpad -0x6D (109)
Numpad .0x6E (110)
Numpad /0x6F (111)
F1 to F120x70 to 0x7B (112 to 123)
Num Lock0x90 (144)
Scroll Lock0x91 (145)
Left Shift0xA0 (160)
Right Shift0xA1 (161)
Left Ctrl0xA2 (162)
Right Ctrl0xA3 (163)
Left Alt0xA4 (164)
Right Alt0xA5 (165)
; :0xBA (186)
= +0xBB (187)
, <0xBC (188)
- _0xBD (189)
. >0xBE (190)
/ ?0xBF (191)
` ~0xC0 (192)
[ {0xDB (219)
\ |0xDC (220)
] }0xDD (221)
' "0xDE (222)
lua
local VK_F6, VK_X, VK_SPACE = 0x75, string.byte("X"), 0x20
local VK_LBUTTON = 0x01      -- the mouse buttons are keys too

Examples#

Every example below was run before it went into this page: the file is loaded, its top level has to finish within the 8 ms budget, load is delivered twice like on a first start, and then ticks, frames and the events it uses are fed to it. Strict globals are on, so a missing local is an error. Copy any of them into Documents\dh-<username>\scripts (or paste it into a new script in the built-in editor) and press Run.

Hello overlay#

The smallest script that puts something on screen. Shows where drawing belongs: inside the frame handler.

01_hello.lua
-- hello.lua
-- The smallest script that puts something on screen.
-- Drop it in Documents\dh-<username>\scripts, press Refresh in the Scripts tab, tick it on.

dismhook.log("hello.lua loaded (API " .. dismhook.api.major .. "." .. dismhook.api.minor .. ")")

-- everything that is drawn is drawn from the "frame" event, about 60 times a second
dismhook.events.on("frame", function(frame)
    local map = frame.map ~= "" and frame.map or "no map"
    dismhook.draw.panel(20, 200, 260, 64, "Hello from Lua", 90, 160, 255, 255)
    dismhook.draw.text(30, 226, string.format("%s  |  %d players", map, #frame.players), 235, 235, 240, 255)
end)

Enemy boxes, health bars and names#

Boxes around every enemy with a health bar, name, distance and weapon. Green when you have a clear line to them, red when not.

02_esp_boxes.lua
-- esp_boxes.lua
-- Boxes, health bars, names and distance for every enemy, using world_to_screen.
--
-- Two things worth knowing:
--   * player.bones is 1-based: bone id 7 (the head) is bones[8].
--   * colour channels must be whole numbers, so use math.floor() when a colour is calculated.

local ENEMIES_ONLY = true

local function health_colour(fraction)
    return math.floor(255 * (1 - fraction)), math.floor(255 * fraction), 60
end

dismhook.events.on("frame", function(frame)
    local me = frame.local_player
    local eye = frame.camera.origin

    for _, player in ipairs(frame.players) do
        if not ENEMIES_ONLY or player.team ~= me.team then
            local head = player.bones[8] or { x = player.origin.x, y = player.origin.y, z = player.origin.z + 64 }
            local top = dismhook.game.world_to_screen({ x = head.x, y = head.y, z = head.z + 9 })
            local bottom = dismhook.game.world_to_screen(player.origin)

            if top and bottom then
                local height = bottom.y - top.y
                if height > 4 then
                    local width = height * 0.45
                    local left = top.x - width / 2

                    -- the box: green while you can see them, red while they are behind something
                    local r, g, b = 255, 90, 90
                    if player.visible then r, g, b = 90, 255, 120 end
                    dismhook.draw.rect(left, top.y, width, height, r, g, b, 255, 1.5)

                    -- health bar on the left
                    local fraction = math.max(0, math.min(100, player.health)) / 100
                    dismhook.draw.filled_rect(left - 6, top.y, 3, height, 0, 0, 0, 170)
                    local hr, hg, hb = health_colour(fraction)
                    dismhook.draw.filled_rect(left - 6, top.y + height * (1 - fraction), 3, height * fraction, hr, hg, hb, 255)

                    -- name above, distance and weapon below
                    local dx, dy, dz = player.origin.x - eye.x, player.origin.y - eye.y, player.origin.z - eye.z
                    local distance = math.sqrt(dx * dx + dy * dy + dz * dz)
                    dismhook.draw.text(left, top.y - 14, player.name, 255, 255, 255, 255)
                    dismhook.draw.text(left, bottom.y + 2, string.format("%d u  %s", math.floor(distance), player.weapon.name), 200, 200, 210, 255)
                end
            end
        end
    end
end)

Event feed#

A fading list of what happened: damage, players leaving, weapon switches, the bomb. Built from events, not by polling the frame.

04_event_feed.lua
-- event_feed.lua
-- A fading feed of what happens in the round, built from events instead of polling.
--
-- Note: a player who dies is simply gone from frame.players, so a death shows up as
-- "player_left" (after a "player_hurt"), not as "player_killed".

local MAX_LINES = 8
local LIFETIME = 6.0            -- seconds a line stays
local FADE = 1.0                -- seconds it takes to fade out

local lines = {}
local now = 0                   -- seconds, kept up to date by the frame handler
local names = {}                -- pawn handle -> name, learned from the frames

local function push(text, r, g, b)
    table.insert(lines, 1, { text = text, born = now, r = r, g = g, b = b })
    while #lines > MAX_LINES do table.remove(lines) end
end

local function who(handle)
    return names[handle] or "someone"
end

dismhook.events.on("map_changed", function(map)
    lines = {}
    push("Map: " .. map, 255, 255, 255)
end)

dismhook.events.on("player_hurt", function(e)
    push(string.format("%s lost %d hp (%d left)", who(e.handle), e.damage, e.health), 255, 200, 90)
end)

dismhook.events.on("player_left", function(handle)
    push(who(handle) .. " is gone", 255, 120, 120)
end)

dismhook.events.on("weapon_changed", function(e)
    push(who(e.handle) .. " switched to " .. e.weapon.name, 150, 190, 255)
end)

dismhook.events.on("bomb_planted", function() push("Bomb planted", 255, 190, 60) end)
dismhook.events.on("bomb_defuse_started", function() push("Defuse started", 120, 255, 160) end)
dismhook.events.on("bomb_defused", function() push("Bomb defused", 120, 255, 160) end)

dismhook.events.on("frame", function(frame)
    now = frame.timestamp_us / 1000000
    for _, player in ipairs(frame.players) do names[player.handle] = player.name end

    local y = 400
    for _, line in ipairs(lines) do
        local age = now - line.born
        if age < LIFETIME then
            local alpha = math.floor(255 * math.min(1, (LIFETIME - age) / FADE))
            dismhook.draw.text(20, y, line.text, line.r, line.g, line.b, alpha)
            y = y + 15
        end
    end
end)

Where will that grenade land?#

For each grenade in the air, simulates the rest of its flight and draws the path, the bounces and the landing point with the time left.

06_grenade_paths.lua
-- grenade_paths.lua
-- Draws where every grenade in the air is going to land, with a countdown to the bang.
--
-- projectile.kind: 1 HE, 2 flash, 3 smoke, 4 molotov, 5 molotov fire (no flight), 6 decoy

local NAMES = { [1] = "he", [2] = "flash", [3] = "smoke", [4] = "molotov", [6] = "decoy" }

dismhook.events.on("frame", function(frame)
    for _, projectile in ipairs(frame.projectiles) do
        local name = NAMES[projectile.kind]
        if name and not projectile.detonated then
            -- predict from where the grenade is now, at the speed it has now
            local path = dismhook.game.predict_grenade(projectile.origin, projectile.velocity, name)
            if path and path.valid then
                local previous
                for _, point in ipairs(path.points) do
                    if previous then
                        dismhook.draw.world_line(previous, point, 255, 200, 80, 200, 1.5)
                    end
                    previous = point
                end

                for _, bounce in ipairs(path.bounces) do
                    local spot = dismhook.game.world_to_screen(bounce)
                    if spot then dismhook.draw.filled_circle(spot.x, spot.y, 2.5, 255, 255, 255, 220) end
                end

                local landing = dismhook.game.world_to_screen(path.end_position)
                if landing then
                    dismhook.draw.circle(landing.x, landing.y, 7, 255, 200, 80, 255, 2)
                    dismhook.draw.text(landing.x + 11, landing.y - 6, string.format("%s  %.1f s", name, path.duration), 255, 255, 255, 255)
                end
            end
        end
    end
end)

Change settings with a key#

F6 turns the player ESP on and off, F7 the radar, by writing to the program's settings. Shows edge detection (act on the press, not while held) and the config functions.

07_hotkey_toggle.lua
-- hotkey_toggle.lua
-- Flip settings of the program with a key: F6 for player ESP, F7 for the radar.
-- Uses dismhook.config to read and write any setting by its path, and shows a small toast.
--
-- Virtual-key codes: F6 is 0x75, F7 is 0x76 (see the key table in the reference).

local HOTKEYS = {
    { vk = 0x75, path = "esp.m_player.enabled", label = "Player ESP" },
    { vk = 0x76, path = "esp.m_radar.enabled",  label = "Radar" },
}

local was_down = {}
local toast, toast_until = "", 0

-- "tick" runs every 16 ms, whatever the game is doing
dismhook.events.on("tick", function()
    for _, hotkey in ipairs(HOTKEYS) do
        local down = dismhook.input.is_down(hotkey.vk)
        if down and not was_down[hotkey.vk] then          -- only on the press, not while held
            local current = dismhook.config.get(hotkey.path)
            if type(current) == "boolean" then
                dismhook.config.set(hotkey.path, not current)   -- queued, applied on the next frame
                toast = hotkey.label .. (current and " off" or " on")
                toast_until = os.time() + 2
            else
                dismhook.log("no such setting: " .. hotkey.path)
            end
        end
        was_down[hotkey.vk] = down
    end
end)

-- raised after a script's config change has been applied (not for changes made in the menu)
dismhook.events.on("config_changed", function()
    dismhook.log("config changed by a script")
end)

-- registering a frame handler is what makes the toast possible
dismhook.events.on("frame", function()
    if os.time() < toast_until then
        dismhook.draw.panel(20, 120, 200, 40, toast, 90, 160, 255, 255)
    end
end)

A radar with the game's own map picture#

A radar in the corner of the screen. Exports the map's overview picture once per map, then places every player on it.

08_radar.lua
-- radar.lua
-- A radar in the corner of the screen, on top of the game's own overview image for the map.
-- Save it directly in the scripts folder (not in a sub-folder): the exported picture is
-- loaded from .data\<script name>\, which is only reachable from there.

local SIZE = 240                     -- radar size in pixels
local LEFT, TOP = 20, 20             -- where it sits on screen

local id = dismhook.api.data_dir:match("[^\\/]+$")     -- this script's name, as the runtime knows it
local overview, texture, loaded_for = nil, nil, nil

local function load_overview(map)
    loaded_for, overview, texture = map, nil, nil
    if map == "" then return end
    -- this reads and decodes the game's own map image: do it once per map, never per frame
    local info, err = dismhook.game.radar_overview(map, "overview")
    if not info then
        dismhook.log("no radar image for " .. map .. ": " .. tostring(err))
        return
    end
    overview = info
    texture = dismhook.draw.load_texture(".data/" .. id .. "/overview-primary.png")
end

-- world position -> pixel on the radar
local function to_radar(pos)
    local u = (pos.x - overview.pos_x) / overview.scale         -- pixel on the overview image
    local v = (overview.pos_y - pos.y) / overview.scale
    return LEFT + u / overview.width * SIZE, TOP + v / overview.height * SIZE
end

dismhook.events.on("frame", function(frame)
    if frame.map ~= loaded_for then load_overview(frame.map) end
    if not overview then return end

    dismhook.draw.filled_rect(LEFT, TOP, SIZE, SIZE, 12, 13, 18, 200)
    if texture then dismhook.draw.image(texture, LEFT, TOP, SIZE, SIZE, 255, 255, 255, 235) end

    for _, player in ipairs(frame.players) do
        local x, y = to_radar(player.origin)
        if x >= LEFT and x <= LEFT + SIZE and y >= TOP and y <= TOP + SIZE then
            local mine = player.team == frame.local_player.team
            if mine then
                dismhook.draw.filled_circle(x, y, 3.5, 90, 170, 255, 255)
            else
                dismhook.draw.filled_circle(x, y, 3.5, 255, 90, 90, 255)
            end
        end
    end

    local x, y = to_radar(frame.camera.origin)                    -- you
    dismhook.draw.circle(x, y, 4.5, 255, 255, 255, 255, 1.5)
    dismhook.draw.rect(LEFT, TOP, SIZE, SIZE, 54, 57, 69, 255, 1)
end)

Who is watching me?#

A red panel with the names of everyone spectating you, plus a log line whenever someone starts or stops.

09_spectator_alert.lua
-- spectator_alert.lua
-- A red panel with the names of everyone who is watching you.
-- (The list is refreshed about five times a second.)

local X, Y, W = 20, 560, 230

dismhook.events.on("frame", function(frame)
    local count = #frame.spectators
    if count == 0 then return end

    dismhook.draw.panel(X, Y, W, 34 + count * 16, "Watched by " .. count, 255, 90, 90, 255)
    for index, spectator in ipairs(frame.spectators) do
        dismhook.draw.text(X + 10, Y + 28 + (index - 1) * 16, spectator.name, 235, 235, 240, 255)
    end
end)

-- the same information as a one-off message: when somebody starts or stops watching
local known = {}
dismhook.events.on("tick", function()
    local frame = dismhook.game.snapshot()
    if not frame then return end
    local seen = {}
    for _, spectator in ipairs(frame.spectators) do
        seen[spectator.steam_id64] = true
        if not known[spectator.steam_id64] then dismhook.log(spectator.name .. " started watching you") end
    end
    for steam_id in pairs(known) do
        if not seen[steam_id] then dismhook.log("a spectator stopped watching you") end
    end
    known = seen
end)

Toggle crouch on a key#

Press X once to crouch and stay crouched, again to stand up. Looks up the key you bound to crouch in the game.

10_crouch_toggle.lua
-- crouch_toggle.lua
-- Press X once to crouch and stay crouched, press it again to stand up.
-- It presses whatever key you have bound to crouch in the game, found with dismhook.input.binding.
--
-- Input is only sent while the menu is closed and the game window is ready; the runtime lets go
-- of every key the script holds when that stops being true, and when the script stops.

local HOTKEY = 0x58                 -- X
local crouch_key                    -- the virtual key the game's crouch is bound to
local was_down, crouching = false, false

local function find_crouch_key()
    local binding = dismhook.input.binding("duck")
    if binding and binding.device == 1 then     -- 1 = a keyboard key (mouse buttons are 2 and up)
        crouch_key = binding.virtual_key
    end
end

-- "load" can arrive more than once on the very first start, so keep it harmless to repeat
dismhook.events.on("load", find_crouch_key)

dismhook.events.on("tick", function()
    local down = dismhook.input.is_down(HOTKEY)
    if down and not was_down and crouch_key then
        crouching = not crouching
        if not dismhook.input.key(crouch_key, crouching) then
            crouching = false                       -- refused: the menu is open or the game is not ready
        end
    end
    was_down = down
end)

-- the runtime releases held keys when the menu opens; keep our own idea of it in step
dismhook.events.on("frame", function(frame)
    if frame.menu_open or not frame.input_ready then crouching = false end
end)

A template to start from#

Every part of a script where it belongs: setup, load and unload, tick, game events, and a frame handler protected with pcall.

12_template.lua
-- template.lua
-- A starting point: every part of a script in the place it belongs.
-- Copy it, rename it, delete what you do not need.

------------------------------------------------------------------------------------------
-- 1. Top of the file. This runs once, when the script starts (and again after every reload).
--    Keep it quick: the whole chunk has to finish within 8 ms.
------------------------------------------------------------------------------------------
local SETTINGS = {
    panel_x = 20,
    panel_y = 200,
}

-- whatever you saved last time (nil the first time)
local saved = dismhook.storage.get("state") or { runs = 0 }

------------------------------------------------------------------------------------------
-- 2. Lifecycle events
------------------------------------------------------------------------------------------
-- "load": the script is up. On the very first start it can arrive more than once, so make
-- what you do here safe to repeat.
dismhook.events.on("load", function()
    dismhook.log("loaded")
end)

-- "unload": the script is stopping or being replaced by a new version of the file.
-- Keys and mouse buttons the script holds are released by the runtime; save what you need.
dismhook.events.on("unload", function()
    saved.runs = saved.runs + 1
    dismhook.storage.set("state", saved)
end)

------------------------------------------------------------------------------------------
-- 3. "tick": every 16 ms, always. For logic that does not need the game's data:
--    polling keys, timers, talking to dismhook.config.
------------------------------------------------------------------------------------------
local elapsed = 0
dismhook.events.on("tick", function(dt)
    elapsed = elapsed + dt
end)

------------------------------------------------------------------------------------------
-- 4. Game events: they only fire once you register for them, and only while a game is running.
------------------------------------------------------------------------------------------
dismhook.events.on("map_changed", function(map)
    dismhook.log("now on " .. map)
end)

------------------------------------------------------------------------------------------
-- 5. "frame": the game's data plus the one place you may draw. About 60 times a second.
--    A handler that errors stops the whole script (it shows "Error" in the list) until you
--    save the file again, so guard anything that can go wrong with pcall.
------------------------------------------------------------------------------------------
local function draw_panel(frame)
    dismhook.draw.panel(SETTINGS.panel_x, SETTINGS.panel_y, 240, 60, "Template", 90, 160, 255, 255)
    dismhook.draw.text(SETTINGS.panel_x + 10, SETTINGS.panel_y + 26,
        string.format("%d players, up %.0f s", #frame.players, elapsed), 235, 235, 240, 255)
end

dismhook.events.on("frame", function(frame)
    local ok, err = pcall(draw_panel, frame)
    if not ok then dismhook.log("draw failed: " .. tostring(err)) end
end)

A folder script with a manifest and a module#

Three files in scripts\kill_tracker\. The manifest names the script; main.lua runs; require("lib.util") finds the module next to it.

kill_tracker\manifest.json
{
    "id": "kill_tracker",
    "name": "Kill tracker",
    "version": "1.2.0",
    "author": "you",
    "description": "Counts the kills you confirm, and how long the map has been running",
    "api_version": 2,
    "entry": "main.lua"
}
kill_tracker\main.lua
-- kill_tracker\main.lua
-- The entry point named in manifest.json. It can require() anything inside this folder:
-- "lib.util" is the file lib\util.lua next to this one.

local util = require("lib.util")

local kills = 0
local started = nil

dismhook.events.on("map_changed", function()
    kills = 0
    started = nil
end)

dismhook.events.on("confirmed_hit", function(hit)
    if hit.killed then kills = kills + 1 end
end)

dismhook.events.on("frame", function(frame)
    started = started or frame.local_player.game_time
    local up = frame.local_player.game_time - started
    dismhook.draw.text(20, 640, string.format("Kills: %d   Map time: %s", kills, util.clock(up)), 255, 255, 255, 255)
end)
kill_tracker\lib\util.lua
-- kill_tracker\lib\util.lua
-- A module: whatever it returns is what require("lib.util") gives back.

local util = {}

-- 75.4 -> "1:15"
function util.clock(seconds)
    seconds = math.max(0, math.floor(seconds))
    return string.format("%d:%02d", seconds // 60, seconds % 60)
end

function util.clamp(value, low, high)
    return math.max(low, math.min(high, value))
end

return util

Recipes#

Short pieces to copy into your own scripts.

Distance between two points#

Positions are tables with x, y and z.

lua
local function distance(a, b)
    local dx, dy, dz = a.x - b.x, a.y - b.y, a.z - b.z
    return math.sqrt(dx * dx + dy * dy + dz * dz)
end

-- in a frame handler:  distance(player.origin, frame.camera.origin)

Act on a key press, not while it is held#

Keep the previous state per key.

lua
local last = {}
local function pressed(vk)
    local down = dismhook.input.is_down(vk)
    local fired = down and not last[vk]
    last[vk] = down
    return fired
end

dismhook.events.on("tick", function()
    if pressed(0x75) then dismhook.log("F6") end     -- call it once per tick for each key
end)

Find a player by handle#

Events give you a handle; the frame gives you the player. Index the frame once.

lua
local by_handle = {}

dismhook.events.on("frame", function(frame)
    by_handle = {}
    for _, player in ipairs(frame.players) do by_handle[player.handle] = player end
end)

dismhook.events.on("player_hurt", function(e)
    local player = by_handle[e.handle]
    if player then dismhook.log(player.name .. " took " .. e.damage) end
end)

Roughly centre a piece of text#

There is no text measuring, but every character is about 6.4 pixels wide.

lua
local function centred_text(cx, y, text, r, g, b, a)
    dismhook.draw.text(cx - #text * 3.2, y, text, r, g, b, a)
end

-- centred_text(frame.screen.width / 2, 80, "ROUND START", 255, 255, 255, 255)

Do something every N seconds#

tick gives you dt, so accumulate it.

lua
local elapsed, every = 0, 5.0

dismhook.events.on("tick", function(dt)
    elapsed = elapsed + dt
    if elapsed >= every then
        elapsed = elapsed - every
        dismhook.log("five seconds passed")
    end
end)

Only work when something changed#

A frame arrives up to 60 times a second. Compare sequence or your own state before doing anything expensive.

lua
local last_map = nil

dismhook.events.on("frame", function(frame)
    if frame.map ~= last_map then
        last_map = frame.map
        dismhook.log("map changed to " .. frame.map)     -- runs once, not every frame
    end
end)

Is this enemy looking at me?#

Compare the direction from the enemy to you with where the enemy is looking.

lua
local function angle_difference(a, b)                 -- degrees, -180..180
    return (a - b + 180) % 360 - 180
end

local function is_looking_at_me(player, my_position, tolerance)
    local dx, dy = my_position.x - player.origin.x, my_position.y - player.origin.y
    local yaw_to_me = math.deg(math.atan(dy, dx))     -- Lua 5.4: atan(y, x)
    return math.abs(angle_difference(player.eye_angles.y, yaw_to_me)) < (tolerance or 10)
end

Keep a mistake from killing the script#

An error in a handler puts the script into Error. pcall turns it into a log line.

lua
dismhook.events.on("frame", function(frame)
    local ok, err = pcall(function()
        -- everything that might go wrong
        local first = frame.players[1]
        dismhook.draw.text(20, 20, first.name, 255, 255, 255, 255)   -- fails when there is no first player
    end)
    if not ok then dismhook.log("frame failed: " .. tostring(err)) end
end)

Read a setting you saved, with a default#

Storage returns nil the first time.

lua
local options = dismhook.storage.get("options") or {}
options.scale = options.scale or 1.0
options.show_names = options.show_names ~= false      -- true unless it was saved as false

local function save() dismhook.storage.set("options", options) end

Draw a bar#

A background and a fill; fraction is 0 to 1.

lua
local function bar(x, y, w, h, fraction, r, g, b)
    fraction = math.max(0, math.min(1, fraction))
    dismhook.draw.filled_rect(x, y, w, h, 20, 22, 30, 200)
    dismhook.draw.filled_rect(x, y, w * fraction, h, r, g, b, 255)
    dismhook.draw.rect(x, y, w, h, 0, 0, 0, 255, 1)
end

Config reference#

Every setting that dismhook.config can read and write, as of this version: 927 settings in 552 paths. The lists are generated from the program's own default config, so the paths, types and defaults are exact.

How to read it#

  • Path: pass it to dismhook.config.get / set exactly as written.
  • Type: boolean, integer, number, string, color (a table { r, g, b, a } of integers 0 to 255) or list. A change is only accepted when the new value has the same type as the old one; an integer and a number are interchangeable.
  • Default: the value of a fresh, raw config. It is what a setting has after Reset in the Configs tab, not what your saved config has.
  • A setting that is a choice in the menu (a drop-down, or a list of options) is an integer: the 0-based position of the choice, in the order the menu lists it. A multi-choice is a bit mask.
  • A key is a Windows virtual-key code; 0 means not bound.
  • Some settings only take effect on the next start of the program: the overlay device settings (misc.render_type, misc.device_type).

esp: visuals#

esp.*: the visuals: player ESP, chams, items, projectiles, the bomb, the radar, the crosshair, sounds. 283 settings.

esp.m_bomb16 settings
PathTypeDefault
esp.m_bomb.active_bomb_colorcolorr 255, g 200, b 50, a 255
esp.m_bomb.bomb_color_ctcolorr 50, g 180, b 255, a 255
esp.m_bomb.bomb_color_tcolorr 255, g 180, b 50, a 255
esp.m_bomb.display_modeinteger0
esp.m_bomb.enabledbooleanfalse
esp.m_bomb.panel_backgroundcolorr 12, g 13, b 18, a 225
esp.m_bomb.safe_zone_band_stepnumber4
esp.m_bomb.safe_zone_bandsinteger1
esp.m_bomb.safe_zone_colorcolorr 70, g 235, b 105, a 240
esp.m_bomb.safe_zone_draw_radiusnumber1200
esp.m_bomb.show_active_bombbooleantrue
esp.m_bomb.show_info_panelbooleanfalse
esp.m_bomb.show_planted_bombbooleantrue
esp.m_bomb.show_safe_zonebooleantrue
esp.m_bomb.show_timerbooleantrue
esp.m_bomb.timer_text_colorcolorr 255, g 220, b 100, a 255
esp.m_chams52 settings
PathTypeDefault
esp.m_chams.antialiasingbooleantrue
esp.m_chams.enabledbooleanfalse
esp.m_chams.glow_effect.colorcolorr 125, g 85, b 255, a 150
esp.m_chams.glow_effect.enabledbooleanfalse
esp.m_chams.glow_effect.layersinteger3
esp.m_chams.glow_effect.radiusnumber4
esp.m_chams.glow_effect.strengthnumber0.65
esp.m_chams.invisible.colorcolorr 60, g 71, b 255, a 200
esp.m_chams.invisible.enabledbooleanfalse
esp.m_chams.invisible.exponentnumber2
esp.m_chams.invisible.falloffnumber0.5
esp.m_chams.invisible.fresnel_fillnumber0.25
esp.m_chams.invisible.metalnessnumber0
esp.m_chams.invisible.roughnessnumber0.35
esp.m_chams.invisible.speednumber1.2
esp.m_chams.invisible.strengthnumber1
esp.m_chams.invisible.tintcolorr 120, g 200, b 255, a 255
esp.m_chams.invisible.typeinteger5
esp.m_chams.invisible.wireframebooleanfalse
esp.m_chams.kill_effect.colorcolorr 170, g 125, b 255, a 220
esp.m_chams.kill_effect.countinteger14
esp.m_chams.kill_effect.durationnumber0.9
esp.m_chams.kill_effect.enabledbooleanfalse
esp.m_chams.kill_effect.sizenumber3
esp.m_chams.occlude_dynamic_doorsbooleantrue
esp.m_chams.occlude_smokebooleantrue
esp.m_chams.on_shot.appearance.colorcolorr 255, g 185, b 80, a 150
esp.m_chams.on_shot.appearance.enabledbooleanfalse
esp.m_chams.on_shot.appearance.exponentnumber3
esp.m_chams.on_shot.appearance.falloffnumber0.18
esp.m_chams.on_shot.appearance.fresnel_fillnumber0.22
esp.m_chams.on_shot.appearance.metalnessnumber0
esp.m_chams.on_shot.appearance.roughnessnumber0.35
esp.m_chams.on_shot.appearance.speednumber1
esp.m_chams.on_shot.appearance.strengthnumber1
esp.m_chams.on_shot.appearance.tintcolorr 120, g 200, b 255, a 255
esp.m_chams.on_shot.appearance.typeinteger3
esp.m_chams.on_shot.appearance.wireframebooleanfalse
esp.m_chams.on_shot.durationnumber0.55
esp.m_chams.on_shot.enabledbooleanfalse
esp.m_chams.visible.colorcolorr 136, g 60, b 255, a 184
esp.m_chams.visible.enabledbooleanfalse
esp.m_chams.visible.exponentnumber5.1
esp.m_chams.visible.falloffnumber0.05
esp.m_chams.visible.fresnel_fillnumber0.72
esp.m_chams.visible.metalnessnumber0
esp.m_chams.visible.roughnessnumber0.35
esp.m_chams.visible.speednumber4
esp.m_chams.visible.strengthnumber0.33
esp.m_chams.visible.tintcolorr 120, g 200, b 255, a 255
esp.m_chams.visible.typeinteger3
esp.m_chams.visible.wireframebooleanfalse
esp.m_crosshair17 settings
PathTypeDefault
esp.m_crosshair.colorcolorr 255, g 255, b 255, a 255
esp.m_crosshair.copy_gamebooleantrue
esp.m_crosshair.dotbooleantrue
esp.m_crosshair.enabledbooleanfalse
esp.m_crosshair.gapnumber3
esp.m_crosshair.lengthnumber5
esp.m_crosshair.linesbooleantrue
esp.m_crosshair.outlinebooleantrue
esp.m_crosshair.outline_colorcolorr 0, g 0, b 0, a 255
esp.m_crosshair.outline_thicknessnumber1
esp.m_crosshair.penetration_color_nocolorr 255, g 0, b 0, a 255
esp.m_crosshair.penetration_color_yescolorr 0, g 255, b 0, a 255
esp.m_crosshair.penetration_enabledbooleanfalse
esp.m_crosshair.penetration_min_damagenumber90
esp.m_crosshair.syncbooleantrue
esp.m_crosshair.t_stylebooleanfalse
esp.m_crosshair.thicknessnumber1
esp.m_item17 settings
PathTypeDefault
esp.m_item.enabledbooleanfalse
esp.m_item.m_ammo.colorcolorr 255, g 255, b 255, a 200
esp.m_item.m_ammo.empty_colorcolorr 220, g 80, b 80, a 200
esp.m_item.m_ammo.enabledbooleanfalse
esp.m_item.m_filters.grenadesbooleantrue
esp.m_item.m_filters.heavybooleantrue
esp.m_item.m_filters.pistolsbooleantrue
esp.m_item.m_filters.riflesbooleantrue
esp.m_item.m_filters.shotgunsbooleantrue
esp.m_item.m_filters.smgsbooleantrue
esp.m_item.m_filters.snipersbooleantrue
esp.m_item.m_filters.utilitybooleantrue
esp.m_item.m_icon.colorcolorr 255, g 255, b 255, a 220
esp.m_item.m_icon.enabledbooleanfalse
esp.m_item.m_name.colorcolorr 200, g 200, b 200, a 180
esp.m_item.m_name.enabledbooleanfalse
esp.m_item.max_distancenumber40
esp.m_no_flash4 settings
PathTypeDefault
esp.m_no_flash.background_colorcolorr 0, g 0, b 0, a 255
esp.m_no_flash.enabledbooleanfalse
esp.m_no_flash.max_distancenumber1000
esp.m_no_flash.wireframe_colorcolorr 200, g 200, b 200, a 150
esp.m_no_smoke2 settings
PathTypeDefault
esp.m_no_smoke.enabledbooleanfalse
esp.m_no_smoke.wireframe_colorcolorr 190, g 210, b 220, a 180
esp.m_player122 settings
PathTypeDefault
esp.m_player.activation_keyinteger0
esp.m_player.activation_modeinteger0
esp.m_player.enabledbooleanfalse
esp.m_player.m_armor_bar.background_colorcolorr 10, g 10, b 14, a 220
esp.m_player.m_armor_bar.enabledbooleanfalse
esp.m_player.m_armor_bar.full_colorcolorr 50, g 80, b 220, a 255
esp.m_player.m_armor_bar.gradientbooleanfalse
esp.m_player.m_armor_bar.low_colorcolorr 50, g 80, b 220, a 255
esp.m_player.m_armor_bar.outlinebooleantrue
esp.m_player.m_armor_bar.outline_colorcolorr 0, g 0, b 0, a 255
esp.m_player.m_armor_bar.outline_thicknessnumber1
esp.m_player.m_armor_bar.positioninteger2
esp.m_player.m_armor_bar.segment_gapnumber1
esp.m_player.m_armor_bar.segmentsinteger1
esp.m_player.m_armor_bar.show_valuebooleanfalse
esp.m_player.m_armor_bar.text_colorcolorr 245, g 245, b 250, a 255
esp.m_player.m_armor_bar.thicknessnumber3.5
esp.m_player.m_box.corner_lengthnumber10
esp.m_player.m_box.enabledbooleanfalse
esp.m_player.m_box.fillbooleanfalse
esp.m_player.m_box.fill_occluded_colorcolorr 100, g 100, b 110, a 80
esp.m_player.m_box.fill_visible_colorcolorr 60, g 200, b 100, a 80
esp.m_player.m_box.occluded_colorcolorr 160, g 50, b 50, a 180
esp.m_player.m_box.outlinebooleanfalse
esp.m_player.m_box.styleinteger1
esp.m_player.m_box.visible_colorcolorr 220, g 60, b 60, a 255
esp.m_player.m_head_circle.colorcolorr 255, g 255, b 255, a 255
esp.m_player.m_head_circle.enabledbooleanfalse
esp.m_player.m_head_circle.thicknessnumber1
esp.m_player.m_health_bar.background_colorcolorr 0, g 17, b 28, a 220
esp.m_player.m_health_bar.enabledbooleanfalse
esp.m_player.m_health_bar.full_colorcolorr 215, g 50, b 220, a 255
esp.m_player.m_health_bar.gradientbooleantrue
esp.m_player.m_health_bar.low_colorcolorr 220, g 50, b 50, a 255
esp.m_player.m_health_bar.outlinebooleantrue
esp.m_player.m_health_bar.outline_colorcolorr 0, g 0, b 0, a 255
esp.m_player.m_health_bar.outline_thicknessnumber1
esp.m_player.m_health_bar.positioninteger0
esp.m_player.m_health_bar.segment_gapnumber2.5
esp.m_player.m_health_bar.segmentsinteger1
esp.m_player.m_health_bar.show_valuebooleantrue
esp.m_player.m_health_bar.text_colorcolorr 245, g 245, b 250, a 255
esp.m_player.m_health_bar.thicknessnumber3
esp.m_player.m_hitboxes.enabledbooleanfalse
esp.m_player.m_hitboxes.fillbooleantrue
esp.m_player.m_hitboxes.occluded_colorcolorr 70, g 70, b 80, a 12
esp.m_player.m_hitboxes.outlinebooleantrue
esp.m_player.m_hitboxes.visible_colorcolorr 240, g 240, b 245, a 15
esp.m_player.m_info_flags.armor.colorcolorr 60, g 120, b 220, a 255
esp.m_player.m_info_flags.armor.scalenumber1
esp.m_player.m_info_flags.bomb_damage.colorcolorr 255, g 120, b 120, a 255
esp.m_player.m_info_flags.bomb_damage.scalenumber1
esp.m_player.m_info_flags.defusing.colorcolorr 220, g 60, b 60, a 255
esp.m_player.m_info_flags.defusing.scalenumber1
esp.m_player.m_info_flags.distance.colorcolorr 130, g 130, b 145, a 255
esp.m_player.m_info_flags.distance.scalenumber1
esp.m_player.m_info_flags.enabledbooleanfalse
esp.m_player.m_info_flags.flagsinteger383
esp.m_player.m_info_flags.flashed.colorcolorr 200, g 200, b 210, a 255
esp.m_player.m_info_flags.flashed.scalenumber1
esp.m_player.m_info_flags.kit.colorcolorr 180, g 180, b 200, a 255
esp.m_player.m_info_flags.kit.scalenumber1
esp.m_player.m_info_flags.money.colorcolorr 60, g 210, b 100, a 255
esp.m_player.m_info_flags.money.scalenumber1
esp.m_player.m_info_flags.ping.colorcolorr 98, g 217, b 109, a 255
esp.m_player.m_info_flags.ping.scalenumber1
esp.m_player.m_info_flags.scoped.colorcolorr 240, g 240, b 245, a 255
esp.m_player.m_info_flags.scoped.scalenumber1
esp.m_player.m_layout.armor.scalenumber1
esp.m_player.m_layout.armor.xnumber0.5
esp.m_player.m_layout.armor.ynumber1.012
esp.m_player.m_layout.flags.scalenumber1
esp.m_player.m_layout.flags.xnumber0.949291
esp.m_player.m_layout.flags.ynumber-0.00905667
esp.m_player.m_layout.health.scalenumber0.92
esp.m_player.m_layout.health.xnumber-0.0177352
esp.m_player.m_layout.health.ynumber0.5
esp.m_player.m_layout.name.scalenumber1.16
esp.m_player.m_layout.name.xnumber0.494192
esp.m_player.m_layout.name.ynumber-0.0189946
esp.m_player.m_layout.weapon.scalenumber1
esp.m_player.m_layout.weapon.xnumber0.5
esp.m_player.m_layout.weapon.ynumber1.039
esp.m_player.m_name.colorcolorr 245, g 245, b 250, a 240
esp.m_player.m_name.enabledbooleanfalse
esp.m_player.m_offscreen_arrows.bloombooleantrue
esp.m_player.m_offscreen_arrows.bloom_colorcolorr 120, g 190, b 255, a 210
esp.m_player.m_offscreen_arrows.bloom_max_alphanumber0.68
esp.m_player.m_offscreen_arrows.bloom_min_alphanumber0.12
esp.m_player.m_offscreen_arrows.bloom_radiusnumber6
esp.m_player.m_offscreen_arrows.bloom_speednumber0.5
esp.m_player.m_offscreen_arrows.colorcolorr 204, g 230, b 241, a 255
esp.m_player.m_offscreen_arrows.enabledbooleanfalse
esp.m_player.m_offscreen_arrows.radiusnumber377
esp.m_player.m_offscreen_arrows.sizenumber22
esp.m_player.m_offscreen_arrows.thicknessnumber2.25
esp.m_player.m_skeleton.enabledbooleanfalse
esp.m_player.m_skeleton.occluded_colorcolorr 255, g 255, b 255, a 255
esp.m_player.m_skeleton.thicknessnumber1
esp.m_player.m_skeleton.visible_colorcolorr 255, g 255, b 255, a 255
esp.m_player.m_threat_module.body_colorcolorr 255, g 255, b 255, a 70
esp.m_player.m_threat_module.body_hitboxbooleantrue
esp.m_player.m_threat_module.enabledbooleanfalse
esp.m_player.m_threat_module.fill_alphanumber30
esp.m_player.m_threat_module.head_colorcolorr 230, g 50, b 50, a 110
esp.m_player.m_threat_module.head_hitboxbooleantrue
esp.m_player.m_threat_module.limb_colorcolorr 255, g 255, b 255, a 50
esp.m_player.m_threat_module.limb_hitboxbooleantrue
esp.m_player.m_threat_module.max_distancenumber60
esp.m_player.m_threat_module.outline_alphanumber220
esp.m_player.m_threat_module.outline_thicknessnumber1
esp.m_player.m_view_line.colorcolorr 255, g 255, b 255, a 255
esp.m_player.m_view_line.enabledbooleanfalse
esp.m_player.m_view_line.lengthnumber50
esp.m_player.m_view_line.thicknessnumber1
esp.m_player.m_weapon.ammo.empty_colorcolorr 225, g 55, b 65, a 245
esp.m_player.m_weapon.ammo.enabledbooleanfalse
esp.m_player.m_weapon.ammo.show_countbooleanfalse
esp.m_player.m_weapon.displayinteger1
esp.m_player.m_weapon.enabledbooleanfalse
esp.m_player.m_weapon.icon_colorcolorr 240, g 240, b 245, a 240
esp.m_player.m_weapon.text_colorcolorr 200, g 200, b 210, a 220
esp.m_projectile16 settings
PathTypeDefault
esp.m_projectile.color_decoycolorr 200, g 200, b 200, a 200
esp.m_projectile.color_flashcolorr 230, g 220, b 150, a 220
esp.m_projectile.color_hecolorr 220, g 150, b 150, a 220
esp.m_projectile.color_molotovcolorr 220, g 170, b 130, a 220
esp.m_projectile.color_smokecolorr 160, g 200, b 180, a 220
esp.m_projectile.default_colorcolorr 195, g 200, b 215, a 200
esp.m_projectile.display_modeinteger0
esp.m_projectile.enabledbooleanfalse
esp.m_projectile.indicator_backgroundcolorr 15, g 16, b 22, a 185
esp.m_projectile.inferno_gradient_opacitynumber45
esp.m_projectile.inferno_gradient_widthnumber34
esp.m_projectile.show_iconbooleantrue
esp.m_projectile.show_inferno_boundsbooleantrue
esp.m_projectile.show_timer_ringbooleantrue
esp.m_projectile.timer_high_colorcolorr 255, g 255, b 255, a 255
esp.m_projectile.timer_low_colorcolorr 220, g 100, b 100, a 255
esp.m_radar32 settings
PathTypeDefault
esp.m_radar.activation_keyinteger0
esp.m_radar.activation_modeinteger0
esp.m_radar.armor_colorcolorr 80, g 155, b 255, a 255
esp.m_radar.decoy_colorcolorr 140, g 190, b 255, a 255
esp.m_radar.direction_colorcolorr 255, g 255, b 255, a 255
esp.m_radar.enabledbooleanfalse
esp.m_radar.enemy_colorcolorr 255, g 64, b 64, a 255
esp.m_radar.flash_colorcolorr 250, g 245, b 180, a 255
esp.m_radar.he_colorcolorr 255, g 105, b 80, a 255
esp.m_radar.health_colorcolorr 95, g 230, b 125, a 255
esp.m_radar.information_scalenumber0.75
esp.m_radar.marker_scalenumber1
esp.m_radar.molotov_colorcolorr 255, g 145, b 55, a 255
esp.m_radar.name_colorcolorr 245, g 245, b 250, a 255
esp.m_radar.show_armorbooleantrue
esp.m_radar.show_grenade_zonesbooleantrue
esp.m_radar.show_healthbooleantrue
esp.m_radar.show_namesbooleantrue
esp.m_radar.show_projectilesbooleantrue
esp.m_radar.show_trajectoriesbooleantrue
esp.m_radar.show_weaponbooleantrue
esp.m_radar.smoke_colorcolorr 180, g 190, b 205, a 255
esp.m_radar.status_colorcolorr 225, g 230, b 240, a 245
esp.m_radar.text_outlinebooleantrue
esp.m_radar.text_outline_colorcolorr 0, g 0, b 0, a 230
esp.m_radar.text_outline_thicknessnumber1
esp.m_radar.trajectory_endpoint_sizenumber3
esp.m_radar.trajectory_thicknessnumber1.5
esp.m_radar.weapon_colorcolorr 205, g 212, b 225, a 245
esp.m_radar.zone_fill_alphanumber15
esp.m_radar.zone_outline_alphanumber59
esp.m_radar.zone_outline_thicknessnumber1
esp.m_sound5 settings
PathTypeDefault
esp.m_sound.colorcolorr 120, g 200, b 255, a 200
esp.m_sound.durationnumber1.6
esp.m_sound.enabledbooleanfalse
esp.m_sound.local_syncbooleanfalse
esp.m_sound.radiusnumber34

misc: general#

misc.*: general settings, panels, grenade helpers, performance and the Lua switch. 101 settings.

misc (top level)14 settings
PathTypeDefault
misc.auto_acceptbooleanfalse
misc.device_typeinteger0
misc.entity_intervalinteger50
misc.exit_keyinteger35
misc.fps_limitinteger240
misc.gpu_priorityinteger2
misc.limit_fpsbooleanfalse
misc.lua_enabledbooleanfalse
misc.menu_keyinteger45
misc.obs_bypassbooleanfalse
misc.process_priorityinteger2
misc.render_typeinteger0
misc.trace_intervalinteger5
misc.update_intervalinteger5
misc.m_bomb_info5 settings
PathTypeDefault
misc.m_bomb_info.layout.anchorinteger0
misc.m_bomb_info.layout.offset_xnumber40
misc.m_bomb_info.layout.offset_ynumber100
misc.m_bomb_info.layout.scalenumber1
misc.m_bomb_info.layout.versioninteger1
misc.m_bullet_tracers13 settings
PathTypeDefault
misc.m_bullet_tracers.bloombooleantrue
misc.m_bullet_tracers.colorcolorr 192, g 203, b 229, a 200
misc.m_bullet_tracers.cube_edge_colorcolorr 192, g 203, b 229, a 230
misc.m_bullet_tracers.cube_face_alphanumber38
misc.m_bullet_tracers.cube_halfnumber1.25
misc.m_bullet_tracers.draw_cubesbooleantrue
misc.m_bullet_tracers.draw_linebooleantrue
misc.m_bullet_tracers.durationnumber2
misc.m_bullet_tracers.enabledbooleanfalse
misc.m_bullet_tracers.fade_farnumber160
misc.m_bullet_tracers.fade_nearnumber45
misc.m_bullet_tracers.max_countinteger40
misc.m_bullet_tracers.thicknessnumber1.5
misc.m_edge_jump2 settings
PathTypeDefault
misc.m_edge_jump.activation_keyinteger0
misc.m_edge_jump.enabledbooleanfalse
misc.m_floating_damage5 settings
PathTypeDefault
misc.m_floating_damage.colorcolorr 255, g 108, b 112, a 255
misc.m_floating_damage.durationnumber0.75
misc.m_floating_damage.enabledbooleanfalse
misc.m_floating_damage.risenumber30
misc.m_floating_damage.sizenumber13
misc.m_grenades13 settings
PathTypeDefault
misc.m_grenades.bloombooleantrue
misc.m_grenades.bloom_colorcolorr 120, g 170, b 255, a 58
misc.m_grenades.bloom_radiusnumber3
misc.m_grenades.bounce_colorcolorr 120, g 170, b 255, a 235
misc.m_grenades.bounce_sizenumber4
misc.m_grenades.colorcolorr 255, g 255, b 255, a 200
misc.m_grenades.enabledbooleanfalse
misc.m_grenades.endpoint_colorcolorr 255, g 255, b 255, a 235
misc.m_grenades.endpoint_sizenumber8
misc.m_grenades.local_onlybooleanfalse
misc.m_grenades.show_bouncesbooleantrue
misc.m_grenades.show_endpointbooleantrue
misc.m_grenades.thicknessnumber2
misc.m_hitmarker6 settings
PathTypeDefault
misc.m_hitmarker.colorcolorr 245, g 247, b 250, a 255
misc.m_hitmarker.durationnumber0.55
misc.m_hitmarker.enabledbooleanfalse
misc.m_hitmarker.gapnumber3
misc.m_hitmarker.sizenumber8
misc.m_hitmarker.thicknessnumber1.5
misc.m_keybind_list9 settings
PathTypeDefault
misc.m_keybind_list.enabledbooleanfalse
misc.m_keybind_list.layout.anchorinteger0
misc.m_keybind_list.layout.offset_xnumber40
misc.m_keybind_list.layout.offset_ynumber340
misc.m_keybind_list.layout.scalenumber1
misc.m_keybind_list.layout.versioninteger1
misc.m_keybind_list.show_alwaysbooleantrue
misc.m_keybind_list.show_holdbooleantrue
misc.m_keybind_list.show_togglebooleantrue
misc.m_nade_helper17 settings
PathTypeDefault
misc.m_nade_helper.aim_assistbooleanfalse
misc.m_nade_helper.aim_keyinteger0
misc.m_nade_helper.aim_markercolorr 245, g 245, b 250, a 235
misc.m_nade_helper.aim_smoothinginteger18
misc.m_nade_helper.aim_thresholdnumber0.35
misc.m_nade_helper.auto_releasebooleantrue
misc.m_nade_helper.draw_distancenumber800
misc.m_nade_helper.enabledbooleanfalse
misc.m_nade_helper.height_tolerancenumber8
misc.m_nade_helper.lock_time_msinteger45
misc.m_nade_helper.release_radiusnumber6
misc.m_nade_helper.show_actionbooleantrue
misc.m_nade_helper.show_distancebooleantrue
misc.m_nade_helper.stand_distancenumber220
misc.m_nade_helper.stand_markercolorr 120, g 170, b 255, a 235
misc.m_nade_helper.stand_marker_activecolorr 110, g 235, b 140, a 245
misc.m_nade_helper.stand_radiusnumber22
misc.m_spectator_list7 settings
PathTypeDefault
misc.m_spectator_list.enabledbooleanfalse
misc.m_spectator_list.layout.anchorinteger0
misc.m_spectator_list.layout.offset_xnumber40
misc.m_spectator_list.layout.offset_ynumber240
misc.m_spectator_list.layout.scalenumber1
misc.m_spectator_list.layout.versioninteger1
misc.m_spectator_list.show_avatarsbooleantrue
misc.m_watermark10 settings
PathTypeDefault
misc.m_watermark.enabledbooleanfalse
misc.m_watermark.layout.anchorinteger0
misc.m_watermark.layout.offset_xnumber40
misc.m_watermark.layout.offset_ynumber40
misc.m_watermark.layout.scalenumber1
misc.m_watermark.layout.versioninteger1
misc.m_watermark.show_cpubooleantrue
misc.m_watermark.show_fpsbooleantrue
misc.m_watermark.show_lossbooleantrue
misc.m_watermark.show_pingbooleantrue

combat_global: global combat#

combat_global.*: the combat settings that apply to every weapon group unless a group has its own. 93 settings.

combat_global (top level)46 settings
PathTypeDefault
combat_global.aimbot_activation_modeinteger0
combat_global.aimbot_autowallbooleantrue
combat_global.aimbot_draw_fovbooleantrue
combat_global.aimbot_enabledbooleanfalse
combat_global.aimbot_fovinteger2
combat_global.aimbot_fov_colorcolorr 217, g 217, b 217, a 125
combat_global.aimbot_hitbox_partsinteger15
combat_global.aimbot_humanizeinteger35
combat_global.aimbot_keyinteger0
combat_global.aimbot_lethal_onlybooleantrue
combat_global.aimbot_min_damagenumber65
combat_global.aimbot_min_damage_overridenumber10
combat_global.aimbot_min_damage_override_enabledbooleanfalse
combat_global.aimbot_min_damage_override_keyinteger0
combat_global.aimbot_min_damage_override_modeinteger0
combat_global.aimbot_multipointbooleantrue
combat_global.aimbot_predictivebooleanfalse
combat_global.aimbot_recoil_syncbooleanfalse
combat_global.aimbot_smoothinginteger5
combat_global.aimbot_visible_onlybooleantrue
combat_global.penetration_color_nocolorr 255, g 0, b 0, a 255
combat_global.penetration_color_yescolorr 0, g 255, b 0, a 255
combat_global.penetration_crosshairbooleanfalse
combat_global.triggerbot_activation_modeinteger0
combat_global.triggerbot_autowallbooleantrue
combat_global.triggerbot_delayinteger10
combat_global.triggerbot_delay_after_msinteger100
combat_global.triggerbot_enabledbooleanfalse
combat_global.triggerbot_hitbox_partsinteger1
combat_global.triggerbot_hitchancenumber75
combat_global.triggerbot_keyinteger0
combat_global.triggerbot_lethal_onlybooleanfalse
combat_global.triggerbot_min_damagenumber90
combat_global.triggerbot_min_damage_overridenumber1
combat_global.triggerbot_min_damage_override_enabledbooleanfalse
combat_global.triggerbot_min_damage_override_keyinteger0
combat_global.triggerbot_min_damage_override_modeinteger0
combat_global.triggerbot_outlier_chancenumber3
combat_global.triggerbot_outlier_delay_msinteger20
combat_global.triggerbot_predictivebooleantrue
combat_global.triggerbot_randomize_msinteger5
combat_global.triggerbot_reaction_timeinteger0
combat_global.triggerbot_revolver_pre_cockbooleanfalse
combat_global.triggerbot_revolver_release_margin_msinteger16
combat_global.triggerbot_seed_ticksinteger2
combat_global.triggerbot_seed_typeinteger0
combat_global.aimbot_checks6 settings
PathTypeDefault
combat_global.aimbot_checks.airbornebooleanfalse
combat_global.aimbot_checks.flash_thresholdnumber85
combat_global.aimbot_checks.flash_threshold_percentnumber85
combat_global.aimbot_checks.flashedbooleanfalse
combat_global.aimbot_checks.smokebooleanfalse
combat_global.aimbot_checks.wallsinteger1
combat_global.aimbot_fov_config8 settings
PathTypeDefault
combat_global.aimbot_fov_config.draw_target_pointbooleantrue
combat_global.aimbot_fov_config.selectioninteger1
combat_global.aimbot_fov_config.target_distance_curvenumber1
combat_global.aimbot_fov_config.target_far_distance_mnumber10
combat_global.aimbot_fov_config.target_far_fovnumber2
combat_global.aimbot_fov_config.target_near_distance_mnumber2.5
combat_global.aimbot_fov_config.target_near_fovnumber20
combat_global.aimbot_fov_config.visualizationinteger0
combat_global.aimbot_humanizer11 settings
PathTypeDefault
combat_global.aimbot_humanizer.curvenumber0.4
combat_global.aimbot_humanizer.dampingnumber0.85
combat_global.aimbot_humanizer.deadzonenumber0.25
combat_global.aimbot_humanizer.gravitynumber9
combat_global.aimbot_humanizer.jitternumber0.1
combat_global.aimbot_humanizer.max_stepnumber8
combat_global.aimbot_humanizer.overshoot_amountnumber0.04
combat_global.aimbot_humanizer.overshoot_chancenumber5
combat_global.aimbot_humanizer.reaction_max_msinteger60
combat_global.aimbot_humanizer.reaction_min_msinteger25
combat_global.aimbot_humanizer.windnumber0.8
combat_global.aimbot_multipoint_config5 settings
PathTypeDefault
combat_global.aimbot_multipoint_config.body_scalenumber0.5
combat_global.aimbot_multipoint_config.capsbooleantrue
combat_global.aimbot_multipoint_config.head_scalenumber0.5
combat_global.aimbot_multipoint_config.limb_scalenumber0.5
combat_global.aimbot_multipoint_config.sidesbooleantrue
combat_global.aimbot_prediction3 settings
PathTypeDefault
combat_global.aimbot_prediction.accelerationbooleanfalse
combat_global.aimbot_prediction.enabledbooleanfalse
combat_global.aimbot_prediction.max_horizon_msnumber120
combat_global.aimbot_rcs8 settings
PathTypeDefault
combat_global.aimbot_rcs.driftnumber30
combat_global.aimbot_rcs.enabledbooleanfalse
combat_global.aimbot_rcs.pitchnumber100
combat_global.aimbot_rcs.randomnessnumber30
combat_global.aimbot_rcs.response_msnumber35
combat_global.aimbot_rcs.smoothnessnumber75
combat_global.aimbot_rcs.start_bulletinteger2
combat_global.aimbot_rcs.yawnumber100
combat_global.triggerbot_checks6 settings
PathTypeDefault
combat_global.triggerbot_checks.airbornebooleanfalse
combat_global.triggerbot_checks.flash_thresholdnumber85
combat_global.triggerbot_checks.flash_threshold_percentnumber85
combat_global.triggerbot_checks.flashedbooleanfalse
combat_global.triggerbot_checks.smokebooleanfalse
combat_global.triggerbot_checks.wallsinteger1

combat_overrides: per weapon group#

combat_overrides.N.* with N from 0 to 5 (Pistol, SMG, Rifle, Shotgun, Sniper, Heavy). 75 settings per group.

combat_overrides.N (top level)28 settings
PathTypeDefault
combat_overrides.N.aimbot_autowallboolean"true"
combat_overrides.N.aimbot_fovinteger"2"
combat_overrides.N.aimbot_hitbox_partsinteger"15"
combat_overrides.N.aimbot_humanizeinteger"35"
combat_overrides.N.aimbot_lethal_onlyboolean"true"
combat_overrides.N.aimbot_min_damagenumber"65"
combat_overrides.N.aimbot_multipointboolean"true"
combat_overrides.N.aimbot_predictiveboolean"false"
combat_overrides.N.aimbot_recoil_syncboolean"false"
combat_overrides.N.aimbot_smoothinginteger"5"
combat_overrides.N.aimbot_visible_onlyboolean"true"
combat_overrides.N.triggerbot_autowallboolean"true"
combat_overrides.N.triggerbot_delayinteger"10"
combat_overrides.N.triggerbot_delay_after_msinteger"100"
combat_overrides.N.triggerbot_hitbox_partsinteger"1"
combat_overrides.N.triggerbot_hitchancenumber"75"
combat_overrides.N.triggerbot_lethal_onlyboolean"false"
combat_overrides.N.triggerbot_min_damagenumber"90"
combat_overrides.N.triggerbot_outlier_chancenumber"3"
combat_overrides.N.triggerbot_outlier_delay_msinteger"20"
combat_overrides.N.triggerbot_predictiveboolean"true"
combat_overrides.N.triggerbot_randomize_msinteger"5"
combat_overrides.N.triggerbot_reaction_timeinteger"0"
combat_overrides.N.triggerbot_revolver_pre_cockboolean"false"
combat_overrides.N.triggerbot_revolver_release_margin_msinteger"16"
combat_overrides.N.triggerbot_seed_ticksinteger"2"
combat_overrides.N.triggerbot_seed_typeinteger"0"
combat_overrides.N.use_globalboolean"true"
combat_overrides.N.aimbot_checks6 settings
PathTypeDefault
combat_overrides.N.aimbot_checks.airborneboolean"false"
combat_overrides.N.aimbot_checks.flash_thresholdnumber"85"
combat_overrides.N.aimbot_checks.flash_threshold_percentnumber"85"
combat_overrides.N.aimbot_checks.flashedboolean"false"
combat_overrides.N.aimbot_checks.smokeboolean"false"
combat_overrides.N.aimbot_checks.wallsinteger"1"
combat_overrides.N.aimbot_fov_config8 settings
PathTypeDefault
combat_overrides.N.aimbot_fov_config.draw_target_pointboolean"true"
combat_overrides.N.aimbot_fov_config.selectioninteger"1"
combat_overrides.N.aimbot_fov_config.target_distance_curvenumber"1"
combat_overrides.N.aimbot_fov_config.target_far_distance_mnumber"10"
combat_overrides.N.aimbot_fov_config.target_far_fovnumber"2"
combat_overrides.N.aimbot_fov_config.target_near_distance_mnumber"2.5"
combat_overrides.N.aimbot_fov_config.target_near_fovnumber"20"
combat_overrides.N.aimbot_fov_config.visualizationinteger"0"
combat_overrides.N.aimbot_humanizer11 settings
PathTypeDefault
combat_overrides.N.aimbot_humanizer.curvenumber"0.4"
combat_overrides.N.aimbot_humanizer.dampingnumber"0.85"
combat_overrides.N.aimbot_humanizer.deadzonenumber"0.25"
combat_overrides.N.aimbot_humanizer.gravitynumber"9"
combat_overrides.N.aimbot_humanizer.jitternumber"0.1"
combat_overrides.N.aimbot_humanizer.max_stepnumber"8"
combat_overrides.N.aimbot_humanizer.overshoot_amountnumber"0.04"
combat_overrides.N.aimbot_humanizer.overshoot_chancenumber"5"
combat_overrides.N.aimbot_humanizer.reaction_max_msinteger"60"
combat_overrides.N.aimbot_humanizer.reaction_min_msinteger"25"
combat_overrides.N.aimbot_humanizer.windnumber"0.8"
combat_overrides.N.aimbot_multipoint_config5 settings
PathTypeDefault
combat_overrides.N.aimbot_multipoint_config.body_scalenumber"0.5"
combat_overrides.N.aimbot_multipoint_config.capsboolean"true"
combat_overrides.N.aimbot_multipoint_config.head_scalenumber"0.5"
combat_overrides.N.aimbot_multipoint_config.limb_scalenumber"0.5"
combat_overrides.N.aimbot_multipoint_config.sidesboolean"true"
combat_overrides.N.aimbot_prediction3 settings
PathTypeDefault
combat_overrides.N.aimbot_prediction.accelerationboolean"false"
combat_overrides.N.aimbot_prediction.enabledboolean"false"
combat_overrides.N.aimbot_prediction.max_horizon_msnumber"120"
combat_overrides.N.aimbot_rcs8 settings
PathTypeDefault
combat_overrides.N.aimbot_rcs.driftnumber"30"
combat_overrides.N.aimbot_rcs.enabledboolean"false"
combat_overrides.N.aimbot_rcs.pitchnumber"100"
combat_overrides.N.aimbot_rcs.randomnessnumber"30"
combat_overrides.N.aimbot_rcs.response_msnumber"35"
combat_overrides.N.aimbot_rcs.smoothnessnumber"75"
combat_overrides.N.aimbot_rcs.start_bulletinteger"2"
combat_overrides.N.aimbot_rcs.yawnumber"100"
combat_overrides.N.triggerbot_checks6 settings
PathTypeDefault
combat_overrides.N.triggerbot_checks.airborneboolean"false"
combat_overrides.N.triggerbot_checks.flash_thresholdnumber"85"
combat_overrides.N.triggerbot_checks.flash_threshold_percentnumber"85"
combat_overrides.N.triggerbot_checks.flashedboolean"false"
combat_overrides.N.triggerbot_checks.smokeboolean"false"
combat_overrides.N.triggerbot_checks.wallsinteger"1"