Lua API
/

Lua API — destahook

Версия API: 1 · Lua 5.4.9 · CS2 Dev, 14.10.2026

Быстрый старт · gamesensical read-only · Blueprint Editor

Каждый .lua-файл получает собственное Lua-состояние: глобальные переменные разных скриптов не пересекаются. Код файла выполняется при загрузке, а дальнейшая работа происходит через events.on. Колбэки вызываются без аргументов; данные берутся через таблицы API.

Жизненный цикл

script = { name = "My overlay", author = "you", version = "1.0" }

events.on("tick", function() end)      -- логика кадра
events.on("render", function() end)    -- фоновые примитивы оверлея
events.on("menu", function() end)      -- только когда открыта вкладка Lua
events.on("shutdown", function() end)  -- перед выгрузкой или reload

events.on(name, callback) возвращает true для поддерживаемых событий и false для неизвестного имени или невалидной функции. Несколько колбэков одного события исполняются в порядке регистрации. В обычном кадре сначала строится меню, затем вызываются tick и render. Даже вне матча последние два события вызываются, но entity.in_game() будет false.

Скрипт с ошибкой показывает её в менеджере и пишет в лог. Вызовы Lua защищены; действует лимит примерно 250 000 инструкций на загрузку/колбэк. После reload состояние скрипта создаётся заново. Ошибку исправляют через Edit source → Save, затем нажимают Reload; новые ревизии облачной библиотеки также отслеживаются автоматически.

Типы

ТипСозданиеПоля и методы
Vec2Vec2.new(x, y)x, y, :length(), :distance_to(other)
Vec3Vec3.new(x, y, z)x, y, z, :length(), :distance_to(other), :distance_2d(other)
ColorColor.new(r, g, b, a) или render.color(...)r, g, b, a — целые 0–255
EntityВозвращается entity.*Снимок данных игрока, не изменяет память игры

У render.color(r, g, b, a?) параметр a по умолчанию равен 255. Цветовые компоненты при отрисовке ограничиваются диапазоном 0–255.

render — отрисовка

Все координаты для 2D-примитивов — пиксели экрана. Примитивы добавляются в фоновый draw list текущего кадра; их удобно вызывать из события render.

ВызовЧто делает
render.screen_size() → Vec2Размер экрана оверлея
render.world_to_screen(Vec3) → visible, Vec2Проекция мировой точки на экран; рисуйте её только при visible == true
render.line(x1, y1, x2, y2, color, thickness?)Линия; толщина по умолчанию 1
render.rect(x, y, width, height, color, rounding?, thickness?)Контур прямоугольника
render.rect_filled(x, y, width, height, color, rounding?)Заполненный прямоугольник
render.circle(x, y, radius, color, thickness?, segments?)Контур окружности
render.circle_filled(x, y, radius, color, segments?)Заполненная окружность
render.text(x, y, text, color, size?, outline?)Текст; outline=true добавляет тёмную обводку
render.measure_text(text, size?) → Vec2Ширина и высота текста для заданного шрифта
render.draggable_text(id, x, y, text, color, size?, outline?, relative?) → Vec2Текст с сохраняемой перемещаемой позицией; relative по умолчанию true

size по умолчанию берётся из текущего шрифта. Необязательные параметры можно опускать только справа.

events.on("render", function()
    local size = render.screen_size()
    local red = render.color(255, 80, 80)
    render.line(size.x / 2 - 8, size.y / 2,
                size.x / 2 + 8, size.y / 2, red, 2)
end)

layout — перемещаемые элементы

Переключатель Edit overlay positions на вкладке Lua и в окне Blueprint показывает рамки элементов, созданных через render.draggable_text или layout.position. При открытом меню перетащите рамку мышью; если окно редактора её закрывает, отодвиньте или закройте окно. Координаты автоматически сохраняются в облаке после отпускания кнопки в профиле конкретного скрипта и восстанавливаются после reload. Для каждого элемента в Overlay positions можно переключить Scale with screen или нажать Reset.

ВызовРезультат
layout.position(id, defaultX, defaultY, width, height, relative?) → Vec2Текущая позиция прямоугольной области; id должен быть уникален внутри скрипта
layout.save() → booleanПринудительно сохранить позиции; обычно не требуется
layout.set_relative(id, enabled) → booleanПереключить режим для уже созданного элемента, сохранив его видимое место
layout.reset(id) → booleanУдалить сохранённую позицию; при следующем вызове применятся значения по умолчанию

width и height задают размер области захвата мышью, не рисуют прямоугольник. В режиме relative=true сохраняется доля доступной области экрана, поэтому элемент остаётся примерно на том же месте при смене разрешения. При false сохраняются пиксели; позиция ограничивается границами экрана. Параметр relative задаёт режим при первом появлении ID; затем действует сохранённый режим до layout.set_relative или Reset. Максимальная длина ID — 128 символов.

