mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Set up split/join points.
This commit is contained in:
parent
56a5864600
commit
6ce82be46b
@ -14,9 +14,13 @@ class Doctrine:
|
||||
strike_max_range: int
|
||||
sead_max_range: int
|
||||
|
||||
rendezvous_altitude: int
|
||||
join_distance: int
|
||||
split_distance: int
|
||||
ingress_egress_distance: int
|
||||
ingress_altitude: int
|
||||
egress_altitude: int
|
||||
|
||||
min_patrol_altitude: int
|
||||
max_patrol_altitude: int
|
||||
pattern_altitude: int
|
||||
@ -35,6 +39,9 @@ MODERN_DOCTRINE = Doctrine(
|
||||
antiship=True,
|
||||
strike_max_range=1500000,
|
||||
sead_max_range=1500000,
|
||||
rendezvous_altitude=feet_to_meter(25000),
|
||||
join_distance=nm_to_meter(20),
|
||||
split_distance=nm_to_meter(20),
|
||||
ingress_egress_distance=nm_to_meter(45),
|
||||
ingress_altitude=feet_to_meter(20000),
|
||||
egress_altitude=feet_to_meter(20000),
|
||||
@ -55,6 +62,9 @@ COLDWAR_DOCTRINE = Doctrine(
|
||||
antiship=True,
|
||||
strike_max_range=1500000,
|
||||
sead_max_range=1500000,
|
||||
rendezvous_altitude=feet_to_meter(22000),
|
||||
join_distance=nm_to_meter(10),
|
||||
split_distance=nm_to_meter(10),
|
||||
ingress_egress_distance=nm_to_meter(30),
|
||||
ingress_altitude=feet_to_meter(18000),
|
||||
egress_altitude=feet_to_meter(18000),
|
||||
@ -75,6 +85,9 @@ WWII_DOCTRINE = Doctrine(
|
||||
antiship=True,
|
||||
strike_max_range=1500000,
|
||||
sead_max_range=1500000,
|
||||
join_distance=nm_to_meter(5),
|
||||
split_distance=nm_to_meter(5),
|
||||
rendezvous_altitude=feet_to_meter(10000),
|
||||
ingress_egress_distance=nm_to_meter(7),
|
||||
ingress_altitude=feet_to_meter(8000),
|
||||
egress_altitude=feet_to_meter(8000),
|
||||
|
||||
@ -89,6 +89,7 @@ class Game:
|
||||
)
|
||||
|
||||
self.sanitize_sides()
|
||||
self.on_load()
|
||||
|
||||
|
||||
def sanitize_sides(self):
|
||||
@ -204,9 +205,10 @@ class Game:
|
||||
else:
|
||||
return event and event.name and event.name == self.player_name
|
||||
|
||||
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint] = None):
|
||||
def on_load(self) -> None:
|
||||
ObjectiveDistanceCache.set_theater(self.theater)
|
||||
|
||||
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint] = None):
|
||||
logging.info("Pass turn")
|
||||
self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0))
|
||||
self.turn = self.turn + 1
|
||||
|
||||
@ -13,6 +13,7 @@ from dataclasses import dataclass, field
|
||||
import logging
|
||||
from typing import Dict, Iterator, List, Optional
|
||||
|
||||
from dcs.mapping import Point
|
||||
from .flights.flight import Flight, FlightType
|
||||
from theater.missiontarget import MissionTarget
|
||||
|
||||
@ -39,6 +40,11 @@ class Package:
|
||||
#: The set of flights in the package.
|
||||
flights: List[Flight] = field(default_factory=list)
|
||||
|
||||
join_point: Optional[Point] = field(default=None, init=False, hash=False)
|
||||
split_point: Optional[Point] = field(default=None, init=False, hash=False)
|
||||
ingress_point: Optional[Point] = field(default=None, init=False, hash=False)
|
||||
egress_point: Optional[Point] = field(default=None, init=False, hash=False)
|
||||
|
||||
def add_flight(self, flight: Flight) -> None:
|
||||
"""Adds a flight to the package."""
|
||||
self.flights.append(flight)
|
||||
@ -46,6 +52,9 @@ class Package:
|
||||
def remove_flight(self, flight: Flight) -> None:
|
||||
"""Removes a flight from the package."""
|
||||
self.flights.remove(flight)
|
||||
if not self.flights:
|
||||
self.ingress_point = None
|
||||
self.egress_point = None
|
||||
|
||||
@property
|
||||
def primary_task(self) -> Optional[FlightType]:
|
||||
|
||||
@ -414,9 +414,9 @@ class CoalitionMissionPlanner:
|
||||
return
|
||||
|
||||
package = builder.build()
|
||||
builder = FlightPlanBuilder(self.game, self.is_player, package)
|
||||
builder = FlightPlanBuilder(self.game, package, self.is_player)
|
||||
for flight in package.flights:
|
||||
builder.populate_flight_plan(flight, package.target)
|
||||
builder.populate_flight_plan(flight)
|
||||
self.ato.add_package(package)
|
||||
|
||||
def message(self, title, text) -> None:
|
||||
|
||||
@ -47,6 +47,8 @@ class FlightWaypointType(Enum):
|
||||
TARGET_GROUP_LOC = 13 # A target group approximate location
|
||||
TARGET_SHIP = 14 # A target ship known location
|
||||
CUSTOM = 15 # User waypoint (no specific behaviour)
|
||||
JOIN = 16
|
||||
SPLIT = 17
|
||||
|
||||
|
||||
class PredefinedWaypointCategory(Enum):
|
||||
|
||||
@ -38,8 +38,7 @@ class InvalidObjectiveLocation(RuntimeError):
|
||||
class FlightPlanBuilder:
|
||||
"""Generates flight plans for flights."""
|
||||
|
||||
def __init__(self, game: Game, is_player: bool,
|
||||
package: Optional[Package] = None) -> None:
|
||||
def __init__(self, game: Game, package: Package, is_player: bool) -> None:
|
||||
self.game = game
|
||||
self.package = package
|
||||
self.is_player = is_player
|
||||
@ -49,9 +48,15 @@ class FlightPlanBuilder:
|
||||
faction = self.game.enemy_faction
|
||||
self.doctrine: Doctrine = faction.get("doctrine", MODERN_DOCTRINE)
|
||||
|
||||
def populate_flight_plan(self, flight: Flight,
|
||||
objective_location: MissionTarget) -> None:
|
||||
def populate_flight_plan(
|
||||
self, flight: Flight,
|
||||
# TODO: Custom targets should be an attribute of the flight.
|
||||
custom_targets: Optional[List[Unit]] = None) -> None:
|
||||
"""Creates a default flight plan for the given mission."""
|
||||
if flight not in self.package.flights:
|
||||
raise RuntimeError("Flight must be a part of the package")
|
||||
self.generate_missing_package_waypoints()
|
||||
|
||||
# TODO: Flesh out mission types.
|
||||
try:
|
||||
task = flight.flight_type
|
||||
@ -62,17 +67,17 @@ class FlightPlanBuilder:
|
||||
elif task == FlightType.BAI:
|
||||
logging.error("BAI flight plan generation not implemented")
|
||||
elif task == FlightType.BARCAP:
|
||||
self.generate_barcap(flight, objective_location)
|
||||
self.generate_barcap(flight)
|
||||
elif task == FlightType.CAP:
|
||||
self.generate_barcap(flight, objective_location)
|
||||
self.generate_barcap(flight)
|
||||
elif task == FlightType.CAS:
|
||||
self.generate_cas(flight, objective_location)
|
||||
self.generate_cas(flight)
|
||||
elif task == FlightType.DEAD:
|
||||
self.generate_sead(flight, objective_location)
|
||||
self.generate_sead(flight, custom_targets)
|
||||
elif task == FlightType.ELINT:
|
||||
logging.error("ELINT flight plan generation not implemented")
|
||||
elif task == FlightType.ESCORT:
|
||||
self.generate_escort(flight, objective_location)
|
||||
self.generate_escort(flight)
|
||||
elif task == FlightType.EVAC:
|
||||
logging.error("Evac flight plan generation not implemented")
|
||||
elif task == FlightType.EWAR:
|
||||
@ -88,11 +93,11 @@ class FlightPlanBuilder:
|
||||
elif task == FlightType.RECON:
|
||||
logging.error("Recon flight plan generation not implemented")
|
||||
elif task == FlightType.SEAD:
|
||||
self.generate_sead(flight, objective_location)
|
||||
self.generate_sead(flight, custom_targets)
|
||||
elif task == FlightType.STRIKE:
|
||||
self.generate_strike(flight, objective_location)
|
||||
self.generate_strike(flight)
|
||||
elif task == FlightType.TARCAP:
|
||||
self.generate_frontline_cap(flight, objective_location)
|
||||
self.generate_frontline_cap(flight)
|
||||
elif task == FlightType.TROOP_TRANSPORT:
|
||||
logging.error(
|
||||
"Troop transport flight plan generation not implemented"
|
||||
@ -100,23 +105,32 @@ class FlightPlanBuilder:
|
||||
except InvalidObjectiveLocation as ex:
|
||||
logging.error(f"Could not create flight plan: {ex}")
|
||||
|
||||
def generate_strike(self, flight: Flight, location: MissionTarget) -> None:
|
||||
def generate_missing_package_waypoints(self) -> None:
|
||||
if self.package.ingress_point is None:
|
||||
self.package.ingress_point = self._ingress_point()
|
||||
if self.package.egress_point is None:
|
||||
self.package.egress_point = self._egress_point()
|
||||
if self.package.join_point is None:
|
||||
self.package.join_point = self._join_point()
|
||||
if self.package.split_point is None:
|
||||
self.package.split_point = self._split_point()
|
||||
|
||||
def generate_strike(self, flight: Flight) -> None:
|
||||
"""Generates a strike flight plan.
|
||||
|
||||
Args:
|
||||
flight: The flight to generate the flight plan for.
|
||||
location: The strike target location.
|
||||
"""
|
||||
location = self.package.target
|
||||
|
||||
# TODO: Support airfield strikes.
|
||||
if not isinstance(location, TheaterGroundObject):
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
# TODO: Stop clobbering flight type.
|
||||
flight.flight_type = FlightType.STRIKE
|
||||
|
||||
builder = WaypointBuilder(self.doctrine)
|
||||
builder.ascent(flight.from_cp)
|
||||
builder.ingress_strike(self.ingress_point(flight, location), location)
|
||||
builder.join(self.package.join_point)
|
||||
builder.ingress_strike(self.package.ingress_point, location)
|
||||
|
||||
if len(location.groups) > 0 and location.dcs_identifier == "AA":
|
||||
# TODO: Replace with DEAD?
|
||||
@ -143,26 +157,23 @@ class FlightPlanBuilder:
|
||||
location
|
||||
)
|
||||
|
||||
builder.egress(self.egress_point(flight, location), location)
|
||||
builder.egress(self.package.egress_point, location)
|
||||
builder.split(self.package.split_point)
|
||||
builder.rtb(flight.from_cp)
|
||||
|
||||
flight.points = builder.build()
|
||||
|
||||
def generate_barcap(self, flight: Flight, location: MissionTarget) -> None:
|
||||
def generate_barcap(self, flight: Flight) -> None:
|
||||
"""Generate a BARCAP flight at a given location.
|
||||
|
||||
Args:
|
||||
flight: The flight to generate the flight plan for.
|
||||
location: The control point to protect.
|
||||
"""
|
||||
location = self.package.target
|
||||
|
||||
if isinstance(location, FrontLine):
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
if isinstance(location, ControlPoint) and location.is_carrier:
|
||||
flight.flight_type = FlightType.BARCAP
|
||||
else:
|
||||
flight.flight_type = FlightType.CAP
|
||||
|
||||
patrol_alt = random.randint(
|
||||
self.doctrine.min_patrol_altitude,
|
||||
self.doctrine.max_patrol_altitude
|
||||
@ -198,19 +209,18 @@ class FlightPlanBuilder:
|
||||
builder.rtb(flight.from_cp)
|
||||
flight.points = builder.build()
|
||||
|
||||
def generate_frontline_cap(self, flight: Flight,
|
||||
location: MissionTarget) -> None:
|
||||
def generate_frontline_cap(self, flight: Flight) -> None:
|
||||
"""Generate a CAP flight plan for the given front line.
|
||||
|
||||
Args:
|
||||
flight: The flight to generate the flight plan for.
|
||||
location: Front line to protect.
|
||||
"""
|
||||
location = self.package.target
|
||||
|
||||
if not isinstance(location, FrontLine):
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
ally_cp, enemy_cp = location.control_points
|
||||
flight.flight_type = FlightType.CAP
|
||||
patrol_alt = random.randint(self.doctrine.min_patrol_altitude,
|
||||
self.doctrine.max_patrol_altitude)
|
||||
|
||||
@ -240,26 +250,26 @@ class FlightPlanBuilder:
|
||||
builder.rtb(flight.from_cp)
|
||||
flight.points = builder.build()
|
||||
|
||||
def generate_sead(self, flight: Flight, location: MissionTarget,
|
||||
custom_targets: Optional[List[Unit]] = None) -> None:
|
||||
def generate_sead(self, flight: Flight,
|
||||
custom_targets: Optional[List[Unit]]) -> None:
|
||||
"""Generate a SEAD/DEAD flight at a given location.
|
||||
|
||||
Args:
|
||||
flight: The flight to generate the flight plan for.
|
||||
location: Location of the SAM site.
|
||||
custom_targets: Specific radar equipped units selected by the user.
|
||||
"""
|
||||
location = self.package.target
|
||||
|
||||
if not isinstance(location, TheaterGroundObject):
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
if custom_targets is None:
|
||||
custom_targets = []
|
||||
|
||||
flight.flight_type = random.choice([FlightType.SEAD, FlightType.DEAD])
|
||||
|
||||
builder = WaypointBuilder(self.doctrine)
|
||||
builder.ascent(flight.from_cp)
|
||||
builder.ingress_sead(self.ingress_point(flight, location), location)
|
||||
builder.join(self.package.join_point)
|
||||
builder.ingress_sead(self.package.ingress_point, location)
|
||||
|
||||
# TODO: Unify these.
|
||||
# There doesn't seem to be any reason to treat the UI fragged missions
|
||||
@ -283,14 +293,13 @@ class FlightPlanBuilder:
|
||||
else:
|
||||
builder.sead_area(location)
|
||||
|
||||
builder.egress(self.egress_point(flight, location), location)
|
||||
builder.egress(self.package.egress_point, location)
|
||||
builder.split(self.package.split_point)
|
||||
builder.rtb(flight.from_cp)
|
||||
|
||||
flight.points = builder.build()
|
||||
|
||||
def generate_escort(self, flight: Flight, location: MissionTarget) -> None:
|
||||
flight.flight_type = FlightType.ESCORT
|
||||
|
||||
def generate_escort(self, flight: Flight) -> None:
|
||||
# TODO: Decide common waypoints for the package ahead of time.
|
||||
# Packages should determine some common points like push, ingress,
|
||||
# egress, and split points ahead of time so they can be shared by all
|
||||
@ -303,25 +312,30 @@ class FlightPlanBuilder:
|
||||
|
||||
builder = WaypointBuilder(self.doctrine)
|
||||
builder.ascent(flight.from_cp)
|
||||
builder.race_track(self.ingress_point(flight, location),
|
||||
self.egress_point(flight, location), patrol_alt)
|
||||
builder.join(self.package.join_point)
|
||||
builder.race_track(
|
||||
self.package.ingress_point,
|
||||
self.package.egress_point,
|
||||
patrol_alt
|
||||
)
|
||||
builder.split(self.package.split_point)
|
||||
builder.rtb(flight.from_cp)
|
||||
|
||||
flight.points = builder.build()
|
||||
|
||||
def generate_cas(self, flight: Flight, location: MissionTarget) -> None:
|
||||
def generate_cas(self, flight: Flight) -> None:
|
||||
"""Generate a CAS flight plan for the given target.
|
||||
|
||||
Args:
|
||||
flight: The flight to generate the flight plan for.
|
||||
location: Front line with CAS targets.
|
||||
"""
|
||||
location = self.package.target
|
||||
|
||||
if not isinstance(location, FrontLine):
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
is_helo = getattr(flight.unit_type, "helicopter", False)
|
||||
cap_alt = 500 if is_helo else 1000
|
||||
flight.flight_type = FlightType.CAS
|
||||
|
||||
ingress, heading, distance = Conflict.frontline_vector(
|
||||
location.control_points[0], location.control_points[1],
|
||||
@ -332,9 +346,11 @@ class FlightPlanBuilder:
|
||||
|
||||
builder = WaypointBuilder(self.doctrine)
|
||||
builder.ascent(flight.from_cp, is_helo)
|
||||
builder.join(self.package.join_point)
|
||||
builder.ingress_cas(ingress, location)
|
||||
builder.cas(center, cap_alt)
|
||||
builder.egress(egress, location)
|
||||
builder.split(self.package.split_point)
|
||||
builder.rtb(flight.from_cp, is_helo)
|
||||
|
||||
flight.points = builder.build()
|
||||
@ -370,33 +386,51 @@ class FlightPlanBuilder:
|
||||
builder.land(arrival)
|
||||
return builder.build()[0]
|
||||
|
||||
def ingress_point(self, flight: Flight, target: MissionTarget) -> Point:
|
||||
heading = self._heading_to_package_airfield(flight, target)
|
||||
return target.position.point_from_heading(
|
||||
def _join_point(self) -> Point:
|
||||
ingress_point = self.package.ingress_point
|
||||
heading = self._heading_to_package_airfield(ingress_point)
|
||||
return ingress_point.point_from_heading(heading,
|
||||
-self.doctrine.join_distance)
|
||||
|
||||
def _split_point(self) -> Point:
|
||||
egress_point = self.package.egress_point
|
||||
heading = self._heading_to_package_airfield(egress_point)
|
||||
return egress_point.point_from_heading(heading,
|
||||
-self.doctrine.split_distance)
|
||||
|
||||
def _ingress_point(self) -> Point:
|
||||
heading = self._target_heading_to_package_airfield()
|
||||
return self.package.target.position.point_from_heading(
|
||||
heading - 180 + 25, self.doctrine.ingress_egress_distance
|
||||
)
|
||||
|
||||
def egress_point(self, flight: Flight, target: MissionTarget) -> Point:
|
||||
heading = self._heading_to_package_airfield(flight, target)
|
||||
return target.position.point_from_heading(
|
||||
def _egress_point(self) -> Point:
|
||||
heading = self._target_heading_to_package_airfield()
|
||||
return self.package.target.position.point_from_heading(
|
||||
heading - 180 - 25, self.doctrine.ingress_egress_distance
|
||||
)
|
||||
|
||||
def _heading_to_package_airfield(self, flight: Flight,
|
||||
target: MissionTarget) -> int:
|
||||
airfield = self.package_airfield(flight, target)
|
||||
return airfield.position.heading_between_point(target.position)
|
||||
def _target_heading_to_package_airfield(self) -> int:
|
||||
return self._heading_to_package_airfield(self.package.target.position)
|
||||
|
||||
def _heading_to_package_airfield(self, point: Point) -> int:
|
||||
return self.package_airfield().position.heading_between_point(point)
|
||||
|
||||
# TODO: Set ingress/egress/join/split points in the Package.
|
||||
def package_airfield(self, flight: Flight,
|
||||
target: MissionTarget) -> ControlPoint:
|
||||
def package_airfield(self) -> ControlPoint:
|
||||
# We'll always have a package, but if this is being planned via the UI
|
||||
# it could be the first flight in the package.
|
||||
if not self.package.flights:
|
||||
raise RuntimeError(
|
||||
"Cannot determine source airfield for package with no flights"
|
||||
)
|
||||
|
||||
# The package airfield is either the flight's airfield (when there is no
|
||||
# package) or the closest airfield to the objective that is the
|
||||
# departure airfield for some flight in the package.
|
||||
if self.package is None:
|
||||
return flight.from_cp
|
||||
|
||||
cache = ObjectiveDistanceCache.get_closest_airfields(target)
|
||||
cache = ObjectiveDistanceCache.get_closest_airfields(
|
||||
self.package.target
|
||||
)
|
||||
for airfield in cache.closest_airfields:
|
||||
for flight in self.package.flights:
|
||||
if flight.from_cp == airfield:
|
||||
|
||||
@ -86,6 +86,30 @@ class WaypointBuilder:
|
||||
waypoint.pretty_name = "Land"
|
||||
self.waypoints.append(waypoint)
|
||||
|
||||
def join(self, position: Point) -> None:
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.JOIN,
|
||||
position.x,
|
||||
position.y,
|
||||
self.doctrine.ingress_altitude
|
||||
)
|
||||
waypoint.pretty_name = "Join"
|
||||
waypoint.description = "Rendezvous with package"
|
||||
waypoint.name = "JOIN"
|
||||
self.waypoints.append(waypoint)
|
||||
|
||||
def split(self, position: Point) -> None:
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.SPLIT,
|
||||
position.x,
|
||||
position.y,
|
||||
self.doctrine.ingress_altitude
|
||||
)
|
||||
waypoint.pretty_name = "Split"
|
||||
waypoint.description = "Depart from package"
|
||||
waypoint.name = "SPLIT"
|
||||
self.waypoints.append(waypoint)
|
||||
|
||||
def ingress_cas(self, position: Point, objective: MissionTarget) -> None:
|
||||
self._ingress(FlightWaypointType.INGRESS_CAS, position, objective)
|
||||
|
||||
|
||||
@ -54,7 +54,12 @@ class Dialog:
|
||||
cls.edit_package_dialog.show()
|
||||
|
||||
@classmethod
|
||||
def open_edit_flight_dialog(cls, flight: Flight):
|
||||
def open_edit_flight_dialog(cls, package_model: PackageModel,
|
||||
flight: Flight) -> None:
|
||||
"""Opens the dialog to edit the given flight."""
|
||||
cls.edit_flight_dialog = QEditFlightDialog(cls.game_model.game, flight)
|
||||
cls.edit_flight_dialog = QEditFlightDialog(
|
||||
cls.game_model.game,
|
||||
package_model.package,
|
||||
flight
|
||||
)
|
||||
cls.edit_flight_dialog.show()
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
from PySide2.QtWidgets import QFrame, QGroupBox, QHBoxLayout, QPushButton
|
||||
|
||||
import qt_ui.uiconstants as CONST
|
||||
@ -74,7 +76,7 @@ class QTopPanel(QFrame):
|
||||
self.layout.setContentsMargins(0,0,0,0)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def setGame(self, game:Game):
|
||||
def setGame(self, game: Optional[Game]):
|
||||
self.game = game
|
||||
if game is not None:
|
||||
self.turnCounter.setCurrentTurn(self.game.turn, self.game.current_day)
|
||||
|
||||
@ -123,7 +123,7 @@ class QFlightPanel(QGroupBox):
|
||||
return
|
||||
from qt_ui.dialogs import Dialog
|
||||
Dialog.open_edit_flight_dialog(
|
||||
self.package_model.flight_at_index(index)
|
||||
self.package_model, self.package_model.flight_at_index(index)
|
||||
)
|
||||
|
||||
def on_delete(self) -> None:
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from typing import Dict, List, Tuple
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from PySide2.QtCore import Qt
|
||||
from PySide2.QtGui import QBrush, QColor, QPen, QPixmap, QWheelEvent
|
||||
@ -43,6 +43,7 @@ class QLiberationMap(QGraphicsView):
|
||||
super(QLiberationMap, self).__init__()
|
||||
QLiberationMap.instance = self
|
||||
self.game_model = game_model
|
||||
self.game: Optional[Game] = game_model.game
|
||||
|
||||
self.flight_path_items: List[QGraphicsItem] = []
|
||||
|
||||
@ -71,7 +72,7 @@ class QLiberationMap(QGraphicsView):
|
||||
def connectSignals(self):
|
||||
GameUpdateSignal.get_instance().gameupdated.connect(self.setGame)
|
||||
|
||||
def setGame(self, game: Game):
|
||||
def setGame(self, game: Optional[Game]):
|
||||
self.game = game
|
||||
print("Reloading Map Canvas")
|
||||
if self.game is not None:
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from PySide2.QtCore import QObject, Signal
|
||||
|
||||
from game import Game
|
||||
@ -31,7 +33,7 @@ class GameUpdateSignal(QObject):
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.flight_paths_changed.emit()
|
||||
|
||||
def updateGame(self, game: Game):
|
||||
def updateGame(self, game: Optional[Game]):
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.gameupdated.emit(game)
|
||||
|
||||
|
||||
@ -232,6 +232,8 @@ class QLiberationWindow(QMainWindow):
|
||||
sys.exit(0)
|
||||
|
||||
def setGame(self, game: Optional[Game]):
|
||||
if game is not None:
|
||||
game.on_load()
|
||||
self.game = game
|
||||
if self.info_panel:
|
||||
self.info_panel.setGame(game)
|
||||
|
||||
@ -5,6 +5,7 @@ from PySide2.QtWidgets import (
|
||||
)
|
||||
|
||||
from game import Game
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight
|
||||
from qt_ui.uiconstants import EVENT_ICONS
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
@ -14,7 +15,7 @@ from qt_ui.windows.mission.flight.QFlightPlanner import QFlightPlanner
|
||||
class QEditFlightDialog(QDialog):
|
||||
"""Dialog window for editing flight plans and loadouts."""
|
||||
|
||||
def __init__(self, game: Game, flight: Flight) -> None:
|
||||
def __init__(self, game: Game, package: Package, flight: Flight) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.game = game
|
||||
@ -24,7 +25,7 @@ class QEditFlightDialog(QDialog):
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
self.flight_planner = QFlightPlanner(flight, game)
|
||||
self.flight_planner = QFlightPlanner(package, flight, game)
|
||||
layout.addWidget(self.flight_planner)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
@ -14,6 +14,7 @@ from PySide2.QtWidgets import (
|
||||
from game.game import Game
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight
|
||||
from gen.flights.flightplan import FlightPlanBuilder
|
||||
from qt_ui.models import AtoModel, PackageModel
|
||||
from qt_ui.uiconstants import EVENT_ICONS
|
||||
from qt_ui.widgets.ato import QFlightList
|
||||
@ -100,15 +101,17 @@ class QPackageDialog(QDialog):
|
||||
|
||||
def on_add_flight(self) -> None:
|
||||
"""Opens the new flight dialog."""
|
||||
self.add_flight_dialog = QFlightCreator(
|
||||
self.game, self.package_model.package
|
||||
)
|
||||
self.add_flight_dialog = QFlightCreator(self.game,
|
||||
self.package_model.package)
|
||||
self.add_flight_dialog.created.connect(self.add_flight)
|
||||
self.add_flight_dialog.show()
|
||||
|
||||
def add_flight(self, flight: Flight) -> None:
|
||||
"""Adds the new flight to the package."""
|
||||
self.package_model.add_flight(flight)
|
||||
planner = FlightPlanBuilder(self.game, self.package_model.package,
|
||||
is_player=True)
|
||||
planner.populate_flight_plan(flight)
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.package_changed.emit()
|
||||
# noinspection PyUnresolvedReferences
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from PySide2.QtCore import Qt, Signal
|
||||
@ -11,15 +10,14 @@ from dcs.planes import PlaneType
|
||||
|
||||
from game import Game
|
||||
from gen.ato import Package
|
||||
from gen.flights.flightplan import FlightPlanBuilder
|
||||
from gen.flights.flight import Flight, FlightType
|
||||
from gen.flights.flight import Flight
|
||||
from qt_ui.uiconstants import EVENT_ICONS
|
||||
from qt_ui.widgets.QFlightSizeSpinner import QFlightSizeSpinner
|
||||
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, FrontLine, TheaterGroundObject
|
||||
from theater import ControlPoint
|
||||
|
||||
|
||||
class QFlightCreator(QDialog):
|
||||
@ -29,9 +27,6 @@ class QFlightCreator(QDialog):
|
||||
super().__init__()
|
||||
|
||||
self.game = game
|
||||
self.package = package
|
||||
|
||||
self.planner = FlightPlanBuilder(self.game, is_player=True)
|
||||
|
||||
self.setWindowTitle("Create flight")
|
||||
self.setWindowIcon(EVENT_ICONS["strike"])
|
||||
@ -39,7 +34,7 @@ class QFlightCreator(QDialog):
|
||||
layout = QVBoxLayout()
|
||||
|
||||
self.task_selector = QFlightTypeComboBox(
|
||||
self.game.theater, self.package.target
|
||||
self.game.theater, package.target
|
||||
)
|
||||
self.task_selector.setCurrentIndex(0)
|
||||
layout.addLayout(QLabeledWidget("Task:", self.task_selector))
|
||||
@ -95,7 +90,6 @@ class QFlightCreator(QDialog):
|
||||
size = self.flight_size_spinner.value()
|
||||
|
||||
flight = Flight(aircraft, size, origin, task)
|
||||
self.planner.populate_flight_plan(flight, self.package.target)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.created.emit(flight)
|
||||
|
||||
@ -2,6 +2,7 @@ from PySide2.QtCore import Signal
|
||||
from PySide2.QtWidgets import QTabWidget
|
||||
|
||||
from game import Game
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight
|
||||
from qt_ui.windows.mission.flight.payload.QFlightPayloadTab import \
|
||||
QFlightPayloadTab
|
||||
@ -15,14 +16,14 @@ class QFlightPlanner(QTabWidget):
|
||||
|
||||
on_planned_flight_changed = Signal()
|
||||
|
||||
def __init__(self, flight: Flight, game: Game):
|
||||
def __init__(self, package: Package, flight: Flight, game: Game):
|
||||
super().__init__()
|
||||
|
||||
self.general_settings_tab = QGeneralFlightSettingsTab(game, flight)
|
||||
self.general_settings_tab.on_flight_settings_changed.connect(
|
||||
lambda: self.on_planned_flight_changed.emit())
|
||||
self.payload_tab = QFlightPayloadTab(flight, game)
|
||||
self.waypoint_tab = QFlightWaypointTab(game, flight)
|
||||
self.waypoint_tab = QFlightWaypointTab(game, package, flight)
|
||||
self.waypoint_tab.on_flight_changed.connect(
|
||||
lambda: self.on_planned_flight_changed.emit())
|
||||
self.addTab(self.general_settings_tab, "General Flight settings")
|
||||
|
||||
@ -2,6 +2,7 @@ from PySide2.QtCore import Qt
|
||||
from PySide2.QtWidgets import QDialog, QPushButton
|
||||
|
||||
from game import Game
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight
|
||||
from gen.flights.flightplan import FlightPlanBuilder
|
||||
from qt_ui.uiconstants import EVENT_ICONS
|
||||
@ -10,9 +11,11 @@ from qt_ui.windows.mission.flight.waypoints.QFlightWaypointInfoBox import QFligh
|
||||
|
||||
class QAbstractMissionGenerator(QDialog):
|
||||
|
||||
def __init__(self, game: Game, flight: Flight, flight_waypoint_list, title):
|
||||
def __init__(self, game: Game, package: Package, flight: Flight,
|
||||
flight_waypoint_list, title) -> None:
|
||||
super(QAbstractMissionGenerator, self).__init__()
|
||||
self.game = game
|
||||
self.package = package
|
||||
self.flight = flight
|
||||
self.setWindowFlags(Qt.WindowStaysOnTopHint)
|
||||
self.setMinimumSize(400, 250)
|
||||
@ -20,7 +23,7 @@ class QAbstractMissionGenerator(QDialog):
|
||||
self.setWindowTitle(title)
|
||||
self.setWindowIcon(EVENT_ICONS["strike"])
|
||||
self.flight_waypoint_list = flight_waypoint_list
|
||||
self.planner = FlightPlanBuilder(self.game, is_player=True)
|
||||
self.planner = FlightPlanBuilder(self.game, package, is_player=True)
|
||||
|
||||
self.selected_waypoints = []
|
||||
self.wpt_info = QFlightWaypointInfoBox()
|
||||
|
||||
@ -1,15 +1,26 @@
|
||||
import logging
|
||||
|
||||
from PySide2.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout
|
||||
|
||||
from game import Game
|
||||
from gen.flights.flight import Flight, PredefinedWaypointCategory
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight, FlightType
|
||||
from qt_ui.widgets.combos.QPredefinedWaypointSelectionComboBox import QPredefinedWaypointSelectionComboBox
|
||||
from qt_ui.windows.mission.flight.generator.QAbstractMissionGenerator import QAbstractMissionGenerator
|
||||
from theater import ControlPoint, FrontLine
|
||||
|
||||
|
||||
class QCAPMissionGenerator(QAbstractMissionGenerator):
|
||||
|
||||
def __init__(self, game: Game, flight: Flight, flight_waypoint_list):
|
||||
super(QCAPMissionGenerator, self).__init__(game, flight, flight_waypoint_list, "CAP Generator")
|
||||
def __init__(self, game: Game, package: Package, flight: Flight,
|
||||
flight_waypoint_list) -> None:
|
||||
super(QCAPMissionGenerator, self).__init__(
|
||||
game,
|
||||
package,
|
||||
flight,
|
||||
flight_waypoint_list,
|
||||
"CAP Generator"
|
||||
)
|
||||
|
||||
self.wpt_selection_box = QPredefinedWaypointSelectionComboBox(self.game, self, False, True, True, False, False, True)
|
||||
self.wpt_selection_box.setMinimumWidth(200)
|
||||
@ -34,16 +45,22 @@ class QCAPMissionGenerator(QAbstractMissionGenerator):
|
||||
self.setLayout(layout)
|
||||
|
||||
def apply(self):
|
||||
self.flight.points = []
|
||||
|
||||
wpt = self.selected_waypoints[0]
|
||||
if wpt.category == PredefinedWaypointCategory.FRONTLINE:
|
||||
self.planner.generate_frontline_cap(self.flight, wpt.data[0], wpt.data[1])
|
||||
elif wpt.category == PredefinedWaypointCategory.ALLY_CP:
|
||||
self.planner.generate_barcap(self.flight, wpt.data)
|
||||
location = self.package.target
|
||||
if isinstance(location, FrontLine):
|
||||
self.flight.flight_type = FlightType.TARCAP
|
||||
self.planner.populate_flight_plan(self.flight)
|
||||
elif isinstance(location, ControlPoint):
|
||||
if location.is_fleet:
|
||||
self.flight.flight_type = FlightType.BARCAP
|
||||
else:
|
||||
self.flight.flight_type = FlightType.CAP
|
||||
else:
|
||||
name = location.__class__.__name__
|
||||
logging.error(f"Unexpected objective type for CAP: {name}")
|
||||
return
|
||||
|
||||
self.planner.generate_barcap(self.flight)
|
||||
|
||||
self.flight_waypoint_list.update_list()
|
||||
self.close()
|
||||
|
||||
|
||||
@ -4,15 +4,23 @@ from dcs import Point
|
||||
|
||||
from game import Game
|
||||
from game.utils import meter_to_nm
|
||||
from gen.flights.flight import Flight
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight, FlightType
|
||||
from qt_ui.widgets.combos.QPredefinedWaypointSelectionComboBox import QPredefinedWaypointSelectionComboBox
|
||||
from qt_ui.windows.mission.flight.generator.QAbstractMissionGenerator import QAbstractMissionGenerator
|
||||
|
||||
|
||||
class QCASMissionGenerator(QAbstractMissionGenerator):
|
||||
|
||||
def __init__(self, game: Game, flight: Flight, flight_waypoint_list):
|
||||
super(QCASMissionGenerator, self).__init__(game, flight, flight_waypoint_list, "CAS Generator")
|
||||
def __init__(self, game: Game, package: Package, flight: Flight,
|
||||
flight_waypoint_list) -> None:
|
||||
super(QCASMissionGenerator, self).__init__(
|
||||
game,
|
||||
package,
|
||||
flight,
|
||||
flight_waypoint_list,
|
||||
"CAS Generator"
|
||||
)
|
||||
|
||||
self.wpt_selection_box = QPredefinedWaypointSelectionComboBox(self.game, self, False, False, True, False, False)
|
||||
self.wpt_selection_box.setMinimumWidth(200)
|
||||
@ -55,8 +63,8 @@ class QCASMissionGenerator(QAbstractMissionGenerator):
|
||||
self.setLayout(layout)
|
||||
|
||||
def apply(self):
|
||||
self.flight.points = []
|
||||
self.planner.generate_cas(self.flight, self.selected_waypoints[0].data[0], self.selected_waypoints[0].data[1])
|
||||
self.flight.flight_type = FlightType.CAS
|
||||
self.planner.populate_flight_plan(self.flight)
|
||||
self.flight_waypoint_list.update_list()
|
||||
self.close()
|
||||
|
||||
|
||||
@ -3,7 +3,8 @@ from PySide2.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout, QGroupBox
|
||||
|
||||
from game import Game
|
||||
from game.utils import meter_to_nm
|
||||
from gen.flights.flight import Flight
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight, FlightType
|
||||
from qt_ui.widgets.combos.QSEADTargetSelectionComboBox import QSEADTargetSelectionComboBox
|
||||
from qt_ui.widgets.views.QSeadTargetInfoView import QSeadTargetInfoView
|
||||
from qt_ui.windows.mission.flight.generator.QAbstractMissionGenerator import QAbstractMissionGenerator
|
||||
@ -11,8 +12,15 @@ from qt_ui.windows.mission.flight.generator.QAbstractMissionGenerator import QAb
|
||||
|
||||
class QSEADMissionGenerator(QAbstractMissionGenerator):
|
||||
|
||||
def __init__(self, game: Game, flight: Flight, flight_waypoint_list):
|
||||
super(QSEADMissionGenerator, self).__init__(game, flight, flight_waypoint_list, "SEAD/DEAD Generator")
|
||||
def __init__(self, game: Game, package: Package, flight: Flight,
|
||||
flight_waypoint_list) -> None:
|
||||
super(QSEADMissionGenerator, self).__init__(
|
||||
game,
|
||||
package,
|
||||
flight,
|
||||
flight_waypoint_list,
|
||||
"SEAD/DEAD Generator"
|
||||
)
|
||||
|
||||
self.tgt_selection_box = QSEADTargetSelectionComboBox(self.game)
|
||||
self.tgt_selection_box.setMinimumWidth(200)
|
||||
@ -73,9 +81,9 @@ class QSEADMissionGenerator(QAbstractMissionGenerator):
|
||||
self.setLayout(layout)
|
||||
|
||||
def apply(self):
|
||||
self.flight.points = []
|
||||
target = self.tgt_selection_box.get_selected_target()
|
||||
self.planner.generate_sead(self.flight, target.location, target.radars)
|
||||
self.flight.flight_type = FlightType.SEAD
|
||||
self.planner.populate_flight_plan(self.flight, target.radars)
|
||||
self.flight_waypoint_list.update_list()
|
||||
self.close()
|
||||
|
||||
|
||||
@ -3,7 +3,8 @@ from PySide2.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout, QGroupBox
|
||||
|
||||
from game import Game
|
||||
from game.utils import meter_to_nm
|
||||
from gen.flights.flight import Flight
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight, FlightType
|
||||
from qt_ui.widgets.combos.QStrikeTargetSelectionComboBox import QStrikeTargetSelectionComboBox
|
||||
from qt_ui.widgets.views.QStrikeTargetInfoView import QStrikeTargetInfoView
|
||||
from qt_ui.windows.mission.flight.generator.QAbstractMissionGenerator import QAbstractMissionGenerator
|
||||
@ -11,8 +12,15 @@ from qt_ui.windows.mission.flight.generator.QAbstractMissionGenerator import QAb
|
||||
|
||||
class QSTRIKEMissionGenerator(QAbstractMissionGenerator):
|
||||
|
||||
def __init__(self, game: Game, flight: Flight, flight_waypoint_list):
|
||||
super(QSTRIKEMissionGenerator, self).__init__(game, flight, flight_waypoint_list, "Strike Generator")
|
||||
def __init__(self, game: Game, package: Package, flight: Flight,
|
||||
flight_waypoint_list) -> None:
|
||||
super(QSTRIKEMissionGenerator, self).__init__(
|
||||
game,
|
||||
package,
|
||||
flight,
|
||||
flight_waypoint_list,
|
||||
"Strike Generator"
|
||||
)
|
||||
|
||||
self.tgt_selection_box = QStrikeTargetSelectionComboBox(self.game)
|
||||
self.tgt_selection_box.setMinimumWidth(200)
|
||||
@ -53,9 +61,9 @@ class QSTRIKEMissionGenerator(QAbstractMissionGenerator):
|
||||
self.setLayout(layout)
|
||||
|
||||
def apply(self):
|
||||
self.flight.points = []
|
||||
target = self.tgt_selection_box.get_selected_target()
|
||||
self.planner.generate_strike(self.flight, target.location)
|
||||
self.flight.flight_type = FlightType.STRIKE
|
||||
self.planner.populate_flight_plan(self.flight, target.location)
|
||||
self.flight_waypoint_list.update_list()
|
||||
self.close()
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ from PySide2.QtCore import Signal
|
||||
from PySide2.QtWidgets import QFrame, QGridLayout, QLabel, QPushButton, QVBoxLayout
|
||||
|
||||
from game import Game
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight
|
||||
from gen.flights.flightplan import FlightPlanBuilder
|
||||
from qt_ui.windows.mission.flight.generator.QCAPMissionGenerator import QCAPMissionGenerator
|
||||
@ -16,11 +17,12 @@ class QFlightWaypointTab(QFrame):
|
||||
|
||||
on_flight_changed = Signal()
|
||||
|
||||
def __init__(self, game: Game, flight: Flight):
|
||||
def __init__(self, game: Game, package: Package, flight: Flight):
|
||||
super(QFlightWaypointTab, self).__init__()
|
||||
self.flight = flight
|
||||
self.game = game
|
||||
self.planner = FlightPlanBuilder(self.game, is_player=True)
|
||||
self.package = package
|
||||
self.flight = flight
|
||||
self.planner = FlightPlanBuilder(self.game, package, is_player=True)
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
@ -104,22 +106,42 @@ class QFlightWaypointTab(QFrame):
|
||||
self.on_change()
|
||||
|
||||
def on_cas_generator(self):
|
||||
self.subwindow = QCASMissionGenerator(self.game, self.flight, self.flight_waypoint_list)
|
||||
self.subwindow = QCASMissionGenerator(
|
||||
self.game,
|
||||
self.package,
|
||||
self.flight,
|
||||
self.flight_waypoint_list
|
||||
)
|
||||
self.subwindow.finished.connect(self.on_change)
|
||||
self.subwindow.show()
|
||||
|
||||
def on_cap_generator(self):
|
||||
self.subwindow = QCAPMissionGenerator(self.game, self.flight, self.flight_waypoint_list)
|
||||
self.subwindow = QCAPMissionGenerator(
|
||||
self.game,
|
||||
self.package,
|
||||
self.flight,
|
||||
self.flight_waypoint_list
|
||||
)
|
||||
self.subwindow.finished.connect(self.on_change)
|
||||
self.subwindow.show()
|
||||
|
||||
def on_sead_generator(self):
|
||||
self.subwindow = QSEADMissionGenerator(self.game, self.flight, self.flight_waypoint_list)
|
||||
self.subwindow = QSEADMissionGenerator(
|
||||
self.game,
|
||||
self.package,
|
||||
self.flight,
|
||||
self.flight_waypoint_list
|
||||
)
|
||||
self.subwindow.finished.connect(self.on_change)
|
||||
self.subwindow.show()
|
||||
|
||||
def on_strike_generator(self):
|
||||
self.subwindow = QSTRIKEMissionGenerator(self.game, self.flight, self.flight_waypoint_list)
|
||||
self.subwindow = QSTRIKEMissionGenerator(
|
||||
self.game,
|
||||
self.package,
|
||||
self.flight,
|
||||
self.flight_waypoint_list
|
||||
)
|
||||
self.subwindow.finished.connect(self.on_change)
|
||||
self.subwindow.show()
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user