Add SEAD/DEAD target info kneeboard page.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/965
This commit is contained in:
Dan Albert 2021-05-20 00:13:49 -07:00
parent f1adcd1836
commit 8274e68846
2 changed files with 110 additions and 19 deletions

40
game/data/alic.py Normal file
View File

@ -0,0 +1,40 @@
from dcs.unit import Unit
from dcs.vehicles import AirDefence
class AlicCodes:
CODES = {
AirDefence.EWR_1L13.id: 101,
AirDefence.EWR_55G6.id: 102,
AirDefence.SAM_SA_10_S_300_Grumble_Clam_Shell_SR.id: 103,
AirDefence.SAM_SA_10_S_300_Grumble_Big_Bird_SR.id: 104,
AirDefence.SAM_SA_11_Buk_Gadfly_Snow_Drift_SR.id: 107,
AirDefence.SAM_SA_6_Kub_Long_Track_STR.id: 108,
AirDefence.MCC_SR_Sborka_Dog_Ear_SR.id: 109,
AirDefence.SAM_SA_10_S_300_Grumble_Flap_Lid_TR.id: 110,
AirDefence.SAM_SA_11_Buk_Gadfly_Fire_Dome_TEL.id: 115,
AirDefence.SAM_SA_8_Osa_Gecko_TEL.id: 117,
AirDefence.SAM_SA_13_Strela_10M3_Gopher_TEL.id: 118,
AirDefence.SAM_SA_15_Tor_Gauntlet.id: 119,
AirDefence.SAM_SA_19_Tunguska_Grison.id: 120,
AirDefence.SPAAA_ZSU_23_4_Shilka_Gun_Dish.id: 121,
AirDefence.SAM_P19_Flat_Face_SR__SA_2_3.id: 122,
AirDefence.SAM_SA_3_S_125_Low_Blow_TR.id: 123,
AirDefence.SAM_Rapier_Blindfire_TR.id: 124,
AirDefence.SAM_Rapier_LN.id: 125,
AirDefence.SAM_SA_2_S_75_Fan_Song_TR.id: 126,
AirDefence.HQ_7_Self_Propelled_LN.id: 127,
AirDefence.HQ_7_Self_Propelled_STR.id: 128,
AirDefence.SAM_Roland_ADS.id: 201,
AirDefence.SAM_Patriot_STR.id: 202,
AirDefence.SAM_Hawk_SR__AN_MPQ_50.id: 203,
AirDefence.SAM_Hawk_TR__AN_MPQ_46.id: 204,
AirDefence.SAM_Roland_EWR.id: 205,
AirDefence.SAM_Hawk_CWAR_AN_MPQ_55.id: 206,
AirDefence.SPAAA_Gepard.id: 207,
AirDefence.SPAAA_Vulcan_M163.id: 208,
}
@classmethod
def code_for(cls, unit: Unit) -> int:
return cls.CODES[unit.type]

View File

