mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Rather than polling at 60Hz (which may be faster than the tick rate, wasting cycles; and also makes synchronization annoying), collect events during the tick and emit them after (rate limited, pooling events until it is time for another event to send). This can be improved by paying attention to the aircraft update list, which would allow us to avoid updating aircraft that don't have a status change. To do that we need to be able to quickly lookup a FlightJs matching a Flight through, and Flight isn't hashable. We should also be removing dead events and de-duplicating. Currently each flight has an update for every tick, but only the latest one matters. Combat update events also don't matter if the same combat is new in the update. https://github.com/dcs-liberation/dcs_liberation/issues/1680
316 lines
11 KiB
Python
316 lines
11 KiB
Python
from datetime import datetime
|
|
|
|
from PySide2.QtGui import QPixmap
|
|
from PySide2.QtWidgets import (
|
|
QFrame,
|
|
QGridLayout,
|
|
QGroupBox,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QVBoxLayout,
|
|
)
|
|
from dcs.weather import CloudPreset, Weather as PydcsWeather
|
|
|
|
import qt_ui.uiconstants as CONST
|
|
from game.sim.gameupdateevents import GameUpdateEvents
|
|
from game.utils import mps
|
|
from game.weather import Conditions, TimeOfDay
|
|
from qt_ui.simcontroller import SimController
|
|
|
|
|
|
class QTimeTurnWidget(QGroupBox):
|
|
"""
|
|
UI Component to display current turn and time info
|
|
"""
|
|
|
|
def __init__(self, sim_controller: SimController) -> None:
|
|
super(QTimeTurnWidget, self).__init__("Turn")
|
|
self.sim_controller = sim_controller
|
|
self.setStyleSheet(
|
|
"padding: 0px; margin-left: 5px; margin-right: 0px; margin-top: 1ex; margin-bottom: 5px; border-right: 0px"
|
|
)
|
|
|
|
self.icons = {
|
|
TimeOfDay.Dawn: CONST.ICONS["Dawn"],
|
|
TimeOfDay.Day: CONST.ICONS["Day"],
|
|
TimeOfDay.Dusk: CONST.ICONS["Dusk"],
|
|
TimeOfDay.Night: CONST.ICONS["Night"],
|
|
}
|
|
|
|
# self.setProperty('style', 'conditions__widget--turn')
|
|
self.layout = QHBoxLayout()
|
|
self.setLayout(self.layout)
|
|
|
|
self.daytime_icon = QLabel()
|
|
self.daytime_icon.setPixmap(self.icons[TimeOfDay.Dawn])
|
|
self.layout.addWidget(self.daytime_icon)
|
|
|
|
self.time_column = QVBoxLayout()
|
|
self.layout.addLayout(self.time_column)
|
|
|
|
self.date_display = QLabel()
|
|
self.time_column.addWidget(self.date_display)
|
|
|
|
self.time_display = QLabel()
|
|
self.time_column.addWidget(self.time_display)
|
|
|
|
sim_controller.sim_update.connect(self.on_sim_update)
|
|
|
|
def on_sim_update(self, _events: GameUpdateEvents) -> None:
|
|
time = self.sim_controller.current_time_in_sim
|
|
if time is None:
|
|
self.date_display.setText("")
|
|
self.time_display.setText("")
|
|
else:
|
|
self.set_date_and_time(time)
|
|
|
|
def set_current_turn(self, turn: int, conditions: Conditions) -> None:
|
|
"""Sets the turn information display.
|
|
|
|
:arg turn Current turn number.
|
|
:arg conditions Current time and weather conditions.
|
|
"""
|
|
self.daytime_icon.setPixmap(self.icons[conditions.time_of_day])
|
|
self.set_date_and_time(conditions.start_time)
|
|
self.setTitle(f"Turn {turn}")
|
|
|
|
def set_date_and_time(self, time: datetime) -> None:
|
|
self.date_display.setText(time.strftime("%d %b %Y"))
|
|
self.time_display.setText(time.strftime("%H:%M:%S Local"))
|
|
|
|
|
|
class QWeatherWidget(QGroupBox):
|
|
"""
|
|
UI Component to display current weather forecast
|
|
"""
|
|
|
|
turn = None
|
|
conditions = None
|
|
|
|
def __init__(self):
|
|
super(QWeatherWidget, self).__init__("")
|
|
self.setProperty("style", "QWeatherWidget")
|
|
|
|
self.icons = {
|
|
TimeOfDay.Dawn: CONST.ICONS["Dawn"],
|
|
TimeOfDay.Day: CONST.ICONS["Day"],
|
|
TimeOfDay.Dusk: CONST.ICONS["Dusk"],
|
|
TimeOfDay.Night: CONST.ICONS["Night"],
|
|
}
|
|
|
|
self.layout = QHBoxLayout()
|
|
self.setLayout(self.layout)
|
|
|
|
self.makeWeatherIcon()
|
|
self.makeCloudRainFogWidget()
|
|
self.makeWindsWidget()
|
|
|
|
def makeWeatherIcon(self):
|
|
"""Makes the Weather Icon Widget"""
|
|
self.weather_icon = QLabel()
|
|
self.weather_icon.setPixmap(self.icons[TimeOfDay.Dawn])
|
|
self.layout.addWidget(self.weather_icon)
|
|
|
|
def makeCloudRainFogWidget(self):
|
|
"""Makes the Cloud, Rain, Fog Widget"""
|
|
self.textLayout = QVBoxLayout()
|
|
self.layout.addLayout(self.textLayout)
|
|
|
|
self.forecastClouds = self.makeLabel()
|
|
self.textLayout.addWidget(self.forecastClouds)
|
|
|
|
self.forecastRain = self.makeLabel()
|
|
self.textLayout.addWidget(self.forecastRain)
|
|
|
|
self.forecastFog = self.makeLabel()
|
|
self.textLayout.addWidget(self.forecastFog)
|
|
|
|
def makeWindsWidget(self):
|
|
"""Factory for the winds widget."""
|
|
windsLayout = QGridLayout()
|
|
self.layout.addLayout(windsLayout)
|
|
|
|
windsLayout.addWidget(self.makeIcon(CONST.ICONS["Weather_winds"]), 0, 0, 3, 1)
|
|
|
|
windsLayout.addWidget(self.makeLabel("At GL"), 0, 1)
|
|
windsLayout.addWidget(self.makeLabel("At FL08"), 1, 1)
|
|
windsLayout.addWidget(self.makeLabel("At FL26"), 2, 1)
|
|
|
|
self.windGLSpeedLabel = self.makeLabel("0kts")
|
|
self.windGLDirLabel = self.makeLabel("0º")
|
|
windsLayout.addWidget(self.windGLSpeedLabel, 0, 2)
|
|
windsLayout.addWidget(self.windGLDirLabel, 0, 3)
|
|
|
|
self.windFL08SpeedLabel = self.makeLabel("0kts")
|
|
self.windFL08DirLabel = self.makeLabel("0º")
|
|
windsLayout.addWidget(self.windFL08SpeedLabel, 1, 2)
|
|
windsLayout.addWidget(self.windFL08DirLabel, 1, 3)
|
|
|
|
self.windFL26SpeedLabel = self.makeLabel("0kts")
|
|
self.windFL26DirLabel = self.makeLabel("0º")
|
|
windsLayout.addWidget(self.windFL26SpeedLabel, 2, 2)
|
|
windsLayout.addWidget(self.windFL26DirLabel, 2, 3)
|
|
|
|
def makeLabel(self, text: str = "") -> QLabel:
|
|
"""Shorthand to generate a QLabel with widget standard style
|
|
|
|
:arg pixmap QPixmap for the icon.
|
|
"""
|
|
label = QLabel(text)
|
|
label.setProperty("style", "text-sm")
|
|
|
|
return label
|
|
|
|
def makeIcon(self, pixmap: QPixmap) -> QLabel:
|
|
"""Shorthand to generate a QIcon with pixmap.
|
|
|
|
:arg pixmap QPixmap for the icon.
|
|
"""
|
|
icon = QLabel()
|
|
icon.setPixmap(pixmap)
|
|
|
|
return icon
|
|
|
|
def setCurrentTurn(self, turn: int, conditions: Conditions) -> None:
|
|
"""Sets the turn information display.
|
|
|
|
:arg turn Current turn number.
|
|
:arg conditions Current time and weather conditions.
|
|
"""
|
|
self.turn = turn
|
|
self.conditions = conditions
|
|
|
|
self.update_forecast()
|
|
self.updateWinds()
|
|
|
|
def updateWinds(self):
|
|
"""Updates the UI with the current conditions wind info."""
|
|
windGlSpeed = mps(self.conditions.weather.wind.at_0m.speed or 0)
|
|
windGlDir = str(self.conditions.weather.wind.at_0m.direction or 0).rjust(3, "0")
|
|
self.windGLSpeedLabel.setText(f"{int(windGlSpeed.knots)}kts")
|
|
self.windGLDirLabel.setText(f"{windGlDir}º")
|
|
|
|
windFL08Speed = mps(self.conditions.weather.wind.at_2000m.speed or 0)
|
|
windFL08Dir = str(self.conditions.weather.wind.at_2000m.direction or 0).rjust(
|
|
3, "0"
|
|
)
|
|
self.windFL08SpeedLabel.setText(f"{int(windFL08Speed.knots)}kts")
|
|
self.windFL08DirLabel.setText(f"{windFL08Dir}º")
|
|
|
|
windFL26Speed = mps(self.conditions.weather.wind.at_8000m.speed or 0)
|
|
windFL26Dir = str(self.conditions.weather.wind.at_8000m.direction or 0).rjust(
|
|
3, "0"
|
|
)
|
|
self.windFL26SpeedLabel.setText(f"{int(windFL26Speed.knots)}kts")
|
|
self.windFL26DirLabel.setText(f"{windFL26Dir}º")
|
|
|
|
def update_forecast_from_preset(self, preset: CloudPreset) -> None:
|
|
self.forecastFog.setText("No fog")
|
|
if "Rain" in preset.name:
|
|
self.forecastRain.setText("Rain")
|
|
self.update_forecast_icons("rain")
|
|
else:
|
|
self.forecastRain.setText("No rain")
|
|
self.update_forecast_icons("partly-cloudy")
|
|
|
|
# We get a description like the following for the cloud preset.
|
|
#
|
|
# 09 ##Two Layer Broken/Scattered \nMETAR:BKN 7.5/10 SCT 20/22 FEW41
|
|
#
|
|
# The second line is probably interesting but doesn't fit into the widget
|
|
# currently, so for now just extract the first line.
|
|
self.forecastClouds.setText(preset.description.splitlines()[0].split("##")[1])
|
|
|
|
def update_forecast(self):
|
|
"""Updates the Forecast Text and icon with the current conditions wind info."""
|
|
if (
|
|
self.conditions.weather.clouds
|
|
and self.conditions.weather.clouds.preset is not None
|
|
):
|
|
self.update_forecast_from_preset(self.conditions.weather.clouds.preset)
|
|
return
|
|
|
|
if self.conditions.weather.clouds is None:
|
|
cloud_density = 0
|
|
precipitation = None
|
|
else:
|
|
cloud_density = self.conditions.weather.clouds.density
|
|
precipitation = self.conditions.weather.clouds.precipitation
|
|
|
|
if not cloud_density:
|
|
self.forecastClouds.setText("Clear")
|
|
weather_type = "clear"
|
|
elif cloud_density < 3:
|
|
self.forecastClouds.setText("Partly Cloudy")
|
|
weather_type = "partly-cloudy"
|
|
elif cloud_density < 5:
|
|
self.forecastClouds.setText("Mostly Cloudy")
|
|
weather_type = "partly-cloudy"
|
|
else:
|
|
self.forecastClouds.setText("Totally Cloudy")
|
|
weather_type = "partly-cloudy"
|
|
|
|
if precipitation == PydcsWeather.Preceptions.Rain:
|
|
self.forecastRain.setText("Rain")
|
|
weather_type = "rain"
|
|
elif precipitation == PydcsWeather.Preceptions.Thunderstorm:
|
|
self.forecastRain.setText("Thunderstorm")
|
|
weather_type = "thunderstorm"
|
|
else:
|
|
self.forecastRain.setText("No rain")
|
|
|
|
if not self.conditions.weather.fog is not None:
|
|
self.forecastFog.setText("No fog")
|
|
else:
|
|
visibility = round(self.conditions.weather.fog.visibility.nautical_miles, 1)
|
|
self.forecastFog.setText(f"Fog vis: {visibility}nm")
|
|
if cloud_density > 1:
|
|
weather_type = "cloudy-fog"
|
|
else:
|
|
weather_type = "fog"
|
|
|
|
self.update_forecast_icons(weather_type)
|
|
|
|
def update_forecast_icons(self, weather_type: str) -> None:
|
|
time = "night" if self.conditions.time_of_day == TimeOfDay.Night else "day"
|
|
icon_key = f"Weather_{time}-{weather_type}"
|
|
icon = CONST.ICONS.get(icon_key) or CONST.ICONS["Weather_night-partly-cloudy"]
|
|
self.weather_icon.setPixmap(icon)
|
|
|
|
|
|
class QConditionsWidget(QFrame):
|
|
"""
|
|
UI Component to display Turn Number, Day Time & Hour and weather combined.
|
|
"""
|
|
|
|
def __init__(self, sim_controller: SimController) -> None:
|
|
super(QConditionsWidget, self).__init__()
|
|
self.setProperty("style", "QConditionsWidget")
|
|
|
|
self.layout = QGridLayout()
|
|
self.layout.setContentsMargins(0, 0, 0, 0)
|
|
self.layout.setHorizontalSpacing(0)
|
|
self.layout.setVerticalSpacing(0)
|
|
self.setLayout(self.layout)
|
|
|
|
self.time_turn_widget = QTimeTurnWidget(sim_controller)
|
|
self.time_turn_widget.setStyleSheet("QGroupBox { margin-right: 0px; }")
|
|
self.layout.addWidget(self.time_turn_widget, 0, 0)
|
|
|
|
self.weather_widget = QWeatherWidget()
|
|
self.weather_widget.setStyleSheet(
|
|
"QGroupBox { margin-top: 5px; margin-left: 0px; border-left: 0px; }"
|
|
)
|
|
self.weather_widget.hide()
|
|
self.layout.addWidget(self.weather_widget, 0, 1)
|
|
|
|
def setCurrentTurn(self, turn: int, conditions: Conditions) -> None:
|
|
"""Sets the turn information display.
|
|
|
|
:arg turn Current turn number.
|
|
:arg conditions Current time and weather conditions.
|
|
"""
|
|
self.time_turn_widget.set_current_turn(turn, conditions)
|
|
self.weather_widget.setCurrentTurn(turn, conditions)
|
|
self.weather_widget.show()
|