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

نظام الجراج في FiveM: تخزين، ظهور، وحجز المركبات

تصميم نظام جراج متين لـ FiveM. جراجات شخصية ووظيفية، تدفق الحجز، المفاتيح، روابط الجرد وأفضل سكربتات الجراج المجانية والمدفوعة.

Agency Scripts

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

فهم بنية نظام الجراج

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

مخطط قاعدة البيانات واستمرارية المركبات

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

CREATE TABLE IF NOT EXISTS player_vehicles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    citizenid VARCHAR(50) NOT NULL,
    vehicle VARCHAR(50) NOT NULL,
    hash VARCHAR(50) NOT NULL,
    mods LONGTEXT DEFAULT '{}',
    plate VARCHAR(8) NOT NULL,
    fakeplate VARCHAR(8) DEFAULT NULL,
    garage VARCHAR(50) DEFAULT 'pillboxgarage',
    fuel INT DEFAULT 100,
    engine FLOAT DEFAULT 1000.0,
    body FLOAT DEFAULT 1000.0,
    state INT DEFAULT 1,  -- 0 = out, 1 = garaged, 2 = impounded
    depotprice INT DEFAULT 0,
    drivingdistance INT DEFAULT 0,
    INDEX idx_citizenid (citizenid),
    INDEX idx_plate (plate),
    INDEX idx_state (state)
);

ال mods عمود يخزن كائن مشفر بصيغة JSON يحتوي على جميع تعديلات المركبة التي تعيدها دوال مثل QBCore.Functions.GetVehicleProperties(vehicle) أو ما يعادلها في ESX. فهرسة citizenid, plate، و state الأعمدة تضمن بقاء عمليات البحث سريعة حتى مع نمو قاعدة اللاعبين إلى الآلاف. استخدم دائماً استعلامات معلمات عند التفاعل مع هذا الجدول لمنع هجمات حقن SQL.

منطق الظهور والاختفاء على جانب الخادم

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

RegisterNetEvent('garage:server:takeVehicle', function(vehicleId, garageId)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local citizenid = Player.PlayerData.citizenid
    local result = MySQL.query.await(
        'SELECT * FROM player_vehicles WHERE id = ? AND citizenid = ? AND state = 1',
        {vehicleId, citizenid}
    )

    if not result or not result[1] then
        TriggerClientEvent('QBCore:Notify', src, 'Vehicle not found', 'error')
        return
    end

    local vehData = result[1]
    local spawnPoint = GetAvailableSpawnPoint(garageId)

    if not spawnPoint then
        TriggerClientEvent('QBCore:Notify', src, 'No parking spots available', 'error')
        return
    end

    MySQL.update('UPDATE player_vehicles SET state = 0 WHERE id = ?', {vehicleId})
    TriggerClientEvent('garage:client:spawnVehicle', src, vehData, spawnPoint)
end)

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

واجهة مرآب على جانب العميل مع NUI

واجهة الجراج هي المكان الذي يتفاعل فيه اللاعبون مع النظام، وواجهة مصممة جيدًا تصنع الفرق بين تجربة محبطة وتجربة سلسة. استخدم NUI مع HTML وCSS وJavaScript لبناء لوحة استجابة تعرض كل المركبات المخزنة في الجراج الحالي. يجب أن يعرض كل إدخال مركبة اسم المركبة، لوحة الترخيص، مستوى الوقود، والحالة العامة بنظرة سريعة. ضمن نظام معاينة ينشئ نموذج المركبة مؤقتًا حتى يتمكن اللاعبون من رؤية ما يختارونه، وهو مفيد خصوصًا عندما يمتلك اللاعب عدة مركبات من نفس النوع. هذه هي منطق العميل لفتح قائمة الجراج وجمع بيانات المركبة:

RegisterNetEvent('garage:client:openMenu', function(garageId)
    QBCore.Functions.TriggerCallback('garage:server:getVehicles', function(vehicles)
        if not vehicles or #vehicles == 0 then
            QBCore.Functions.Notify('No vehicles stored here', 'info')
            return
        end

        SetNuiFocus(true, true)
        SendNUIMessage({
            action = 'openGarage',
            vehicles = vehicles,
            garageName = Config.Garages[garageId].label
        })
    end, garageId)
end)

RegisterNUICallback('takeVehicle', function(data, cb)
    SetNuiFocus(false, false)
    TriggerServerEvent('garage:server:takeVehicle', data.vehicleId, currentGarage)
    cb('ok')
end)

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

تخزين واستعادة ممتلكات المركبة

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

function SpawnAndApplyMods(vehData, spawnPoint)
    local model = GetHashKey(vehData.vehicle)
    RequestModel(model)

    while not HasModelLoaded(model) do
        Wait(10)
    end

    local veh = CreateVehicle(model, spawnPoint.x, spawnPoint.y, spawnPoint.z,
        spawnPoint.w, true, false)

    while not DoesEntityExist(veh) do
        Wait(10)
    end

    local props = json.decode(vehData.mods)
    if props then
        QBCore.Functions.SetVehicleProperties(veh, props)
    end

    SetVehicleFuelLevel(veh, vehData.fuel + 0.0)
    SetVehicleEngineHealth(veh, vehData.engine + 0.0)
    SetVehicleBodyHealth(veh, vehData.body + 0.0)
    SetEntityAsMissionEntity(veh, true, true)
    SetModelAsNoLongerNeeded(model)
    TaskWarpPedIntoVehicle(PlayerPedId(), veh, -1)
