العودة إلى المدونة
Guide7 دقيقة قراءة

تقنيات تصحيح أخطاء FiveM: العثور على الأخطاء بسرعة في Lua و JS

تصحيح سكربتات FiveM بشكل أسرع. سجلات الخادم، طباعة العميل، أدوات txAdmin، حيل المحلل، وسير العمل المثبت لإصلاح المشكلات قبل أن يلاحظها اللاعبون.

Agency Scripts

المؤسس والمطور الرئيسي في Agency Scripts

فن تصحيح أخطاء سكربتات FiveM

تصحيح سكربتات FiveM يختلف جوهريًا عن تصحيح التطبيقات القياسية. أنت تتعامل مع بنية عميل-خادم مقسمة حيث يعمل الكود على بيئتين منفصلتين، تنتقل الأحداث عبر الشبكة، وتتغير حالة اللعبة في كل إطار. عندما يحدث خطأ، قد يكون مصدره الخادم لكنه يظهر على العميل، أو العكس. تطوير نهج منهجي للتصحيح سيوفر عليك ساعات لا تحصى ويجعل سير عمل التطوير أسرع بشكل كبير. يغطي هذا الدليل الأدوات الأساسية، التقنيات، والأنماط التي يستخدمها مطورو FiveM المحترفون يوميًا.

استخدام بيانات الطباعة بفعالية

أداة تصحيح الأخطاء الأساسية في FiveM هي print() function، لكن استخدامه بفعالية يتطلب أكثر من مجرد إلقاء المتغيرات. نظم مخرجات التصحيح الخاصة بك مع بادئات تحدد السكريبت، الجانب (العميل أو الخادم)، والfunction التي يحدث فيها الطباعة. استخدم رموز الألوان لجعل الرسائل المهمة بارزة في وحدة التحكم. أنشئ أداة تصحيح يمكنك تشغيلها وإيقافها دون إزالة أسطر التصحيح من كودك.

-- shared/debug.lua
local DEBUG_ENABLED = GetConvar('myresource_debug', 'false') == 'true'
local RESOURCE_NAME = GetCurrentResourceName()

function DebugLog(module, message...)
    if not DEBUG_ENABLED then return end

    local side = IsDuplicityVersion() and 'SERVER' else 'CLIENT'
    local formatted = type(message) == 'string' and message:format(...) or tostring(message)
    local timestamp = os.date('%H:%M:%S')

    print(('[^3%s^0][^5%s^0][^2%s^0] %s'):format(
        timestamp, RESOURCE_NAME, side .. ':' .. module, formatted
    ))
end

function DebugTable(module, tbl, depth)
    if not DEBUG_ENABLED then return end
    depth = depth or 0
    local indent = string.rep('  ', depth)

    if type(tbl) ~= 'table' then
        DebugLog(module, '%s%s', indent, tostring(tbl))
        return
    end

    for k, v in pairs(tbl) do
        if type(v) == 'table' then
            DebugLog(module, '%s%s = {', indent, tostring(k))
            DebugTable(module, v, depth + 1)
            DebugLog(module, '%s}', indent)
        else
            DebugLog(module, '%s%s = %s (%s)', indent, tostring(k), tostring(v), type(v))
        end
    end
end

استخدام أداة التصحيح

مع وجود هذه الأداة، يمكنك إضافة تسجيل تصحيح الأخطاء في جميع أنحاء سكريبتاتك بصمت تام في بيئة الإنتاج. قم بتمكينه بإضافة set myresource_debug true إلى تكوين الخادم الخاص بك عند الحاجة لاستكشاف الأخطاء وإصلاحها. يجعل الإخراج المنظم من السهل البحث في سجلات وحدة التحكم والعثور على ما تبحث عنه بالضبط.

-- server/jobs.lua
RegisterNetEvent('myresource:startJob', function(jobName)
    local src = source
    DebugLog('jobs', 'Player %d attempting to start job: %s', src, jobName)

    local playerData = GetPlayerData(src)
    DebugTable('jobs', playerData)

    if not playerData then
        DebugLog('jobs', 'ERROR: No player data found for source %d', src)
        return
    end

    if playerData.job == jobName then
        DebugLog('jobs', 'Player %d already has job %s, skipping', src, jobName)
        return
    end

    DebugLog('jobs', 'Job %s assigned to player %d successfully', jobName, src)
end)

أخطاء FiveM الشائعة وحلولها

خطأ في السكربت: محاولة فهرسة قيمة nil

