Add situational temperature and pressure variation.

Now varies by:

* Season
* Theater
* Weather
* Time of day
This commit is contained in:
Magnus Wolffelt 2021-07-16 23:08:14 +02:00 committed by GitHub
parent e5c0fc92ec
commit 04a346678c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 192 additions and 29 deletions

View File

@ -497,6 +497,17 @@ class ReferencePoint:
image_coordinates: Point image_coordinates: Point
@dataclass(frozen=True)
class SeasonalConditions:
# Units are inHg and degrees Celsius
# Future improvement: add clouds/precipitation
summer_avg_pressure: float
winter_avg_pressure: float
summer_avg_temperature: float
winter_avg_temperature: float
temperature_day_night_difference: float
class ConflictTheater: class ConflictTheater:
terrain: Terrain terrain: Terrain
@ -719,6 +730,10 @@ class ConflictTheater:
MizCampaignLoader(directory / miz, t).populate_theater() MizCampaignLoader(directory / miz, t).populate_theater()
return t return t
@property
def seasonal_conditions(self) -> SeasonalConditions:
raise NotImplementedError
@property @property
def projection_parameters(self) -> TransverseMercator: def projection_parameters(self) -> TransverseMercator:
raise NotImplementedError raise NotImplementedError
@ -748,6 +763,16 @@ class CaucasusTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def seasonal_conditions(self) -> SeasonalConditions:
return SeasonalConditions(
summer_avg_pressure=30.02, # TODO: More science
winter_avg_pressure=29.72, # TODO: More science
summer_avg_temperature=22.5,
winter_avg_temperature=3.0,
temperature_day_night_difference=6.0,
)
@property @property
def projection_parameters(self) -> TransverseMercator: def projection_parameters(self) -> TransverseMercator:
from .caucasus import PARAMETERS from .caucasus import PARAMETERS
@ -770,6 +795,16 @@ class PersianGulfTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def seasonal_conditions(self) -> SeasonalConditions:
return SeasonalConditions(
summer_avg_pressure=29.98, # TODO: More science
winter_avg_pressure=29.80, # TODO: More science
summer_avg_temperature=32.5,
winter_avg_temperature=15.0,
temperature_day_night_difference=2.0,
)
@property @property
def projection_parameters(self) -> TransverseMercator: def projection_parameters(self) -> TransverseMercator:
from .persiangulf import PARAMETERS from .persiangulf import PARAMETERS
@ -792,6 +827,16 @@ class NevadaTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def seasonal_conditions(self) -> SeasonalConditions:
return SeasonalConditions(
summer_avg_pressure=30.02, # TODO: More science
winter_avg_pressure=29.72, # TODO: More science
summer_avg_temperature=31.5,
winter_avg_temperature=5.0,
temperature_day_night_difference=6.0,
)
@property @property
def projection_parameters(self) -> TransverseMercator: def projection_parameters(self) -> TransverseMercator:
from .nevada import PARAMETERS from .nevada import PARAMETERS
@ -814,6 +859,16 @@ class NormandyTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def seasonal_conditions(self) -> SeasonalConditions:
return SeasonalConditions(
summer_avg_pressure=30.02, # TODO: More science
winter_avg_pressure=29.72, # TODO: More science
summer_avg_temperature=20.0,
winter_avg_temperature=0.0,
temperature_day_night_difference=5.0,
)
@property @property
def projection_parameters(self) -> TransverseMercator: def projection_parameters(self) -> TransverseMercator:
from .normandy import PARAMETERS from .normandy import PARAMETERS
@ -836,6 +891,16 @@ class TheChannelTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def seasonal_conditions(self) -> SeasonalConditions:
return SeasonalConditions(
summer_avg_pressure=30.02, # TODO: More science
winter_avg_pressure=29.72, # TODO: More science
summer_avg_temperature=20.0,
winter_avg_temperature=0.0,
temperature_day_night_difference=5.0,
)
@property @property
def projection_parameters(self) -> TransverseMercator: def projection_parameters(self) -> TransverseMercator:
from .thechannel import PARAMETERS from .thechannel import PARAMETERS
@ -858,6 +923,16 @@ class SyriaTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def seasonal_conditions(self) -> SeasonalConditions:
return SeasonalConditions(
summer_avg_pressure=29.98, # TODO: More science
winter_avg_pressure=29.86, # TODO: More science
summer_avg_temperature=28.5,
winter_avg_temperature=10.0,
temperature_day_night_difference=8.0,
)
@property @property
def projection_parameters(self) -> TransverseMercator: def projection_parameters(self) -> TransverseMercator:
from .syria import PARAMETERS from .syria import PARAMETERS
@ -877,6 +952,16 @@ class MarianaIslandsTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def seasonal_conditions(self) -> SeasonalConditions:
return SeasonalConditions(
summer_avg_pressure=30.02, # TODO: More science
winter_avg_pressure=29.82, # TODO: More science
summer_avg_temperature=28.0,
winter_avg_temperature=27.0,
temperature_day_night_difference=1.0,
)
@property @property
def projection_parameters(self) -> TransverseMercator: def projection_parameters(self) -> TransverseMercator:
from .marianaislands import PARAMETERS from .marianaislands import PARAMETERS

