Tutorial 2026-03-11

Sincronização de Clima e Sistema Dinâmico de Tempo em FiveM

TDYSKY

TDYSKY

Fundador & Lead Developer na Agency Scripts

O problema com o clima dessincronizado

Por padrão, GTA V lida com o clima e o horário de forma independente em cada cliente. Isso significa que um jogador pode estar dirigindo sob o sol forte enquanto outro jogador ao lado dele vê uma tempestade. Para servidores de roleplay, isso quebra completamente a imersão. Imagine tentar coordenar uma cena em que um personagem reclama da chuva enquanto a tela de outro jogador mostra o céu limpo. Um sistema sincronizado de clima e tempo garante que cada jogador no servidor experimente o mesmo ambiente no mesmo momento, o que é fundamental para uma interpretação consistente, condições de direção realistas e coordenação de eventos. Neste tutorial, construiremos um sistema completo de sincronização climática com velocidade de tempo configurável, transições sazonais, votação climática e eventos de blecaute.

Gerenciamento do estado meteorológico do lado do servidor

O servidor é a única fonte de verdade sobre o clima e o tempo. Ele mantém o tipo de clima atual, o próximo tipo de clima para transições e o relógio do jogo. Cada cliente sincroniza com este estado quando ingressa e recebe atualizações sempre que o tempo muda. GTA V suporta um conjunto específico de tipos de clima, incluindo CLEAR, EXTRASUNNY, CLOUDS, OVERCAST, RAIN, THUNDER, CLEARING, FOGGY, SMOG, SNOW, SNOWLIGHT, BLIZZARD, e XMAS. Defina regras de transição lógicas para que o clima flua naturalmente, em vez de saltar de uma nevasca para um sol extra sem um estado intermediário.

-- 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},
}

Progressão de tempo e loop de sincronização

O sistema de tempo funciona como um loop do lado do servidor que incrementa o relógio do jogo com base na velocidade configurada. Um timeSpeed de 2 significa que cada segundo do mundo real avança o relógio do jogo em 2 minutos. Isso cria um ciclo dia-noite que leva aproximadamente 12 minutos no mundo real durante 24 horas completas de jogo. O servidor transmite a hora atual e o estado do tempo para todos os clientes em intervalos regulares e também envia atualizações imediatas quando um administrador altera o clima ou aciona um evento de blecaute. Novos jogadores que ingressam no meio da sessão recebem o estado atual por meio de um evento de sincronização dedicado para que correspondam imediatamente a todos os outros.

-- 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)

Aplicativo meteorológico do lado do cliente

No cliente, você precisa substituir os sistemas nativos de clima e horário do GTA. Os principais nativos são SetWeatherTypeNowPersist para mudanças climáticas instantâneas, SetWeatherTypeTransition para uma combinação suave entre dois estados climáticos e NetworkOverrideClockTime para acertar o relógio do jogo. Você deve chamar NetworkOverrideClockTime a cada quadro para evitar que o jogo volte ao seu próprio cálculo de tempo. Para transições climáticas, o GTA suporta um fator de mistura entre 0,0 e 1,0 que permite interpolar suavemente entre dois estados climáticos durante vários segundos, criando transições realistas onde as nuvens gradualmente aparecem antes do início da chuva.

-- 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)

Sistema de votação meteorológica

Um sistema de votação meteorológica permite que sua comunidade decida o clima democraticamente. Os jogadores podem votar em seu tipo de clima preferido e, após um período de votação, a opção mais popular vence. Isso funciona bem para eventos em que você deseja o envolvimento do jogador, como decidir se um encontro de carros acontece sob céu limpo ou tempestades dramáticas. Limite a votação uma vez por ciclo para evitar spam e exiba as contagens atuais de votos por meio de seu sistema de notificação ou de uma simples sobreposição de NUI. O servidor coleta votos, contabiliza-os quando o período expira e aplica o clima vencedor por meio de uma transição suave para que todos os jogadores experimentem a mudança simultaneamente.

-- 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

Sistema sazonal e efeitos climáticos na jogabilidade

Um sistema sazonal adiciona uma camada de profundidade que a maioria dos servidores ignora. Acompanhando o calendário do jogo ou mapeando meses do mundo real em estações, você pode avaliar as probabilidades climáticas de acordo. O verão favorece um clima claro e ensolarado com tempestades ocasionais, enquanto o inverno muda o equilíbrio para neve, céu nublado e neblina. As temporadas também podem afetar a mecânica de jogo além do visual. Durante a chuva, você pode reduzir a tração do veículo acionando um modificador de dirigibilidade em todos os veículos ativos. O nevoeiro pode reduzir a distância de renderização dos peds de IA, tornando a jogabilidade furtiva mais viável. A neve pode diminuir ligeiramente a velocidade de movimento do jogador e exigir roupas de inverno para evitar um lento esgotamento da saúde. Esses pequenos toques criam um mundo envolvente que parece vivo e responsivo ao seu ambiente.

Eventos de blackout e controles administrativos

Os eventos de blackout são uma ferramenta poderosa para eventos de servidor e cenários de roleplay. O nativo SetArtificialLightsState desativa todas as fontes de luz artificial no mundo do jogo, mergulhando a cidade na escuridão. Combinado com um estado de tempo nublado ou nebuloso, isso cria um ambiente incrivelmente atmosférico perfeito para eventos de terror, cenários de assalto ou roleplay de sobrevivência. Os comandos administrativos devem fornecer controle total sobre o sistema meteorológico: congelar o tempo, definir horários específicos, forçar tipos de clima, desencadear blecautes e iniciar votações meteorológicas. Proteja esses comandos com permissões Ace para que apenas funcionários autorizados possam modificar o ambiente. Um sistema meteorológico bem projetado se torna uma ferramenta de contar histórias que os administradores usam para definir o clima para cada evento e cenário em seu servidor.

-- 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)

Partilhar este artigo

Pronto para melhorar o teu servidor?

Explora os nossos scripts FiveM premium na loja Agency Scripts ou junta-te à nossa comunidade no Discord para suporte e atualizações.