mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Airlift support.
UI isn't finished. Bulk transfers where the player doesn't care what aircraft get used work (though they're chosen with no thought at all), but being able to plan your own airlift flight isn't here yet. Cargo planes are not implemented yet. No way to view the cargo of a flight (will come with the cargo flight planning UI). The airlift flight/package creation should probably be moved out of the UI and into the game code. AI doesn't use these yet. https://github.com/Khopa/dcs_liberation/issues/825
This commit is contained in:
parent
8e361a8776
commit
481f195725
@ -1174,6 +1174,7 @@ EXPANDED_TASK_PAYLOAD_OVERRIDE = {
|
|||||||
"SWEEP": ("CAP HEAVY", "CAP"),
|
"SWEEP": ("CAP HEAVY", "CAP"),
|
||||||
"OCA_RUNWAY": ("RUNWAY_ATTACK", "RUNWAY_STRIKE", "STRIKE"),
|
"OCA_RUNWAY": ("RUNWAY_ATTACK", "RUNWAY_STRIKE", "STRIKE"),
|
||||||
"OCA_AIRCRAFT": ("OCA", "CAS MAVERICK F", "CAS"),
|
"OCA_AIRCRAFT": ("OCA", "CAS MAVERICK F", "CAS"),
|
||||||
|
"TRANSPORT": (),
|
||||||
}
|
}
|
||||||
|
|
||||||
PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
||||||
|
|||||||
@ -122,7 +122,7 @@ class Game:
|
|||||||
|
|
||||||
self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints)
|
self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints)
|
||||||
|
|
||||||
self.transfers = PendingTransfers()
|
self.transfers = PendingTransfers(self)
|
||||||
|
|
||||||
self.sanitize_sides()
|
self.sanitize_sides()
|
||||||
|
|
||||||
|
|||||||
@ -3,16 +3,17 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from functools import singledispatchmethod
|
||||||
from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type
|
from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type
|
||||||
|
|
||||||
from dcs.unittype import VehicleType
|
from dcs.unittype import VehicleType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
pass
|
from game import Game
|
||||||
from game.theater import ControlPoint, MissionTarget
|
from game.theater import ControlPoint, MissionTarget
|
||||||
from game.theater.supplyroutes import SupplyRoute
|
from game.theater.supplyroutes import SupplyRoute
|
||||||
from gen.naming import namegen
|
from gen.naming import namegen
|
||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import Flight, FlightType
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -31,14 +32,18 @@ class TransferOrder:
|
|||||||
#: True if the transfer order belongs to the player.
|
#: True if the transfer order belongs to the player.
|
||||||
player: bool
|
player: bool
|
||||||
|
|
||||||
|
#: The units being transferred.
|
||||||
|
units: Dict[Type[VehicleType], int]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RoadTransferOrder(TransferOrder):
|
class RoadTransferOrder(TransferOrder):
|
||||||
"""A transfer order that moves units by road."""
|
"""A transfer order that moves units by road."""
|
||||||
|
|
||||||
#: The units being transferred.
|
|
||||||
units: Dict[Type[VehicleType], int]
|
|
||||||
|
|
||||||
#: The current position of the group being transferred. Groups move one control
|
#: The current position of the group being transferred. Groups move one control
|
||||||
#: point a turn through the supply line.
|
#: point a turn through the supply line.
|
||||||
position: ControlPoint = field(init=False)
|
position: ControlPoint = field(init=False)
|
||||||
@ -53,6 +58,26 @@ class RoadTransferOrder(TransferOrder):
|
|||||||
def next_stop(self) -> ControlPoint:
|
def next_stop(self) -> ControlPoint:
|
||||||
return self.path()[0]
|
return self.path()[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
path = self.path()
|
||||||
|
if len(path) == 1:
|
||||||
|
turns = "1 turn"
|
||||||
|
else:
|
||||||
|
turns = f"{len(path)} turns"
|
||||||
|
return f"Currently at {self.position}. Arrives at destination in {turns}."
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AirliftOrder(TransferOrder):
|
||||||
|
"""A transfer order that moves units by cargo planes and helicopters."""
|
||||||
|
|
||||||
|
flight: Flight
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return "Airlift"
|
||||||
|
|
||||||
|
|
||||||
class Convoy(MissionTarget):
|
class Convoy(MissionTarget):
|
||||||
def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None:
|
def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None:
|
||||||
@ -159,27 +184,54 @@ class ConvoyMap:
|
|||||||
|
|
||||||
|
|
||||||
class PendingTransfers:
|
class PendingTransfers:
|
||||||
def __init__(self) -> None:
|
def __init__(self, game: Game) -> None:
|
||||||
|
self.game = game
|
||||||
self.convoys = ConvoyMap()
|
self.convoys = ConvoyMap()
|
||||||
self.pending_transfers: List[RoadTransferOrder] = []
|
self.pending_transfers: List[TransferOrder] = []
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[RoadTransferOrder]:
|
def __iter__(self) -> Iterator[TransferOrder]:
|
||||||
yield from self.pending_transfers
|
yield from self.pending_transfers
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pending_transfer_count(self) -> int:
|
def pending_transfer_count(self) -> int:
|
||||||
return len(self.pending_transfers)
|
return len(self.pending_transfers)
|
||||||
|
|
||||||
def transfer_at_index(self, index: int) -> RoadTransferOrder:
|
def transfer_at_index(self, index: int) -> TransferOrder:
|
||||||
return self.pending_transfers[index]
|
return self.pending_transfers[index]
|
||||||
|
|
||||||
def new_transfer(self, transfer: RoadTransferOrder) -> None:
|
def index_of_transfer(self, transfer: TransferOrder) -> int:
|
||||||
transfer.origin.base.commit_losses(transfer.units)
|
return self.pending_transfers.index(transfer)
|
||||||
self.pending_transfers.append(transfer)
|
|
||||||
|
# TODO: Move airlift arrangements here?
|
||||||
|
@singledispatchmethod
|
||||||
|
def arrange_transport(self, transfer) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@arrange_transport.register
|
||||||
|
def _arrange_transport_road(self, transfer: RoadTransferOrder) -> None:
|
||||||
self.convoys.add(transfer)
|
self.convoys.add(transfer)
|
||||||
|
|
||||||
def cancel_transfer(self, transfer: RoadTransferOrder) -> None:
|
def new_transfer(self, transfer: TransferOrder) -> None:
|
||||||
|
transfer.origin.base.commit_losses(transfer.units)
|
||||||
|
self.pending_transfers.append(transfer)
|
||||||
|
self.arrange_transport(transfer)
|
||||||
|
|
||||||
|
@singledispatchmethod
|
||||||
|
def cancel_transport(self, transfer) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@cancel_transport.register
|
||||||
|
def _cancel_transport_air(self, transfer: AirliftOrder) -> None:
|
||||||
|
flight = transfer.flight
|
||||||
|
flight.package.remove_flight(flight)
|
||||||
|
self.game.aircraft_inventory.return_from_flight(flight)
|
||||||
|
|
||||||
|
@cancel_transport.register
|
||||||
|
def _cancel_transport_road(self, transfer: RoadTransferOrder) -> None:
|
||||||
self.convoys.remove(transfer)
|
self.convoys.remove(transfer)
|
||||||
|
|
||||||
|
def cancel_transfer(self, transfer: TransferOrder) -> None:
|
||||||
|
self.cancel_transport(transfer)
|
||||||
self.pending_transfers.remove(transfer)
|
self.pending_transfers.remove(transfer)
|
||||||
transfer.origin.base.commision_units(transfer.units)
|
transfer.origin.base.commision_units(transfer.units)
|
||||||
|
|
||||||
@ -194,9 +246,27 @@ class PendingTransfers:
|
|||||||
def rebuild_convoys(self) -> None:
|
def rebuild_convoys(self) -> None:
|
||||||
self.convoys.disband_all()
|
self.convoys.disband_all()
|
||||||
for transfer in self.pending_transfers:
|
for transfer in self.pending_transfers:
|
||||||
self.convoys.add(transfer)
|
self.arrange_transport(transfer)
|
||||||
|
|
||||||
def perform_transfer(self, transfer: RoadTransferOrder) -> bool:
|
@singledispatchmethod
|
||||||
|
def perform_transfer(self, transfer) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@perform_transfer.register
|
||||||
|
def _perform_transfer_air(self, transfer: AirliftOrder) -> bool:
|
||||||
|
if transfer.player != transfer.destination.captured:
|
||||||
|
logging.info(
|
||||||
|
f"Transfer destination {transfer.destination} was captured. Cancelling "
|
||||||
|
"transport."
|
||||||
|
)
|
||||||
|
transfer.origin.base.commision_units(transfer.units)
|
||||||
|
return True
|
||||||
|
|
||||||
|
transfer.destination.base.commision_units(transfer.units)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@perform_transfer.register
|
||||||
|
def _perform_transfer_road(self, transfer: RoadTransferOrder) -> bool:
|
||||||
# TODO: Can be improved to use the convoy map.
|
# TODO: Can be improved to use the convoy map.
|
||||||
# The convoy map already has a lot of the data that we're recomputing here.
|
# The convoy map already has a lot of the data that we're recomputing here.
|
||||||
if transfer.player != transfer.destination.captured:
|
if transfer.player != transfer.destination.captured:
|
||||||
|
|||||||
@ -8,6 +8,8 @@ example, the package to strike an enemy airfield may contain an escort flight,
|
|||||||
a SEAD flight, and the strike aircraft themselves. CAP packages may contain only
|
a SEAD flight, and the strike aircraft themselves. CAP packages may contain only
|
||||||
the single CAP flight.
|
the single CAP flight.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
@ -172,6 +174,7 @@ class Package:
|
|||||||
FlightType.OCA_RUNWAY,
|
FlightType.OCA_RUNWAY,
|
||||||
FlightType.BAI,
|
FlightType.BAI,
|
||||||
FlightType.DEAD,
|
FlightType.DEAD,
|
||||||
|
FlightType.TRANSPORT,
|
||||||
FlightType.SEAD,
|
FlightType.SEAD,
|
||||||
FlightType.TARCAP,
|
FlightType.TARCAP,
|
||||||
FlightType.BARCAP,
|
FlightType.BARCAP,
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections import defaultdict
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, List, Optional, TYPE_CHECKING, Type
|
from typing import Dict, List, Optional, TYPE_CHECKING, Type
|
||||||
@ -15,6 +14,7 @@ from game.theater.controlpoint import ControlPoint, MissionTarget
|
|||||||
from game.utils import Distance, meters
|
from game.utils import Distance, meters
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from game.transfers import AirliftOrder
|
||||||
from gen.ato import Package
|
from gen.ato import Package
|
||||||
from gen.flights.flightplan import FlightPlan
|
from gen.flights.flightplan import FlightPlan
|
||||||
|
|
||||||
@ -43,6 +43,7 @@ class FlightType(Enum):
|
|||||||
OCA_RUNWAY = "OCA/Runway"
|
OCA_RUNWAY = "OCA/Runway"
|
||||||
OCA_AIRCRAFT = "OCA/Aircraft"
|
OCA_AIRCRAFT = "OCA/Aircraft"
|
||||||
AEWC = "AEW&C"
|
AEWC = "AEW&C"
|
||||||
|
TRANSPORT = "Transport"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
@ -75,6 +76,8 @@ class FlightWaypointType(Enum):
|
|||||||
DIVERT = 23
|
DIVERT = 23
|
||||||
INGRESS_OCA_RUNWAY = 24
|
INGRESS_OCA_RUNWAY = 24
|
||||||
INGRESS_OCA_AIRCRAFT = 25
|
INGRESS_OCA_AIRCRAFT = 25
|
||||||
|
PICKUP = 26
|
||||||
|
DROP_OFF = 27
|
||||||
|
|
||||||
|
|
||||||
class FlightWaypoint:
|
class FlightWaypoint:
|
||||||
@ -164,6 +167,7 @@ class Flight:
|
|||||||
arrival: ControlPoint,
|
arrival: ControlPoint,
|
||||||
divert: Optional[ControlPoint],
|
divert: Optional[ControlPoint],
|
||||||
custom_name: Optional[str] = None,
|
custom_name: Optional[str] = None,
|
||||||
|
cargo: Optional[AirliftOrder] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.package = package
|
self.package = package
|
||||||
self.country = country
|
self.country = country
|
||||||
@ -181,6 +185,9 @@ class Flight:
|
|||||||
self.client_count = 0
|
self.client_count = 0
|
||||||
self.custom_name = custom_name
|
self.custom_name = custom_name
|
||||||
|
|
||||||
|
# Only used by transport missions.
|
||||||
|
self.cargo = cargo
|
||||||
|
|
||||||
# Will be replaced with a more appropriate FlightPlan by
|
# Will be replaced with a more appropriate FlightPlan by
|
||||||
# FlightPlanBuilder, but an empty flight plan the flight begins with an
|
# FlightPlanBuilder, but an empty flight plan the flight begins with an
|
||||||
# empty flight plan.
|
# empty flight plan.
|
||||||
|
|||||||
@ -31,7 +31,6 @@ from game.theater import (
|
|||||||
TheaterGroundObject,
|
TheaterGroundObject,
|
||||||
)
|
)
|
||||||
from game.theater.theatergroundobject import EwrGroundObject
|
from game.theater.theatergroundobject import EwrGroundObject
|
||||||
from game.transfers import Convoy
|
|
||||||
from game.utils import Distance, Speed, feet, meters, nautical_miles
|
from game.utils import Distance, Speed, feet, meters, nautical_miles
|
||||||
from .closestairfields import ObjectiveDistanceCache
|
from .closestairfields import ObjectiveDistanceCache
|
||||||
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
||||||
@ -42,6 +41,7 @@ from ..conflictgen import Conflict, FRONTLINE_LENGTH
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
from gen.ato import Package
|
from gen.ato import Package
|
||||||
|
from game.transfers import Convoy
|
||||||
|
|
||||||
INGRESS_TYPES = {
|
INGRESS_TYPES = {
|
||||||
FlightWaypointType.INGRESS_CAS,
|
FlightWaypointType.INGRESS_CAS,
|
||||||
@ -736,6 +736,46 @@ class AwacsFlightPlan(LoiterFlightPlan):
|
|||||||
return self.push_time
|
return self.push_time
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AirliftFlightPlan(FlightPlan):
|
||||||
|
takeoff: FlightWaypoint
|
||||||
|
nav_to_pickup: List[FlightWaypoint]
|
||||||
|
pickup: Optional[FlightWaypoint]
|
||||||
|
nav_to_drop_off: List[FlightWaypoint]
|
||||||
|
drop_off: FlightWaypoint
|
||||||
|
nav_to_home: List[FlightWaypoint]
|
||||||
|
land: FlightWaypoint
|
||||||
|
divert: Optional[FlightWaypoint]
|
||||||
|
|
||||||
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
|
yield self.takeoff
|
||||||
|
yield from self.nav_to_pickup
|
||||||
|
if self.pickup:
|
||||||
|
yield self.pickup
|
||||||
|
yield from self.nav_to_drop_off
|
||||||
|
yield self.drop_off
|
||||||
|
yield from self.nav_to_home
|
||||||
|
yield self.land
|
||||||
|
if self.divert is not None:
|
||||||
|
yield self.divert
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tot_waypoint(self) -> Optional[FlightWaypoint]:
|
||||||
|
return self.drop_off
|
||||||
|
|
||||||
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
|
# TOT planning isn't really useful for transports. They're behind the front
|
||||||
|
# lines so no need to wait for escorts or for other missions to complete.
|
||||||
|
return None
|
||||||
|
|
||||||
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mission_departure_time(self) -> timedelta:
|
||||||
|
return self.package.time_over_target
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class CustomFlightPlan(FlightPlan):
|
class CustomFlightPlan(FlightPlan):
|
||||||
custom_waypoints: List[FlightWaypoint]
|
custom_waypoints: List[FlightWaypoint]
|
||||||
@ -844,6 +884,8 @@ class FlightPlanBuilder:
|
|||||||
return self.generate_tarcap(flight)
|
return self.generate_tarcap(flight)
|
||||||
elif task == FlightType.AEWC:
|
elif task == FlightType.AEWC:
|
||||||
return self.generate_aewc(flight)
|
return self.generate_aewc(flight)
|
||||||
|
elif task == FlightType.TRANSPORT:
|
||||||
|
return self.generate_transport(flight)
|
||||||
raise PlanningError(f"{task} flight plan generation not implemented")
|
raise PlanningError(f"{task} flight plan generation not implemented")
|
||||||
|
|
||||||
def regenerate_package_waypoints(self) -> None:
|
def regenerate_package_waypoints(self) -> None:
|
||||||
@ -1023,6 +1065,8 @@ class FlightPlanBuilder:
|
|||||||
"""
|
"""
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
|
from game.transfers import Convoy
|
||||||
|
|
||||||
targets: List[StrikeTarget] = []
|
targets: List[StrikeTarget] = []
|
||||||
if isinstance(location, TheaterGroundObject):
|
if isinstance(location, TheaterGroundObject):
|
||||||
for group in location.groups:
|
for group in location.groups:
|
||||||
@ -1141,6 +1185,57 @@ class FlightPlanBuilder:
|
|||||||
divert=builder.divert(flight.divert),
|
divert=builder.divert(flight.divert),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def generate_transport(self, flight: Flight) -> AirliftFlightPlan:
|
||||||
|
"""Generate an airlift flight at a given location.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flight: The flight to generate the flight plan for.
|
||||||
|
"""
|
||||||
|
cargo = flight.cargo
|
||||||
|
if cargo is None:
|
||||||
|
raise PlanningError(
|
||||||
|
"Cannot plan transport mission for flight with no cargo."
|
||||||
|
)
|
||||||
|
|
||||||
|
altitude = feet(1500)
|
||||||
|
altitude_is_agl = True
|
||||||
|
|
||||||
|
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||||
|
|
||||||
|
pickup = None
|
||||||
|
nav_to_pickup = []
|
||||||
|
if cargo.origin != flight.departure:
|
||||||
|
pickup = builder.pickup(cargo.origin)
|
||||||
|
nav_to_pickup = builder.nav_path(
|
||||||
|
flight.departure.position,
|
||||||
|
cargo.origin.position,
|
||||||
|
altitude,
|
||||||
|
altitude_is_agl,
|
||||||
|
)
|
||||||
|
|
||||||
|
return AirliftFlightPlan(
|
||||||
|
package=self.package,
|
||||||
|
flight=flight,
|
||||||
|
takeoff=builder.takeoff(flight.departure),
|
||||||
|
nav_to_pickup=nav_to_pickup,
|
||||||
|
pickup=pickup,
|
||||||
|
nav_to_drop_off=builder.nav_path(
|
||||||
|
cargo.origin.position,
|
||||||
|
cargo.destination.position,
|
||||||
|
altitude,
|
||||||
|
altitude_is_agl,
|
||||||
|
),
|
||||||
|
drop_off=builder.drop_off(cargo.destination),
|
||||||
|
nav_to_home=builder.nav_path(
|
||||||
|
cargo.origin.position,
|
||||||
|
flight.arrival.position,
|
||||||
|
altitude,
|
||||||
|
altitude_is_agl,
|
||||||
|
),
|
||||||
|
land=builder.land(flight.arrival),
|
||||||
|
divert=builder.divert(flight.divert),
|
||||||
|
)
|
||||||
|
|
||||||
def racetrack_for_objective(
|
def racetrack_for_objective(
|
||||||
self, location: MissionTarget, barcap: bool
|
self, location: MissionTarget, barcap: bool
|
||||||
) -> Tuple[Point, Point]:
|
) -> Tuple[Point, Point]:
|
||||||
|
|||||||
@ -16,10 +16,9 @@ from dcs.mapping import Point
|
|||||||
from dcs.unit import Unit
|
from dcs.unit import Unit
|
||||||
from dcs.unitgroup import Group, VehicleGroup
|
from dcs.unitgroup import Group, VehicleGroup
|
||||||
|
|
||||||
from game.transfers import Convoy
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
|
from game.transfers import Convoy
|
||||||
|
|
||||||
from game.theater import (
|
from game.theater import (
|
||||||
ControlPoint,
|
ControlPoint,
|
||||||
@ -444,24 +443,69 @@ class WaypointBuilder:
|
|||||||
return ingress, waypoint, egress
|
return ingress, waypoint, egress
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def nav(position: Point, altitude: Distance) -> FlightWaypoint:
|
def pickup(control_point: ControlPoint) -> FlightWaypoint:
|
||||||
|
"""Creates a cargo pickup waypoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
control_point: Pick up location.
|
||||||
|
"""
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.PICKUP,
|
||||||
|
control_point.position.x,
|
||||||
|
control_point.position.y,
|
||||||
|
meters(0),
|
||||||
|
)
|
||||||
|
waypoint.alt_type = "RADIO"
|
||||||
|
waypoint.name = "PICKUP"
|
||||||
|
waypoint.description = f"Pick up cargo from {control_point}"
|
||||||
|
waypoint.pretty_name = "Pick up location"
|
||||||
|
return waypoint
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def drop_off(control_point: ControlPoint) -> FlightWaypoint:
|
||||||
|
"""Creates a cargo drop-off waypoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
control_point: Drop-off location.
|
||||||
|
"""
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.PICKUP,
|
||||||
|
control_point.position.x,
|
||||||
|
control_point.position.y,
|
||||||
|
meters(0),
|
||||||
|
)
|
||||||
|
waypoint.alt_type = "RADIO"
|
||||||
|
waypoint.name = "DROP OFF"
|
||||||
|
waypoint.description = f"Drop off cargo at {control_point}"
|
||||||
|
waypoint.pretty_name = "Drop off location"
|
||||||
|
return waypoint
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def nav(
|
||||||
|
position: Point, altitude: Distance, altitude_is_agl: bool = False
|
||||||
|
) -> FlightWaypoint:
|
||||||
"""Creates a navigation point.
|
"""Creates a navigation point.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
position: Position of the waypoint.
|
position: Position of the waypoint.
|
||||||
altitude: Altitude of the waypoint.
|
altitude: Altitude of the waypoint.
|
||||||
|
altitude_is_agl: True for altitude is AGL. False if altitude is MSL.
|
||||||
"""
|
"""
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.NAV, position.x, position.y, altitude
|
FlightWaypointType.NAV, position.x, position.y, altitude
|
||||||
)
|
)
|
||||||
|
if altitude_is_agl:
|
||||||
|
waypoint.alt_type = "RADIO"
|
||||||
waypoint.name = "NAV"
|
waypoint.name = "NAV"
|
||||||
waypoint.description = "NAV"
|
waypoint.description = "NAV"
|
||||||
waypoint.pretty_name = "Nav"
|
waypoint.pretty_name = "Nav"
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
def nav_path(self, a: Point, b: Point, altitude: Distance) -> List[FlightWaypoint]:
|
def nav_path(
|
||||||
|
self, a: Point, b: Point, altitude: Distance, altitude_is_agl: bool = False
|
||||||
|
) -> List[FlightWaypoint]:
|
||||||
path = self.clean_nav_points(self.navmesh.shortest_path(a, b))
|
path = self.clean_nav_points(self.navmesh.shortest_path(a, b))
|
||||||
return [self.nav(self.perturb(p), altitude) for p in path]
|
return [self.nav(self.perturb(p), altitude, altitude_is_agl) for p in path]
|
||||||
|
|
||||||
def clean_nav_points(self, points: Iterable[Point]) -> Iterator[Point]:
|
def clean_nav_points(self, points: Iterable[Point]) -> Iterator[Point]:
|
||||||
# Examine a sliding window of three waypoints. `current` is the waypoint
|
# Examine a sliding window of three waypoints. `current` is the waypoint
|
||||||
|
|||||||
@ -15,7 +15,7 @@ from PySide2.QtGui import QIcon
|
|||||||
from game import db
|
from game import db
|
||||||
from game.game import Game
|
from game.game import Game
|
||||||
from game.theater.missiontarget import MissionTarget
|
from game.theater.missiontarget import MissionTarget
|
||||||
from game.transfers import RoadTransferOrder
|
from game.transfers import TransferOrder
|
||||||
from gen.ato import AirTaskingOrder, Package
|
from gen.ato import AirTaskingOrder, Package
|
||||||
from gen.flights.flight import Flight
|
from gen.flights.flight import Flight
|
||||||
from gen.flights.traveltime import TotEstimator
|
from gen.flights.traveltime import TotEstimator
|
||||||
@ -52,8 +52,13 @@ class DeletableChildModelManager:
|
|||||||
|
|
||||||
ModelDict = Dict[DataType, ModelType]
|
ModelDict = Dict[DataType, ModelType]
|
||||||
|
|
||||||
def __init__(self, create_model: Callable[[DataType], ModelType]) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
create_model: Callable[[DataType, GameModel], ModelType],
|
||||||
|
game_model: GameModel,
|
||||||
|
) -> None:
|
||||||
self.create_model = create_model
|
self.create_model = create_model
|
||||||
|
self.game_model = game_model
|
||||||
self.models: DeletableChildModelManager.ModelDict = {}
|
self.models: DeletableChildModelManager.ModelDict = {}
|
||||||
|
|
||||||
def acquire(self, data: DataType) -> ModelType:
|
def acquire(self, data: DataType) -> ModelType:
|
||||||
@ -64,7 +69,7 @@ class DeletableChildModelManager:
|
|||||||
"""
|
"""
|
||||||
if data in self.models:
|
if data in self.models:
|
||||||
return self.models[data]
|
return self.models[data]
|
||||||
model = self.create_model(data)
|
model = self.create_model(data, self.game_model)
|
||||||
self.models[data] = model
|
self.models[data] = model
|
||||||
return model
|
return model
|
||||||
|
|
||||||
@ -105,9 +110,10 @@ class PackageModel(QAbstractListModel):
|
|||||||
|
|
||||||
tot_changed = Signal()
|
tot_changed = Signal()
|
||||||
|
|
||||||
def __init__(self, package: Package) -> None:
|
def __init__(self, package: Package, game_model: GameModel) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.package = package
|
self.package = package
|
||||||
|
self.game_model = game_model
|
||||||
|
|
||||||
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||||
return len(self.package.flights)
|
return len(self.package.flights)
|
||||||
@ -154,14 +160,15 @@ class PackageModel(QAbstractListModel):
|
|||||||
self.delete_flight(self.flight_at_index(index))
|
self.delete_flight(self.flight_at_index(index))
|
||||||
|
|
||||||
def delete_flight(self, flight: Flight) -> None:
|
def delete_flight(self, flight: Flight) -> None:
|
||||||
"""Removes the given flight from the package.
|
"""Removes the given flight from the package."""
|
||||||
|
|
||||||
If the flight is using claimed inventory, the caller is responsible for
|
|
||||||
returning that inventory.
|
|
||||||
"""
|
|
||||||
index = self.package.flights.index(flight)
|
index = self.package.flights.index(flight)
|
||||||
self.beginRemoveRows(QModelIndex(), index, index)
|
self.beginRemoveRows(QModelIndex(), index, index)
|
||||||
self.package.remove_flight(flight)
|
if flight.cargo is None:
|
||||||
|
self.game_model.game.aircraft_inventory.return_from_flight(flight)
|
||||||
|
self.package.remove_flight(flight)
|
||||||
|
else:
|
||||||
|
# Deleted transfers will clean up after themselves.
|
||||||
|
self.game_model.transfer_model.cancel_transfer(flight.cargo)
|
||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
self.update_tot()
|
self.update_tot()
|
||||||
|
|
||||||
@ -210,11 +217,15 @@ class AtoModel(QAbstractListModel):
|
|||||||
|
|
||||||
client_slots_changed = Signal()
|
client_slots_changed = Signal()
|
||||||
|
|
||||||
def __init__(self, game: Optional[Game], ato: AirTaskingOrder) -> None:
|
def __init__(self, game_model: GameModel, ato: AirTaskingOrder) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.game = game
|
self.game_model = game_model
|
||||||
self.ato = ato
|
self.ato = ato
|
||||||
self.package_models = DeletableChildModelManager(PackageModel)
|
self.package_models = DeletableChildModelManager(PackageModel, game_model)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def game(self) -> Optional[Game]:
|
||||||
|
return self.game_model.game
|
||||||
|
|
||||||
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||||
return len(self.ato.packages)
|
return len(self.ato.packages)
|
||||||
@ -249,6 +260,8 @@ class AtoModel(QAbstractListModel):
|
|||||||
self.ato.remove_package(package)
|
self.ato.remove_package(package)
|
||||||
for flight in package.flights:
|
for flight in package.flights:
|
||||||
self.game.aircraft_inventory.return_from_flight(flight)
|
self.game.aircraft_inventory.return_from_flight(flight)
|
||||||
|
if flight.cargo is not None:
|
||||||
|
self.game_model.transfer_model.cancel_transfer(flight.cargo)
|
||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
self.client_slots_changed.emit()
|
self.client_slots_changed.emit()
|
||||||
@ -257,20 +270,19 @@ class AtoModel(QAbstractListModel):
|
|||||||
"""Returns the package at the given index."""
|
"""Returns the package at the given index."""
|
||||||
return self.ato.packages[index.row()]
|
return self.ato.packages[index.row()]
|
||||||
|
|
||||||
def replace_from_game(self, game: Optional[Game], player: bool) -> None:
|
def replace_from_game(self, player: bool) -> None:
|
||||||
"""Updates the ATO object to match the updated game object.
|
"""Updates the ATO object to match the updated game object.
|
||||||
|
|
||||||
If the game is None (as is the case when no game has been loaded), an
|
If the game is None (as is the case when no game has been loaded), an
|
||||||
empty ATO will be used.
|
empty ATO will be used.
|
||||||
"""
|
"""
|
||||||
self.beginResetModel()
|
self.beginResetModel()
|
||||||
self.game = game
|
|
||||||
self.package_models.clear()
|
self.package_models.clear()
|
||||||
if self.game is not None:
|
if self.game is not None:
|
||||||
if player:
|
if player:
|
||||||
self.ato = game.blue_ato
|
self.ato = self.game.blue_ato
|
||||||
else:
|
else:
|
||||||
self.ato = game.red_ato
|
self.ato = self.game.red_ato
|
||||||
else:
|
else:
|
||||||
self.ato = AirTaskingOrder()
|
self.ato = AirTaskingOrder()
|
||||||
self.endResetModel()
|
self.endResetModel()
|
||||||
@ -313,7 +325,7 @@ class TransferModel(QAbstractListModel):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def text_for_transfer(transfer: RoadTransferOrder) -> str:
|
def text_for_transfer(transfer: TransferOrder) -> str:
|
||||||
"""Returns the text that should be displayed for the transfer."""
|
"""Returns the text that should be displayed for the transfer."""
|
||||||
count = sum(transfer.units.values())
|
count = sum(transfer.units.values())
|
||||||
origin = transfer.origin.name
|
origin = transfer.origin.name
|
||||||
@ -321,11 +333,11 @@ class TransferModel(QAbstractListModel):
|
|||||||
return f"Transfer of {count} units from {origin} to {destination}"
|
return f"Transfer of {count} units from {origin} to {destination}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def icon_for_transfer(_transfer: RoadTransferOrder) -> Optional[QIcon]:
|
def icon_for_transfer(_transfer: TransferOrder) -> Optional[QIcon]:
|
||||||
"""Returns the icon that should be displayed for the transfer."""
|
"""Returns the icon that should be displayed for the transfer."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def new_transfer(self, transfer: RoadTransferOrder) -> None:
|
def new_transfer(self, transfer: TransferOrder) -> None:
|
||||||
"""Updates the game with the new unit transfer."""
|
"""Updates the game with the new unit transfer."""
|
||||||
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
|
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
|
||||||
# TODO: Needs to regenerate base inventory tab.
|
# TODO: Needs to regenerate base inventory tab.
|
||||||
@ -334,13 +346,17 @@ class TransferModel(QAbstractListModel):
|
|||||||
|
|
||||||
def cancel_transfer_at_index(self, index: QModelIndex) -> None:
|
def cancel_transfer_at_index(self, index: QModelIndex) -> None:
|
||||||
"""Cancels the planned unit transfer at the given index."""
|
"""Cancels the planned unit transfer at the given index."""
|
||||||
transfer = self.transfer_at_index(index)
|
self.cancel_transfer(self.transfer_at_index(index))
|
||||||
self.beginRemoveRows(QModelIndex(), index.row(), index.row())
|
|
||||||
|
def cancel_transfer(self, transfer: TransferOrder) -> None:
|
||||||
|
"""Cancels the planned unit transfer at the given index."""
|
||||||
|
index = self.game_model.game.transfers.index_of_transfer(transfer)
|
||||||
|
self.beginRemoveRows(QModelIndex(), index, index)
|
||||||
# TODO: Needs to regenerate base inventory tab.
|
# TODO: Needs to regenerate base inventory tab.
|
||||||
self.game_model.game.transfers.cancel_transfer(transfer)
|
self.game_model.game.transfers.cancel_transfer(transfer)
|
||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
|
|
||||||
def transfer_at_index(self, index: QModelIndex) -> RoadTransferOrder:
|
def transfer_at_index(self, index: QModelIndex) -> TransferOrder:
|
||||||
"""Returns the transfer located at the given index."""
|
"""Returns the transfer located at the given index."""
|
||||||
return self.game_model.game.transfers.transfer_at_index(index.row())
|
return self.game_model.game.transfers.transfer_at_index(index.row())
|
||||||
|
|
||||||
@ -356,11 +372,11 @@ class GameModel:
|
|||||||
self.game: Optional[Game] = game
|
self.game: Optional[Game] = game
|
||||||
self.transfer_model = TransferModel(self)
|
self.transfer_model = TransferModel(self)
|
||||||
if self.game is None:
|
if self.game is None:
|
||||||
self.ato_model = AtoModel(self.game, AirTaskingOrder())
|
self.ato_model = AtoModel(self, AirTaskingOrder())
|
||||||
self.red_ato_model = AtoModel(self.game, AirTaskingOrder())
|
self.red_ato_model = AtoModel(self, AirTaskingOrder())
|
||||||
else:
|
else:
|
||||||
self.ato_model = AtoModel(self.game, self.game.blue_ato)
|
self.ato_model = AtoModel(self, self.game.blue_ato)
|
||||||
self.red_ato_model = AtoModel(self.game, self.game.red_ato)
|
self.red_ato_model = AtoModel(self, self.game.red_ato)
|
||||||
|
|
||||||
def set(self, game: Optional[Game]) -> None:
|
def set(self, game: Optional[Game]) -> None:
|
||||||
"""Updates the managed Game object.
|
"""Updates the managed Game object.
|
||||||
@ -371,5 +387,5 @@ class GameModel:
|
|||||||
loaded.
|
loaded.
|
||||||
"""
|
"""
|
||||||
self.game = game
|
self.game = game
|
||||||
self.ato_model.replace_from_game(self.game, player=True)
|
self.ato_model.replace_from_game(player=True)
|
||||||
self.red_ato_model.replace_from_game(self.game, player=False)
|
self.red_ato_model.replace_from_game(player=False)
|
||||||
|
|||||||
@ -204,7 +204,6 @@ class QFlightList(QListView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def delete_flight(self, index: QModelIndex) -> None:
|
def delete_flight(self, index: QModelIndex) -> None:
|
||||||
self.game_model.game.aircraft_inventory.return_from_flight(self.selected_item)
|
|
||||||
self.package_model.delete_flight_at_index(index)
|
self.package_model.delete_flight_at_index(index)
|
||||||
GameUpdateSignal.get_instance().redraw_flight_paths()
|
GameUpdateSignal.get_instance().redraw_flight_paths()
|
||||||
|
|
||||||
|
|||||||
@ -22,8 +22,7 @@ from PySide2.QtWidgets import (
|
|||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
)
|
)
|
||||||
|
|
||||||
from game.theater.supplyroutes import SupplyRoute
|
from game.transfers import TransferOrder
|
||||||
from game.transfers import RoadTransferOrder
|
|
||||||
from qt_ui.delegate_helpers import painter_context
|
from qt_ui.delegate_helpers import painter_context
|
||||||
from qt_ui.models import GameModel, TransferModel
|
from qt_ui.models import GameModel, TransferModel
|
||||||
|
|
||||||
@ -43,20 +42,14 @@ class TransferDelegate(QStyledItemDelegate):
|
|||||||
return font
|
return font
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def transfer(index: QModelIndex) -> RoadTransferOrder:
|
def transfer(index: QModelIndex) -> TransferOrder:
|
||||||
return index.data(TransferModel.TransferRole)
|
return index.data(TransferModel.TransferRole)
|
||||||
|
|
||||||
def first_row_text(self, index: QModelIndex) -> str:
|
def first_row_text(self, index: QModelIndex) -> str:
|
||||||
return self.transfer_model.data(index, Qt.DisplayRole)
|
return self.transfer_model.data(index, Qt.DisplayRole)
|
||||||
|
|
||||||
def second_row_text(self, index: QModelIndex) -> str:
|
def second_row_text(self, index: QModelIndex) -> str:
|
||||||
transfer = self.transfer(index)
|
return self.transfer(index).description
|
||||||
path = transfer.path()
|
|
||||||
if len(path) == 1:
|
|
||||||
turns = "1 turn"
|
|
||||||
else:
|
|
||||||
turns = f"{len(path)} turns"
|
|
||||||
return f"Currently at {transfer.position}. Arrives at destination in {turns}."
|
|
||||||
|
|
||||||
def paint(
|
def paint(
|
||||||
self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex
|
self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from PySide2.QtWidgets import (
|
|||||||
QGroupBox,
|
QGroupBox,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
|
QMessageBox,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QScrollArea,
|
QScrollArea,
|
||||||
QSizePolicy,
|
QSizePolicy,
|
||||||
@ -23,12 +24,16 @@ from PySide2.QtWidgets import (
|
|||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
from dcs.task import PinpointStrike
|
from dcs.task import PinpointStrike
|
||||||
from dcs.unittype import UnitType
|
from dcs.unittype import FlyingType, UnitType, VehicleType
|
||||||
|
|
||||||
from game import Game, db
|
from game import Game, db
|
||||||
|
from game.inventory import ControlPointAircraftInventory
|
||||||
from game.theater import ControlPoint, SupplyRoute
|
from game.theater import ControlPoint, SupplyRoute
|
||||||
from game.transfers import RoadTransferOrder
|
from game.transfers import AirliftOrder, RoadTransferOrder
|
||||||
from qt_ui.models import GameModel
|
from gen.ato import Package
|
||||||
|
from gen.flights.flight import Flight, FlightType
|
||||||
|
from gen.flights.flightplan import FlightPlanBuilder, PlanningError
|
||||||
|
from qt_ui.models import GameModel, PackageModel
|
||||||
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
||||||
|
|
||||||
|
|
||||||
@ -111,7 +116,7 @@ class TransferOptionsPanel(QVBoxLayout):
|
|||||||
self.addLayout(QLabeledWidget("Destination:", self.source_combo_box))
|
self.addLayout(QLabeledWidget("Destination:", self.source_combo_box))
|
||||||
self.airlift = QCheckBox()
|
self.airlift = QCheckBox()
|
||||||
self.airlift.toggled.connect(self.set_airlift)
|
self.airlift.toggled.connect(self.set_airlift)
|
||||||
self.addLayout(QLabeledWidget("Airlift (non-functional):", self.airlift))
|
self.addLayout(QLabeledWidget("Airlift (WIP):", self.airlift))
|
||||||
self.addWidget(
|
self.addWidget(
|
||||||
QLabel(
|
QLabel(
|
||||||
f"{airlift_capacity.total} airlift capacity "
|
f"{airlift_capacity.total} airlift capacity "
|
||||||
@ -363,12 +368,112 @@ class NewUnitTransferDialog(QDialog):
|
|||||||
)
|
)
|
||||||
transfers[unit_type] = count
|
transfers[unit_type] = count
|
||||||
|
|
||||||
self.game_model.transfer_model.new_transfer(
|
if self.dest_panel.airlift.isChecked():
|
||||||
RoadTransferOrder(
|
self.create_package_for_airlift(
|
||||||
|
self.transfer_panel.cp,
|
||||||
|
self.dest_panel.current,
|
||||||
|
transfers,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
transfer = RoadTransferOrder(
|
||||||
player=True,
|
player=True,
|
||||||
origin=self.transfer_panel.cp,
|
origin=self.transfer_panel.cp,
|
||||||
destination=self.dest_panel.current,
|
destination=self.dest_panel.current,
|
||||||
units=transfers,
|
units=transfers,
|
||||||
)
|
)
|
||||||
)
|
self.game_model.transfer_model.new_transfer(transfer)
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def take_units(
|
||||||
|
units: Dict[Type[VehicleType], int], count: int
|
||||||
|
) -> Dict[Type[VehicleType], int]:
|
||||||
|
taken = {}
|
||||||
|
for unit_type, remaining in units.items():
|
||||||
|
take = min(remaining, count)
|
||||||
|
count -= take
|
||||||
|
units[unit_type] -= take
|
||||||
|
taken[unit_type] = take
|
||||||
|
if not count:
|
||||||
|
break
|
||||||
|
return taken
|
||||||
|
|
||||||
|
def create_airlift_flight(
|
||||||
|
self,
|
||||||
|
game: Game,
|
||||||
|
package_model: PackageModel,
|
||||||
|
unit_type: Type[FlyingType],
|
||||||
|
inventory: ControlPointAircraftInventory,
|
||||||
|
needed_capacity: int,
|
||||||
|
pickup: ControlPoint,
|
||||||
|
drop_off: ControlPoint,
|
||||||
|
units: Dict[Type[VehicleType], int],
|
||||||
|
) -> int:
|
||||||
|
available = inventory.available(unit_type)
|
||||||
|
# 4 is the max flight size in DCS.
|
||||||
|
flight_size = min(needed_capacity, available, 4)
|
||||||
|
flight = Flight(
|
||||||
|
package_model.package,
|
||||||
|
game.player_country,
|
||||||
|
unit_type,
|
||||||
|
flight_size,
|
||||||
|
FlightType.TRANSPORT,
|
||||||
|
game.settings.default_start_type,
|
||||||
|
departure=inventory.control_point,
|
||||||
|
arrival=inventory.control_point,
|
||||||
|
divert=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
transfer = AirliftOrder(
|
||||||
|
player=True,
|
||||||
|
origin=pickup,
|
||||||
|
destination=drop_off,
|
||||||
|
units=self.take_units(units, flight_size),
|
||||||
|
flight=flight,
|
||||||
|
)
|
||||||
|
flight.cargo = transfer
|
||||||
|
|
||||||
|
package_model.add_flight(flight)
|
||||||
|
planner = FlightPlanBuilder(game, package_model.package, is_player=True)
|
||||||
|
try:
|
||||||
|
planner.populate_flight_plan(flight)
|
||||||
|
except PlanningError as ex:
|
||||||
|
package_model.delete_flight(flight)
|
||||||
|
logging.exception("Could not create flight")
|
||||||
|
QMessageBox.critical(
|
||||||
|
self, "Could not create flight", str(ex), QMessageBox.Ok
|
||||||
|
)
|
||||||
|
game.aircraft_inventory.claim_for_flight(flight)
|
||||||
|
self.game_model.transfer_model.new_transfer(transfer)
|
||||||
|
return flight_size
|
||||||
|
|
||||||
|
def create_package_for_airlift(
|
||||||
|
self,
|
||||||
|
pickup: ControlPoint,
|
||||||
|
drop_off: ControlPoint,
|
||||||
|
units: Dict[Type[VehicleType], int],
|
||||||
|
) -> None:
|
||||||
|
package = Package(target=drop_off, auto_asap=True)
|
||||||
|
package_model = PackageModel(package, self.game_model)
|
||||||
|
|
||||||
|
needed_capacity = sum(c for c in units.values())
|
||||||
|
game = self.game_model.game
|
||||||
|
for cp in game.theater.player_points():
|
||||||
|
inventory = game.aircraft_inventory.for_control_point(cp)
|
||||||
|
for unit_type, available in inventory.all_aircraft:
|
||||||
|
if unit_type.helicopter:
|
||||||
|
while available and needed_capacity:
|
||||||
|
flight_size = self.create_airlift_flight(
|
||||||
|
self.game_model.game,
|
||||||
|
package_model,
|
||||||
|
unit_type,
|
||||||
|
inventory,
|
||||||
|
needed_capacity,
|
||||||
|
pickup,
|
||||||
|
drop_off,
|
||||||
|
units,
|
||||||
|
)
|
||||||
|
available -= flight_size
|
||||||
|
needed_capacity -= flight_size
|
||||||
|
package_model.update_tot()
|
||||||
|
self.game_model.ato_model.add_package(package)
|
||||||
|
|||||||
@ -185,7 +185,6 @@ class QPackageDialog(QDialog):
|
|||||||
try:
|
try:
|
||||||
planner.populate_flight_plan(flight)
|
planner.populate_flight_plan(flight)
|
||||||
except PlanningError as ex:
|
except PlanningError as ex:
|
||||||
self.game.aircraft_inventory.return_from_flight(flight)
|
|
||||||
self.package_model.delete_flight(flight)
|
self.package_model.delete_flight(flight)
|
||||||
logging.exception("Could not create flight")
|
logging.exception("Could not create flight")
|
||||||
QMessageBox.critical(
|
QMessageBox.critical(
|
||||||
@ -201,7 +200,6 @@ class QPackageDialog(QDialog):
|
|||||||
if flight is None:
|
if flight is None:
|
||||||
logging.error(f"Cannot delete flight when no flight is selected.")
|
logging.error(f"Cannot delete flight when no flight is selected.")
|
||||||
return
|
return
|
||||||
self.game.aircraft_inventory.return_from_flight(flight)
|
|
||||||
self.package_model.delete_flight(flight)
|
self.package_model.delete_flight(flight)
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
self.package_changed.emit()
|
self.package_changed.emit()
|
||||||
@ -216,7 +214,9 @@ class QNewPackageDialog(QPackageDialog):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, game_model: GameModel, model: AtoModel, target: MissionTarget, parent=None
|
self, game_model: GameModel, model: AtoModel, target: MissionTarget, parent=None
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(game_model, PackageModel(Package(target)), parent=parent)
|
super().__init__(
|
||||||
|
game_model, PackageModel(Package(target), game_model), parent=parent
|
||||||
|
)
|
||||||
self.ato_model = model
|
self.ato_model = model
|
||||||
|
|
||||||
self.save_button = QPushButton("Save")
|
self.save_button = QPushButton("Save")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user