events.on("render", function()
    local pos = layout.position("my_box", 40, 80, 180, 50, true)
    render.rect(pos.x, pos.y, 180, 50, render.color(80, 220, 150), 4, 2)
end)

Для текста проще использовать render.draggable_text: он сам измерит надпись и нарисует её на сохранённой позиции.

math — числовые функции

Стандартная Lua-библиотека math дополнена следующими функциями:

ВызовРезультат
math.clamp(value, min, max)Ограничение диапазоном; перепутанные границы меняются местами
math.lerp(from, to, amount)Линейная интерполяция; amount не ограничивается
math.remap(value, inMin, inMax, outMin, outMax)Перенос числа между диапазонами; при равных входных границах вернёт outMin
math.smoothstep(edge0, edge1, value)Плавный переход от 0 до 1
math.round(value)Округление до ближайшего целого значения
math.distance2d(x1, y1, x2, y2)Расстояние на плоскости
math.distance3d(x1, y1, z1, x2, y2, z2)Расстояние в 3D

thread — ожидание без блокировки кадров

thread.create(function() ... end) запускает задачу на Lua-корутине и возвращает её числовой ID. Внутри такой задачи thread.wait(milliseconds) уступает выполнение до следующего подходящего кадра. thread.cancel(id) отменяет ожидающую задачу и возвращает true, если она существовала. В одном скрипте допускается до 64 задач. Это не поток ОС: все продолжения исполняются последовательно вместе с Lua-событиями, а фактическая пауза может быть чуть дольше указанной из-за частоты кадров. Вызов thread.wait вне thread.create является ошибкой.

events.on("tick", function()
    if not timer_started then
        timer_started = true
        thread.create(function()
            thread.wait(500)
            client.log("Прошло не менее 500 мс")
        end)
    end
end)

Обычные элементы ui.* вызывайте из menu, а отрисовку render.* — из render: корутина предназначена для отложенной логики, не для самостоятельной отрисовки.

entity — игровой контекст

ВызовРезультат
entity.in_game()true, когда в текущем кадре доступен local player
entity.local_player(with_bones?)Entity локального игрока; вне игры valid=false
entity.players(with_bones?)Lua-массив снимков остальных игроков; может включать умерших
entity.by_id(id, with_bones?)Entity по индексу 1–63 или невалидный Entity

Поля Entity: valid, id, pawn_address, controller_address, name, weapon, health, armor, flags, on_ground, team, ping, ammo, max_ammo, distance, velocity, alive, scoped, reloading, has_helmet, origin (Vec3), absolute_velocity (Vec3), bone_count, :has_bone(index), :bone(index) (Vec3).

health и armor — целые значения, flags — целая битовая маска состояния, on_ground — готовый boolean-флаг «стоит на земле» (первый бит flags). Для простой проверки прыжка удобнее on_ground, чем ручная работа с битами.

with_bones по умолчанию false: это экономит чтения памяти и FPS. Перед работой с костями вызывайте entity.local_player(true), entity.players(true) или entity.by_id(id, true). Первый такой вызов может вернуть пустой список/незаполненные кости: чтение включается со следующего кадра. Индексы костей с нуля. Перед :bone(index) проверяйте :has_bone(index). distance — расстояние в игровых единицах, не метры. Снимки отражают прочитанное состояние текущего кадра; не храните их как «живые» объекты для будущих кадров.

events.on("render", function()
    if not entity.in_game() then return end
    for _, player in ipairs(entity.players(true)) do
        if player.valid and player.alive and player:has_bone(7) then
            local on_screen, head = render.world_to_screen(player:bone(7))
            if on_screen then
                render.text(head.x, head.y, player.name,
                            render.color(255, 255, 255), 14, true)
            end
        end
    end
end)

schema — чтение runtime schema

Этот API только читает память игры. Он не записывает значения и не создаёт игровые объекты.

ВызовРезультат
schema.ready()Готова ли runtime schema
schema.offset(class_name, field_name, module?)Смещение поля или nil
schema.read_int(address, class_name, field_name)Целое или nil
schema.read_uint64(address, class_name, field_name)64-битное целое (в том числе указатель) или nil
schema.read_float(address, class_name, field_name)Число или nil
schema.read_bool(address, class_name, field_name)Boolean или nil
schema.read_vec2(address, class_name, field_name)Vec2 или nil
schema.read_vec3(address, class_name, field_name)Vec3 или nil

address — адрес базового объекта, например player.pawn_address; чтение производится по адресу address + найденное_смещение. Первые два строковых параметра должны совпадать с именами класса и поля в runtime schema. Третий параметр module поддерживается у schema.offset, если нужно уточнить модуль. Неизвестное поле, неготовая schema или неудачное чтение дают nil.

