ब्लॉग पर वापस
Tutorial9 मिनट पढ़ें

FiveM स्कोरबोर्ड सिस्टम: खिलाड़ी सूचियाँ, पिंग और नौकरियाँ

एक साफ़ FiveM स्कोरबोर्ड बनाएं। खिलाड़ी संख्या, पिंग, नौकरियां, IDs और डिफ़ॉल्ट TAB मेनू को स्टाइल के साथ बदलने के लिए सर्वश्रेष्ठ स्कोरबोर्ड स्क्रिप्ट।

Agency Scripts

Agency Scripts के संस्थापक और प्रमुख डेवलपर

क्यों एक कस्टम स्कोरबोर्ड बनाएं

डिफ़ॉल्ट FiveM खिलाड़ी सूची कार्यात्मक है लेकिन उसमें वह चमक और सूचना घनत्व नहीं है जिसकी रोलप्ले सर्वरों को आवश्यकता होती है। एक कस्टम स्कोरबोर्ड आपको खिलाड़ी नामों को उनके इन-चरित्र पहचान, नौकरी शीर्षक, सर्वर ID, और कनेक्शन गुणवत्ता के साथ दिखाने देता है, सभी आपके सर्वर की ब्रांडिंग के अनुरूप स्टाइल किए गए। सौंदर्यशास्त्र से परे, एक कस्टम स्कोरबोर्ड आपको विभिन्न खिलाड़ी समूहों को दिखाई देने वाली जानकारी पर नियंत्रण देता है। नियमित खिलाड़ी चरित्र नाम और ID देख सकते हैं, जबकि एडमिन अतिरिक्त फ़ील्ड जैसे Steam पहचानकर्ता और पिंग मान देखते हैं। अपना खुद का बनाना आपको छंटाई, नौकरी द्वारा फ़िल्टरिंग, और वास्तविक समय खिलाड़ी संख्या आँकड़े जैसे फीचर्स जोड़ने की अनुमति देता है जो खिलाड़ियों और स्टाफ दोनों को सर्वर को प्रभावी ढंग से प्रबंधित करने में मदद करते हैं।

संसाधन संरचना और मैनिफेस्ट

एक स्कोरबोर्ड संसाधन अपेक्षाकृत हल्का होता है, जिसमें कीबाइंड हैंडलिंग और डेटा संग्रह के लिए क्लाइंट स्क्रिप्ट, खिलाड़ी डेटा को समेकित करने के लिए सर्वर स्क्रिप्ट, और दृश्य प्रदर्शन के लिए NUI पेज शामिल होता है। संसाधन को आपके फ्रेमवर्क से परे बाहरी निर्भरताओं के बिना स्व-निहित रखना इसे स्थापित और बनाए रखना आसान बनाता है। यहाँ संसाधन मैनिफेस्ट और फ़ोल्डर संरचना है:

-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'

description 'Custom Scoreboard System'
author 'Agency Scripts'
version '1.0.0'

ui_page 'html/index.html'

client_scripts {
    'client/main.lua',
}

server_scripts {
    'server/main.lua',
}

files {
    'html/index.html',
    'html/style.css',
    'html/script.js',
}

lua54 'yes'

यह ui_page directive points to the NUI entry file, and the files ब्लॉक सुनिश्चित करता है कि सभी NUI एसेट्स संसाधन के साथ बंडल किए गए हैं। lua54 Lua 5.4 की विशेषताएं सक्षम करता है जैसे integer division और bitwise operators, जो अधिक उन्नत स्कोरबोर्ड कार्यान्वयन में फॉर्मेटिंग और डेटा पैकिंग के लिए उपयोगी हो सकते हैं।

सर्वर-साइड खिलाड़ी डेटा संग्रह

