FiveM Mail System Script: Внутриигровые сообщения и счета
Настройте почтовую систему 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 отображает список входящих писем с именем отправителя, темой, иконкой типа и временной меткой. Непрочитанные письма выделены или имеют значок. Клик по письму открывает его в полноэкранном режиме, стилизованном под бумажное письмо с отправителем и датой вверху, текстом в центре и кнопкой закрытия. Для посылок отображается вложенная записка и список содержимого с кнопкой "Open Package", которая переносит предметы в инвентарь игрока. Письма помечаются как прочитанные при открытии и удаляются из почтового ящика после настраиваемого периода хранения, чтобы избежать разрастания базы данных. Добавьте кнопку ответа, которая предварительно заполняет поле получателя именем оригинального отправителя, облегчая переписку. Для системных уведомлений используйте другой стиль с официальным бланком, чтобы отличать их от писем игроков.
Системные уведомления и автоматическая почта
Помимо общения между игроками, почтовая система становится мощным инструментом для других серверных скриптов, позволяя отправлять погружающие уведомления. Вместо скучных всплывающих уведомлений скрипты могут отправлять официальную почту. Судебная система отправляет официальные повестки на бланках правительства. Бизнес отправляет счета и напоминания о платежах. Скрипт управления недвижимостью отправляет уведомления о сроках аренды и предупреждения о выселении. Фракционная система отправляет письма о наборе или брифинги миссий. Создайте простой серверный экспорт, который любой ресурс сможет вызвать для постановки системного письма в очередь без необходимости разбираться в внутренностях почтовой системы. Экспорт принимает идентификатор получателя, тему, тело письма, тип почты и необязательные вложения. Это превращает вашу почтовую систему в универсальный слой уведомлений, который ощущается интегрированным в игровой мир, а не навязчивым UI-слоем, разрушающим погружение.
-- 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'
-- )