dcs-retribution/game/theater/theaterloader.py
Dan Albert 0697a0dd5b
Fix file encoding for some loads.
We've actually been pretty good at getting this right in most of the
code base, but we've missed it in a few places. Python defaults to the
encoding of the current locale unless otherwise specified, and for a US
English Windows install that's cp1252, not UTF-8. I'm not brave enough
to change the locale at startup because I don't know how that might
affect CJK encoding users (or for that matter, non-Latin derived
alphabet UTF-8 variants).

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2786.
2023-05-07 19:31:20 +02:00

187 lines
6.5 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 icon_path(self) -> Path:
return self.descriptor_path.with_name("icon.gif")
@property
def menu_thumbnail_dcs_relative_path(self) -> Path:
with self.descriptor_path.open(encoding="utf-8") 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(encoding="utf-8") 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,
},
)