mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Weather and exact time of day information is helpful during mission planning, so generate it at the start of the turn rather than at takeoff time. Another advantage aside from planning is that we can now use the wind information to set carrier headings and takeoff runways appropriately.
184 lines
5.0 KiB
Python
184 lines
5.0 KiB
Python
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, min(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()
|