local local_player = entity.local_player()
if local_player.valid and schema.ready() then
    local value = schema.read_int(local_player.pawn_address,
                                  "C_CSPlayerPawn", "m_iHealth")
    if value ~= nil then print("health", value) end
end

Названия schema-полей зависят от текущей версии игры. Не выполняйте десятки сырых чтений каждый кадр без необходимости.

config — настройки приложения

config.get(key) возвращает значение или nil для неизвестного ключа. config.set(key, value) возвращает true при успехе и false для неизвестного, read-only или неверно типизированного значения. config.keys() возвращает отсортированный Lua-массив доступных ключей. config.describe(key) возвращает сведения о типе ключа или nil для неизвестного ключа: type (boolean, integer, number, string, color), read_only, keybind; для бинда также key_code и key_name.

print(config.get("aim.fov"))
config.set("aim.fov", 7.5)
config.set("esp.box_color", render.color(255, 120, 60, 255))
local info = config.describe("aim.key")
if info and info.keybind then print(info.key_name, info.key_code) end

Внизу вкладки Lua раскройте Developer mode: Lua config keys: там можно найти ключ, скопировать точный путь кнопкой Copy, увидеть его тип и текущую клавишу бинда. Это избавляет от угадывания имени поля при работе с config.* и нодами Blueprint.

Доступные ключи сгруппированы ниже. Типы: bool, int, float, string, Color.

ПрефиксКлючи
aim.autowall, autowall_key, draw_fov, enabled, fov, hitbox_mask, key, minimum_damage, minimum_damage_key, minimum_damage_override, smoothing, visible_only
autofire.autowall, autowall_minimum_damage, before_delay, between_delay, enabled, hitchance, key, key_mode, minimum_damage
esp.bone_color, bones, box_color, box_length, box_style, box_thickness, c4, c4_color, chams_only_visible, enabled, flag_armor, flag_distance, flag_lethal, flag_ping, flags, glow_color, head, head_color, health_bar, health_number, history_chams, history_color, history_seconds, history_skeleton, local_viewmodel_chams, local_viewmodel_color, local_weapon_chams, local_weapon_color, name, offscreen, offscreen_color, shot_chams, shot_color, shot_skeleton, sound, sound_color, sound_distance, sound_enemies_only, sound_label, sound_lifetime, sound_radius, sound_thickness, team_check, visible_only, weapon
game.map
menu.frame_limit, frame_limit_enabled, renderer, vsync
misc.anti_flash, anti_smoke, bomb_timer, damage_log, dropped_glow, dropped_glow_color, grenade_warning, hitmarker, hotkey_list, penetration_crosshair, penetration_crosshair_mode, sniper_crosshair, spectator_avatars, spectator_color, spectator_list, spectator_warning, velocity, watermark, watermark_style
trigger.autowall, autowall_minimum_damage, before_delay, between_delay, enabled, hitchance, key, minimum_damage, prediction, prediction_seconds, seeded, seeded_key, team_check
walkbot.auto_buy, auto_reconnect, debug, enabled, smoothing, use_aim_trigger

Точный тип возвращает config.describe(key). config.set меняет настройки самого оверлея, а не память CS2. trigger.prediction_seconds задаётся в секундах и ограничивается при использовании диапазоном 0–0.2; esp.history_seconds — 0.025–0.5. Цвет задаётся объектом Color с RGBA. misc.bhop удалён.

esp.visible_only управляет фильтром обычного ESP: игрок считается видимым, если открыта хотя бы одна проверенная точка тела. Проверяются кости и крайние точки поверхности деформированной модели из текущей камеры. esp.chams_only_visible независимо включает отсечение скрытых частей chams, overlay, wireframe и glow по карте глубины BVH: видимая рука не открывает подсветку всего тела. Режим также применяется к chams оружия противников и следам chams. Без доступной геометрии или камеры соответствующий режим скрывает эффект. Проверка использует снимок физической геометрии карты; дым, прозрачные материалы и перемещённые динамические объекты не воспроизводят точную видимость движка CS2.

menu.frame_limit принимает значения от 100 до 1000 FPS; по умолчанию установлено 400. Значения вне диапазона приводятся к ближайшей границе. Это ограничение частоты оверлея, а не гарантия фактического FPS.

autofire.key_mode: 0 = Hold, 1 = Toggle. Системы autofire и trigger независимы и имеют отдельные key.

