Ir a la guía

Documentación 9.6.24

Eventos y triggers

Los eventos que Tosun AntiCheat emite para que tus scripts los escuchen, los eventos que puedes disparar para integrarte y las listas de configuración que asignan tus propios eventos a exenciones, comprobaciones de clave, límites de frecuencia y trampas. Los eventos internos del anticheat están protegidos y no deben llamarse.

Resumen#

FiveM tiene eventos locales y eventos de red (net events). Un evento local se queda en un lado del juego: TriggerEvent lo dispara y AddEventHandler lo recibe. Un evento de red cruza la red: el servidor lo envía con TriggerClientEvent, un cliente lo envía con TriggerServerEvent, y el lado que lo recibe debe registrarlo con RegisterNetEvent. Tosun AntiCheat usa ambos tipos, en el servidor y en el cliente.

Esta página lista los eventos que el anticheat emite para que los escuches, los eventos que puedes disparar para integrar tus scripts y las listas de configuración (ts.DetectionExempt, ts.ProtectedEvents, ts.EventLimiter, ts.triggerList) que convierten tus propios nombres de evento en exenciones, comprobaciones de clave, límites de frecuencia o trampas.

El anticheat también registra muchos eventos internos para su propio handshake de entrada, intercambio de claves, Shield, menú de administración y acciones del panel web. Están protegidos con nonces, claves, firmas, límites de frecuencia o comprobaciones de staff, y algunos son trampas. No forman parte de la superficie de integración: no los dispares, no registres handlers en ellos y no reutilices sus nombres. Usa los exports en su lugar.

TipoSe dispara conSe recibe conQuién puede dispararlo
Evento local de servidorTriggerEventAddEventHandlerCualquier recurso del servidor.
Evento local de clienteTriggerEventAddEventHandlerCualquier script de cliente en el juego de ese jugador, incluido código de trampas inyectado.
Evento de red, de cliente a servidorTriggerServerEventRegisterNetEvent + AddEventHandlerCualquier jugador conectado, con cualquier argumento.
Evento de red, de servidor a clienteTriggerClientEventRegisterNetEvent + AddEventHandlerCualquier recurso del servidor. Un script de cliente también puede disparar el mismo nombre en local con TriggerEvent.

Tareas de integración habituales#

Elige el evento o ajuste que corresponde a lo que quieres hacer. Cada uno se describe en detalle más abajo en esta página.

ObjetivoQué usar
Reaccionar cuando una detección termina en un ban, una expulsión o una línea de logts_anticheat:playerBanned
Reaccionar solo cuando realmente se emitió un bants_anticheat:banCommitted
Reaccionar a un desbaneots_anticheat:playerUnbanned
Replicar el feed de logs en directots_anticheat:newLog
Refrescar tu caché cuando el anticheat vuelve a aplicar los ajustestosun-ac:settingsReloaded / ts_anticheat:configUpdated
Aplicar un cambio de nombre de personaje del panel en un framework personalizadotosun-ac:renameCharacter
Activar las comprobaciones de cliente tras un spawn personalizadoplayerSpawned
Refrescar el panel tras una selección de personaje personalizadatosun-ac:panel:characterReady
Hacer que el cliente vuelva a comprobar el estado de staffts_anticheat:adminStateChanged
Eximir a un jugador en el cliente desde tu script de servidortosun-ac:bridge:clientExempt
Eximir jugadores automáticamente cuando se dispara el evento de tu menú o acciónts.DetectionExempt
Exigir una clave por jugador en tu propio evento de cliente a servidorts.ProtectedEvents + <name>:safe

Eventos de servidor que emite el anticheat#

Son eventos locales de servidor. Escúchalos con AddEventHandler en cualquier script de servidor. No los dispares tú; consulta las secciones de seguridad más abajo.

EventoCuándo se disparaDatos
ts_anticheat:playerBannedCada resultado de una detección: BAN, KICK y LOG. Se dispara antes de la expulsión, la inserción del ban y los logs. No se dispara para jugadores omitidos por ser staff o estar en whitelist.tabla data: playerId (string), playerName, reason, banID, side ('server'), punishment ('BAN', 'KICK' o 'LOG')
ts_anticheat:banCommittedSolo resultados BAN (detecciones y los exports ban y punish), justo después de poner en cola la inserción de la fila del ban y antes del webhook del panel y de la expulsión.playerId (number), tabla row: banID, playerName, reason, license, steam, discord, ip, HWID, HWID2 a HWID5, config (clave de configuración de la detección)
ts_anticheat:playerUnbannedDespués de que el export unban, el comando /ts unban o el menú del juego hayan quitado un ban. Los desbaneos desde el panel web no lo disparan.banID (el valor pasado al export unban, sin cambios)
ts_anticheat:newLogCada línea del log en directo: detecciones, líneas de log del cliente, acciones del menú de administración y algunas líneas internas del anticheat.tabla payload: src (etiqueta de categoría, no un id de jugador), event (texto del mensaje), status (por defecto 'Info'), playerName, time (os.time())
tosun-ac:settingsReloadedDespués de que una sincronización de ajustes los volviera a aplicar. Esto ocurre al arrancar y en casi todos los ciclos de sincronización, no solo cuando algo cambió.ninguno
ts_anticheat:shield:screenshot_savedLlegó una captura de Shield solicitada y superó las comprobaciones.src (number), b64 (imagen en base64, de 64 a 2.500.000 caracteres)
ts_anticheat:shield:ban_video_savedEl vídeo de juego de un jugador expulsado o baneado se subió en un plazo de 120 s.src (number), videoUrl, reason, config (reason y config vienen del cliente)
<name>:safeUna llamada a un evento de ts.ProtectedEvents superó las comprobaciones de clave y de payload.src (number, id de un jugador conectado), y luego los argumentos originales después de la clave
tosun-ac:renameCharacterUn cambio de nombre de personaje desde el panel web en un framework distinto de qb, qbcore, qbox, qbx o esx.citizenid (string), firstName (primera palabra), lastName (resto del nombre, puede ser '')

