Move and split up weather.py.

This is getting out of hand, and I'm about to make it worse.
This commit is contained in:
Dan Albert 2023-05-15 23:34:21 -07:00 committed by Raffson
parent 080d346517
commit 8ed843a9cf
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
13 changed files with 183 additions and 155 deletions

View File

@ -39,7 +39,7 @@ from .theater.theatergroundobject import (
)
from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from .timeofday import TimeOfDay
from .weather import Conditions
from .weather.conditions import Conditions
if TYPE_CHECKING:
from .ato.airtaaskingorder import AirTaskingOrder

View File

@ -3,7 +3,11 @@ from typing import Optional
from dcs.mission import Mission
from game.weather import Clouds, Fog, Conditions, WindConditions, AtmosphericConditions
from game.weather.atmosphericconditions import AtmosphericConditions
from game.weather.clouds import Clouds
from game.weather.conditions import Conditions
from game.weather.fog import Fog
from game.weather.wind import WindConditions
class EnvironmentGenerator:

View File

@ -43,8 +43,8 @@ from game.radio.radios import RadioFrequency
from game.runways import RunwayData
from game.theater import TheaterGroundObject, TheaterUnit
from game.theater.bullseye import Bullseye
from game.utils import Distance, UnitSystem, meters, mps, pounds, knots, feet
from game.weather import Weather
from game.utils import Distance, UnitSystem, meters, mps, pounds
from game.weather.weather import Weather
from .aircraft.flightdata import FlightData
from .missiondata import AwacsInfo, TankerInfo
from .briefinggenerator import CommInfo, JtacInfo, MissionInfoGenerator

View File

@ -11,7 +11,7 @@ from game.dcs.beacons import BeaconType, Beacons
from game.radio.radios import RadioFrequency
from game.radio.tacan import TacanChannel
from game.utils import Heading
from game.weather import Conditions
from game.weather.conditions import Conditions
if TYPE_CHECKING:
from game.theater import ConflictTheater

View File

@ -81,7 +81,7 @@ from ..radio.Link4Container import Link4Container
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
from ..radio.TacanContainer import TacanContainer
from ..utils import nautical_miles
from ..weather import Conditions
from ..weather.conditions import Conditions
if TYPE_CHECKING:
from game import Game

0
game/weather/__init__.py Normal file
View File

View File

@ -0,0 +1,17 @@
from __future__ import annotations
from dataclasses import dataclass
from game.utils import Pressure
@dataclass(frozen=True)
class AtmosphericConditions:
#: Pressure at sea level.
qnh: Pressure
#: Temperature at sea level in Celcius.
temperature_celsius: float
#: Turbulence per 10 cm.
turbulence_per_10cm: float

33
game/weather/clouds.py Normal file
View File

@ -0,0 +1,33 @@
from __future__ import annotations
import random
from dataclasses import dataclass, field
from typing import Optional
from dcs.cloud_presets import Clouds as PydcsClouds
from dcs.weather import Weather as PydcsWeather, CloudPreset
@dataclass(frozen=True)
class Clouds:
base: int
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,
)

View File

@ -0,0 +1,93 @@
from __future__ import annotations
import datetime
import logging
import random
from dataclasses import dataclass
from game.settings import Settings
from game.theater import ConflictTheater, DaytimeMap, SeasonalConditions
from game.theater.seasonalconditions import determine_season
from game.timeofday import TimeOfDay
from game.weather.weather import Weather, Thunderstorm, Raining, Cloudy, ClearSkies
@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,
forced_time: datetime.time | None = None,
) -> Conditions:
# The time might be forced by the campaign for the first turn.
if forced_time is not None:
_start_time = datetime.datetime.combine(day, forced_time)
else:
_start_time = cls.generate_start_time(
theater, day, time_of_day, settings.night_disabled
)
return cls(
time_of_day=time_of_day,
start_time=_start_time,
weather=cls.generate_weather(theater.seasonal_conditions, day, time_of_day),
)
@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 = DaytimeMap(
dawn=(datetime.time(hour=8), datetime.time(hour=9)),
day=(datetime.time(hour=10), datetime.time(hour=12)),
dusk=(datetime.time(hour=12), datetime.time(hour=14)),
night=(datetime.time(hour=14), datetime.time(hour=17)),
).range_of(time_of_day)
else:
time_range = theater.daytime_map.range_of(time_of_day)
# Starting missions on the hour is a nice gameplay property, so keep the random
# time constrained to that. DaytimeMap enforces that we have only whole hour
# ranges for now, so we don't need to worry about accidentally changing the time
# of day by truncating sub-hours.
time = datetime.time(
hour=random.randint(time_range[0].hour, time_range[1].hour)
)
return datetime.datetime.combine(day, time)
@classmethod
def generate_weather(
cls,
seasonal_conditions: SeasonalConditions,
day: datetime.date,
time_of_day: TimeOfDay,
) -> Weather:
season = determine_season(day)
logging.debug("Weather: Season {}".format(season))
weather_chances = seasonal_conditions.weather_type_chances[season]
chances = {
Thunderstorm: weather_chances.thunderstorm,
Raining: weather_chances.raining,
Cloudy: weather_chances.cloudy,
ClearSkies: weather_chances.clear_skies,
}
logging.debug("Weather: Chances {}".format(weather_chances))
weather_type = random.choices(
list(chances.keys()), weights=list(chances.values())
)[0]
logging.debug("Weather: Type {}".format(weather_type))
return weather_type(seasonal_conditions, day, time_of_day)

11
game/weather/fog.py Normal file
View File

