# esx_weather (/docs/addons/esx_weather)

esx_weather is the ESX Legacy weather and time sync. It splits the map into zones (Los Santos, Sandy Shores and Paleto Bay by default), gives each zone its own weather that rotates on a timer, and lets admins change a zone's weather from an in-game panel. Every player sees the weather of the zone closest to them, with a smooth transition when they drive into another zone.

## Overview [#overview]

| | |
| --- | --- |
| Resource | `esx_weather` (version 1.0.0, added with ESX Legacy 1.16.0) |
| Side | Server state, client sync, NUI admin panel |
| Dependencies | `esx_lib`, `es_extended` |
| Database | None; persistence uses resource KVP |

Run only one weather sync. Remove vSync, cd_easytime, qb-weathersync or similar resources before using esx_weather, or they will overwrite each other every tick.

## Installation [#installation]

```bash title="server.cfg"
ensure esx_lib
ensure es_extended
ensure esx_weather
```

## How it works [#how-it-works]

1. On start the server gives each zone a random weather from `ValidTypes` (or `defaultType` when `dynamic` is off), then restores saved weather from KVP if `persist` is on.
2. With `dynamic = true`, every `cycleTimeSeconds` all zones get a new random weather and the server broadcasts the list.
3. Each client checks once per second which zone center is closest and applies that zone's weather with `SetWeatherTypeOvertimePersist` over `transitionTimeSeconds`.
4. For time, the server sends its real clock (`os.date`) on start and when a player loads. When the client enters a zone, it sets the clock to that time plus the zone's hour offset. GTA then advances the clock at `secondsPerGameMinute` real seconds per game minute.

## Configuration [#configuration]

All keys are in `config/main.lua`.

| Key | Default | Description |
| --- | --- | --- |
| `Config.debug` | `false` | Debug prints |
| `Config.panelCommand` | `"weatherpanel"` | Command that opens the admin panel |
| `Config.AdminGroups` | `{ admin = true, moderator = true }` | ESX groups allowed to use the panel |
| `Config.Zones` | 3 zones | Zone name to `vector2` center, see below |
| `Config.Weather.ValidTypes` | all 17 types | Weather types used for random rotation and accepted from the panel |
| `Config.Weather.transitionTimeSeconds` | `30` | Seconds to blend into a new weather |
| `Config.Weather.cycleTimeSeconds` | `1800` (30 min) | Seconds between automatic rotations |
| `Config.Weather.dynamic` | `true` | Rotate weather automatically; `false` keeps `defaultType` until changed in the panel |
| `Config.Weather.defaultType` | `CLEAR` | Start weather when `dynamic` is `false`; must be in `ValidTypes` |
| `Config.Weather.persist` | `true` | Save zone weather (and the rotation timer) in KVP across restarts |
| `Config.Weather.persistKey` | `"esx_weather_zones"` | KVP key used for persistence |
| `Config.Time.secondsPerGameMinute` | `15` | Real seconds per in-game minute (a full day takes 6 hours) |
| `Config.Time.Zones` | LS `0`, Sandy `1`, Paleto `2` | Hour offset per zone |

Default zone centers:

```lua title="config/main.lua"
Config.Zones = {
    [Shared.Enum.Zone.LOS_SANTOS] = vector2(-33.6997, -1102.0000),
    [Shared.Enum.Zone.SANDY_SHORES] = vector2(1728.2000, 3709.3000),
    [Shared.Enum.Zone.PALETO_BAY] = vector2(-220.2753, 6408.9116),
}
```

The weather types are `CLEAR`, `EXTRASUNNY`, `CLOUDS`, `OVERCAST`, `RAIN`, `CLEARING`, `THUNDER`, `SMOG`, `FOGGY`, `XMAS`, `SNOW`, `SNOWLIGHT`, `BLIZZARD`, `HALLOWEEN`, `NEUTRAL`, `RAIN_HALLOWEEN` and `SNOW_HALLOWEEN`.

