diff --git a/game/operation/operation.py b/game/operation/operation.py index 5bf7c4e7..1376be88 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -1,10 +1,9 @@ from __future__ import annotations -from game.theater.theatergroundobject import TheaterGroundObject import logging import os from pathlib import Path -from typing import TYPE_CHECKING, Iterable, List, Optional, Set +from typing import Iterable, List, Optional, Set, TYPE_CHECKING from dcs import Mission from dcs.action import DoScript, DoScriptFile @@ -14,7 +13,9 @@ from dcs.lua.parse import loads from dcs.mapping import Point from dcs.translation import String from dcs.triggers import TriggerStart + from game.plugins import LuaPluginManager +from game.theater.theatergroundobject import TheaterGroundObject from gen import Conflict, FlightType, VisualGenerator from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData from gen.airfields import AIRFIELD_DATA @@ -31,7 +32,6 @@ from gen.naming import namegen from gen.radios import RadioFrequency, RadioRegistry from gen.tacan import TacanRegistry from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator - from .. import db from ..theater import Airfield from ..unitmap import UnitMap @@ -43,18 +43,13 @@ if TYPE_CHECKING: class Operation: """Static class for managing the final Mission generation""" - current_mission = None # type: Mission - airgen = None # type: AircraftConflictGenerator - triggersgen = None # type: TriggersGenerator - airsupportgen = None # type: AirSupportConflictGenerator - visualgen = None # type: VisualGenerator - groundobjectgen = None # type: GroundObjectsGenerator - briefinggen = None # type: BriefingGenerator - forcedoptionsgen = None # type: ForcedOptionsGenerator - radio_registry: Optional[RadioRegistry] = None - tacan_registry: Optional[TacanRegistry] = None - game = None # type: Game - environment_settings = None + current_mission: Mission + airgen: AircraftConflictGenerator + airsupportgen: AirSupportConflictGenerator + groundobjectgen: GroundObjectsGenerator + radio_registry: RadioRegistry + tacan_registry: TacanRegistry + game: Game trigger_radius = TRIGGER_RADIUS_MEDIUM is_quick = None player_awacs_enabled = True @@ -309,13 +304,13 @@ class Operation: # Set mission time and weather conditions. EnvironmentGenerator(cls.current_mission, cls.game.conditions).generate() cls._generate_ground_units() + cls._generate_convoys() cls._generate_destroyed_units() cls._generate_air_units() cls.assign_channels_to_flights( cls.airgen.flights, cls.airsupportgen.air_support ) cls._generate_ground_conflicts() - cls._generate_convoys() # Triggers triggersgen = TriggersGenerator(cls.current_mission, cls.game) diff --git a/game/theater/supplyroutes.py b/game/theater/supplyroutes.py index 790f95a8..eb02663d 100644 --- a/game/theater/supplyroutes.py +++ b/game/theater/supplyroutes.py @@ -6,8 +6,6 @@ from collections import defaultdict from dataclasses import dataclass, field from typing import Dict, Iterator, List, Optional -from dcs import Point -from game.theater import FlightType, MissionTarget from game.theater.controlpoint import ControlPoint @@ -99,25 +97,3 @@ class SupplyRoute: current = previous path.reverse() return path - - -class SupplyRouteLink(MissionTarget): - def __init__(self, a: ControlPoint, b: ControlPoint) -> None: - self.control_point_a = a - self.control_point_b = b - super().__init__( - f"Supply route between {a} and {b}", - Point((a.position.x + b.position.x) / 2, (a.position.y + b.position.y) / 2), - ) - - def mission_types(self, for_player: bool) -> Iterator[FlightType]: - yield from [ - FlightType.BAI, - # TODO: Escort - # TODO: SEAD - # TODO: Recon - # TODO: TARCAP - ] - - def is_friendly(self, to_player: bool) -> bool: - return self.control_point_a.captured diff --git a/game/transfers.py b/game/transfers.py index e9a5fdc3..dce97546 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -3,8 +3,10 @@ from dataclasses import dataclass, field from typing import Dict, Iterator, List, Type from dcs.unittype import VehicleType -from game.theater import ControlPoint +from game.theater import ControlPoint, MissionTarget from game.theater.supplyroutes import SupplyRoute +from gen.naming import namegen +from gen.flights.flight import FlightType @dataclass @@ -35,6 +37,8 @@ class RoadTransferOrder(TransferOrder): #: point a turn through the supply line. position: ControlPoint = field(init=False) + name: str = field(init=False, default_factory=namegen.next_convoy_name) + def __post_init__(self) -> None: self.position = self.origin @@ -46,6 +50,27 @@ class RoadTransferOrder(TransferOrder): return self.path()[0] +class Convoy(MissionTarget): + def __init__(self, transfer: RoadTransferOrder) -> None: + self.transfer = transfer + count = sum(c for c in transfer.units.values()) + super().__init__( + f"{transfer.name} of {count} units moving from {transfer.position} to " + f"{transfer.destination}", + transfer.position.position, + ) + + def mission_types(self, for_player: bool) -> Iterator[FlightType]: + if self.is_friendly(for_player): + return + + yield FlightType.BAI + yield from super().mission_types(for_player) + + def is_friendly(self, to_player: bool) -> bool: + return self.transfer.position.captured + + class PendingTransfers: def __init__(self) -> None: self.pending_transfers: List[RoadTransferOrder] = [] diff --git a/gen/aircraft.py b/gen/aircraft.py index 024a49c4..b4893704 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -72,7 +72,7 @@ from dcs.task import ( ) from dcs.terrain.terrain import Airport, NoParkingSlotError from dcs.triggers import Event, TriggerOnce, TriggerRule -from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup +from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup, VehicleGroup from dcs.unittype import FlyingType, UnitType from game import db @@ -88,6 +88,7 @@ from game.theater.controlpoint import ( OffMapSpawn, ) from game.theater.theatergroundobject import TheaterGroundObject +from game.transfers import Convoy, RoadTransferOrder from game.unitmap import UnitMap from game.utils import Distance, meters, nautical_miles from gen.ato import AirTaskingOrder, Package @@ -1691,25 +1692,30 @@ class BaiIngressBuilder(PydcsWaypointBuilder): def build(self) -> MovingPoint: waypoint = super().build() + # TODO: Add common "UnitGroupTarget" base type. target_group = self.package.target if isinstance(target_group, TheaterGroundObject): - tgroup = self.mission.find_group(target_group.group_name) - if tgroup is not None: - task = AttackGroup(tgroup.id, weapon_type=WeaponType.Auto) - task.params["attackQtyLimit"] = False - task.params["directionEnabled"] = False - task.params["altitudeEnabled"] = False - task.params["groupAttack"] = True - waypoint.tasks.append(task) - else: - logging.error( - "Could not find group for BAI mission %s", target_group.group_name - ) + group_name = target_group.group_name + elif isinstance(target_group, Convoy): + group_name = target_group.transfer.name else: logging.error( "Unexpected target type for BAI mission: %s", target_group.__class__.__name__, ) + return waypoint + + group = self.mission.find_group(group_name) + if group is None: + logging.error("Could not find group for BAI mission %s", group_name) + return waypoint + + task = AttackGroup(group.id, weapon_type=WeaponType.Auto) + task.params["attackQtyLimit"] = False + task.params["directionEnabled"] = False + task.params["altitudeEnabled"] = False + task.params["groupAttack"] = True + waypoint.tasks.append(task) return waypoint diff --git a/gen/convoys.py b/gen/convoys.py index 520902ea..9f021135 100644 --- a/gen/convoys.py +++ b/gen/convoys.py @@ -12,6 +12,7 @@ from dcs.unittype import VehicleType from game.transfers import RoadTransferOrder from game.unitmap import UnitMap +from game.utils import kph if TYPE_CHECKING: from game import Game @@ -26,24 +27,26 @@ class ConvoyGenerator: def generate(self) -> None: # Reset the count to make generation deterministic. - self.count = itertools.count() for transfer in self.game.transfers: self.generate_convoy_for(transfer) - def generate_convoy_for(self, transfer: RoadTransferOrder) -> None: + def generate_convoy_for(self, transfer: RoadTransferOrder) -> VehicleGroup: next_hop = transfer.path()[0] origin = transfer.position.convoy_spawns[next_hop] destination = next_hop.convoy_spawns[transfer.position] group = self._create_mixed_unit_group( - f"Convoy {next(self.count)}", + transfer.name, origin, transfer.units, transfer.player, ) - group.add_waypoint(destination, move_formation=PointAction.OnRoad) + group.add_waypoint( + destination, speed=kph(40).kph, move_formation=PointAction.OnRoad + ) self.make_drivable(group) self.unit_map.add_convoy_units(group, transfer) + return group def _create_mixed_unit_group( self, diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 85c2895a..d447e3ed 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -30,8 +30,8 @@ from game.theater import ( SamGroundObject, TheaterGroundObject, ) -from game.theater.supplyroutes import SupplyRouteLink from game.theater.theatergroundobject import EwrGroundObject +from game.transfers import Convoy from game.utils import Distance, Speed, feet, meters, nautical_miles from .closestairfields import ObjectiveDistanceCache from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType @@ -467,25 +467,6 @@ class CasFlightPlan(PatrollingFlightPlan): return self.patrol_end -@dataclass(frozen=True) -class ConvoyInterdictionFlightPlan(PatrollingFlightPlan): - takeoff: FlightWaypoint - land: FlightWaypoint - divert: Optional[FlightWaypoint] - - def iter_waypoints(self) -> Iterator[FlightWaypoint]: - yield self.takeoff - yield from self.nav_to - yield from [ - self.patrol_start, - self.patrol_end, - ] - yield from self.nav_from - yield self.land - if self.divert is not None: - yield self.divert - - @dataclass(frozen=True) class TarCapFlightPlan(PatrollingFlightPlan): takeoff: FlightWaypoint @@ -1042,75 +1023,22 @@ class FlightPlanBuilder: """ location = self.package.target - if isinstance(location, SupplyRouteLink): - return self.generate_supply_route_bai(flight, location) - - if not isinstance(location, TheaterGroundObject): - raise InvalidObjectiveLocation(flight.flight_type, location) - targets: List[StrikeTarget] = [] - for group in location.groups: - if group.units: - targets.append(StrikeTarget(f"{group.name} at {location.name}", group)) + if isinstance(location, TheaterGroundObject): + for group in location.groups: + if group.units: + targets.append( + StrikeTarget(f"{group.name} at {location.name}", group) + ) + elif isinstance(location, Convoy): + targets.append(StrikeTarget(location.name, location)) + else: + raise InvalidObjectiveLocation(flight.flight_type, location) return self.strike_flightplan( flight, location, FlightWaypointType.INGRESS_BAI, targets ) - def generate_supply_route_bai( - self, flight: Flight, location: SupplyRouteLink - ) -> ConvoyInterdictionFlightPlan: - """Generates a BAI flight plan for attacking a supply route. - - These flight plans are extremely rough because we do not know where the roads - are. For now they're mostly only usable by players. The flight plan includes a - start and end patrol point matching the end points of the convoy's route and a - 30 minute time on station. It is up to the player to find the target. - - Args: - flight: The flight to generate the flight plan for. - location: The supply route link to attack. - """ - - origin = self.package_airfield() - a_dist = origin.distance_to(location.control_point_a) - b_dist = origin.distance_to(location.control_point_b) - if a_dist < b_dist: - near = location.control_point_a - far = location.control_point_b - else: - near = location.control_point_b - far = location.control_point_a - - patrol_alt = meters( - random.randint( - int(self.doctrine.min_patrol_altitude.meters), - int(self.doctrine.max_patrol_altitude.meters), - ) - ) - - builder = WaypointBuilder(flight, self.game, self.is_player) - start, end = builder.convoy_search(near, far, patrol_alt) - - return ConvoyInterdictionFlightPlan( - self.package, - flight, - takeoff=builder.takeoff(flight.departure), - nav_to=builder.nav_path( - flight.departure.position, near.position, patrol_alt - ), - nav_from=builder.nav_path( - far.position, flight.arrival.position, patrol_alt - ), - patrol_start=start, - patrol_end=end, - land=builder.land(flight.arrival), - divert=builder.divert(flight.divert), - # Not relevant because player only. - engagement_distance=meters(0), - patrol_duration=timedelta(minutes=30), - ) - def generate_anti_ship(self, flight: Flight) -> StrikeFlightPlan: """Generates an anti-ship flight plan. diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index ad1db18a..4559ca20 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -16,6 +16,8 @@ from dcs.mapping import Point from dcs.unit import Unit from dcs.unitgroup import Group, VehicleGroup +from game.transfers import Convoy + if TYPE_CHECKING: from game import Game @@ -32,7 +34,7 @@ from .flight import Flight, FlightWaypoint, FlightWaypointType @dataclass(frozen=True) class StrikeTarget: name: str - target: Union[VehicleGroup, TheaterGroundObject, Unit, Group] + target: Union[VehicleGroup, TheaterGroundObject, Unit, Group, Convoy] class WaypointBuilder: @@ -349,63 +351,6 @@ class WaypointBuilder: self.race_track_end(end, altitude), ) - @staticmethod - def convoy_search_start( - control_point: ControlPoint, altitude: Distance - ) -> FlightWaypoint: - """Creates a convoy search start waypoint. - - Args: - control_point: Control point for the beginning of the search. - altitude: Altitude of the racetrack. - """ - waypoint = FlightWaypoint( - FlightWaypointType.INGRESS_BAI, - control_point.position.x, - control_point.position.y, - altitude, - ) - waypoint.name = control_point.name - waypoint.description = "Beginning of convoy search area" - waypoint.pretty_name = "Search start" - return waypoint - - @staticmethod - def convoy_search_end( - control_point: ControlPoint, altitude: Distance - ) -> FlightWaypoint: - """Creates a convoy search start waypoint. - - Args: - control_point: Control point for the beginning of the search. - altitude: Altitude of the racetrack. - """ - waypoint = FlightWaypoint( - FlightWaypointType.EGRESS, - control_point.position.x, - control_point.position.y, - altitude, - ) - waypoint.name = control_point.name - waypoint.description = "End of convoy search area" - waypoint.pretty_name = "Search end" - return waypoint - - def convoy_search( - self, start: ControlPoint, end: ControlPoint, altitude: Distance - ) -> Tuple[FlightWaypoint, FlightWaypoint]: - """Creates two waypoint for a convoy search path. - - Args: - start: The beginning convoy search waypoint. - end: The ending convoy search waypoint. - altitude: The convoy search altitude. - """ - return ( - self.convoy_search_start(start, altitude), - self.convoy_search_end(end, altitude), - ) - @staticmethod def orbit(start: Point, altitude: Distance) -> FlightWaypoint: """Creates an circular orbit point. @@ -463,7 +408,7 @@ class WaypointBuilder: end: The end of the sweep. altitude: The sweep altitude. """ - return (self.sweep_start(start, altitude), self.sweep_end(end, altitude)) + return self.sweep_start(start, altitude), self.sweep_end(end, altitude) def escort( self, ingress: Point, target: MissionTarget, egress: Point diff --git a/gen/naming.py b/gen/naming.py index f1b14114..dcd09c2c 100644 --- a/gen/naming.py +++ b/gen/naming.py @@ -250,6 +250,7 @@ class NameGenerator: number = 0 infantry_number = 0 aircraft_number = 0 + convoy_number = 0 ANIMALS = ANIMALS existing_alphas: List[str] = [] @@ -258,6 +259,7 @@ class NameGenerator: def reset(cls): cls.number = 0 cls.infantry_number = 0 + cls.convoy_number = 0 cls.ANIMALS = ANIMALS cls.existing_alphas = [] @@ -266,6 +268,7 @@ class NameGenerator: cls.number = 0 cls.infantry_number = 0 cls.aircraft_number = 0 + cls.convoy_number = 0 @classmethod def next_aircraft_name(cls, country: Country, parent_base_id: int, flight: Flight): @@ -327,6 +330,11 @@ class NameGenerator: cls.number += 1 return "carrier|{}|{}|0|".format(country.id, cls.number) + @classmethod + def next_convoy_name(cls) -> str: + cls.convoy_number += 1 + return f"Convoy {cls.convoy_number:04}" + @classmethod def random_objective_name(cls): if len(cls.ANIMALS) == 0: diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index f08e84e0..f43657f7 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -16,8 +16,6 @@ import qt_ui.uiconstants as CONST from game import Game from game.event.airwar import AirWarEvent from gen.ato import Package -from gen.flights.flight import FlightType -from gen.flights.flightplan import ConvoyInterdictionFlightPlan from gen.flights.traveltime import TotEstimator from qt_ui.models import GameModel from qt_ui.widgets.QBudgetBox import QBudgetBox @@ -201,36 +199,6 @@ class QTopPanel(QFrame): ) return result == QMessageBox.Yes - def ato_has_ai_convoy_interdiction(self) -> bool: - for package in self.game.blue_ato.packages: - for flight in package.flights: - if ( - isinstance(flight.flight_plan, ConvoyInterdictionFlightPlan) - and not flight.client_count - ): - return True - return False - - def confirm_ai_convoy_interdiction_launch(self) -> bool: - result = QMessageBox.question( - self, - "Continue with AI convoy interdiction missions?", - ( - "AI only convoy interdiction missions were planned. AI behavior for " - "these missions has not been developed so they will probably get " - "themselves killed. Continuing is not recommended.
" - "
" - "To remove AI convoy interdiction missions, delete any BAI flights " - "that are planned against supply route objectives.
" - "
" - "Click 'Yes' to continue with AI only convoy interdiction missions." - "

