mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
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.
This commit is contained in:
@@ -17,6 +17,7 @@ class EnvironmentGenerator:
|
||||
def set_atmospheric(self, atmospheric: AtmosphericConditions) -> None:
|
||||
self.mission.weather.qnh = atmospheric.qnh.mm_hg
|
||||
self.mission.weather.season_temperature = atmospheric.temperature_celsius
|
||||
self.mission.weather.turbulence_at_ground = int(atmospheric.turbulence_per_10cm)
|
||||
|
||||
def set_clouds(self, clouds: Optional[Clouds]) -> None:
|
||||
if clouds is None:
|
||||
|
||||
@@ -400,6 +400,9 @@ class BriefingPage(KneeboardPage):
|
||||
f"Temperature: {round(self.weather.atmospheric.temperature_celsius)} °C at sea level"
|
||||
)
|
||||
writer.text(f"QNH: {qnh_in_hg} inHg / {qnh_mm_hg} mmHg / {qnh_hpa} hPa")
|
||||
writer.text(
|
||||
f"Turbulence: {round(self.weather.atmospheric.turbulence_per_10cm)} per 10cm at ground level."
|
||||
)
|
||||
|
||||
fl = self.flight
|
||||
|
||||
|
||||
@@ -45,4 +45,9 @@ class SeasonalConditions:
|
||||
winter_avg_temperature: float
|
||||
temperature_day_night_difference: float
|
||||
|
||||
high_avg_yearly_turbulence_per_10cm: float
|
||||
low_avg_yearly_turbulence_per_10cm: float
|
||||
solar_noon_turbulence_per_10cm: float
|
||||
midnight_turbulence_per_10cm: float
|
||||
|
||||
weather_type_chances: dict[Season, WeatherTypeChances]
|
||||
|
||||
@@ -57,6 +57,23 @@ class SeasonData:
|
||||
)
|
||||
|
||||
|
||||
@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")
|
||||
|
||||
@@ -113,6 +130,7 @@ class TheaterLoader:
|
||||
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"
|
||||
@@ -129,12 +147,32 @@ class TheaterLoader:
|
||||
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,
|
||||
|
||||
137
game/weather.py
137
game/weather.py
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import math
|
||||
import random
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
@@ -11,7 +12,15 @@ from dcs.weather import CloudPreset, 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, interpolate, meters
|
||||
from game.utils import (
|
||||
Distance,
|
||||
Heading,
|
||||
Pressure,
|
||||
inches_hg,
|
||||
interpolate,
|
||||
knots,
|
||||
meters,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.settings import Settings
|
||||
@@ -27,6 +36,9 @@ class AtmosphericConditions:
|
||||
#: Temperature at sea level in Celcius.
|
||||
temperature_celsius: float
|
||||
|
||||
#: Turbulence per 10 cm.
|
||||
turbulence_per_10cm: float
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WindConditions:
|
||||
@@ -99,18 +111,38 @@ class Weather:
|
||||
day,
|
||||
)
|
||||
|
||||
seasonal_turbulence = self.interpolate_seasonal_turbulence(
|
||||
seasonal_conditions.high_avg_yearly_turbulence_per_10cm,
|
||||
seasonal_conditions.low_avg_yearly_turbulence_per_10cm,
|
||||
day,
|
||||
)
|
||||
|
||||
day_turbulence = seasonal_conditions.solar_noon_turbulence_per_10cm
|
||||
night_turbulence = seasonal_conditions.midnight_turbulence_per_10cm
|
||||
time_of_day_turbulence = self.interpolate_solar_activity(
|
||||
time_of_day, day_turbulence, night_turbulence
|
||||
)
|
||||
|
||||
random_turbulence = random.normalvariate(mu=0, sigma=0.5)
|
||||
|
||||
turbulence = abs(
|
||||
seasonal_turbulence + time_of_day_turbulence + random_turbulence
|
||||
)
|
||||
|
||||
if time_of_day == TimeOfDay.Day:
|
||||
temperature += seasonal_conditions.temperature_day_night_difference / 2
|
||||
if time_of_day == TimeOfDay.Night:
|
||||
temperature -= seasonal_conditions.temperature_day_night_difference / 2
|
||||
pressure += self.pressure_adjustment
|
||||
temperature += self.temperature_adjustment
|
||||
turbulence += self.turbulence_adjustment
|
||||
logging.debug(
|
||||
"Weather: Before random: temp {} press {}".format(temperature, pressure)
|
||||
)
|
||||
conditions = AtmosphericConditions(
|
||||
qnh=self.random_pressure(pressure),
|
||||
temperature_celsius=self.random_temperature(temperature),
|
||||
turbulence_per_10cm=turbulence,
|
||||
)
|
||||
logging.debug(
|
||||
"Weather: After random: temp {} press {}".format(
|
||||
@@ -127,6 +159,10 @@ class Weather:
|
||||
def temperature_adjustment(self) -> float:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def turbulence_adjustment(self) -> float:
|
||||
raise NotImplementedError
|
||||
|
||||
def generate_clouds(self) -> Optional[Clouds]:
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -147,15 +183,52 @@ class Weather:
|
||||
wind_direction_2000m = wind_direction + Heading.random(-90, 90)
|
||||
wind_direction_8000m = wind_direction + Heading.random(-90, 90)
|
||||
at_0m_factor = 1
|
||||
at_2000m_factor = 2
|
||||
at_8000m_factor = 3
|
||||
at_2000m_factor = 3 + random.choice([0, 0, 0, 0, 0, 1, 1])
|
||||
|
||||
high_alt_variation = random.choice(
|
||||
[
|
||||
-3,
|
||||
-3,
|
||||
-2,
|
||||
-2,
|
||||
-2,
|
||||
-2,
|
||||
-2,
|
||||
-2,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
)
|
||||
at_8000m_factor = at_2000m_factor + 5 + high_alt_variation
|
||||
|
||||
base_wind = random.randint(minimum, maximum)
|
||||
|
||||
# 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, base_wind * at_0m_factor)),
|
||||
at_2000m=Wind(wind_direction_2000m.degrees, base_wind * at_2000m_factor),
|
||||
at_8000m=Wind(wind_direction_8000m.degrees, base_wind * at_8000m_factor),
|
||||
at_2000m=Wind(
|
||||
wind_direction_2000m.degrees,
|
||||
min(max_supported_wind_speed, base_wind * at_2000m_factor),
|
||||
),
|
||||
at_8000m=Wind(
|
||||
wind_direction_8000m.degrees,
|
||||
min(max_supported_wind_speed, base_wind * at_8000m_factor),
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -198,6 +271,42 @@ class Weather:
|
||||
winter_factor = distance_from_peak_summer / day_of_year_peak_summer
|
||||
return interpolate(summer_value, winter_value, winter_factor, clamp=True)
|
||||
|
||||
@staticmethod
|
||||
def interpolate_seasonal_turbulence(
|
||||
high_value: float, low_value: float, day: datetime.date
|
||||
) -> float:
|
||||
day_of_year = day.timetuple().tm_yday
|
||||
day_of_year_peak_summer = 183
|
||||
distance_from_peak_summer = -day_of_year_peak_summer + day_of_year
|
||||
|
||||
amplitude = 0.5 * (high_value - low_value)
|
||||
offset = amplitude + low_value
|
||||
|
||||
# A high peak in summer and winter, between high_value and low_value.
|
||||
return (
|
||||
amplitude * math.cos(4 * math.pi * distance_from_peak_summer / 365.25)
|
||||
+ offset
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def interpolate_solar_activity(
|
||||
time_of_day: TimeOfDay, high: float, low: float
|
||||
) -> float:
|
||||
|
||||
scale: float = 0
|
||||
|
||||
match time_of_day:
|
||||
case TimeOfDay.Dawn:
|
||||
scale = 0.4
|
||||
case TimeOfDay.Day:
|
||||
scale = 1
|
||||
case TimeOfDay.Dusk:
|
||||
scale = 0.6
|
||||
case TimeOfDay.Night:
|
||||
scale = 0
|
||||
|
||||
return interpolate(value1=low, value2=high, factor=scale, clamp=True)
|
||||
|
||||
|
||||
class ClearSkies(Weather):
|
||||
@property
|
||||
@@ -208,6 +317,10 @@ class ClearSkies(Weather):
|
||||
def temperature_adjustment(self) -> float:
|
||||
return 3.0
|
||||
|
||||
@property
|
||||
def turbulence_adjustment(self) -> float:
|
||||
return 0.0
|
||||
|
||||
def generate_clouds(self) -> Optional[Clouds]:
|
||||
return None
|
||||
|
||||
@@ -227,6 +340,10 @@ class Cloudy(Weather):
|
||||
def temperature_adjustment(self) -> float:
|
||||
return 0.0
|
||||
|
||||
@property
|
||||
def turbulence_adjustment(self) -> float:
|
||||
return 0.75
|
||||
|
||||
def generate_clouds(self) -> Optional[Clouds]:
|
||||
return Clouds.random_preset(rain=False)
|
||||
|
||||
@@ -247,6 +364,10 @@ class Raining(Weather):
|
||||
def temperature_adjustment(self) -> float:
|
||||
return -3.0
|
||||
|
||||
@property
|
||||
def turbulence_adjustment(self) -> float:
|
||||
return 1.5
|
||||
|
||||
def generate_clouds(self) -> Optional[Clouds]:
|
||||
return Clouds.random_preset(rain=True)
|
||||
|
||||
@@ -255,7 +376,7 @@ class Raining(Weather):
|
||||
return None
|
||||
|
||||
def generate_wind(self) -> WindConditions:
|
||||
return self.random_wind(1, 6)
|
||||
return self.random_wind(2, 6)
|
||||
|
||||
|
||||
class Thunderstorm(Weather):
|
||||
@@ -267,6 +388,10 @@ class Thunderstorm(Weather):
|
||||
def temperature_adjustment(self) -> float:
|
||||
return -3.0
|
||||
|
||||
@property
|
||||
def turbulence_adjustment(self) -> float:
|
||||
return 3.0
|
||||
|
||||
def generate_clouds(self) -> Optional[Clouds]:
|
||||
return Clouds(
|
||||
base=self.random_cloud_base(),
|
||||
|
||||
Reference in New Issue
Block a user