Synchronizacja pogody FiveM: Skrypty na dynamiczną pogodę i czas
Synchronizuj pogodę i czas na swoim serwerze FiveM. Dynamiczne cykle, burze, śnieg, pakiety pór roku i najlepsze skrypty pogodowe do budowania klimatu.
Agency Scripts
Założyciel i główny deweloper w Agency Scripts
Problem ze zdesynchronizowaną pogodą
Domyślnie GTA V obsługuje pogodę i czas niezależnie na każdym kliencie. Oznacza to, że jeden gracz może jechać w pełnym słońcu, podczas gdy inny stojący tuż obok widzi burzę. Na serwerach roleplay całkowicie niszczy to immersję. Wyobraź sobie koordynowanie sceny, w której jedna postać narzeka na deszcz, a na ekranie drugiego gracza świeci słońce. Zsynchronizowany system pogody i czasu gwarantuje, że każdy gracz na serwerze doświadcza tego samego otoczenia w tym samym momencie, co ma kluczowe znaczenie dla spójnego roleplayu, realistycznych warunków jazdy i koordynacji eventów. W tym poradniku zbudujemy kompletny system synchronizacji pogody z konfigurowalną prędkością czasu, przejściami między porami roku, głosowaniem na pogodę i blackoutami.
Zarządzanie stanem pogody po stronie serwera
Serwer jest jedynym źródłem prawdy dla pogody i czasu. Utrzymuje aktualny typ pogody, kolejny typ pogody dla przejść oraz zegar w grze. Każdy klient synchronizuje się z tym stanem po dołączeniu i otrzymuje aktualizacje za każdym razem, gdy pogoda ulegnie zmianie. GTA V obsługuje określony zestaw typów pogody, w tym CLEAR, EXTRASUNNY, CLOUDS, OVERCAST, RAIN, THUNDER, CLEARING, FOGGY, SMOG, SNOW, SNOWLIGHT, BLIZZARD, oraz XMAS. Zdefiniuj logiczne zasady przejść, aby pogoda zmieniała się naturalnie, zamiast przeskakiwać ze śnieżycy do pełnego słońca bez stanu pośredniego.
-- 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},
}
Postęp czasu i pętla synchronizacji
System czasu działa jako pętla po stronie serwera, która przesuwa zegar w grze w oparciu o skonfigurowaną prędkość. A timeSpeed o wartości 2 oznacza, że każda sekunda w świecie rzeczywistym przesuwa zegar w grze o 2 minuty. Tworzy to cykl dnia i nocy, w którym pełne 24 godziny w grze trwają około 12 minut w świecie rzeczywistym. Serwer rozsyła aktualny czas i stan pogody do wszystkich klientów w regularnych odstępach, a także wysyła natychmiastowe aktualizacje, gdy administrator zmieni pogodę lub wywoła blackout. Nowi gracze dołączający w trakcie sesji otrzymują aktualny stan poprzez dedykowany event synchronizacyjny, dzięki czemu natychmiast widzą to samo co pozostali.
-- 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)
Aplikacja pogodowa po stronie klienta
Po stronie klienta musisz nadpisać natywne systemy pogody i czasu w GTA. Kluczowe nativki to SetWeatherTypeNowPersist do natychmiastowych zmian pogody, SetWeatherTypeTransition do płynnego przejścia między dwoma stanami pogody oraz NetworkOverrideClockTime do ustawiania zegara w grze. Musisz wywołać NetworkOverrideClockTime w każdej klatce, aby zapobiec powrotowi gry do własnego przeliczania czasu. W przypadku przejść pogodowych GTA obsługuje blend factor od 0.0 do 1.0, co pozwala płynnie interpolować między dwoma stanami pogody przez kilka sekund, tworząc realistyczne przejścia, w których chmury stopniowo nadciągają przed opadami deszczu.
-- 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)
System głosowania na pogodę
System głosowania na pogodę pozwala twojej społeczności decydować o pogodzie w sposób demokratyczny. Gracze mogą oddać głos na preferowany typ pogody, a po upływie czasu głosowania wygrywa najpopularniejsza opcja. Sprawdza się to doskonale podczas wydarzeń, w których zależy ci na zaangażowaniu graczy, na przykład przy decydowaniu, czy zlot samochodowy odbędzie się przy bezchmurnym niebie, czy podczas gwałtownej burzy. Ogranicz głosowanie do jednego na cykl, aby zapobiec spamowi, i wyświetlaj aktualną liczbę głosów za pomocą systemu powiadomień lub prostej nakładki NUI. Serwer zbiera głosy, podlicza je po zakończeniu okresu głosowania i ustawia zwycięską pogodę poprzez płynne przejście, dzięki czemu wszyscy gracze doświadczają zmiany jednocześnie.
-- 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
System pór roku i wpływ pogody na rozgrywkę
System pór roku dodaje warstwę głębi, którą większość serwerów pomija. Śledząc kalendarz w grze lub mapując rzeczywiste miesiące na pory roku, możesz odpowiednio dobrać wagi prawdopodobieństwa pogody. Lato sprzyja bezchmurnej, słonecznej pogodzie z okazjonalnymi burzami, podczas gdy zima przesuwa balans w stronę śniegu, zachmurzonego nieba i mgły. Pory roku mogą również wpływać na mechaniki rozgrywki, a nie tylko na wygląd. Podczas deszczu możesz zmniejszyć przyczepność pojazdów, wyzwalając modyfikator handling we wszystkich aktywnych pojazdach. Mgła mogłaby zmniejszać dystans renderowania dla pedów AI, czyniąc rozgrywkę stealth bardziej opłacalną. Śnieg mógłby nieznacznie spowalniać prędkość poruszania się gracza i wymagać zimowych ubrań, aby uniknąć powolnej utraty zdrowia. Te drobne akcenty tworzą immersyjny świat, który wydaje się żywy i reaguje na swoje otoczenie.
Zdarzenia blackout i opcje administratorskie
Zdarzenia blackout to potężne narzędzie do eventów na serwerze i scenariuszy roleplay. Skrypt SetArtificialLightsState native wyłącza wszystkie sztuczne źródła światła w świecie gry, pogrążając miasto w ciemności. W połączeniu z zachmurzeniem lub mgłą tworzy to niesamowicie klimatyczne otoczenie, idealne do eventów horrorowych, scenariuszy napadów lub survival roleplay. Komendy administratorskie powinny zapewniać pełną kontrolę nad systemem pogody: zamrażanie czasu, ustawianie konkretnych godzin, wymuszanie typów pogody, wywoływanie blackoutów i uruchamianie głosowań na pogodę. Zabezpiecz te komendy uprawnieniami ace, aby tylko upoważniona administracja mogła modyfikować otoczenie. Dobrze zaprojektowany system pogody staje się narzędziem narracyjnym, którego administratorzy używają do budowania nastroju podczas każdego wydarzenia i scenariusza na twoim serwerze.
-- 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)