Sound ESP: esp.sound включает затухающие 3D-кольца. esp.sound_enemies_only исключает союзников; esp.sound_label включает подпись с расстоянием. esp.sound_color задаёт RGBA. Параметры: esp.sound_lifetime — 0.3–3 секунды, esp.sound_radius — 8–100 игровых единиц, esp.sound_distance — 1–100 метров, esp.sound_thickness — 0.5–4 пикселя. Событие определяется по изменению читаемого C_CSPlayerPawn::m_flEmitSoundTime. Позиция игрока фиксируется при наблюдении изменения; кольцо затем не следует за игроком. Первое значение задаёт базу и не создаёт метку. Звук не определяется по скорости движения. Это индикатор обновления звуковой метки игрока, а не перехват аудиопотока: API не подтверждает, был ли звук слышен локальному игроку, и не различает типы звуков. Пропущенные между чтениями события и недоступные клиенту источники не восстанавливаются. Фильтр Only visible ESP не скрывает звуковые метки.

input и keys

input.is_down(vk) возвращает состояние Windows virtual-key. input.mouse_position() возвращает экранную позицию курсора как Vec2. input.key_name(vk) возвращает читаемое имя клавиши для VK-кода.

Таблица keys содержит: mouse1, mouse2, mouse3, shift, control, alt, insert, delete, space. Можно передать и числовой VK-код.

if input.is_down(keys.shift) then client.log("Shift удерживается") end

Собственный бинд можно задать через ui.keybind в menu, сохранить результат в переменной скрипта и проверять через input.is_down в tick или render. Такой бинд не меняет встроенную настройку приложения и сбрасывается при reload; для встроенного бинда используйте ui.config_keybind.

ui — элементы меню

Вызывайте UI-функции из events.on("menu", ...). Меню-колбэк рисуется под соответствующим скриптом на вкладке Lua. Вне этой вкладки он не вызывается.

ВызовРезультат
ui.text(text)Обычный текст
ui.colored_text(text, color)Цветной текст
ui.separator(), ui.same_line(), ui.spacing()Компоновка
ui.button(label)true в кадре клика
ui.tooltip(text)Подсказка к предыдущему элементу при наведении
ui.checkbox(label, initial?)Собственный булевый флаг скрипта
ui.config_checkbox(label, key)Чекбокс, напрямую связанный с булевым config-ключом
ui.slider_int(label, min, max, initial?)Собственное целое значение
ui.slider_float(label, min, max, initial?)Собственное число
ui.input_int(label, initial?)Ввод целого значения
ui.input_float(label, initial?)Ввод числа
ui.combo(label, {"A", "B", ...}, initialIndex?)Выбор варианта, индекс с 1
ui.input_text(label, initial?)Строка до 255 байт
ui.keybind(label, initial?)Захват VK-кода
ui.config_keybind(label, key)Захват встроенного бинда по config-пути
ui.color_edit(label, initial?)Color с alpha
ui.begin_window(title) / ui.end_window()Начало и конец изменяемого по размеру окна; даже при false у begin_window нужно вызвать end_window
ui.window(title, function() ... end)Удобный вариант окна с callback и автоматическим закрытием; возвращает видимость содержимого

У собственных контролов состояние привязано к label и сохраняется между кадрами, но не между reload. initial применяется только при первом показе контрола. Для прямой синхронизации булевой настройки используйте ui.config_checkbox; для других типов явно вызывайте config.get/config.set.

Окна ui.begin_window и ui.window можно перемещать и менять в размере. Рисуйте их в menu-событии: оно исполняется, когда открыта вкладка Lua. У ui.begin_window всегда ставьте парный ui.end_window, даже если окно свернуто. Если callback завершится ошибкой, незакрытые окна этого скрипта закрываются автоматически, но на это не стоит полагаться как на обычную компоновку.

events.on("menu", function()
    ui.config_checkbox("Aimbot", "aim.enabled")
    ui.separator()
    local color = ui.color_edit("My color", render.color(100, 200, 255))
    if ui.button("Show color") then
        client.log(("RGB: %d, %d, %d"):format(color.r, color.g, color.b))
    end
end)

client и лог

ВызовРезультат
client.api_version1
client.realtime()Реальное время игры из последнего игрового кадра; вне матча 0
client.frame_time()Длительность текущего overlay-кадра в секундах
client.map()Имя текущей карты, если оно доступно
client.log(text)Информационная запись с именем скрипта
print(...)Тоже пишет в лог; аргументы разделяются табуляцией

В облачной Dev-сборке ошибки доступны под скриптом в менеджере. Локальный logs.txt не создаётся. client.log принимает несколько аргументов, объединяя их без разделителя; print разделяет аргументы табуляцией.

gamesensical read-only

Дополнительные таблицы renderer, globals, методы entity.get_*, callbacks client.set_event_callback и ui.new_* описаны в отдельном справочнике. Он содержит точные сигнатуры и отличия от оригинальной API: события кадра оверлея, приблизительные hitbox-позиции, статический BVH и поведение вне матча. Запись свойств, FFI, создание игровых интерфейсов, client.exec и изменение команд игрока отсутствуют.