@ -30,10 +30,13 @@ from typing import Dict, List, Optional, TYPE_CHECKING, Tuple, Iterator
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
from dcs.mission import Mission from dcs.mission import Mission
from dcs.unit import Unit
from dcs.unittype import FlyingType from dcs.unittype import FlyingType
from tabulate import tabulate from tabulate import tabulate
from game.theater import ConflictTheater from game.data.alic import AlicCodes
from game.db import find_unittype, unit_type_from_name
from game.theater import ConflictTheater, TheaterGroundObject, LatLon
from game.utils import meters from game.utils import meters
from .aircraft import AIRCRAFT_DATA, FlightData from .aircraft import AIRCRAFT_DATA, FlightData
from .airsupportgen import AwacsInfo, TankerInfo from .airsupportgen import AwacsInfo, TankerInfo
@ -42,7 +45,6 @@ from .flights.flight import FlightWaypoint, FlightWaypointType, FlightType
from .radios import RadioFrequency from .radios import RadioFrequency
from .runways import RunwayData from .runways import RunwayData
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -138,6 +140,11 @@ class KneeboardPage:
"""Writes the kneeboard page to the given path.""" """Writes the kneeboard page to the given path."""
raise NotImplementedError raise NotImplementedError
def format_ll(self, ll: LatLon) -> str:
ns = "N" if ll.latitude >= 0 else "S"
ew = "E" if ll.longitude >= 0 else "W"
return f"{ll.latitude:.4}°{ns} {ll.longitude:.4}°{ew}"
@dataclass(frozen=True) @dataclass(frozen=True)
class NumberedWaypoint: class NumberedWaypoint:
@ -415,21 +422,70 @@ class BriefingPage(KneeboardPage):
return local_time.strftime(f"%H:%M:%S") return local_time.strftime(f"%H:%M:%S")
class TargetInfoPage(KneeboardPage): class SeadTaskPage(KneeboardPage):
"""A kneeboard page containing target information.""" """A kneeboard page containing SEAD/DEAD target information."""
def __init__(
self, flight: FlightData, dark_kneeboard: bool, theater: ConflictTheater
) -> None:
self.flight = flight
self.dark_kneeboard = dark_kneeboard
self.theater = theater
@property
def target_units(self) -> Iterator[Unit]:
if isinstance(self.flight.package.target, TheaterGroundObject):
yield from self.flight.package.target.units
@staticmethod
def alic_for(unit: Unit) -> str:
try:
return str(AlicCodes.code_for(unit))
except KeyError:
return ""
def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
if self.flight.custom_name is not None:
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
else:
custom_name_title = ""
task = "DEAD" if self.flight.flight_type == FlightType.DEAD else "SEAD"
writer.title(f"{self.flight.callsign} {task} Target Info{custom_name_title}")
writer.table(
[self.target_info_row(t) for t in self.target_units],
headers=["Description", "ALIC", "Location"],
)
writer.write(path)
def target_info_row(self, unit: Unit) -> List[str]:
ll = self.theater.point_to_ll(unit.position)
unit_type = unit_type_from_name(unit.type)
name = unit.name if unit_type is None else unit_type.name
return [name, self.alic_for(unit), self.format_ll(ll)]
class StrikeTaskPage(KneeboardPage):
"""A kneeboard page containing strike target information."""
def __init__( def __init__(
self, self,
flight: FlightData, flight: FlightData,
targets: List[FlightWaypoint],
dark_kneeboard: bool, dark_kneeboard: bool,
theater: ConflictTheater, theater: ConflictTheater,
) -> None: ) -> None:
self.flight = flight self.flight = flight
self.targets = targets
self.dark_kneeboard = dark_kneeboard self.dark_kneeboard = dark_kneeboard
self.theater = theater self.theater = theater
@property
def targets(self) -> Iterator[FlightWaypoint]:
for waypoint in self.flight.waypoints:
if waypoint.waypoint_type == FlightWaypointType.TARGET_POINT:
yield waypoint
def write(self, path: Path) -> None: def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard) writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
if self.flight.custom_name is not None: if self.flight.custom_name is not None:
@ -447,7 +503,7 @@ class TargetInfoPage(KneeboardPage):
def target_info_row(self, target: FlightWaypoint) -> List[str]: def target_info_row(self, target: FlightWaypoint) -> List[str]:
ll = self.theater.point_to_ll(target.position) ll = self.theater.point_to_ll(target.position)
return [target.pretty_name, f"{ll.latitude} {ll.longitude}"] return [target.pretty_name, self.format_ll(ll)]
class KneeboardGenerator(MissionInfoGenerator): class KneeboardGenerator(MissionInfoGenerator):
@ -491,17 +547,12 @@ class KneeboardGenerator(MissionInfoGenerator):
) )
return all_flights return all_flights
def generate_target_page(self, flight: FlightData) -> Optional[KneeboardPage]: def generate_task_page(self, flight: FlightData) -> Optional[KneeboardPage]:
target_waypoints = [ if flight.flight_type in (FlightType.DEAD, FlightType.SEAD):
w return SeadTaskPage(flight, self.dark_kneeboard, self.game.theater)
for w in flight.waypoints elif flight.flight_type is FlightType.STRIKE:
if w.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC return StrikeTaskPage(flight, self.dark_kneeboard, self.game.theater)
] return None
if not target_waypoints:
return None
return TargetInfoPage(
flight, target_waypoints, self.dark_kneeboard, self.game.theater
)
def generate_flight_kneeboard(self, flight: FlightData) -> List[KneeboardPage]: def generate_flight_kneeboard(self, flight: FlightData) -> List[KneeboardPage]:
"""Returns a list of kneeboard pages for the given flight.""" """Returns a list of kneeboard pages for the given flight."""
@ -517,7 +568,7 @@ class KneeboardGenerator(MissionInfoGenerator):
), ),
] ]
if (target_page := self.generate_target_page(flight)) is not None: if (target_page := self.generate_task_page(flight)) is not None:
pages.append(target_page) pages.append(target_page)
return pages return pages