mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Implement CAP and CAS for front lines.
This commit is contained in:
parent
8a4a81a008
commit
0e1dfb8ccb
@ -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)
|
||||
|
||||
|
||||
82
qt_ui/widgets/map/QFrontLine.py
Normal file
82
qt_ui/widgets/map/QFrontLine.py
Normal 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)
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
27
theater/frontline.py
Normal 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}"
|
||||
@ -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."""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user