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))
|
connected.extend(cp.transitive_connected_friendly_points(seen))
|
||||||
return connected
|
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
|
@property
|
||||||
def has_factory(self) -> bool:
|
def has_factory(self) -> bool:
|
||||||
for tgo in self.connected_objectives:
|
for tgo in self.connected_objectives:
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import heapq
|
|||||||
import math
|
import math
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass, field
|
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
|
from game.theater.controlpoint import ControlPoint
|
||||||
|
|
||||||
@ -32,6 +32,25 @@ class Frontier:
|
|||||||
return bool(self.nodes)
|
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:
|
class SupplyRoute:
|
||||||
def __init__(self, control_points: List[ControlPoint]) -> None:
|
def __init__(self, control_points: List[ControlPoint]) -> None:
|
||||||
self.control_points = control_points
|
self.control_points = control_points
|
||||||
@ -45,20 +64,16 @@ class SupplyRoute:
|
|||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(self.control_points)
|
return len(self.control_points)
|
||||||
|
|
||||||
@classmethod
|
def connections_from(self, control_point: ControlPoint) -> Iterable:
|
||||||
def for_control_point(cls, control_point: ControlPoint) -> SupplyRoute:
|
raise NotImplementedError
|
||||||
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 shortest_path_between(
|
def shortest_path_between(
|
||||||
self, origin: ControlPoint, destination: ControlPoint
|
self, origin: ControlPoint, destination: ControlPoint
|
||||||
) -> List[ControlPoint]:
|
) -> List[ControlPoint]:
|
||||||
if origin not in self:
|
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:
|
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 = Frontier()
|
||||||
frontier.push(origin, 0)
|
frontier.push(origin, 0)
|
||||||
@ -74,7 +89,7 @@ class SupplyRoute:
|
|||||||
if cost > best_known[current]:
|
if cost > best_known[current]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for neighbor in current.connected_points:
|
for neighbor in self.connections_from(current):
|
||||||
if current.captured != neighbor.captured:
|
if current.captured != neighbor.captured:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -97,3 +112,29 @@ class SupplyRoute:
|
|||||||
current = previous
|
current = previous
|
||||||
path.reverse()
|
path.reverse()
|
||||||
return path
|
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.closestairfields import ObjectiveDistanceCache
|
||||||
from gen.flights.flightplan import FlightPlanBuilder
|
from gen.flights.flightplan import FlightPlanBuilder
|
||||||
from game.theater import ControlPoint, MissionTarget
|
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.naming import namegen
|
||||||
from gen.flights.flight import Flight, FlightType
|
from gen.flights.flight import Flight, FlightType
|
||||||
|
|
||||||
@ -242,20 +242,15 @@ class AirliftPlanner:
|
|||||||
return flight_size
|
return flight_size
|
||||||
|
|
||||||
|
|
||||||
class Convoy(MissionTarget, Transport):
|
class MultiGroupTransport(MissionTarget, Transport):
|
||||||
def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None:
|
def __init__(
|
||||||
super().__init__(namegen.next_convoy_name(), origin.position)
|
self, name: str, origin: ControlPoint, destination: ControlPoint
|
||||||
|
) -> None:
|
||||||
|
super().__init__(name, origin.position)
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
self.destination = destination
|
self.destination = destination
|
||||||
self.transfers: List[TransferOrder] = []
|
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:
|
def is_friendly(self, to_player: bool) -> bool:
|
||||||
return self.origin.captured
|
return self.origin.captured
|
||||||
|
|
||||||
@ -298,10 +293,30 @@ class Convoy(MissionTarget, Transport):
|
|||||||
return self.origin.captured
|
return self.origin.captured
|
||||||
|
|
||||||
def find_escape_route(self) -> Optional[ControlPoint]:
|
def find_escape_route(self) -> Optional[ControlPoint]:
|
||||||
return None
|
raise NotImplementedError
|
||||||
|
|
||||||
def description(self) -> str:
|
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
|
@property
|
||||||
def route_start(self) -> Point:
|
def route_start(self) -> Point:
|
||||||
@ -311,44 +326,85 @@ class Convoy(MissionTarget, Transport):
|
|||||||
def route_end(self) -> Point:
|
def route_end(self) -> Point:
|
||||||
return self.destination.convoy_spawns[self.origin]
|
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:
|
def __init__(self) -> None:
|
||||||
# Dict of origin -> destination -> convoy.
|
# Dict of origin -> destination -> transport.
|
||||||
self.convoys: Dict[ControlPoint, Dict[ControlPoint, Convoy]] = defaultdict(dict)
|
self.transports: Dict[
|
||||||
|
ControlPoint, Dict[ControlPoint, MultiGroupTransport]
|
||||||
|
] = defaultdict(dict)
|
||||||
|
|
||||||
def convoy_exists(self, origin: ControlPoint, destination: ControlPoint) -> bool:
|
def create_transport(
|
||||||
return destination in self.convoys[origin]
|
|
||||||
|
|
||||||
def find_convoy(
|
|
||||||
self, origin: ControlPoint, destination: ControlPoint
|
self, origin: ControlPoint, destination: ControlPoint
|
||||||
) -> Optional[Convoy]:
|
) -> MultiGroupTransport:
|
||||||
return self.convoys[origin].get(destination)
|
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
|
self, origin: ControlPoint, destination: ControlPoint
|
||||||
) -> Convoy:
|
) -> Optional[MultiGroupTransport]:
|
||||||
convoy = self.find_convoy(origin, destination)
|
return self.transports[origin].get(destination)
|
||||||
if convoy is None:
|
|
||||||
convoy = Convoy(origin, destination)
|
|
||||||
self.convoys[origin][destination] = convoy
|
|
||||||
return convoy
|
|
||||||
|
|
||||||
def departing_from(self, origin: ControlPoint) -> Iterator[Convoy]:
|
def find_or_create_transport(
|
||||||
yield from self.convoys[origin].values()
|
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]:
|
def departing_from(self, origin: ControlPoint) -> Iterator[MultiGroupTransport]:
|
||||||
for destination_dict in self.convoys.values():
|
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:
|
if destination in destination_dict:
|
||||||
yield destination_dict[destination]
|
yield destination_dict[destination]
|
||||||
|
|
||||||
def disband_convoy(self, convoy: Convoy) -> None:
|
def disband_transport(self, transport: MultiGroupTransport) -> None:
|
||||||
self.convoys[convoy.origin][convoy.destination].disband()
|
transport.disband()
|
||||||
del self.convoys[convoy.origin][convoy.destination]
|
del self.transports[transport.origin][transport.destination]
|
||||||
|
|
||||||
@staticmethod
|
def network_for(self, control_point: ControlPoint) -> SupplyRoute:
|
||||||
def path_for(transfer: TransferOrder) -> List[ControlPoint]:
|
raise NotImplementedError
|
||||||
supply_route = SupplyRoute.for_control_point(transfer.position)
|
|
||||||
|
def path_for(self, transfer: TransferOrder) -> List[ControlPoint]:
|
||||||
|
supply_route = self.network_for(transfer.position)
|
||||||
return supply_route.shortest_path_between(
|
return supply_route.shortest_path_between(
|
||||||
transfer.position, transfer.destination
|
transfer.position, transfer.destination
|
||||||
)
|
)
|
||||||
@ -358,26 +414,47 @@ class ConvoyMap:
|
|||||||
|
|
||||||
def add(self, transfer: TransferOrder) -> None:
|
def add(self, transfer: TransferOrder) -> None:
|
||||||
next_stop = self.next_stop_for(transfer)
|
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:
|
def remove(self, transport: MultiGroupTransport, transfer: TransferOrder) -> None:
|
||||||
convoy.remove_units(transfer)
|
transport.remove_units(transfer)
|
||||||
if not convoy.transfers:
|
if not transport.transfers:
|
||||||
self.disband_convoy(convoy)
|
self.disband_transport(transport)
|
||||||
|
|
||||||
def disband_all(self) -> None:
|
def disband_all(self) -> None:
|
||||||
for convoy in list(self):
|
for transport in list(self):
|
||||||
self.disband_convoy(convoy)
|
self.disband_transport(transport)
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[Convoy]:
|
def __iter__(self) -> Iterator[MultiGroupTransport]:
|
||||||
for destination_dict in self.convoys.values():
|
for destination_dict in self.transports.values():
|
||||||
yield from destination_dict.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:
|
class PendingTransfers:
|
||||||
def __init__(self, game: Game) -> None:
|
def __init__(self, game: Game) -> None:
|
||||||
self.game = game
|
self.game = game
|
||||||
self.convoys = ConvoyMap()
|
self.convoys = ConvoyMap()
|
||||||
|
self.cargo_ships = CargoShipMap()
|
||||||
self.pending_transfers: List[TransferOrder] = []
|
self.pending_transfers: List[TransferOrder] = []
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[TransferOrder]:
|
def __iter__(self) -> Iterator[TransferOrder]:
|
||||||
@ -394,9 +471,12 @@ class PendingTransfers:
|
|||||||
return self.pending_transfers.index(transfer)
|
return self.pending_transfers.index(transfer)
|
||||||
|
|
||||||
def arrange_transport(self, transfer: TransferOrder) -> None:
|
def arrange_transport(self, transfer: TransferOrder) -> None:
|
||||||
supply_route = SupplyRoute.for_control_point(transfer.position)
|
if transfer.destination in RoadNetwork.for_control_point(transfer.position):
|
||||||
if transfer.destination in supply_route:
|
|
||||||
self.convoys.add(transfer)
|
self.convoys.add(transfer)
|
||||||
|
elif transfer.destination in ShippingNetwork.for_control_point(
|
||||||
|
transfer.position
|
||||||
|
):
|
||||||
|
self.cargo_ships.add(transfer)
|
||||||
else:
|
else:
|
||||||
AirliftPlanner(self.game, transfer).create_package_for_airlift()
|
AirliftPlanner(self.game, transfer).create_package_for_airlift()
|
||||||
|
|
||||||
@ -439,6 +519,11 @@ class PendingTransfers:
|
|||||||
) -> None:
|
) -> None:
|
||||||
self.convoys.remove(transport, transfer)
|
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:
|
def cancel_transfer(self, transfer: TransferOrder) -> None:
|
||||||
if transfer.transport is not None:
|
if transfer.transport is not None:
|
||||||
self.cancel_transport(transfer, transfer.transport)
|
self.cancel_transport(transfer, transfer.transport)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from dcs.unittype import UnitType, VehicleType
|
|||||||
from game.theater import ControlPoint, SupplyRoute
|
from game.theater import ControlPoint, SupplyRoute
|
||||||
from gen.flights.closestairfields import ObjectiveDistanceCache
|
from gen.flights.closestairfields import ObjectiveDistanceCache
|
||||||
from .db import PRICES
|
from .db import PRICES
|
||||||
|
from .theater.supplyroutes import RoadNetwork, ShippingNetwork
|
||||||
from .transfers import TransferOrder
|
from .transfers import TransferOrder
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -19,7 +20,6 @@ if TYPE_CHECKING:
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class GroundUnitSource:
|
class GroundUnitSource:
|
||||||
control_point: ControlPoint
|
control_point: ControlPoint
|
||||||
requires_airlift: bool
|
|
||||||
|
|
||||||
|
|
||||||
class PendingUnitDeliveries:
|
class PendingUnitDeliveries:
|
||||||
@ -84,9 +84,9 @@ class PendingUnitDeliveries:
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
issubclass(unit_type, VehicleType)
|
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
|
d = units_needing_transfer
|
||||||
else:
|
else:
|
||||||
source = self.destination
|
source = self.destination
|
||||||
@ -106,41 +106,45 @@ class PendingUnitDeliveries:
|
|||||||
self.destination.base.commit_losses(sold_units)
|
self.destination.base.commit_losses(sold_units)
|
||||||
|
|
||||||
if units_needing_transfer:
|
if units_needing_transfer:
|
||||||
ground_unit_source.control_point.base.commision_units(
|
ground_unit_source.base.commision_units(units_needing_transfer)
|
||||||
units_needing_transfer
|
self.create_transfer(game, ground_unit_source, units_needing_transfer)
|
||||||
)
|
|
||||||
self.create_transfer(
|
|
||||||
game, ground_unit_source.control_point, units_needing_transfer
|
|
||||||
)
|
|
||||||
|
|
||||||
def create_transfer(
|
def create_transfer(
|
||||||
self, game: Game, source: ControlPoint, units: Dict[Type[VehicleType], int]
|
self, game: Game, source: ControlPoint, units: Dict[Type[VehicleType], int]
|
||||||
) -> None:
|
) -> None:
|
||||||
game.transfers.new_transfer(TransferOrder(source, self.destination, units))
|
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
|
# 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
|
# 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.
|
# delivery on turn 1 so that turn 1 always starts with units on the front line.
|
||||||
if game.turn == 1:
|
if game.turn == 1:
|
||||||
return GroundUnitSource(self.destination, requires_airlift=False)
|
return self.destination
|
||||||
|
|
||||||
# Fast path if the destination is a valid source.
|
# Fast path if the destination is a valid source.
|
||||||
if self.destination.can_recruit_ground_units(game):
|
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:
|
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)
|
by_air = self.find_ground_unit_source_by_air(game)
|
||||||
if by_air is not None:
|
if by_air is not None:
|
||||||
return GroundUnitSource(by_air, requires_airlift=True)
|
return by_air
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def find_ground_unit_source_by_road(self, game: Game) -> Optional[ControlPoint]:
|
def find_ground_unit_source_in_supply_route(
|
||||||
supply_route = SupplyRoute.for_control_point(self.destination)
|
self, supply_route: SupplyRoute, game: Game
|
||||||
|
) -> Optional[ControlPoint]:
|
||||||
sources = []
|
sources = []
|
||||||
for control_point in supply_route:
|
for control_point in supply_route:
|
||||||
if control_point.can_recruit_ground_units(game):
|
if control_point.can_recruit_ground_units(game):
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from dcs.unittype import VehicleType
|
|||||||
from game import db
|
from game import db
|
||||||
from game.theater import Airfield, ControlPoint, TheaterGroundObject
|
from game.theater import Airfield, ControlPoint, TheaterGroundObject
|
||||||
from game.theater.theatergroundobject import BuildingGroundObject
|
from game.theater.theatergroundobject import BuildingGroundObject
|
||||||
from game.transfers import Convoy, TransferOrder
|
from game.transfers import MultiGroupTransport, TransferOrder
|
||||||
from gen.flights.flight import Flight
|
from gen.flights.flight import Flight
|
||||||
|
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ class GroundObjectUnit:
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ConvoyUnit:
|
class ConvoyUnit:
|
||||||
unit_type: Type[VehicleType]
|
unit_type: Type[VehicleType]
|
||||||
convoy: Convoy
|
convoy: MultiGroupTransport
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@ -130,7 +130,7 @@ class UnitMap:
|
|||||||
def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]:
|
def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]:
|
||||||
return self.ground_object_units.get(name, None)
|
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:
|
for unit in group.units:
|
||||||
# The actual name is a String (the pydcs translatable string), which
|
# The actual name is a String (the pydcs translatable string), which
|
||||||
# doesn't define __eq__.
|
# doesn't define __eq__.
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from dcs.unit import Vehicle
|
|||||||
from dcs.unitgroup import VehicleGroup
|
from dcs.unitgroup import VehicleGroup
|
||||||
from dcs.unittype import VehicleType
|
from dcs.unittype import VehicleType
|
||||||
|
|
||||||
from game.transfers import Convoy
|
from game.transfers import MultiGroupTransport
|
||||||
from game.unitmap import UnitMap
|
from game.unitmap import UnitMap
|
||||||
from game.utils import kph
|
from game.utils import kph
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ class ConvoyGenerator:
|
|||||||
for convoy in self.game.transfers.convoys:
|
for convoy in self.game.transfers.convoys:
|
||||||
self.generate_convoy(convoy)
|
self.generate_convoy(convoy)
|
||||||
|
|
||||||
def generate_convoy(self, convoy: Convoy) -> VehicleGroup:
|
def generate_convoy(self, convoy: MultiGroupTransport) -> VehicleGroup:
|
||||||
group = self._create_mixed_unit_group(
|
group = self._create_mixed_unit_group(
|
||||||
convoy.name,
|
convoy.name,
|
||||||
convoy.route_start,
|
convoy.route_start,
|
||||||
|
|||||||
@ -39,7 +39,7 @@ from game.theater.theatergroundobject import (
|
|||||||
NavalGroundObject,
|
NavalGroundObject,
|
||||||
VehicleGroupGroundObject,
|
VehicleGroupGroundObject,
|
||||||
)
|
)
|
||||||
from game.transfers import Convoy, TransferOrder
|
from game.transfers import Convoy, MultiGroupTransport, TransferOrder
|
||||||
from game.utils import Distance, nautical_miles
|
from game.utils import Distance, nautical_miles
|
||||||
from gen import Conflict
|
from gen import Conflict
|
||||||
from gen.ato import Package
|
from gen.ato import Package
|
||||||
@ -445,7 +445,7 @@ class ObjectiveFinder:
|
|||||||
airfields.append(control_point)
|
airfields.append(control_point)
|
||||||
return self._targets_by_range(airfields)
|
return self._targets_by_range(airfields)
|
||||||
|
|
||||||
def convoys(self) -> Iterator[Convoy]:
|
def convoys(self) -> Iterator[MultiGroupTransport]:
|
||||||
for front_line in self.front_lines():
|
for front_line in self.front_lines():
|
||||||
if front_line.control_point_a.is_friendly(self.is_player):
|
if front_line.control_point_a.is_friendly(self.is_player):
|
||||||
enemy_cp = front_line.control_point_a
|
enemy_cp = front_line.control_point_a
|
||||||
|
|||||||
@ -251,6 +251,7 @@ class NameGenerator:
|
|||||||
infantry_number = 0
|
infantry_number = 0
|
||||||
aircraft_number = 0
|
aircraft_number = 0
|
||||||
convoy_number = 0
|
convoy_number = 0
|
||||||
|
cargo_ship_number = 0
|
||||||
|
|
||||||
ANIMALS = ANIMALS
|
ANIMALS = ANIMALS
|
||||||
existing_alphas: List[str] = []
|
existing_alphas: List[str] = []
|
||||||
@ -260,6 +261,7 @@ class NameGenerator:
|
|||||||
cls.number = 0
|
cls.number = 0
|
||||||
cls.infantry_number = 0
|
cls.infantry_number = 0
|
||||||
cls.convoy_number = 0
|
cls.convoy_number = 0
|
||||||
|
cls.cargo_ship_number = 0
|
||||||
cls.ANIMALS = ANIMALS
|
cls.ANIMALS = ANIMALS
|
||||||
cls.existing_alphas = []
|
cls.existing_alphas = []
|
||||||
|
|
||||||
@ -269,6 +271,7 @@ class NameGenerator:
|
|||||||
cls.infantry_number = 0
|
cls.infantry_number = 0
|
||||||
cls.aircraft_number = 0
|
cls.aircraft_number = 0
|
||||||
cls.convoy_number = 0
|
cls.convoy_number = 0
|
||||||
|
cls.cargo_ship_number = 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def next_aircraft_name(cls, country: Country, parent_base_id: int, flight: Flight):
|
def next_aircraft_name(cls, country: Country, parent_base_id: int, flight: Flight):
|
||||||
@ -335,6 +338,11 @@ class NameGenerator:
|
|||||||
cls.convoy_number += 1
|
cls.convoy_number += 1
|
||||||
return f"Convoy {cls.convoy_number:03}"
|
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
|
@classmethod
|
||||||
def random_objective_name(cls):
|
def random_objective_name(cls):
|
||||||
if len(cls.ANIMALS) == 0:
|
if len(cls.ANIMALS) == 0:
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import datetime
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
from functools import singledispatchmethod
|
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 import QtCore, QtWidgets
|
||||||
from PySide2.QtCore import QLineF, QPointF, QRectF, Qt
|
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))
|
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"""
|
"""Calculate coordinate of a point in the bezier curve"""
|
||||||
n = len(points) - 1
|
n = len(points) - 1
|
||||||
x = y = 0
|
x = y = 0
|
||||||
@ -99,7 +99,7 @@ def bezier(t: float, points: Iterable[Tuple[float, float]]) -> Tuple[float, floa
|
|||||||
|
|
||||||
|
|
||||||
def bezier_curve_range(
|
def bezier_curve_range(
|
||||||
n: int, points: Iterable[Tuple[float, float]]
|
n: int, points: Sequence[Tuple[float, float]]
|
||||||
) -> Iterator[Tuple[float, float]]:
|
) -> Iterator[Tuple[float, float]]:
|
||||||
"""Range of points in a curve bezier"""
|
"""Range of points in a curve bezier"""
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
@ -145,7 +145,7 @@ class QLiberationMap(QGraphicsView):
|
|||||||
QtCore.QLineF(QPointF(0, 0), QPointF(0, 0))
|
QtCore.QLineF(QPointF(0, 0), QPointF(0, 0))
|
||||||
)
|
)
|
||||||
self.movement_line.setPen(QPen(CONST.COLORS["orange"], width=10.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(
|
GameUpdateSignal.get_instance().flight_paths_changed.connect(
|
||||||
lambda: self.draw_flight_plans(self.scene())
|
lambda: self.draw_flight_plans(self.scene())
|
||||||
@ -767,7 +767,7 @@ class QLiberationMap(QGraphicsView):
|
|||||||
scene: QGraphicsScene,
|
scene: QGraphicsScene,
|
||||||
number: int,
|
number: int,
|
||||||
waypoint: FlightWaypoint,
|
waypoint: FlightWaypoint,
|
||||||
position: Tuple[int, int],
|
position: Tuple[float, float],
|
||||||
flight_plan: FlightPlan,
|
flight_plan: FlightPlan,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
@ -880,19 +880,28 @@ class QLiberationMap(QGraphicsView):
|
|||||||
self.draw_shipping_lane_between(cp, destination)
|
self.draw_shipping_lane_between(cp, destination)
|
||||||
|
|
||||||
def draw_shipping_lane_between(self, a: ControlPoint, b: ControlPoint) -> None:
|
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()
|
scene = self.scene()
|
||||||
for pa, pb in self.bezier_points(a.shipping_lanes[b]):
|
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:
|
def draw_supply_route_between(self, a: ControlPoint, b: ControlPoint) -> None:
|
||||||
scene = self.scene()
|
scene = self.scene()
|
||||||
|
|
||||||
convoy_map = self.game.transfers.convoys
|
convoy_map = self.game.transfers.convoys
|
||||||
convoys = []
|
convoys = []
|
||||||
convoy = convoy_map.find_convoy(a, b)
|
convoy = convoy_map.find_transport(a, b)
|
||||||
if convoy is not None:
|
if convoy is not None:
|
||||||
convoys.append(convoy)
|
convoys.append(convoy)
|
||||||
convoy = convoy_map.find_convoy(b, a)
|
convoy = convoy_map.find_transport(b, a)
|
||||||
if convoy is not None:
|
if convoy is not None:
|
||||||
convoys.append(convoy)
|
convoys.append(convoy)
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from PySide2.QtCore import Qt
|
from PySide2.QtCore import Qt
|
||||||
from PySide2.QtGui import QColor, QPen
|
from PySide2.QtGui import QColor, QPen
|
||||||
@ -8,6 +8,7 @@ from PySide2.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from game.theater import ControlPoint
|
from game.theater import ControlPoint
|
||||||
|
from game.transfers import CargoShip
|
||||||
from qt_ui.uiconstants import COLORS
|
from qt_ui.uiconstants import COLORS
|
||||||
|
|
||||||
|
|
||||||
@ -20,12 +21,13 @@ class ShippingLaneSegment(QGraphicsLineItem):
|
|||||||
y1: float,
|
y1: float,
|
||||||
control_point_a: ControlPoint,
|
control_point_a: ControlPoint,
|
||||||
control_point_b: ControlPoint,
|
control_point_b: ControlPoint,
|
||||||
|
ships: List[CargoShip],
|
||||||
parent: Optional[QGraphicsItem] = None,
|
parent: Optional[QGraphicsItem] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(x0, y0, x1, y1, parent)
|
super().__init__(x0, y0, x1, y1, parent)
|
||||||
self.control_point_a = control_point_a
|
self.control_point_a = control_point_a
|
||||||
self.control_point_b = control_point_b
|
self.control_point_b = control_point_b
|
||||||
self.ships = []
|
self.ships = ships
|
||||||
self.setPen(self.make_pen())
|
self.setPen(self.make_pen())
|
||||||
self.setToolTip(self.make_tooltip())
|
self.setToolTip(self.make_tooltip())
|
||||||
self.setAcceptHoverEvents(True)
|
self.setAcceptHoverEvents(True)
|
||||||
|
|||||||
@ -2,12 +2,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Callable, Dict, Type
|
from typing import Callable, Dict, Type
|
||||||
|
|
||||||
from PySide2.QtCore import Qt
|
from PySide2.QtCore import Qt
|
||||||
from PySide2.QtWidgets import (
|
from PySide2.QtWidgets import (
|
||||||
QCheckBox,
|
|
||||||
QComboBox,
|
QComboBox,
|
||||||
QDialog,
|
QDialog,
|
||||||
QFrame,
|
QFrame,
|
||||||
@ -15,7 +13,6 @@ from PySide2.QtWidgets import (
|
|||||||
QGroupBox,
|
QGroupBox,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
QMessageBox,
|
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QScrollArea,
|
QScrollArea,
|
||||||
QSizePolicy,
|
QSizePolicy,
|
||||||
@ -27,7 +24,7 @@ from dcs.task import PinpointStrike
|
|||||||
from dcs.unittype import UnitType
|
from dcs.unittype import UnitType
|
||||||
|
|
||||||
from game import Game, db
|
from game import Game, db
|
||||||
from game.theater import ControlPoint, SupplyRoute
|
from game.theater import ControlPoint
|
||||||
from game.transfers import TransferOrder
|
from game.transfers import TransferOrder
|
||||||
from qt_ui.models import GameModel
|
from qt_ui.models import GameModel
|
||||||
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user