FiveM: интеграция с картами реального времени, погода и сезоны
Ставишь дефолтный FiveM-сервер — и через день кто-то из игроков пишет в тикет, что у него на экране солнечный полдень, а у соседа по гаражу в это же время идёт дождь ночью. Дело не в багнутом клиенте: без явной синхронизации каждый клиент считает время и погоду сам по себе, и на RP-сервере, где сцены завязаны на «сейчас ночь, все в масках» или «шторм, все прячутся», это ломает атмосферу. Разберём, как привязать время к реальным часам или задать ускоренный цикл, синхронизировать состояние между всеми подключёнными, подтянуть настоящую погоду через внешний API и честно сымитировать смену сезонов там, где GTA V нативно её не поддерживает.
Содержание
Реальное время или ускоренный цикл — что выбрать
Игровые часы GTA V по умолчанию у каждого клиента идут своим ходом и без вмешательства сервера рано или поздно расходятся. Прежде чем что-то синхронизировать, нужно решить, откуда сервер вообще берёт «эталонное» время.
Привязка к реальному времени — сервер читает системные часы (os.date) и транслирует клиентам время, максимально близкое к реальному, иногда со сдвигом часового пояса под аудиторию сервера. Подходит проектам, где важна предсказуемость: вечером по своим часам игрок видит вечер и в игре, и на это можно завязывать активности вроде ночных патрулей или комендантского часа по расписанию.
Ускоренный цикл — сервер сам крутит игровые сутки с заданной скоростью независимо от реальных часов, например «столько-то игровых минут за одну реальную секунду». Стандартный выбор для RP-серверов, где хочется, чтобы за одну сессию игрок застал и день, и ночь. Конкретное соотношение — настройка проекта, а не величина движка, и её стоит подбирать под длину типичной сессии на своём сервере, а не копировать чужой конфиг вслепую.
-- config/time.lua
Config = Config or {}
Config.Time = {
mode = 'accelerated', -- 'realtime' или 'accelerated'
gameMinutesPerRealSecond = 1.0, -- актуально только для 'accelerated'
freezeWeather = false,
startHour = 8,
syncIntervalMs = 60000, -- как часто рассылать актуальное время клиентам
}
Смешивать оба режима без крайней необходимости не стоит — переключение туда-обратно на живом сервере обычно даёт заметный скачок времени у части игроков, которые были в этот момент в интерьере или катсцене.
Синхронизация времени между всеми клиентами
Ключевая ошибка новичков — вызывать нативы времени локально на каждом клиенте, ожидая, что те как-то сами договорятся. Не договорятся: NETWORK_OVERRIDE_CLOCK_TIME меняет часы только у клиента, где вызвана, а между рассылками игра продолжает тикать свои дефолтные секунды, и клиенты снова расходятся.
Рабочая схема — сервер как единственный источник правды: хранит текущее игровое время, периодически рассылает его всем через TriggerClientEvent, а клиент держит его зафиксированным между рассылками через PAUSE_CLOCK.
-- server/time.lua
local hour, minute = Config.Time.startHour, 0
CreateThread(function()
while true do
Wait(Config.Time.syncIntervalMs)
if Config.Time.mode == 'accelerated' then
minute = minute + math.floor(Config.Time.syncIntervalMs / 1000 * Config.Time.gameMinutesPerRealSecond)
hour = hour + math.floor(minute / 60)
minute = minute % 60
hour = hour % 24
else
hour, minute = tonumber(os.date('%H')), tonumber(os.date('%M'))
end
TriggerClientEvent('rp2:syncTime', -1, hour, minute)
end
end)
-- отдаём текущее состояние сразу при коннекте, не дожидаясь ближайшего тика рассылки
AddEventHandler('playerConnecting', function()
local src = source
Wait(0)
TriggerClientEvent('rp2:syncTime', src, hour, minute)
end)
-- client/time.lua
RegisterNetEvent('rp2:syncTime', function(hour, minute)
PauseClock(true)
NetworkOverrideClockTime(hour, minute, 0)
end)
Отдельно стоит поймать playerConnecting (или playerJoining, в зависимости от того, на каком этапе фреймворк уже готов принимать события) — без явной досылки текущего состояния при коннекте игрок, зашедший между двумя плановыми рассылками, несколько секунд-минут видит дефолтное игровое время, что на RP-сервере с активной ночной сценой выглядит как баг. Если сервер только разворачивается с нуля и этого куска инфраструктуры ещё нет, порядок действий с самого начала — в статье про установку FiveM-сервера с нуля.
Поднять сервер FiveM (GTA V) за пару минут
Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.
Создать серверПогода из внешнего API: как подключить реальную погоду
Погода в FiveM синхронизируется по тому же принципу — сервер авторитетен, клиенты применяют. Разница в том, что источником данных для погоды может быть не игровая логика, а настоящий погодный сервис. Делается это через серверный натив PerformHttpRequest, который FXServer поддерживает из коробки — отдельных зависимостей в fxmanifest.lua, кроме server_script, не требуется.
-- server/weather.lua
local weatherMap = {
Clear = 'CLEAR',
Clouds = 'CLOUDS',
Rain = 'RAIN',
Thunderstorm = 'THUNDER',
Snow = 'SNOW',
Fog = 'FOGGY',
Mist = 'FOGGY',
}
local function fetchRealWeather(cb)
PerformHttpRequest('https://api.openweathermap.org/data/2.5/weather?q=' .. Config.Weather.city
.. '&appid=' .. Config.Weather.apiKey, function(status, body)
if status ~= 200 or not body then
cb('CLEAR') -- сервис недоступен — не роняем ресурс, откатываемся на безопасное значение
return
end
local data = json.decode(body)
local condition = data and data.weather and data.weather[1] and data.weather[1].main
cb(weatherMap[condition] or 'CLEAR')
end, 'GET')
end
Здесь важны три вещи, на которых спотыкаются чаще всего:
- Опрос по таймеру, а не на каждое событие. Погодные API почти всегда лимитируют бесплатный тариф по числу запросов — точные цифры стоит смотреть в актуальной документации выбранного провайдера, они периодически меняются. На практике опроса раз в 5-15 игровых минут достаточно, реальная погода не меняется поминутно.
- Исходящий доступ в интернет с сервера.
PerformHttpRequestвыполняется на стороне FXServer, а не клиента, значит серверу нужен исходящий HTTPS. Если хостинг закрывает исходящий трафик по умолчанию на уровне файрвола или security group, запрос просто зависнет в таймауте — это стоит проверить отдельно, прежде чем списывать проблему на кривой скрипт. - Фолбэк на случай сбоя API. Сервис может упасть или вернуть не тот формат JSON — код выше на этот случай откатывается на
CLEAR, а не роняет ресурс ошибкой.
Маппинг условий погоды на типы GTA (CLEAR, EXTRASUNNY, CLOUDS, OVERCAST, RAIN, THUNDER, CLEARING, NEUTRAL, SNOW, BLIZZARD, SNOWLIGHT, FOGGY, XMAS, HALLOWEEN) — вещь проекта, не движка: разные сервисы называют условия по-разному, и таблицу соответствий нужно сверять с реальным ответом своего провайдера, а не копировать чужую вслепую.
Плавные переходы погоды и защита от рывков
Прямая замена погоды через SET_WEATHER_TYPE_NOW_PERSIST работает мгновенно — было солнечно, стало «щёлк» и гроза. Для одиночного триггера (админ-команда, ивент) это нормально, но для регулярной синхронизации по таймеру резкие рывки погоды каждые несколько минут выглядят дёшево и сбивают с толку игроков в машине, которые не понимают, откуда взялся ливень посреди дороги.
Более щадящий вариант — плавный переход через SET_WEATHER_TYPE_OVERTIME_PERSIST, где вторым параметром задаётся время перехода в секундах:
-- client/weather.lua
local currentWeather = 'CLEAR'
RegisterNetEvent('rp2:syncWeather', function(newWeather)
if newWeather == currentWeather then return end -- не дёргаем погоду без реальной смены
SetWeatherTypeOvertimePersist(newWeather, 60.0)
currentWeather = newWeather
end)
Проверка if newWeather == currentWeather then return end на клиенте не декоративная — без неё каждая плановая рассылка от сервера (даже когда погода объективно не изменилась) заново триггерит переход, и на слабом железе это добавляет лишнюю нагрузку на рендер без всякой пользы. То же самое стоит сделать и на сервере: сравнивать новое значение с последним отправленным и не гонять TriggerClientEvent впустую.
Отдельно нужно закрыть тот же кейс позднего коннекта, что и с временем — новый игрок должен получить текущую погоду сразу при входе, а не ждать следующего планового тика синхронизации.
Влияние на видимость и атмосферу
Погода и время суток — не только картинка, это прямое влияние на геймплей, которое стоит настраивать осознанно, а не оставлять на волю движка.
- Дальность видимости в тумане и дожде. Сильный туман или ливень заметно режут видимость — усиливает атмосферу, но на серверах с активными перестрелками может превращаться в фрустрацию: игрок не видит противника не потому, что тот спрятался, а потому что погода. Если жалобы повторяются, стоит ограничить пул тяжёлых по видимости типов (
FOGGY,THUNDER) более редкой вероятностью, а не убирать совсем. - Освещение улиц в грозу. Натив
SET_ARTIFICIAL_LIGHTS_STATEпозволяет принудительно гасить или зажигать уличное освещение отдельно от погодного цикла — усиливает ощущение шторма или подсвечивает зоны спавна, если ночь выставлена слишком тёмной. - Баланс тёмных ночей. Полностью реалистичная беспросветная ночь эффектна на скриншотах, но мешает и игрокам, и админам модерировать происходящее. Многие RP-проекты сознательно занижают глубину ночной темноты модификатором таймцикла на клиенте, а не полагаются на дефолтную яркость движка — это компромисс, который стоит проговорить с командой заранее.
- Сцепление на мокрой дороге. Дождь и снег влияют на управляемость через хендлинг машин — для гонка-ориентированных фреймворков часть серверов ослабляет эффект правкой
handling.meta, для остальных реалистичное скольжение в дождь как раз часть атмосферы.
Если после включения погодных эффектов и синхронизации по расписанию сервер начинает подтормаживать именно при большом онлайне — не всегда виновата сама погода, часто накладывается общая нагрузка от количества стриминг-объектов и скриптов; общий разбор таких проблем — в статье про лаги при большом онлайне на FiveM.
Имитация смены сезонов без нативной поддержки
У GTA V и FiveM нет встроенной системы времён года — карта всегда одна и та же, независимо от календаря. «Сезоны» на RP-серверах — это всегда имитация, собранная из нескольких приёмов, а не отдельная фича движка.
Ограничение пула погоды по календарю. Самый дешёвый по ресурсам способ — просто сузить набор погоды, из которого сервер выбирает случайное значение (или к которому обращается при отсутствии реального API), в зависимости от текущего месяца:
-- config/seasons.lua
Config.SeasonWeatherPool = {
winter = {'SNOW', 'BLIZZARD', 'SNOWLIGHT', 'FOGGY', 'XMAS'},
summer = {'CLEAR', 'EXTRASUNNY', 'THUNDER'},
default = {'CLEAR', 'CLOUDS', 'OVERCAST', 'RAIN', 'CLEARING'},
}
local function getSeasonPool()
local month = tonumber(os.date('%m'))
if month == 12 or month == 1 or month == 2 then
return Config.SeasonWeatherPool.winter
elseif month >= 6 and month <= 8 then
return Config.SeasonWeatherPool.summer
end
return Config.SeasonWeatherPool.default
end
Эта логика применяется там, где сервер сам выбирает случайную погоду (если реальный API не подключён или временно недоступен) — реальную сгенерированную сезонность API и так учитывает сам, дублировать фильтр поверх него не нужно.
Визуальная подмена снегом и текстурами. Настоящий снежный покров на земле, деревьях и крышах — это уже не про натив погоды, а про карту: отдельные ресурсы подменяют пропсы и текстуры земли на зимние варианты, аналогично тому, как ставятся кастомные MLO-интерьеры. Общая логика структуры ресурса и стрим-файлов та же, что и для обычных карт — подробнее в статье про карты и MLO для FiveM-сервера. Нюанс: снежные текстурные паки почти всегда чисто визуальные, физика и коллизии остаются летними — машина «едет по снегу», но по факту скользит на асфальтовом трении, если отдельно не подключена правка хендлинга под сезон.
Конфликты с уже стоящими картами. Если на сервере уже стоят кастомные MLO или пропсы под конкретный вид локации, зимняя текстурная подмена может визуально конфликтовать с ними в тех же точках — это тот же класс проблем, что и конфликт двух MLO на одной локации, и диагностируется так же: временный stop одного из ресурсов и проверка, воспроизводится ли нестыковка без него.
Поднять сервер FiveM (GTA V) за пару минут
Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.
Создать серверЧастые вопросы
Как быстро протестировать смену дня и ночи, не дожидаясь реального цикла?
На тестовом инстансе временно выставить Config.Time.mode = 'accelerated' с высоким значением gameMinutesPerRealSecond — полный цикл суток пройдёт за пару минут. На проде вернуть нормальное значение отдельным коммитом.
После рестарта ресурса погода и время сбрасываются на дефолт — это нормально?
Нет, значит состояние хранится только в оперативной переменной и теряется при restart. Решение — сохранять последнее значение в KVP (SetResourceKvp) или во внешнем хранилище фреймворка и восстанавливать при старте, а не начинать каждый раз с дефолта.
Обязательно писать синхронизацию с нуля или можно взять готовый ресурс?
Готовые решения для тайм- и погодной синхронизации в комьюнити FiveM есть, включая версии под конкретные фреймворки. Свой скрипт нужен, если требуется нестандартная интеграция — например внешний API реальной погоды или сезонный пул, которые типовой ресурс может не поддерживать.
У части игроков погода "долетает" с задержкой при подключении — почему?
Чаще всего событие рассылается только по таймеру, а playerConnecting не досылает текущее состояние сразу. Игрок заходит между двумя плановыми тиками и какое-то время видит дефолт — фикс тот же, что и для времени: явная отправка состояния сразу на коннект.
Можно ли завязать погоду одновременно на API и на ручное управление админом?
Можно, но нужно разделить приоритеты: ручной триггер выставляет флаг manualOverride на фиксированное время, в течение которого плановый опрос API игнорируется, а по истечении срока синхронизация возвращается к реальным данным — иначе очередной тик молча перезапишет то, что выставил админ.