هذا هو أكثر أخطاء Lua شيوعًا في تطوير FiveM. يعني أنك تحاول الوصول إلى خاصية أو طريقة على متغير هو nil. الأسباب الأكثر شيوعًا هي الوصول إلى بيانات اللاعب قبل تحميلها، أو الإشارة إلى دالة إطار عمل غير موجودة في النسخة التي تستخدمها، أو قراءة قيمة إعداد بها خطأ مطبعي في المفتاح. تحقق دائمًا من nil قبل الوصول إلى الخصائص المتداخلة.

-- BAD: Will crash if GetPlayerData returns nil
local name = GetPlayerData(src).charinfo.firstname

-- GOOD: Defensive nil checks
local playerData = GetPlayerData(src)
if not playerData then
    print('[ERROR] Player data is nil for source: ' .. src)
    return
end

local charinfo = playerData.charinfo
if not charinfo then
    print('[ERROR] charinfo missing for source: ' .. src)
    return
end

local name = charinfo.firstname or 'Unknown'

خطأ في السكربت: محاولة استدعاء قيمة nil

يحدث هذا الخطأ عندما تحاول استدعاء دالة غير موجودة. في FiveM، يحدث هذا عادة عند استدعاء تصدير من مورد لم يبدأ بعد، استخدام دالة إطار عمل تم إعادة تسميتها في تحديث، أو نسيان تحميل ملف مشترك في ملف التعريف الخاص بك. تحقق من fxmanifest.lua للتأكد من سرد جميع الملفات المطلوبة وبالترتيب الصحيح.

-- Safely calling an export that might not be available
local function SafeExport(resource, exportName...)
    local success, result = pcall(function(...)
        return exports[resource][exportName](...)
    end...)

    if not success then
        print(('[^1ERROR^0] Failed to call export %s:%s - %s'):format(
            resource, exportName, tostring(result)
        ))
        return nil
    end

    return result
end

-- Usage
local inventory = SafeExport('ox_inventory', 'GetInventory', src)

لم يتم تسجيل الحدث

عندما تثير حدثًا لم يسجل أي سكربت معالجًا له، يتجاهله FiveM بصمت دون خطأ في وحدة التحكم. قد يكون هذا محبطًا في التصحيح لأن كل شيء يبدو صحيحًا لكن لا يحدث شيء. استخدم دالة مساعدة للتحقق من تسجيل الأحداث قبل إثارتها، وأضف تسجيلًا حول محفزات الأحداث أثناء التطوير.

-- server/debug_events.lua
-- Wrap TriggerClientEvent to log when events fire
local originalTrigger = TriggerClientEvent

if GetConvar('myresource_debug', 'false') == 'true' then
    TriggerClientEvent = function(eventName, target...)
        print(('[^3EVENT^0] TriggerClientEvent: %s -> target: %s'):format(
            eventName, tostring(target)
        ))
        return originalTrigger(eventName, target...)
    end
end

تصحيح NUI باستخدام DevTools

للسكريبتات التي تحتوي على واجهات NUI، أدوات مطوري Chromium المدمجة لا تقدر بثمن. افتحها باستخدام وحدة تحكم F8 بكتابة nui_devtools للوصول إلى أداة Chrome الكاملة للتفتيش. هذا يمنحك لوحة العناصر لفحص هيكل DOM، والكونسول لأخطاء JavaScript، وعلامة الشبكة لتحميل الموارد، ولوحة المصادر لتعيين نقاط التوقف. لمشاكل الاتصال بـ NUI، سجل كلا جانبي جسر الرسائل.

// nui/js/debug.js
// Log all incoming NUI messages
window.addEventListener('message', (event) => {
    if (event.data && event.data.action) {
        console.log(
            '%c[NUI Received]%c ' + event.data.action,
            'background: #2dd4bf; color: #000; padding: 2px 6px; border-radius: 3px;',
            'color: #94a3b8;',
            event.data
        );
    }
});

// Wrap fetch to log NUI callbacks
const originalFetch = window.fetch;
window.fetch = function(url, options) {
    const body = options?.body ? JSON.parse(options.body) : null;
    console.log(
        '%c[NUI Callback]%c ' + url,
        'background: #8b5cf6; color: #fff; padding: 2px 6px; border-radius: 3px;',
        'color: #94a3b8;',
        body
    );
    return originalFetch.apply(this, arguments);
};

التحليل باستخدام Resmon و Timing

