API Reference
Developer reference for oxide-weather — the o-link weather namespace, server and client exports, write permissions, return shapes, events, state bags and integration examples.
This page is for developers writing code against oxide-weather. Server owners don't need anything on this page.
Two Ways In
Through o-link (recommended)
local olink = exports['o-link']:olink()
local weather = olink.weather.GetWeather()Write against the weather namespace and your script keeps working if the server owner ever swaps weather resources. o-link ships adapters for qb-weathersync, cd_easytime, Renewed-Weathersync, night_natural_disasters, and a native fallback, so the read functions those providers can answer will still answer.
oxide-weather is the only provider that implements the full namespace. Gate anything beyond the basics:
if olink.weather.GetResourceName() == 'oxide-weather' then
local conditions = olink.weather.GetConditionsAt(coords)
endMinimum o-link version: 1.8.0. The server-side weather namespace was added in 1.7.0, but this resource also loads the shared map files that o-link first ships in 1.8.0, so it does not start on anything older.
Direct exports
local weather = exports['oxide-weather']:GetWeather()Faster to write, but ties your resource to oxide-weather specifically.
Permissions for Writes
Every read export is open to any resource. Every write export is gated twice:
- Resource allowlist. The calling resource must be listed in
Config.SetterResourceson the server. Calls from anywhere else returnfalse, 'Calling resource is not allowed to change weather'and are logged. See Configuration → Trusted resources. - Admin check, when a player is involved. Every write export takes an optional trailing
actorparameter, the player's server id. When you pass one, that player must passolink.framework.IsAdmin. Passnilfor a system-driven change with no player behind it.
The calling resource's identity is taken from the engine (GetInvokingResource). It cannot be supplied or spoofed by the caller, whether you call directly or through o-link.
-- server, from a resource listed in Config.SetterResources
local ok, reason = exports['oxide-weather']:SetWeather('THUNDER', nil)
if not ok then print(reason) endWrites are also refused, with a reason, while a holiday preset is active or while a global scene lock is held. While the real-world weather mirror is driving the weather, SetWeather, SetDynamicWeather, SetWeatherInterval, SetForecast, their zone variants and SpawnFront are refused too, and fronts are paused.
Common Return Shapes
Conditions
Returned by GetConditionsAt on both sides.
{
weather = 'RAIN', -- the dominant weather at that point
weights = { RAIN = 0.7, CLOUDS = 0.3 }, -- blend of every contributing weather, sums to 1
zone = 'city', -- region id, or 'global'
rain = 0.7, -- 0-1, share of RAIN + THUNDER
snow = 0.0, -- 0-1, share of snow weathers
raining = true, -- rain >= 0.1
wind = { speed = 3.2, direction = 218.4 }, -- m/s, degrees
temperature = 14.2, -- Celsius. nil when seasons are disabled
snowLevel = 0.0, -- 0-1 ground cover
season = 'autumn', -- nil when seasons are disabled
blackout = false,
}WeatherData
Returned by GetWeatherData and GetZoneWeather-adjacent calls.
{
current = {
weather = 'OVERCAST',
time = { hour = 14, minute = 37 },
zone = 'city',
label = 'Los Santos',
temperature = 12.4, -- present when seasons are enabled
snowLevel = 0.0,
season = 'autumn',
baseWeather = 'CLOUDS', -- client GetWeatherData only: present when a front is overriding the region
front = { id, weather, strength, windStrength, phase, coords }, -- client GetWeatherData only. phase is 'weather' or 'approaching'
},
forecast = {
{
weather = 'RAIN',
startsIn = 8.4, -- real minutes from now
gameTime = { hour = 18, minute = 12 }, -- in-game time it lands
temperature = 9.8,
season = 'autumn',
baseWeather = 'RAIN', -- client GetWeatherData only
},
-- ...up to Config.ForecastLength entries
},
settings = {
dynamicWeather = true,
weatherInterval = 15, -- real minutes
timeScale = 30,
timeFrozen = false,
blackout = false,
clock = { mode = 'game', nightScale = 30, nightStart = 21, nightEnd = 6, utcOffset = 0 }, -- global data only
forecastRevision = 12,
temperatureUnit = 'F', -- display only. Exports always return Celsius
season = 'autumn',
seasonMode = 'game', -- 'game' | 'calendar' | 'manual' | 'holiday'
holiday = nil,
clockOffset = nil, -- per-region clock offset in game minutes, region data only
broadcast = { ... }, -- the live public weather settings, global data only
realWorld = nil, -- { enabled, active, stale, station, attribution } while the real-world mirror is on, global data only
},
wind = { speed = 3.2, direction = 218.4 },
}forecast is empty when automatic weather is off for that region.
Front
{
id = 'front-3',
weather = 'THUNDER',
coords = { x = -5000.0, y = 2800.0, z = 45.0 }, -- where it spawned
position = { x = -1420.5, y = 2800.0, z = 45.0 }, -- where it is now
heading = 90.0, -- compass bearing
speed = 12.0, -- m/s
radius = 900.0,
blendWidth = 350.0,
windLead = 500.0,
windSpeed = 6.0,
duration = 1200.0, -- seconds
fadeSeconds = 20.0,
priority = 0,
age = 184000, -- milliseconds since spawn
}Alert
{
id = 'weather-1757000000-0-4',
zone = 'paleto',
region = 'Paleto Bay',
weather = 'THUNDER',
severity = 3, -- 1 advisory, 2 watch, 3 warning
status = 'active', -- 'upcoming' | 'active' | 'ended'
label = 'Thunderstorm',
title = 'Thunderstorm warning',
advice = 'Lightning and heavy rain are expected...',
message = 'Paleto Bay · until 04:15 tomorrow. Lightning and heavy rain...',
window = 'until 04:15 tomorrow',
startsIn = 0, -- real seconds
endsIn = 620, -- real seconds. nil means "until conditions change"
startTime = { hour = 3, minute = 5, dayOffset = 0 }, -- in-game
endTime = { hour = 4, minute = 15, dayOffset = 1 },
source = 'front', -- 'region' | 'forecast' | 'front' | 'scene'
revision = 2, -- bumps when the alert meaningfully changes
issuedAt = 1757000000, -- unix seconds
updatedAt = 1757000420,
}Server Exports
Status
GetApiVersion
local version = exports['oxide-weather']:GetApiVersion() -- 2Returns: number — the API contract version. Currently 2. Check it before relying on a shape.
IsReady
if exports['oxide-weather']:IsReady() then ... endReturns: boolean — true once the simulation has finished loading and restoring. Before this is true, read exports return an empty or default value: nil for single values, {} for lists and false for flags.
Point queries
All coordinate parameters accept a vector3 or a plain { x = , y = , z = } table. z defaults to 0 when omitted. Coordinates outside -20000..20000 on x/y return nil.
GetConditionsAt
local data = exports['oxide-weather']:GetConditionsAt(vector3(215.0, -810.0, 31.0))Parameters:
| Name | Type | Description |
|---|---|---|
coords | vector3 or table | The point to sample |
Returns: table — a Conditions table, or nil if not ready or the coordinates are invalid.
GetWeatherAt
local weather = exports['oxide-weather']:GetWeatherAt(coords) -- 'THUNDER'Returns: string — the dominant weather at that point, including any front overriding it. nil if not ready.
GetWindAt
local wind = exports['oxide-weather']:GetWindAt(coords)
-- { speed = 3.2, direction = 218.4 }Returns: table with speed (m/s) and direction (degrees), or nil.
IsRainingAt
if exports['oxide-weather']:IsRainingAt(coords) then ... endReturns: boolean — true when the rain share at that point is at least 0.1. nil if not ready.
IsSnowOnGround
local snowy = exports['oxide-weather']:IsSnowOnGround(coords) -- coords optionalParameters:
| Name | Type | Description |
|---|---|---|
coords | vector3 or table | Optional. Without it, the global snow level is used |
Returns: boolean — true when snow cover exceeds Config.Climate.Snow.GroundThreshold. Without seasons, the level is 1 under snowy weather and 0 otherwise. nil when nothing is ready, or when called without coordinates while seasons are disabled.
GetTemperatureAt
local celsius = exports['oxide-weather']:GetTemperatureAt(coords) -- 12.4Returns: number — degrees Celsius, always, regardless of the display unit setting. nil when seasons are disabled.
GetSnowLevelAt
local level = exports['oxide-weather']:GetSnowLevelAt(coords) -- 0.0 to 1.0Returns: number — ground cover from 0 to 1. nil when seasons are disabled.
GetZoneAt
local zone = exports['oxide-weather']:GetZoneAt(coords) -- 'city'Returns: string — the region id, or 'global' when regions are disabled or the point is in no region. nil when the coordinates are invalid.
GetRoadConditionsAt
local road = exports['oxide-weather']:GetRoadConditionsAt(coords)
-- { wetness = 0.42, ice = 0.0, cellSize = 500 }Returns: table with wetness (0-1), ice (0-1) and cellSize (metres), or nil when the point is outside the simulated bounds.
Global state
GetWeather
local weather = exports['oxide-weather']:GetWeather() -- 'CLEAR'Returns: string — the global weather. Regions and fronts may differ locally. Use GetWeatherAt for a specific point.
GetTime
local time = exports['oxide-weather']:GetTime() -- { hour = 14, minute = 37 }Returns: table with hour (0-23) and minute (0-59).
GetTimeScale
local scale = exports['oxide-weather']:GetTimeScale() -- 30Returns: number — game minutes per real minute during the day.
IsTimeFrozen
local frozen = exports['oxide-weather']:IsTimeFrozen()Returns: boolean.
IsBlackout
local dark = exports['oxide-weather']:IsBlackout()Returns: boolean — the global blackout state. Lightning outages are always regional, so use IsZoneBlackout or GetBlackouts to see those.
IsDynamicWeather
local auto = exports['oxide-weather']:IsDynamicWeather()Returns: boolean.
GetWeatherInterval
local minutes = exports['oxide-weather']:GetWeatherInterval() -- 15Returns: number — real minutes between global weather changes.
GetForecast
local forecast = exports['oxide-weather']:GetForecast()Returns: table — the global forecast, a list of entries as described in WeatherData. Empty when automatic weather is off.
GetWeatherData
local data = exports['oxide-weather']:GetWeatherData() -- global
local paleto = exports['oxide-weather']:GetWeatherData('paleto') -- one regionParameters:
| Name | Type | Description |
|---|---|---|
zone | string | Optional. A region id, or 'global'. Defaults to global |
Returns: WeatherData, or nil when the region does not exist or the resource is not ready.
Regions
GetZones
local zones = exports['oxide-weather']:GetZones()Returns: table — a list of region definitions, sorted by id:
{
{ id = 'city', label = 'Los Santos', climate = 'temperate', color = '#57c6c5',
enabled = true, priority = 0, blendWidth = 200.0,
dynamicWeather = true, weatherInterval = 15,
wind = { speed = 1.5, direction = 225.0 },
polygon = { points = { { x = -3200, y = -3800 }, ... } } },
-- ...
}The configured regions are returned even when regional weather is switched off. Check GetSetting('Regional').Enabled or the enabled field of GlobalState['oxide:weatherZones'] before treating them as active.
GetZoneWeather
local weather = exports['oxide-weather']:GetZoneWeather('sandy') -- 'EXTRASUNNY'Returns: string, or nil when the region does not exist.
GetZoneForecast
local forecast = exports['oxide-weather']:GetZoneForecast('sandy')Returns: table — that region's forecast list. Empty when the region has automatic weather off.
Season and climate
GetSeason
local season = exports['oxide-weather']:GetSeason() -- 'winter'Returns: string — 'spring', 'summer', 'autumn' or 'winter'. nil when seasons are disabled.
GetClimateData
local climate = exports['oxide-weather']:GetClimateData()
-- { season = 'winter', mode = 'game', day = 3, daysPerSeason = 7,
-- holiday = nil, temperatureUnit = 'F' }Returns: table, or nil when seasons are disabled. mode is 'game', 'calendar', 'manual' or 'holiday'. day and daysPerSeason are only present in game mode with no holiday running.
GetTemperature
local celsius = exports['oxide-weather']:GetTemperature() -- global
local paleto = exports['oxide-weather']:GetTemperature('paleto') -- one regionReturns: number — degrees Celsius at that region's reference point. nil when seasons are disabled.
GetSnowLevel
local level = exports['oxide-weather']:GetSnowLevel() -- global
local alps = exports['oxide-weather']:GetSnowLevel('chiliad') -- one regionReturns: number — 0 to 1. nil when seasons are disabled.
Fronts and blackouts
GetFronts
local fronts = exports['oxide-weather']:GetFronts()Returns: table — a list of Front tables, sorted by priority then id. Empty when fronts are disabled.
IsZoneBlackout
local dark = exports['oxide-weather']:IsZoneBlackout('paleto')Parameters:
| Name | Type | Description |
|---|---|---|
zone | string | A region id, or 'global' |
Returns: boolean, or nil when the region does not exist.
GetBlackouts
local blackouts = exports['oxide-weather']:GetBlackouts()
-- {
-- overrides = { paleto = true },
-- outages = { { id = 'blackout-2', zone = 'city', source = 'scheduled',
-- startsIn = 480000, endsIn = 2280000 } }, -- milliseconds
-- }Returns: table with overrides (region id to boolean) and outages (times in milliseconds). source is 'scheduled' for ScheduleBlackout, 'manual' for a timed SetZoneBlackout and 'storm' for a lightning strike.
GetRealWorldWeather
local mirror = exports['oxide-weather']:GetRealWorldWeather()
-- {
-- revision = 8, enabled = true, active = true, stale = false, exhausted = false,
-- location = 'Paris, FR', pollMinutes = 15, syncClock = false,
-- station = { name = 'Paris, Ile-de-France, France', latitude = 48.85, longitude = 2.35,
-- timezone = 'Europe/Paris', utcOffset = 7200 },
-- weather = 'RAIN', code = 63, temperature = 14.2, humidity = 81, isDay = true,
-- wind = { speed = 4.1, direction = 230 },
-- observedAt = 1789200900, fetchedAt = 1789201013, nextPollIn = 612,
-- lastError = nil, attribution = 'Weather data by Open-Meteo.com',
-- }Returns: table describing the real-world weather mirror, or nil before the simulation is ready.
activeis true while the mirror owns the weather;enabledalone means it is switched on but has nothing to show yet.revisiongoes up whenever any of this changes.codeis the raw WMO weather code andweatherits mapped type.- Times are UTC epoch seconds and
nextPollInis seconds. utcOffsetis the station's offset in seconds, which is what the clock mirror uses.- Show
attributionnext to the data if you display it.
Alerts and reports
GetWeatherAlerts
local alerts = exports['oxide-weather']:GetWeatherAlerts() -- every region
local local_ = exports['oxide-weather']:GetWeatherAlerts('paleto') -- one regionParameters:
| Name | Type | Description |
|---|---|---|
zone | string | Optional. A region id, or 'global' |
Returns: table — a list of active Alert tables, most severe first. On a bad region id, returns nil, reason.
GenerateWeazelReport
local report, reason = exports['oxide-weather']:GenerateWeazelReport(nil, { unit = 'C' })
if report then print(report.text) endParameters:
| Name | Type | Description |
|---|---|---|
zone | string | Optional. A region id to report on. Omit for every region |
options | table | Optional. { unit = 'C' or 'F' }. Defaults to the server's display unit |
Returns:
{
headline = 'Weazel Weather · 14:37',
text = 'Weazel Weather · 14:37\nLos Santos: OVERCAST 12.4°C, wind 3.2 m/s at 218\n...',
generatedAt = 1757000000, -- unix seconds
gameTime = { hour = 14, minute = 37 },
season = 'autumn',
unit = 'C',
zones = { 'city', 'paleto', ... },
alerts = { <Alert>, ... }, -- active alerts included in the text
}Returns nil, reason when the public weather service is turned off or the region is unknown.
text is a ready-to-print bulletin with a headline, one line per region with conditions, temperature and wind, an outlook line, and every active alert. Use text for a quick print, or build your own layout from the structured fields.
Scene locks
GetSceneLocks
local locks = exports['oxide-weather']:GetSceneLocks()
-- { { target = 'global', owner = 'admin', weather = 'THUNDER',
-- time = { hour = 3, minute = 5 }, remaining = 412.0 } } -- secondsReturns: table — a list of active locks, sorted by target. target is 'global' or a player server id as a string. owner is the resource that requested it, or 'admin' for a command.
Exposure
GetExposureAt
local cover = exports['oxide-weather']:GetExposureAt(coords, 0)
-- { known = true, covered = false, bucket = 0, age = 4.2 }Parameters:
| Name | Type | Description |
|---|---|---|
coords | vector3 or table | The point to check |
bucket | number | Optional routing bucket. Defaults to 0 |
Returns: table:
| Field | Type | Meaning |
|---|---|---|
known | boolean | Whether an answer is available yet |
covered | boolean | Present when known is true |
bucket | number | The bucket the answer applies to |
age | number | Seconds since the answer was measured |
swimming | boolean | Whether the player is swimming. Always false for a plain coordinate |
nil when the coordinates or bucket are invalid, or before the simulation is ready. Points inside one of your configured covered areas answer { known = true, covered = true, bucket } at once, with no age.
The first call for a new point almost always returns { known = false }. The point is queued and a nearby player's game is asked to check it. Poll again a second or two later. Results stay fresh for Config.Exposure.FreshSeconds (30 by default). If no player is within Config.Exposure.Radius of the point, it never resolves. Do not treat known = false as "not covered".
GetPlayerExposure
local exposure = exports['oxide-weather']:GetPlayerExposure(source)Parameters:
| Name | Type | Description |
|---|---|---|
source | number | Player server id |
Returns: table:
{
known = true,
covered = false, -- interiors and enclosed vehicles count as covered
wetness = 0.63, -- 0-1. Rises in rain, falls when dry, 1 while swimming
insulation = 0.0, -- 0-1, from Config.Exposure.Clothing
temperature = 4.1, -- Celsius at the player's position
conditions = <Conditions>,
}nil when the player is offline or has no ped.
Settings
GetSetting
local snow = exports['oxide-weather']:GetSetting('Snow')Parameters:
| Name | Type | Description |
|---|---|---|
key | string | A top-level config key, for example 'Snow', 'Climate', 'Broadcast' |
Returns: a deep copy of the live value, or nil for an unknown key. Open to any resource.
Write exports
All of these require the calling resource to be listed in Config.SetterResources. All return success, reason.
The trailing actor parameter is optional in every case. Pass a player's server id to have the change attributed to and permission-checked against that player, or nil for a system change. See Permissions for writes.
| Export | Signature | Notes |
|---|---|---|
SetWeather | (weather, actor?) | Sets the global weather and every region to match |
SetTime | (hour, minute, actor?) | Hour 0-23, minute 0-59. Clears every per-region clock offset and switches the clock to game mode |
SetTimeScale | (scale, actor?) | Game minutes per real minute during the day, 0.01-1440 |
FreezeTime | (frozen, hour?, actor?) | frozen is a boolean. Optional hour to freeze at |
SetClock | (clock, actor?) | A full clock table: mode, nightScale, nightStart, nightEnd, utcOffset. utcOffset is in minutes, -720 to 840 |
SetBlackout | (enabled, actor?) | Global blackout. Clears every regional override and schedule |
SetDynamicWeather | (enabled, actor?) | Automatic weather, globally and in every region |
SetWeatherInterval | (minutes, actor?) | Real minutes between changes, 0.017-10080 |
SetZoneWeather | (zone, weather, actor?) | One region |
SetZoneDynamicWeather | (zone, enabled, actor?) | One region |
SetZoneWeatherInterval | (zone, minutes, actor?) | One region |
SetZoneTime | (zone, hour, minute?, actor?) | Gives a region its own clock offset. Pass hour = false to clear it. 'global' is rejected, use SetTime |
SetForecast | (zone, entries, revision, actor?) | Pins specific weather into a region's queue. entries is a list of exactly Config.ForecastLength weather names, revision must match the region's current forecastRevision, and the region's automatic weather must be on |
SetSeason | (season, actor?) | 'spring', 'summer', 'autumn', 'winter', 'auto' or 'calendar' |
SetHoliday | (name, enabled, actor?) | 'christmas' or 'halloween' |
SpawnFront | (options, actor?) | See below. Returns true, frontId |
RemoveFront | (id, actor?) | |
SetZoneBlackout | (zone, enabled, duration?, actor?) | duration in seconds. Omit for indefinite |
ScheduleBlackout | (zone, delay, duration, actor?) | Both in seconds. Returns true, outageId |
CancelBlackout | (id, actor?) | |
SetSceneLock | (target, seconds, actor?) | target is 'global' or a player server id. seconds 1-7200 |
ClearSceneLock | (target, actor?) | Only the resource that took the lock may release it |
SetSetting | (key, value, actor?) | Writes a setting to the database, with the same validation as the settings menu |
SetSetting and restart-only keys
SetSetting has a third return value. For a key that needs a restart of the resource before it takes effect, it returns true, nil, 'restart':
local ok, reason, pending = exports['oxide-weather']:SetSetting('WeatherTransitionTime', 0, nil)
if ok and pending == 'restart' then
print('saved, but oxide-weather must be restarted for it to apply')
endRead the current value back with GetSetting(key). Keys are the top-level names from the config files, for example 'Snow', 'Climate', 'Fronts', 'Broadcast', 'Roads', 'Exposure'. See Configuration for the full list and which ones need a restart.
SpawnFront options
local ok, frontId = exports['oxide-weather']:SpawnFront({
coords = vector3(-1200.0, 2400.0, 30.0), -- required
weather = 'THUNDER', -- defaults to Config.Fronts.Defaults.weather
heading = 90.0, -- 0-360, compass bearing
speed = 12.0, -- 0-100 m/s
radius = 900.0, -- 100-5000 m
blendWidth = 350.0, -- 1-5000 m, cannot exceed radius
windLead = 500.0, -- 0-5000 m
windSpeed = 6.0, -- 0-12 m/s
duration = 1200.0, -- 10-7200 seconds
fadeSeconds = 20.0, -- 0-60, cannot exceed half the duration
priority = 0, -- -1000 to 1000
}, nil)Anything you leave out falls back to Config.Fronts.Defaults.
Bridge entry points
WeatherBridge(method, origin, ...) and WeatherReadBridge(method, origin, ...) exist so o-link can forward calls while preserving the original caller's identity. Both reject any invoker other than o-link, and both refuse nested calls.
Do not call these directly. Use the named exports above, or the o-link namespace.
Client Exports
local weather = exports['oxide-weather']:GetWeather()Client exports read from the state the server has already pushed, so they are cheap. They reflect what the local player actually sees, including region blending, fronts and any scene lock applied to them.
| Export | Signature | Returns |
|---|---|---|
GetApiVersion | () | number — 2 |
IsReady | () | boolean — settings received and the server is ready |
GetWeather | () | string — the weather where this player is |
GetTime | () | table — { hour, minute }, including any regional offset |
IsTimeFrozen | () | boolean |
IsBlackout | () | boolean — for this player's region |
GetForecast | () | table — the forecast for this player's region, with live countdowns |
GetCurrentZone | () | string — region id, or 'global' |
GetWind | () | table — { speed, direction } |
GetSeason | () | string or nil |
GetTemperature | () | number — Celsius at the player, or nil |
GetTemperatureAt | (coords) | number — Celsius, or nil |
GetSnowLevel | () | number — 0-1 at the player, or nil |
GetSnowLevelAt | (coords) | number — 0-1, or nil |
GetConditionsAt | (coords) | Conditions, or nil. Defaults to the player's position |
GetWindAt | (coords) | table — { speed, direction }, or nil |
IsRainingAt | (coords) | boolean or nil |
IsSnowOnGround | (coords) | boolean or nil |
GetWeatherData | () | WeatherData for this player's location. Blocking, asks the server |
GetFronts | () | table — a list of Front tables |
GetCurrentFront | () | table — { id, weather, strength, windStrength, phase, coords } for the front the player is inside (phase = 'weather') or the one whose wind has reached them (phase = 'approaching', strength = 0), or nil |
GetRoadConditionsAt | (coords) | table — { wetness, ice, cellSize }, or nil. Cached 5 seconds per grid cell. The server answers at most one request every 500 ms per player, so a faster cache miss returns nil |
IsCovered | (coords) | boolean when it can tell, nil when it cannot. Defaults to the player's position |
GetExposureAt | (coords) | table — { known, covered } |
RegisterExposureEntity | (entity) | boolean — see below |
UnregisterExposureEntity | (entity) | boolean |
ToggleSync | (enabled) | boolean — see below |
IsSyncEnabled | () | boolean |
IsCovered
local covered = exports['oxide-weather']:IsCovered() -- current positionFires a probe straight up from the point and reports whether it hits anything. Bounded to loaded collision within 150 metres of the player, and yields while the probe runs.
Returns nil when it cannot tell: too far away, collision not streamed, or the probe timed out. Treat nil as unknown, not as "not covered".
RegisterExposureEntity / UnregisterExposureEntity
-- Ignore a prop you spawned, so it does not read as shelter
exports['oxide-weather']:RegisterExposureEntity(umbrellaObject)
exports['oxide-weather']:UnregisterExposureEntity(umbrellaObject)Registers an object so cover probes near it ignore it. Useful for props your own resource spawns that should not count as a roof.
Only objects (entity type 3) are accepted. Only the resource that registered an entity may unregister it, and everything a resource registered is dropped automatically when that resource stops. Registrations are also dropped if the entity stops existing or its model changes.
Returns: boolean — whether the call was accepted.
ToggleSync
exports['oxide-weather']:ToggleSync(false) -- suspend weather sync for this client
exports['oxide-weather']:ToggleSync(true) -- resumeSuspends oxide-weather's control of the sky and clock on this client so your resource can take over, for a cutscene or a minigame.
Ownership is tracked per resource and is idempotent. Only the resource that suspended sync can resume it. Suspensions are released automatically when the suspending resource stops. Sync resumes only when every holder has released it.
While suspended, IsSyncEnabled() returns false and the resource stops applying weather, time, blackout and snow. Whatever you set stays until you release.
Returns: boolean — false if the arguments were rejected.
The o-link Weather Namespace
local olink = exports['o-link']:olink()Server
olink.weather.GetResourceName() returns 'oxide-weather' when it is running, otherwise 'none'.
Read functions: GetApiVersion, IsReady, GetSetting, GetWeather, GetTime, GetTimeScale, IsTimeFrozen, IsBlackout, IsDynamicWeather, GetWeatherInterval, GetForecast, GetWeatherData, GetZones, GetZoneWeather, GetZoneForecast, GetZoneAt, GetSeason, GetClimateData, GetTemperature, GetTemperatureAt, GetSnowLevel, GetSnowLevelAt, GetFronts, GetWeatherAt, IsZoneBlackout, GetBlackouts, GetSceneLocks, GetWeatherAlerts, GenerateWeazelReport, GetConditionsAt, IsRainingAt, GetWindAt, IsSnowOnGround, GetExposureAt, GetPlayerExposure, GetRoadConditionsAt, GetRealWorldWeather
Write functions: SetWeather, SetSetting, SetTime, SetTimeScale, FreezeTime, SetBlackout, SetDynamicWeather, SetWeatherInterval, SetZoneWeather, SetZoneDynamicWeather, SetZoneWeatherInterval, SetSeason, SetHoliday, SpawnFront, RemoveFront, SetZoneBlackout, ScheduleBlackout, CancelBlackout, SetSceneLock, ClearSceneLock, SetForecast, SetClock, SetZoneTime
Signatures match the direct exports. Writes normalise their first return to a strict boolean, so check ok == true. Only the first two returns pass through, so olink.weather.SetSetting cannot report the 'restart' marker; call the direct export when you need it.
Client
GetResourceName, GetApiVersion, IsReady, ToggleSync, GetWeather, GetTime, IsTimeFrozen, IsBlackout, GetForecast, GetCurrentZone, GetWind, GetSeason, GetTemperature, GetTemperatureAt, GetSnowLevel, GetSnowLevelAt, GetFronts, GetCurrentFront, IsSyncEnabled, GetConditionsAt, GetWindAt, IsRainingAt, IsSnowOnGround, IsCovered, GetExposureAt, RegisterExposureEntity, UnregisterExposureEntity, GetWeatherData, GetRoadConditionsAt
ToggleSync, RegisterExposureEntity and UnregisterExposureEntity fill in your resource name from the engine, so you do not pass it.
Bridge events
o-link re-emits a subset of the events below under its own names, so you can listen without depending on oxide-weather directly:
Server: olink:server:weather:ready, olink:server:weather:stopped, olink:server:weather:timeChanged, olink:server:weather:clockProgress, olink:server:weather:weatherChanged, olink:server:weather:zoneWeatherChanged, olink:server:weather:seasonChanged, olink:server:weather:blackoutChanged, olink:server:weather:alertsChanged
Client: olink:client:weather:ready, olink:client:weather:stopped
Payloads are identical to the oxide:weather:* events they mirror.
Server Events
Listen with AddEventHandler. These are server-local, not network events.
oxide:weather:weatherChanged
AddEventHandler('oxide:weather:weatherChanged', function(data)
-- data.weather : string, the new global weather
-- data.previous : string or nil
end)oxide:weather:timeChanged
Fires once per in-game minute.
AddEventHandler('oxide:weather:timeChanged', function(time)
-- time.hour, time.minute
end)oxide:weather:clockProgress
Fires on every simulation step: at least once a second, and again whenever a read export forces the simulation to advance. Do not assume a fixed cadence.
AddEventHandler('oxide:weather:clockProgress', function(data)
-- data.minutes : number, total in-game minutes advanced since the resource started
-- data.frozen : boolean, true while the clock is frozen or a global scene lock is held
end)oxide:weather:freezeChanged
AddEventHandler('oxide:weather:freezeChanged', function(data)
-- data.frozen : boolean
end)oxide:weather:blackoutChanged
AddEventHandler('oxide:weather:blackoutChanged', function(data)
-- data.enabled : boolean, the global blackout state
end)oxide:weather:zoneWeatherChanged
AddEventHandler('oxide:weather:zoneWeatherChanged', function(data)
-- data.zone : string, region id
-- data.weather : string
-- data.previous : string or nil
end)oxide:weather:zoneBlackoutChanged
AddEventHandler('oxide:weather:zoneBlackoutChanged', function(data)
-- data.zone : string, region id
-- data.enabled : boolean
end)oxide:weather:seasonChanged
AddEventHandler('oxide:weather:seasonChanged', function(data)
-- data.season : string or nil
-- data.previous : string or nil
end)oxide:weather:frontSpawned / oxide:weather:frontRemoved
AddEventHandler('oxide:weather:frontSpawned', function(front)
-- front : the front as spawned, with age = 0 and no position field. Use GetFronts for the live shape
end)
AddEventHandler('oxide:weather:frontRemoved', function(data)
-- data.id : string
-- data.reason : string, for example 'admin' or 'expired'
end)oxide:weather:lightningStrike
Also sent to every client as a network event.
AddEventHandler('oxide:weather:lightningStrike', function(data)
-- data.id : string, unique strike id
-- data.front : string, the front id that produced it
-- data.zone : string, region id
-- data.coords : { x, y, z }
-- data.radius : number, metres within which players see the flash
-- data.flicker : boolean
-- data.flickerMilliseconds : number
end)oxide:weather:alertsChanged
Fires whenever the active alert set meaningfully changes.
AddEventHandler('oxide:weather:alertsChanged', function(alerts)
-- alerts : a list of Alert tables, most severe first
end)oxide:weather:server:configUpdated
Fires after a live setting is saved, except for the control keys that apply through the simulation directly (DynamicWeather, WeatherInterval, TimeScale, FreezeTime, Clock, Broadcast) and keys that wait for a restart. Re-read anything you cached from GetSetting.
Client Events
oxide:weather:forecastChanged
AddEventHandler('oxide:weather:forecastChanged', function(forecast)
-- forecast : the forecast list for this player's region
end)Fires on first sync, when the global forecast is republished, and when the player crosses into another region.
oxide:weather:zoneChanged
AddEventHandler('oxide:weather:zoneChanged', function(data)
-- data.zone : string, the region entered
-- data.previous : string or nil
end)oxide:weather:lightningStrike (client)
The same payload as the server event. Delivered to every client; filter by distance yourself if you need to.
oxide:weather:bulletinChanged
RegisterNetEvent('oxide:weather:bulletinChanged', function(packet)
-- packet.session : string, changes when the resource restarts
-- packet.sequence : number, increments on every change
-- packet.alerts : a list of Alert tables
end)oxide:weather:client:configUpdated
Fires after settings arrive or change on this client.
State Bags
Everything below is on GlobalState, readable from server and client without a call. This is the cheapest way to follow the weather in a loop.
| Key | Type | Contents |
|---|---|---|
oxide:weatherReady | boolean | true once the simulation is running |
oxide:weather | string | Global weather |
oxide:time | table | { hour, minute, minutes, scale, frozen, clock }. minutes is the fractional game minute of the day, scale the current timescale, frozen whether the clock is held and clock the clock settings. Re-published on each minute rollover or when the clock changes, so advance it yourself from minutes and scale between updates |
oxide:blackout | boolean | Global blackout |
oxide:freezeTime | boolean | Clock frozen |
oxide:dynamicWeather | boolean | Automatic weather on |
oxide:timeScale | number | Game minutes per real minute |
oxide:weatherInterval | number | Real minutes between changes |
oxide:forecast | table | The global forecast list |
oxide:season | string | Current season, or absent when seasons are off |
oxide:snow | table | Region id to snow level, plus global |
oxide:climate | table | The same shape as GetClimateData() |
oxide:realWorld | table | The same shape as GetRealWorldWeather() without lastError, published whenever it changes |
oxide:zoneTimes | table | Region id to { hour, minute, offset }, only for regions with a clock offset |
oxide:regionalWeather | table | { revision, zones = { [id] = WeatherData }, climate } |
oxide:weatherZones | table | { enabled, revision, zones = { <definition>, ... } } |
oxide:fronts | table | { version, epoch, stamp, sequence, paused, fronts, outages, overrides } |
oxide:weatherHolds | table | Active scene locks by target, each with remaining in milliseconds |
oxide:weatherBroadcastConfig | table | The live public weather settings |
oxide-weather:config | table | The list of setting keys published to clients |
oxide-weather:config:<Key> | any | The live value of one setting. SetterResources, Persistence, Exposure and RealWorld are never published; read those with GetSetting on the server |
oxide:regionalWeather and oxide:weatherZones carry a revision. Only trust them together when the two revisions match, since they are published separately.
-- client
local weather = GlobalState['oxide:weather']
local time = GlobalState['oxide:time']
AddStateBagChangeHandler('oxide:weather', 'global', function(_, _, value)
print('weather is now', value)
end)Integration Examples
Cold damage while exposed
-- server, in any resource
local olink = exports['o-link']:olink()
CreateThread(function()
while true do
Wait(30000)
for _, id in ipairs(GetPlayers()) do
local src = tonumber(id)
local exposure = olink.weather.GetPlayerExposure(src)
if exposure and exposure.known and not exposure.covered
and exposure.temperature and exposure.temperature <= 0 then
local protection = exposure.insulation - exposure.wetness * 0.5
if protection < 0.5 then
local ped = GetPlayerPed(src)
SetEntityHealth(ped, math.max(101, GetEntityHealth(ped) - 5))
end
end
end
end
end)Slow crop growth in the cold
-- server
local olink = exports['o-link']:olink()
local function GrowthMultiplier(coords)
local conditions = olink.weather.GetConditionsAt(coords)
if not conditions then return 1.0 end -- weather unavailable, do not punish the player
local multiplier = 1.0
if conditions.raining then multiplier = multiplier * 1.15 end
if conditions.temperature then
if conditions.temperature <= 0 then multiplier = multiplier * 0.7
elseif conditions.temperature >= 35 then multiplier = multiplier * 0.8 end
end
return multiplier
endReduced grip on wet and icy roads
-- client
local olink = exports['o-link']:olink()
CreateThread(function()
while true do
Wait(2000)
local vehicle = GetVehiclePedIsIn(PlayerPedId(), false)
if vehicle ~= 0 and GetPedInVehicleSeat(vehicle, -1) == PlayerPedId() then
local road = olink.weather.GetRoadConditionsAt(GetEntityCoords(PlayerPedId()))
if road then
-- slippery once the road is properly wet, or once ice has formed
SetVehicleReduceGrip(vehicle, road.ice > 0.3 or road.wetness > 0.6)
end
end
end
end)A Weazel News weather segment
-- server, in a news or radio resource
local olink = exports['o-link']:olink()
RegisterCommand('weatherreport', function(source)
local report = olink.weather.GenerateWeazelReport(nil, { unit = 'C' })
if not report then
olink.notify.Send(source, 'The weather desk is offline.', 'error')
return
end
TriggerClientEvent('chat:addMessage', -1, {
color = { 200, 200, 255 },
multiline = true,
args = { 'Weazel News', report.text },
})
end, false)Suspending sync for a cutscene
-- client
local weatherResource = exports['o-link']:olink().weather
local function BeginCutscene()
weatherResource.ToggleSync(false) -- oxide-weather stops touching the sky
SetWeatherTypeNowPersist('EXTRASUNNY')
NetworkOverrideClockTime(6, 0, 0)
end
local function EndCutscene()
weatherResource.ToggleSync(true) -- normal weather resumes immediately
endPrefer a scene lock over ToggleSync when you want the real weather held still rather than replaced. A lock keeps everyone consistent and expires on its own:
-- server, from a resource listed in Config.SetterResources
exports['oxide-weather']:SetSceneLock('global', 300, nil)
-- ...run the scene...
exports['oxide-weather']:ClearSceneLock('global', nil)Reacting to a storm rolling in
-- server
AddEventHandler('olink:server:weather:alertsChanged', function(alerts)
for _, alert in ipairs(alerts) do
if alert.severity == 3 and alert.status == 'active' then
print(('Severe weather in %s: %s'):format(alert.region, alert.title))
end
end
end)