Le problème de la météo désynchronisée
Par défaut, GTA V gère la météo et l'heure indépendamment sur chaque client. Cela signifie qu'un joueur peut conduire sous un soleil radieux tandis qu'un autre joueur se tenant juste à côté de lui voit un orage. Pour les serveurs de jeu de rôle, cela rompt complètement l'immersion. Imaginez que tu essayies de coordonner une scène dans laquelle un personnage se plaint de la pluie tandis que l'écran d'un autre joueur affiche un ciel dégagé. Un système météo et horaire synchronisé garantit que chaque joueur sur le serveur vit le même environnement au même moment, ce qui est fondamental pour un jeu de rôle cohérent, des conditions de conduite réalistes et la coordination des événements. Dans ce didacticiel, nous allons créer un système complet de synchronisation météo avec une vitesse de temps configurable, des transitions saisonnières, un vote météo et des événements de panne d'électricité.
Gestion de l'état météorologique côté serveur
Le serveur est la seule source d’information sur la météo et l’heure. Il conserve le type de météo actuel, le type de météo suivant pour les transitions et l'horloge du jeu. Chaque client se synchronise sur cet état lors de son inscription et reçoit des mises à jour chaque fois que la météo change. GTA V prend en charge un ensemble spécifique de types de temps, notamment CLEAR, EXTRASUNNY, CLOUDS, OVERCAST, RAIN, THUNDER, CLEARING, FOGGY, SMOG, SNOW, SNOWLIGHT, BLIZZARD, et XMAS. Définissez des règles de transition logiques pour que le temps s'écoule naturellement plutôt que de passer du blizzard à un temps très ensoleillé sans état intermédiaire.
-- 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},
}
Boucle de progression temporelle et de synchronisation
Le système de temps fonctionne comme une boucle côté serveur qui incrémente l'horloge du jeu en fonction de la vitesse configurée. UN timeSpeed de 2 signifie que chaque seconde du monde réel avance l'horloge du jeu de 2 minutes. Cela crée un cycle jour-nuit qui prend environ 12 minutes réelles pour 24 heures complètes de jeu. Le serveur diffuse l'heure actuelle et l'état météorologique à tous les clients à intervalles réguliers et envoie également des mises à jour immédiates lorsqu'un administrateur modifie la météo ou déclenche un événement de panne d'électricité. Les nouveaux joueurs rejoignant la mi-session reçoivent l'état actuel via un événement de synchronisation dédié afin qu'ils correspondent immédiatement à tous les autres.
-- 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)
Application météo côté client
Sur le client, tu dois remplacer les systèmes météorologiques et horaires natifs de GTA. Les principaux autochtones sont SetWeatherTypeNowPersist pour des changements de temps instantanés, SetWeatherTypeTransition pour un mélange en douceur entre deux états météorologiques, et NetworkOverrideClockTime pour régler l'horloge du jeu. Tu dois appeler NetworkOverrideClockTime chaque image pour empêcher le jeu de revenir à son propre calcul de temps. Pour les transitions météorologiques, GTA prend en charge un facteur de fusion compris entre 0,0 et 1,0 qui tu permet d'interpoler en douceur entre deux états météorologiques sur plusieurs secondes, créant ainsi des transitions réalistes où les nuages arrivent progressivement avant que la pluie ne commence.
-- 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)
Système de vote météo
Un système de vote météo permet à ta communauté de décider démocratiquement de la météo. Les joueurs peuvent voter pour leur type de météo préféré et, après une période de vote, l'option la plus populaire l'emporte. Cela fonctionne bien pour les événements où tu souhaites l'engagement des joueurs, comme décider si une compétition automobile se déroule sous un ciel clair ou sous des orages dramatiques. Limitez le vote à une fois par cycle pour éviter le spam et affichez le décompte actuel des votes via ton système de notification ou une simple superposition NUI. Le serveur collecte les votes, les compte à l'expiration de la période et applique la météo gagnante via une transition en douceur afin que tous les joueurs expérimentent le changement simultanément.
-- 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
Système saisonnier et effets météorologiques sur le gameplay
Un système saisonnier ajoute une couche de profondeur que la plupart des serveurs négligent. En suivant le calendrier du jeu ou en mappant les mois du monde réel aux saisons, tu peux pondérer les probabilités météorologiques en conséquence. L'été favorise un temps clair et ensoleillé avec des orages occasionnels, tandis que l'hiver fait pencher la balance vers la neige, le ciel couvert et le brouillard. Les saisons peuvent également affecter les mécanismes de jeu au-delà des seuls visuels. Par temps de pluie, tu peux réduire la traction du véhicule en déclenchant un modificateur de tenue de route sur tous les véhicules actifs. Le brouillard pourrait réduire la distance de rendu pour les robots IA, rendant le jeu furtif plus viable. La neige pourrait ralentir légèrement la vitesse de déplacement des joueurs et nécessiter des vêtements d'hiver pour éviter une lente perte de santé. Ces petites touches créent un monde immersif qui semble vivant et réactif à son environnement.
Événements de panne et contrôles d'administration
Les événements Blackout sont un outil puissant pour les événements de serveur et les scénarios de jeu de rôle. Le SetArtificialLightsState native désactive toutes les sources de lumière artificielle du monde du jeu, plongeant la ville dans l’obscurité. Combiné avec un temps couvert ou brumeux, cela crée un environnement incroyablement atmosphérique, parfait pour les événements d'horreur, les scénarios de braquage ou les jeux de rôle de survie. Les commandes d'administration doivent fournir un contrôle total sur le système météo : temps de gel, définition d'heures spécifiques, forçage des types de météo, déclenchement de pannes d'électricité et démarrage des votes météorologiques. Protégez ces commandes avec des autorisations as afin que seul le personnel autorisé puisse modifier l'environnement. Un système météo bien conçu devient un outil de narration que les administrateurs utilisent pour créer l'ambiance de chaque événement et scénario sur ton serveur.
-- 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)
