Make theater properties moddable.

Only the Caucasus has been migrated so far. Will follow up with the
others, and also will be adding beacon/airport data to this.
This commit is contained in:
Dan Albert 2022-09-07 15:58:46 -07:00 committed by Raffson
parent 44166203ab
commit 401a0ae557
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
6 changed files with 207 additions and 35 deletions

View File

@ -10,9 +10,9 @@ from typing import Any, Dict, Tuple
import yaml
from packaging.version import Version
from game import persistency
from game.profiling import logged_duration
from game.theater import (
CaucasusTheater,
ConflictTheater,
FalklandsTheater,
MarianaIslandsTheater,
@ -23,10 +23,10 @@ from game.theater import (
TheChannelTheater,
)
from game.theater.iadsnetwork.iadsnetwork import IadsNetwork
from game.theater.theaterloader import TheaterLoader
from game.version import CAMPAIGN_FORMAT_VERSION
from .campaignairwingconfig import CampaignAirWingConfig
from .mizcampaignloader import MizCampaignLoader
from .. import persistency
PERF_FRIENDLY = 0
PERF_MEDIUM = 1
@ -116,7 +116,6 @@ class Campaign:
def load_theater(self, advanced_iads: bool) -> ConflictTheater:
theaters = {
"Caucasus": CaucasusTheater,
"Nevada": NevadaTheater,
"Persian Gulf": PersianGulfTheater,
"Normandy": NormandyTheater,
@ -125,8 +124,11 @@ class Campaign:
"MarianaIslands": MarianaIslandsTheater,
"Falklands": FalklandsTheater,
}
theater = theaters[self.data["theater"]]
t = theater()
try:
theater = theaters[self.data["theater"]]
t = theater()
except KeyError:
t = TheaterLoader(self.data["theater"].lower()).load()
try:
miz = self.data["miz"]

View File

@ -2,14 +2,12 @@ from __future__ import annotations
import datetime
import math
from dataclasses import dataclass
from pathlib import Path
from typing import Iterator, List, Optional, TYPE_CHECKING, Tuple
from uuid import UUID
from dcs.mapping import Point
from dcs.terrain import (
caucasus,
falklands,
marianaislands,
nevada,
@ -33,12 +31,6 @@ if TYPE_CHECKING:
from .theatergroundobject import TheaterGroundObject
@dataclass
class ReferencePoint:
world_coordinates: Point
image_coordinates: Point
class ConflictTheater:
terrain: Terrain
@ -254,28 +246,6 @@ class ConflictTheater:
return Heading.from_degrees(position.heading_between_point(conflict_center))
class CaucasusTheater(ConflictTheater):
terrain = caucasus.Caucasus()
landmap = load_landmap(Path("resources/caulandmap.p"))
daytime_map = DaytimeMap(
dawn=(datetime.time(hour=6), datetime.time(hour=9)),
day=(datetime.time(hour=9), datetime.time(hour=18)),
dusk=(datetime.time(hour=18), datetime.time(hour=20)),
night=(datetime.time(hour=0), datetime.time(hour=5)),
)
@property
def timezone(self) -> datetime.timezone:
return datetime.timezone(datetime.timedelta(hours=4))
@property
def seasonal_conditions(self) -> SeasonalConditions:
from .seasonalconditions.caucasus import CONDITIONS
return CONDITIONS
class PersianGulfTheater(ConflictTheater):
terrain = persiangulf.PersianGulf()
landmap = load_landmap(Path("resources/gulflandmap.p"))

View File

@ -0,0 +1,126 @@
from __future__ import annotations
import datetime
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
from .yamltheater import YamlTheater
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"],
),
)
class TheaterLoader:
def __init__(self, name: str) -> None:
self.name = name
self.descriptor_path = Path("resources/theaters") / self.name / "info.yaml"
def load(self) -> ConflictTheater:
with self.descriptor_path.open() as descriptor_file:
data = yaml.safe_load(descriptor_file)
return YamlTheater(
TERRAINS_BY_NAME[data["name"]],
load_landmap(self.descriptor_path.with_name("landmap.p")),
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"])
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"
)
return SeasonalConditions(
summer.average_pressure,
winter.average_pressure,
summer.average_temperature,
winter.average_temperature,
climate_data["day_night_temperature_difference"],
{
Season.Winter: winter.weather,
Season.Spring: spring.weather,
Season.Summer: summer.weather,
Season.Fall: fall.weather,
},
)

View File

@ -0,0 +1,35 @@
from __future__ import annotations
from datetime import timezone
from dcs.terrain import Terrain
from .conflicttheater import ConflictTheater
from .daytimemap import DaytimeMap
from .landmap import Landmap
from .seasonalconditions import SeasonalConditions
class YamlTheater(ConflictTheater):
def __init__(
self,
terrain: Terrain,
landmap: Landmap | None,
time_zone: timezone,
seasonal_conditions: SeasonalConditions,
daytime_map: DaytimeMap,
) -> None:
super().__init__()
self.terrain = terrain
self.landmap = landmap
self._timezone = time_zone
self._seasonal_conditions = seasonal_conditions
self.daytime_map = daytime_map
@property
def timezone(self) -> timezone:
return self._timezone
@property
def seasonal_conditions(self) -> SeasonalConditions:
return self._seasonal_conditions

View File

@ -0,0 +1,39 @@
---
name: Caucasus
timezone: +4
daytime:
dawn: [6, 9]
day: [9, 18]
dusk: [18, 20]
night: [0, 5]
climate:
day_night_temperature_difference: 6.0
seasons:
winter:
average_pressure: 29.72 # TODO: Find real-world data
average_temperature: 3.0
weather:
thunderstorm: 1
raining: 20
cloudy: 60
clear: 20
spring:
weather:
thunderstorm: 1
raining: 20
cloudy: 40
clear: 40
summer:
average_pressure: 30.02 # TODO: Find real-world data
average_temperature: 22.5
weather:
thunderstorm: 1
raining: 10
cloudy: 35
clear: 55
fall:
weather:
thunderstorm: 1
raining: 30
cloudy: 50
clear: 20