diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 90a91500..99cf8427 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -372,17 +372,26 @@ class FlightPlanner: egress_heading = heading - 180 - 25 ingress_pos = location.position.point_from_heading(ingress_heading, self.doctrine["INGRESS_EGRESS_DISTANCE"]) - ingress_point = FlightWaypoint(ingress_pos.x, ingress_pos.y, self.doctrine["INGRESS_ALT"]) + ingress_point = FlightWaypoint( + FlightWaypointType.INGRESS_STRIKE, + ingress_pos.x, + ingress_pos.y, + self.doctrine["INGRESS_ALT"] + ) ingress_point.pretty_name = "INGRESS on " + location.obj_name ingress_point.description = "INGRESS on " + location.obj_name ingress_point.name = "INGRESS" - ingress_point.waypoint_type = FlightWaypointType.INGRESS_STRIKE flight.points.append(ingress_point) if len(location.groups) > 0 and location.dcs_identifier == "AA": for g in location.groups: for j, u in enumerate(g.units): - point = FlightWaypoint(u.position.x, u.position.y, 0) + point = FlightWaypoint( + FlightWaypointType.TARGET_POINT, + u.position.x, + u.position.y, + 0 + ) point.description = "STRIKE " + "[" + str(location.obj_name) + "] : " + u.type + " #" + str(j) point.pretty_name = "STRIKE " + "[" + str(location.obj_name) + "] : " + u.type + " #" + str(j) point.name = location.obj_name + "#" + str(j) @@ -398,7 +407,12 @@ class FlightPlanner: if building.is_dead: continue - point = FlightWaypoint(building.position.x, building.position.y, 0) + point = FlightWaypoint( + FlightWaypointType.TARGET_POINT, + building.position.x, + building.position.y, + 0 + ) point.description = "STRIKE on " + building.obj_name + " " + building.category + " [" + str(building.dcs_identifier) + " ]" point.pretty_name = "STRIKE on " + building.obj_name + " " + building.category + " [" + str(building.dcs_identifier) + " ]" point.name = building.obj_name @@ -406,7 +420,12 @@ class FlightPlanner: ingress_point.targets.append(building) flight.points.append(point) else: - point = FlightWaypoint(location.position.x, location.position.y, 0) + point = FlightWaypoint( + FlightWaypointType.TARGET_GROUP_LOC, + location.position.x, + location.position.y, + 0 + ) point.description = "STRIKE on " + location.obj_name point.pretty_name = "STRIKE on " + location.obj_name point.name = location.obj_name @@ -415,11 +434,15 @@ class FlightPlanner: flight.points.append(point) egress_pos = location.position.point_from_heading(egress_heading, self.doctrine["INGRESS_EGRESS_DISTANCE"]) - egress_point = FlightWaypoint(egress_pos.x, egress_pos.y, self.doctrine["EGRESS_ALT"]) + egress_point = FlightWaypoint( + FlightWaypointType.EGRESS, + egress_pos.x, + egress_pos.y, + self.doctrine["EGRESS_ALT"] + ) egress_point.name = "EGRESS" egress_point.pretty_name = "EGRESS from " + location.obj_name egress_point.description = "EGRESS from " + location.obj_name - egress_point.waypoint_type = FlightWaypointType.EGRESS flight.points.append(egress_point) descend = self.generate_descend_point(flight.from_cp) @@ -454,18 +477,26 @@ class FlightPlanner: ascend = self.generate_ascend_point(flight.from_cp) flight.points.append(ascend) - orbit0 = FlightWaypoint(orbit0p.x, orbit0p.y, patrol_alt) + orbit0 = FlightWaypoint( + FlightWaypointType.PATROL_TRACK, + orbit0p.x, + orbit0p.y, + patrol_alt + ) orbit0.name = "ORBIT 0" orbit0.description = "Standby between this point and the next one" orbit0.pretty_name = "Race-track start" - orbit0.waypoint_type = FlightWaypointType.PATROL_TRACK flight.points.append(orbit0) - orbit1 = FlightWaypoint(orbit1p.x, orbit1p.y, patrol_alt) + orbit1 = FlightWaypoint( + FlightWaypointType.PATROL, + orbit1p.x, + orbit1p.y, + patrol_alt + ) orbit1.name = "ORBIT 1" orbit1.description = "Standby between this point and the previous one" orbit1.pretty_name = "Race-track end" - orbit1.waypoint_type = FlightWaypointType.PATROL flight.points.append(orbit1) orbit0.targets.append(for_cp) @@ -512,18 +543,26 @@ class FlightPlanner: ascend = self.generate_ascend_point(flight.from_cp) flight.points.append(ascend) - orbit0 = FlightWaypoint(orbit0p.x, orbit0p.y, patrol_alt) + orbit0 = FlightWaypoint( + FlightWaypointType.PATROL_TRACK, + orbit0p.x, + orbit0p.y, + patrol_alt + ) orbit0.name = "ORBIT 0" orbit0.description = "Standby between this point and the next one" orbit0.pretty_name = "Race-track start" - orbit0.waypoint_type = FlightWaypointType.PATROL_TRACK flight.points.append(orbit0) - orbit1 = FlightWaypoint(orbit1p.x, orbit1p.y, patrol_alt) + orbit1 = FlightWaypoint( + FlightWaypointType.PATROL, + orbit1p.x, + orbit1p.y, + patrol_alt + ) orbit1.name = "ORBIT 1" orbit1.description = "Standby between this point and the previous one" orbit1.pretty_name = "Race-track end" - orbit1.waypoint_type = FlightWaypointType.PATROL flight.points.append(orbit1) # Note : Targets of a PATROL TRACK waypoints are the points to be defended @@ -555,49 +594,67 @@ class FlightPlanner: egress_heading = heading - 180 - 25 ingress_pos = location.position.point_from_heading(ingress_heading, self.doctrine["INGRESS_EGRESS_DISTANCE"]) - ingress_point = FlightWaypoint(ingress_pos.x, ingress_pos.y, self.doctrine["INGRESS_ALT"]) + ingress_point = FlightWaypoint( + FlightWaypointType.INGRESS_SEAD, + ingress_pos.x, + ingress_pos.y, + self.doctrine["INGRESS_ALT"] + ) ingress_point.name = "INGRESS" ingress_point.pretty_name = "INGRESS on " + location.obj_name ingress_point.description = "INGRESS on " + location.obj_name - ingress_point.waypoint_type = FlightWaypointType.INGRESS_SEAD flight.points.append(ingress_point) if len(custom_targets) > 0: for target in custom_targets: - point = FlightWaypoint(target.position.x, target.position.y, 0) + point = FlightWaypoint( + FlightWaypointType.TARGET_POINT, + target.position.x, + target.position.y, + 0 + ) point.alt_type = "RADIO" if flight.flight_type == FlightType.DEAD: - point.description = "SEAD on " + target.type - point.pretty_name = "SEAD on " + location.obj_name - point.only_for_player = True - else: - point.description = "DEAD on " + location.obj_name + point.description = "DEAD on " + target.type point.pretty_name = "DEAD on " + location.obj_name point.only_for_player = True + else: + point.description = "SEAD on " + location.obj_name + point.pretty_name = "SEAD on " + location.obj_name + point.only_for_player = True + flight.points.append(point) ingress_point.targets.append(location) ingress_point.targetGroup = location - flight.points.append(point) else: - point = FlightWaypoint(location.position.x, location.position.y, 0) + point = FlightWaypoint( + FlightWaypointType.TARGET_GROUP_LOC, + location.position.x, + location.position.y, + 0 + ) point.alt_type = "RADIO" if flight.flight_type == FlightType.DEAD: - point.description = "SEAD on " + location.obj_name - point.pretty_name = "SEAD on " + location.obj_name - point.only_for_player = True - else: point.description = "DEAD on " + location.obj_name point.pretty_name = "DEAD on " + location.obj_name point.only_for_player = True + else: + point.description = "SEAD on " + location.obj_name + point.pretty_name = "SEAD on " + location.obj_name + point.only_for_player = True ingress_point.targets.append(location) ingress_point.targetGroup = location flight.points.append(point) egress_pos = location.position.point_from_heading(egress_heading, self.doctrine["INGRESS_EGRESS_DISTANCE"]) - egress_point = FlightWaypoint(egress_pos.x, egress_pos.y, self.doctrine["EGRESS_ALT"]) + egress_point = FlightWaypoint( + FlightWaypointType.EGRESS, + egress_pos.x, + egress_pos.y, + self.doctrine["EGRESS_ALT"] + ) egress_point.name = "EGRESS" egress_point.pretty_name = "EGRESS from " + location.obj_name egress_point.description = "EGRESS from " + location.obj_name - egress_point.waypoint_type = FlightWaypointType.EGRESS flight.points.append(egress_point) descend = self.generate_descend_point(flight.from_cp) @@ -628,28 +685,40 @@ class FlightPlanner: ascend.alt = 500 flight.points.append(ascend) - ingress_point = FlightWaypoint(ingress.x, ingress.y, cap_alt) + ingress_point = FlightWaypoint( + FlightWaypointType.INGRESS_CAS, + ingress.x, + ingress.y, + cap_alt + ) ingress_point.alt_type = "RADIO" ingress_point.name = "INGRESS" ingress_point.pretty_name = "INGRESS" ingress_point.description = "Ingress into CAS area" - ingress_point.waypoint_type = FlightWaypointType.INGRESS_CAS flight.points.append(ingress_point) - center_point = FlightWaypoint(center.x, center.y, cap_alt) + center_point = FlightWaypoint( + FlightWaypointType.CAS, + center.x, + center.y, + cap_alt + ) center_point.alt_type = "RADIO" center_point.description = "Provide CAS" center_point.name = "CAS" center_point.pretty_name = "CAS" - center_point.waypoint_type = FlightWaypointType.CAS flight.points.append(center_point) - egress_point = FlightWaypoint(egress.x, egress.y, cap_alt) + egress_point = FlightWaypoint( + FlightWaypointType.EGRESS, + egress.x, + egress.y, + cap_alt + ) egress_point.alt_type = "RADIO" egress_point.description = "Egress from CAS area" egress_point.name = "EGRESS" egress_point.pretty_name = "EGRESS" - egress_point.waypoint_type = FlightWaypointType.EGRESS flight.points.append(egress_point) descend = self.generate_descend_point(flight.from_cp) @@ -660,7 +729,6 @@ class FlightPlanner: rtb = self.generate_rtb_waypoint(flight.from_cp) flight.points.append(rtb) - def generate_ascend_point(self, from_cp): """ Generate ascend point @@ -669,15 +737,18 @@ class FlightPlanner: """ ascend_heading = from_cp.heading pos_ascend = from_cp.position.point_from_heading(ascend_heading, 10000) - ascend = FlightWaypoint(pos_ascend.x, pos_ascend.y, self.doctrine["PATTERN_ALTITUDE"]) + ascend = FlightWaypoint( + FlightWaypointType.ASCEND_POINT, + pos_ascend.x, + pos_ascend.y, + self.doctrine["PATTERN_ALTITUDE"] + ) ascend.name = "ASCEND" ascend.alt_type = "RADIO" ascend.description = "Ascend" ascend.pretty_name = "Ascend" - ascend.waypoint_type = FlightWaypointType.ASCEND_POINT return ascend - def generate_descend_point(self, from_cp): """ Generate approach/descend point @@ -686,15 +757,18 @@ class FlightPlanner: """ ascend_heading = from_cp.heading descend = from_cp.position.point_from_heading(ascend_heading - 180, 10000) - descend = FlightWaypoint(descend.x, descend.y, self.doctrine["PATTERN_ALTITUDE"]) + descend = FlightWaypoint( + FlightWaypointType.DESCENT_POINT, + descend.x, + descend.y, + self.doctrine["PATTERN_ALTITUDE"] + ) descend.name = "DESCEND" descend.alt_type = "RADIO" descend.description = "Descend to pattern alt" descend.pretty_name = "Descend to pattern alt" - descend.waypoint_type = FlightWaypointType.DESCENT_POINT return descend - def generate_rtb_waypoint(self, from_cp): """ Generate RTB landing point @@ -702,10 +776,14 @@ class FlightPlanner: :return: """ rtb = from_cp.position - rtb = FlightWaypoint(rtb.x, rtb.y, 0) + rtb = FlightWaypoint( + FlightWaypointType.LANDING_POINT, + rtb.x, + rtb.y, + 0 + ) rtb.name = "LANDING" rtb.alt_type = "RADIO" rtb.description = "RTB" rtb.pretty_name = "RTB" - rtb.waypoint_type = FlightWaypointType.LANDING_POINT - return rtb \ No newline at end of file + return rtb diff --git a/gen/flights/flight.py b/gen/flights/flight.py index d00b4783..0c5c7956 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -62,7 +62,9 @@ class PredefinedWaypointCategory(Enum): class FlightWaypoint: - def __init__(self, x: float, y: float, alt=0): + def __init__(self, waypoint_type: FlightWaypointType, x: float, y: float, + alt: int = 0) -> None: + self.waypoint_type = waypoint_type self.x = x self.y = y self.alt = alt @@ -73,8 +75,7 @@ class FlightWaypoint: self.targetGroup = None self.obj_name = "" self.pretty_name = "" - self.waypoint_type = FlightWaypointType.TAKEOFF # type: FlightWaypointType - self.category = PredefinedWaypointCategory.NOT_PREDEFINED# type: PredefinedWaypointCategory + self.category: PredefinedWaypointCategory = PredefinedWaypointCategory.NOT_PREDEFINED self.only_for_player = False self.data = None diff --git a/gen/kneeboard.py b/gen/kneeboard.py index 4bc5d64d..d96dc466 100644 --- a/gen/kneeboard.py +++ b/gen/kneeboard.py @@ -23,19 +23,21 @@ only be added per airframe, so PvP missions where each side have the same aircraft will be able to see the enemy's kneeboard for the same airframe. """ from collections import defaultdict +from dataclasses import dataclass from pathlib import Path from typing import Dict, List, Optional, Tuple from PIL import Image, ImageDraw, ImageFont -from tabulate import tabulate - from dcs.mission import Mission from dcs.unittype import FlyingType +from tabulate import tabulate + from . import units from .aircraft import FlightData from .airfields import RunwayData from .airsupportgen import AwacsInfo, TankerInfo from .briefinggen import CommInfo, JtacInfo, MissionInfoGenerator +from .flights.flight import FlightWaypoint, FlightWaypointType from .radios import RadioFrequency @@ -95,6 +97,54 @@ class KneeboardPage: raise NotImplementedError +@dataclass(frozen=True) +class NumberedWaypoint: + number: int + waypoint: FlightWaypoint + + +class FlightPlanBuilder: + def __init__(self) -> None: + self.rows: List[List[str]] = [] + self.target_points: List[NumberedWaypoint] = [] + + def add_waypoint(self, waypoint_num: int, waypoint: FlightWaypoint) -> None: + if waypoint.waypoint_type == FlightWaypointType.TARGET_POINT: + self.target_points.append(NumberedWaypoint(waypoint_num, waypoint)) + return + + if self.target_points: + self.coalesce_target_points() + self.target_points = [] + + self.add_waypoint_row(NumberedWaypoint(waypoint_num, waypoint)) + + def coalesce_target_points(self) -> None: + if len(self.target_points) <= 4: + for steerpoint in self.target_points: + self.add_waypoint_row(steerpoint) + return + + first_waypoint_num = self.target_points[0].number + last_waypoint_num = self.target_points[-1].number + + self.rows.append([ + f"{first_waypoint_num}-{last_waypoint_num}", + "Target points", + "0" + ]) + + def add_waypoint_row(self, waypoint: NumberedWaypoint) -> None: + self.rows.append([ + waypoint.number, + waypoint.waypoint.pretty_name, + str(int(units.meters_to_feet(waypoint.waypoint.alt))) + ]) + + def build(self) -> List[List[str]]: + return self.rows + + class BriefingPage(KneeboardPage): """A kneeboard page containing briefing information.""" def __init__(self, flight: FlightData, comms: List[CommInfo], @@ -122,11 +172,11 @@ class BriefingPage(KneeboardPage): ], headers=["", "Airbase", "ATC", "TCN", "I(C)LS", "RWY"]) writer.heading("Flight Plan") - flight_plan = [] + flight_plan_builder = FlightPlanBuilder() for num, waypoint in enumerate(self.flight.waypoints): - alt = int(units.meters_to_feet(waypoint.alt)) - flight_plan.append([num, waypoint.pretty_name, str(alt)]) - writer.table(flight_plan, headers=["STPT", "Action", "Alt"]) + flight_plan_builder.add_waypoint(num, waypoint) + writer.table(flight_plan_builder.build(), + headers=["STPT", "Action", "Alt"]) writer.heading("Comm Ladder") comms = [] diff --git a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py index c8721a17..079aab99 100644 --- a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py +++ b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py @@ -57,13 +57,16 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox): enemy_cp = [ecp for ecp in cp.connected_points if ecp.captured != cp.captured] for ecp in enemy_cp: pos = Conflict.frontline_position(self.game.theater, cp, ecp)[0] - wpt = FlightWaypoint(pos.x, pos.y, 800) + wpt = FlightWaypoint( + FlightWaypointType.CUSTOM, + pos.x, + pos.y, + 800) wpt.name = "Frontline " + cp.name + "/" + ecp.name + " [CAS]" wpt.alt_type = "RADIO" wpt.pretty_name = wpt.name wpt.description = "Frontline" wpt.data = [cp, ecp] - wpt.waypoint_type = FlightWaypointType.CUSTOM wpt.category = PredefinedWaypointCategory.FRONTLINE i = add_model_item(i, model, wpt.pretty_name, wpt) @@ -72,14 +75,18 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox): if (self.include_enemy and not cp.captured) or (self.include_friendly and cp.captured): for ground_object in cp.ground_objects: if not ground_object.is_dead and not ground_object.dcs_identifier == "AA": - wpt = FlightWaypoint(ground_object.position.x,ground_object.position.y, 0) + wpt = FlightWaypoint( + FlightWaypointType.CUSTOM, + ground_object.position.x, + ground_object.position.y, + 0 + ) wpt.alt_type = "RADIO" wpt.name = wpt.name = "[" + str(ground_object.obj_name) + "] : " + ground_object.category + " #" + str(ground_object.object_id) wpt.pretty_name = wpt.name wpt.obj_name = ground_object.obj_name wpt.targets.append(ground_object) wpt.data = ground_object - wpt.waypoint_type = FlightWaypointType.CUSTOM if cp.captured: wpt.description = "Friendly Building" wpt.category = PredefinedWaypointCategory.ALLY_BUILDING @@ -95,7 +102,12 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox): if not ground_object.is_dead and ground_object.dcs_identifier == "AA": for g in ground_object.groups: for j, u in enumerate(g.units): - wpt = FlightWaypoint(u.position.x, u.position.y, 0) + wpt = FlightWaypoint( + FlightWaypointType.CUSTOM, + u.position.x, + u.position.y, + 0 + ) wpt.alt_type = "RADIO" wpt.name = wpt.name = "[" + str(ground_object.obj_name) + "] : " + u.type + " #" + str(j) wpt.pretty_name = wpt.name @@ -114,11 +126,15 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox): if self.include_airbases: for cp in self.game.theater.controlpoints: if (self.include_enemy and not cp.captured) or (self.include_friendly and cp.captured): - wpt = FlightWaypoint(cp.position.x, cp.position.y, 0) + wpt = FlightWaypoint( + FlightWaypointType.CUSTOM, + cp.position.x, + cp.position.y, + 0 + ) wpt.alt_type = "RADIO" wpt.name = cp.name wpt.data = cp - wpt.waypoint_type = FlightWaypointType.CUSTOM if cp.captured: wpt.description = "Position of " + cp.name + " [Friendly Airbase]" wpt.category = PredefinedWaypointCategory.ALLY_CP @@ -133,7 +149,6 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox): else: wpt.pretty_name = cp.name + " (Airbase)" - i = add_model_item(i, model, wpt.pretty_name, wpt) self.setModel(model)