mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Calculate turbulance.
Turbulance is based off time of day, and day of year. Each theatre may adjust their turbulance parameters.
This commit is contained in:
parent
bc6f953f76
commit
1eccedb74d
@ -10,6 +10,7 @@ Saves from 5.x are not compatible with 6.0.
|
|||||||
* **[Mission Generation]** Reworked the ground object generation which now uses a new layout system
|
* **[Mission Generation]** Reworked the ground object generation which now uses a new layout system
|
||||||
* **[Mission Generation]** Added information about the modulation (AM/FM) of the assigned frequencies to the kneeboard and assign AM modulation instead of FM for JTAC.
|
* **[Mission Generation]** Added information about the modulation (AM/FM) of the assigned frequencies to the kneeboard and assign AM modulation instead of FM for JTAC.
|
||||||
* **[Mission Generation]** Adjusted wind speeds. Wind speeds at high altitude are generally higher now.
|
* **[Mission Generation]** Adjusted wind speeds. Wind speeds at high altitude are generally higher now.
|
||||||
|
* **[Mission Generation]** Added turbulance. Higher in Summer and Winter, also higher at day time than at night time.
|
||||||
* **[Factions]** Updated the Faction file structure. Older custom faction files will not work correctly and have to be updated to the new structure.
|
* **[Factions]** Updated the Faction file structure. Older custom faction files will not work correctly and have to be updated to the new structure.
|
||||||
* **[Flight Planning]** Added preset formations for different flight types at hold, join, ingress, and split waypoints. Air to Air flights will tend toward line-abreast and spread-four formations. Air to ground flights will tend towards trail formation.
|
* **[Flight Planning]** Added preset formations for different flight types at hold, join, ingress, and split waypoints. Air to Air flights will tend toward line-abreast and spread-four formations. Air to ground flights will tend towards trail formation.
|
||||||
* **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. AI does not plan.
|
* **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. AI does not plan.
|
||||||
|
|||||||
@ -17,6 +17,7 @@ class EnvironmentGenerator:
|
|||||||
def set_atmospheric(self, atmospheric: AtmosphericConditions) -> None:
|
def set_atmospheric(self, atmospheric: AtmosphericConditions) -> None:
|
||||||
self.mission.weather.qnh = atmospheric.qnh.mm_hg
|
self.mission.weather.qnh = atmospheric.qnh.mm_hg
|
||||||
self.mission.weather.season_temperature = atmospheric.temperature_celsius
|
self.mission.weather.season_temperature = atmospheric.temperature_celsius
|
||||||
|
self.mission.weather.turbulence_at_ground = int(atmospheric.turbulance_per_10cm)
|
||||||
|
|
||||||
def set_clouds(self, clouds: Optional[Clouds]) -> None:
|
def set_clouds(self, clouds: Optional[Clouds]) -> None:
|
||||||
if clouds is None:
|
if clouds is None:
|
||||||
|
|||||||
@ -400,6 +400,9 @@ class BriefingPage(KneeboardPage):
|
|||||||
f"Temperature: {round(self.weather.atmospheric.temperature_celsius)} °C at sea level"
|
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"QNH: {qnh_in_hg} inHg / {qnh_mm_hg} mmHg / {qnh_hpa} hPa")
|
||||||
|
writer.text(
|
||||||
|
f"Turbulance: {round(self.weather.atmospheric.turbulance_per_10cm)} per 10cm at ground level."
|
||||||
|
)
|
||||||
|
|
||||||
fl = self.flight
|
fl = self.flight
|
||||||
|
|
||||||
|
|||||||
@ -45,4 +45,9 @@ class SeasonalConditions:
|
|||||||
winter_avg_temperature: float
|
winter_avg_temperature: float
|
||||||
temperature_day_night_difference: float
|
temperature_day_night_difference: float
|
||||||
|
|
||||||
|
high_avg_yearly_turbulance_per_10cm: float
|
||||||
|
low_avg_yearly_turbulance_per_10cm: float
|
||||||
|
solar_noon_turbulance_per_10cm: float
|
||||||
|
midnight_turbulance_per_10cm: float
|
||||||
|
|
||||||
weather_type_chances: dict[Season, WeatherTypeChances]
|
weather_type_chances: dict[Season, WeatherTypeChances]
|
||||||
|
|||||||
@ -57,6 +57,23 @@ class SeasonData:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class TurbulanceData:
|
||||||
|
high_avg_yearly_turbulance_per_10cm: float | None
|
||||||
|
low_avg_yearly_turbulance_per_10cm: float | None
|
||||||
|
solar_noon_turbulance_per_10cm: float | None
|
||||||
|
midnight_turbulance_per_10cm: float | None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_yaml(data: dict[str, Any]) -> TurbulanceData:
|
||||||
|
return TurbulanceData(
|
||||||
|
data.get("high_avg_yearly_turbulance_per_10cm"),
|
||||||
|
data.get("low_avg_yearly_turbulance_per_10cm"),
|
||||||
|
data.get("solar_noon_turbulance_per_10cm"),
|
||||||
|
data.get("midnight_turbulance_per_10cm"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TheaterLoader:
|
class TheaterLoader:
|
||||||
THEATER_RESOURCE_DIR = Path("resources/theaters")
|
THEATER_RESOURCE_DIR = Path("resources/theaters")
|
||||||
|
|
||||||
@ -113,6 +130,7 @@ class TheaterLoader:
|
|||||||
spring = SeasonData.from_yaml(climate_data["seasons"]["spring"])
|
spring = SeasonData.from_yaml(climate_data["seasons"]["spring"])
|
||||||
summer = SeasonData.from_yaml(climate_data["seasons"]["summer"])
|
summer = SeasonData.from_yaml(climate_data["seasons"]["summer"])
|
||||||
fall = SeasonData.from_yaml(climate_data["seasons"]["fall"])
|
fall = SeasonData.from_yaml(climate_data["seasons"]["fall"])
|
||||||
|
turbulance = TurbulanceData.from_yaml(climate_data["turbulance"])
|
||||||
if summer.average_pressure is None:
|
if summer.average_pressure is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"{self.descriptor_path} does not define a summer average pressure"
|
f"{self.descriptor_path} does not define a summer average pressure"
|
||||||
@ -129,12 +147,32 @@ class TheaterLoader:
|
|||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"{self.descriptor_path} does not define a winter average temperature"
|
f"{self.descriptor_path} does not define a winter average temperature"
|
||||||
)
|
)
|
||||||
|
if turbulance.high_avg_yearly_turbulance_per_10cm is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{self.descriptor_path} does not define a yearly average high turbulance"
|
||||||
|
)
|
||||||
|
if turbulance.low_avg_yearly_turbulance_per_10cm is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{self.descriptor_path} does not define a yearly average low turbulance"
|
||||||
|
)
|
||||||
|
if turbulance.solar_noon_turbulance_per_10cm is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{self.descriptor_path} does not define a solar noon turbulance"
|
||||||
|
)
|
||||||
|
if turbulance.midnight_turbulance_per_10cm is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{self.descriptor_path} does not define a midnight turbulance"
|
||||||
|
)
|
||||||
return SeasonalConditions(
|
return SeasonalConditions(
|
||||||
summer.average_pressure,
|
summer.average_pressure,
|
||||||
winter.average_pressure,
|
winter.average_pressure,
|
||||||
summer.average_temperature,
|
summer.average_temperature,
|
||||||
winter.average_temperature,
|
winter.average_temperature,
|
||||||
climate_data["day_night_temperature_difference"],
|
climate_data["day_night_temperature_difference"],
|
||||||
|
turbulance.high_avg_yearly_turbulance_per_10cm,
|
||||||
|
turbulance.low_avg_yearly_turbulance_per_10cm,
|
||||||
|
turbulance.solar_noon_turbulance_per_10cm,
|
||||||
|
turbulance.midnight_turbulance_per_10cm,
|
||||||
{
|
{
|
||||||
Season.Winter: winter.weather,
|
Season.Winter: winter.weather,
|
||||||
Season.Spring: spring.weather,
|
Season.Spring: spring.weather,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import random
|
import random
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING
|
||||||
@ -36,6 +37,9 @@ class AtmosphericConditions:
|
|||||||
#: Temperature at sea level in Celcius.
|
#: Temperature at sea level in Celcius.
|
||||||
temperature_celsius: float
|
temperature_celsius: float
|
||||||
|
|
||||||
|
#: Turbulance per 10 cm.
|
||||||
|
turbulance_per_10cm: float
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class WindConditions:
|
class WindConditions:
|
||||||
@ -108,18 +112,38 @@ class Weather:
|
|||||||
day,
|
day,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
seasonal_turbulance = self.interpolate_seasonal_turbulance(
|
||||||
|
seasonal_conditions.high_avg_yearly_turbulance_per_10cm,
|
||||||
|
seasonal_conditions.low_avg_yearly_turbulance_per_10cm,
|
||||||
|
day,
|
||||||
|
)
|
||||||
|
|
||||||
|
day_turbulance = seasonal_conditions.solar_noon_turbulance_per_10cm
|
||||||
|
night_turbulance = seasonal_conditions.midnight_turbulance_per_10cm
|
||||||
|
time_of_day_turbulance = self.interpolate_solar_activity(
|
||||||
|
time_of_day, day_turbulance, night_turbulance
|
||||||
|
)
|
||||||
|
|
||||||
|
random_turbulance = random.normalvariate(mu=0, sigma=0.5)
|
||||||
|
|
||||||
|
turbulance = abs(
|
||||||
|
seasonal_turbulance + time_of_day_turbulance + random_turbulance
|
||||||
|
)
|
||||||
|
|
||||||
if time_of_day == TimeOfDay.Day:
|
if time_of_day == TimeOfDay.Day:
|
||||||
temperature += seasonal_conditions.temperature_day_night_difference / 2
|
temperature += seasonal_conditions.temperature_day_night_difference / 2
|
||||||
if time_of_day == TimeOfDay.Night:
|
if time_of_day == TimeOfDay.Night:
|
||||||
temperature -= seasonal_conditions.temperature_day_night_difference / 2
|
temperature -= seasonal_conditions.temperature_day_night_difference / 2
|
||||||
pressure += self.pressure_adjustment
|
pressure += self.pressure_adjustment
|
||||||
temperature += self.temperature_adjustment
|
temperature += self.temperature_adjustment
|
||||||
|
turbulance += self.turbulance_adjustment
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"Weather: Before random: temp {} press {}".format(temperature, pressure)
|
"Weather: Before random: temp {} press {}".format(temperature, pressure)
|
||||||
)
|
)
|
||||||
conditions = AtmosphericConditions(
|
conditions = AtmosphericConditions(
|
||||||
qnh=self.random_pressure(pressure),
|
qnh=self.random_pressure(pressure),
|
||||||
temperature_celsius=self.random_temperature(temperature),
|
temperature_celsius=self.random_temperature(temperature),
|
||||||
|
turbulance_per_10cm=turbulance,
|
||||||
)
|
)
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"Weather: After random: temp {} press {}".format(
|
"Weather: After random: temp {} press {}".format(
|
||||||
@ -136,6 +160,10 @@ class Weather:
|
|||||||
def temperature_adjustment(self) -> float:
|
def temperature_adjustment(self) -> float:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def turbulance_adjustment(self) -> float:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def generate_clouds(self) -> Optional[Clouds]:
|
def generate_clouds(self) -> Optional[Clouds]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@ -243,6 +271,42 @@ class Weather:
|
|||||||
winter_factor = distance_from_peak_summer / day_of_year_peak_summer
|
winter_factor = distance_from_peak_summer / day_of_year_peak_summer
|
||||||
return interpolate(summer_value, winter_value, winter_factor, clamp=True)
|
return interpolate(summer_value, winter_value, winter_factor, clamp=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def interpolate_seasonal_turbulance(
|
||||||
|
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):
|
class ClearSkies(Weather):
|
||||||
@property
|
@property
|
||||||
@ -253,6 +317,10 @@ class ClearSkies(Weather):
|
|||||||
def temperature_adjustment(self) -> float:
|
def temperature_adjustment(self) -> float:
|
||||||
return 3.0
|
return 3.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def turbulance_adjustment(self) -> float:
|
||||||
|
return 0.3
|
||||||
|
|
||||||
def generate_clouds(self) -> Optional[Clouds]:
|
def generate_clouds(self) -> Optional[Clouds]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -272,6 +340,10 @@ class Cloudy(Weather):
|
|||||||
def temperature_adjustment(self) -> float:
|
def temperature_adjustment(self) -> float:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def turbulance_adjustment(self) -> float:
|
||||||
|
return 0.6
|
||||||
|
|
||||||
def generate_clouds(self) -> Optional[Clouds]:
|
def generate_clouds(self) -> Optional[Clouds]:
|
||||||
return Clouds.random_preset(rain=False)
|
return Clouds.random_preset(rain=False)
|
||||||
|
|
||||||
@ -292,6 +364,10 @@ class Raining(Weather):
|
|||||||
def temperature_adjustment(self) -> float:
|
def temperature_adjustment(self) -> float:
|
||||||
return -3.0
|
return -3.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def turbulance_adjustment(self) -> float:
|
||||||
|
return 0.9
|
||||||
|
|
||||||
def generate_clouds(self) -> Optional[Clouds]:
|
def generate_clouds(self) -> Optional[Clouds]:
|
||||||
return Clouds.random_preset(rain=True)
|
return Clouds.random_preset(rain=True)
|
||||||
|
|
||||||
@ -312,6 +388,10 @@ class Thunderstorm(Weather):
|
|||||||
def temperature_adjustment(self) -> float:
|
def temperature_adjustment(self) -> float:
|
||||||
return -3.0
|
return -3.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def turbulance_adjustment(self) -> float:
|
||||||
|
return 1.2
|
||||||
|
|
||||||
def generate_clouds(self) -> Optional[Clouds]:
|
def generate_clouds(self) -> Optional[Clouds]:
|
||||||
return Clouds(
|
return Clouds(
|
||||||
base=self.random_cloud_base(),
|
base=self.random_cloud_base(),
|
||||||
|
|||||||
@ -37,3 +37,8 @@ climate:
|
|||||||
raining: 30
|
raining: 30
|
||||||
cloudy: 50
|
cloudy: 50
|
||||||
clear: 20
|
clear: 20
|
||||||
|
turbulance:
|
||||||
|
high_avg_yearly_turbulance_per_10cm: 4
|
||||||
|
low_avg_yearly_turbulance_per_10cm: 1
|
||||||
|
solar_noon_turbulance_per_10cm: 1
|
||||||
|
midnight_turbulance_per_10cm: 0
|
||||||
@ -46,3 +46,8 @@ climate:
|
|||||||
raining: 30
|
raining: 30
|
||||||
cloudy: 45
|
cloudy: 45
|
||||||
clear: 25
|
clear: 25
|
||||||
|
turbulance:
|
||||||
|
high_avg_yearly_turbulance_per_10cm: 9
|
||||||
|
low_avg_yearly_turbulance_per_10cm: 3
|
||||||
|
solar_noon_turbulance_per_10cm: 2
|
||||||
|
midnight_turbulance_per_10cm: 0
|
||||||
@ -38,3 +38,8 @@ climate:
|
|||||||
raining: 45
|
raining: 45
|
||||||
cloudy: 30
|
cloudy: 30
|
||||||
clear: 20
|
clear: 20
|
||||||
|
turbulance:
|
||||||
|
high_avg_yearly_turbulance_per_10cm: 3
|
||||||
|
low_avg_yearly_turbulance_per_10cm: 1
|
||||||
|
solar_noon_turbulance_per_10cm: 2
|
||||||
|
midnight_turbulance_per_10cm: 1
|
||||||
@ -37,3 +37,8 @@ climate:
|
|||||||
raining: 10
|
raining: 10
|
||||||
cloudy: 45
|
cloudy: 45
|
||||||
clear: 45
|
clear: 45
|
||||||
|
turbulance:
|
||||||
|
high_avg_yearly_turbulance_per_10cm: 9
|
||||||
|
low_avg_yearly_turbulance_per_10cm: 1
|
||||||
|
solar_noon_turbulance_per_10cm: 6
|
||||||
|
midnight_turbulance_per_10cm: 0
|
||||||
@ -37,3 +37,8 @@ climate:
|
|||||||
raining: 30
|
raining: 30
|
||||||
cloudy: 50
|
cloudy: 50
|
||||||
clear: 20
|
clear: 20
|
||||||
|
turbulance:
|
||||||
|
high_avg_yearly_turbulance_per_10cm: 5
|
||||||
|
low_avg_yearly_turbulance_per_10cm: 2
|
||||||
|
solar_noon_turbulance_per_10cm: 3
|
||||||
|
midnight_turbulance_per_10cm: 1
|
||||||
@ -38,3 +38,8 @@ climate:
|
|||||||
raining: 2
|
raining: 2
|
||||||
cloudy: 28
|
cloudy: 28
|
||||||
clear: 70
|
clear: 70
|
||||||
|
turbulance:
|
||||||
|
high_avg_yearly_turbulance_per_10cm: 8
|
||||||
|
low_avg_yearly_turbulance_per_10cm: 1
|
||||||
|
solar_noon_turbulance_per_10cm: 5
|
||||||
|
midnight_turbulance_per_10cm: 0
|
||||||
@ -37,3 +37,8 @@ climate:
|
|||||||
raining: 15
|
raining: 15
|
||||||
cloudy: 35
|
cloudy: 35
|
||||||
clear: 50
|
clear: 50
|
||||||
|
turbulance:
|
||||||
|
high_avg_yearly_turbulance_per_10cm: 7
|
||||||
|
low_avg_yearly_turbulance_per_10cm: 2
|
||||||
|
solar_noon_turbulance_per_10cm: 4
|
||||||
|
midnight_turbulance_per_10cm: 1
|
||||||
@ -38,3 +38,8 @@ climate:
|
|||||||
raining: 30
|
raining: 30
|
||||||
cloudy: 50
|
cloudy: 50
|
||||||
clear: 20
|
clear: 20
|
||||||
|
turbulance:
|
||||||
|
high_avg_yearly_turbulance_per_10cm: 5
|
||||||
|
low_avg_yearly_turbulance_per_10cm: 2
|
||||||
|
solar_noon_turbulance_per_10cm: 3
|
||||||
|
midnight_turbulance_per_10cm: 1
|
||||||
Loading…
x
Reference in New Issue
Block a user