Denver Studio - Docs

Coletores

O que cada arquivo de collectors faz e em qual intervalo

Coletores

O script é dividido em 5 coletores independentes em server/collectors/. Cada um tem sua própria responsabilidade e cadência de push.

Resumo dos intervalos

ColetorCadênciaTipoEndpoint
push_metrics.luaConfig.IntervalSec (60s padrão)Snapshot agregado/v1/ingest/push-metrics
orgs.luaA cada ~5 minSnapshot por permissão/v1/ingest/org-members
money.luaA cada ~10 minSnapshot por jogador/v1/ingest/player-money
players.luaEvent-driven1 evento por sessão/v1/ingest/player-session-start /v1/ingest/player-session-end /v1/ingest/player-job-change
heatmap.luaSnapshot 30 min + eventos imediatosPosições + drops/v1/ingest/positions /v1/ingest/disconnect-batch

push_metrics.lua

O coração do script. Roda a cada Config.IntervalSec (padrão 60s) e envia um snapshot consolidado:

{
  "cfxId": "abc123",
  "players": 200,
  "maxPlayers": 1000,
  "entries": 12,         // novos players desde último push
  "exits": 8,
  "newPlayers": 2,
  "bans": 0,
  "legalOnline": 35,
  "illegalOnline": 41,
  "groups": {
    "legal":   { "Polícia": 12, "Paramédico": 8, "Mecânica": 15 },
    "ilegal":  { "Drogas": 20, "Contrabando": 21 }
  },
  "staffMembers": [
    { "externalId": "1234", "name": "João", "role": "Admin" }
  ]
}

Alimenta: Visão Geral, Métricas, Legal & Ilegal (counts), Banimentos, Staff online.

Inicia automaticamente 2 segundos após o resource subir (via server/main.lua).


players.lua

Event-driven. Não faz push em intervalo — reage a eventos:

EventoAção
vRP:playerSpawn / ConnectEnvia player-session-start com license, nome, cfxId, cohortTag
vRP:playerLeave / Disconnect / playerDroppedEnvia player-session-end com durationMs, lastJob
vRP:playerJoinGroupEnvia player-job-change com fromJob, toJob, jobType

Alimenta: página Retenção — cohorts, churn risk, time-to-first-job legal vs ilegal.

Mantém um mapa local sessionStart[license] = os.time() para calcular duração corretamente.


orgs.lua

Push periódico (loop com Wait(INTERVAL_MS), inicia 15s após o resource subir).

Para cada permissão em Config.LegalOrganizations + Config.IllegalOrganizations, chama Adapter.getOrgMembers(permission) e envia:

{
  "cfxId": "abc123",
  "groupName": "Polícia",
  "groupType": "legal",
  "fullSnapshot": true,
  "members": [
    {
      "passport": "1234",
      "name": "João",
      "lastName": "Silva",
      "license": "abc",
      "lastLoginAt": 1700000000,
      "level": 5
    }
  ]
}

Alimenta: páginas Facções e Grupos do painel — lista de membros, hierarquia, último login.

Mesmo se o player estiver offline, o Adapter.getOrgMembers retorna ele — porque os dados vêm do banco (characters table), não da memória.


money.lua

Push periódico (loop, inicia 30s após o resource subir).

Chama Adapter.getPlayersMoney() que retorna todos os personagens que logaram nos últimos 7 dias com cash + bank:

{
  "cfxId": "abc123",
  "players": [
    { "passport": "1234", "name": "João", "cash": 5000, "bank": 250000 }
  ]
}

Alimenta: página Economia — M2 em circulação, inflação semanal, top milionários, distribuição (Gini), detector de duping via z-score.

Este coletor faz uma query MySQL pesada (JOIN com playerdata para extrair cash via JSON_VALUE). Por isso a cadência é longa (10+ minutos). Se o seu DB for pequeno (< 50k characters) você pode reduzir, mas evite cadências menores que 5 min.


heatmap.lua

Dois threads paralelos:

Thread 1 — Snapshot de posições (30min)

A cada 30 minutos:

  1. Itera todos os players online via GetPlayers()
  2. Para cada um, lê GetEntityCoords(GetPlayerPed(src)) (server-side, OneSync)
  3. Captura passport + jobType via adapter
  4. Adiciona ao batch pendingPositions
Wait(0)  -- yield entre cada player para não travar

Thread 2 — Eventos de disconnect (imediato)

No handler playerDropped(reason):

  1. Lê a posição final do ped (ainda válido por alguns ms após o drop)
  2. Classifica reason em: crash / drop_s2c / drop_c2s / quit
  3. Adiciona ao batch pendingDisconnects

Thread 3 — Flush (30s)

A cada 30 segundos:

  • Se há disconnects pendentes → 1 POST batch para /v1/ingest/disconnect-batch
  • Se há snapshots pendentes → 1 POST para /v1/ingest/positions

Alimenta: página Mapa de calor — concentração de jogadores, locais de crash, drops S→C e C→S.

Requer OneSync. Sem OneSync, GetEntityCoords server-side retorna (0, 0, 0) para todos, e o mapa fica vazio.


Classificação de drop reason

Regras aplicadas em classifyReason(reason):

Padrão (lowercase)kind
disconnected. exiting kickedquit
timed out time-out connection lostdrop_c2s
reliable network event overflow onesync server overloadeddrop_s2c
crash failed to set up game crashed couldn't loadcrash
(default)crash