Redo convoy attack flight plans.

The previous flight plan only makes sense if the convoy will make it a
significant distance from its starting point. At road speeds over the
typical mission duration this is not true, so we can actually plan this
as if it was a strike mission near the origin point and that's close
enough.

There's some cleanup work to do here that I've added todos for.

Fixes https://github.com/Khopa/dcs_liberation/issues/996
This commit is contained in:
Dan Albert 2021-04-20 21:06:58 -07:00
parent 3f16c0378a
commit 50d8e08a34
12 changed files with 216 additions and 301 deletions

View File

@ -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)

View File

@ -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

View File

@ -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] = []

View File

@ -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

View File

@ -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,

View File

@ -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.

View File

@ -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

View File

@ -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:

View File

@ -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.<br />"
"<br />"
"To remove AI convoy interdiction missions, delete any BAI flights "
"that are planned against supply route objectives.<br />"
"<br />"
"Click 'Yes' to continue with AI only convoy interdiction missions."
"<br /><br />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 = "<br />".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):

View File

@ -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)

View File

@ -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("<b>" + unit_type.id[:8] + "</b>")
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 <strong>{unit_display_name}</strong>"),
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)

View File

@ -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")