Eventos de cliente que emite el anticheat#

Estos eventos llegan a los scripts de cliente. ts_anticheat:configUpdated es un evento local de cliente; escúchalo con AddEventHandler. ts_anticheat:receiveEventKey es un evento de red que viene del servidor; regístralo con RegisterNetEvent en tu script de cliente.

EventoTipoCuándo se disparaDatos
ts_anticheat:configUpdatedEvento local de clienteDespués de que el cliente aplicara los ajustes del servidor y volviera a escanear sus hooks de DetectionExempt. Espera que ocurra más o menos una vez por intervalo de sincronización.ninguno
ts_anticheat:receiveEventKeyEvento de red, de servidor a clienteEn respuesta a ts_anticheat:requestEventKey (tu petición o la del propio anticheat), y cada vez que el servidor emite una clave nueva para el jugador. Llega después del handshake de entrada.key (string, la clave SafeEvents del jugador)

Ejemplo de listener#

Un script de servidor que reacciona a expulsiones y bans, y un script de cliente que descarta una caché cuando el anticheat vuelve a aplicar los ajustes. Cada listener de servidor comprueba primero que el evento lo disparó el anticheat.

-- server.lua (tu recurso)
local AC = 'tosun-ac' -- nombre del recurso (carpeta) del anticheat

AddEventHandler('ts_anticheat:playerBanned', function(d)
  if GetInvokingResource() ~= AC then return end -- ignora las llamadas falsificadas
  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 (tu recurso)
local myCache
AddEventHandler('ts_anticheat:configUpdated', function()
  myCache = nil
end)

playerBanned en detalle#

El nombre es histórico. ts_anticheat:playerBanned se dispara para cada resultado de detección, no solo para los bans. Filtra por data.punishment.

data.playerId es un string. Conviértelo con tonumber antes de usarlo como id de servidor.

El evento se dispara antes de la acción. El aviso por chat, los logs, DropPlayer y la inserción del ban se ejecutan después de tu handler.

No se dispara cuando el objetivo está desconectado, cuando el jugador se omite por ser staff (el staff se omite salvo que ts.AdminBypassDetections sea false o ts.Debug esté activado), o cuando el jugador está en la whitelist temporal o en la whitelist del panel.

Se genera un banID para cada resultado, pero solo se guarda en BAN. Un KICK a un miembro del staff se cancela después de que el evento ya se haya disparado.

Mientras se muestra la pantalla de ban (editable/server/sv_banscreen.lua) a un jugador, la mayoría de las detecciones posteriores de ese jugador se descartan y no se dispara ningún evento para ellas.

banCommitted en detalle#

ts_anticheat:banCommitted es el mejor hook para 'realmente se emitió un ban'. Usa playerBanned para expulsiones y líneas de log.

Se dispara para cada resultado BAN del flujo de sanciones del anticheat: detecciones y los exports ban y punish. Los bans offline (los exports offlineBan y offlineBanByLicense) y los bans hechos en el panel web no lo disparan.

La inserción de la fila del ban se pone en cola con MySQL.Async.execute y no se espera, así que puede que la fila aún no esté en la base de datos cuando se ejecuta tu handler.

Los identificadores de la fila no llevan prefijo (se quitan license:, license2:, steam:, discord:, ip:). Un identificador o token ausente es el texto en inglés 'Not found'; una ip ausente es 'Hidden'. HWID a HWID5 son tokens de jugador en bruto.

newLog y settingsReloaded en detalle#

Ambos eventos se disparan a menudo. Mantén sus handlers ligeros.

ts_anticheat:newLog: el campo src es una etiqueta de categoría, no un id de jugador. Las detecciones usan 'DETECTION' con status BAN, KICK o LOG. Las líneas de log del cliente usan una etiqueta y un status fijos: 'AntiCheat' con 'Tespit' ('Detección' en turco, fijo en el código), 'AC-Grace' con 'Info' y 'AC-FakeTrigger' con 'Warning'. Las entradas del menú de administración normalmente usan el nombre del admin con status BAN, KICK, INFO o SUCCESS. Algunas líneas internas usan otras categorías.

tosun-ac:settingsReloaded: el anticheat no compara los valores antiguos con los nuevos, así que el evento se dispara después de la sincronización de arranque y en la mayoría de los ciclos de sincronización, cada ts.ServerPerf.configSyncIntervalSec segundos (120 en la configuración que se distribuye, 60 si falta la clave, mínimo 20). Interprétalo como 'los ajustes se volvieron a aplicar', no como 'los ajustes cambiaron'.

Después de cada sincronización de este tipo, el servidor también envía los ajustes a todos los clientes, y cada cliente dispara entonces ts_anticheat:configUpdated.

Si otro recurso del servidor dispara tosun-ac:settingsReloaded, el anticheat hace un refresco completo de permisos de staff para todos los jugadores conectados.

Eventos de captura y vídeo de Shield#

Ambos eventos necesitan Shield (ts.shield.enabled distinto de false; está activado en la configuración que se distribuye).

ts_anticheat:shield:screenshot_saved: pide una captura con el export de servidor RequestShieldScreenshot(src). La imagen debe llegar en los 60 s siguientes a la petición, y el anticheat acepta como máximo una cada 20 s por jugador. El panel web también puede pedir capturas, y esas también disparan el evento.

ts_anticheat:shield:ban_video_saved: necesita ts.shield.banGameplayVideo distinto de false (activado en la configuración que se distribuye). La ventana de subida se abre durante 120 s después de un KICK o BAN, y el host de la URL del vídeo debe coincidir con el panel.

reason y config en ban_video_saved vienen del cliente y no se comprueba su tipo ni se recortan. Trátalos como texto no fiable.

local AC = 'tosun-ac'

-- servidor: pide una captura al juego del jugador
exports[AC]:RequestShieldScreenshot(src)

AddEventHandler('ts_anticheat:shield:screenshot_saved', function(src, b64)
  if GetInvokingResource() ~= AC then return end
  -- b64 es un string con una imagen en base64
end)

Cambio de nombre de personaje en un framework personalizado#

El panel web puede cambiar el nombre de un personaje. Para qb, qbcore, qbox y qbx el anticheat actualiza players.charinfo; para esx actualiza users.firstname y users.lastname. Para cualquier otro valor de ts.Framework.framework dispara tosun-ac:renameCharacter para que apliques tú el cambio de nombre.

El anticheat informa 'event_emitted_unknown_fw' al panel exista o no un listener.

Al nombre nuevo se le quitan los caracteres de control, se limita a 64 caracteres y debe tener al menos 3 caracteres. firstName es la primera palabra; lastName es el resto y puede estar vacío.

El ejemplo usa la llamada MySQL.update de oxmysql y una tabla characters inventada. Adapta la consulta a tu esquema.

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)