end

انتبه بشكل خاص لمركبات الإضافة لأنها أحيانًا تحتوي على إضافات مخصصة أو مؤشرات طلاء تتصرف بشكل مختلف عن مركبات GTA الأصلية. اختبر دورة حفظ واستعادة الممتلكات الخاصة بك بدقة مع مجموعة متنوعة من أنواع المركبات لاكتشاف الحالات الحافة مبكرًا.

تكامل نظام الحجز

نظام الحجز يعمل جنبًا إلى جنب مع المرآب الخاص بك ويضيف طبقة من الواقعية التي تتطلبها خوادم لعب الأدوار. تنتهي المركبات في الحجز لأسباب عدة: مصادرة الشرطة أثناء الاعتقال، التنظيف التلقائي للمركبات المهجورة بعد إعادة تشغيل الخادم، أو إجراء إداري لانتهاكات القواعد. عند حجز مركبة، حدّث حالتها إلى 2 في قاعدة البيانات وحدد سعر مستودع اختياري يجب على اللاعب دفعه لاسترجاعها. يجب أن يعمل موقف الحجز بشكل مشابه للمرآب لكن مع شرط الدفع قبل الإفراج. أنشئ موقع حجز منفصل على الخريطة مع نقاط استدعاء خاصة وواجهة NUI تعرض رسوم الحجز بوضوح.

RegisterNetEvent('police:server:impoundVehicle', function(plate, price)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Verify the player has police job authorization
    if Player.PlayerData.job.name ~= 'police' then return end

    local result = MySQL.update.await(
        'UPDATE player_vehicles SET state = 2, depotprice = ? WHERE plate = ?',
        {price or 500, plate}
    )

    if result > 0 then
        TriggerClientEvent('QBCore:Notify', src, 'Vehicle impounded', 'success')
    end
end)

فكر في تنفيذ نظام تسعير متدرج حيث تزداد رسوم الحجز في كل مرة يتم فيها حجز نفس المركبة، مما يثني اللاعبين عن اعتبار الحجز كموقف مجاني. يمكنك أيضًا إضافة ميكانيكية تعتمد على الوقت حيث يتم إطلاق سراح المركبات التي تُترك في الحجز لأكثر من عدد معين من الأيام الحقيقية بشكل تلقائي إلى المرآب دون رسوم، مما يمنع سيناريوهات الفقدان الدائم التي تثير استياء اللاعبين.

علامات المرآب وتكامل الهدف

جعل الكراجات قابلة للاكتشاف وسهلة التفاعل يتطلب وضع علامات مناسبة ومناطق تفاعل. أضف علامات على الخريطة لكل موقع كراج حتى يتمكن اللاعبون من العثور عليها على الخريطة المصغرة، واستخدم إما علامات تعتمد على القرب أو تكامل نظام الهدف لتفعيل التفاعل. توفر أنظمة الهدف مثل ox_target أو qb-target تجربة أنظف لأنها تظهر خيارات التفاعل فقط عندما يوجه اللاعب إلى نقطة محددة، مما يقلل من ازدحام الشاشة. عرّف مواقع الكراجات في ملف تكوين مشترك يمكن لكل من العميل والخادم الرجوع إليه، مع الحفاظ على إحداثيات ونقاط الظهور والإعدادات متزامنة:

Config.Garages = {
    ['pillboxgarage'] = {
        label = 'Pillbox Garage',
        coords = vector3(215.83, -810.18, 30.73),
        spawnPoints = {
            vector4(218.32, -803.28, 30.73, 248.5),
            vector4(222.41, -799.84, 30.73, 248.5),
            vector4(226.52, -796.41, 30.73, 248.5),
        },
        blip = { sprite = 357, color = 3, scale = 0.7 },
        vehicleType = 'car',  -- car, boat, aircraft
    },
}

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

نصائح تحسين الأداء

يمكن لأنظمة المرآب أن تصبح عنق زجاجة في الأداء إذا لم تُنفذ بعناية، خاصة على الخوادم التي تضم مئات اللاعبين المتزامنين كل منهم يمتلك مركبات متعددة. خزّن قوائم المركبات على جانب الخادم بدلاً من الاستعلام من قاعدة البيانات في كل مرة يفتح فيها اللاعب قائمة المرآب، وقم بإبطال التخزين المؤقت فقط عندما يتغير حالة المركبة. على جانب العميل، تجنب إبقاء إطارات NUI مفتوحة عندما لا تكون ضرورية لأن حتى إطارات NUI المخفية تستهلك موارد إذا كانت تشغل مؤقتات JavaScript أو الرسوم المتحركة. عند استدعاء المركبات، تأكد من تنظيف الكيانات بشكل صحيح عن طريق تعيينها كغير ضرورية بعد أن يخزنها اللاعب، ونفذ روتين تنظيف احتياطي يعمل دورياً لالتقاط أي كيانات مركبات يتيمة لم يتم إزالتها بشكل صحيح بسبب الأعطال أو الانقطاعات. استخدم الدوال الأصلية مثل GetGamePool('CVehicle') بحذر وتخزين النتائج مؤقتًا عند الحاجة للتحقق من وجود مركبات اللاعبين في العالم. وأخيرًا، فكر في تنفيذ حد أقصى للمركبات لكل كراج للحفاظ على استعلامات قاعدة البيانات محدودة ومنع أي لاعب من تخزين مئات المركبات التي قد تبطئ عمليات الاسترجاع.

جاهز للبدء؟

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