تقنيات تصحيح أخطاء 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للتقاط الأخطاء بسلاسة بدلاً من السماح لها بتحطيم سكربتك. - قم بترقيم إصدارات إعداداتك. عندما يبلغ اللاعبون عن أخطاء، اسأل عن الإصدار الذي يستخدمونه. العديد من المشاكل تنشأ من ملفات التكوين القديمة بعد التحديث.