Документация 9.6.24
События и триггеры
События, которые Tosun AntiCheat генерирует для ваших скриптов, события, которые можно вызывать для интеграции, и списки конфигурации, превращающие ваши собственные события в освобождения, проверки ключа, лимиты частоты и ловушки. Внутренние события античита защищены, вызывать их нельзя.
Обзор#
В FiveM есть локальные и сетевые (net) события. Локальное событие остаётся на одной стороне игры: TriggerEvent вызывает его, AddEventHandler принимает. Сетевое событие передаётся по сети: сервер отправляет его через TriggerClientEvent, клиент — через TriggerServerEvent, а принимающая сторона должна зарегистрировать его через RegisterNetEvent. Tosun AntiCheat использует оба вида, на сервере и на клиенте.
На этой странице перечислены события, которые античит генерирует для вас, события, которые вы можете вызывать для интеграции своих скриптов, и списки конфигурации (ts.DetectionExempt, ts.ProtectedEvents, ts.EventLimiter, ts.triggerList), превращающие имена ваших событий в освобождения, проверки ключа, лимиты частоты или ловушки.
Античит также регистрирует множество внутренних событий для своего рукопожатия при входе, обмена ключами, Shield, меню администратора и действий веб-панели. Они защищены nonce, ключами, подписями, лимитами частоты или проверками персонала, а некоторые из них являются ловушками. Они не входят в интерфейс интеграции: не вызывайте их, не регистрируйте на них обработчики и не используйте их имена повторно. Используйте вместо них экспорты.
| Вид | Вызов | Приём | Кто может вызвать |
|---|---|---|---|
| Локальное событие сервера | TriggerEvent | AddEventHandler | Любой серверный ресурс. |
| Локальное событие клиента | TriggerEvent | AddEventHandler | Любой клиентский скрипт в игре этого игрока, включая внедрённый чит-код. |
| Сетевое событие, клиент → сервер | TriggerServerEvent | RegisterNetEvent + AddEventHandler | Любой подключённый игрок, с любыми аргументами. |
| Сетевое событие, сервер → клиент | TriggerClientEvent | RegisterNetEvent + AddEventHandler | Любой серверный ресурс. Клиентский скрипт также может вызвать то же имя локально через TriggerEvent. |
Типовые задачи интеграции#
Выберите событие или настройку, соответствующую вашей задаче. Каждое подробно описано ниже на этой странице.
| Задача | Что использовать |
|---|---|
| Реагировать, когда обнаружение заканчивается баном, киком или строкой лога | ts_anticheat:playerBanned |
| Реагировать только на реально выданный бан | ts_anticheat:banCommitted |
| Реагировать на разбан | ts_anticheat:playerUnbanned |
| Дублировать живую ленту логов | ts_anticheat:newLog |
| Обновлять свой кеш, когда античит повторно применяет настройки | tosun-ac:settingsReloaded / ts_anticheat:configUpdated |
| Применять переименование персонажа из панели на собственном фреймворке | tosun-ac:renameCharacter |
| Включать клиентские проверки после собственного спавна | playerSpawned |
| Обновлять панель после собственного выбора персонажа | tosun-ac:panel:characterReady |
| Заставить клиент заново проверить статус персонала | ts_anticheat:adminStateChanged |
| Освободить игрока на клиенте из вашего серверного скрипта | tosun-ac:bridge:clientExempt |
| Автоматически освобождать игроков при срабатывании события вашего меню или действия | ts.DetectionExempt |
| Требовать ключ игрока для вашего собственного события клиент → сервер | ts.ProtectedEvents + <name>:safe |
Серверные события, которые генерирует античит#
Это локальные события сервера. Слушайте их через AddEventHandler в любом серверном скрипте. Не вызывайте их сами; см. разделы о безопасности ниже.
| Событие | Когда срабатывает | Данные |
|---|---|---|
ts_anticheat:playerBanned | При каждом исходе обнаружения: BAN, KICK и LOG. Срабатывает до кика, вставки бана и логов. Не срабатывает для игроков, пропущенных как персонал или как находящиеся в белом списке. | таблица data: playerId (строка), playerName, reason, banID, side ('server'), punishment ('BAN', 'KICK' или 'LOG') |
ts_anticheat:banCommitted | Только при исходе BAN (обнаружения и экспорты ban и punish), сразу после постановки вставки строки бана в очередь и до вебхука панели и отключения игрока. | playerId (число), таблица row: banID, playerName, reason, license, steam, discord, ip, HWID, HWID2–HWID5, config (ключ конфигурации обнаружения) |
ts_anticheat:playerUnbanned | После того как экспорт unban, команда /ts unban или внутриигровое меню сняли бан. Разбаны из веб-панели его не вызывают. | banID (значение, переданное экспорту unban, без изменений) |
ts_anticheat:newLog | Каждая строка живого лога: обнаружения, строки лога клиента, действия меню администратора и некоторые внутренние строки античита. | таблица payload: src (метка категории, а не id игрока), event (текст сообщения), status (по умолчанию 'Info'), playerName, time (os.time()) |
tosun-ac:settingsReloaded | После того как синхронизация настроек повторно их применила. Это происходит при запуске и почти в каждом цикле синхронизации, а не только при изменениях. | нет |
ts_anticheat:shield:screenshot_saved | Запрошенный скриншот Shield получен и прошёл проверки. | src (число), b64 (изображение base64, от 64 до 2 500 000 символов) |
ts_anticheat:shield:ban_video_saved | Видео геймплея кикнутого или забаненного игрока загружено в течение 120 с. | src (число), videoUrl, reason, config (reason и config приходят от клиента) |
<name>:safe | Вызов события из ts.ProtectedEvents прошёл проверки ключа и данных. | src (число, id игрока онлайн), затем исходные аргументы после ключа |
tosun-ac:renameCharacter | Переименование персонажа в веб-панели на фреймворке, отличном от qb, qbcore, qbox, qbx или esx. | citizenid (строка), firstName (первое слово), lastName (остаток имени, может быть '') |
Клиентские события, которые генерирует античит#
Эти события доходят до клиентских скриптов. ts_anticheat:configUpdated — локальное событие клиента; слушайте его через AddEventHandler. ts_anticheat:receiveEventKey — сетевое событие от сервера; зарегистрируйте его через RegisterNetEvent в своём клиентском скрипте.
| Событие | Тип | Когда срабатывает | Данные |
|---|---|---|---|
ts_anticheat:configUpdated | Локальное событие клиента | После того как клиент применил настройки с сервера и заново просканировал свои хуки DetectionExempt. Ожидайте его примерно раз за интервал синхронизации. | нет |
ts_anticheat:receiveEventKey | Сетевое событие, сервер → клиент | В ответ на ts_anticheat:requestEventKey (ваш запрос или запрос самого античита), а также каждый раз, когда сервер выдаёт игроку новый ключ. Приходит после рукопожатия при входе. | key (строка, ключ SafeEvents игрока) |
Пример слушателя#
Серверный скрипт, реагирующий на кики и баны, и клиентский скрипт, сбрасывающий кеш, когда античит повторно применяет настройки. Каждый серверный слушатель сначала проверяет, что событие вызвал античит.
-- server.lua (ваш ресурс)
local AC = 'tosun-ac' -- имя ресурса (папки) античита
AddEventHandler('ts_anticheat:playerBanned', function(d)
if GetInvokingResource() ~= AC then return end -- игнорировать поддельные вызовы
if d.punishment == 'LOG' then return end
print(('[AC] %s %s: %s'):format(d.punishment, d.playerName, d.reason))
end)
AddEventHandler('ts_anticheat:banCommitted', function(playerId, row)
if GetInvokingResource() ~= AC then return end
print('AC ban', row.banID, row.license, row.reason)
end)
-- client.lua (ваш ресурс)
local myCache
AddEventHandler('ts_anticheat:configUpdated', function()
myCache = nil
end)Подробнее о playerBanned#
Название сложилось исторически. ts_anticheat:playerBanned срабатывает при каждом исходе обнаружения, а не только при банах. Фильтруйте по data.punishment.
data.playerId — строка. Преобразуйте её через tonumber, прежде чем использовать как серверный id.
Событие срабатывает до действия. Рассылка в чат, логи, DropPlayer и вставка бана выполняются после вашего обработчика.
Не срабатывает, если цель не в сети, если игрок пропущен как персонал (персонал пропускается, если ts.AdminBypassDetections не равен false и ts.Debug выключен) или если игрок во временном белом списке или в белом списке панели.
banID генерируется для каждого исхода, но сохраняется только для BAN. KICK сотрудника персонала отменяется уже после срабатывания события.
Пока игроку показывается экран бана (editable/server/sv_banscreen.lua), большинство последующих обнаружений для этого игрока поглощаются, и события для них не срабатывают.
Подробнее о banCommitted#
ts_anticheat:banCommitted — лучший хук для случая «бан действительно выдан». Для киков и строк лога используйте playerBanned.
Срабатывает при каждом исходе BAN в конвейере наказаний античита: обнаружения и экспорты ban и punish. Офлайн-баны (экспорты offlineBan и offlineBanByLicense) и баны из веб-панели его не вызывают.
Вставка строки бана ставится в очередь через MySQL.Async.execute без ожидания, поэтому к моменту запуска вашего обработчика строки в базе данных может ещё не быть.
У идентификаторов в row удалён префикс (license:, license2:, steam:, discord:, ip:). Отсутствующий идентификатор или токен — английский текст 'Not found'; отсутствующий ip — 'Hidden'. HWID–HWID5 — необработанные токены игрока.
Подробнее о newLog и settingsReloaded#
Оба события срабатывают часто. Делайте их обработчики лёгкими.
ts_anticheat:newLog: поле src — метка категории, а не id игрока. Обнаружения используют 'DETECTION' со статусом BAN, KICK или LOG. Строки лога клиента используют фиксированные тег и статус: 'AntiCheat' с 'Tespit' (жёстко заданное турецкое слово «обнаружение»), 'AC-Grace' с 'Info' и 'AC-FakeTrigger' с 'Warning'. Записи меню администратора обычно используют имя администратора со статусом BAN, KICK, INFO или SUCCESS. Несколько внутренних строк используют другие категории.
tosun-ac:settingsReloaded: античит не сравнивает старые и новые значения, поэтому событие срабатывает после синхронизации при запуске и в большинстве циклов синхронизации, каждые ts.ServerPerf.configSyncIntervalSec секунд (120 в поставляемой конфигурации, 60 при отсутствии ключа, минимум 20). Понимайте его как «настройки применены повторно», а не «настройки изменились».
После каждой такой синхронизации сервер также отправляет настройки всем клиентам, и каждый клиент затем вызывает ts_anticheat:configUpdated.
Если tosun-ac:settingsReloaded вызовет другой серверный ресурс, античит выполнит полное обновление прав персонала для каждого игрока онлайн.
События скриншотов и видео Shield#
Обоим событиям нужен Shield (ts.shield.enabled не равен false; в поставляемой конфигурации он включён).
ts_anticheat:shield:screenshot_saved: запросите скриншот серверным экспортом RequestShieldScreenshot(src). Изображение должно прийти в течение 60 с после запроса, и античит принимает не более одного за 20 с на игрока. Веб-панель тоже может запрашивать скриншоты, и они также вызывают событие.
ts_anticheat:shield:ban_video_saved: требует, чтобы ts.shield.banGameplayVideo не был равен false (включён в поставляемой конфигурации). Окно загрузки открыто 120 с после KICK или BAN, а хост URL видео должен совпадать с панелью.
reason и config в ban_video_saved приходят от клиента и не проверяются по типу и не обрезаются. Считайте их недоверенным текстом.
local AC = 'tosun-ac'
-- сервер: запросить скриншот у игры игрока
exports[AC]:RequestShieldScreenshot(src)
AddEventHandler('ts_anticheat:shield:screenshot_saved', function(src, b64)
if GetInvokingResource() ~= AC then return end
-- b64 — строка изображения в base64
end)Переименование персонажа на собственном фреймворке#
Веб-панель может переименовать персонажа. Для qb, qbcore, qbox и qbx античит обновляет players.charinfo; для esx — users.firstname и users.lastname. Для любого другого значения ts.Framework.framework он вызывает tosun-ac:renameCharacter, чтобы вы применили переименование сами.
Античит сообщает панели 'event_emitted_unknown_fw' независимо от того, есть ли слушатель.
Из нового имени удаляются управляющие символы, оно обрезается до 64 символов и должно быть длиной не менее 3 символов. firstName — первое слово; lastName — остаток, может быть пустым.
В примере используется вызов MySQL.update из oxmysql и вымышленная таблица characters. Адаптируйте запрос под свою схему.
AddEventHandler('tosun-ac:renameCharacter', function(cid, first, last)
if GetInvokingResource() ~= 'tosun-ac' then return end
MySQL.update('UPDATE characters SET first = ?, last = ? WHERE cid = ?', { first, last, cid })
end)События, которые вы можете вызывать#
Это события античита, предназначенные для вызова из ваших скриптов. События смерти и воскрешения фреймворков и ваши события ts.DetectionExempt описаны в отдельных разделах.
| Событие | Откуда вызывать | Эффект | Ограничения |
|---|---|---|---|
tosun-ac:panel:characterReady | Клиент, через TriggerServerEvent. | Обновляет данные персонажа игрока в списке игроков онлайн в панели. | Один вызов в 20 с на игрока. |
ts_anticheat:locale:setSelf | Клиент, через TriggerServerEvent с кодом языка. | Задаёт язык серверных сообщений античита только для этого игрока. Выводит строку в серверную консоль. | Без лимита частоты. Код должен состоять из 2 или 3 строчных букв, и должен существовать файл locales/<lang>.json. |
ts_anticheat:requestEventKey | Клиент, через TriggerServerEvent. | Отправляет ключ SafeEvents игрока обратно через ts_anticheat:receiveEventKey. | Один вызов в 3 с на игрока. Ключ получает только запросивший игрок. |
playerSpawned | Клиент, через TriggerEvent (локально). | Включает большинство клиентских проверок и запускает окно отсрочки после спавна. | Его также получают все остальные ресурсы на этом клиенте. |
ts_anticheat:adminStateChanged | Сервер, через TriggerClientEvent одному игроку. | Клиент снова запрашивает у сервера свой реальный статус персонала. | Сервер отвечает не чаще одного раза в секунду на игрока. |
tosun-ac:bridge:clientExempt | Сервер, через TriggerClientEvent; требуется файл моста в вашем ресурсе. | Освобождение на стороне клиента для этого игрока. | От 500 до 300000 мс. |
Собственный спавн: playerSpawned#
Большинство клиентских проверок остаются выключенными, пока античит не увидит локальное событие playerSpawned: регенерация, godmode, телепорт, невидимость, freecam, оружие, OCR, нативные функции, координаты и godmode транспорта. Обычно его вызывает spawnmanager.
Если ваша система спавна или мультиперсонажей не использует spawnmanager, вызовите playerSpawned локально, как только игрок окажется в мире, или вызовите клиентский экспорт SetSpawned(true).
playerSpawned также запускает окно отсрочки после спавна длительностью ts.spawnGraceSecondsMs. Поставляемое значение 500, код поднимает его до минимума 5000 мс; без ключа код использует 25000. Обнаружение внутри окна не отменяется. Оно лишь добавляет строку лога 'AC-Grace'.
playerSpawned также сбрасывает состояние смерти в античите и отмечает клиентский телепорт.
Поставляемый список ts.DetectionExempt.clientEvents содержит playerSpawned, поэтому каждый playerSpawned также запускает клиентское освобождение на defaultDurationMs (60 с).
SetSpawned(false) действует не чаще одного раза в 5 минут, а проверки снова включаются примерно через 30 с заметного движения.
-- клиент, после вашей собственной логики спавна
TriggerEvent('playerSpawned')
-- или без побочных эффектов для других ресурсов
exports['tosun-ac']:SetSpawned(true)Собственный мультиперсонаж: characterReady#
На сервере античит слушает QBCore:Server:OnPlayerLoaded, QBCore:Server:PlayerLoaded, esx:playerLoaded и qbx_core:server:playerLoaded. Каждое из них сразу отправляет загруженного персонажа в список игроков онлайн в панели. Собственный скрипт мультиперсонажей, который не вызывает ни одно из них, должен вызывать tosun-ac:panel:characterReady с клиента после выбора персонажа.
Каждый вызов записывает строку игрока в базу данных и, если заданы URL и ключ панели, отправляет HTTPS-запрос в панель. Поэтому сервер принимает один вызов в 20 с на игрока и игнорирует остальные.
-- клиент, после завершения выбора персонажа
TriggerServerEvent('tosun-ac:panel:characterReady')Язык игрока: locale:setSelf#
Клиент может сам выбрать язык серверных сообщений античита. При успехе сервер выводит строку в серверную консоль.
Меняются только серверные сообщения, отправляемые этому игроку. Локаль на стороне клиента не меняется.
Собственный клиент античита это событие не отправляет. Серверные альтернативы — экспорт setPlayerLocale и команда ts_setlang.
-- клиент
TriggerServerEvent('ts_anticheat:locale:setSelf', 'en')Изменения персонала: adminStateChanged#
После того как ваш скрипт администрирования выдал или отозвал права персонала, сообщите клиенту игрока, чтобы он заново проверил свой статус персонала. Само событие не может изменить статус: клиент отправляет серверу новый nonce, и решает только ответ сервера.
Сам античит это событие не отправляет. Клиент и так перепроверяет статус каждые 20 с, поэтому вызов лишь убирает эту задержку.
Чит, вызвавший его локально, ничего не получит; сервер отвечает на проверку не чаще одного раза в секунду на игрока.
-- сервер, после изменения прав персонала для src
TriggerClientEvent('ts_anticheat:adminStateChanged', src)Событие моста: tosun-ac:bridge:clientExempt#
Клиентский файл моста регистрирует это сетевое событие внутри вашего собственного ресурса. Ваш серверный скрипт отправляет его игроку, а мост вызывает для этого игрока клиентский экспорт SetExempt античита.
durationMs — число в мс или имя пресета в любом регистре: SHORT, MEDIUM, LONG или XL (30, 60, 120 или 180 с). Значения ограничиваются диапазоном от 500 до 300000 мс. Без значения применяется значение convar по умолчанию (60000).
Ключ освобождения — 'bridge:', за которым следует tag или имя вашего ресурса, если tag равен nil.
Это освобождение только на стороне клиента. Серверные проверки продолжают работать.
Античит никогда не отправляет это событие. Если файл моста подключён в нескольких ваших ресурсах, один TriggerClientEvent выполнится в каждом из них.
- Добавьте client_script '@tosun-ac/bridge/tosun_ac_client.lua' в fxmanifest.lua вашего ресурса.
- Необязательно, в server.cfg: если вы переименовали папку античита, добавьте setr tosun_ac_resource с новым именем (по умолчанию tosun-ac). Чтобы изменить длительность по умолчанию, добавьте setr tosun_ac_bridge_client_ms со значением в мс (по умолчанию 60000).
- Из своего серверного скрипта отправьте TriggerClientEvent('tosun-ac:bridge:clientExempt', src, durationMs, tag).
-- сервер: освободить игрока на 2 минуты, пока открыто меню барбершопа
TriggerClientEvent('tosun-ac:bridge:clientExempt', src, 'LONG', 'barber')События смерти и воскрешения фреймворков#
Клиент слушает эти события, чтобы знать, когда игрок мёртв или ранен (downed). Пока это состояние активно, проверки скачков регенерации, godmode, телепорта, невидимости, наблюдения и некоторые другие клиентские проверки пропускаются. Нативные смерти также отслеживаются через событие урона gameEventTriggered.
Начиная с 9.5.9 античит сам снимает это состояние, как только игрок снова явно активен.
Собственный скрипт скорой помощи может вызвать одно из этих имён локально, чтобы синхронизировать состояние античита. Это общие имена фреймворков, поэтому все остальные ресурсы на этом клиенте, которые их слушают, тоже сработают (например, экраны смерти ESX).
| Событие | Тип | Эффект |
|---|---|---|
hospital:client:isDead | Сетевое | Ранен; запускает окно 25 с после смерти. |
hospital:client:SetDeathState | Сетевое, логический аргумент | true: ранен и окно 25 с после смерти. false: воскрешён и окно 10 с после воскрешения. |
hospital:client:SetLaststand | Сетевое, логический аргумент | true: ранен и окно 25 с после смерти (false игнорируется). Также автоосвобождение на 45 с. |
qb-medical:client:OnDeath | Сетевое | Ранен; запускает окно 25 с после смерти. |
esx:onPlayerDeath | Сетевое | Ранен; запускает окно 25 с после смерти. Также автоосвобождение на 45 с. |
baseevents:onPlayerDied | Локальное (сетевое, пока включён autoExempt) | Запускает окно 25 с после смерти. Также автоосвобождение на 45 с. |
baseevents:onPlayerKilled | Только локальное | Запускает окно 25 с после смерти. |
hospital:client:Revive | Сетевое | Воскрешён; запускает окно 10 с после воскрешения. |
qb-ambulancejob:client:RevivePlayer | Сетевое | Воскрешён; запускает окно 10 с после воскрешения. |
qb-medical:client:OnRevive | Сетевое | Воскрешён; запускает окно 10 с после воскрешения. |
esx_ambulancejob:revive | Сетевое | Воскрешён; запускает окно 10 с после воскрешения. |
esx_basicneeds:onRevive | Сетевое | Воскрешён; запускает окно 10 с после воскрешения. |
playerSpawned | Локальное | Сбрасывает состояние ранения и оставляет около 17 с окна после смерти. |
-- собственная скорая помощь (клиент), после вашей логики воскрешения
TriggerEvent('esx_ambulancejob:revive')События автоматического освобождения (autoExempt)#
При ts.autoExempt.enabled (true в поставляемой конфигурации) клиент слушает распространённые события фреймворков и выдаёт короткое клиентское освобождение через экспорт SetExempt. Клиентские обнаружения во время освобождения не наказываются; о них сообщается строками LOG с пометкой '[exempt]'. Чтобы отключить это, задайте ts.autoExempt.enabled = false.
Комментарий в заголовке client/anticheat_auto_exempt.lua утверждает, что эти события нельзя вызвать удалённо. Это неверно: они зарегистрированы через RegisterNetEvent, поэтому их может вызвать и сервер. Эффект всё равно ограничивается клиентским освобождением.
| Ситуация | Длительность | События |
|---|---|---|
| Меню внешности | 180 с | illenium-appearance:client:openMenu, appearance:client:openMenu, fivem-appearance:client:openMenu, ox_appearance:openMenu |
| Меню одежды | 180 с | qb-clothing:client:openMenu, qb-clothes:client:openMenu, rcore_clothing:openClothingShop, clothing:client:openMenu |
| Меню скина и барбершопа ESX | 180 с | esx_skin:openSaveableMenu, esx_skin:openRestrictedMenu, skinchanger:model:loaded, tgg_barber:open, barbershop:client:open |
| Выбор персонажа | 30 с | qb-multicharacter:client:chooseChar, qb-multicharacter:client:closeNUIdefault, esx_multicharacter:SetupCharacters, esx:restoreLoadout, ox:playerLoaded, qbx_core:client:playerLoggedOut |
| Смерть и laststand (QB, ESX) | 45 с | esx_ambulancejob:setDeathStatus, esx:onPlayerDeath, hospital:client:SetLaststand, hospital:client:OnPlayerLaststand, qb-ambulancejob:client:playerDead |
| Смерть (другие скрипты) | 45 с | wasabi_ambulance:clientDeath, ars_ambulancejob:client:dead, baseevents:onPlayerDied, baseevents:onPlayerWasted |
| Персонаж загружен | 15 с | QBCore:Client:OnPlayerLoaded, qbx_core:client:playerLoaded, esx:playerLoaded, ox:playerLoaded |
| Запуск античита на клиенте | 60 с | onClientResourceStart |
Барьер входа: события загрузки фреймворка#
При ts.NetworkJoin.waitForFrameworkLoaded (true в поставляемой конфигурации) и фреймворке qb, qbox или esx клиент ждёт события загрузки фреймворка, прежде чем завершить вход в античит. Это задерживает запуск всей клиентской защиты для этого игрока. При других значениях фреймворка ожидание пропускается.
После minReadyDelayMs (15 с в поставляемой конфигурации) и окончания экрана загрузки клиент ждёт одного из событий загрузки либо LocalPlayer.state.isLoggedIn (qb, qbox) или ESX.PlayerLoaded (esx), в сумме до 180 с. Затем он ждёт postFrameworkSettleMs (5 с в поставляемой конфигурации) и коллизию, прежде чем сообщить о готовности.
| Фреймворк | События загрузки | События сброса |
|---|---|---|
qb | QBCore:Client:OnPlayerLoaded | QBCore:Client:OnPlayerUnload |
qbox | QBCore:Client:OnPlayerLoaded, qbx_core:client:playerLoggedIn, qbx_multicharacter:client:chooseChar | QBCore:Client:OnPlayerUnload |
esx | esx:playerLoaded | esx:onPlayerLogout |
Защищённые события (SafeEvents)#
ts.ProtectedEvents заставляет античит проверять ключ игрока в ваших собственных событиях клиент → сервер. Прошедший проверку вызов повторно вызывается на сервере как локальное событие '<name>:safe' с id игрока первым аргументом. Поставляемый список содержит только 'test:event'.
TriggerSafeServerEvent существует только внутри собственного клиента античита и не экспортируется, поэтому другие ресурсы должны использовать показанный выше шаблон с receiveEventKey. Клиент античита запрашивает ключ каждые 5 с, пока он не придёт (не более 12 попыток).
Сервер отвечает не более чем на один запрос ключа за 3 с на игрока, и собственные запросы античита тоже учитываются. Если ключ не пришёл, запросите его снова через несколько секунд. Держите обработчик зарегистрированным и всегда отправляйте последний полученный ключ.
- Добавьте имя своего события в ts.ProtectedEvents в configs/anticheat_config.lua. Список читается один раз при запуске, поэтому после изменений перезапустите античит.
- В своём клиентском скрипте зарегистрируйте ts_anticheat:receiveEventKey для сохранения ключа, затем отправьте ts_anticheat:requestEventKey.
- Отправляйте своё событие с ключом в качестве первого аргумента: TriggerServerEvent(name, key, ...).
- В своём серверном скрипте обрабатывайте '<name>:safe' через AddEventHandler. Первый аргумент — серверный id игрока, за ним следуют ваши аргументы.
-- configs/anticheat_config.lua
ts.ProtectedEvents = { ['my-shop:buy'] = true }
-- клиент (ваш ресурс)
local key
RegisterNetEvent('ts_anticheat:receiveEventKey', function(k) key = k end)
TriggerServerEvent('ts_anticheat:requestEventKey')
-- позже, когда key получен:
TriggerServerEvent('my-shop:buy', key, 'bread')
-- сервер (ваш ресурс)
AddEventHandler('my-shop:buy:safe', function(src, item)
if GetInvokingResource() ~= 'tosun-ac' then return end
-- здесь проверьте деньги, запасы и права
end)Что проверяет SafeEvents и чего не проверяет#
SafeEvents проверяет только транспорт. Он никогда никого не наказывает и не заменяет проверки в вашем собственном обработчике.
- Каждый вызов клиента сначала учитывается в ts.SafeEventRateLimit за ts.SafeEventRateWindow секунд (по умолчанию 30 вызовов за 10 с), даже без ключа.
- Если у игрока ещё нет ключа, вызов молча отбрасывается.
- Неверный ключ, превышение лимита частоты, более ts.SafeEventMaxArgs аргументов (по умолчанию 64) или некорректные данные приводят к отбрасыванию вызова и записи одной строки лога в базу данных на игрока, событие и окно.
- Вызовы с сервера (source 0) игнорируются и не вызывают событие ':safe'.
- Исходное сетевое событие всё равно доходит до любого обработчика, который ваш ресурс зарегистрировал через RegisterNetEvent. Защищено только зеркальное событие ':safe'.
- Никогда не регистрируйте имя ':safe' через RegisterNetEvent: это позволит клиентам вызывать его напрямую.
- SafeEvents — это дополнительный уровень защиты. Ваш собственный обработчик всё равно должен проверять права, балансы и владение.
- Не добавляйте в список события фреймворков (esx, qb). Их отправители не передают ключ, поэтому SafeEvents отклоняет такие вызовы, пишет строки лога и никогда не вызывает ':safe'. Собственные обработчики фреймворка при этом всё равно выполняются.
- Турецкий комментарий в configs/anticheat_config.lua утверждает, что вызовы без ключа приводят к ложному бану. В этой версии они лишь отбрасываются.
ts.DetectionExempt: привязка ваших событий к освобождениям#
ts.DetectionExempt превращает имена ваших событий во временные освобождения от обнаружений. Используйте его для меню и серверных действий, которые легитимно меняют внешность, позицию или состояние игрока: одежда, барбершопы, жильё, гаражи, выбор персонажа и воскрешение. Списки читаются из configs/anticheat_config.lua; настройки панели их не меняют. ts.MenuDetectionExempt — псевдоним, сохранённый для старых конфигураций.
| Ключ | Тип | Поставляемое значение | Эффект |
|---|---|---|---|
enabled | логическое | true | false отключает все хуки DetectionExempt. |
defaultDurationMs | число (мс) | 60000 | Длительность освобождения для записей без собственного durationMs. Серверные записи ограничиваются диапазоном от 1000 до 120000 мс, клиентские — от 1000 до 300000 мс. |
budgetMsPer10Min | число (мс) | 240000 | Максимальное время мягкого освобождения, которое один игрок может накопить из serverEvents за 10 минут. |
clientEvents | список строк | 153 записи (142 уникальных имени) | Сетевые события клиента. Когда одно из них срабатывает, клиентские проверки игрока освобождаются. |
localEvents | список строк | 11 записей | Локальные события клиента (только TriggerEvent на клиенте). Тот же эффект, что у clientEvents. |
serverEvents | список строк или таблиц | 34 записи | Сетевые события клиент → сервер. Когда игрок вызывает одно из них, он получает мягкое освобождение. |
DetectionExempt.serverEvents#
Античит регистрирует каждое указанное имя через RegisterNetEvent. Когда игрок вызывает такое событие, он получает мягкое освобождение: серверные проверки движения и серверные проверки godmode, невидимости, noclip и камеры смягчаются, а клиентские проверки игрока освобождаются через state bag. Это никогда не является иммунитетом от наказания.
Запись — строка или таблица: { event = 'name', durationMs = 60000, resource = 'owner-resource', always = true }. durationMs ограничивается диапазоном от 1000 до 120000 мс.
Хук устанавливается, только пока ресурс-владелец запущен или запускается. Владелец — префикс события до первого двоеточия; qb-clothes сопоставляется с qb-clothing, а hospital — с qb-ambulancejob. Задайте resource, если префикс отличается от имени ресурса. always = true устанавливает хук, даже если владелец не запущен.
Хуки устанавливаются через 2,5 с после запуска античита и снова через 1 с после запуска любого другого ресурса.
Игроки могут вызывать эти события сами, поэтому у каждого игрока есть бюджет (budgetMsPer10Min). Когда игрок его исчерпывает, античит пишет одно предупреждение в консоль и одну строку лога в базу данных и до конца 10 минут игнорирует дальнейшие запросы этого игрока.
Выполните tosunac_integration в серверной консоли, чтобы увидеть, сколько серверных событий освобождения перехвачено. Команда работает только из серверной консоли, и вывод идёт туда же.
DetectionExempt.clientEvents и localEvents#
Имена из clientEvents регистрируются на клиенте через RegisterNetEvent. Когда одно из них срабатывает — с сервера или из локального TriggerEvent, — клиент освобождается на defaultDurationMs.
clientEvents принимает только строки. Записи-таблицы, включая записи с durationMs, на клиенте молча игнорируются.
Во время освобождения клиентские обнаружения не наказываются. О них сообщается строками LOG с пометкой '[exempt]', не чаще раза в минуту на проверку, чтобы злоупотребления оставались видны в панели. Серверные проверки это не затрагивает.
localEvents работает так же, но использует только AddEventHandler, поэтому реагирует лишь на TriggerEvent на клиенте.
Клиент сканирует оба списка через 3,5 с после загрузки и снова при каждой синхронизации настроек.
Событие скрипта, который у вас не запущен, никогда не срабатывает, поэтому лишние имена в списке не вредят. Поставляемый список clientEvents повторяет 11 имён; дубликаты безвредны.
Пример DetectionExempt#
Добавьте свои имена в существующие списки в configs/anticheat_config.lua, затем перезапустите античит. Замена всей таблицы удалит поставляемые записи. В примере показаны только новые записи.
Если код под вашим контролем, предпочитайте экспорты: клиентский экспорт SetExempt(true, ms, reason), серверный экспорт SetExempt(src, ms, reason) из доверенного ресурса и серверный экспорт MarkTeleport(src, ms) непосредственно перед телепортацией игрока.
ts.DetectionExempt = {
enabled = true,
defaultDurationMs = 60000,
budgetMsPer10Min = 240000,
-- сетевые события клиента: только строки
clientEvents = {
-- ...поставляемые записи...
'my-clothing:client:openMenu',
},
-- локальные события клиента (TriggerEvent на клиенте)
localEvents = {
-- ...поставляемые записи...
'my-menu:opened',
},
-- события клиент → сервер: мягкое освобождение для отправителя
serverEvents = {
-- ...поставляемые записи...
'my-clothing:server:saveOutfit',
{ event = 'my-housing:server:enter', durationMs = 60000 },
{ event = 'myhouse:server:enter', resource = 'my-housing' },
{ event = 'my-garage:server:takeOut', durationMs = 45000, always = true },
},
}ts.EventLimiter: лимиты частоты для сетевых событий#
ts.EventLimiter сопоставляет именам событий максимальное число вызовов на игрока за 5 с. Античит при загрузке регистрирует каждое имя через RegisterServerEvent и считает вызовы по игрокам; все счётчики сбрасываются каждые 5 с. Ваш собственный обработчик события продолжает выполняться.
Когда счётчик достигает лимита, античит пытается зафиксировать обнаружение с конфигурацией ts.EventLimiter (server_event_spam, по умолчанию KICK).
Поставляемый список содержит 25 записей: test:event, esx:getSharedObject, денежные события банка и QBCore, воскрешение, спавн транспорта, инвентарь, события тюрьмы и наручников, а также HCheat:TempDisableDetection.
Имена и лимиты читаются при загрузке. Таблица базы данных ac_event_limits заменяет ts.EventLimiter при синхронизации, но не меняет зарегистрированные обработчики, поэтому после изменения любого из них перезапустите античит.
ts.EventLimiter = {
-- ...поставляемые записи...
['my-shop:buy'] = 10,
}События-ловушки: никогда не используйте эти имена#
Некоторые события существуют только для поимки читов. Следуйте этим правилам, чтобы ваши скрипты никогда их не вызывали.
- Античит регистрирует события-ловушки, которые вызывают только читы. Никогда не вызывайте их и никогда не используйте их имена повторно.
- Чтобы увидеть имена ловушек на своём сервере, выполните /ts evtlist (список выводится в серверную консоль). Чтобы событие больше не считалось ловушкой, используйте /ts evtwhitelist <eventName>. См. раздел «Команды».
- Давайте своим событиям префикс с именем вашего ресурса, например myresource:doThing, чтобы они никогда не совпали с ловушкой.
Ограничитель частоты звуковых событий#
Античит регистрирует эти звуковые события как сетевые в своём собственном ресурсе и считает их по игрокам. Выше ts.premiumGuard.soundPerSec (по умолчанию 6 в секунду) он применяет soundAction (по умолчанию 'log'). Событие отменяется, только если soundCancel = true (по умолчанию false). Обработчики вашего звукового ресурса продолжают работать, если этот ресурс сам зарегистрировал событие.
Ограничитель работает, только пока ts.premiumGuard.enabled и ts.premiumGuard.soundGuard равны true (оба true в поставляемой конфигурации). Оставляйте soundCancel = false, пока не убедитесь, что ваши звуковые скрипты не превышают лимит.
| Событие | Ресурс |
|---|---|
InteractSound_SV:PlayOnAll | InteractSound |
InteractSound_SV:PlayWithinDistance | InteractSound |
InteractSound_SV:PlayOnOne | InteractSound |
xsound:stateSound | xsound |
xsound:server:play | xsound |
xsound:server:playUrl | xsound |
xsound:server:playUrlPos | xsound |
События, которые античит отправляет другим ресурсам#
После действий персонала, панели или обнаружений античит вызывает события, принадлежащие другим ресурсам. Если у вас запущен один из этих ресурсов, ожидайте таких вызовов.
Для погоды и времени используется только первый запущенный ресурс синхронизации в таком порядке: qb-weather, Renewed-Weathersync (только экспорт), cd_easytime, wd_weather, weathersync, vSync, qb-weathersync. Если ни один не запущен, античит задаёт погоду и время напрямую на каждом клиенте.
hospital:client:Revive и esx_ambulancejob:revive также слушает античит (см. «События смерти и воскрешения фреймворков»), поэтому такие воскрешения тоже открывают окно 10 с после воскрешения.
| Событие | Сторона | Когда отправляется |
|---|---|---|
qb-weather:server:RequestStateChange | Локальное серверное | Смена погоды или времени, если у qb-weather нет экспорта setWeather или setTime. Для времени значение — таблица { hour, minute }. |
cd_easytime:setWeather, cd_easytime:setTime | Локальное серверное | Смена погоды или времени с cd_easytime. |
<res>:setWeather, <res>:setTime | Локальное серверное | Смена погоды или времени с wd_weather, weathersync, vSync или qb-weathersync, если у этого ресурса нет подходящего экспорта. |
wasabi_ambulance:revive | Локальное клиентское | Воскрешение персоналом или из панели, если запущен wasabi_ambulance. |
hospital:client:Revive | Локальное клиентское | Воскрешение персоналом или из панели с qb-ambulancejob или Starter-Hospital. |
esx_ambulancejob:revive | Локальное клиентское | Воскрешение персоналом или из панели с esx_ambulancejob. |
<res>:revive, <res>:client:Revive, <res>:client:revive | Локальное клиентское | Воскрешение персоналом или из панели в остальных случаях: первый запущенный из ambulance, qb-ambulancejob, cd_ambulance и ars_ambulancejob получает все три имени. Если игрок всё ещё мёртв, затем выполняется нативное воскрешение. |
QBCore:Notify | Локальное клиентское; сервер → клиент | Каждое лечение, выполняемое античитом, даже без qb-core (его проверка qb-core всегда истинна). Также действия панели с деньгами, инвентарём и уведомлениями. |
esx:showNotification, QBCore:Player:SetPlayerData, inventory:client:ItemBox | Сервер → клиент | Действия панели с деньгами, инвентарём и уведомлениями, а также выдача оружия как предмета. |
chat:addMessage | Сервер → клиент; локальное клиентское | Сообщения чата и рассылки из панели. При ts.chatMessages = true (по умолчанию false) каждый исход обнаружения, включая LOG, отправляется всем игрокам на жёстко заданном турецком (editable/server/sv_editme.lua). |
chat:addSuggestion | Локальное клиентское | Добавляет подсказку /unspectate при загрузке файла меню администратора. |
qb-phone:client:CustomNotification, qs-smartphone:client:sendNotification, lb-phone:notification, yseries:notification, gks-phone:client:notification, roadphone:notify | Сервер → клиент | Оповещения персонала о киках и банах, а также экспорты phoneNotify и phoneNotifyAdmins. Их получает только первый запущенный телефон (npwd использует экспорт; если телефона нет — чат). Требует ts.AdminMenu.enable = true. |
Безопасность: кто может вызывать какое событие#
Генерируемые античитом события может вызывать только сам античит. Другие ресурсы не должны их вызывать. В таблице указано, кто может вызывать каждое событие.
| Событие | Кто может вызывать |
|---|---|
ts_anticheat:playerBanned | Только античит. |
ts_anticheat:banCommitted | Только античит. |
ts_anticheat:newLog | Только античит. |
ts_anticheat:shield:ban_video_saved | Только античит. |
tosun-ac:settingsReloaded | Только античит. |
ts_anticheat:configUpdated | Только античит. |
ts_anticheat:adminStateChanged | Любой серверный ресурс. |
tosun-ac:panel:characterReady | Любой клиент. Ограничено одним вызовом в 20 с на игрока. |
ts_anticheat:locale:setSelf | Любой клиент. Влияет только на вызывающего. |
ts_anticheat:requestEventKey | Любой клиент. Ключ получает только запросивший игрок. |
tosun-ac:bridge:clientExempt | Ваши серверные скрипты. |
Безопасность: проверяйте GetInvokingResource#
Любой серверный ресурс может вызвать локальное событие сервера. В своих слушателях проверяйте, что событие вызвал античит, прежде чем действовать. Собственный слушатель экрана бана в античите использует похожую проверку.
Применяйте проверку к playerBanned, banCommitted, playerUnbanned, newLog, settingsReloaded, событиям Shield, renameCharacter и вашим обработчикам '<name>:safe'.
Никогда не вызывайте события античита из своих ресурсов, даже для тестирования. Некоторые встроенные слушатели не проверяют вызывающего.
AddEventHandler('ts_anticheat:banCommitted', function(playerId, row)
if GetInvokingResource() ~= 'tosun-ac' then return end -- имя вашей папки, если переименована
-- здесь можно безопасно работать с row
end)Безопасность: серверно-авторитетная архитектура#
Всё, что может вызвать клиент, считается подсказкой, а не решением. Соблюдайте то же правило в своих обработчиках.
- Клиентские освобождения (clientEvents, localEvents, autoExempt, событие моста и клиентский экспорт SetExempt) никогда не меняют серверный контроль. Пока они активны, клиентские обнаружения записываются в лог как '[exempt]' вместо наказания.
- Освобождения serverEvents мягкие: на сервере они смягчают только проверки движения и видимости (наказания за оружие, деньги, урон, злоупотребление событиями, инъекции и краши по-прежнему применяются), а каждый игрок ограничен budgetMsPer10Min.
- Статус персонала определяется защищённым nonce ответом сервера. Передача флага персонала клиенту не может дать права администратора.
- Ключ SafeEvents проверяет только транспорт. Ваш обработчик ':safe' должен проверять права, балансы и владение на сервере.
- Чит может вызвать события смерти фреймворка локально. Флаг ранения сбрасывается примерно через 20 с явно активной игры.
- Чтобы освободить игрока на стороне сервера, используйте экспорты: SetExempt(src, ms, reason) из доверенного ресурса или MarkTeleport(src, ms) перед телепортацией.
Внутренние события#
Помимо событий на этой странице, античит регистрирует внутренние события для рукопожатия при входе, обмена ключами, Shield, меню администратора, действий веб-панели и сопутствующего ресурса. Они защищены nonce, ключами, подписями, лимитами частоты или проверками персонала, а некоторые из них являются ловушками, наказывающими вызывающего. Они намеренно здесь не описаны.
- Не вызывайте внутренние события ни с сервера, ни с клиента.
- Не регистрируйте на них обработчики и не используйте их имена повторно.
- Не вызывайте события, имя которых начинается с ts_anticheat: или tosun-ac:, если эта страница не указывает их как доступные для вызова.
- Для действий используйте экспорты: например punish, MarkTeleport, freezePlayer, setPlayerHealth, RequestShieldScreenshot и quarantine.
Известные проблемы в 9.6.15#
Это поведение присутствует в поставляемом коде. Учитывайте его при планировании.
- tosun-ac:settingsReloaded и ts_anticheat:configUpdated срабатывают почти в каждом цикле синхронизации, а не только при изменении настроек.
- ts_anticheat:playerBanned срабатывает не только для BAN, но и для KICK и LOG.
- Оповещение персонала на телефон для живых логов ждёт статус 'TESPİT' или 'Detection' в ts_anticheat:newLog, но ни одно из этих значений нигде не генерируется, поэтому оповещение никогда не срабатывает.
- Каждое лечение, выполняемое античитом, вызывает QBCore:Notify на клиенте, даже на серверах без qb-core.
- ts.quarantine.autoQuarantine ни на что не влияет: его единственный вход — внутреннее событие, которое ничто не генерирует. Используйте вместо этого экспорт quarantine.
- Несколько комментариев в configs/anticheat_config.lua написаны на турецком, и некоторые устарели: вызовы ts.ProtectedEvents без ключа отбрасываются, а не банятся, а ts.EventLimiter не кикает.
Не повторять лишний анализ после перезапуска#
При каждом запуске проверяются SHA256-отпечатки содержимого ресурсов. Неизменённые файлы и правила используют аутентифицированные результаты KVP сервера вместо повторного дорогостоящего анализа сигнатур и событий. Изменение файлов, исключений или правил запускает новый анализ. Проверка отпечатков по-прежнему читает файлы; фиксированное значение resmon не гарантируется.
- При обычном обновлении сохраняйте KVP-кэш; повреждённые или непроверяемые записи анализируются заново.
- Начальный каталог собирает явно заданные имена событий Lua/JS. Динамические имена, нечитаемые или escrow-файлы и DLL не считаются полностью проверенными.
- Ручная автоматическая настройка ждёт завершения анализа. Неполные, подозрительные, изменённые ресурсы и тайм-ауты не получают автоматического разрешения.
- На тестовом сервере проверьте вход игроков, обычную игру и действия администратора; оценивайте resmon вместе с числом игроков и нагрузкой framework.