Weather App
Full documentation for the tablet Weather app — live weather display with animated particles and admin controls integrated with Renewed-Weathersync.
Overview
The Weather app displays current in-game weather with animated canvas particles, temperature, humidity, wind stats, and a digital clock. It integrates with the Renewed-Weathersync resource for live data and falls back to static data if the resource is not found. Players with command.weather ACE permission get an admin control panel to change weather, set time, toggle blackout, and freeze time.
Architecture
┌─────────────┐ NUI ┌──────────────┐ GlobalState ┌──────────────────┐
│ React UI │ ──────────→ │ client/ │ ←────────────── │ Renewed- │
│ (Weather) │ │ tablet.lua │ │ Weathersync │
│ │ ←────────── │ │ TriggerServer │ (server/weather) │
│ data │ SendNUI │ 8 callbacks │ ──────────────→ │ │
│ isAdmin │ │ │ │ GlobalState: │
│ adminData │ └──────────────┘ │ .weather │
└─────────────┘ │ │ .currentTime │
│ │ .freezeTime │
▼ │ .blackOut │
┌──────────────┐ └──────────────────┘
│ server/ │
│ weather_ │──→ lib.callback.await('Renewed-Weathersync:...')
│ control.lua │──→ GlobalState.blackOut = not GlobalState.blackOut
│ │──→ GlobalState.freezeTime = not GlobalState.freezeTime
│ (91 lines) │──→ GlobalState.currentTime = { hour, minute }
└──────────────┘Files
| File | Purpose |
|---|---|
ui/src/apps/Weather/index.jsx | React weather UI: animated particles, display, admin panel (534 lines) |
client/tablet.lua | 8 NUI callbacks for weather data + admin commands (lines 161–250) |
server/weather_control.lua | 7 admin server events with ACE permission checks (91 lines) |
config/config.lua | (No weather config — uses Renewed-Weathersync config) |
Data Flow
1. Load Weather Data
WeatherApp mounts
→ nuiFetch('weather:getData')
→ client/tablet.lua reads GlobalState.weather + GlobalState.currentTime
→ Returns:
{
source: 'Renewed-Weathersync' | 'fallback',
weather: 'EXTRASUNNY' | 'CLOUDS' | ...,
hour: 14,
minute: 30,
windSpeed: 0.1,
windDirection: 0,
hasSnow: false,
timeLeft: 120, -- minutes until weather change
}
→ React sets data + source
→ If source = 'Renewed-Weathersync' → "Live" badge (green)
→ If fallback → "Static · Resource not found" badge (grey)2. Auto-Refresh
Every 15 seconds:
→ nuiFetch('weather:getData') → updates display
→ nuiFetch('weather:getQueue') → updates admin panel state3. Admin Check
WeatherApp mounts
→ nuiPost('weather:checkAdmin')
→ client/tablet.lua → TriggerServerEvent('wtf_group:server:weather:checkAdmin')
→ server/weather_control.lua: IsPlayerAceAllowed(source, 'command.weather')
→ TriggerClientEvent('wtf_group:client:weather:adminStatus', src, isAdmin)
→ SendNUIMessage({ action: 'weather:adminStatus', isAdmin })
→ React shows/hides "Admin" button4. Admin: Set Weather Type
Admin clicks weather type button (e.g., RAIN)
→ nuiPost('weather:setType', { index: 1, weatherType: 'RAIN' })
→ client/tablet.lua → TriggerServerEvent('wtf_group:server:weather:setType', 1, 'RAIN')
→ server/weather_control.lua: lib.callback.await('Renewed-Weathersync:server:setWeatherType', src, 1, 'RAIN')
→ Renewed-Weathersync updates GlobalState.weather
→ All clients receive new weather state
→ TriggerClientEvent('wtf_group:client:weather:controlSuccess', src, 'setType', 'RAIN')
→ SendNUIMessage({ action: 'weather:controlResult', success: true, ... })
→ React refreshes data after 300ms5. Admin: Set Time
Admin enters hour/minute → clicks "Set Time"
→ nuiPost('weather:setTime', { hour: 14, minute: 30 })
→ client → server: TriggerServerEvent('wtf_group:server:weather:setTime', 14, 30)
→ server: GlobalState.currentTime = { hour = 14, minute = 30 }
→ Renewed-Weathersync reads GlobalState.currentTime on all clients
→ Game time updates for everyone6. Admin: Toggle Blackout
Admin clicks blackout toggle
→ nuiPost('weather:toggleBlackout')
→ client → server: TriggerServerEvent('wtf_group:server:weather:toggleBlackout')
→ server: GlobalState.blackOut = not GlobalState.blackOut
→ All clients: city lights turn on/off7. Admin: Toggle Freeze Time
Admin clicks freeze time toggle
→ nuiPost('weather:toggleFreezeTime')
→ client → server: TriggerServerEvent('wtf_group:server:weather:toggleFreezeTime')
→ server: GlobalState.freezeTime = not GlobalState.freezeTime
→ Game clock pauses/resumes for everyoneWeather Types
| ID | Icon | Label | Temp (°F) | Humidity | Particle |
|---|---|---|---|---|---|
EXTRASUNNY | ☀️ | Extra Sunny | 88 | 25% | sun |
CLEAR | ☀️ | Clear | 82 | 35% | sun |
CLEARING | ⛅ | Clearing | 72 | 50% | sun |
CLOUDS | ☁️ | Cloudy | 70 | 55% | cloud |
OVERCAST | ☁️ | Overcast | 64 | 65% | cloud |
NEUTRAL | ☁️ | Neutral | 68 | 50% | cloud |
RAIN | 🌧️ | Rainy | 62 | 80% | rain |
THUNDER | ⛈️ | Thunderstorm | 58 | 90% | thunder |
SNOW | ❄️ | Snow | 30 | 70% | snow |
SNOWLIGHT | ❄️ | Light Snow | 34 | 75% | snow |
BLIZZARD | ❄️ | Blizzard | 22 | 80% | blizzard |
XMAS | 🎄 | Christmas | 28 | 70% | snow |
FOGGY | 🌫️ | Foggy | 65 | 85% | fog |
SMOG | 🌫️ | Smog | 78 | 60% | fog |
Particle Types
Canvas-based animated particles rendered via WeatherParticles component:
| Type | Behavior | Max Particles |
|---|---|---|
sun | Concentric glowing rings with slow pulse | N/A (no particles) |
rain | Diagonal falling lines (blue-white) | 80 |
thunder | Rain + random white flash overlay | 80 |
snow | Rotating falling circles (white-blue) | 50 |
blizzard | Snow + horizontal drift + more particles | 120 |
fog | Radial gradient blobs drifting right | 8 |
cloud | Large radial gradient blobs drifting right | 8 |
Admin Panel
Two-column layout available to players with command.weather ACE permission:
Left Column: Time Control
- Digital clock display — current hour/minute in large font
- Hour/Minute inputs — numeric inputs (0–23, 0–59)
- Set Time button — applies the entered time
- Quick time buttons — Morning (9AM), Noon (12PM), Evening (6PM), Night (10PM)
Left Column: Controls
- Freeze Time toggle — pause/resume game clock
- Blackout toggle — disable/enable city lights
Right Column: Weather Control
- Weather type grid — 14 weather types in a 5-column grid, active type highlighted
- Current State panel — 6 stat cards:
- Weather (icon + label)
- Wind (mph)
- Time (digital)
- Time Left (minutes until weather change)
- Blackout (ON/OFF)
- Time Frozen (YES/NO)
Accessing the Admin Panel
- Player opens Weather app
- If ACE
command.weatheris granted, "Admin" button appears (top-right) - Click "Admin" → admin panel overlays the weather display
- Click "Weather View" to return to the normal display
NUI Callbacks
| Callback | Data | Description |
|---|---|---|
weather:getData | — | Get current weather from GlobalState (Renewed-Weathersync or fallback) |
weather:checkAdmin | — | Check if player has command.weather ACE permission |
weather:setType | { index, weatherType } | Set weather type at queue index |
weather:setDuration | { index, duration } | Set event duration at queue index |
weather:removeEvent | { index } | Remove weather event at queue index |
weather:toggleBlackout | — | Toggle city blackout on/off |
weather:toggleFreezeTime | — | Toggle time freeze on/off |
weather:setTime | { hour, minute } | Set in-game time |
weather:getQueue | — | Get current weather queue state (admin panel poll) |
Server Events
| Event | Parameters | ACE Required | Description |
|---|---|---|---|
wtf_group:server:weather:checkAdmin | — | No | Returns admin status to client |
wtf_group:server:weather:setType | index, weatherType | command.weather | Sets weather type via Renewed-Weathersync |
wtf_group:server:weather:setDuration | index, duration | command.weather | Sets event duration via Renewed-Weathersync |
wtf_group:server:weather:removeEvent | index | command.weather | Removes weather event |
wtf_group:server:weather:toggleBlackout | — | command.weather | Toggles GlobalState.blackOut |
wtf_group:server:weather:toggleFreezeTime | — | command.weather | Toggles GlobalState.freezeTime |
wtf_group:server:weather:setTime | hour, minute | command.weather | Sets GlobalState.currentTime |
Client Events (Received)
| Event | Data | Description |
|---|---|---|
wtf_group:client:weather:adminStatus | isAdmin | Admin check result |
wtf_group:client:weather:controlSuccess | action, result | Admin action succeeded |
wtf_group:client:weather:controlError | action, message | Admin action failed |
NUI Messages (Received by React)
| Action | Data | Description |
|---|---|---|
weather:adminStatus | { isAdmin } | Show/hide admin button |
weather:controlResult | { success, action, result/message } | Admin action feedback toast |
Renewed-Weathersync Integration
The weather app reads from Renewed-Weathersync's GlobalState bag replication:
| GlobalState Key | Type | Description |
|---|---|---|
weather | { weather, windSpeed, windDirection, hasSnow, time } | Current weather event |
currentTime | { hour, minute } | Current in-game time |
freezeTime | boolean | Whether time is frozen |
blackOut | boolean | Whether city lights are off |
timeScale | number | Time progression speed |
No server roundtrip needed — the client reads GlobalState directly (bag replication).
Admin Commands (via server)
Uses Renewed-Weathersync's built-in callbacks:
lib.callback.await('Renewed-Weathersync:server:setWeatherType', src, index, weatherType)lib.callback.await('Renewed-Weathersync:server:setEventTime', src, index, duration)TriggerServerEvent('Renewed-Weather:server:removeWeatherEvent', index)
Fallback Mode
If Renewed-Weathersync is not running:
GlobalState.weatherreturns nil- App falls back to static data:
{ weather: 'CLOUDS', hour: 12, minute: 0, ... } - Source badge shows "Static · Resource not found"
- Admin panel still works (controls GlobalState directly)
UI Features
- Animated Canvas Particles — 6 particle types with physics, fading, rotation
- Dynamic Background — gradient changes per weather type
- Large Weather Icon — with glow animation pulsing effect
- Digital Clock — JetBrains Mono font, large display
- Stat Cards — humidity, wind speed, wind label with glass morphism
- Source Badge — green "Live" or grey "Static" indicator
- Admin Button — visible only to ACE-authorized players
- Admin Feedback Toasts — success (green) / error (red) slide-in notifications
- Auto-Refresh — updates every 15 seconds
Time of Day Labels
| Hour Range | Label |
|---|---|
| 5–7 | Dawn |
| 8–11 | Morning |
| 12–16 | Afternoon |
| 17–19 | Dusk |
| 20–21 | Evening |
| 22–4 | Night |
Wind Labels
| Speed (game units) | MPH | Label |
|---|---|---|
| ≤ 0.1 | ≤ 2 | Calm |
| ≤ 0.5 | ≤ 11 | Light Breeze |
| ≤ 1.0 | ≤ 22 | Breezy |
| ≤ 3.0 | ≤ 67 | Windy |
| ≤ 8.0 | ≤ 179 | Strong Wind |
| > 8.0 | > 179 | Storm |
ACE Permission
To grant weather admin access, add to your server.cfg:
add_ace group.admin command.weather allowOr for specific principals:
add_ace principal.youradmin command.weather allowNotes
- Weather data is read from GlobalState (no server roundtrip) — updates are instant
- Admin actions modify GlobalState on the server — changes propagate to all clients
- The
weather:getQueuecallback polls GlobalState for current state (not the internal Renewed-Weathersync queue) - Particle canvas uses
requestAnimationFrame— pauses when tab is hidden - The app auto-refreshes every 15 seconds when open
- Temperature values are display-only (not synced from Renewed-Weathersync)
- Wind direction is tracked but not displayed in the current UI
- Admin panel overlays the weather view — click "Weather View" to return