from __future__ import annotations import datetime import logging import random from dataclasses import dataclass from enum import Enum from typing import Optional, TYPE_CHECKING from dcs.weather import Weather as PydcsWeather, Wind from game.settings import Settings if TYPE_CHECKING: from game.theater import ConflictTheater class TimeOfDay(Enum): Dawn = "dawn" Day = "day" Dusk = "dusk" Night = "night" @dataclass(frozen=True) class WindConditions: at_0m: Wind at_2000m: Wind at_8000m: Wind @dataclass(frozen=True) class Clouds: base: int density: int thickness: int precipitation: PydcsWeather.Preceptions @dataclass(frozen=True) class Fog: visibility: int thickness: int class Weather: def __init__(self) -> None: self.clouds = self.generate_clouds() self.fog = self.generate_fog() self.wind = self.generate_wind() def generate_clouds(self) -> Optional[Clouds]: raise NotImplementedError def generate_fog(self) -> Optional[Fog]: if random.randrange(5) != 0: return None return Fog( visibility=random.randint(2500, 5000), thickness=random.randint(100, 500) ) def generate_wind(self) -> WindConditions: raise NotImplementedError @staticmethod def random_wind(minimum: int, maximum) -> WindConditions: wind_direction = random.randint(0, 360) at_0m_factor = 1 at_2000m_factor = 2 at_8000m_factor = 3 base_wind = random.randint(minimum, maximum) return WindConditions( # Always some wind to make the smoke move a bit. at_0m=Wind(wind_direction, max(1, base_wind * at_0m_factor)), at_2000m=Wind(wind_direction, base_wind * at_2000m_factor), at_8000m=Wind(wind_direction, base_wind * at_8000m_factor) ) @staticmethod def random_cloud_base() -> int: return random.randint(2000, 3000) @staticmethod def random_cloud_thickness() -> int: return random.randint(100, 400) class ClearSkies(Weather): def generate_clouds(self) -> Optional[Clouds]: return None def generate_fog(self) -> Optional[Fog]: return None def generate_wind(self) -> WindConditions: return self.random_wind(0, 0) 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_ ) def generate_wind(self) -> WindConditions: return self.random_wind(0, 4) 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 ) def generate_wind(self) -> WindConditions: return self.random_wind(0, 6) class Thunderstorm(Weather): def generate_clouds(self) -> Optional[Clouds]: return Clouds( base=self.random_cloud_base(), density=random.randint(9, 10), thickness=self.random_cloud_thickness(), precipitation=PydcsWeather.Preceptions.Thunderstorm ) def generate_wind(self) -> WindConditions: return self.random_wind(0, 8) @dataclass class Conditions: time_of_day: TimeOfDay start_time: datetime.datetime weather: Weather @classmethod def generate(cls, theater: ConflictTheater, day: datetime.date, time_of_day: TimeOfDay, settings: Settings) -> Conditions: return cls( time_of_day=time_of_day, start_time=cls.generate_start_time( theater, day, time_of_day, settings.night_disabled ), weather=cls.generate_weather() ) @classmethod def generate_start_time(cls, theater: ConflictTheater, day: datetime.date, time_of_day: TimeOfDay, night_disabled: bool) -> datetime.datetime: if night_disabled: logging.info("Skip Night mission due to user settings") time_range = { TimeOfDay.Dawn: (8, 9), TimeOfDay.Day: (10, 12), TimeOfDay.Dusk: (12, 14), TimeOfDay.Night: (14, 17), }[time_of_day] else: time_range = theater.daytime_map[time_of_day.value] time = datetime.time(hour=random.randint(*time_range)) return datetime.datetime.combine(day, time) @classmethod def generate_weather(cls) -> Weather: chances = { Thunderstorm: 1, Raining: 20, Cloudy: 60, ClearSkies: 20, } weather_type = random.choices(list(chances.keys()), weights=list(chances.values()))[0] return weather_type()