-- Prevent running in monitor mode if not TX_SERVER_MODE then return end -- ============================================= -- Server PlayerList handler -- ============================================= local function logError(x) txPrint("^1" .. x) end local oneSyncConvar = GetConvar('onesync', 'off') local onesyncEnabled = oneSyncConvar == 'on' or oneSyncConvar == 'legacy' -- Optimizations local floor = math.floor local min = math.min local sub = string.sub local tonumber = tonumber local tostring = tostring local pairs = pairs -- Variables & Consts -- https://www.desmos.com/calculator/dx9f5ko2ge local refreshMinDelay = 1500 local refreshMaxDelay = 5000 local maxPlayersDelayCeil = 300 --at this number, the delay won't increase more local intervalYieldLimit = 50 local vTypeMap = { ["nil"] = -1, ["walking"] = 0, ["automobile"] = 1, ["bike"] = 2, ["boat"] = 3, ["heli"] = 4, ["plane"] = 5, ["submarine"] = 6, ["trailer"] = 7, ["train"] = 8, } --[[ Wrapper to refresh player list data ]] local function refreshPlayerList() -- For each player local players = GetPlayers() for yieldCounter, serverID in pairs(players) do -- Updating player vehicle/health local health = -1 local vType = -1 local xCoord = nil local yCoord = nil if onesyncEnabled == true then local ped = GetPlayerPed(serverID) if ped and DoesEntityExist(ped) then health = GetPedHealthPercent(ped) local veh = GetVehiclePedIsIn(ped) if veh ~= 0 and DoesEntityExist(veh) then vType = vTypeMap[tostring(GetVehicleType(veh))] else vType = vTypeMap["walking"] end local coords = GetEntityCoords(ped) xCoord = math.floor(coords.x) yCoord = math.floor(coords.y) end end -- Updating TX_PLAYERLIST if type(TX_PLAYERLIST[serverID]) ~= 'table' then TX_PLAYERLIST[serverID] = { name = sub(GetPlayerName(serverID) or "unknown", 1, 75), health = health, vType = vType, xCoord = xCoord, yCoord = yCoord, } else TX_PLAYERLIST[serverID].health = health TX_PLAYERLIST[serverID].vType = vType TX_PLAYERLIST[serverID].xCoord = xCoord TX_PLAYERLIST[serverID].yCoord = yCoord end -- Mark as refreshed TX_PLAYERLIST[serverID].foundLastCheck = true -- Yield to prevent hitches if yieldCounter % intervalYieldLimit == 0 then Wait(0) end end --end for players --Check if player disconnected local playersOnline = 0 for playerID, playerData in pairs(TX_PLAYERLIST) do if playerData.foundLastCheck == true then playersOnline = playersOnline + 1 playerData.foundLastCheck = false else TX_PLAYERLIST[playerID] = nil end end return playersOnline end --[[ Thread to refresh player list ]] CreateThread(function() while true do -- Attempt to refresh player list local callSuccess, callOutput = pcall(refreshPlayerList) local playersOnline = 0 if callSuccess then playersOnline = callOutput else logError("failed to update playerlist") end -- DEBUG -- debugPrint("====================================") -- print(json.encode(TX_PLAYERLIST, {indent = true})) -- debugPrint("====================================") -- Refresh interval with linear function local hDiff = refreshMaxDelay - refreshMinDelay local calcDelay = (hDiff / maxPlayersDelayCeil) * (playersOnline) + refreshMinDelay local delay = floor(min(calcDelay, refreshMaxDelay)) Wait(delay) end --end while true end) --[[ Handle player Join or Leave ]] AddEventHandler('playerJoining', function(srcString, _oldID) -- sanity checking source if source <= 0 then logError('playerJoining event with source ' .. json.encode(source)) return end -- checking if the player was not already dropped local playerDetectedName = GetPlayerName(source) if type(playerDetectedName) ~= 'string' then logError('Received a playerJoining for a player that was already dropped. There is some resource dropping the player at the playerJoining event handler without first waiting for the next tick.') return end local playerData = { name = sub(playerDetectedName or "unknown", 1, 128), ids = GetPlayerIdentifiers(source), hwids = GetPlayerTokens(source), } PrintStructuredTrace(json.encode({ type = 'txAdminPlayerlistEvent', event = 'playerJoining', id = source, player = playerData })) -- relaying this info to all admins for adminID, _ in pairs(TX_ADMINS) do TriggerClientEvent('txcl:plist:updatePlayer', adminID, source, playerData.name) end end) AddEventHandler('playerDropped', function(reason, resource, category) -- sanity checking source if source <= 0 then logError('playerDropped event with source ' .. json.encode(source)) return end if resource == 'monitor' and TX_IS_SERVER_SHUTTING_DOWN then reason = 'server_shutting_down' end PrintStructuredTrace(json.encode({ type = 'txAdminPlayerlistEvent', event = 'playerDropped', id = source, reason = reason, resource = resource, category = category, })) -- relaying this info to all admins for adminID, _ in pairs(TX_ADMINS) do TriggerClientEvent('txcl:plist:updatePlayer', adminID, source, false) end end) -- Handle getDetailedPlayerlist -- This event is only called when the menu "players" tab is opened, and every 5s while the tab is open -- DEBUG playerlist scroll test stuff -- math.randomseed(os.time()) -- local fake_playerlist = {} -- local fake_admins = {1, 10, 21, 61, 91, 141, 281} -- local function getFakePlayer() -- return { -- name = 'fake'..tostring(math.random(999999)), -- health = 0, -- vType = math.random(8), -- } -- end -- for serverID=1, 500 do -- fake_playerlist[serverID] = getFakePlayer() -- end RegisterNetEvent('txsv:req:plist:getDetailed', function(getPlayerNames) if TX_ADMINS[tostring(source)] == nil then debugPrint('Ignoring unauthenticated getDetailedPlayerlist() by ' .. source) return end local players = {} --DEBUG replace TX_PLAYERLIST with fake_playerlist and playerData.health with math.random(150) for playerID, playerData in pairs(TX_PLAYERLIST) do players[#players + 1] = { tonumber(playerID), playerData.health, playerData.vType, playerData.xCoord, playerData.yCoord, } if getPlayerNames then players[#players][6] = playerData.name end end local admins = {} for adminID, _ in pairs(TX_ADMINS) do admins[#admins + 1] = tonumber(adminID) end --DEBUG replace admins with fake_admins TriggerClientEvent('txcl:plist:setDetailed', source, players, admins) end) -- Sends the initial playlist to a specific admin -- Triggered by the server after admin auth function sendInitialPlayerlist(adminID) local payload = {} --DEBUG replace TX_PLAYERLIST with fake_playerlist for playerID, playerData in pairs(TX_PLAYERLIST) do payload[#payload + 1] = { tonumber(playerID), playerData.name } end --DEBUG -- debugPrint("====================================") -- print(json.encode(payload, {indent = true})) -- debugPrint("====================================") debugPrint('Sending initial playerlist to ' .. adminID) TriggerClientEvent('txcl:plist:setInitial', adminID, payload) end