<Callout type="warn" title="Remove snow and Halloween types for normal play">
The default `ValidTypes` list contains every type, so random rotation can produce `XMAS`, `BLIZZARD` or `HALLOWEEN` in the middle of summer. Most servers trim the list to `CLEAR`, `EXTRASUNNY`, `CLOUDS`, `OVERCAST`, `RAIN`, `CLEARING`, `THUNDER`, `SMOG` and `FOGGY`.
</Callout>

The server validates the config on start and throws a clear error if, for example, `Zones` is empty, `cycleTimeSeconds` is not positive, or `defaultType` is not in `ValidTypes`.

### Adding a zone [#adding-a-zone]

Zones are matched by name, so a new zone needs an entry in three places:

```lua title="shared/enum/Zone.lua"
Shared.Enum.Zone = {
    LOS_SANTOS = "Los Santos",
    SANDY_SHORES = "Sandy Shores",
    PALETO_BAY = "Paleto Bay",
    CAYO_PERICO = "Cayo Perico",
}
```

```lua title="config/main.lua"
Config.Zones[Shared.Enum.Zone.CAYO_PERICO] = vector2(4840.0, -5174.0)
Config.Time.Zones[Shared.Enum.Zone.CAYO_PERICO] = 0
```

## Admin panel [#admin-panel]

`/weatherpanel` opens a NUI panel with the zone you are in, its current weather and a selector for every zone. The client hides the panel from non-admins, and the server re-checks the group on every change, so the panel cannot be abused by a modified client.

## Client exports [#client-exports]

### ToggleWeatherSync [#toggleweathersync]

```lua
exports.esx_weather:ToggleWeatherSync(state)
```

### ToggleTimeSync [#toggletimesync]

```lua
exports.esx_weather:ToggleTimeSync(state)
```

| Name | Type | Description |
| --- | --- | --- |
| `state` | `boolean` | `false` pauses syncing on this client, `true` resumes it; anything else throws an error |

```lua title="client/heist_intro.lua"
exports.esx_weather:ToggleWeatherSync(false)
exports.esx_weather:ToggleTimeSync(false)

SetWeatherTypeNowPersist('EXTRASUNNY')
NetworkOverrideClockTime(12, 0, 0)
-- play the scene

exports.esx_weather:ToggleWeatherSync(true)
exports.esx_weather:ToggleTimeSync(true)
```

When you resume, esx_weather only re-applies weather or time if the zone value differs from what it last applied. If you changed weather or time manually while sync was off, the change can stay until the zone's weather changes or the player enters another zone.

## Events [#events]

| Event | Direction | Description |
| --- | --- | --- |
| `esx_weather:server:setZoneWeather` | client to server | `(zone, weatherType)` from the panel. Admin group check, zone and type validation, 1 s rate limit |
| `esx_weather:client:weather:setZones` | server to client | Full zone weather table |
| `esx_weather:client:weather:setZone` | server to client | `(zone, weatherType)` for a single zone |
| `esx_weather:client:time:setTime` | server to client | `{ hours, minutes, seconds }` base time |

esx_weather has no server exports. The internal `Modules.Weather.setZone` function is not exposed to other resources, so changing a zone's weather from another script means editing esx_weather itself (for example by adding your own `exports('SetZoneWeather', Modules.Weather.setZone)` in `server/main.lua`).

## Common issues [#common-issues]

- **Weather flickers between two states:** another weather sync resource is running. Remove it.
- **Players see different times:** time is set when a player enters a zone, from the server time sent at login plus elapsed real seconds, while GTA's own clock runs faster. Clocks can drift between players who have not changed zones for a while.
- **Snow or Halloween weather appears:** trim `Config.Weather.ValidTypes`.
- **Panel does not open:** your ESX group is not in `Config.AdminGroups`, or the zone weather has not been received yet (wait a few seconds after joining).
- **Changes in config are ignored after restart:** `persist = true` restores the saved weather. Delete the KVP or change `persistKey` to start fresh.

## Related [#related]

- [esx_adminmenu](/docs/addons/esx_adminmenu)
- [Admin permissions](/docs/guides/admin-permissions)
- [esx_lib](/docs/core/esx_lib)
- [Addons overview](/docs/addons)
