Browse docs
Weather integrations
Use this tutorial when setting up local activity or interior lighting through Sky Base. Complete the common setup, then follow the section for your weather resource. The QB and CD sections contain the required source changes; no patch files are installed with Sky Base.
Select the provider
In the existing Config = { ... } table in sky_base/config/config.lua, set:
weather = "auto",
Start your chosen weather resource before sky_base, followed by sky_jobs_base and
sky_activities. For example, after your framework and the provider setup below:
ensure qb-weathersync
ensure sky_base
ensure sky_jobs_base
ensure sky_activities
Config.weather = "auto" selects the first started resource in this order:
cd_easytime, Renewed-Weathersync, esx_weather, qb-weathersync, weathersync.
Without a match it uses native. Codesign and Renewed precede their QB compatibility
name. Run one actual synchronization provider. Explicit selections use the resource
name, native, or custom. Like fuel and vehiclekeys, auto-detection runs once in
source/shared/set_config.lua. Each adapter uses if Sky.Config.weather == "..." then
and defines its Sky.Functions integration functions only when selected. Start the provider before Base;
restart Base to select a different integration. An explicitly unavailable provider fails visibly.
Use the local override
This client API never changes global server weather/time:
local success, reason = Sky.Functions.SetWeatherOverride("my-interior", "CLEAR", 12)
Sky.Functions.SetWeatherOverride("my-interior", "CLEAR", 0)
Sky.Functions.ClearWeatherOverride("my-interior")
local current = Sky.Functions.GetWeatherOverride()
SetWeatherOverride(scope, weather, hour) freezes an integer hour 0–23, with minutes/seconds
zero. Weather names are the standard GTA types listed in source/client/weather.lua.
It returns true or false, reason. Updating the same scope preserves its original
restoration snapshot. ClearWeatherOverride(scope) has the same result shape and is idempotent
when no scope exists. GetWeatherOverride() returns false or
{scope, owner, weather, hour, provider, active}. One resource/scope owns the environment;
another receives override_in_use and cannot clear it. Cfx invocation context identifies
the owner. Base releases automatically when that owner or Base stops.
Weather now uses Sky.Functions, like fuel. Replace former Sky.Weather calls with
the three functions above. Custom provider tables are replaced by the direct integration
functions described below. Update Base and its consumers together.
The selected provider's restart rebinds outstanding requests, including unavailable ones. A stopping provider is not called. Renewed's initial clock application runs after its state handlers, and is cancelled on release. Stable targets do not repeat native writes.
Prepare your weather resource
The following changes address writers that continue after a pause request. They were checked against the linked upstream revisions. Compare your installed files first and preserve your own customizations. Only modify the provider you use.
Each block is a unified diff: remove lines starting with -, add lines starting with
+, and keep the surrounding context. Do not paste the diff markers or @@ headings into
Lua. The sky_weather_pause '1' manifest entry belongs to the weather resource, not
Sky Base. Add it only after applying all corresponding client changes: Base checks this
entry before using the provider.
QB WeatherSync
- Open
qb-weathersync/client.luaand apply the two loop checks below. Rechecking the disabled state after each wait prevents an already running transition from writing into the local override. - Add the shown metadata to
qb-weathersync/fxmanifest.lua. - Restart
qb-weathersync, then Sky Base and its consumers in the configured start order.
Source: qbcore-fivem/qb-weathersync at f7ae442137ff0cd2e62fee75e2f963465c5429be.
--- a/client.lua
+++ b/client.lua
@@ -45,6 +45,7 @@
Wait(15000)
end
Wait(100) -- Wait 0 seconds to prevent crashing.
+ if disable then goto continue end
SetArtificialLightsState(blackout)
SetArtificialLightsStateAffectsVehicles(blackoutVehicle)
ClearOverrideWeather()
@@ -69,6 +70,7 @@
else
Wait(1000)
end
+ ::continue::
end
end)
@@ -82,6 +84,7 @@
while true do
if not disable then
Wait(0)
+ if disable then goto continue end
local _, _, _, hours, minutes, _ = GetLocalTime()
local newBaseTime = baseTime
if tick - (Config.RealTimeSync and 500 or 22) > tick then
@@ -107,5 +110,6 @@
else
Wait(1000)
end
+ ::continue::
end
end)
--- a/fxmanifest.lua
+++ b/fxmanifest.lua
@@ -14,3 +14,6 @@
server_script 'server.lua'
client_script 'client.lua'
+
+-- Local pause fixes required by Sky.Functions weather overrides (see the weather integration tutorial).
+sky_weather_pause '1'
CD EasyTime
- Open
cd_easytime/client/client.luaand apply all changes below. They update the local target during a pause, cancel old pause/release coroutines and suspend clock/rain writers, including a time transition already in progress. - Add the shown metadata to
cd_easytime/fxmanifest.lua. - Restart
cd_easytime, then Sky Base and its consumers in the configured start order.
The optional third argument of cd_easytime:PauseSync(boolean, hours, weather) is added
by these changes. Existing callers retain their default hour/weather. Release requests
the full state through cd_easytime:SyncMe so an already frozen server clock resumes its
freeze loop. Base rejects an already paused CD provider because the existing getter does
not expose its previous pause target.
Source: dsheedes/cd_easytime at 174ddc1e823d1aad54361988ea54c302c08b980a. Codesign documents the pause-state getter in its exports reference.
--- a/client/client.lua
+++ b/client/client.lua
@@ -6,6 +6,7 @@
local my_source = GetPlayerServerId(PlayerId())
PauseSync = {}
PauseSync.state = false
+local pause_generation = 0
RegisterNetEvent('cd_easytime:OpenUI', function(values)
@@ -17,22 +18,29 @@
SendNUIMessage({action = 'open', values = values})
end)
-RegisterNetEvent('cd_easytime:PauseSync', function(boolean, hours)
- if boolean == PauseSync.state then return end
+RegisterNetEvent('cd_easytime:PauseSync', function(boolean, hours, weather)
if boolean then
+ PauseSync.hours = hours or 20
+ ChangeWeather(weather or 'EXTRASUNNY', true)
+ if PauseSync.state then return end
PauseSync.state = true
- ChangeWeather('EXTRASUNNY', true)
- while PauseSync.state do
+ pause_generation = pause_generation + 1
+ local generation = pause_generation
+ while PauseSync.state and generation == pause_generation do
+ NetworkOverrideClockTime(PauseSync.hours, 0, 0)
Wait(0)
- NetworkOverrideClockTime(hours or 20, 00, 00)
- end
- else
+ end
+ else
+ if not PauseSync.state then return end
PauseSync.state = false
+ pause_generation = pause_generation + 1
+ local generation = pause_generation
Wait(300)
- TriggerServerEvent('cd_easytime:SyncMe_basics', {weather = true, time = true})
- end
-end)
-
+ if not PauseSync.state and generation == pause_generation then
+ TriggerServerEvent('cd_easytime:SyncMe')
+ end
+ end
+end)
RegisterNetEvent('cd_easytime:ForceUpdate', function(data, source)
if not PauseSync.state then
if data.weather ~= nil and data.weather ~= self.weather then
@@ -64,6 +72,7 @@
CreateThread(function()
while self.freeze and not PauseSync.state do
Wait(0)
+ if PauseSync.state then break end
NetworkOverrideClockTime(data.hours, data.mins, 0)
end
end)
@@ -113,7 +122,7 @@
CreateThread(function()
while true do
Wait(5) --increase this timer to reduce resource usage. but this may cause jumping clouds.
- if not time_transition and not self.freeze and not forcedInstantTimeUpdate then
+ if not PauseSync.state and not time_transition and not self.freeze and not forcedInstantTimeUpdate then
NetworkOverrideClockTime(self.hours, self.mins, 0)
end
end
@@ -146,6 +155,7 @@
local start_time = GetGameTimer()
while GetGameTimer() - start_time < (transition_speed*1000) do
+ if PauseSync.state then return end
local progress = (GetGameTimer() - start_time) / (transition_speed*1000)
local total_seconds = (current_total_seconds + ((target_total_seconds - current_total_seconds) * progress)) % 86400
@@ -157,6 +167,7 @@
if forcedInstantTimeUpdate then break end
Wait(0)
end
+ if PauseSync.state then return end
if forcedInstantTimeUpdate then NetworkOverrideClockTime(self.hours, self.mins, 0) return end
NetworkOverrideClockTime(data.hours, data.mins, data.seconds or 0)
end
@@ -263,7 +274,7 @@
CreateThread(function()
while true do
Wait(2000)
- if self.weather then
+ if self.weather and not PauseSync.state then
if RainWeathers[self.weather] then
SetRainLevel(1.0)
else
--- a/fxmanifest.lua
+++ b/fxmanifest.lua
@@ -61,4 +61,6 @@
provide 'vSync'
provide 'qb-weathersync'
-provide 'qbx_weathersync'
\ No newline at end of file
+provide 'qbx_weathersync'
+-- Local pause fixes required by Sky.Functions weather overrides (see the weather integration tutorial).
+sky_weather_pause '1'
Check the setup
Enter the Activities center, start and leave a night activity, return to the center, and leave it. Check noon in the center, midnight during the night activity, clear local weather and normal server synchronization after leaving. Repeat during a weather transition and with server time already frozen. Another player outside Activities should retain ordinary server weather/time.
If the console asks you to complete this tutorial, verify both the client edits and the metadata in the selected provider. Changing the metadata alone does not fix its writers. Review these changes again when updating that provider.
Existing adapters and restoration
- ESX pauses via
ToggleWeatherSync/ToggleTimeSync. A removable native weather layer preserves the persistent baseline. ESX does not reapply unchanged-zone time on resume, so Base restores the captured clock plus elapsed game time. Administrator changes during the override are not represented in that snapshot. - Kibook uses
setMyWeather/setMyTime; release enables sync and requestsweathersync:initfor current state. - Renewed preserves
syncWeather/playerWeather, changes them without replication, and restores them plus the captured clock rate on release. - Native uses removable weather/clock overrides and elapsed-clock restoration. There is no authoritative server clock when no provider is present.
ESX, Kibook and QB lack prior-pause getters: these adapters assume normal sync on entry and enable it on release. Unrelated scripts must not independently own the same clock or weather. Native clock pause has no verified getter; Base unpauses on release.
Customer adapters belong in sky_base/config/integration/weather/custom.lua and define
these client-side functions directly, like the fuel adapters:
Sky.Functions.AcquireWeatherOverride(weather, hour)captures the clock withSky.Functions.CaptureWeatherState(), pauses local provider sync and returns that state. Store the provider's restoration token instate.token. Returnfalse, reasonbefore changing provider state when acquisition fails. Setstate.targets = trueif the provider handles both weather and time; setstate.deferred = trueif its initial apply must wait for the provider's state handlers.Sky.Functions.ApplyWeatherOverride(weather, hour, state)updates the target while retaining the same restoration state. CallSky.Functions.ApplyNativeWeatherOverride(weather, hour)for removable native weather/time overrides, or apply both targets through the provider and callPauseClock(true).Sky.Functions.ReleaseWeatherOverride(state)callsSky.Functions.RestoreWeatherState(state)before restoring provider sync fromstate.token. The shared helper clears native overrides and restores the elapsed clock whenstate.targetsis absent, and always unpauses the clock. Base usesRestoreWeatherState(state, true)for a stopped provider without calling its unavailable exports.
Acquire/apply must not yield. The manifest loads native.lua first so its shared helpers
are available to every adapter. Native-only integrations reuse those functions directly;
the custom template fails explicitly until implemented. Consumer resources continue to use
SetWeatherOverride, ClearWeatherOverride and GetWeatherOverride, which own validation,
scope ownership and resource cleanup.
Activities migration and editor ownership
Move Activities' old Config.Environment.provider value to Base's Config.weather.
Move customer ActivityEnvironmentProviders.custom callbacks to the direct
Sky.Functions.AcquireWeatherOverride, ApplyWeatherOverride and ReleaseWeatherOverride
functions in sky_base/config/integration/weather/custom.lua. Acquisition now returns the
captured clock state with the former provider token in state.token; apply receives
(weather, hour, state) and release receives that same state.
Activities no longer loads config/environment.lua. Update Base and Activities together.
Environment.enabled remains in /activitiesconfig with unchanged persistence/defaults.
Job Configurator impact: provider selection/callbacks are plaintext-only bootstrap
integration settings. Base has no /jobconfig registration; Activities keeps its existing
feature switch in /activitiesconfig. No persisted setting key or SQL migration changes.
Source and test boundary
Cfx revision e34d12cd9a39cc223548a5be1ab09f60e9183051 inspected:
ext/natives/natives_stash/gta_universal.lua, ext/natives/codegen_out_lua.lua,
code/components/citizen-scripting-lua/src/LuaScriptNatives.cpp
(LuaScriptNativeContext::PushArgument, Lua_DoInvokeNative),
code/components/scripting-gta/src/ScriptEngine.cpp (CallNativeHandlerRage),
code/components/extra-natives-five/src/TimeExtraNatives.cpp (SetMillisecondsPerGameMinute),
code/components/citizen-scripting-core/src/ResourceScriptFunctions.cpp, and
code/components/citizen-scripting-core/src/MetadataScriptFunctions.cpp (metadata lookup),
code/components/citizen-scripting-core/src/ResourceScriptingComponent.cpp
(CreateEnvironments loads shared scripts before the ordered client script list),
data/shared/citizen/scripting/lua/scheduler.lua (event/export dispatch and state bags).
Official client weather/clock signatures and shared resource lookup/metadata/invocation
signatures were checked. Lua booleans marshal as 0/1, clock arguments as integers,
rain as a float; these calls have no pointer outputs. Native overrides are client-local,
with no server RPC. Renewed uses explicit state:set(..., false).
For the Sky.Functions migration, ResourceScriptFunctions.cpp
(GET_INVOKING_RESOURCE, GET_CURRENT_RESOURCE_NAME) and scheduler.lua
(funcref_mt packing/invocation) were rechecked at that revision. The functions remain
in Base's runtime and retain the caller's resource context after the namespace change.
The existing environment contracts pass for all six providers using the manifest's
actual adapter load order. The local ESX server is reachable, but no client is connected
for a live weather/rendering check.
Observed server: FXServer 36897. The cached Cfx revision is not proven to match that artifact or the connected client. GTA weather/clock internals are proprietary; source and mocked contracts do not prove engine rendering. Activities contracts exercise all six adapters and lifecycle/ownership. Actual upstream coroutine reproductions fail before and pass after the provider changes shown above. No performance claim or profiler measurement is made.