Created
March 26, 2019 23:40
-
-
Save bladecoding/5c18f31bd824f5454d1c5c6d53152ac7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
resource_manifest_version '77731fab-63ca-442c-a67b-abc70f28dfa5' | |
client_script 'afkchecker_client.lua' | |
server_script 'afkchecker_server.lua' | |
server_exports { | |
'getTracking', | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--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