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