Implement CAP and CAS for front lines.

This commit is contained in:
Dan Albert 2020-09-24 19:32:10 -07:00
parent 8a4a81a008
commit 0e1dfb8ccb
7 changed files with 155 additions and 44 deletions

View File

@ -24,9 +24,7 @@ from gen.flights.flight import (
FlightWaypoint,
FlightWaypointType,
)
from theater.controlpoint import ControlPoint
from theater.missiontarget import MissionTarget
from theater.theatergroundobject import TheaterGroundObject
from theater import ControlPoint, FrontLine, MissionTarget, TheaterGroundObject
MISSION_DURATION = 80
@ -119,9 +117,8 @@ class FlightPlanner:
)
if len(self._get_cas_locations()) > 0:
enemy_cp = random.choice(self._get_cas_locations())
location = enemy_cp
self.generate_frontline_cap(flight, flight.from_cp, enemy_cp)
location = random.choice(self._get_cas_locations())
self.generate_frontline_cap(flight, location)
else:
location = flight.from_cp
self.generate_barcap(flight, flight.from_cp)
@ -143,7 +140,7 @@ class FlightPlanner:
self.doctrine["CAS_EVERY_X_MINUTES"] + 5)
location = random.choice(cas_locations)
self.generate_cas(flight, flight.from_cp, location)
self.generate_cas(flight, location)
self.plan_legacy_mission(flight, location)
def commission_sead(self) -> None:
@ -196,14 +193,15 @@ class FlightPlanner:
self.generate_strike(flight, location)
self.plan_legacy_mission(flight, location)
def _get_cas_locations(self):
def _get_cas_locations(self) -> List[FrontLine]:
return self._get_cas_locations_for_cp(self.from_cp)
def _get_cas_locations_for_cp(self, for_cp):
@staticmethod
def _get_cas_locations_for_cp(for_cp: ControlPoint) -> List[FrontLine]:
cas_locations = []
for cp in for_cp.connected_points:
if cp.captured != for_cp.captured:
cas_locations.append(cp)
cas_locations.append(FrontLine(for_cp, cp))
return cas_locations
def compute_strike_targets(self):
@ -428,17 +426,17 @@ class FlightPlanner:
rtb = self.generate_rtb_waypoint(flight.from_cp)
flight.points.append(rtb)
def generate_frontline_cap(self, flight: Flight,
front_line: FrontLine) -> None:
"""Generate a CAP flight plan for the given front line.
def generate_frontline_cap(self, flight, ally_cp, enemy_cp):
"""
Generate a cap flight for the frontline between ally_cp and enemy cp in order to ensure air superiority and
protect friendly CAP airbase
:param flight: Flight to setup
:param ally_cp: CP to protect
:param enemy_cp: Enemy connected cp
:param front_line: Front line to protect.
"""
ally_cp, enemy_cp = front_line.control_points
flight.flight_type = FlightType.CAP
patrol_alt = random.randint(self.doctrine["PATROL_ALT_RANGE"][0], self.doctrine["PATROL_ALT_RANGE"][1])
patrol_alt = random.randint(self.doctrine["PATROL_ALT_RANGE"][0],
self.doctrine["PATROL_ALT_RANGE"][1])
# Find targets waypoints
ingress, heading, distance = Conflict.frontline_vector(ally_cp, enemy_cp, self.game.theater)
@ -579,19 +577,21 @@ class FlightPlanner:
rtb = self.generate_rtb_waypoint(flight.from_cp)
flight.points.append(rtb)
def generate_cas(self, flight: Flight, front_line: FrontLine) -> None:
"""Generate a CAS flight plan for the given target.
def generate_cas(self, flight, from_cp, location):
"""
Generate a CAS flight at a given location
:param flight: Flight to setup
:param location: Location of the CAS targets
:param front_line: Front line containing CAS targets.
"""
from_cp, location = front_line.control_points
is_helo = hasattr(flight.unit_type, "helicopter") and flight.unit_type.helicopter
cap_alt = 1000
flight.points = []
flight.flight_type = FlightType.CAS
ingress, heading, distance = Conflict.frontline_vector(from_cp, location, self.game.theater)
ingress, heading, distance = Conflict.frontline_vector(
from_cp, location, self.game.theater
)
center = ingress.point_from_heading(heading, distance / 2)
egress = ingress.point_from_heading(heading, distance)