सर्वर वह एकमात्र सत्य स्रोत है जो स्कोरबोर्ड को प्रदर्शित करने के लिए आवश्यक सभी जानकारी के साथ पूर्ण खिलाड़ी सूची बनाता है। क्लाइंट को प्रत्येक खिलाड़ी को व्यक्तिगत रूप से क्वेरी करने के बजाय, सर्वर सभी खिलाड़ी डेटा को एक तालिका में एकत्र करता है और इसे अनुरोध करने वाले क्लाइंट को एक ही पेलोड में भेजता है। यह दृष्टिकोण अच्छी तरह से स्केल करता है क्योंकि डेटा एकत्रीकरण प्रति अनुरोध एक बार होता है न कि प्रति खिलाड़ी। पेलोड में पिंग मान, नौकरी जानकारी, और खिलाड़ी पहचानकर्ता शामिल करें:

-- server/main.lua
local QBCore = exports['qb-core']:GetCoreObject()

QBCore.Functions.CreateCallback('scoreboard:server:getPlayers', function(source, cb)
    local players = {}
    local srcPlayer = QBCore.Functions.GetPlayer(source)
    local isAdmin = srcPlayer and IsPlayerAceAllowed(source, 'admin')

    for _, playerId in ipairs(GetPlayers()) do
        local Player = QBCore.Functions.GetPlayer(tonumber(playerId))
        if Player then
            local playerData = {
                serverId = playerId,
                name = Player.PlayerData.charinfo.firstname .. ' ' ..
                       Player.PlayerData.charinfo.lastname,
                job = Player.PlayerData.job.label or 'Unemployed',
                jobGrade = Player.PlayerData.job.grade.name or '',
                onDuty = Player.PlayerData.job.onduty,
                ping = GetPlayerPing(playerId),
            }

            -- Only include sensitive data for admins
            if isAdmin then
                playerData.steamName = GetPlayerName(playerId)
                playerData.identifiers = GetPlayerIdentifiers(playerId)
            end

            table.insert(players, playerData)
        end
    end

    -- Sort by server ID
    table.sort(players, function(a, b)
        return tonumber(a.serverId) < tonumber(b.serverId)
    end)

    cb(players, #players, GetConvar('sv_maxclients', '64'))
end)

कॉलबैक यह जांचता है कि अनुरोध करने वाले खिलाड़ी के पास संवेदनशील जानकारी जैसे Steam नाम और पहचानकर्ताओं को शामिल करने से पहले व्यवस्थापक अनुमतियाँ हैं या नहीं। यह नियमित खिलाड़ियों को स्कोरबोर्ड के माध्यम से खाता जानकारी इकट्ठा करने से रोकता है। यह GetConvar कॉल अधिकतम खिलाड़ी संख्या प्राप्त करता है ताकि स्कोरबोर्ड "32/64 Players" जैसे क्षमता संकेतक दिखा सके।

क्लाइंट-साइड कीबाइंड और टॉगल लॉजिक

स्कोरबोर्ड उस समय खुलना चाहिए जब खिलाड़ी एक विशिष्ट कुंजी दबाए और बंद हो जाना चाहिए जब वे उसे छोड़ें। FiveM प्रदान करता है RegisterKeyMapping रीबाइंडेबल कीबाइंड्स के लिए फ़ंक्शन, जो हार्डकोडेड की जांचों की तुलना में पसंदीदा तरीका है क्योंकि यह खिलाड़ियों को गेम सेटिंग्स मेनू के माध्यम से अपने नियंत्रणों को अनुकूलित करने देता है। एक होल्ड-टू-शो पैटर्न का उपयोग करें जहां NUI की प्रेस पर खुलती है और की रिलीज पर बंद होती है ताकि एक स्मूद, गैर-हस्तक्षेपकारी अनुभव मिले:

-- client/main.lua
local QBCore = exports['qb-core']:GetCoreObject()
local isOpen = false
local refreshInterval = 5000  -- ms between data refreshes while open

RegisterKeyMapping('+scoreboard', 'Open Scoreboard', 'keyboard', 'HOME')

RegisterCommand('+scoreboard', function()
    if isOpen then return end
    isOpen = true
    RefreshAndShow()
end, false)

RegisterCommand('-scoreboard', function()
    if not isOpen then return end
    isOpen = false
    SetNuiFocus(false, false)
    SendNUIMessage({action = 'hide'})
end, false)

function RefreshAndShow()
    QBCore.Functions.TriggerCallback('scoreboard:server:getPlayers',
        function(players, count, maxPlayers)
            SendNUIMessage({
                action = 'show',
                players = players,
                playerCount = count,
                maxPlayers = maxPlayers,
                serverName = 'My Roleplay Server',
            })
        end
    )
end

-- Auto-refresh while scoreboard is open
CreateThread(function()
    while true do
        Wait(refreshInterval)
        if isOpen then
            RefreshAndShow()
        end
    end
end)

यह + और - कमांड नामों पर पूर्वसर्ग स्वतः एक प्रेस और रिलीज़ जोड़ी बनाते हैं। जब खिलाड़ी बाउंड की दबाता है, तो +scoreboard कमांड फायर करता है। जब वे इसे रिलीज़ करते हैं, -scoreboard आग लगती है। ध्यान दें कि SetNuiFocus को कॉल किया जाता है false, false क्योंकि स्कोरबोर्ड केवल प्रदर्शन के लिए है और माउस इनपुट की आवश्यकता नहीं है। यदि आप एक क्लिक करने योग्य स्कोरबोर्ड चाहते हैं जिसमें छंटाई या फ़िल्टरिंग हो, तो पास करें true, true इसके बजाय और NUI में एक बंद करने का बटन जोड़ें।

NUI रेंडरिंग विथ HTML और CSS

NUI परत खिलाड़ी सूची को एक स्टाइलिश तालिका या कार्ड लेआउट के रूप में प्रस्तुत करती है। एक अर्ध-पारदर्शी पृष्ठभूमि का उपयोग करें जो गेम स्क्रीन को ओवरले करता है बिना दृश्य को पूरी तरह से ब्लॉक किए। CSS Grid या Flexbox डेटा के कॉलम को संरेखित करने के लिए अच्छा काम करता है। पिंग मानों को रंग-कोड करें ताकि खिलाड़ी जल्दी से कनेक्शन गुणवत्ता पहचान सकें, अच्छा पिंग के लिए हरा, मध्यम के लिए पीला, और खराब के लिए लाल। यहाँ मुख्य NUI संरचना है:

<!-- html/index.html -->
<div id="scoreboard" class="hidden">
    <div class="sb-header">
        <h2 id="server-name"></h2>
        <span id="player-count"></span>
    </div>
    <div class="sb-columns">
        <span>ID</span>
        <span>Name</span>
        <span>Job</span>
        <span>Ping</span>
    </div>
    <div id="player-list"></div>
</div>

<script>
window.addEventListener('message', (event) => {
    const data = event.data;

    if (data.action === 'show') {
        const sb = document.getElementById('scoreboard');
        sb.classList.remove('hidden');
        document.getElementById('server-name').textContent = data.serverName;
        document.getElementById('player-count').textContent =
            data.playerCount + '/' + data.maxPlayers + ' Players';

        const list = document.getElementById('player-list');
        list.innerHTML = data.players.map(p => {
            const pingClass = p.ping < 80 ? 'ping-good' :
                              p.ping < 150 ? 'ping-warn' : 'ping-bad';
            return `<div class="sb-row">
                <span class="sb-id">${p.serverId}</span>
                <span class="sb-name">${p.name}</span>
                <span class="sb-job">${p.job}</span>
                <span class="sb-ping ${pingClass}">${p.ping}ms</span>
            </div>`;
        }).join('');
    }

    if (data.action === 'hide') {
        document.getElementById('scoreboard').classList.add('hidden');
    }
});
</script>

स्कोरबोर्ड को एक अंधेरे, अर्ध-पारदर्शी पृष्ठभूमि और निश्चित स्थिति के साथ स्टाइल करें ताकि यह स्क्रीन के केंद्र में बना रहे। खिलाड़ी सूची के लिए अधिकतम ऊंचाई और ओवरफ़्लो स्क्रॉलिंग जोड़ें ताकि 200 या अधिक खिलाड़ियों वाले सर्वर स्कोरबोर्ड को स्क्रीन से बाहर न धकेलें। CSS ट्रांज़िशन का उपयोग करके एक सूक्ष्म फेड-इन एनिमेशन जोड़ने पर विचार करें ताकि खोलना और बंद करना चिकना महसूस हो, न कि अचानक।

खोज और फ़िल्टर फ़ंक्शन जोड़ना

बड़े खिलाड़ी संख्या वाले सर्वरों के लिए, स्कोरबोर्ड की खोज और फ़िल्टरिंग आवश्यक हो जाती है। शीर्ष पर एक खोज इनपुट जोड़ें जो खिलाड़ी नाम, सर्वर ID, या नौकरी शीर्षक द्वारा खिलाड़ी सूची को फ़िल्टर करता है जैसे ही खिलाड़ी टाइप करता है। फ़िल्टर को पूरी तरह से JavaScript में NUI पक्ष पर लागू करें ताकि कोई सर्वर राउंड-ट्रिप विलंब न हो। आप ID, नाम, नौकरी, या पिंग द्वारा क्रमबद्ध करने के लिए क्लिक करने योग्य कॉलम हेडर भी जोड़ सकते हैं:

// html/script.js - Search and sort logic
let currentPlayers = [];
let sortField = 'serverId';
let sortAsc = true;

function renderPlayers(filter = '') {
    let filtered = currentPlayers;

    if (filter) {
        const term = filter.toLowerCase();
        filtered = currentPlayers.filter(p =>
            p.name.toLowerCase().includes(term) ||
            p.serverId.toString().includes(term) ||
            p.job.toLowerCase().includes(term)
        );
    }

    filtered.sort((a, b) => {
        let valA = a[sortField];
        let valB = b[sortField];
        if (typeof valA === 'string') {
            valA = valA.toLowerCase();
            valB = valB.toLowerCase();
        }
        if (valA < valB) return sortAsc ? -1 : 1;
        if (valA > valB) return sortAsc ? 1 : -1;
        return 0;
    });

    const list = document.getElementById('player-list');
    list.innerHTML = filtered.map(p => {
        const pingClass = p.ping < 80 ? 'ping-good' :
                          p.ping < 150 ? 'ping-warn' : 'ping-bad';
        const dutyDot = p.onDuty
            ? ''
            : '';
        return `
${p.serverId} ${p.name} ${dutyDot}${p.job} ${p.ping}ms
`; }).join(''); document.getElementById('filtered-count').textContent = filtered.length + ' shown'; } document.querySelectorAll('.sb-sort').forEach(btn => { btn.addEventListener('click', () => { const field = btn.dataset.field; if (sortField === field) { sortAsc = !sortAsc; } else { sortField = field; sortAsc = true; } renderPlayers(document.getElementById('sb-search').value); }); });

सॉर्ट स्थिति क्लाइंट-साइड पर बनी रहती है इसलिए आरोही और अवरोही क्रम के बीच टॉगल करना तुरंत होता है। नौकरी के नाम के बगल में ड्यूटी संकेतक डॉट यह त्वरित दृश्य संकेत प्रदान करता है कि क्या कोई खिलाड़ी वर्तमान में ड्यूटी पर है, जो विशेष रूप से एडमिन के लिए उपयोगी है जो विभिन्न विभागों में स्टाफिंग स्तरों की जांच करते हैं।

एडमिन फीचर्स और विस्तारित जानकारी

जब कोई एडमिन स्कोरबोर्ड खोलता है, तो उन्हें अतिरिक्त कॉलम और क्रिया बटन दिखने चाहिए जो सामान्य खिलाड़ियों को नहीं मिलते। खिलाड़ी पंक्तियों पर राइट-क्लिक संदर्भ मेनू जोड़ें जो एडमिन को सीधे स्कोरबोर्ड से खिलाड़ी को किक, बैन, टेलीपोर्ट या स्पेक्टेट करने देता है। यह स्कोरबोर्ड को एक सरल डिस्प्ले से एक हल्के एडमिन पैनल में बदल देता है। क्लाइंट और सर्वर दोनों पक्ष पर अनुमति जांच लागू करें। क्लाइंट यह जांचता है कि एडमिन UI तत्व रेंडर करने हैं या नहीं, और सर्वर हर एडमिन क्रिया को स्वतंत्र रूप से मान्य करता है:

-- Admin action handler (server)
RegisterNetEvent('scoreboard:server:adminAction', function(targetId, action)
    local src = source
    if not IsPlayerAceAllowed(src, 'admin') then
        DropPlayer(src, 'Unauthorized admin action')
        return
    end

    local target = tonumber(targetId)
    if not target or not GetPlayerName(target) then return end

    if action == 'kick' then
        DropPlayer(target, 'Kicked by admin')
    elseif action == 'teleport' then
        local targetPed = GetPlayerPed(target)
        local coords = GetEntityCoords(targetPed)
        TriggerClientEvent('scoreboard:client:teleport', src, coords)
    elseif action == 'spectate' then
        TriggerClientEvent('scoreboard:client:spectate', src, target)
    elseif action == 'freeze' then
        local targetPed = GetPlayerPed(target)
        FreezeEntityPosition(targetPed, true)
        SetTimeout(30000, function()
            if DoesEntityExist(targetPed) then
                FreezeEntityPosition(targetPed, false)
            end
        end)
    end
end)

हमेशा प्रशासनिक क्रियाओं को डेटाबेस या Discord वेबहुक में लॉग करें ताकि जवाबदेही बनी रहे। इसमें प्रशासक की पहचान, लक्षित खिलाड़ी, लिया गया कार्य, और टाइमस्टैम्प शामिल करें। यह ऑडिट ट्रेल स्टाफ सदस्यों के बीच विवादों को सुलझाने और संभावित दुरुपयोग करने वाले समझौता किए गए प्रशासनिक खातों का पता लगाने के लिए आवश्यक है।

प्रदर्शन और अनुकूलन

एक स्कोरबोर्ड जो फ्रेम ड्रॉप करता है उसका उद्देश्य विफल हो जाता है। सबसे आम प्रदर्शन गलती हर फ्रेम या हर कुछ मिलीसेकंड में खिलाड़ी डेटा को ताज़ा करना है। ऑटो-रिफ्रेश अंतराल तीन से पांच सेकंड से कम नहीं होना चाहिए क्योंकि खिलाड़ी डेटा जैसे पिंग और जॉब स्थिति इतनी तेजी से नहीं बदलते कि तेज़ अपडेट की आवश्यकता हो। NUI पक्ष पर, हर रिफ्रेश पर पूरे DOM को नष्ट करने और पुनः बनाने से बचें। इसके बजाय, एक डिफिंग दृष्टिकोण का उपयोग करें जो केवल उन पंक्तियों को अपडेट करता है जो बदली हैं, या कम से कम, उपयोग करें innerHTML संपूर्ण स्कोरबोर्ड के बजाय केवल खिलाड़ी सूची कंटेनर पर। CSS को सरल रखें और हर पंक्ति पर blur या box-shadow जैसे भारी प्रभाव से बचें क्योंकि ये सैकड़ों पंक्तियों पर GPU कंपोजिटिंग ऑपरेशन को महंगा बना देते हैं। 200 या अधिक खिलाड़ियों वाले बहुत बड़े सर्वरों के लिए, वर्चुअल स्क्रॉलिंग लागू करें जो केवल दिखाई देने वाली पंक्तियों और एक छोटे बफर को रेंडर करता है, जिससे DOM नोड की संख्या नियंत्रित रहती है चाहे कितने भी खिलाड़ी जुड़े हों। अपने स्कोरबोर्ड का परीक्षण पूर्ण सर्वर पर करें ताकि वास्तविक प्रदर्शन प्रभाव मापा जा सके, क्योंकि 10 खिलाड़ियों के साथ ठीक दिखने वाली सुविधा 200 पर बाधा बन सकती है।

शुरू करने के लिए तैयार?

हमारी शॉप से स्क्रिप्ट लें, या सपोर्ट, अपडेट और आगे आने वाली चीज़ों की झलक के लिए Discord से जुड़ें।