Eventos que puedes disparar#

Estos son los eventos del anticheat pensados para que los disparen tus scripts. Los eventos de muerte y reanimación del framework y tus eventos de ts.DetectionExempt se tratan en sus propias secciones.

EventoSe dispara desdeEfectoLímites
tosun-ac:panel:characterReadyCliente, con TriggerServerEvent.Refresca los datos del personaje del jugador en la lista de jugadores conectados del panel.Una llamada cada 20 s por jugador.
ts_anticheat:locale:setSelfCliente, con TriggerServerEvent y un código de idioma.Fija el idioma de los mensajes de servidor del anticheat solo para ese jugador. Imprime una línea en la consola del servidor.Sin límite de frecuencia. El código debe tener 2 o 3 letras minúsculas y existir un archivo locales/<lang>.json.
ts_anticheat:requestEventKeyCliente, con TriggerServerEvent.Devuelve la clave SafeEvents del jugador mediante ts_anticheat:receiveEventKey.Una llamada cada 3 s por jugador. La clave solo va al jugador que la pide.
playerSpawnedCliente, con TriggerEvent (local).Activa la mayoría de las comprobaciones de cliente e inicia la ventana de gracia del spawn.Todos los demás recursos de ese cliente también lo reciben.
ts_anticheat:adminStateChangedServidor, con TriggerClientEvent a un jugador.El cliente vuelve a pedir al servidor su estado real de staff.El servidor responde como máximo una vez por segundo por jugador.
tosun-ac:bridge:clientExemptServidor, con TriggerClientEvent; necesita el archivo del bridge en tu recurso.Exención del lado del cliente para ese jugador.De 500 a 300000 ms.

Spawn personalizado: playerSpawned#

La mayoría de las comprobaciones de cliente siguen desactivadas hasta que el anticheat ve el evento local playerSpawned: regen, godmode, teleport, invisibilidad, freecam, armas, OCR, natives, coordenadas y godmode de vehículo. Normalmente lo dispara spawnmanager.

Si tu sistema de spawn o de multipersonaje no usa spawnmanager, dispara playerSpawned en local cuando el jugador ya esté en el mundo, o llama al export de cliente SetSpawned(true).

playerSpawned también inicia la ventana de gracia del spawn de ts.spawnGraceSecondsMs. El valor que se distribuye es 500, que el código sube a su mínimo de 5000 ms; sin la clave el código usa 25000. Una detección dentro de la ventana no se cancela. Solo añade una línea de log 'AC-Grace'.

playerSpawned también borra el estado de muerte del anticheat y marca un teletransporte del cliente.

La lista ts.DetectionExempt.clientEvents que se distribuye contiene playerSpawned, así que cada playerSpawned también inicia una exención de cliente de defaultDurationMs (60 s).

SetSpawned(false) surte efecto como máximo una vez cada 5 minutos, y las comprobaciones se vuelven a activar tras unos 30 s de movimiento visible.

-- cliente, después de tu propia lógica de spawn
TriggerEvent('playerSpawned')

-- o, sin los efectos secundarios en otros recursos
exports['tosun-ac']:SetSpawned(true)

Multipersonaje personalizado: characterReady#

En el servidor el anticheat escucha QBCore:Server:OnPlayerLoaded, QBCore:Server:PlayerLoaded, esx:playerLoaded y qbx_core:server:playerLoaded. Cada uno envía al momento el personaje cargado a la lista de jugadores conectados del panel. Un script de multipersonaje personalizado que no dispare ninguno de ellos debe disparar tosun-ac:panel:characterReady desde el cliente después de la selección de personaje.

Cada llamada escribe la fila del jugador en la base de datos y, cuando la URL y la clave del panel están configuradas, envía una petición HTTPS al panel. Por eso el servidor acepta una llamada cada 20 s por jugador e ignora el resto.

-- cliente, cuando termina la selección de personaje
TriggerServerEvent('tosun-ac:panel:characterReady')

Idioma del jugador: locale:setSelf#

Un cliente puede elegir para sí mismo el idioma de los mensajes de servidor del anticheat. Si funciona, el servidor imprime una línea en la consola del servidor.

Solo cambia los mensajes de servidor enviados a ese jugador. No cambia el idioma del lado del cliente.

El propio cliente del anticheat no envía este evento. Las alternativas del lado del servidor son el export setPlayerLocale y el comando ts_setlang.

-- cliente
TriggerServerEvent('ts_anticheat:locale:setSelf', 'en')

Cambios de staff: adminStateChanged#

