local PUSH_MS = 15000 local appOpen = false local function cfg() local weather = Config and Config.Weather or nil if not weather or weather.Enable == false then return nil end return weather end local function zoneName(coords) local ok, label = pcall(function() return GetLabelText(GetNameOfZone(coords.x, coords.y, coords.z)) end) if ok and label and label ~= "NULL" and label ~= "" then return label end return nil end local function isNight(c, hour) local from = tonumber(c.NightStart) or 21 local to = tonumber(c.NightEnd) or 6 if from == to then return false end if from < to then return hour >= from and hour < to end return hour >= from or hour < to end local function nameOf(c, value) if type(value) == "string" then return value end for key in pairs(c.Temps or {}) do if GetHashKey(key) == value then return key end end return "NEUTRAL" end local function baseTemp(c, type_) return tonumber((c.Temps or {})[type_]) or tonumber((c.Temps or {})["NEUTRAL"]) or 22 end --- Celsius in, whatever the server asked for out. local function toUnit(c, celsius) if c.Celsius == false then return celsius * 9 / 5 + 32 end return celsius end local function rounded(value) return math.floor(value + 0.5) end --------------------------------------------------------------------------------- -- Invented forecast --------------------------------------------------------------------------------- local function rng(seed) local state = seed % 2147483647 if state <= 0 then state = state + 2147483646 end return function(n) state = (state * 16807) % 2147483647 return (state % n) + 1 end end local FORECAST_POOL = { "EXTRASUNNY", "CLEAR", "CLEAR", "CLOUDS", "CLOUDS", "CLEARING", "OVERCAST", "SMOG", "FOGGY", "RAIN", "THUNDER" } --- @param c table Config.Weather --- @param days number --- @param seed number stable per in-game day local function dailyForecast(c, days, seed) if days <= 0 then return {} end local next_ = rng(seed) local out = {} for i = 1, days do local type_ = FORECAST_POOL[next_(#FORECAST_POOL)] local base = baseTemp(c, type_) local drift = next_(7) - 4 local high = base + drift local low = high + (tonumber(c.NightOffset) or -6) - next_(3) out[#out + 1] = { offset = i - 1, weather = type_, high = rounded(toUnit(c, high)), low = rounded(toUnit(c, low)), } end return out end local function hourlyForecast(c, hours, seed, hour, current, upcoming) if hours <= 0 then return {} end local next_ = rng(seed + 7919) local out = {} for i = 1, hours do local at = (hour + i - 1) % 24 local type_ if i == 1 then type_ = current elseif i == 2 then type_ = upcoming else type_ = FORECAST_POOL[next_(#FORECAST_POOL)] end local celsius = baseTemp(c, type_) if isNight(c, at) then celsius = celsius + (tonumber(c.NightOffset) or 0) end out[#out + 1] = { hour = at, weather = type_, temperature = rounded(toUnit(c, celsius)), real = i <= 2, } end return out end --------------------------------------------------------------------------------- --- @return table|nil snapshot for the UI function WeatherSnapshot() local c = cfg() if not c then return nil end local current = nameOf(c, GetPrevWeatherTypeHashName()) local upcoming = nameOf(c, GetNextWeatherTypeHashName()) local hour = GetClockHours() local minute = GetClockMinutes() local night = isNight(c, hour) local celsius = baseTemp(c, current) if night then celsius = celsius + (tonumber(c.NightOffset) or 0) end local seed = (tonumber(c.ForecastSeed) or 1) + GetClockDayOfMonth() * 31 + (GetClockMonth() + 1) * 372 + GetClockYear() * 4464 local coords = GetEntityCoords(PlayerPedId()) return { weather = current, next = upcoming, temperature = rounded(toUnit(c, celsius)), unit = c.Celsius == false and "F" or "C", hour = hour, minute = minute, night = night, zone = zoneName(coords), raining = GetRainLevel() > 0.0, rainLevel = math.floor(GetRainLevel() * 100 + 0.5) / 100, windSpeed = math.floor(GetWindSpeed() * 10 + 0.5) / 10, sunrise = tonumber(c.NightEnd) or 6, sunset = tonumber(c.NightStart) or 21, hourly = hourlyForecast(c, tonumber(c.HourlyHours) or 12, seed, hour, current, upcoming), daily = dailyForecast(c, tonumber(c.ForecastDays) or 10, seed), } end local function push() local snapshot = WeatherSnapshot() if not snapshot then return end SendNUIMessage({ action = "weatherData", weather = snapshot }) end function GetWeatherConfig() local c = cfg() if not c then return { enabled = false } end return { enabled = true, celsius = c.Celsius ~= false, forecastDays = tonumber(c.ForecastDays) or 10, hourlyHours = tonumber(c.HourlyHours) or 12, } end exports("GetWeatherConfig", GetWeatherConfig) RegisterNUICallback("weatherRequest", function(_, cb) appOpen = true push() cb("ok") end) RegisterNUICallback("weatherClosed", function(_, cb) appOpen = false cb("ok") end) CreateThread(function() if not cfg() then return end while true do Wait(PUSH_MS) if appOpen then push() end end end) exports("GetWeather", WeatherSnapshot)