View File

@ -0,0 +1,82 @@
"""Common base for objects drawn on the game map."""
from typing import Optional
from PySide2.QtCore import Qt
from PySide2.QtGui import QPen
from PySide2.QtWidgets import (
QAction,
QGraphicsLineItem,
QGraphicsSceneContextMenuEvent,
QGraphicsSceneHoverEvent,
QGraphicsSceneMouseEvent,
QMenu,
)
import qt_ui.uiconstants as const
from qt_ui.dialogs import Dialog
from qt_ui.windows.mission.QPackageDialog import QNewPackageDialog
from theater.missiontarget import MissionTarget
class QFrontLine(QGraphicsLineItem):
"""Base class for objects drawn on the game map.
Game map objects have an on_click behavior that triggers on left click, and
change the mouse cursor on hover.
"""
def __init__(self, x1: float, y1: float, x2: float, y2: float,
mission_target: MissionTarget) -> None:
super().__init__(x1, y1, x2, y2)
self.mission_target = mission_target
self.new_package_dialog: Optional[QNewPackageDialog] = None
self.setAcceptHoverEvents(True)
pen = QPen(brush=const.COLORS["bright_red"])
pen.setColor(const.COLORS["orange"])
pen.setWidth(8)
self.setPen(pen)
def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
self.setCursor(Qt.PointingHandCursor)
def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
if event.button() == Qt.LeftButton:
self.on_click()
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None:
menu = QMenu("Menu")
object_details_action = QAction(self.object_dialog_text)
object_details_action.triggered.connect(self.on_click)
menu.addAction(object_details_action)
new_package_action = QAction(f"New package")
new_package_action.triggered.connect(self.open_new_package_dialog)
menu.addAction(new_package_action)
menu.exec_(event.screenPos())
@property
def object_dialog_text(self) -> str:
"""Text to for the object's dialog in the context menu.
Right clicking a map object will open a context menu and the first item
will open the details dialog for this object. This menu action has the
same behavior as the on_click event.
Return:
The text that should be displayed for the menu item.
"""
return "Details"
def on_click(self) -> None:
"""The action to take when this map object is left-clicked.
Typically this should open a details view of the object.
"""
raise NotImplementedError
def open_new_package_dialog(self) -> None:
"""Opens the dialog for planning a new mission package."""
Dialog.open_new_package_dialog(self.mission_target)

View File

@ -21,8 +21,9 @@ from qt_ui.models import GameModel
from qt_ui.widgets.map.QLiberationScene import QLiberationScene
from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint
from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject
from qt_ui.widgets.map.QFrontLine import QFrontLine
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from theater import ControlPoint
from theater import ControlPoint, FrontLine
class QLiberationMap(QGraphicsView):
@ -245,10 +246,8 @@ class QLiberationMap(QGraphicsView):
p1 = point_from_heading(pos2[0], pos2[1], h+180, 25)
p2 = point_from_heading(pos2[0], pos2[1], h, 25)
frontline_pen = QPen(brush=CONST.COLORS["bright_red"])
frontline_pen.setColor(CONST.COLORS["orange"])
frontline_pen.setWidth(8)
scene.addLine(p1[0], p1[1], p2[0], p2[1], pen=frontline_pen)
scene.addItem(QFrontLine(p1[0], p1[1], p2[0], p2[1],
FrontLine(cp, connected_cp)))
else:
scene.addLine(pos[0], pos[1], pos2[0], pos2[1], pen=pen)

View File

