Синхронизация погоды FiveM: Скрипты динамической погоды и времени
Синхронизируйте погоду и время на вашем сервере FiveM. Динамические циклы, гром, снег, сезонные паки и лучшие скрипты погоды для создания атмосферы.
Agency Scripts
Основатель и ведущий разработчик Agency Scripts
Проблема с рассинхронизацией погоды
По умолчанию GTA V обрабатывает погоду и время независимо на каждом клиенте. Это означает, что один игрок может ехать под ярким солнцем, в то время как другой игрок рядом с ним видит грозу. Для ролевых серверов это полностью разрушает погружение. Представьте, что пытаетесь скоординировать сцену, где персонаж жалуется на дождь, а у другого игрока на экране ясное небо. Синхронизированная система погоды и времени гарантирует, что каждый игрок на сервере испытывает одинаковую среду в один и тот же момент, что является основой для последовательного ролевого взаимодействия, реалистичных условий вождения и координации событий. В этом уроке мы создадим полную систему синхронизации погоды с настраиваемой скоростью времени, сезонными переходами, голосованием за погоду и событиями отключения света.
Управление состоянием погоды на стороне сервера
Сервер , единственный источник правды для погоды и времени. Он поддерживает текущий тип погоды, следующий для переходов и внутриигровые часы. Каждый клиент синхронизируется с этим состоянием при подключении и получает обновления при изменении погоды. GTA V поддерживает определённый набор типов погоды, включая CLEAR, EXTRASUNNY, CLOUDS, OVERCAST, RAIN, THUNDER, CLEARING, FOGGY, SMOG, SNOW, SNOWLIGHT, BLIZZARD , и XMAS. Определите логические правила перехода, чтобы погода менялась плавно, а не прыгала от метели к яркому солнцу без промежуточного состояния.
-- server.lua
local Config = {
timeSpeed = 2, -- how many in-game minutes per real second
syncInterval = 30000, -- full sync broadcast every 30s
autoChange = true, -- automatic weather cycling
changeCooldown = 600, -- seconds between automatic changes
}
local State = {
weather = 'CLEAR',
nextWeather = 'CLOUDS',
hour = 12,
minute = 0,
frozen = false,
blackout = false,
season = 'summer',
}
local weatherTransitions = {
EXTRASUNNY = {'CLEAR', 'CLOUDS'},
CLEAR = {'EXTRASUNNY', 'CLOUDS', 'OVERCAST'},
CLOUDS = {'CLEAR', 'OVERCAST', 'RAIN'},
OVERCAST = {'CLOUDS', 'RAIN', 'THUNDER', 'FOGGY'},
RAIN = {'OVERCAST', 'THUNDER', 'CLEARING'},
THUNDER = {'RAIN', 'OVERCAST'},
CLEARING = {'CLEAR', 'CLOUDS'},
FOGGY = {'CLEAR', 'CLOUDS', 'SMOG'},
SMOG = {'FOGGY', 'CLEAR'},
}
local seasonWeights = {
summer = {EXTRASUNNY = 30, CLEAR = 30, CLOUDS = 20, RAIN = 10, THUNDER = 5, FOGGY = 5},
winter = {CLOUDS = 20, OVERCAST = 20, SNOW = 25, SNOWLIGHT = 15, BLIZZARD = 10, FOGGY = 10},
spring = {CLEAR = 25, CLOUDS = 25, RAIN = 20, CLEARING = 15, FOGGY = 10, OVERCAST = 5},
autumn = {CLOUDS = 25, OVERCAST = 25, RAIN = 20, FOGGY = 15, CLEAR = 10, SMOG = 5},
}
Цикл прогрессии времени и синхронизации
Система времени работает как серверный цикл, который увеличивает игровой часы в соответствии с настроенной скоростью. A timeSpeed значение 2 означает, что каждая реальная секунда ускоряет игровой час на 2 минуты. Это создаёт цикл день-ночь, который занимает примерно 12 реальных минут на полные 24 игровых часа. Сервер регулярно транслирует текущее время и состояние погоды всем клиентам, а также отправляет мгновенные обновления, когда администратор меняет погоду или вызывает событие отключения электроэнергии. Новые игроки, присоединяющиеся в середине сессии, получают текущее состояние через специальное событие синхронизации, чтобы сразу соответствовать остальным.
-- Time progression loop (server.lua continued)
CreateThread(function()
while true do
Wait(1000)
if not State.frozen then
State.minute = State.minute + Config.timeSpeed
if State.minute >= 60 then
State.hour = State.hour + math.floor(State.minute / 60)
State.minute = State.minute % 60
end
if State.hour >= 24 then
State.hour = State.hour % 24
end
end
end
end)
-- Periodic full sync
CreateThread(function()
while true do
Wait(Config.syncInterval)
TriggerClientEvent('weather:fullSync', -1, State)
end
end)
-- Sync new players on join
AddEventHandler('playerJoining', function()
local src = source
Wait(2000)
TriggerClientEvent('weather:fullSync', src, State)
end)
Клиентское приложение погоды
На клиенте необходимо переопределить нативные системы погоды и времени GTA. Ключевые нативы: SetWeatherTypeNowPersist для мгновенных изменений погоды, SetWeatherTypeTransition для плавного смешивания между двумя погодными состояниями, и NetworkOverrideClockTime для установки внутриигровых часов. Вы должны вызвать NetworkOverrideClockTime каждый кадр, чтобы предотвратить возврат игры к собственному расчету времени. Для переходов погоды GTA поддерживает коэффициент смешивания от 0.0 до 1.0, который позволяет плавно интерполировать между двумя состояниями погоды в течение нескольких секунд, создавая реалистичные переходы, когда облака постепенно собираются перед началом дождя.
-- client.lua
local currentWeather = 'CLEAR'
local nextWeather = 'CLEAR'
local weatherBlend = 0.0
local hour, minute = 12, 0
local blackout = false
RegisterNetEvent('weather:fullSync', function(state)
currentWeather = state.weather
nextWeather = state.nextWeather or state.weather
hour = state.hour
minute = state.minute
blackout = state.blackout or false
applyWeather()
end)
function applyWeather()
ClearOverrideWeather()
ClearWeatherTypePersist()
SetWeatherTypeNowPersist(currentWeather)
if currentWeather ~= nextWeather then
SetWeatherTypeTransition(
GetHashKey(currentWeather),
GetHashKey(nextWeather),
weatherBlend
)
end
if blackout then
SetArtificialLightsState(true)
SetArtificialLightsStateAffectsVehicles(false)
else
SetArtificialLightsState(false)
end
end
-- Override clock every frame
CreateThread(function()
while true do
Wait(0)
NetworkOverrideClockTime(hour, minute, 0)
end
end)
Система голосования за погоду
Система голосования за погоду позволяет вашему сообществу демократично выбирать погодные условия. Игроки голосуют за предпочитаемый тип погоды, и после окончания голосования побеждает самый популярный вариант. Это хорошо работает для ивентов, где важна вовлечённость игроков, например, решая, будет ли встреча автомобилей при ясной погоде или драматической грозе. Ограничьте голосование одним разом за цикл, чтобы избежать спама, и отображайте текущие результаты через систему уведомлений или простое NUI-окно. Сервер собирает голоса, подсчитывает их по окончании периода и плавно меняет погоду, чтобы все игроки одновременно ощутили изменение.
-- Weather voting (server.lua)
local votes = {}
local voteCooldown = {}
local votingOpen = false
local voteOptions = {'CLEAR', 'RAIN', 'THUNDER', 'FOGGY', 'SNOW'}
RegisterCommand('voteweather', function(source, args)
if not votingOpen then
TriggerClientEvent('chat:addMessage', source, {args = {'Weather', 'No vote is currently active.'}})
return
end
if voteCooldown[source] then
TriggerClientEvent('chat:addMessage', source, {args = {'Weather', 'You already voted this round.'}})
return
end
local choice = string.upper(args[1] or '')
local valid = false
for _, opt in ipairs(voteOptions) do
if opt == choice then valid = true break end
end
if not valid then
TriggerClientEvent('chat:addMessage', source, {
args = {'Weather', 'Options: ' .. table.concat(voteOptions, ', ')}
})
return
end
votes[choice] = (votes[choice] or 0) + 1
voteCooldown[source] = true
TriggerClientEvent('chat:addMessage', -1, {
args = {'Weather', GetPlayerName(source) .. ' voted for ' .. choice}
})
end, false)
function startWeatherVote()
votes = {}
voteCooldown = {}
votingOpen = true
TriggerClientEvent('chat:addMessage', -1, {
args = {'Weather', 'Weather vote started! Use /voteweather [type]. Options: ' .. table.concat(voteOptions, ', ')}
})
SetTimeout(60000, function()
votingOpen = false
local winner, maxVotes = 'CLEAR', 0
for weather, count in pairs(votes) do
if count > maxVotes then winner = weather; maxVotes = count end
end
transitionWeather(winner)
TriggerClientEvent('chat:addMessage', -1, {
args = {'Weather', 'Vote ended! Weather changing to: ' .. winner}
})
end)
end
Сезонная система и погодные эффекты на игровой процесс
Сезонная система добавляет глубину, которую большинство серверов упускают. Отслеживая внутриигровой календарь или сопоставляя реальные месяцы с сезонами, можно корректировать вероятности погоды. Лето предпочитает ясную и солнечную погоду с редкими грозами, а зима смещает баланс в сторону снега, пасмурного неба и тумана. Сезоны также могут влиять на игровые механики помимо визуальных эффектов. Во время дождя можно уменьшать сцепление транспорта, активируя модификатор управления на всех активных машинах. Туман снижает дальность прорисовки AI-пешеходов, делая стелс-геймплей более эффективным. Снег слегка замедляет скорость передвижения игроков и требует зимней одежды, чтобы избежать медленной потери здоровья. Эти мелкие детали создают живой мир, который реагирует на окружающую среду.
События отключения электроэнергии и админские инструменты
События отключения электроэнергии , мощный инструмент для серверных событий и ролевых сценариев. SetArtificialLightsState native отключает все искусственные источники света в игровом мире, погружая город во тьму. В сочетании с пасмурной или туманной погодой это создаёт невероятно атмосферную среду, идеально подходящую для хоррор-событий, ограблений или ролевых игр на выживание. Админ-команды должны обеспечивать полный контроль над системой погоды: заморозку времени, установку конкретных часов, принудительный выбор типов погоды, вызов отключений электроэнергии и запуск голосований по погоде. Защитите эти команды с помощью ace-разрешений, чтобы только уполномоченный персонал мог изменять окружение. Хорошо продуманная система погоды становится инструментом повествования, который админы используют для создания настроения на каждом событии и сценарии вашего сервера.
-- Admin commands (server.lua)
RegisterCommand('setweather', function(source, args)
if source > 0 and not IsPlayerAceAllowed(source, 'command.setweather') then return end
local weather = string.upper(args[1] or 'CLEAR')
transitionWeather(weather)
TriggerClientEvent('chat:addMessage', -1, {
args = {'Admin', 'Weather changed to ' .. weather}
})
end, true)
RegisterCommand('settime', function(source, args)
if source > 0 and not IsPlayerAceAllowed(source, 'command.settime') then return end
State.hour = tonumber(args[1]) or 12
State.minute = tonumber(args[2]) or 0
TriggerClientEvent('weather:fullSync', -1, State)
end, true)
RegisterCommand('blackout', function(source, args)
if source > 0 and not IsPlayerAceAllowed(source, 'command.blackout') then return end
State.blackout = not State.blackout
TriggerClientEvent('weather:fullSync', -1, State)
TriggerClientEvent('chat:addMessage', -1, {
args = {'Admin', 'Blackout ' .. (State.blackout and 'enabled' or 'disabled')}
})
end, true)
RegisterCommand('freezetime', function(source, args)
if source > 0 and not IsPlayerAceAllowed(source, 'command.freezetime') then return end
State.frozen = not State.frozen
TriggerClientEvent('chat:addMessage', -1, {
args = {'Admin', 'Time ' .. (State.frozen and 'frozen' or 'unfrozen')}
})
end, true)