Después de que tu script de administración conceda o quite derechos de staff, dile al cliente del jugador que vuelva a comprobar su estado de staff. El evento no puede cambiar el estado por sí solo: el cliente envía un nonce nuevo al servidor y solo la respuesta del servidor decide.

Nada en el anticheat envía este evento. El cliente también vuelve a comprobarlo por su cuenta cada 20 s, así que la llamada solo elimina ese retraso.

Una trampa que lo dispare en local no gana nada; el servidor responde a la comprobación como máximo una vez por segundo por jugador.

-- servidor, después de cambiar los derechos de staff de src
TriggerClientEvent('ts_anticheat:adminStateChanged', src)

Evento del bridge: tosun-ac:bridge:clientExempt#

El archivo de bridge de cliente registra este evento de red dentro de tu propio recurso. Tu script de servidor lo envía a un jugador, y el bridge llama al export de cliente SetExempt del anticheat para ese jugador.

durationMs es un número en ms o el nombre de un preset, en mayúsculas o minúsculas: SHORT, MEDIUM, LONG o XL (30, 60, 120 o 180 s). Los valores se limitan entre 500 y 300000 ms. Sin valor se aplica el valor por defecto del convar (60000).

La clave de exención es 'bridge:' seguida de tag, o del nombre de tu recurso cuando tag es nil.

Es solo una exención del lado del cliente. Las comprobaciones del servidor siguen funcionando.

El anticheat nunca envía este evento. Si varios de tus recursos incluyen el archivo de bridge, un solo TriggerClientEvent se ejecuta una vez en cada uno de ellos.

  1. Añade client_script '@tosun-ac/bridge/tosun_ac_client.lua' al fxmanifest.lua de tu recurso.
  2. Opcional, en server.cfg: si cambiaste el nombre de la carpeta del anticheat, añade setr tosun_ac_resource con el nombre nuevo (por defecto tosun-ac). Para cambiar la duración por defecto, añade setr tosun_ac_bridge_client_ms con un valor en ms (por defecto 60000).
  3. Desde tu script de servidor, envía TriggerClientEvent('tosun-ac:bridge:clientExempt', src, durationMs, tag).
-- servidor: exime al jugador 2 minutos mientras un menú de barbería está abierto
TriggerClientEvent('tosun-ac:bridge:clientExempt', src, 'LONG', 'barber')

Eventos de muerte y reanimación del framework#

El cliente escucha estos eventos para saber cuándo un jugador está muerto o derribado. Mientras ese estado está activo, se omiten las comprobaciones de pico de regeneración, godmode, teleport, invisibilidad, espectador y varias otras de cliente. Las muertes nativas también se detectan mediante el evento de daño gameEventTriggered.

Desde la 9.5.9, el anticheat termina este estado por sí solo cuando el jugador vuelve a estar claramente activo.

Un script de ambulancia personalizado puede disparar uno de estos nombres en local para alinear el estado del anticheat. Son nombres compartidos del framework, así que cualquier otro recurso de ese cliente que los escuche también se ejecuta (por ejemplo, las pantallas de muerte de ESX).

EventoTipoEfecto
hospital:client:isDeadNetDerribado; inicia la ventana posterior a la muerte de 25 s.
hospital:client:SetDeathStateNet, argumento booleanotrue: derribado y ventana posterior a la muerte de 25 s. false: reanimado y ventana posterior a la reanimación de 10 s.
hospital:client:SetLaststandNet, argumento booleanotrue: derribado y ventana posterior a la muerte de 25 s (false se ignora). También una autoexención de 45 s.
qb-medical:client:OnDeathNetDerribado; inicia la ventana posterior a la muerte de 25 s.
esx:onPlayerDeathNetDerribado; inicia la ventana posterior a la muerte de 25 s. También una autoexención de 45 s.
baseevents:onPlayerDiedLocal (net mientras autoExempt está activado)Inicia la ventana posterior a la muerte de 25 s. También una autoexención de 45 s.
baseevents:onPlayerKilledSolo localInicia la ventana posterior a la muerte de 25 s.
hospital:client:ReviveNetReanimado; inicia la ventana posterior a la reanimación de 10 s.
qb-ambulancejob:client:RevivePlayerNetReanimado; inicia la ventana posterior a la reanimación de 10 s.
qb-medical:client:OnReviveNetReanimado; inicia la ventana posterior a la reanimación de 10 s.
esx_ambulancejob:reviveNetReanimado; inicia la ventana posterior a la reanimación de 10 s.
esx_basicneeds:onReviveNetReanimado; inicia la ventana posterior a la reanimación de 10 s.
playerSpawnedLocalBorra el estado de derribado y deja unos 17 s de la ventana posterior a la muerte.
-- ambulancia personalizada (cliente), después de tu propia lógica de reanimación
TriggerEvent('esx_ambulancejob:revive')

Eventos de exención automática (autoExempt)#

Con ts.autoExempt.enabled (true en la configuración que se distribuye), el cliente escucha eventos habituales de los frameworks y concede una exención de cliente corta mediante el export SetExempt. Las detecciones de cliente durante la exención no se sancionan; se reportan como líneas LOG marcadas con '[exempt]'. Pon ts.autoExempt.enabled = false para desactivarlo.

El comentario de cabecera de client/anticheat_auto_exempt.lua dice que estos eventos no se pueden disparar de forma remota. Es incorrecto: se registran con RegisterNetEvent, así que el servidor también puede dispararlos. Aun así, el efecto es solo una exención de cliente.

