Skrypt systemu poczty FiveM: Wiadomości w grze i rachunki
Skonfiguruj system poczty FiveM dla rachunków, notatek frakcyjnych i ogłoszeń serwerowych. Najlepsze skrypty, wsparcie dla frameworków i pomysły na integrację poczty z pracami.
Agency Scripts
Założyciel i główny deweloper w Agency Scripts
Wartość tradycyjnej poczty w roleplayu
W czasach, gdy większość serwerów FiveM polega wyłącznie na powiadomieniach w telefonie i cyfrowych wiadomościach, fizyczny system poczty i listów wprowadza wyjątkowy poziom immersji. Gracze mogą pisać listy stylizowane na odręczne do innych postaci, wysyłać paczki z przedmiotami, otrzymywać oficjalne pisma urzędowe, rachunki od firm, a nawet znajdować tajemnicze, anonimowe notatki. Tworzy to możliwości roleplay, których komunikacja cyfrowa po prostu nie jest w stanie odtworzyć. List z pogróżkami zostawiony w skrzynce ma większą wagę niż SMS. Odręczny list miłosny jest bardziej osobisty niż e-mail. Wezwanie do sądu doręczone pocztą wygląda znacznie oficjalniej. Poza wartością dla roleplay, system pocztowy stwarza naturalną pracę dla listonoszy, którzy odbierają, sortują i doręczają przesyłki w całym mieście. Ten poradnik obejmuje budowę kompletnego systemu pocztowego: od schematu bazy danych po mechanikę doręczania.
Schemat bazy danych dla systemu poczty
Baza danych musi obsługiwać listy, paczki, przypisania skrzynek pocztowych i statusy doręczenia. Każda przesyłka posiada identyfikator nadawcy, identyfikator odbiorcy, typ rozróżniający listy od paczek, temat, treść dla listów, dołączone dane przedmiotów dla paczek, znaczniki czasu utworzenia i doręczenia oraz flagi statusu informujące, czy przesyłka oczekuje, jest w drodze, została doręczona lub przeczytana. Skrzynki pocztowe są powiązane z adresami nieruchomości, dzięki czemu gracze posiadający lub wynajmujący mieszkania mają własną skrzynkę. Skrzynki publiczne rozmieszczone w całym mieście obsługują graczy bezdomnych, wymagając od nich wizyty na poczcie w celu odebrania poczty. Schemat powinien również obsługiwać przesyłki systemowe dla automatycznych powiadomień, takich jak rachunki, pisma sądowe, aplikacje o pracę i ogłoszenia urzędowe, które skrypty mogą wysyłać bez nadawcy będącego graczem.
-- 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)
);
Pisanie i wysyłanie listów
Interfejs pisania listów powinien dawać poczucie tworzenia prawdziwego listu. Przedstaw graczom NUI przypominające kartkę papieru z polami na nazwisko odbiorcy, temat i treść. Użyj w NUI czcionki przypominającej pismo odręczne, aby wzmocnić estetykę fizycznego listu. Pole odbiorcy powinno przeszukiwać bazę danych postaci po nazwisku, zamiast wymagać od graczy znajomości wewnętrznych identyfikatorów. Kiedy gracz skończy pisać i kliknie wyślij, klient przesyła dane do serwera, który weryfikuje treść, sprawdza, czy nadawca ma wystarczająco dużo pieniędzy na opłacenie przesyłki, pobiera opłatę i dodaje przesyłkę do bazy danych ze statusem oczekującym. Nałóż rozsądne limity na długość treści, na przykład 500 znaków dla zwykłych listów i 1000 dla przesyłek premium, aby zapobiec nadużyciom, jednocześnie umożliwiając pisanie sensownych wiadomości. List trafia następnie do kolejki doręczeń i czeka na przetworzenie przez listonosza lub na upłynięcie automatycznego licznika doręczenia.
-- 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)
System paczek z załącznikami przedmiotów
Paczki rozszerzają system pocztowy poza zwykły tekst, umożliwiając graczom przesyłanie sobie fizycznych przedmiotów. Gracz odwiedza pocztę, wybiera przedmioty ze swojego ekwipunku do dołączenia do paczki, pisze opcjonalną notatkę i uiszcza opłatę pocztową. Serwer usuwa przedmioty z ekwipunku nadawcy i zapisuje je jako obiekt JSON w kolumnie attachments elementu pocztowego. Gdy odbiorca otwiera paczkę przy swojej skrzynce pocztowej lub na poczcie, serwer deserializuje dane załącznika i dodaje każdy przedmiot z powrotem do ekwipunku odbiorcy, sprawdzając najpierw dostępne miejsce. Jeśli ekwipunek odbiorcy jest pełny, paczka pozostaje w jego skrzynce pocztowej do momentu zrobienia miejsca. Tworzy to w pełni legalny sposób przesyłania przedmiotów do graczy offline i otwiera możliwości roleplay dla paczek pomocowych, podrzucania dowodów oraz wymiany prezentów. Waliduj wszystkie dane przedmiotów po stronie serwera, aby zapobiec exploitom duplikowania, i loguj każdą transakcję paczki do weryfikacji przez administratorów.
-- 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)
Praca listonosza i trasy dostaw
Praca listonosza przekształca doręczanie poczty z procesu w tle w aktywny content roleplay. Gracze, którzy rozpoczynają służbę na poczcie, otrzymują furgonetkę dostawczą i trasę oczekujących przesyłek do doręczenia. Trasa jest generowana poprzez pobranie wszystkich oczekujących przesyłek, pogrupowanie ich według stref doręczeń i utworzenie uporządkowanej sekwencji punktów trasy, która minimalizuje pokonywany dystans. Każdy przystanek na trasie umieszcza blip na mapie oraz marker przy docelowej skrzynce pocztowej. Gdy listonosz dociera do markera i naciska klawisz interakcji, status przesyłki zmienia się z oczekującej na doręczoną i staje się ona dostępna w skrzynce odbiorcy. Listonosze zarabiają pensję podstawową powiększoną o premie za każdą doręczoną przesyłkę, co tworzy uczciwe źródło dochodu. W przypadku serwerów bez aktywnych listonoszy wdróż automatyczny timer doręczeń, który zmienia status oczekującej poczty na doręczoną po konfigurowalnym czasie, zapewniając, że przesyłki ostatecznie dotrą na miejsce, nawet gdy nikt nie pracuje na trasie pocztowej.
Interakcja ze skrzynką pocztową i interfejs odczytu
Gdy gracz podchodzi do swojej skrzynki pocztowej i wchodzi z nią w interakcję, serwer odpytuje o wszystkie wiadomości zaadresowane do niego ze statusem doręczono lub przeczytano. NUI wyświetla listę w stylu skrzynki odbiorczej, pokazując każdy element poczty wraz z nazwą nadawcy, tematem, ikoną typu i znacznikiem czasu. Nieprzeczytane listy pojawiają się z wyróżnieniem lub wskaźnikiem powiadomienia. Kliknięcie listu otwiera go w trybie pełnoekranowym stylizowanym na fizyczny list na papierze, z nadawcą i datą na górze, treścią na środku i przyciskiem zamknięcia. W przypadku paczek widok pokazuje dołączoną notatkę i listę zawartych przedmiotów z przyciskiem "Otwórz paczkę", który przenosi przedmioty do ekwipunku gracza. Oznaczaj listy jako przeczytane po otwarciu i usuwaj je ze skrzynki pocztowej po konfigurowalnym okresie przechowywania, aby zapobiec rozrastaniu się bazy danych. Dodaj przycisk odpowiedzi, który automatycznie uzupełnia pole odbiorcy nazwą pierwotnego nadawcy, co ułatwia prowadzenie korespondencji. W przypadku powiadomień systemowych ostyluj je inaczej, dodając oficjalny nagłówek, aby odróżnić je od poczty pisanej przez graczy.
Powiadomienia systemowe i automatyczne maile
Poza komunikacją między graczami system poczty staje się potężnym narzędziem dla innych skryptów serwerowych do dostarczania immersyjnych powiadomień. Zamiast mdłych powiadomień typu toast, skrypty mogą wysyłać formalne wiadomości. System sądownictwa wysyła oficjalne wezwania na rządowym papierze firmowym. Firma przesyła faktury i przypomnienia o płatnościach. Skrypt zarządzania nieruchomościami wysyła powiadomienia o terminie czynszu i ostrzeżenia o eksmisji. System frakcji wysyła listy rekrutacyjne lub odprawy misji. Stwórz prosty server export, który dowolny resource może wywołać, aby zakolejkować wiadomość systemową bez konieczności rozumienia wewnętrznego działania systemu poczty. Export przyjmuje identyfikator odbiorcy, temat, treść, typ wiadomości i opcjonalne załączniki. Przekształca to twój system poczty w uniwersalną warstwę powiadomień, która sprawia wrażenie zintegrowanej ze światem gry, zamiast być nachalną nakładką UI niszczącą immersję.
-- 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'
-- )