Click 'No' to cancel and revise your flight planning." - ), - QMessageBox.No, - QMessageBox.Yes, - ) - return result == QMessageBox.Yes - def confirm_negative_start_time(self, negative_starts: List[Package]) -> bool: formatted = "
".join( [f"{p.primary_task} {p.target.name}" for p in negative_starts] @@ -273,12 +241,6 @@ class QTopPanel(QFrame): if not self.ato_has_clients() and not self.confirm_no_client_launch(): return - if ( - self.ato_has_ai_convoy_interdiction() - and not self.confirm_ai_convoy_interdiction_launch() - ): - return - negative_starts = self.negative_start_packages() if negative_starts: if not self.confirm_negative_start_time(negative_starts): diff --git a/qt_ui/widgets/map/SupplyRouteSegment.py b/qt_ui/widgets/map/SupplyRouteSegment.py index 58fda738..68a23cfc 100644 --- a/qt_ui/widgets/map/SupplyRouteSegment.py +++ b/qt_ui/widgets/map/SupplyRouteSegment.py @@ -4,18 +4,12 @@ from typing import List, Optional from PySide2.QtCore import Qt from PySide2.QtGui import QColor, QPen from PySide2.QtWidgets import ( - QAction, QGraphicsItem, QGraphicsLineItem, - QGraphicsSceneContextMenuEvent, - QGraphicsSceneHoverEvent, - QMenu, ) from game.theater import ControlPoint -from game.theater.supplyroutes import SupplyRouteLink from game.transfers import RoadTransferOrder -from qt_ui.dialogs import Dialog from qt_ui.uiconstants import COLORS @@ -39,12 +33,16 @@ class SupplyRouteSegment(QGraphicsLineItem): self.setToolTip(self.make_tooltip()) self.setAcceptHoverEvents(True) + @property + def has_convoys(self) -> bool: + return bool(self.convoys) + @cached_property def convoy_size(self) -> int: return sum(sum(c.units.values()) for c in self.convoys) def make_tooltip(self) -> str: - if not self.convoys: + if not self.has_convoys: return "No convoys present on this supply route." units = "units" if self.convoy_size > 1 else "unit" @@ -77,37 +75,3 @@ class SupplyRouteSegment(QGraphicsLineItem): pen.setStyle(self.line_style) pen.setWidth(6) return pen - - @property - def has_convoys(self) -> bool: - return bool(self.convoys) - - @property - def targetable(self) -> bool: - return self.convoys and not self.control_point_a.captured - - def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: - # Can only plan missions against enemy supply routes that have convoys. - if not self.targetable: - super().contextMenuEvent(event) - return - - menu = QMenu("Menu") - - 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()) - - def open_new_package_dialog(self) -> None: - """Opens the dialog for planning a new mission package.""" - Dialog.open_new_package_dialog( - SupplyRouteLink(self.control_point_a, self.control_point_b) - ) - - def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): - if self.targetable: - self.setCursor(Qt.PointingHandCursor) - else: - super().hoverEnterEvent(event) diff --git a/qt_ui/windows/basemenu/DepartingConvoysMenu.py b/qt_ui/windows/basemenu/DepartingConvoysMenu.py new file mode 100644 index 00000000..193aafdd --- /dev/null +++ b/qt_ui/windows/basemenu/DepartingConvoysMenu.py @@ -0,0 +1,101 @@ +from PySide2.QtCore import Qt +from PySide2.QtWidgets import ( + QFrame, + QGridLayout, + QGroupBox, + QLabel, + QPushButton, + QScrollArea, + QVBoxLayout, + QWidget, +) + +from game import db +from game.theater import ControlPoint +from game.transfers import Convoy, RoadTransferOrder +from qt_ui.dialogs import Dialog +from qt_ui.models import GameModel +from qt_ui.uiconstants import VEHICLES_ICONS + + +class DepartingConvoyInfo(QGroupBox): + def __init__(self, convoy: RoadTransferOrder, game_model: GameModel) -> None: + super().__init__(f"To {convoy.destination}") + self.convoy = convoy + + main_layout = QVBoxLayout() + self.setLayout(main_layout) + + unit_layout = QGridLayout() + main_layout.addLayout(unit_layout) + + for idx, (unit_type, count) in enumerate(convoy.units.items()): + icon = QLabel() + if unit_type.id in VEHICLES_ICONS.keys(): + icon.setPixmap(VEHICLES_ICONS[unit_type.id]) + else: + icon.setText("" + unit_type.id[:8] + "") + icon.setProperty("style", "icon-armor") + unit_layout.addWidget(icon, idx, 0) + unit_display_name = db.unit_get_expanded_info( + game_model.game.enemy_country, unit_type, "name" + ) + unit_layout.addWidget( + QLabel(f"{count} x {unit_display_name}"), + idx, + 1, + ) + + if not convoy.units: + unit_layout.addWidget(QLabel("/"), 0, 0) + + attack_button = QPushButton("Attack") + attack_button.setProperty("style", "btn-danger") + attack_button.setMaximumWidth(180) + attack_button.clicked.connect(self.on_attack) + main_layout.addWidget(attack_button, 0, Qt.AlignLeft) + + def on_attack(self): + # TODO: Maintain Convoy list in Game. + # The fact that we create these here makes some of the other bookkeeping + # complicated. We could instead generate this at the start of the turn (and + # update whenever transfers are created or canceled) and also use that time to + # precalculate things like the next stop and group names. + Dialog.open_new_package_dialog(Convoy(self.convoy), parent=self.window()) + + +class DepartingConvoysList(QFrame): + def __init__(self, cp: ControlPoint, game_model: GameModel): + super().__init__() + self.cp = cp + self.game_model = game_model + self.setMinimumWidth(500) + + layout = QVBoxLayout() + self.setLayout(layout) + + scroll_content = QWidget() + task_box_layout = QGridLayout() + scroll_content.setLayout(task_box_layout) + + for convoy in game_model.game.transfers: + if convoy.position != cp: + continue + group_info = DepartingConvoyInfo(convoy, game_model) + task_box_layout.addWidget(group_info) + + scroll_content.setLayout(task_box_layout) + scroll = QScrollArea() + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + scroll.setWidgetResizable(True) + scroll.setWidget(scroll_content) + layout.addWidget(scroll) + + +class DepartingConvoysMenu(QFrame): + def __init__(self, cp: ControlPoint, game_model: GameModel): + super().__init__() + layout = QVBoxLayout() + layout.addWidget(DepartingConvoysList(cp, game_model)) + self.setLayout(layout) diff --git a/qt_ui/windows/basemenu/QBaseMenuTabs.py b/qt_ui/windows/basemenu/QBaseMenuTabs.py index c6c35603..fe80dbe3 100644 --- a/qt_ui/windows/basemenu/QBaseMenuTabs.py +++ b/qt_ui/windows/basemenu/QBaseMenuTabs.py @@ -2,6 +2,7 @@ from PySide2.QtWidgets import QTabWidget from game.theater import ControlPoint, OffMapSpawn, Fob from qt_ui.models import GameModel +from qt_ui.windows.basemenu.DepartingConvoysMenu import DepartingConvoysMenu from qt_ui.windows.basemenu.airfield.QAirfieldCommand import QAirfieldCommand from qt_ui.windows.basemenu.base_defenses.QBaseDefensesHQ import QBaseDefensesHQ from qt_ui.windows.basemenu.ground_forces.QGroundForcesHQ import QGroundForcesHQ @@ -18,27 +19,28 @@ class QBaseMenuTabs(QTabWidget): self.addTab(self.base_defenses_hq, "Base Defenses") self.intel = QIntelInfo(cp, game_model.game) self.addTab(self.intel, "Intel") + + self.departing_convoys = DepartingConvoysMenu(cp, game_model) + self.addTab(self.departing_convoys, "Departing Convoys") + return + + if isinstance(cp, Fob): + self.ground_forces_hq = QGroundForcesHQ(cp, game_model) + self.addTab(self.ground_forces_hq, "Ground Forces HQ") + if cp.helipads: + self.airfield_command = QAirfieldCommand(cp, game_model) + self.addTab(self.airfield_command, "Heliport") + self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) + self.addTab(self.base_defenses_hq, "Base Defenses") else: + self.airfield_command = QAirfieldCommand(cp, game_model) + self.addTab(self.airfield_command, "Airfield Command") - if cp: - if isinstance(cp, Fob): - self.ground_forces_hq = QGroundForcesHQ(cp, game_model) - self.addTab(self.ground_forces_hq, "Ground Forces HQ") - if cp.helipads: - self.airfield_command = QAirfieldCommand(cp, game_model) - self.addTab(self.airfield_command, "Heliport") - self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) - self.addTab(self.base_defenses_hq, "Base Defenses") - else: - - self.airfield_command = QAirfieldCommand(cp, game_model) - self.addTab(self.airfield_command, "Airfield Command") - - if cp.is_carrier: - self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) - self.addTab(self.base_defenses_hq, "Fleet") - elif not isinstance(cp, OffMapSpawn): - self.ground_forces_hq = QGroundForcesHQ(cp, game_model) - self.addTab(self.ground_forces_hq, "Ground Forces HQ") - self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) - self.addTab(self.base_defenses_hq, "Base Defenses") + if cp.is_carrier: + self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) + self.addTab(self.base_defenses_hq, "Fleet") + elif not isinstance(cp, OffMapSpawn): + self.ground_forces_hq = QGroundForcesHQ(cp, game_model) + self.addTab(self.ground_forces_hq, "Ground Forces HQ") + self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) + self.addTab(self.base_defenses_hq, "Base Defenses")