@ -4,7 +4,6 @@ from typing import Optional
from PySide2.QtCore import Qt, Signal
from PySide2.QtWidgets import (
QDialog,
QMessageBox,
QPushButton,
QVBoxLayout,
)
@ -20,7 +19,7 @@ from qt_ui.widgets.QLabeledWidget import QLabeledWidget
from qt_ui.widgets.combos.QAircraftTypeSelector import QAircraftTypeSelector
from qt_ui.widgets.combos.QFlightTypeComboBox import QFlightTypeComboBox
from qt_ui.widgets.combos.QOriginAirfieldSelector import QOriginAirfieldSelector
from theater import ControlPoint, TheaterGroundObject
from theater import ControlPoint, FrontLine, TheaterGroundObject
class QFlightCreator(QDialog):
@ -109,9 +108,6 @@ class QFlightCreator(QDialog):
def populate_flight_plan(self, flight: Flight, task: FlightType) -> None:
# TODO: Flesh out mission types.
# Probably most important to add, since it's a regression, is CAS. Right
# now it's not possible to frag a package on a front line though, and
# that's the only location where CAS missions are valid.
if task == FlightType.ANTISHIP:
logging.error("Anti-ship flight plan generation not implemented")
elif task == FlightType.BAI:
@ -121,7 +117,7 @@ class QFlightCreator(QDialog):
elif task == FlightType.CAP:
self.generate_cap(flight)
elif task == FlightType.CAS:
logging.error("CAS flight plan generation not implemented")
self.generate_cas(flight)
elif task == FlightType.DEAD:
self.generate_sead(flight)
elif task == FlightType.ELINT:
@ -147,14 +143,26 @@ class QFlightCreator(QDialog):
"Troop transport flight plan generation not implemented"
)
def generate_cas(self, flight: Flight) -> None:
if not isinstance(self.package.target, FrontLine):
logging.error(
"Could not create flight plan: CAS missions only valid for "
"front lines"
)
return
self.planner.generate_cas(flight, self.package.target)
def generate_cap(self, flight: Flight) -> None:
if not isinstance(self.package.target, ControlPoint):
if isinstance(self.package.target, TheaterGroundObject):
logging.error(
"Could not create flight plan: CAP missions for strike targets "
"not implemented"
)
return
self.planner.generate_barcap(flight, self.package.target)
if isinstance(self.package.target, FrontLine):
self.planner.generate_frontline_cap(flight, self.package.target)
else:
self.planner.generate_barcap(flight, self.package.target)
def generate_sead(self, flight: Flight) -> None:
self.planner.generate_sead(flight, self.package.target)

View File

@ -1,3 +1,5 @@
from .controlpoint import *
from .conflicttheater import *
from .base import *
from .conflicttheater import *
from .controlpoint import *
from .frontline import FrontLine
from .missiontarget import MissionTarget

27
theater/frontline.py Normal file
View File

@ -0,0 +1,27 @@
"""Battlefield front lines."""
from typing import Tuple
from . import ControlPoint, MissionTarget
class FrontLine(MissionTarget):
"""Defines a front line location between two control points.
Front lines are the area where ground combat happens.
"""
def __init__(self, control_point_a: ControlPoint,
control_point_b: ControlPoint) -> None:
self.control_point_a = control_point_a
self.control_point_b = control_point_b
@property
def control_points(self) -> Tuple[ControlPoint, ControlPoint]:
"""Returns a tuple of the two control points."""
return self.control_point_a, self.control_point_b
@property
def name(self) -> str:
a = self.control_point_a.name
b = self.control_point_b.name
return f"Front line {a}/{b}"

View File

@ -1,7 +1,5 @@
from abc import ABC, abstractmethod
from dcs.mapping import Point
class MissionTarget(ABC):
# TODO: These should just be required objects to the constructor
@ -11,8 +9,3 @@ class MissionTarget(ABC):
@abstractmethod
def name(self) -> str:
"""The name of the mission target."""
@property
@abstractmethod
def position(self) -> Point:
"""The position of the mission target."""