Sincronización del clima en FiveM: scripts de clima y hora dinámicos
Sincroniza el clima y la hora en tu servidor de FiveM. Ciclos dinámicos, tormentas, nieve, paquetes de temporada y los mejores scripts para crear ambiente.
Agency Scripts
Fundador y desarrollador principal en Agency Scripts
El problema del clima desincronizado
Por defecto, GTA V gestiona el clima y la hora por separado en cada cliente. Esto significa que un jugador puede conducir bajo un sol radiante mientras otro, justo a su lado, ve una tormenta eléctrica. En los servidores de roleplay, esto rompe por completo la inmersión. Imagina intentar coordinar una escena en la que un personaje se queja de la lluvia mientras en la pantalla de otro jugador hay un cielo despejado. Un sistema sincronizado de clima y hora garantiza que todos los jugadores del servidor experimenten el mismo entorno al mismo tiempo, algo fundamental para lograr un roleplay coherente, condiciones de conducción realistas y una buena coordinación de eventos. En este tutorial, crearemos un sistema completo de sincronización del clima con velocidad del tiempo configurable, cambios estacionales, votaciones sobre el clima y eventos de apagón.
Gestión del estado del clima del lado del servidor
El servidor es la única fuente de verdad para el clima y la hora. Mantiene el tipo de clima actual, el siguiente tipo de clima para las transiciones y el reloj del juego. Cada cliente se sincroniza con este estado al conectarse y recibe actualizaciones cada vez que cambia el clima. GTA V admite un conjunto específico de tipos de clima, entre ellos CLEAR, EXTRASUNNY, CLOUDS, OVERCAST, RAIN, THUNDER, CLEARING, FOGGY, SMOG, SNOW, SNOWLIGHT, BLIZZARD, y XMASDefine reglas lógicas de transición para que el clima evolucione de forma natural, en lugar de pasar de una ventisca a un sol radiante sin un estado intermedio.
-- 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},
}
Progresión de la hora y bucle de sincronización
El sistema de hora funciona como un bucle del lado del servidor que adelanta el reloj del juego según la velocidad configurada. Un timeSpeed de 2 significa que cada segundo en tiempo real adelanta el reloj del juego 2 minutos. Esto crea un ciclo de día y noche que dura aproximadamente 12 minutos en tiempo real por cada 24 horas dentro del juego. El servidor transmite la hora actual y el estado del clima a todos los clientes en intervalos regulares, y también envía actualizaciones inmediatas cuando un administrador cambia el clima o activa un apagón. Los nuevos jugadores que se conectan a mitad de la sesión reciben el estado actual mediante un evento de sincronización dedicado, para que coincidan de inmediato con los demás.
-- 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)
Aplicación del clima del lado del cliente
En el cliente, tienes que anular los sistemas nativos de clima y hora de GTA. Los natives clave son SetWeatherTypeNowPersist para cambiar el clima de inmediato, SetWeatherTypeTransition para mezclar suavemente dos estados del clima y NetworkOverrideClockTime para establecer el reloj del juego. Debes llamar a NetworkOverrideClockTime en cada frame para evitar que el juego vuelva a calcular la hora por su cuenta. Para las transiciones del clima, GTA admite un factor de mezcla entre 0.0 y 1.0 que permite interpolar suavemente entre dos estados del clima durante varios segundos, creando transiciones realistas en las que las nubes se van acercando antes de que empiece a llover.
-- 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 votación del clima
Un sistema de votación del clima permite que tu comunidad decida el clima de forma democrática. Los jugadores pueden votar por su tipo de clima preferido y, al terminar el periodo de votación, gana la opción más popular. Funciona bien para eventos en los que quieres fomentar la participación de los jugadores, como decidir si una reunión de coches se celebra con el cielo despejado o en medio de tormentas eléctricas intensas. Limita la votación a una vez por ciclo para evitar el spam y muestra el recuento de votos actual mediante tu sistema de notificaciones o una superposición NUI sencilla. El servidor recopila los votos, los cuenta cuando termina el periodo y aplica el clima ganador mediante una transición suave para que todos los jugadores experimenten el cambio a la vez.
-- 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 estacional y efectos del clima en la jugabilidad
Un sistema estacional añade una capa de profundidad que la mayoría de los servidores pasan por alto. Al hacer un seguimiento del calendario del juego o asignar estaciones a los meses del mundo real, puedes ajustar las probabilidades del clima según corresponda. En verano predominan los cielos despejados y soleados, con tormentas eléctricas ocasionales, mientras que en invierno aumenta la probabilidad de nieve, cielos nublados y niebla. Las estaciones también pueden afectar a las mecánicas de juego, más allá del aspecto visual. Cuando llueve, podrías reducir la tracción de los vehículos aplicando un modificador de manejo a todos los vehículos activos. La niebla podría reducir la distancia de renderizado de los peds de la IA, haciendo más viable la jugabilidad de sigilo. La nieve podría reducir ligeramente la velocidad de movimiento de los jugadores y exigir ropa de invierno para evitar una pérdida gradual de salud. Estos pequeños detalles crean un mundo inmersivo que parece vivo y reacciona a su entorno.
Apagones y controles de administrador
Los apagones son una herramienta eficaz para eventos del servidor y situaciones de roleplay. El native SetArtificialLightsState desactiva todas las fuentes de luz artificial del mundo del juego y sume la ciudad en la oscuridad. Combinado con un estado del clima nublado o con niebla, crea un ambiente increíble, perfecto para eventos de terror, situaciones de atracos o roleplay de supervivencia. Los comandos de administrador deben ofrecer control total sobre el sistema de clima: congelar la hora, establecer horas específicas, forzar tipos de clima, activar apagones e iniciar votaciones del clima. Protege estos comandos con permisos ace para que solo el personal autorizado pueda modificar el entorno. Un sistema de clima bien diseñado se convierte en una herramienta narrativa que los administradores usan para crear el ambiente de cada evento y situación en tu 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)