diff --git a/changelog.md b/changelog.md index 8803863d..48d1be08 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ Saves from 2.4 are not compatible with 2.5. * **[Flight Planner]** Added AEW&C missions. (by siKruger) * **[Kneeboard]** Added dark kneeboard option (by GvonH) * **[Campaigns]** Multiple EWR sites may now be generated, and EWR sites may be generated outside bases (by SnappyComebacks) +* **[Mission Generation]** Cloudy and rainy (but not thunderstorm) weather will use the cloud presets from DCS 2.7. ## Fixes diff --git a/game/weather.py b/game/weather.py index c7d33c39..fc077634 100644 --- a/game/weather.py +++ b/game/weather.py @@ -3,11 +3,12 @@ from __future__ import annotations import datetime import logging import random -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum from typing import Optional, TYPE_CHECKING -from dcs.weather import Weather as PydcsWeather, Wind +from dcs.cloud_presets import Clouds as PydcsClouds +from dcs.weather import CloudPreset, Weather as PydcsWeather, Wind from game.settings import Settings from game.utils import Distance, meters @@ -36,6 +37,23 @@ class Clouds: density: int thickness: int precipitation: PydcsWeather.Preceptions + preset: Optional[CloudPreset] = field(default=None) + + @classmethod + def random_preset(cls, rain: bool) -> Clouds: + clouds = (p.value for p in PydcsClouds) + if rain: + presets = [p for p in clouds if "Rain" in p.name] + else: + presets = [p for p in clouds if "Rain" not in p.name] + preset = random.choice(presets) + return Clouds( + base=random.randint(preset.min_base, preset.max_base), + density=0, + thickness=0, + precipitation=PydcsWeather.Preceptions.None_, + preset=preset, + ) @dataclass(frozen=True) @@ -101,12 +119,11 @@ class ClearSkies(Weather): class Cloudy(Weather): def generate_clouds(self) -> Optional[Clouds]: - return Clouds( - base=self.random_cloud_base(), - density=random.randint(1, 8), - thickness=self.random_cloud_thickness(), - precipitation=PydcsWeather.Preceptions.None_, - ) + return Clouds.random_preset(rain=False) + + def generate_fog(self) -> Optional[Fog]: + # DCS 2.7 says to not use fog with the cloud presets. + return None def generate_wind(self) -> WindConditions: return self.random_wind(0, 4) @@ -114,12 +131,11 @@ class Cloudy(Weather): class Raining(Weather): def generate_clouds(self) -> Optional[Clouds]: - return Clouds( - base=self.random_cloud_base(), - density=random.randint(5, 8), - thickness=self.random_cloud_thickness(), - precipitation=PydcsWeather.Preceptions.Rain, - ) + return Clouds.random_preset(rain=True) + + def generate_fog(self) -> Optional[Fog]: + # DCS 2.7 says to not use fog with the cloud presets. + return None def generate_wind(self) -> WindConditions: return self.random_wind(0, 6) diff --git a/gen/environmentgen.py b/gen/environmentgen.py index 9d645a1d..5e393e04 100644 --- a/gen/environmentgen.py +++ b/gen/environmentgen.py @@ -17,6 +17,7 @@ class EnvironmentGenerator: self.mission.weather.clouds_thickness = clouds.thickness self.mission.weather.clouds_density = clouds.density self.mission.weather.clouds_iprecptns = clouds.precipitation + self.mission.weather.clouds_preset = clouds.preset def set_fog(self, fog: Optional[Fog]) -> None: if fog is None: diff --git a/qt_ui/widgets/QConditionsWidget.py b/qt_ui/widgets/QConditionsWidget.py index 661905f4..23abedb0 100644 --- a/qt_ui/widgets/QConditionsWidget.py +++ b/qt_ui/widgets/QConditionsWidget.py @@ -7,7 +7,7 @@ from PySide2.QtWidgets import ( QLabel, QVBoxLayout, ) -from dcs.weather import Weather as PydcsWeather +from dcs.weather import CloudPreset, Weather as PydcsWeather import qt_ui.uiconstants as CONST from game.utils import mps @@ -162,7 +162,7 @@ class QWeatherWidget(QGroupBox): self.turn = turn self.conditions = conditions - self.updateForecast() + self.update_forecast() self.updateWinds() def updateWinds(self): @@ -186,55 +186,76 @@ class QWeatherWidget(QGroupBox): self.windFL26SpeedLabel.setText(f"{int(windFL26Speed.knots)}kts") self.windFL26DirLabel.setText(f"{windFL26Dir}ยบ") - def updateForecast(self): + def update_forecast_from_preset(self, preset: CloudPreset) -> None: + self.forecastFog.setText("No fog") + if "Rain" in preset.name: + self.forecastRain.setText("Rain") + self.update_forecast_icons("rain") + else: + self.forecastRain.setText("No rain") + self.update_forecast_icons("partly-cloudy") + + # We get a description like the following for the cloud preset. + # + # 09 ##Two Layer Broken/Scattered \nMETAR:BKN 7.5/10 SCT 20/22 FEW41 + # + # The second line is probably interesting but doesn't fit into the widget + # currently, so for now just extract the first line. + self.forecastClouds.setText(preset.description.splitlines()[0].split("##")[1]) + + def update_forecast(self): """Updates the Forecast Text and icon with the current conditions wind info.""" - icon = [] + if ( + self.conditions.weather.clouds + and self.conditions.weather.clouds.preset is not None + ): + self.update_forecast_from_preset(self.conditions.weather.clouds.preset) + return + if self.conditions.weather.clouds is None: - cloudDensity = 0 + cloud_density = 0 precipitation = None else: - cloudDensity = self.conditions.weather.clouds.density + cloud_density = self.conditions.weather.clouds.density precipitation = self.conditions.weather.clouds.precipitation - fog = self.conditions.weather.fog or None - is_night = self.conditions.time_of_day == TimeOfDay.Night - time = "night" if is_night else "day" - - if cloudDensity <= 0: + if not cloud_density: self.forecastClouds.setText("Sunny") - icon = [time, "clear"] - - if cloudDensity > 0 and cloudDensity < 3: + weather_type = "clear" + elif cloud_density < 3: self.forecastClouds.setText("Partly Cloudy") - icon = [time, "partly-cloudy"] - - if cloudDensity >= 3 and cloudDensity < 5: + weather_type = "partly-cloudy" + elif cloud_density < 5: self.forecastClouds.setText("Mostly Cloudy") - icon = [time, "partly-cloudy"] - - if cloudDensity >= 5: + weather_type = "partly-cloudy" + else: self.forecastClouds.setText("Totally Cloudy") - icon = [time, "partly-cloudy"] + weather_type = "partly-cloudy" if precipitation == PydcsWeather.Preceptions.Rain: self.forecastRain.setText("Rain") - icon = [time, "rain"] - + weather_type = "rain" elif precipitation == PydcsWeather.Preceptions.Thunderstorm: self.forecastRain.setText("Thunderstorm") - icon = [time, "thunderstorm"] - + weather_type = "thunderstorm" else: - self.forecastRain.setText("No Rain") + self.forecastRain.setText("No rain") - if not fog: + if not self.conditions.weather.fog is not None: self.forecastFog.setText("No fog") else: - visibility = round(fog.visibility.nautical_miles, 1) + visibility = round(self.conditions.weather.fog.visibility.nautical_miles, 1) self.forecastFog.setText(f"Fog vis: {visibility}nm") - icon = [time, ("cloudy" if cloudDensity > 1 else None), "fog"] + if cloud_density > 1: + weather_type = "cloudy-fog" + else: + weather_type = "fog" - icon_key = "Weather_{}".format("-".join(filter(None.__ne__, icon))) + self.update_forecast_icons(weather_type) + + def update_forecast_icons(self, weather_type: str) -> None: + time = "night" if self.conditions.time_of_day == TimeOfDay.Night else "day" + icon_key = f"Weather_{time}-{weather_type}" icon = CONST.ICONS.get(icon_key) or CONST.ICONS["Weather_night-partly-cloudy"] self.weather_icon.setPixmap(icon)