Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
resource_manifest_version '77731fab-63ca-442c-a67b-abc70f28dfa5'
client_script 'afkchecker_client.lua'
server_script 'afkchecker_server.lua'
server_exports {
'getTracking',
}
local Keys = {
["W"] = 32, ["A"] = 34, ["S"] = 8, ["D"] = 9, ["SPACE"] = 22, ["N"] = 249
--[[["ESC"] = 322, ["F1"] = 288, ["F2"] = 289, ["F3"] = 170, ["F5"] = 166, ["F6"] = 167, ["F7"] = 168, ["F8"] = 169, ["F9"] = 56, ["F10"] = 57,
["~"] = 243, ["1"] = 157, ["2"] = 158, ["3"] = 160, ["4"] = 164, ["5"] = 165, ["6"] = 159, ["7"] = 161, ["8"] = 162, ["9"] = 163, ["-"] = 84, ["="] = 83, ["BACKSPACE"] = 177,
["TAB"] = 37, ["Q"] = 44, ["W"] = 32, ["E"] = 38, ["R"] = 45, ["T"] = 245, ["Y"] = 246, ["U"] = 303, ["P"] = 199, ["["] = 39, ["]"] = 40, ["ENTER"] = 18,
["CAPS"] = 137, ["A"] = 34, ["S"] = 8, ["D"] = 9, ["F"] = 23, ["G"] = 47, ["H"] = 74, ["K"] = 311, ["L"] = 182,
["LEFTSHIFT"] = 21, ["Z"] = 20, ["X"] = 73, ["C"] = 26, ["V"] = 0, ["B"] = 29, ["N"] = 249, ["M"] = 244, [","] = 82, ["."] = 81,
["LEFTCTRL"] = 36, ["LEFTALT"] = 19, ["SPACE"] = 22, ["RIGHTCTRL"] = 70,
["HOME"] = 213, ["PAGEUP"] = 10, ["PAGEDOWN"] = 11, ["DELETE"] = 178,
["LEFT"] = 174, ["RIGHT"] = 175, ["TOP"] = 27, ["DOWN"] = 173,
["NENTER"] = 201, ["N4"] = 108, ["N5"] = 60, ["N6"] = 107, ["N+"] = 96, ["N-"] = 97, ["N7"] = 117, ["N8"] = 61, ["N9"] = 118]]
}
local senddurationsDelay = 5 -- in seconds
local durations = {
["LastMoveInput"] = 0,
["LastMoved"] = 0,
["LastTalkInput"] = 0,
["LastTalked"] = 0,
["LastChat"] = 0,
}
local keyPresses
Citizen.CreateThread(function()
keyPresses = getValuesAsKeys(Keys, 0)
while true do
Wait(0)
for k,v in pairs(keyPresses) do
if IsControlJustPressed(1, k) then
keyPresses[k] = v + 1
end
end
if IsControlPressed(1, 245) then
durations["LastChat"] = 0
end
if IsControlPressed(1, 32) or IsControlPressed(1, 34) or IsControlPressed(1, 8) or IsControlPressed(1, 9) then
durations["LastMoveInput"] = 0
end
if IsControlPressed(1, 249) or IsDisabledControlPressed(1, 249) then
durations["LastTalkInput"] = 0
end
if NetworkIsPlayerTalking(PlayerId()) then
durations["LastTalked"] = 0
end
end
end)
Citizen.CreateThread(function()
local prevPos = nil
local counter = 0
while true do
Wait(1000)
local newPos = GetEntityCoords(GetPlayerPed(-1))
local heading = GetEntityHeading(GetPlayerPed(-1))
if (prevPos == nil or GetDistanceBetweenCoords(newPos, prevPos, true) > .1) then
durations["LastMoved"] = 0
end
prevPos = newPos
for k,v in pairs(durations) do
durations[k] = v + 1
end
counter = counter + 1
if (counter >= senddurationsDelay) then
TriggerServerEvent('afkchecker:tracking', durations, {x = round(newPos.x, 2), y = round(newPos.y, 2), z = round(newPos.z, 2), heading = round(heading, 2)}, keyPresses)
counter = 0
end
end
end)
function getValuesAsKeys(tbl, def)
local keys = {}
for _,v in pairs(tbl) do
keys[v] = def
end
return keys
end
function round(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end
--Store a tracking every 10 minutes for a max of 48(rotating). This gives you 8 hours of history.
local tracksMax = 48
local tracksInterval = 600
local clearTimeout = 600 --Delete tracking for people that have been dced for 10 minutes
local tracking = {}
AddEventHandler('playerDropped', function()
local steamId = getSteamId(source)
if not steamId then
return
end
if tracking[steamId] then
tracking[steamId].isConnected = false
tracking[steamId].handle = nil
tracking[steamId].disconnectTime = os.time()
end
end)
RegisterServerEvent('afkchecker:tracking')
AddEventHandler('afkchecker:tracking', function(durations, position, keyPresses)
local steamId = getSteamId(source)
if not steamId then
return
end
--Start tracking
local root = tracking[steamId]
if root == nil then
root = {}
tracking[steamId] = root
root.tracks = {}
root.handle = source
root.steamId = steamId
root.isConnected = true
root.name = GetPlayerName(source)
root.started = os.time()
end
local track = {}
track.time = os.time()
track.durations = durations
track.position = position
track.keyPresses = keyPresses
root.currentTrack = track
--Kind of a hacky work around so we don't have to filter empty balances
local tracks = root.tracks
if (#tracks == 0 or os.difftime(os.time(), tracks[1].time) > tracksInterval) then
table.insert(tracks, 1, track)
if #tracks > tracksMax then
table.remove(tracks)
end
end
end)
Citizen.CreateThread(function()
while true do
Wait(5000)
local curTime = os.time()
for _, steamId in ipairs(getKeys(tracking)) do
if not tracking[steamId].isConnected and os.difftime(curTime, tracking[steamId].disconnectTime) >= clearTimeout then
tracking[steamId] = nil
end
end
end
end)
--Http endpoint
function handleHttp(request, response)
local key = GetConvar("queueHttpKey")
if key == nil or request.headers["key"] ~= key then
response.writeHead( 200 )
response.send(json.encode({error = "invalid key"}))
return
end
--[[]]
if request.path == "/full" then
response.writeHead( 200 )
response.send(json.encode({time = os.time(), tracking = getValues(tracking)}))
elseif request.path == "/firstlast" then
--Truncate the tracks table from each entry. This is to provide a smaller/faster endpoint to poll.
local trackingCopy = {}
for _,v in pairs(getValues(tracking)) do
local copy = shallowcopy(v)
local tracks = copy.tracks
copy.tracks = {}
if #tracks > 0 then
table.insert(copy.tracks, tracks[1])
end
if #tracks > 1 then
table.insert(copy.tracks, tracks[#tracks])
end
table.insert(trackingCopy, copy)
end
response.writeHead( 200 )
response.send(json.encode({time = os.time(), tracking = trackingCopy}))
else
--Truncate the tracks table from each entry. This is to provide a smaller/faster endpoint to poll.
local trackingCopy = {}
for _,v in pairs(getValues(tracking)) do
if (v.isConnected) then
local copy = shallowcopy(v)
copy.tracks = nil
table.insert(trackingCopy, copy)
end
end
response.writeHead( 200 )
response.send(json.encode({time = os.time(), tracking = trackingCopy}))
end
end
SetHttpHandler(handleHttp)
--Export
function getTracking()
return tracking
end
--functional helpers
function getKeys(tbl)
local keys = {}
for k,_ in pairs(tbl) do
table.insert(keys, k)
end
return keys
end
function getValues(tbl)
local keys = {}
for _,v in pairs(tbl) do
table.insert(keys, v)
end
return keys
end
function getFirst(tbl)
for k,v in pairs(tbl) do
return v
end
return nil
end
function getLast(tbl)
local r = nil
for k,v in pairs(tbl) do
r = v
end
return r
end
function vectorize(tbl)
local vct = {}
for _, row in pairs(tbl) do
for k, v in pairs(row) do
local col = vct[k]
if (col == nil) then
col = {}
vct[k] = col
end
table.insert(col, v)
end
end
return vct;
end
function map(tbl, func)
local newtbl = {}
for i,v in pairs(tbl) do
newtbl[i] = func(v)
end
return newtbl
end
function getMax(data)
local r = nil
for k,v in pairs(data) do
if r == nil or v > r then
r = v
end
end
return r
end
function getSum(data)
local r = 0
for k,v in pairs(data) do
r = r + v
end
return r
end
function array_concat(...)
local t = {}
for n = 1,select("#",...) do
local arg = select(n,...)
if type(arg)=="table" then
for _,v in ipairs(arg) do
t[#t+1] = v
end
else
t[#t+1] = arg
end
end
return t
end
function getSteamId(src)
local identifiers = GetPlayerIdentifiers(src)
for i, identifier in pairs(identifiers) do
local parts = identifier:split(':')
local identifierType = parts[1]
if identifierType == 'steam' then
return identifier
end
end
return nil
end
function string:split(sep)
local sep, fields = sep or ":", {}
local pattern = string.format("([^%s]+)", sep)
self:gsub(pattern, function(c) fields[#fields+1] = c end)
return fields
end
function shallowcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in pairs(orig) do
copy[orig_key] = orig_value
end
else -- number, string, boolean, etc
copy = orig
end
return copy
end
function round(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment