from __future__ import annotations import datetime import logging import random from dataclasses import dataclass from enum import Enum from typing import Optional from dcs.weather import Weather as PydcsWeather, Wind from game.settings import Settings from 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()