--[[ Some helping utilities for working with weather conditions. Examples: • Estimate rain intensity for a certain weather type: local weatherUtils = require('shared/sim/weather') weatherUtils.estimateRainIntensity(ac.WeatherType.Drizzle) • Estimate road temperature based on air temperature for a certain weather type: local weatherUtils = require('shared/sim/weather') weatherUtils.estimateRoadTemperature(ac.WeatherType.Windy, 22, 12 * 60 * 60) • Support weather type override of CSP Debug App: local weatherUtils = require('shared/sim/weather') weatherUtils.debugAware(conditions) ]] ---@diagnostic disable local rainIntensities = {} rainIntensities[ac.WeatherType.Clear] = 0 rainIntensities[ac.WeatherType.FewClouds] = 0 rainIntensities[ac.WeatherType.ScatteredClouds] = 0 rainIntensities[ac.WeatherType.BrokenClouds] = 0 rainIntensities[ac.WeatherType.OvercastClouds] = 0 rainIntensities[ac.WeatherType.Windy] = 0 rainIntensities[ac.WeatherType.Fog] = 0 rainIntensities[ac.WeatherType.Mist] = 0 rainIntensities[ac.WeatherType.Haze] = 0 rainIntensities[ac.WeatherType.Dust] = 0 rainIntensities[ac.WeatherType.Smoke] = 0 rainIntensities[ac.WeatherType.Sand] = 0 rainIntensities[ac.WeatherType.LightDrizzle] = 0.01 rainIntensities[ac.WeatherType.Drizzle] = 0.02 rainIntensities[ac.WeatherType.HeavyDrizzle] = 0.03 rainIntensities[ac.WeatherType.LightRain] = 0.05 rainIntensities[ac.WeatherType.Rain] = 0.1 rainIntensities[ac.WeatherType.HeavyRain] = 0.2 rainIntensities[ac.WeatherType.LightThunderstorm] = 0.3 rainIntensities[ac.WeatherType.Thunderstorm] = 0.5 rainIntensities[ac.WeatherType.HeavyThunderstorm] = 0.6 rainIntensities[ac.WeatherType.Squalls] = 0 rainIntensities[ac.WeatherType.Tornado] = 0.01 rainIntensities[ac.WeatherType.Hurricane] = 1.0 rainIntensities[ac.WeatherType.LightSnow] = 0 rainIntensities[ac.WeatherType.Snow] = 0 rainIntensities[ac.WeatherType.HeavySnow] = 0 rainIntensities[ac.WeatherType.LightSleet] = 0 rainIntensities[ac.WeatherType.Sleet] = 0.05 rainIntensities[ac.WeatherType.HeavySleet] = 0.1 rainIntensities[ac.WeatherType.Hail] = 0.2 -- Values are taken from Sol weathers local roadTemperatureCoefficients = {} roadTemperatureCoefficients[ac.WeatherType.Clear] = 1.0 roadTemperatureCoefficients[ac.WeatherType.FewClouds] = 1.0 roadTemperatureCoefficients[ac.WeatherType.ScatteredClouds] = 0.8 roadTemperatureCoefficients[ac.WeatherType.BrokenClouds] = 0.1 roadTemperatureCoefficients[ac.WeatherType.OvercastClouds] = 0.01 roadTemperatureCoefficients[ac.WeatherType.Windy] = 0.3 roadTemperatureCoefficients[ac.WeatherType.Fog] = -0.3 roadTemperatureCoefficients[ac.WeatherType.Mist] = -0.2 roadTemperatureCoefficients[ac.WeatherType.Haze] = 0.9 roadTemperatureCoefficients[ac.WeatherType.Dust] = 1.0 roadTemperatureCoefficients[ac.WeatherType.Smoke] = -0.2 roadTemperatureCoefficients[ac.WeatherType.Sand] = 1.0 roadTemperatureCoefficients[ac.WeatherType.LightDrizzle] = 0.1 roadTemperatureCoefficients[ac.WeatherType.Drizzle] = -0.1 roadTemperatureCoefficients[ac.WeatherType.HeavyDrizzle] = -0.3 roadTemperatureCoefficients[ac.WeatherType.LightRain] = 0.01 roadTemperatureCoefficients[ac.WeatherType.Rain] = -0.2 roadTemperatureCoefficients[ac.WeatherType.HeavyRain] = -0.5 roadTemperatureCoefficients[ac.WeatherType.LightThunderstorm] = 0.7 roadTemperatureCoefficients[ac.WeatherType.Thunderstorm] = 0.2 roadTemperatureCoefficients[ac.WeatherType.HeavyThunderstorm] = -0.2 roadTemperatureCoefficients[ac.WeatherType.Squalls] = -0.5 roadTemperatureCoefficients[ac.WeatherType.Tornado] = -0.3 roadTemperatureCoefficients[ac.WeatherType.Hurricane] = -0.7 roadTemperatureCoefficients[ac.WeatherType.LightSnow] = -0.7 roadTemperatureCoefficients[ac.WeatherType.Snow] = -0.8 roadTemperatureCoefficients[ac.WeatherType.HeavySnow] = -0.9 roadTemperatureCoefficients[ac.WeatherType.LightSleet] = -1.0 roadTemperatureCoefficients[ac.WeatherType.Sleet] = -1.0 roadTemperatureCoefficients[ac.WeatherType.HeavySleet] = -1.0 roadTemperatureCoefficients[ac.WeatherType.Hail] = -1.0 -- Actual library: local weatherUtils = {} ---Returns string describing given rain intensity. ---@param intensity number ---@return string function weatherUtils.rainDescription(intensity) if intensity < 0.001 then return "None" end if intensity < 0.02 then return "Drizzle" end if intensity < 0.07 then return "Light rain" end if intensity < 0.15 then return "Extended shower" end if intensity < 0.3 then return "Brief thundershower" end if intensity < 0.6 then return "Heavy downpour" end return "Severe storm" end ---Estimate rain intensity (in 0…1 range) for a certain weather type. ---@param weatherType ac.WeatherType ---@return number function weatherUtils.estimateRainIntensity(weatherType) return rainIntensities[weatherType] or 0 end ---Update rain intensity in conditions based on weather type and transition value. ---Set `setWaterLevel` to `true` to update puddles amount as well (however, results ---will be better if you were to accumulate it manually based on rain intensity). ---@param conditions ac.ConditionsSet @Conditions to update. ---@param setWaterLevel boolean? @Default value: `false`. function weatherUtils.setRainIntensity(conditions, setWaterLevel) local currentRain = weatherUtils.estimateRainIntensity(conditions.currentType) local upcomingRain = weatherUtils.estimateRainIntensity(conditions.upcomingType) conditions.rainIntensity = math.lerp(currentRain, upcomingRain, conditions.transition) conditions.rainWetness = conditions.rainIntensity if setWaterLevel then conditions.rainWater = math.sqrt(conditions.rainIntensity) end end ---Estimate road temperature based on weather type, ambient temperature and current time of day using ---the same formula Kunos uses in AC Server Manager. ---@param weatherType ac.WeatherType @Weather type. ---@param ambientTemperature number @Ambient temperature in °C. ---@param timeOfDaySeconds number @Time of day in seconds from midnight. ---@return number @Road temperature in °C. function weatherUtils.estimateRoadTemperature(weatherType, ambientTemperature, timeOfDaySeconds) -- Based on a formula used by original AC Server Manager local weatherCoefficient = roadTemperatureCoefficients[weatherType] or 1 local time = math.clamp((timeOfDaySeconds / 3600 - 7) / 24, 0, 0.5) * math.lerpInvSat(timeOfDaySeconds / 3600, 24, 18) return ambientTemperature * (1 + 5.33332 * weatherCoefficient * (1 - time) * (math.exp(-6 * time) * math.sin(6 * time) + 0.25) * math.sin(0.9 * time)) end ---Update road temperature in conditions based on weather type and transition value. ---@param conditions ac.ConditionsSet @Conditions to update. ---@param timeOfDaySeconds number? @Time of day in seconds from midnight (uses current time by default). function weatherUtils.setRoadTemperature(conditions, timeOfDaySeconds) timeOfDaySeconds = timeOfDaySeconds or ac.getDaySeconds() local current = weatherUtils.estimateRoadTemperature(conditions.currentType, conditions.temperatures.ambient, timeOfDaySeconds) local upcoming = weatherUtils.estimateRoadTemperature(conditions.upcomingType, conditions.temperatures.ambient, timeOfDaySeconds) conditions.temperatures.road = math.lerp(current, upcoming, conditions.transition) end local weatherDebug = nil local weatherDebugExt = nil ---Sync weather type with custom weather type selected in CSP Debug App (in case you want to ---support that dropdown list). ---@param conditions ac.ConditionsSet @Fields `currentType` and `upcomingType` might be updated. ---@param version integer? @Pass `1` to set other parameters, not just the weather type. Default value: `0`. ---@return boolean @Returns `true` if custom weather type is set. function weatherUtils.debugAware(conditions, version) if not weatherDebug then weatherDebug = ac.connect({ ac.StructItem.key('weatherFXDebugOverride'), weatherType = ac.StructItem.byte(), debugSupported = ac.StructItem.boolean() }) weatherDebugExt = ac.connect({ ac.StructItem.key('weatherFXDebugOverride.1'), windDirection = ac.StructItem.float(), windSpeedFrom = ac.StructItem.float(), windSpeedTo = ac.StructItem.float(), humidity = ac.StructItem.float(), pressure = ac.StructItem.float(), rainIntensity = ac.StructItem.float(), rainWetness = ac.StructItem.float(), rainWater = ac.StructItem.float(), }) weatherDebug.debugSupported = true weatherDebug.weatherType = 255 ac.onRelease(function () weatherDebug.debugSupported = false end) end weatherDebug.debugSupported = true if weatherDebug.weatherType == 255 then return false end conditions.currentType = weatherDebug.weatherType conditions.upcomingType = weatherDebug.weatherType if (version or 0) >= 1 then conditions.wind.direction = weatherDebugExt.windDirection conditions.wind.speedFrom = weatherDebugExt.windSpeedFrom conditions.wind.speedTo = weatherDebugExt.windSpeedTo conditions.humidity = weatherDebugExt.humidity conditions.pressure = weatherDebugExt.pressure conditions.rainIntensity = weatherDebugExt.rainIntensity conditions.rainWetness = weatherDebugExt.rainWetness conditions.rainWater = weatherDebugExt.rainWater end return true end return weatherUtils