SituaciónDuraciónEventos
Menús de apariencia180 sillenium-appearance:client:openMenu, appearance:client:openMenu, fivem-appearance:client:openMenu, ox_appearance:openMenu
Menús de ropa180 sqb-clothing:client:openMenu, qb-clothes:client:openMenu, rcore_clothing:openClothingShop, clothing:client:openMenu
Menús de skin y barbería de ESX180 sesx_skin:openSaveableMenu, esx_skin:openRestrictedMenu, skinchanger:model:loaded, tgg_barber:open, barbershop:client:open
Selección de personaje30 sqb-multicharacter:client:chooseChar, qb-multicharacter:client:closeNUIdefault, esx_multicharacter:SetupCharacters, esx:restoreLoadout, ox:playerLoaded, qbx_core:client:playerLoggedOut
Muerte y laststand (QB, ESX)45 sesx_ambulancejob:setDeathStatus, esx:onPlayerDeath, hospital:client:SetLaststand, hospital:client:OnPlayerLaststand, qb-ambulancejob:client:playerDead
Muerte (otros scripts)45 swasabi_ambulance:clientDeath, ars_ambulancejob:client:dead, baseevents:onPlayerDied, baseevents:onPlayerWasted
Personaje cargado15 sQBCore:Client:OnPlayerLoaded, qbx_core:client:playerLoaded, esx:playerLoaded, ox:playerLoaded
Arranque del anticheat en el cliente60 sonClientResourceStart

Puerta de entrada: eventos de framework cargado#

Con ts.NetworkJoin.waitForFrameworkLoaded (true en la configuración que se distribuye) y el framework qb, qbox o esx, el cliente espera un evento de framework cargado antes de terminar de unirse al anticheat. Esto retrasa el inicio de toda la protección de cliente para ese jugador. Otros valores de framework se saltan la espera.

Después de minReadyDelayMs (15 s en la configuración que se distribuye) y del final de la pantalla de carga, el cliente espera uno de los eventos de carga, o LocalPlayer.state.isLoggedIn (qb, qbox) o ESX.PlayerLoaded (esx), hasta 180 s en total. Luego espera postFrameworkSettleMs (5 s en la configuración que se distribuye) y la colisión antes de indicar que está listo.

FrameworkEventos de cargaEventos de reinicio
qbQBCore:Client:OnPlayerLoadedQBCore:Client:OnPlayerUnload
qboxQBCore:Client:OnPlayerLoaded, qbx_core:client:playerLoggedIn, qbx_multicharacter:client:chooseCharQBCore:Client:OnPlayerUnload
esxesx:playerLoadedesx:onPlayerLogout

Eventos protegidos (SafeEvents)#

ts.ProtectedEvents hace que el anticheat compruebe una clave por jugador en tus propios eventos de cliente a servidor. Una llamada que pasa se vuelve a disparar en el servidor como el evento local '<name>:safe' con el id del jugador primero. La lista que se distribuye solo contiene 'test:event'.

TriggerSafeServerEvent solo existe dentro del propio cliente del anticheat y no se exporta, así que otros recursos deben usar el patrón de receiveEventKey de arriba. El cliente del anticheat pide la clave cada 5 s hasta que llega (como máximo 12 intentos).

El servidor responde como máximo a una petición de clave cada 3 s por jugador, y las peticiones del propio anticheat también cuentan. Si no llega ninguna clave, vuelve a pedirla al cabo de unos segundos. Mantén tu handler registrado y envía siempre la última clave que recibiste.

  1. Añade el nombre de tu evento a ts.ProtectedEvents en configs/anticheat_config.lua. La lista se lee una vez al arrancar, así que reinicia el anticheat después de hacer cambios.
  2. En tu script de cliente, registra ts_anticheat:receiveEventKey para guardar la clave y luego envía ts_anticheat:requestEventKey.
  3. Envía tu evento con la clave como primer argumento: TriggerServerEvent(name, key, ...).
  4. En tu script de servidor, gestiona '<name>:safe' con AddEventHandler. El primer argumento es el id de servidor del jugador, seguido de tus argumentos.
-- configs/anticheat_config.lua
ts.ProtectedEvents = { ['my-shop:buy'] = true }

-- cliente (tu recurso)
local key
RegisterNetEvent('ts_anticheat:receiveEventKey', function(k) key = k end)
TriggerServerEvent('ts_anticheat:requestEventKey')
-- más tarde, cuando ya tengas la clave:
TriggerServerEvent('my-shop:buy', key, 'bread')

-- servidor (tu recurso)
AddEventHandler('my-shop:buy:safe', function(src, item)
  if GetInvokingResource() ~= 'tosun-ac' then return end
  -- comprueba aquí dinero, stock y permisos
end)

Qué comprueba SafeEvents y qué no#

SafeEvents solo valida el transporte. Nunca sanciona a nadie y no sustituye las comprobaciones de tu propio handler.

  • Cada llamada del cliente cuenta primero contra ts.SafeEventRateLimit por cada ts.SafeEventRateWindow segundos (30 llamadas cada 10 s por defecto), incluso sin clave.
  • Si el jugador todavía no tiene clave, la llamada se descarta en silencio.
  • Una clave incorrecta, el límite de frecuencia, más de ts.SafeEventMaxArgs argumentos (64 por defecto) o un payload incorrecto descartan la llamada y escriben una línea de log en la base de datos por jugador, evento y ventana.
  • Las llamadas desde el servidor (source 0) se ignoran y no disparan ningún evento ':safe'.
  • El evento de red en bruto sigue llegando a cualquier handler que tu propio recurso haya registrado con RegisterNetEvent. Solo el espejo ':safe' está protegido.
  • Nunca hagas RegisterNetEvent del nombre ':safe'; eso permitiría a los clientes llamarlo directamente.
  • SafeEvents es una capa extra. Tu propio handler debe seguir comprobando permisos, saldos y propiedad.
  • No añadas eventos del framework (esx, qb) a la lista. Quienes los llaman no envían clave, así que SafeEvents rechaza esas llamadas, escribe líneas de log y nunca dispara ':safe'. Los handlers propios del framework siguen ejecutándose.
  • El comentario en turco de configs/anticheat_config.lua dice que las llamadas sin clave provocan un ban falso. En esta versión solo se descartan.

