diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index b3a67d3b..886c3846 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -315,6 +315,23 @@ class ControlPoint(MissionTarget, ABC): connected.extend(cp.transitive_connected_friendly_points(seen)) return connected + def transitive_friendly_shipping_destinations( + self, seen: Optional[Set[ControlPoint]] = None + ) -> List[ControlPoint]: + if seen is None: + seen = {self} + + connected = [] + for cp in self.shipping_lanes: + if cp.captured != self.captured: + continue + if cp in seen: + continue + seen.add(cp) + connected.append(cp) + connected.extend(cp.transitive_friendly_shipping_destinations(seen)) + return connected + @property def has_factory(self) -> bool: for tgo in self.connected_objectives: diff --git a/game/theater/supplyroutes.py b/game/theater/supplyroutes.py index eb02663d..73254df4 100644 --- a/game/theater/supplyroutes.py +++ b/game/theater/supplyroutes.py @@ -4,7 +4,7 @@ import heapq import math from collections import defaultdict from dataclasses import dataclass, field -from typing import Dict, Iterator, List, Optional +from typing import Dict, Iterable, Iterator, List, Optional from game.theater.controlpoint import ControlPoint @@ -32,6 +32,25 @@ class Frontier: return bool(self.nodes) +# TODO: Build a single SupplyRoute for each coalition at the start of the turn. +# Supply routes need to cover the whole network to support multi-mode links. +# +# Traverse each friendly control point and build out a network from each. Nodes create +# connections to: +# +# 1. Bases connected by road +# 2. Bases connected by rail +# 3. Bases connected by shipping lane +# 4. Airports large enough to operate cargo planes connect to each other +# 5. Airports capable of operating helicopters connect to other airports within cargo +# helicopter range, and FOBs within half of the range (since they can't be refueled +# at the drop off). +# +# The costs of each link would be set such that the above order roughly corresponds to +# the prevalence of each type of transport. Most units should move by road, rail should +# be used a little less often than road, ships a bit less often than that, cargo planes +# infrequently, and helicopters rarely. Convoys, trains, and ships make the most +# interesting targets for players (and the easiest to generate AI flight plans for). class SupplyRoute: def __init__(self, control_points: List[ControlPoint]) -> None: self.control_points = control_points @@ -45,20 +64,16 @@ class SupplyRoute: def __len__(self) -> int: return len(self.control_points) - @classmethod - def for_control_point(cls, control_point: ControlPoint) -> SupplyRoute: - connected_friendly_points = control_point.transitive_connected_friendly_points() - if not connected_friendly_points: - return SupplyRoute([control_point]) - return SupplyRoute([control_point] + connected_friendly_points) + def connections_from(self, control_point: ControlPoint) -> Iterable: + raise NotImplementedError def shortest_path_between( self, origin: ControlPoint, destination: ControlPoint ) -> List[ControlPoint]: if origin not in self: - raise ValueError(f"{origin.name} is not in this supply route") + raise ValueError(f"{origin} is not in supply route to {destination}") if destination not in self: - raise ValueError(f"{destination.name} is not in this supply route") + raise ValueError(f"{destination} is not in supply route from {origin}") frontier = Frontier() frontier.push(origin, 0) @@ -74,7 +89,7 @@ class SupplyRoute: if cost > best_known[current]: continue - for neighbor in current.connected_points: + for neighbor in self.connections_from(current): if current.captured != neighbor.captured: continue @@ -97,3 +112,29 @@ class SupplyRoute: current = previous path.reverse() return path + + +class RoadNetwork(SupplyRoute): + @classmethod + def for_control_point(cls, control_point: ControlPoint) -> RoadNetwork: + connected_friendly_points = control_point.transitive_connected_friendly_points() + if not connected_friendly_points: + return RoadNetwork([control_point]) + return RoadNetwork([control_point] + connected_friendly_points) + + def connections_from(self, control_point: ControlPoint) -> Iterable: + yield from control_point.connected_points + + +class ShippingNetwork(SupplyRoute): + @classmethod + def for_control_point(cls, control_point: ControlPoint) -> ShippingNetwork: + connected_friendly_points = ( + control_point.transitive_friendly_shipping_destinations() + ) + if not connected_friendly_points: + return ShippingNetwork([control_point]) + return ShippingNetwork([control_point] + connected_friendly_points) + + def connections_from(self, control_point: ControlPoint) -> Iterable: + yield from control_point.shipping_lanes diff --git a/game/transfers.py b/game/transfers.py index 1a5bfba1..3685ed79 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -16,7 +16,7 @@ from gen.flights.ai_flight_planner_db import TRANSPORT_CAPABLE from gen.flights.closestairfields import ObjectiveDistanceCache from gen.flights.flightplan import FlightPlanBuilder from game.theater import ControlPoint, MissionTarget -from game.theater.supplyroutes import SupplyRoute +from game.theater.supplyroutes import RoadNetwork, ShippingNetwork, SupplyRoute from gen.naming import namegen from gen.flights.flight import Flight, FlightType @@ -242,20 +242,15 @@ class AirliftPlanner: return flight_size -class Convoy(MissionTarget, Transport): - def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: - super().__init__(namegen.next_convoy_name(), origin.position) +class MultiGroupTransport(MissionTarget, Transport): + def __init__( + self, name: str, origin: ControlPoint, destination: ControlPoint + ) -> None: + super().__init__(name, origin.position) self.origin = origin self.destination = destination self.transfers: List[TransferOrder] = [] - 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.origin.captured @@ -298,10 +293,30 @@ class Convoy(MissionTarget, Transport): return self.origin.captured def find_escape_route(self) -> Optional[ControlPoint]: - return None + raise NotImplementedError def description(self) -> str: - return f"In a convoy to {self.destination}" + raise NotImplementedError + + @property + def route_start(self) -> Point: + raise NotImplementedError + + @property + def route_end(self) -> Point: + raise NotImplementedError + + +class Convoy(MultiGroupTransport): + def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: + super().__init__(namegen.next_convoy_name(), origin, destination) + + 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) @property def route_start(self) -> Point: @@ -311,44 +326,85 @@ class Convoy(MissionTarget, Transport): def route_end(self) -> Point: return self.destination.convoy_spawns[self.origin] + def description(self) -> str: + return f"In a convoy to {self.destination}" -class ConvoyMap: + def find_escape_route(self) -> Optional[ControlPoint]: + return None + + +class CargoShip(MultiGroupTransport): + def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: + super().__init__(namegen.next_cargo_ship_name(), origin, destination) + + def mission_types(self, for_player: bool) -> Iterator[FlightType]: + if self.is_friendly(for_player): + return + + yield FlightType.ANTISHIP + yield from super().mission_types(for_player) + + @property + def route_start(self) -> Point: + return self.origin.shipping_lanes[self.destination][0] + + @property + def route_end(self) -> Point: + return self.destination.shipping_lanes[self.origin][-1] + + def description(self) -> str: + return f"On a ship to {self.destination}" + + def find_escape_route(self) -> Optional[ControlPoint]: + return None + + +class TransportMap: def __init__(self) -> None: - # Dict of origin -> destination -> convoy. - self.convoys: Dict[ControlPoint, Dict[ControlPoint, Convoy]] = defaultdict(dict) + # Dict of origin -> destination -> transport. + self.transports: Dict[ + ControlPoint, Dict[ControlPoint, MultiGroupTransport] + ] = defaultdict(dict) - def convoy_exists(self, origin: ControlPoint, destination: ControlPoint) -> bool: - return destination in self.convoys[origin] - - def find_convoy( + def create_transport( self, origin: ControlPoint, destination: ControlPoint - ) -> Optional[Convoy]: - return self.convoys[origin].get(destination) + ) -> MultiGroupTransport: + raise NotImplementedError - def find_or_create_convoy( + def transport_exists(self, origin: ControlPoint, destination: ControlPoint) -> bool: + return destination in self.transports[origin] + + def find_transport( self, origin: ControlPoint, destination: ControlPoint - ) -> Convoy: - convoy = self.find_convoy(origin, destination) - if convoy is None: - convoy = Convoy(origin, destination) - self.convoys[origin][destination] = convoy - return convoy + ) -> Optional[MultiGroupTransport]: + return self.transports[origin].get(destination) - def departing_from(self, origin: ControlPoint) -> Iterator[Convoy]: - yield from self.convoys[origin].values() + def find_or_create_transport( + self, origin: ControlPoint, destination: ControlPoint + ) -> MultiGroupTransport: + transport = self.find_transport(origin, destination) + if transport is None: + transport = self.create_transport(origin, destination) + self.transports[origin][destination] = transport + return transport - def travelling_to(self, destination: ControlPoint) -> Iterator[Convoy]: - for destination_dict in self.convoys.values(): + def departing_from(self, origin: ControlPoint) -> Iterator[MultiGroupTransport]: + yield from self.transports[origin].values() + + def travelling_to(self, destination: ControlPoint) -> Iterator[MultiGroupTransport]: + for destination_dict in self.transports.values(): if destination in destination_dict: yield destination_dict[destination] - def disband_convoy(self, convoy: Convoy) -> None: - self.convoys[convoy.origin][convoy.destination].disband() - del self.convoys[convoy.origin][convoy.destination] + def disband_transport(self, transport: MultiGroupTransport) -> None: + transport.disband() + del self.transports[transport.origin][transport.destination] - @staticmethod - def path_for(transfer: TransferOrder) -> List[ControlPoint]: - supply_route = SupplyRoute.for_control_point(transfer.position) + def network_for(self, control_point: ControlPoint) -> SupplyRoute: + raise NotImplementedError + + def path_for(self, transfer: TransferOrder) -> List[ControlPoint]: + supply_route = self.network_for(transfer.position) return supply_route.shortest_path_between( transfer.position, transfer.destination ) @@ -358,26 +414,47 @@ class ConvoyMap: def add(self, transfer: TransferOrder) -> None: next_stop = self.next_stop_for(transfer) - self.find_or_create_convoy(transfer.position, next_stop).add_units(transfer) + self.find_or_create_transport(transfer.position, next_stop).add_units(transfer) - def remove(self, convoy: Convoy, transfer: TransferOrder) -> None: - convoy.remove_units(transfer) - if not convoy.transfers: - self.disband_convoy(convoy) + def remove(self, transport: MultiGroupTransport, transfer: TransferOrder) -> None: + transport.remove_units(transfer) + if not transport.transfers: + self.disband_transport(transport) def disband_all(self) -> None: - for convoy in list(self): - self.disband_convoy(convoy) + for transport in list(self): + self.disband_transport(transport) - def __iter__(self) -> Iterator[Convoy]: - for destination_dict in self.convoys.values(): + def __iter__(self) -> Iterator[MultiGroupTransport]: + for destination_dict in self.transports.values(): yield from destination_dict.values() +class ConvoyMap(TransportMap): + def create_transport( + self, origin: ControlPoint, destination: ControlPoint + ) -> Convoy: + return Convoy(origin, destination) + + def network_for(self, control_point: ControlPoint) -> RoadNetwork: + return RoadNetwork.for_control_point(control_point) + + +class CargoShipMap(TransportMap): + def create_transport( + self, origin: ControlPoint, destination: ControlPoint + ) -> CargoShip: + return CargoShip(origin, destination) + + def network_for(self, control_point: ControlPoint) -> ShippingNetwork: + return ShippingNetwork.for_control_point(control_point) + + class PendingTransfers: def __init__(self, game: Game) -> None: self.game = game self.convoys = ConvoyMap() + self.cargo_ships = CargoShipMap() self.pending_transfers: List[TransferOrder] = [] def __iter__(self) -> Iterator[TransferOrder]: @@ -394,9 +471,12 @@ class PendingTransfers: return self.pending_transfers.index(transfer) def arrange_transport(self, transfer: TransferOrder) -> None: - supply_route = SupplyRoute.for_control_point(transfer.position) - if transfer.destination in supply_route: + if transfer.destination in RoadNetwork.for_control_point(transfer.position): self.convoys.add(transfer) + elif transfer.destination in ShippingNetwork.for_control_point( + transfer.position + ): + self.cargo_ships.add(transfer) else: AirliftPlanner(self.game, transfer).create_package_for_airlift() @@ -439,6 +519,11 @@ class PendingTransfers: ) -> None: self.convoys.remove(transport, transfer) + def _cancel_transport_cargo_ship( + self, transfer: TransferOrder, transport: CargoShip + ) -> None: + self.cargo_ships.remove(transport, transfer) + def cancel_transfer(self, transfer: TransferOrder) -> None: if transfer.transport is not None: self.cancel_transport(transfer, transfer.transport) diff --git a/game/unitdelivery.py b/game/unitdelivery.py index 402a25cd..d9cf3e0f 100644 --- a/game/unitdelivery.py +++ b/game/unitdelivery.py @@ -10,6 +10,7 @@ from dcs.unittype import UnitType, VehicleType from game.theater import ControlPoint, SupplyRoute from gen.flights.closestairfields import ObjectiveDistanceCache from .db import PRICES +from .theater.supplyroutes import RoadNetwork, ShippingNetwork from .transfers import TransferOrder if TYPE_CHECKING: @@ -19,7 +20,6 @@ if TYPE_CHECKING: @dataclass(frozen=True) class GroundUnitSource: control_point: ControlPoint - requires_airlift: bool class PendingUnitDeliveries: @@ -84,9 +84,9 @@ class PendingUnitDeliveries: if ( issubclass(unit_type, VehicleType) - and self.destination != ground_unit_source.control_point + and self.destination != ground_unit_source ): - source = ground_unit_source.control_point + source = ground_unit_source d = units_needing_transfer else: source = self.destination @@ -106,41 +106,45 @@ class PendingUnitDeliveries: self.destination.base.commit_losses(sold_units) if units_needing_transfer: - ground_unit_source.control_point.base.commision_units( - units_needing_transfer - ) - self.create_transfer( - game, ground_unit_source.control_point, units_needing_transfer - ) + ground_unit_source.base.commision_units(units_needing_transfer) + self.create_transfer(game, ground_unit_source, units_needing_transfer) def create_transfer( self, game: Game, source: ControlPoint, units: Dict[Type[VehicleType], int] ) -> None: game.transfers.new_transfer(TransferOrder(source, self.destination, units)) - def find_ground_unit_source(self, game: Game) -> Optional[GroundUnitSource]: + def find_ground_unit_source(self, game: Game) -> Optional[ControlPoint]: # This is running *after* the turn counter has been incremented, so this is the # reaction to turn 0. On turn zero we allow units to be recruited anywhere for # delivery on turn 1 so that turn 1 always starts with units on the front line. if game.turn == 1: - return GroundUnitSource(self.destination, requires_airlift=False) + return self.destination # Fast path if the destination is a valid source. if self.destination.can_recruit_ground_units(game): - return GroundUnitSource(self.destination, requires_airlift=False) + return self.destination - by_road = self.find_ground_unit_source_by_road(game) + by_road = self.find_ground_unit_source_in_supply_route( + RoadNetwork.for_control_point(self.destination), game + ) if by_road is not None: - return GroundUnitSource(by_road, requires_airlift=False) + return by_road + + by_ship = self.find_ground_unit_source_in_supply_route( + ShippingNetwork.for_control_point(self.destination), game + ) + if by_ship is not None: + return by_ship by_air = self.find_ground_unit_source_by_air(game) if by_air is not None: - return GroundUnitSource(by_air, requires_airlift=True) + return by_air return None - def find_ground_unit_source_by_road(self, game: Game) -> Optional[ControlPoint]: - supply_route = SupplyRoute.for_control_point(self.destination) - + def find_ground_unit_source_in_supply_route( + self, supply_route: SupplyRoute, game: Game + ) -> Optional[ControlPoint]: sources = [] for control_point in supply_route: if control_point.can_recruit_ground_units(game): diff --git a/game/unitmap.py b/game/unitmap.py index c6d8d0de..4571f2b3 100644 --- a/game/unitmap.py +++ b/game/unitmap.py @@ -9,7 +9,7 @@ from dcs.unittype import VehicleType from game import db from game.theater import Airfield, ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import BuildingGroundObject -from game.transfers import Convoy, TransferOrder +from game.transfers import MultiGroupTransport, TransferOrder from gen.flights.flight import Flight @@ -29,7 +29,7 @@ class GroundObjectUnit: @dataclass(frozen=True) class ConvoyUnit: unit_type: Type[VehicleType] - convoy: Convoy + convoy: MultiGroupTransport @dataclass(frozen=True) @@ -130,7 +130,7 @@ class UnitMap: def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]: return self.ground_object_units.get(name, None) - def add_convoy_units(self, group: Group, convoy: Convoy) -> None: + def add_convoy_units(self, group: Group, convoy: MultiGroupTransport) -> None: for unit in group.units: # The actual name is a String (the pydcs translatable string), which # doesn't define __eq__. diff --git a/gen/convoys.py b/gen/convoys.py index 9c904009..f3033e9e 100644 --- a/gen/convoys.py +++ b/gen/convoys.py @@ -10,7 +10,7 @@ from dcs.unit import Vehicle from dcs.unitgroup import VehicleGroup from dcs.unittype import VehicleType -from game.transfers import Convoy +from game.transfers import MultiGroupTransport from game.unitmap import UnitMap from game.utils import kph @@ -30,7 +30,7 @@ class ConvoyGenerator: for convoy in self.game.transfers.convoys: self.generate_convoy(convoy) - def generate_convoy(self, convoy: Convoy) -> VehicleGroup: + def generate_convoy(self, convoy: MultiGroupTransport) -> VehicleGroup: group = self._create_mixed_unit_group( convoy.name, convoy.route_start, diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index a82877b0..16882bc6 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -39,7 +39,7 @@ from game.theater.theatergroundobject import ( NavalGroundObject, VehicleGroupGroundObject, ) -from game.transfers import Convoy, TransferOrder +from game.transfers import Convoy, MultiGroupTransport, TransferOrder from game.utils import Distance, nautical_miles from gen import Conflict from gen.ato import Package @@ -445,7 +445,7 @@ class ObjectiveFinder: airfields.append(control_point) return self._targets_by_range(airfields) - def convoys(self) -> Iterator[Convoy]: + def convoys(self) -> Iterator[MultiGroupTransport]: for front_line in self.front_lines(): if front_line.control_point_a.is_friendly(self.is_player): enemy_cp = front_line.control_point_a diff --git a/gen/naming.py b/gen/naming.py index d8c7f768..a2a344ba 100644 --- a/gen/naming.py +++ b/gen/naming.py @@ -251,6 +251,7 @@ class NameGenerator: infantry_number = 0 aircraft_number = 0 convoy_number = 0 + cargo_ship_number = 0 ANIMALS = ANIMALS existing_alphas: List[str] = [] @@ -260,6 +261,7 @@ class NameGenerator: cls.number = 0 cls.infantry_number = 0 cls.convoy_number = 0 + cls.cargo_ship_number = 0 cls.ANIMALS = ANIMALS cls.existing_alphas = [] @@ -269,6 +271,7 @@ class NameGenerator: cls.infantry_number = 0 cls.aircraft_number = 0 cls.convoy_number = 0 + cls.cargo_ship_number = 0 @classmethod def next_aircraft_name(cls, country: Country, parent_base_id: int, flight: Flight): @@ -335,6 +338,11 @@ class NameGenerator: cls.convoy_number += 1 return f"Convoy {cls.convoy_number:03}" + @classmethod + def next_cargo_ship_name(cls) -> str: + cls.cargo_ship_number += 1 + return f"Cargo Ship {cls.cargo_ship_number:03}" + @classmethod def random_objective_name(cls): if len(cls.ANIMALS) == 0: diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index d5e2ec2a..e353f60c 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -4,7 +4,7 @@ import datetime import logging import math from functools import singledispatchmethod -from typing import Iterable, Iterator, List, Optional, Tuple +from typing import Iterable, Iterator, List, Optional, Sequence, Tuple from PySide2 import QtCore, QtWidgets from PySide2.QtCore import QLineF, QPointF, QRectF, Qt @@ -87,7 +87,7 @@ def bernstein(t: float, i: int, n: int) -> float: return binomial(i, n) * (t ** i) * ((1 - t) ** (n - i)) -def bezier(t: float, points: Iterable[Tuple[float, float]]) -> Tuple[float, float]: +def bezier(t: float, points: Sequence[Tuple[float, float]]) -> Tuple[float, float]: """Calculate coordinate of a point in the bezier curve""" n = len(points) - 1 x = y = 0 @@ -99,7 +99,7 @@ def bezier(t: float, points: Iterable[Tuple[float, float]]) -> Tuple[float, floa def bezier_curve_range( - n: int, points: Iterable[Tuple[float, float]] + n: int, points: Sequence[Tuple[float, float]] ) -> Iterator[Tuple[float, float]]: """Range of points in a curve bezier""" for i in range(n): @@ -145,7 +145,7 @@ class QLiberationMap(QGraphicsView): QtCore.QLineF(QPointF(0, 0), QPointF(0, 0)) ) self.movement_line.setPen(QPen(CONST.COLORS["orange"], width=10.0)) - self.selected_cp: QMapControlPoint = None + self.selected_cp: Optional[QMapControlPoint] = None GameUpdateSignal.get_instance().flight_paths_changed.connect( lambda: self.draw_flight_plans(self.scene()) @@ -767,7 +767,7 @@ class QLiberationMap(QGraphicsView): scene: QGraphicsScene, number: int, waypoint: FlightWaypoint, - position: Tuple[int, int], + position: Tuple[float, float], flight_plan: FlightPlan, ) -> None: @@ -880,19 +880,28 @@ class QLiberationMap(QGraphicsView): self.draw_shipping_lane_between(cp, destination) def draw_shipping_lane_between(self, a: ControlPoint, b: ControlPoint) -> None: + ship_map = self.game.transfers.cargo_ships + ships = [] + ship = ship_map.find_transport(a, b) + if ship is not None: + ships.append(ship) + ship = ship_map.find_transport(b, a) + if ship is not None: + ships.append(ship) + scene = self.scene() for pa, pb in self.bezier_points(a.shipping_lanes[b]): - scene.addItem(ShippingLaneSegment(pa[0], pa[1], pb[0], pb[1], a, b)) + scene.addItem(ShippingLaneSegment(pa[0], pa[1], pb[0], pb[1], a, b, ships)) def draw_supply_route_between(self, a: ControlPoint, b: ControlPoint) -> None: scene = self.scene() convoy_map = self.game.transfers.convoys convoys = [] - convoy = convoy_map.find_convoy(a, b) + convoy = convoy_map.find_transport(a, b) if convoy is not None: convoys.append(convoy) - convoy = convoy_map.find_convoy(b, a) + convoy = convoy_map.find_transport(b, a) if convoy is not None: convoys.append(convoy) diff --git a/qt_ui/widgets/map/ShippingLaneSegment.py b/qt_ui/widgets/map/ShippingLaneSegment.py index 9eb111f8..02d445a4 100644 --- a/qt_ui/widgets/map/ShippingLaneSegment.py +++ b/qt_ui/widgets/map/ShippingLaneSegment.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import List, Optional from PySide2.QtCore import Qt from PySide2.QtGui import QColor, QPen @@ -8,6 +8,7 @@ from PySide2.QtWidgets import ( ) from game.theater import ControlPoint +from game.transfers import CargoShip from qt_ui.uiconstants import COLORS @@ -20,12 +21,13 @@ class ShippingLaneSegment(QGraphicsLineItem): y1: float, control_point_a: ControlPoint, control_point_b: ControlPoint, + ships: List[CargoShip], parent: Optional[QGraphicsItem] = None, ) -> None: super().__init__(x0, y0, x1, y1, parent) self.control_point_a = control_point_a self.control_point_b = control_point_b - self.ships = [] + self.ships = ships self.setPen(self.make_pen()) self.setToolTip(self.make_tooltip()) self.setAcceptHoverEvents(True) diff --git a/qt_ui/windows/basemenu/NewUnitTransferDialog.py b/qt_ui/windows/basemenu/NewUnitTransferDialog.py index 591d8a07..50c44572 100644 --- a/qt_ui/windows/basemenu/NewUnitTransferDialog.py +++ b/qt_ui/windows/basemenu/NewUnitTransferDialog.py @@ -2,12 +2,10 @@ from __future__ import annotations import logging from collections import defaultdict -from dataclasses import dataclass from typing import Callable, Dict, Type from PySide2.QtCore import Qt from PySide2.QtWidgets import ( - QCheckBox, QComboBox, QDialog, QFrame, @@ -15,7 +13,6 @@ from PySide2.QtWidgets import ( QGroupBox, QHBoxLayout, QLabel, - QMessageBox, QPushButton, QScrollArea, QSizePolicy, @@ -27,7 +24,7 @@ from dcs.task import PinpointStrike from dcs.unittype import UnitType from game import Game, db -from game.theater import ControlPoint, SupplyRoute +from game.theater import ControlPoint from game.transfers import TransferOrder from qt_ui.models import GameModel from qt_ui.widgets.QLabeledWidget import QLabeledWidget