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

سكربت نظام البريد في FiveM: الرسائل والفواتير داخل اللعبة

إعداد نظام بريد FiveM للفواتير، ملاحظات الوظائف وإعلانات الخادم. أفضل السكريبتات، دعم الأطر وأفكار لربط البريد بوظائفك.

Agency Scripts

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

القيمة التمثيلية للبريد الورقي

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

مخطط قاعدة البيانات لنظام البريد

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

-- SQL schema
CREATE TABLE IF NOT EXISTS mail_items (
    id           INT AUTO_INCREMENT PRIMARY KEY,
    sender_id    VARCHAR(64),
    sender_name  VARCHAR(64) DEFAULT 'Unknown',
    recipient_id VARCHAR(64) NOT NULL,
    mail_type    ENUM('letter','package','notice') DEFAULT 'letter',
    subject      VARCHAR(128) NOT NULL,
    body         TEXT,
    attachments  JSON DEFAULT '[]',
    postage_paid INT DEFAULT 0,
    status       ENUM('pending','transit','delivered','read','returned')
                 DEFAULT 'pending',
    mailbox_id   INT DEFAULT NULL,
    created_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    delivered_at TIMESTAMP NULL,
    read_at      TIMESTAMP NULL,
    INDEX idx_recipient (recipient_id, status),
    INDEX idx_status (status)
);

CREATE TABLE IF NOT EXISTS mailboxes (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    owner_id    VARCHAR(64),
    property_id INT DEFAULT NULL,
    location_x  FLOAT NOT NULL,
    location_y  FLOAT NOT NULL,
    location_z  FLOAT NOT NULL,
    box_type    ENUM('residential','public','business') DEFAULT 'residential',
    capacity    INT DEFAULT 20,
    UNIQUE KEY unique_owner (owner_id)
);

كتابة وإرسال الرسائل

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

-- server/mail.lua
local Config = {
    postageCost     = 50,
    premiumPostage  = 150,
    maxBodyLength   = 500,
    premiumMaxBody  = 1000,
    autoDeliverTime = 900, -- 15 minutes if no postal worker
    packagePostage  = 200,
}

RegisterNetEvent('mail:send', function(data)
    local src = source
    local sender = GetPlayerIdentifier(src, 0)

    -- Validate recipient exists
    local recipient = MySQL.single.await([[
        SELECT identifier, CONCAT(firstname,' ',lastname) as name
        FROM characters WHERE CONCAT(firstname,' ',lastname) LIKE ?
        LIMIT 1
    ]], {'%' .. data.recipientName .. '%'})

    if not recipient then
        TriggerClientEvent('mail:notify', src, 'Recipient not found.')
        return
    end

    -- Check postage funds
    local cost = data.premium and Config.premiumPostage or Config.postageCost
    local maxLen = data.premium and Config.premiumMaxBody or Config.maxBodyLength

    if #data.body > maxLen then
        TriggerClientEvent('mail:notify', src, 'Letter exceeds maximum length.')
        return
    end

    -- Deduct postage (framework-specific money removal)
    local paid = exports['framework']:RemoveMoney(src, cost, 'cash')
    if not paid then
        TriggerClientEvent('mail:notify', src, 'Not enough cash for postage.')
        return
    end

    -- Insert mail
    MySQL.insert([[
        INSERT INTO mail_items
            (sender_id, sender_name, recipient_id, mail_type,
             subject, body, postage_paid, status)
        VALUES (?, ?, ?, 'letter', ?, ?, ?, 'pending')
    ]], {
        sender, data.senderName, recipient.identifier,
        data.subject, data.body, cost
    })

    TriggerClientEvent('mail:notify', src, 'Letter sent! Postage: $' .. cost)
end)

نظام الطرود مع مرفقات العناصر

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

-- server/packages.lua
RegisterNetEvent('mail:sendPackage', function(data)
    local src = source
    local sender = GetPlayerIdentifier(src, 0)

    -- Validate items exist in sender inventory
    local validItems = {}
    for _, item in ipairs(data.items) do
        local hasItem = exports['ox_inventory']:Search(src, 'count', item.name)
        if hasItem < item.count then
            TriggerClientEvent('mail:notify', src,
                'You do not have enough ' .. item.name)
            return
        end
        table.insert(validItems, {
            name  = item.name,
            count = item.count,
            meta  = item.metadata or {}
        })
    end

    -- Remove items from sender
    for _, item in ipairs(validItems) do
        exports['ox_inventory']:RemoveItem(src, item.name, item.count)
    end

    -- Deduct postage
    local paid = exports['framework']:RemoveMoney(
        src, Config.packagePostage, 'cash')
    if not paid then
        -- Refund items if payment fails
        for _, item in ipairs(validItems) do
            exports['ox_inventory']:AddItem(src, item.name, item.count)
        end
        return
    end

    MySQL.insert([[
        INSERT INTO mail_items
            (sender_id, sender_name, recipient_id, mail_type,
             subject, body, attachments, postage_paid, status)
        VALUES (?, ?, ?, 'package', ?, ?, ?, ?, 'pending')
    ]], {
        sender, data.senderName, data.recipientId,
        data.subject or 'Package',
        data.note or '',
        json.encode(validItems),
        Config.packagePostage
    })
end)

وظيفة عامل البريد ومسارات التوصيل

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

واجهة التفاعل مع صندوق البريد وقراءة الرسائل

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

إشعارات النظام والبريد الآلي

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

-- server/exports.lua
-- Universal mail export for other resources
function SendSystemMail(recipientId, subject, body, mailType, attachments)
    mailType = mailType or 'notice'
    attachments = attachments or '[]'

    if type(attachments) == 'table' then
        attachments = json.encode(attachments)
    end

    MySQL.insert([[
        INSERT INTO mail_items
            (sender_id, sender_name, recipient_id, mail_type,
             subject, body, attachments, status)
        VALUES ('system', 'City of Los Santos', ?, ?, ?, ?, ?, 'pending')
    ]], {recipientId, mailType, subject, body, attachments})
end

exports('SendSystemMail', SendSystemMail)

-- Usage from another resource:
-- exports['mailsystem']:SendSystemMail(
--     playerId, 'Rent Due',
--     'Your rent of $2,500 is due in 3 days.',
--     'notice'
-- )

جاهز للبدء؟

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