Bloga Geri Dön
Tutorial7 dk. okuma

FiveM Posta Sistemi Scripti: Oyun İçi Mesajlar ve Faturalar

Faturalar, meslek notları ve sunucu duyuruları için bir FiveM posta sistemi kurun. En iyi script'ler, framework desteği ve postayı mesleklerinize entegre etme fikirleri.

Agency Scripts

Agency Scripts Kurucusu ve Baş Geliştiricisi

Fiziksel Postanın Roleplay Değeri

Çoğu FiveM sunucusunun tamamen telefon bildirimlerine ve dijital mesajlaşmaya dayandığı bir çağda, fiziksel bir posta ve mektup sistemi fark yaratan bir rol yapma derinliği katar. Oyuncular diğer karakterlere el yazısı tarzında mektuplar yazabilir, eşya içeren paketler gönderebilir, resmi hükümet bildirimleri alabilir, işletmelerden faturalar alabilir ve hatta gizemli anonim notlar bulabilir. Bu durum, dijital iletişimin kesinlikle sunamayacağı rol yapma fırsatları yaratır. Birinin posta kutusuna bırakılan tehdit mektubu, bir kısa mesajdan çok daha etkilidir. Elle yazılmış bir aşk mektubu, bir e-postadan çok daha samimi hissettirir. Postayla iletilen mahkeme celpleri çok daha resmi algılanır. Rol yapma değerinin ötesinde bir posta sistemi, şehir genelinde postaları toplayan, ayıran ve teslim eden postacılar için doğal bir meslek fırsatı oluşturur. Bu eğitim, veritabanı şemasından teslimat mekaniklerine kadar eksiksiz bir posta sistemi geliştirmeyi kapsar.

Posta Sistemi İçin Veri Tabanı Şeması

Veritabanının mektupları, paketleri, posta kutusu atamalarını ve teslimat durumlarını yönetmesi gerekir. Her posta öğesi bir gönderici tanımlayıcısına, bir alıcı tanımlayıcısına, mektupları paketlerden ayıran bir türe, bir konu satırına, mektuplar için gövde içeriğine, paketler için ekli eşya verilerine, oluşturma ve teslimat zaman damgalarına ve postanın beklemede, yolda, teslim edildi veya okundu olup olmadığını takip eden durum bayraklarına sahiptir. Posta kutuları mülk adreslerine bağlıdır, böylece ev sahibi olan veya ev kiralayan oyuncuların kişisel bir posta kutusu olur. Şehrin dört bir yanına yerleştirilen halka açık posta kutuları, evi olmayan oyunculara hizmet verir ve postalarını almak için bir postaneyi ziyaret etmelerini gerektirir. Şema ayrıca faturalar, mahkeme belgeleri, iş başvuruları ve resmi duyurular gibi scriptlerin bir oyuncu gönderici olmadan tetikleyebileceği otomatik bildirimler için sistem tarafından oluşturulan postaları da desteklemelidir.

-- 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)
);

Mektup Yazma ve Gönderme

Mektup yazma arayüzü gerçek bir mektup kaleme alıyormuş hissi vermelidir. Oyunculara alıcı adı, konu satırı ve serbest metin gövdesi alanlarına sahip, bir kağıt parçasını andıran bir NUI sunun. Fiziksel mektup estetiğini güçlendirmek için NUI içinde el yazısı tarzı bir yazı tipi kullanın. Alıcı alanı, oyuncuların dahili kimlikleri (ID) bilmesini gerektirmek yerine karakter veritabanında isme göre arama yapmalıdır. Oyuncu yazmayı bitirip göndere tıkladığında, istemci verileri sunucuya iletir; sunucu içeriği doğrular, gönderenin posta ücretini karşılayacak yeterli parası olduğunu kontrol eder, ücreti keser ve posta ögesini bekleyen durumunda veritabanına ekler. Anlamlı mesajlara izin verirken suistimalleri önlemek için gövde uzunluğuna makul sınırlar getirin; örneğin standart mektuplar için 500, premium gönderiler için 1000 karakter. Ardından mektup teslimat sürecine girer ve bir posta çalışanının işlemesini veya otomatik teslimat süresinin dolmasını bekler.

-- 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)

Öğe Ekleri İçeren Paket Sistemi

Paketler, oyuncuların birbirine fiziksel öğeler göndermesine olanak tanıyarak posta sistemini metinlerin ötesine taşır. Bir oyuncu postaneyi ziyaret eder, envanterinden pakete eklemek istediği öğeleri seçer, isteğe bağlı bir not yazar ve paket posta ücretini öder. Sunucu öğeleri gönderenin envanterinden kaldırır ve posta öğesinin attachments sütununda bir JSON blob olarak saklar. Alıcı paketi posta kutusunda veya postanede açtığında, sunucu ek verisini deserialize eder ve önce boş yer olup olmadığını kontrol ederek her öğeyi alıcının envanterine geri ekler. Alıcının envanteri doluysa paket yer açılana kadar posta kutusunda kalır. Bu durum, çevrimdışı oyunculara öğe göndermenin meşru bir yolunu sunar ve yardım paketleri, delil teslimatları ve hediyeleşmeler için rol yapma fırsatları yaratır. Duplikasyon açıklarını önlemek için tüm öğe verilerini sunucu tarafında doğrulayın ve admin incelemesi için her paket işlemini günlüğe kaydedin.

