dcs-retribution/game/theater/theaterloader.py
SnappyComebacks 9fa04702af
Wind & Turbulence updates from Liberation
Tune turbulence values.

Modify the range of values used to choose a wind speed.

Wind speed at high elevation IRL can range from 20 to 160 knots around the globe.
You may see wind speed generated here up to 100+ knots, but generally around 40 or so.
IRL wind speed appears to depend on the latitude of the sun, not in this implementation.

Note increased wind speeds in the changelog.

Limit wind speed to 97 knots.

Made minor adjustments to wind speed calculation.

Calculate turbulance.

Turbulance is based off time of day, and day of year.
Each theatre may adjust their turbulance parameters.

Spell turbulence correctly.
2022-11-20 16:07:00 +01:00

183 lines
6.3 KiB
Python

from __future__ import annotations
import datetime
from collections.abc import Iterator
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import yaml
from dcs.terrain import (
Caucasus,
Falklands,
MarianaIslands,
Nevada,
Normandy,
PersianGulf,
Syria,
TheChannel,
)
from .conflicttheater import ConflictTheater
from .daytimemap import DaytimeMap
from .landmap import load_landmap
from .seasonalconditions import Season, SeasonalConditions, WeatherTypeChances
ALL_TERRAINS = [
Caucasus(),
Falklands(),
PersianGulf(),
Normandy(),
MarianaIslands(),
Nevada(),
TheChannel(),
Syria(),
]
TERRAINS_BY_NAME = {t.name: t for t in ALL_TERRAINS}
@dataclass(frozen=True)
class SeasonData:
average_temperature: float | None
average_pressure: float | None
weather: WeatherTypeChances
@staticmethod
def from_yaml(data: dict[str, Any]) -> SeasonData:
return SeasonData(
data.get("average_temperature"),
data.get("average_pressure"),
WeatherTypeChances(
data["weather"]["thunderstorm"],
data["weather"]["raining"],
data["weather"]["cloudy"],
data["weather"]["clear"],
),
)
@dataclass(frozen=True)
class TurbulenceData:
high_avg_yearly_turbulence_per_10cm: float | None
low_avg_yearly_turbulence_per_10cm: float | None
solar_noon_turbulence_per_10cm: float | None
midnight_turbulence_per_10cm: float | None
@staticmethod
def from_yaml(data: dict[str, Any]) -> TurbulenceData:
return TurbulenceData(
data.get("high_avg_yearly_turbulence_per_10cm"),
data.get("low_avg_yearly_turbulence_per_10cm"),
data.get("solar_noon_turbulence_per_10cm"),
data.get("midnight_turbulence_per_10cm"),
)
class TheaterLoader:
THEATER_RESOURCE_DIR = Path("resources/theaters")
def __init__(self, name: str) -> None:
self.name = name
self.descriptor_path = self.THEATER_RESOURCE_DIR / self.name / "info.yaml"
@classmethod
def each(cls) -> Iterator[ConflictTheater]:
for theater_dir in cls.THEATER_RESOURCE_DIR.iterdir():
yield TheaterLoader(theater_dir.name).load()
@property
def landmap_path(self) -> Path:
return self.descriptor_path.with_name("landmap.p")
@property
def menu_thumbnail_dcs_relative_path(self) -> Path:
with self.descriptor_path.open() as descriptor_file:
data = yaml.safe_load(descriptor_file)
name = data.get("pydcs_name", data["name"])
return Path("Mods/terrains") / name / "Theme/icon.png"
def load(self) -> ConflictTheater:
with self.descriptor_path.open() as descriptor_file:
data = yaml.safe_load(descriptor_file)
return ConflictTheater(
TERRAINS_BY_NAME[data.get("pydcs_name", data["name"])],
load_landmap(self.landmap_path),
datetime.timezone(datetime.timedelta(hours=data["timezone"])),
self._load_seasonal_conditions(data["climate"]),
self._load_daytime_map(data["daytime"]),
)
def _load_daytime_map(self, daytime_data: dict[str, list[int]]) -> DaytimeMap:
return DaytimeMap(
dawn=self._load_daytime_range(daytime_data["dawn"]),
day=self._load_daytime_range(daytime_data["day"]),
dusk=self._load_daytime_range(daytime_data["dusk"]),
night=self._load_daytime_range(daytime_data["night"]),
)
@staticmethod
def _load_daytime_range(
daytime_range: list[int],
) -> tuple[datetime.time, datetime.time]:
begin, end = daytime_range
return datetime.time(hour=begin), datetime.time(hour=end)
def _load_seasonal_conditions(
self, climate_data: dict[str, Any]
) -> SeasonalConditions:
winter = SeasonData.from_yaml(climate_data["seasons"]["winter"])
spring = SeasonData.from_yaml(climate_data["seasons"]["spring"])
summer = SeasonData.from_yaml(climate_data["seasons"]["summer"])
fall = SeasonData.from_yaml(climate_data["seasons"]["fall"])
turbulence = TurbulenceData.from_yaml(climate_data["turbulence"])
if summer.average_pressure is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a summer average pressure"
)
if summer.average_temperature is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a summer average temperature"
)
if winter.average_pressure is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a winter average pressure"
)
if winter.average_temperature is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a winter average temperature"
)
if turbulence.high_avg_yearly_turbulence_per_10cm is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a yearly average high turbulence"
)
if turbulence.low_avg_yearly_turbulence_per_10cm is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a yearly average low turbulence"
)
if turbulence.solar_noon_turbulence_per_10cm is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a solar noon turbulence"
)
if turbulence.midnight_turbulence_per_10cm is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a midnight turbulence"
)
return SeasonalConditions(
summer.average_pressure,
winter.average_pressure,
summer.average_temperature,
winter.average_temperature,
climate_data["day_night_temperature_difference"],
turbulence.high_avg_yearly_turbulence_per_10cm,
turbulence.low_avg_yearly_turbulence_per_10cm,
turbulence.solar_noon_turbulence_per_10cm,
turbulence.midnight_turbulence_per_10cm,
{
Season.Winter: winter.weather,
Season.Spring: spring.weather,
Season.Summer: summer.weather,
Season.Fall: fall.weather,
},
)