@ -0,0 +1,11 @@
from __future__ import annotations
from dataclasses import dataclass
from game.utils import Distance
@dataclass(frozen=True)
class Fog:
visibility: Distance
thickness: int

View File

@ -4,17 +4,14 @@ import datetime
import logging
import math
import random
from dataclasses import dataclass, field
from dataclasses import dataclass
from enum import Enum
from typing import Optional, TYPE_CHECKING
from dcs.cloud_presets import Clouds as PydcsClouds
from dcs.weather import CloudPreset, Weather as PydcsWeather, Wind
from dcs.weather import Weather as PydcsWeather, Wind
from game.theater.seasonalconditions import determine_season
from game.timeofday import TimeOfDay
from game.utils import (
Distance,
Heading,
Pressure,
inches_hg,
@ -23,10 +20,12 @@ from game.utils import (
meters,
Speed,
)
from game.weather.atmosphericconditions import AtmosphericConditions
from game.weather.clouds import Clouds
from game.weather.fog import Fog
from game.weather.wind import WindConditions
if TYPE_CHECKING:
from game.settings import Settings
from game.theater import ConflictTheater
from game.theater.seasonalconditions import SeasonalConditions
@ -36,62 +35,12 @@ class NightMissions(Enum):
OnlyNight = "nightmissions_onlynight"
@dataclass(frozen=True)
class AtmosphericConditions:
#: Pressure at sea level.
qnh: Pressure
#: Temperature at sea level in Celcius.
temperature_celsius: float
#: Turbulence per 10 cm.
turbulence_per_10cm: float
@dataclass(frozen=True)
class WindConditions:
at_0m: Wind
at_2000m: Wind
at_8000m: Wind
@dataclass(frozen=True)
class WeibullWindSpeedParameters:
shape: float
scale: Speed
@dataclass(frozen=True)
class Clouds:
base: int
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)
class Fog:
visibility: Distance
thickness: int
class Weather:
def __init__(
self,
@ -417,94 +366,3 @@ class Thunderstorm(Weather):
WeibullWindSpeedParameters(6.2, knots(20)),
WeibullWindSpeedParameters(6.4, knots(20)),
)
@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,
forced_time: datetime.time | None = None,
) -> Conditions:
# The time might be forced by the campaign for the first turn.
if forced_time is not None:
_start_time = datetime.datetime.combine(day, forced_time)
else:
_start_time = cls.generate_start_time(
theater, day, time_of_day, settings.night_day_missions
)
return cls(
time_of_day=time_of_day,
start_time=_start_time,
weather=cls.generate_weather(theater.seasonal_conditions, day, time_of_day),
)
@classmethod
def generate_start_time(
cls,
theater: ConflictTheater,
day: datetime.date,
time_of_day: TimeOfDay,
night_day_missions: NightMissions,
) -> datetime.datetime:
from game.theater import DaytimeMap
if night_day_missions == NightMissions.OnlyDay:
logging.info("Skip Night mission due to user settings")
time_range = DaytimeMap(
dawn=(datetime.time(hour=8), datetime.time(hour=9)),
day=(datetime.time(hour=10), datetime.time(hour=12)),
dusk=(datetime.time(hour=12), datetime.time(hour=14)),
night=(datetime.time(hour=14), datetime.time(hour=17)),
).range_of(time_of_day)
elif night_day_missions == NightMissions.OnlyNight:
logging.info("Skip Day mission due to user settings")
time_range = DaytimeMap(
dawn=(datetime.time(hour=0), datetime.time(hour=3)),
day=(datetime.time(hour=3), datetime.time(hour=6)),
dusk=(datetime.time(hour=21), datetime.time(hour=22)),
night=(datetime.time(hour=22), datetime.time(hour=23)),
).range_of(time_of_day)
else:
time_range = theater.daytime_map.range_of(time_of_day)
# Starting missions on the hour is a nice gameplay property, so keep the random
# time constrained to that. DaytimeMap enforces that we have only whole hour
# ranges for now, so we don't need to worry about accidentally changing the time
# of day by truncating sub-hours.
time = datetime.time(
hour=random.randint(time_range[0].hour, time_range[1].hour)
)
return datetime.datetime.combine(day, time)
@classmethod
def generate_weather(
cls,
seasonal_conditions: SeasonalConditions,
day: datetime.date,
time_of_day: TimeOfDay,
) -> Weather:
season = determine_season(day)
logging.debug("Weather: Season {}".format(season))
weather_chances = seasonal_conditions.weather_type_chances[season]
chances = {
Thunderstorm: weather_chances.thunderstorm,
Raining: weather_chances.raining,
Cloudy: weather_chances.cloudy,
ClearSkies: weather_chances.clear_skies,
}
logging.debug("Weather: Chances {}".format(weather_chances))
weather_type = random.choices(
list(chances.keys()), weights=list(chances.values())
)[0]
logging.debug("Weather: Type {}".format(weather_type))
return weather_type(seasonal_conditions, day, time_of_day)

12
game/weather/wind.py Normal file
View File

@ -0,0 +1,12 @@
from __future__ import annotations
from dataclasses import dataclass
from dcs.weather import Wind
@dataclass(frozen=True)
class WindConditions:
at_0m: Wind
at_2000m: Wind
at_8000m: Wind

View File

@ -15,7 +15,7 @@ import qt_ui.uiconstants as CONST
from game.sim.gameupdateevents import GameUpdateEvents
from game.timeofday import TimeOfDay
from game.utils import mps
from game.weather import Conditions
from game.weather.conditions import Conditions
from qt_ui.simcontroller import SimController