-- 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)

Postacı Mesleği ve Teslimat Rotaları

Posta görevlisi mesleği, posta dağıtımını bir arka plan sürecinden çıkarıp aktif bir roleplay içeriğine dönüştürür. Postanede mesaiye başlayan oyuncular, bir teslimat minibüsü ve teslim edilecek bekleyen gönderilerden oluşan bir rota alır. Rota, bekleyen tüm postaların sorgulanması, teslimat bölgelerine göre gruplandırılması ve seyahat mesafesini en aza indiren sıralı bir rota noktası dizisi oluşturulmasıyla üretilir. Rotadaki her durak, haritaya bir blip ve hedef posta kutusuna bir marker yerleştirir. Posta görevlisi marker'a ulaşıp etkileşim tuşuna bastığında, posta gönderisi beklemede durumundan teslim edildi durumuna geçer ve alıcının posta kutusunda erişilebilir hale gelir. Posta çalışanları temel maaşın yanı sıra teslimat başına prim kazanarak dürüst bir gelir kaynağı elde eder. Aktif posta görevlisi bulunmayan sunucular için, yapılandırılabilir bir gecikmenin ardından bekleyen postaları teslim edildi durumuna geçiren otomatik bir teslimat zamanlayıcısı uygulayın; böylece posta rotasında kimse çalışmadığında bile postaların eninde sonunda yerine ulaşmasını sağlarsınız.

Posta Kutusu Etkileşimi ve Okuma Arayüzü

Bir oyuncu posta kutusuna yaklaşıp etkileşime girdiğinde, sunucu kendisine teslim edilmiş veya okunmuş durumdaki tüm posta öğelerini sorgular. NUI, her posta öğesini gönderen adı, konu, tür simgesi ve zaman damgasıyla gösteren gelen kutusu tarzında bir liste görüntüler. Okunmamış mektuplar bir vurgu veya rozet göstergesiyle görünür. Bir mektuba tıklandığında, üstte gönderen ve tarih, ortada gövde metni ve bir kapat butonu bulunan, kağıt üzerindeki fiziksel bir mektup gibi stillendirilmiş tam görünüm modunda açılır. Paketler için görünüm, ekli notu ve eşyaları oyuncunun envanterine aktaran bir "Paketi Aç" butonu ile birlikte içerdiği eşyaların listesini gösterir. Mektupları açıldığında okundu olarak işaretleyin ve veritabanının şişmesini önlemek için yapılandırılabilir bir saklama süresinden sonra posta kutusundan kaldırın. Alıcı alanını orijinal gönderenin adıyla önceden dolduran bir yanıtla butonu ekleyerek karşılıklı yazışmayı kolaylaştırın. Sistem bildirimleri için, bunları oyuncu tarafından yazılan postalardan ayırt etmek amacıyla resmi bir antetle farklı şekilde stillendirin.

Sistem Bildirimleri ve Otomatik Posta

Oyuncular arası iletişimin ötesinde posta sistemi, diğer sunucu scriptlerinin sürükleyici bildirimler iletmesi için güçlü bir araca dönüşür. Yavan toast bildirimleri yerine scriptler resmi posta gönderebilir. Bir mahkeme sistemi resmi antetli kağıtta tebligat gönderir. Bir işletme faturalar ve ödeme hatırlatıcıları gönderir. Bir mülk yönetim scripti kira vadesi bildirimleri ve tahliye uyarıları gönderir. Bir birlik sistemi katılım mektupları veya görev bilgilendirmeleri gönderir. Posta sisteminin iç yapısını anlamaya gerek kalmadan herhangi bir resource'un sistem posta öğesini sıraya almak için çağırabileceği basit bir server export'u oluşturun. Export: alıcı tanımlayıcısı, konu, içerik, posta türü ve isteğe bağlı ekleri kabul eder. Bu sayede posta sisteminiz, rol deneyimini bozan rahatsız edici bir UI katmanı olmak yerine oyun dünyasına entegre hissettiren evrensel bir bildirim katmanına dönüşür.

-- 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'
-- )

Eşleşen script

Sunucunuz için hazır, güncellemeler dahil.

Başlamaya Hazır mısınız?

Scriptleri mağazamızdan edinin veya destek, güncellemeler ve gelecek yeniliklere göz atmak için Discord'a katılın.