بعيداً عن الأساسيات resmon المراقبة، يمكنك بناء أدوات توقيت دقيقة في سكربتاتك. قس مدة العمليات المحددة وسجل تحذيرات عند تجاوزها للحدود المقبولة. هذا مهم بشكل خاص لاستعلامات قواعد البيانات، الحسابات المعقدة، والحلقات التي تعالج العديد من الكيانات.

-- shared/profiler.lua
local Profiler = {}

function Profiler.Start(label)
    return {
        label = label,
        startTime = GetGameTimer()
    }
end

function Profiler.Stop(timer, warnThresholdMs)
    local elapsed = GetGameTimer() - timer.startTime
    warnThresholdMs = warnThresholdMs or 5

    if elapsed >= warnThresholdMs then
        print(('[^1PERF WARNING^0] %s took %dms (threshold: %dms)'):format(
            timer.label, elapsed, warnThresholdMs
        ))
    elseif GetConvar('myresource_debug', 'false') == 'true' then
        print(('[^2PERF^0] %s completed in %dms'):format(timer.label, elapsed))
    end

    return elapsed
end

-- Usage in a server event
RegisterNetEvent('myresource:heavyOperation', function(data)
    local timer = Profiler.Start('heavyOperation')

    -- ... expensive processing ...
    local result = ProcessLargeDataSet(data)

    Profiler.Stop(timer, 10) -- warn if over 10ms
end)

تصحيح State Bag

State bags هي ميزة قوية لكنها قد تكون مربكة أحياناً. عندما لا تتحدث قيم State Bag كما هو متوقع، يكون السبب عادة أنك تحددها على الكيان الخطأ، أو المعالج لا يلتقط اسم الحقيبة الصحيح، أو هناك تأخير في التكرار. أنشئ أمر مفتش State Bag يعرض كل الحالة لكيان معين.

-- server/debug_statebags.lua
RegisterCommand('debugstate', function(source, args)
    local targetId = tonumber(args[1])
    if not targetId then
        print('Usage: debugstate [playerId]')
        return
    end

    local playerPed = GetPlayerPed(targetId)
    if playerPed == 0 then
        print('Player not found: ' .. targetId)
        return
    end

    local entityState = Player(targetId).state
    print(('[^3STATE BAGS^0] Player %d:'):format(targetId))

    -- Print known state keys (state bags don't have an iterator)
    local keysToCheck = {'job', 'gang', 'duty', 'dead', 'phone', 'inventory'}
    for _, key in ipairs(keysToCheck) do
        local val = entityState[key]
        if val ~= nil then
            print(('  %s = %s (%s)'):format(key, tostring(val), type(val)))
        end
    end
end, true)

قائمة التحقق الأساسية للتصحيح

  • تحقق من كلا الطرفين. انظر دائمًا إلى كل من وحدة تحكم الخادم (txAdmin أو الطرفية) ووحدة تحكم العميل (F8) للأخطاء. غالبًا ما يفسر خطأ في جانب واحد السلوك المعطل في الجانب الآخر.
  • تحقق من حالة المورد. استخدم ensure لإعادة تشغيل المورد الخاص بك و restart لإعادة تشغيل مورد واحد. تحقق من resmon للتأكد من أن المورد يعمل فعليًا.
  • اختبر في بيئة نظيفة. تعطيل السكربتات الأخرى التي تتفاعل مع نفس الأنظمة. العديد من الأخطاء تأتي من تعارضات بين الموارد بدلاً من أخطاء داخل سكربت واحد.
  • اقرأ تتبع الخطأ بعناية. تُظهر تتبعات مكدس Lua الملف ورقم السطر بالضبط. اقرأها من الأسفل إلى الأعلى لفهم سلسلة الاستدعاءات التي أدت إلى الخطأ.
  • استخدم pcall للعمليات الخطرة. لف استعلامات قاعدة البيانات، استدعاءات التصدير، وفك ترميز JSON في pcall للتقاط الأخطاء بسلاسة بدلاً من السماح لها بتحطيم سكربتك.
  • قم بترقيم إصدارات إعداداتك. عندما يبلغ اللاعبون عن أخطاء، اسأل عن الإصدار الذي يستخدمونه. العديد من المشاكل تنشأ من ملفات التكوين القديمة بعد التحديث.

جاهز للبدء؟

احصل على السكربتات من متجرنا، أو انضم إلى Discord للدعم والتحديثات ونظرة على ما هو قادم.