dcs_liberation/game/weather/windspeedgenerators.py
Dan Albert a8b7aca4fb Make wind speed moddable.
These should probably be overridable per theater and per season, but
even with that we'll want some defaults.

https://github.com/dcs-liberation/dcs_liberation/issues/2862
2023-05-16 00:52:51 -07:00

96 lines
3.1 KiB
Python

from __future__ import annotations
import random
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any
from dcs.weather import Wind
from game.utils import Speed, knots, Heading
from .wind import WindConditions
@dataclass(frozen=True)
class WeibullWindSpeedParameters:
shape: float
scale: Speed
@staticmethod
def from_data(data: dict[str, Any]) -> WeibullWindSpeedParameters:
return WeibullWindSpeedParameters(
shape=data["shape"], scale=knots(data["scale_kts"])
)
class WindSpeedGenerator(ABC):
@abstractmethod
def random_wind(self) -> WindConditions:
...
@staticmethod
def from_data(data: dict[str, Any]) -> WindSpeedGenerator:
if len(data) != 1:
raise ValueError(
f"Wind speed dict has wrong number of keys ({len(data)}). Expected 1."
)
name = list(data.keys())[0]
match name:
case "weibull":
return WeibullWindSpeedGenerator.from_data(data["weibull"])
raise KeyError(f"Unknown wind speed generator type: {name}")
class WeibullWindSpeedGenerator(WindSpeedGenerator):
def __init__(
self,
at_msl: WeibullWindSpeedParameters,
at_2000m: WeibullWindSpeedParameters,
at_8000m: WeibullWindSpeedParameters,
) -> None:
self.at_msl = at_msl
self.at_2000m = at_2000m
self.at_8000m = at_8000m
def random_wind(self) -> WindConditions:
wind_direction = Heading.random()
wind_direction_2000m = wind_direction + Heading.random(-90, 90)
wind_direction_8000m = wind_direction + Heading.random(-90, 90)
# The first parameter is the scale. 63.2% of all results will fall below that
# value.
# https://www.itl.nist.gov/div898/handbook/eda/section3/weibplot.htm
msl = random.weibullvariate(
self.at_msl.scale.meters_per_second, self.at_msl.shape
)
at_2000m = random.weibullvariate(
msl + self.at_2000m.scale.meters_per_second, self.at_2000m.shape
)
at_8000m = random.weibullvariate(
at_2000m + self.at_8000m.scale.meters_per_second, self.at_8000m.shape
)
# DCS is limited to 97 knots wind speed.
max_supported_wind_speed = knots(97).meters_per_second
return WindConditions(
# Always some wind to make the smoke move a bit.
at_0m=Wind(wind_direction.degrees, max(1.0, msl)),
at_2000m=Wind(
wind_direction_2000m.degrees,
min(max_supported_wind_speed, at_2000m),
),
at_8000m=Wind(
wind_direction_8000m.degrees,
min(max_supported_wind_speed, at_8000m),
),
)
@staticmethod
def from_data(data: dict[str, Any]) -> WindSpeedGenerator:
return WeibullWindSpeedGenerator(
at_msl=WeibullWindSpeedParameters.from_data(data["at_msl"]),
at_2000m=WeibullWindSpeedParameters.from_data(data["at_2000m"]),
at_8000m=WeibullWindSpeedParameters.from_data(data["at_8000m"]),
)