View File

@ -189,3 +189,15 @@ def pairwise(iterable: Iterable[Any]) -> Iterable[tuple[Any, Any]]:
a, b = itertools.tee(iterable) a, b = itertools.tee(iterable)
next(b, None) next(b, None)
return zip(a, b) return zip(a, b)
def interpolate(value1: float, value2: float, factor: float, clamp: bool) -> float:
"""Inerpolate between two values, factor 0-1"""
interpolated = value1 + (value2 - value1) * factor
if clamp:
bigger_value = max(value1, value2)
smaller_value = min(value1, value2)
return min(bigger_value, max(smaller_value, interpolated))
else:
return interpolated

View File

@ -11,10 +11,11 @@ from dcs.cloud_presets import Clouds as PydcsClouds
from dcs.weather import CloudPreset, Weather as PydcsWeather, Wind from dcs.weather import CloudPreset, Weather as PydcsWeather, Wind
from game.settings import Settings from game.settings import Settings
from game.utils import Distance, meters from game.utils import Distance, meters, interpolate
if TYPE_CHECKING: if TYPE_CHECKING:
from game.theater import ConflictTheater from game.theater import ConflictTheater
from game.theater.conflicttheater import SeasonalConditions
class TimeOfDay(Enum): class TimeOfDay(Enum):
@ -71,15 +72,56 @@ class Fog:
class Weather: class Weather:
def __init__(self) -> None: def __init__(
self,
seasonal_conditions: SeasonalConditions,
day: datetime.date,
time_of_day: TimeOfDay,
) -> None:
# Future improvement: Use theater, day and time of day # Future improvement: Use theater, day and time of day
# to get a more realistic conditions # to get a more realistic conditions
self.atmospheric = self.generate_atmospheric() self.atmospheric = self.generate_atmospheric(
seasonal_conditions, day, time_of_day
)
self.clouds = self.generate_clouds() self.clouds = self.generate_clouds()
self.fog = self.generate_fog() self.fog = self.generate_fog()
self.wind = self.generate_wind() self.wind = self.generate_wind()
def generate_atmospheric(self) -> AtmosphericConditions: def generate_atmospheric(
self,
seasonal_conditions: SeasonalConditions,
day: datetime.date,
time_of_day: TimeOfDay,
) -> AtmosphericConditions:
pressure = self.interpolate_summer_winter(
seasonal_conditions.summer_avg_pressure,
seasonal_conditions.winter_avg_pressure,
day,
)
temperature = self.interpolate_summer_winter(
seasonal_conditions.summer_avg_temperature,
seasonal_conditions.winter_avg_temperature,
day,
)
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
conditions = AtmosphericConditions(
qnh_inches_mercury=self.random_pressure(pressure),
temperature_celsius=self.random_temperature(temperature),
)
return conditions
@property
def pressure_adjustment(self) -> float:
raise NotImplementedError
@property
def temperature_adjustment(self) -> float:
raise NotImplementedError raise NotImplementedError
def generate_clouds(self) -> Optional[Clouds]: def generate_clouds(self) -> Optional[Clouds]:
@ -126,7 +168,7 @@ class Weather:
SAFE_MIN = 28.4 SAFE_MIN = 28.4
SAFE_MAX = 30.9 SAFE_MAX = 30.9
# Use normalvariate to get normal distribution, more realistic than uniform # Use normalvariate to get normal distribution, more realistic than uniform
pressure = random.normalvariate(average_pressure, 0.2) pressure = random.normalvariate(average_pressure, 0.1)
return max(SAFE_MIN, min(SAFE_MAX, pressure)) return max(SAFE_MIN, min(SAFE_MAX, pressure))
@staticmethod @staticmethod
@ -136,17 +178,29 @@ class Weather:
SAFE_MIN = -12 SAFE_MIN = -12
SAFE_MAX = 49 SAFE_MAX = 49
# Use normalvariate to get normal distribution, more realistic than uniform # Use normalvariate to get normal distribution, more realistic than uniform
temperature = random.normalvariate(average_temperature, 4) temperature = random.normalvariate(average_temperature, 2)
temperature = round(temperature) temperature = round(temperature)
return max(SAFE_MIN, min(SAFE_MAX, temperature)) return max(SAFE_MIN, min(SAFE_MAX, temperature))
@staticmethod
def interpolate_summer_winter(
summer_value: float, winter_value: float, day: datetime.date
) -> float:
day_of_year = day.timetuple().tm_yday
day_of_year_peak_summer = 183
distance_from_peak_summer = abs(-day_of_year_peak_summer + day_of_year)
winter_factor = distance_from_peak_summer / day_of_year_peak_summer
return interpolate(summer_value, winter_value, winter_factor, clamp=True)
class ClearSkies(Weather): class ClearSkies(Weather):
def generate_atmospheric(self) -> AtmosphericConditions: @property
return AtmosphericConditions( def pressure_adjustment(self) -> float:
qnh_inches_mercury=self.random_pressure(29.96), return 0.22
temperature_celsius=self.random_temperature(22),
) @property
def temperature_adjustment(self) -> float:
return 3.0
def generate_clouds(self) -> Optional[Clouds]: def generate_clouds(self) -> Optional[Clouds]:
return None return None
@ -159,11 +213,13 @@ class ClearSkies(Weather):
class Cloudy(Weather): class Cloudy(Weather):
def generate_atmospheric(self) -> AtmosphericConditions: @property
return AtmosphericConditions( def pressure_adjustment(self) -> float:
qnh_inches_mercury=self.random_pressure(29.90), return 0.0
temperature_celsius=self.random_temperature(20),
) @property
def temperature_adjustment(self) -> float:
return 0.0
def generate_clouds(self) -> Optional[Clouds]: def generate_clouds(self) -> Optional[Clouds]:
return Clouds.random_preset(rain=False) return Clouds.random_preset(rain=False)
@ -177,11 +233,13 @@ class Cloudy(Weather):
class Raining(Weather): class Raining(Weather):
def generate_atmospheric(self) -> AtmosphericConditions: @property
return AtmosphericConditions( def pressure_adjustment(self) -> float:
qnh_inches_mercury=self.random_pressure(29.70), return -0.22
temperature_celsius=self.random_temperature(16),
) @property
def temperature_adjustment(self) -> float:
return -3.0
def generate_clouds(self) -> Optional[Clouds]: def generate_clouds(self) -> Optional[Clouds]:
return Clouds.random_preset(rain=True) return Clouds.random_preset(rain=True)
@ -195,11 +253,13 @@ class Raining(Weather):
class Thunderstorm(Weather): class Thunderstorm(Weather):
def generate_atmospheric(self) -> AtmosphericConditions: @property
return AtmosphericConditions( def pressure_adjustment(self) -> float:
qnh_inches_mercury=self.random_pressure(29.60), return 0.1
temperature_celsius=self.random_temperature(15),
) @property
def temperature_adjustment(self) -> float:
return -3.0
def generate_clouds(self) -> Optional[Clouds]: def generate_clouds(self) -> Optional[Clouds]:
return Clouds( return Clouds(
@ -233,7 +293,7 @@ class Conditions:
return cls( return cls(
time_of_day=time_of_day, time_of_day=time_of_day,
start_time=_start_time, start_time=_start_time,
weather=cls.generate_weather(), weather=cls.generate_weather(theater.seasonal_conditions, day, time_of_day),
) )
@classmethod @classmethod
@ -259,7 +319,13 @@ class Conditions:
return datetime.datetime.combine(day, time) return datetime.datetime.combine(day, time)
@classmethod @classmethod
def generate_weather(cls) -> Weather: def generate_weather(
cls,
seasonal_conditions: SeasonalConditions,
day: datetime.date,
time_of_day: TimeOfDay,
) -> Weather:
# Future improvement: use seasonal weights for theaters
chances = { chances = {
Thunderstorm: 1, Thunderstorm: 1,
Raining: 20, Raining: 20,
@ -269,4 +335,4 @@ class Conditions:
weather_type = random.choices( weather_type = random.choices(
list(chances.keys()), weights=list(chances.values()) list(chances.keys()), weights=list(chances.values())
)[0] )[0]
return weather_type() return weather_type(seasonal_conditions, day, time_of_day)