ts.DetectionExempt: asigna tus eventos a exenciones#

ts.DetectionExempt convierte tus propios nombres de evento en exenciones temporales de detección. Úsalo para menús y acciones de servidor que cambian de forma legítima la apariencia, la posición o el estado de un jugador: ropa, barberías, viviendas, garajes, selección de personaje y reanimaciones. Las listas se leen de configs/anticheat_config.lua; los ajustes del panel no las cambian. ts.MenuDetectionExempt es un alias que se mantiene para configuraciones antiguas.

ClaveTipoValor que se distribuyeEfecto
enabledbooleanotruefalse desactiva todos los hooks de DetectionExempt.
defaultDurationMsnúmero (ms)60000Duración de la exención para las entradas sin su propio durationMs. Las entradas de servidor se limitan entre 1000 y 120000 ms, las de cliente entre 1000 y 300000 ms.
budgetMsPer10Minnúmero (ms)240000Tiempo máximo de exención suave que un jugador puede acumular desde serverEvents en 10 minutos.
clientEventslista de strings153 entradas (142 nombres únicos)Eventos de red del cliente. Cuando se dispara uno, las comprobaciones de cliente del jugador quedan exentas.
localEventslista de strings11 entradasEventos locales del cliente (solo TriggerEvent en el cliente). Mismo efecto que clientEvents.
serverEventslista de strings o tablas34 entradasEventos de red de cliente a servidor. Cuando un jugador dispara uno, ese jugador recibe una exención suave.

DetectionExempt.serverEvents#

El anticheat registra cada nombre de la lista con RegisterNetEvent. Cuando un jugador lo dispara, ese jugador recibe una exención suave: se relajan las comprobaciones de movimiento del servidor y las de godmode, invisibilidad, noclip y cámara del lado del servidor, y las comprobaciones de cliente del jugador quedan exentas mediante un state bag. Nunca es inmunidad frente a sanciones.

Una entrada es un string o una tabla: { event = 'name', durationMs = 60000, resource = 'owner-resource', always = true }. durationMs se limita entre 1000 y 120000 ms.

El hook solo se instala mientras el recurso propietario está iniciado o iniciándose. El propietario es el prefijo del evento antes de los primeros dos puntos; qb-clothes se asigna a qb-clothing y hospital a qb-ambulancejob. Define resource cuando el prefijo no coincide con el nombre del recurso. always = true instala el hook aunque el propietario no esté en marcha.

Los hooks se instalan 2,5 s después de que arranque el anticheat y de nuevo 1 s después de que arranque cualquier otro recurso.

Los jugadores pueden disparar estos eventos ellos mismos, y por eso cada jugador tiene un presupuesto (budgetMsPer10Min). Cuando un jugador lo agota, el anticheat escribe un aviso en consola y una línea de log en la base de datos, e ignora las demás peticiones de ese jugador durante el resto de los 10 minutos.

Ejecuta tosunac_integration en la consola del servidor para ver cuántos eventos de exención de servidor están enganchados. Solo funciona desde la consola del servidor, y la salida va ahí.

DetectionExempt.clientEvents y localEvents#

Los nombres de clientEvents se registran con RegisterNetEvent en el cliente. Cuando se dispara uno, desde el servidor o desde un TriggerEvent local, el cliente queda exento durante defaultDurationMs.

clientEvents solo acepta strings. Las entradas de tipo tabla, incluidas las que tienen durationMs, se ignoran en silencio en el cliente.

Durante la exención, las detecciones de cliente no se sancionan. Se reportan como líneas LOG marcadas con '[exempt]', como máximo una vez por minuto por comprobación, para que el abuso siga siendo visible en el panel. Las comprobaciones del servidor no se ven afectadas.

localEvents funciona igual pero solo usa AddEventHandler, así que solo reacciona a TriggerEvent en el cliente.

El cliente escanea ambas listas 3,5 s después de cargarse y de nuevo en cada sincronización de ajustes.

Un evento de un script que no usas nunca se dispara, así que los nombres de más en la lista no hacen daño. La lista clientEvents que se distribuye repite 11 nombres; los duplicados no causan problemas.

Ejemplo de DetectionExempt#

Añade tus nombres a las listas existentes en configs/anticheat_config.lua y luego reinicia el anticheat. Si sustituyes la tabla entera, se pierden las entradas que se distribuyen. El ejemplo solo muestra las entradas nuevas.

Usa preferiblemente los exports cuando controlas el código: el export de cliente SetExempt(true, ms, reason), el export de servidor SetExempt(src, ms, reason) desde un recurso de confianza y el export de servidor MarkTeleport(src, ms) justo antes de teletransportar a un jugador.

