mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
First pass at cargo ships.
The simple form of this works, but without the multi-mode routing it'll only get used when the final destination is a port with a link to a port with a factory. These also aren't targetable or simulated yet. https://github.com/Khopa/dcs_liberation/issues/826
This commit is contained in:
parent
42694d2004
commit
ba8fafcc95
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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__.
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user