ts.DetectionExempt = {
  enabled           = true,
  defaultDurationMs = 60000,
  budgetMsPer10Min  = 240000,

  -- eventos de red del cliente: solo strings
  clientEvents = {
    -- ...entradas que se distribuyen...
    'my-clothing:client:openMenu',
  },

  -- eventos locales del cliente (TriggerEvent en el cliente)
  localEvents = {
    -- ...entradas que se distribuyen...
    'my-menu:opened',
  },

  -- eventos de cliente a servidor: exención suave para quien lo envía
  serverEvents = {
    -- ...entradas que se distribuyen...
    '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: límites de frecuencia para eventos de red#

ts.EventLimiter asigna a nombres de evento un número máximo de llamadas por jugador en 5 s. El anticheat registra cada nombre con RegisterServerEvent al cargar y cuenta las llamadas por jugador; todos los contadores se reinician cada 5 s. Tu propio handler del evento sigue ejecutándose.

Cuando un contador llega a su límite, el anticheat intenta una detección con la configuración ts.EventLimiter (server_event_spam, KICK por defecto).

La lista que se distribuye tiene 25 entradas: test:event, esx:getSharedObject, eventos de banco y de dinero de QBCore, revive, spawn de vehículos, inventario, cárcel y esposas, y HCheat:TempDisableDetection.

Los nombres y los límites se leen al cargar. La tabla ac_event_limits de la base de datos sustituye a ts.EventLimiter al sincronizar, pero no cambia los handlers registrados, así que reinicia el anticheat después de editar cualquiera de los dos.

ts.EventLimiter = {
  -- ...entradas que se distribuyen...
  ['my-shop:buy'] = 10,
}

Eventos trampa: nunca uses estos nombres#

Algunos eventos existen solo para atrapar trampas. Sigue estas reglas para que tus propios scripts nunca disparen ninguno.

  • El anticheat registra eventos trampa que solo disparan los programas de trampas. Nunca los dispares y nunca reutilices sus nombres.
  • Para ver los nombres de los eventos trampa en tu propio servidor, ejecuta /ts evtlist (la lista se imprime en la consola del servidor). Para que un evento deje de tratarse como trampa, usa /ts evtwhitelist <eventName>. Consulta el tema Comandos.
  • Pon a tus propios eventos el nombre de tu recurso como prefijo, por ejemplo myresource:doThing, para que nunca choquen con un evento trampa.

Control de frecuencia de eventos de sonido#

El anticheat registra estos eventos de sonido como eventos de red en su propio recurso y los cuenta por jugador. Por encima de ts.premiumGuard.soundPerSec (6 por segundo por defecto) aplica soundAction ('log' por defecto). Solo cancela el evento cuando soundCancel = true (false por defecto). Los handlers de tu recurso de sonido siguen ejecutándose cuando ese recurso registró el evento él mismo.

El control solo funciona mientras ts.premiumGuard.enabled y ts.premiumGuard.soundGuard son true (ambos true en la configuración que se distribuye). Deja soundCancel en false hasta comprobar que tus scripts de sonido se mantienen por debajo del límite.

EventoRecurso
InteractSound_SV:PlayOnAllInteractSound
InteractSound_SV:PlayWithinDistanceInteractSound
InteractSound_SV:PlayOnOneInteractSound
xsound:stateSoundxsound
xsound:server:playxsound
xsound:server:playUrlxsound
xsound:server:playUrlPosxsound

Eventos que el anticheat envía a otros recursos#

Tras acciones del staff, del panel o de detección, el anticheat dispara eventos que pertenecen a otros recursos. Si usas uno de estos recursos, espera estas llamadas.

El clima y la hora solo usan el primer recurso de sincronización iniciado, en este orden: qb-weather, Renewed-Weathersync (solo export), cd_easytime, wd_weather, weathersync, vSync, qb-weathersync. Si no hay ninguno iniciado, el anticheat fija el clima y la hora directamente en cada cliente.

hospital:client:Revive y esx_ambulancejob:revive también son listeners del anticheat (consulta Eventos de muerte y reanimación del framework), así que estas reanimaciones también abren la ventana posterior a la reanimación de 10 s.

EventoLadoSe envía cuando
qb-weather:server:RequestStateChangeServidor, localCambio de clima u hora cuando qb-weather no tiene export setWeather o setTime. Para la hora, el valor es una tabla { hour, minute }.
cd_easytime:setWeather, cd_easytime:setTimeServidor, localCambio de clima u hora con cd_easytime.
<res>:setWeather, <res>:setTimeServidor, localCambio de clima u hora con wd_weather, weathersync, vSync o qb-weathersync cuando ese recurso no tiene el export correspondiente.
wasabi_ambulance:reviveCliente, localReanimación del staff o del panel cuando wasabi_ambulance está iniciado.
hospital:client:ReviveCliente, localReanimación del staff o del panel con qb-ambulancejob o Starter-Hospital.
esx_ambulancejob:reviveCliente, localReanimación del staff o del panel con esx_ambulancejob.
<res>:revive, <res>:client:Revive, <res>:client:reviveCliente, localReanimación del staff o del panel en los demás casos: el primero iniciado de ambulance, qb-ambulancejob, cd_ambulance y ars_ambulancejob recibe los tres nombres. Si el jugador sigue muerto, después se hace una resurrección nativa.
QBCore:NotifyCliente, local; servidor a clienteCada curación que hace el anticheat, incluso sin qb-core (su comprobación de qb-core siempre es true). También las acciones de dinero, inventario y notificación del panel.
esx:showNotification, QBCore:Player:SetPlayerData, inventory:client:ItemBoxServidor a clienteAcciones de dinero, inventario y notificación del panel, y entregas de armas como objeto.
chat:addMessageServidor a cliente; cliente, localMensajes de chat y anuncios del panel. Con ts.chatMessages = true (por defecto false), cada resultado de detección, LOG incluido, se envía a todos los jugadores en turco fijo en el código (editable/server/sv_editme.lua).
chat:addSuggestionCliente, localAñade la sugerencia de /unspectate cuando se carga el archivo del menú de administración.
qb-phone:client:CustomNotification, qs-smartphone:client:sendNotification, lb-phone:notification, yseries:notification, gks-phone:client:notification, roadphone:notifyServidor a clienteAlertas al staff en expulsiones y bans, y los exports phoneNotify y phoneNotifyAdmins. Solo las recibe el primer teléfono iniciado (npwd usa un export; chat si no hay ninguno). Necesita ts.AdminMenu.enable = true.

Seguridad: quién puede disparar cada evento#

Solo el anticheat puede disparar los eventos que emite. Otros recursos no deben dispararlos. La tabla indica quién puede disparar cada evento.

EventoQuién puede dispararlo
ts_anticheat:playerBannedSolo el anticheat.
ts_anticheat:banCommittedSolo el anticheat.
ts_anticheat:newLogSolo el anticheat.
ts_anticheat:shield:ban_video_savedSolo el anticheat.
tosun-ac:settingsReloadedSolo el anticheat.
ts_anticheat:configUpdatedSolo el anticheat.
ts_anticheat:adminStateChangedCualquier recurso del servidor.
tosun-ac:panel:characterReadyCualquier cliente. Limitado a una llamada cada 20 s por jugador.
ts_anticheat:locale:setSelfCualquier cliente. Solo afecta a quien lo llama.
ts_anticheat:requestEventKeyCualquier cliente. La clave solo va al jugador que la pide.
tosun-ac:bridge:clientExemptTus scripts de servidor.

Seguridad: comprueba GetInvokingResource#

Cualquier recurso del servidor puede disparar un evento local de servidor. En tus listeners, comprueba que lo disparó el anticheat antes de actuar. El propio listener de la pantalla de ban del anticheat usa una comprobación parecida.

Aplica la comprobación a playerBanned, banCommitted, playerUnbanned, newLog, settingsReloaded, los eventos de Shield, renameCharacter y tus handlers '<name>:safe'.

Nunca dispares los eventos del anticheat desde tus propios recursos, ni siquiera para pruebas. Varios listeners integrados no comprueban quién llama.

AddEventHandler('ts_anticheat:banCommitted', function(playerId, row)
  if GetInvokingResource() ~= 'tosun-ac' then return end -- el nombre de tu carpeta si la renombraste
  -- aquí ya es seguro usar row
end)

Seguridad: diseño con autoridad en el servidor#

Todo lo que un cliente puede disparar se trata como una pista, no como autoridad. Mantén la misma regla en tus propios handlers.

  • Las exenciones de cliente (clientEvents, localEvents, autoExempt, el evento del bridge y el export de cliente SetExempt) nunca cambian la aplicación de sanciones del servidor. Mientras están activas, las detecciones de cliente se registran como '[exempt]' en lugar de sancionarse.
  • Las exenciones de serverEvents son suaves: en el servidor solo relajan las comprobaciones de movimiento y visibilidad (las sanciones por armas, dinero, daño, abuso de eventos, inyección y crash siguen aplicándose), y cada jugador está limitado por budgetMsPer10Min.
  • El estado de staff lo decide la respuesta del servidor protegida con nonce. Enviar un indicador de staff a un cliente no puede dar permisos de administrador.
  • La clave SafeEvents solo valida el transporte. Tu handler ':safe' debe comprobar permisos, saldos y propiedad en el servidor.
  • Una trampa puede disparar en local los eventos de muerte del framework. El indicador de derribado se borra tras unos 20 s de juego claramente activo.
  • Para eximir a un jugador desde el lado del servidor, usa los exports: SetExempt(src, ms, reason) desde un recurso de confianza, o MarkTeleport(src, ms) antes de un teletransporte.

Eventos internos#

Además de los eventos de esta página, el anticheat registra eventos internos para su handshake de entrada, intercambio de claves, Shield, menú de administración, acciones del panel web y un recurso complementario. Están protegidos con nonces, claves, firmas, límites de frecuencia o comprobaciones de staff, y varios de ellos son trampas que sancionan a quien los llama. No se documentan aquí a propósito.

  • No dispares eventos internos, ni desde el servidor ni desde un cliente.
  • No registres handlers en ellos y no reutilices sus nombres.
  • No dispares ningún evento cuyo nombre empiece por ts_anticheat: o tosun-ac: salvo que esta página lo indique como uno que puedes disparar.
  • Usa los exports para las acciones: por ejemplo punish, MarkTeleport, freezePlayer, setPlayerHealth, RequestShieldScreenshot y quarantine.

Problemas conocidos en 9.6.15#

Estos comportamientos están en el código que se distribuye. Tenlos en cuenta.

  • tosun-ac:settingsReloaded y ts_anticheat:configUpdated se disparan en casi todos los ciclos de sincronización, no solo cuando cambian los ajustes.
  • ts_anticheat:playerBanned se dispara para KICK y LOG además de para BAN.
  • La alerta al teléfono del staff para los logs en directo escucha el status 'TESPİT' o 'Detection' en ts_anticheat:newLog, pero nada emite ninguno de esos valores, así que esa alerta nunca se dispara.
  • Cada curación que hace el anticheat dispara QBCore:Notify en el cliente, incluso en servidores sin qb-core.
  • ts.quarantine.autoQuarantine no tiene efecto: su única entrada es un evento interno que nada emite. Usa el export quarantine en su lugar.
  • Varios comentarios de configs/anticheat_config.lua están en turco y algunos están desactualizados: las llamadas sin clave de ts.ProtectedEvents se descartan, no se banean, y ts.EventLimiter no expulsa.

Evitar análisis innecesarios tras reiniciar#

En cada arranque se comprueban huellas SHA256 del contenido. Los recursos sin cambios en archivos o reglas reutilizan resultados autenticados en el KVP del servidor, sin repetir análisis costosos de firmas y eventos. Los cambios en archivos, excepciones o reglas invalidan el análisis correspondiente. Las huellas siguen leyendo archivos; no se garantiza un valor fijo de resmon.

  1. Conserva la caché KVP durante actualizaciones normales; los registros dañados o no verificables se analizan de nuevo.
  2. El catálogo inicial recoge nombres literales de eventos Lua/JS. Los nombres dinámicos, archivos ilegibles o escrow y DLL no se certifican como completamente analizados.
  3. La instalación automática manual espera al análisis de seguridad. No aprueba recursos incompletos, sospechosos, modificados o con tiempo agotado.
  4. Prueba acceso de jugadores, juego normal y acciones administrativas en un servidor de pruebas; evalúa resmon junto con jugadores y carga del framework.