mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Move Flight and its related components out of gen.
This commit is contained in:
@@ -83,14 +83,13 @@ from game.theater.theatergroundobject import TheaterGroundObject
|
||||
from game.transfers import MultiGroupTransport
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import Distance, meters, nautical_miles, pairwise
|
||||
from gen.ato import AirTaskingOrder, Package
|
||||
from game.ato.airtaaskingorder import AirTaskingOrder
|
||||
from game.ato.package import Package
|
||||
from gen.callsigns import create_group_callsign_from_unit
|
||||
from gen.flights.flight import (
|
||||
Flight,
|
||||
FlightType,
|
||||
FlightWaypoint,
|
||||
FlightWaypointType,
|
||||
)
|
||||
from game.ato.flighttype import FlightType
|
||||
from game.ato.flightwaypointtype import FlightWaypointType
|
||||
from game.ato.flightwaypoint import FlightWaypoint
|
||||
from game.ato.flight import Flight
|
||||
from gen.lasercoderegistry import LaserCodeRegistry
|
||||
from gen.radios import RadioFrequency, RadioRegistry
|
||||
from gen.runways import RunwayData
|
||||
|
||||
234
gen/ato.py
234
gen/ato.py
@@ -1,234 +0,0 @@
|
||||
"""Air Tasking Orders.
|
||||
|
||||
The classes of the Air Tasking Order (ATO) define all of the missions that have
|
||||
been planned, and which aircraft have been assigned to them. Each planned
|
||||
mission, or "package" is composed of individual flights. The package may contain
|
||||
dissimilar aircraft performing different roles, but all for the same goal. For
|
||||
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
|
||||
the single CAP flight.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from dcs.mapping import Point
|
||||
|
||||
from game.theater.missiontarget import MissionTarget
|
||||
from game.utils import Speed
|
||||
from .flights.flight import Flight, FlightType
|
||||
from .flights.flightplan import FormationFlightPlan
|
||||
from .flights.traveltime import TotEstimator
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Task:
|
||||
"""The main task of a flight or package."""
|
||||
|
||||
#: The type of task.
|
||||
task_type: FlightType
|
||||
|
||||
#: The location of the objective.
|
||||
location: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PackageWaypoints:
|
||||
join: Point
|
||||
ingress: Point
|
||||
split: Point
|
||||
|
||||
|
||||
@dataclass
|
||||
class Package:
|
||||
"""A mission package."""
|
||||
|
||||
#: The mission target. Currently can be either a ControlPoint or a
|
||||
#: TheaterGroundObject (non-ControlPoint map objectives).
|
||||
target: MissionTarget
|
||||
|
||||
#: The set of flights in the package.
|
||||
flights: List[Flight] = field(default_factory=list)
|
||||
|
||||
delay: int = field(default=0)
|
||||
|
||||
#: True if the package ToT should be reset to ASAP whenever the player makes
|
||||
#: a change. This is really a UI property rather than a game property, but
|
||||
#: we want it to persist in the save.
|
||||
auto_asap: bool = field(default=False)
|
||||
|
||||
#: Desired TOT as an offset from mission start.
|
||||
time_over_target: timedelta = field(default=timedelta())
|
||||
|
||||
waypoints: Optional[PackageWaypoints] = field(default=None)
|
||||
|
||||
@property
|
||||
def has_players(self) -> bool:
|
||||
return any(flight.client_count for flight in self.flights)
|
||||
|
||||
@property
|
||||
def formation_speed(self) -> Optional[Speed]:
|
||||
"""The speed of the package when in formation.
|
||||
|
||||
If none of the flights in the package will join a formation, this
|
||||
returns None. This is nto uncommon, since only strike-like (strike,
|
||||
DEAD, anti-ship, BAI, etc.) flights and their escorts fly in formation.
|
||||
Others (CAP and CAS, currently) will coordinate in target timing but
|
||||
fly their own path to the target.
|
||||
"""
|
||||
speeds = []
|
||||
for flight in self.flights:
|
||||
if isinstance(flight.flight_plan, FormationFlightPlan):
|
||||
speeds.append(flight.flight_plan.best_flight_formation_speed)
|
||||
if not speeds:
|
||||
return None
|
||||
return min(speeds)
|
||||
|
||||
# TODO: Should depend on the type of escort.
|
||||
# SEAD might be able to leave before CAP.
|
||||
@property
|
||||
def escort_start_time(self) -> Optional[timedelta]:
|
||||
times = []
|
||||
for flight in self.flights:
|
||||
waypoint = flight.flight_plan.request_escort_at()
|
||||
if waypoint is None:
|
||||
continue
|
||||
tot = flight.flight_plan.tot_for_waypoint(waypoint)
|
||||
if tot is None:
|
||||
logging.error(
|
||||
f"{flight} requested escort at {waypoint} but that "
|
||||
"waypoint has no TOT. It may not be escorted."
|
||||
)
|
||||
continue
|
||||
times.append(tot)
|
||||
if times:
|
||||
return min(times)
|
||||
return None
|
||||
|
||||
@property
|
||||
def escort_end_time(self) -> Optional[timedelta]:
|
||||
times = []
|
||||
for flight in self.flights:
|
||||
waypoint = flight.flight_plan.dismiss_escort_at()
|
||||
if waypoint is None:
|
||||
continue
|
||||
tot = flight.flight_plan.tot_for_waypoint(waypoint)
|
||||
if tot is None:
|
||||
tot = flight.flight_plan.depart_time_for_waypoint(waypoint)
|
||||
if tot is None:
|
||||
logging.error(
|
||||
f"{flight} dismissed escort at {waypoint} but that "
|
||||
"waypoint has no TOT or departure time. It may not be "
|
||||
"escorted."
|
||||
)
|
||||
continue
|
||||
times.append(tot)
|
||||
if times:
|
||||
return max(times)
|
||||
return None
|
||||
|
||||
@property
|
||||
def mission_departure_time(self) -> Optional[timedelta]:
|
||||
times = []
|
||||
for flight in self.flights:
|
||||
times.append(flight.flight_plan.mission_departure_time)
|
||||
if times:
|
||||
return max(times)
|
||||
return None
|
||||
|
||||
def set_tot_asap(self) -> None:
|
||||
self.time_over_target = TotEstimator(self).earliest_tot()
|
||||
|
||||
def add_flight(self, flight: Flight) -> None:
|
||||
"""Adds a flight to the package."""
|
||||
self.flights.append(flight)
|
||||
|
||||
def remove_flight(self, flight: Flight) -> None:
|
||||
"""Removes a flight from the package."""
|
||||
self.flights.remove(flight)
|
||||
if not self.flights:
|
||||
self.waypoints = None
|
||||
|
||||
@property
|
||||
def primary_task(self) -> Optional[FlightType]:
|
||||
if not self.flights:
|
||||
return None
|
||||
|
||||
flight_counts: Dict[FlightType, int] = defaultdict(lambda: 0)
|
||||
for flight in self.flights:
|
||||
flight_counts[flight.flight_type] += 1
|
||||
|
||||
# The package will contain a mix of mission types, but in general we can
|
||||
# determine the goal of the mission because some mission types are more
|
||||
# likely to be the main task than others. For example, a package with
|
||||
# only CAP flights is a CAP package, a flight with CAP and strike is a
|
||||
# strike package, a flight with CAP and DEAD is a DEAD package, and a
|
||||
# flight with strike and SEAD is an OCA/Strike package. This list defines the
|
||||
# priority order for package task names. The package's primary task will be the
|
||||
# first task in this list that matches a flight in the package.
|
||||
tasks_by_priority = [
|
||||
FlightType.CAS,
|
||||
FlightType.STRIKE,
|
||||
FlightType.ANTISHIP,
|
||||
FlightType.OCA_AIRCRAFT,
|
||||
FlightType.OCA_RUNWAY,
|
||||
FlightType.BAI,
|
||||
FlightType.DEAD,
|
||||
FlightType.TRANSPORT,
|
||||
FlightType.SEAD,
|
||||
FlightType.TARCAP,
|
||||
FlightType.BARCAP,
|
||||
FlightType.AEWC,
|
||||
FlightType.FERRY,
|
||||
FlightType.REFUELING,
|
||||
FlightType.SWEEP,
|
||||
FlightType.ESCORT,
|
||||
]
|
||||
for task in tasks_by_priority:
|
||||
if flight_counts[task]:
|
||||
return task
|
||||
|
||||
# If we get here, our task_priorities list above is incomplete. Log the
|
||||
# issue and return the type of *any* flight in the package.
|
||||
some_mission = next(iter(self.flights)).flight_type
|
||||
logging.warning(f"Unhandled mission type: {some_mission}")
|
||||
return some_mission
|
||||
|
||||
@property
|
||||
def package_description(self) -> str:
|
||||
"""Generates a package description based on flight composition."""
|
||||
task = self.primary_task
|
||||
if task is None:
|
||||
return "No mission"
|
||||
oca_strike_types = {FlightType.OCA_AIRCRAFT, FlightType.OCA_RUNWAY}
|
||||
if task in oca_strike_types:
|
||||
return "OCA Strike"
|
||||
return str(task)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
# TODO: Far from perfect. Number packages?
|
||||
return hash(self.target.name)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AirTaskingOrder:
|
||||
"""The entire ATO for one coalition."""
|
||||
|
||||
#: The set of all planned packages in the ATO.
|
||||
packages: List[Package] = field(default_factory=list)
|
||||
|
||||
def add_package(self, package: Package) -> None:
|
||||
"""Adds a package to the ATO."""
|
||||
self.packages.append(package)
|
||||
|
||||
def remove_package(self, package: Package) -> None:
|
||||
"""Removes a package from the ATO."""
|
||||
self.packages.remove(package)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Removes all packages from the ATO."""
|
||||
self.packages.clear()
|
||||
@@ -15,7 +15,7 @@ from game.theater import ControlPoint, FrontLine
|
||||
from .aircraft import FlightData
|
||||
from .airsupportgen import AwacsInfo, TankerInfo
|
||||
from .armor import JtacInfo
|
||||
from .flights.flight import FlightWaypoint
|
||||
from game.ato.flightwaypoint import FlightWaypoint
|
||||
from .ground_forces.combat_stance import CombatStance
|
||||
from .radios import RadioFrequency
|
||||
from .runways import RunwayData
|
||||
|
||||
@@ -108,7 +108,7 @@ from dcs.planes import (
|
||||
from dcs.unittype import FlyingType
|
||||
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from gen.flights.flight import FlightType
|
||||
from game.ato.flighttype import FlightType
|
||||
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
||||
from pydcs_extensions.f22a.f22a import F_22A
|
||||
from pydcs_extensions.hercules.hercules import Hercules
|
||||
|
||||
@@ -1,369 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from enum import Enum
|
||||
from typing import List, Optional, TYPE_CHECKING, Union, Sequence
|
||||
|
||||
from dcs.mapping import Point
|
||||
from dcs.point import MovingPoint, PointAction
|
||||
from dcs.unit import Unit
|
||||
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.theater.controlpoint import ControlPoint, MissionTarget
|
||||
from game.utils import Distance, meters
|
||||
from gen.flights.loadouts import Loadout
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.squadrons import Pilot, Squadron
|
||||
from game.transfers import TransferOrder
|
||||
from gen.ato import Package
|
||||
from gen.flights.flightplan import FlightPlan
|
||||
|
||||
|
||||
class FlightType(Enum):
|
||||
"""Enumeration of mission types.
|
||||
|
||||
The value of each enumeration is the name that will be shown in the UI.
|
||||
|
||||
These values are persisted to the save game as well since they are a part of
|
||||
each flight and thus a part of the ATO, so changing these values will break
|
||||
save compat.
|
||||
|
||||
When adding new mission types to this list, you will also need to update:
|
||||
|
||||
* flightplan.py: Add waypoint population in generate_flight_plan. Add a new flight
|
||||
plan type if necessary, though most are a subclass of StrikeFlightPlan.
|
||||
* aircraft.py: Add a configuration method and call it in setup_flight_group. This is
|
||||
responsible for configuring waypoint 0 actions like setting ROE, threat reaction,
|
||||
and mission abort parameters (winchester, bingo, etc).
|
||||
* Implementations of MissionTarget.mission_types: A mission type can only be planned
|
||||
against compatible targets. The mission_types method of each target class defines
|
||||
which missions may target it.
|
||||
* ai_flight_planner_db.py: Add the new mission type to aircraft_for_task that
|
||||
returns the list of compatible aircraft in order of preference.
|
||||
|
||||
You may also need to update:
|
||||
|
||||
* flight.py: Add a new waypoint type if necessary. Most mission types will need
|
||||
these, as aircraft.py uses the ingress point type to specialize AI tasks, and non-
|
||||
strike-like missions will need more specialized control.
|
||||
* ai_flight_planner.py: Use the new mission type in propose_missions so the AI will
|
||||
plan the new mission type.
|
||||
* FlightType.is_air_to_air and FlightType.is_air_to_ground: If the new mission type
|
||||
fits either of these categories, update those methods accordingly.
|
||||
"""
|
||||
|
||||
TARCAP = "TARCAP"
|
||||
BARCAP = "BARCAP"
|
||||
CAS = "CAS"
|
||||
INTERCEPTION = "Intercept"
|
||||
STRIKE = "Strike"
|
||||
ANTISHIP = "Anti-ship"
|
||||
SEAD = "SEAD"
|
||||
DEAD = "DEAD"
|
||||
ESCORT = "Escort"
|
||||
BAI = "BAI"
|
||||
SWEEP = "Fighter sweep"
|
||||
OCA_RUNWAY = "OCA/Runway"
|
||||
OCA_AIRCRAFT = "OCA/Aircraft"
|
||||
AEWC = "AEW&C"
|
||||
TRANSPORT = "Transport"
|
||||
SEAD_ESCORT = "SEAD Escort"
|
||||
REFUELING = "Refueling"
|
||||
FERRY = "Ferry"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_name(cls, name: str) -> FlightType:
|
||||
for entry in cls:
|
||||
if name == entry.value:
|
||||
return entry
|
||||
raise KeyError(f"No FlightType with name {name}")
|
||||
|
||||
@property
|
||||
def is_air_to_air(self) -> bool:
|
||||
return self in {
|
||||
FlightType.TARCAP,
|
||||
FlightType.BARCAP,
|
||||
FlightType.INTERCEPTION,
|
||||
FlightType.ESCORT,
|
||||
FlightType.SWEEP,
|
||||
}
|
||||
|
||||
@property
|
||||
def is_air_to_ground(self) -> bool:
|
||||
return self in {
|
||||
FlightType.CAS,
|
||||
FlightType.STRIKE,
|
||||
FlightType.ANTISHIP,
|
||||
FlightType.SEAD,
|
||||
FlightType.DEAD,
|
||||
FlightType.BAI,
|
||||
FlightType.OCA_RUNWAY,
|
||||
FlightType.OCA_AIRCRAFT,
|
||||
FlightType.SEAD_ESCORT,
|
||||
}
|
||||
|
||||
|
||||
class FlightWaypointType(Enum):
|
||||
"""Enumeration of waypoint types.
|
||||
|
||||
The value of the enum has no meaning but should remain stable to prevent breaking
|
||||
save game compatibility.
|
||||
|
||||
When adding a new waypoint type, you will also need to update:
|
||||
|
||||
* waypointbuilder.py: Add a builder to simplify construction of the new waypoint
|
||||
type unless the new waypoint type will be a parameter to an existing builder
|
||||
method (such as how escort ingress waypoints work).
|
||||
* aircraft.py: Associate AI actions with the new waypoint type by subclassing
|
||||
PydcsWaypointBuilder and using it in PydcsWaypointBuilder.for_waypoint.
|
||||
"""
|
||||
|
||||
TAKEOFF = 0 # Take off point
|
||||
ASCEND_POINT = 1 # Ascension point after take off
|
||||
PATROL = 2 # Patrol point
|
||||
PATROL_TRACK = 3 # Patrol race track
|
||||
NAV = 4 # Nav point
|
||||
INGRESS_STRIKE = 5 # Ingress strike (For generator, means that this should have bombing on next TARGET_POINT points)
|
||||
INGRESS_SEAD = 6 # Ingress sead (For generator, means that this should attack groups on TARGET_GROUP_LOC points)
|
||||
INGRESS_CAS = 7 # Ingress cas (should start CAS task)
|
||||
CAS = 8 # Should do CAS there
|
||||
EGRESS = 9 # Should stop attack
|
||||
DESCENT_POINT = 10 # Should start descending to pattern alt
|
||||
LANDING_POINT = 11 # Should land there
|
||||
TARGET_POINT = 12 # A target building or static object, position
|
||||
TARGET_GROUP_LOC = 13 # A target group approximate location
|
||||
TARGET_SHIP = 14 # Unused.
|
||||
CUSTOM = 15 # User waypoint (no specific behaviour)
|
||||
JOIN = 16
|
||||
SPLIT = 17
|
||||
LOITER = 18
|
||||
INGRESS_ESCORT = 19
|
||||
INGRESS_DEAD = 20
|
||||
INGRESS_SWEEP = 21
|
||||
INGRESS_BAI = 22
|
||||
DIVERT = 23
|
||||
INGRESS_OCA_RUNWAY = 24
|
||||
INGRESS_OCA_AIRCRAFT = 25
|
||||
PICKUP = 26
|
||||
DROP_OFF = 27
|
||||
BULLSEYE = 28
|
||||
|
||||
|
||||
class FlightWaypoint:
|
||||
def __init__(
|
||||
self,
|
||||
waypoint_type: FlightWaypointType,
|
||||
x: float,
|
||||
y: float,
|
||||
alt: Distance = meters(0),
|
||||
control_point: Optional[ControlPoint] = None,
|
||||
) -> None:
|
||||
"""Creates a flight waypoint.
|
||||
|
||||
Args:
|
||||
waypoint_type: The waypoint type.
|
||||
x: X coordinate of the waypoint.
|
||||
y: Y coordinate of the waypoint.
|
||||
alt: Altitude of the waypoint. By default this is MSL, but it can be
|
||||
changed to AGL by setting alt_type to "RADIO"
|
||||
control_point: The control point to associate with this waypoint. Needed for
|
||||
landing points.
|
||||
"""
|
||||
self.waypoint_type = waypoint_type
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.alt = alt
|
||||
self.control_point = control_point
|
||||
self.alt_type = "BARO"
|
||||
self.name = ""
|
||||
# TODO: Merge with pretty_name.
|
||||
# Only used in the waypoint list in the flight edit page. No sense
|
||||
# having three names. A short and long form is enough.
|
||||
self.description = ""
|
||||
self.targets: Sequence[Union[MissionTarget, Unit]] = []
|
||||
self.obj_name = ""
|
||||
self.pretty_name = ""
|
||||
self.only_for_player = False
|
||||
self.flyover = False
|
||||
# The minimum amount of fuel remaining at this waypoint in pounds.
|
||||
self.min_fuel: Optional[float] = None
|
||||
|
||||
# These are set very late by the air conflict generator (part of mission
|
||||
# generation). We do it late so that we don't need to propagate changes
|
||||
# to waypoint times whenever the player alters the package TOT or the
|
||||
# flight's offset in the UI.
|
||||
self.tot: Optional[timedelta] = None
|
||||
self.departure_time: Optional[timedelta] = None
|
||||
|
||||
@property
|
||||
def position(self) -> Point:
|
||||
return Point(self.x, self.y)
|
||||
|
||||
@classmethod
|
||||
def from_pydcs(cls, point: MovingPoint, from_cp: ControlPoint) -> "FlightWaypoint":
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.NAV,
|
||||
point.position.x,
|
||||
point.position.y,
|
||||
meters(point.alt),
|
||||
)
|
||||
waypoint.alt_type = point.alt_type
|
||||
# Other actions exist... but none of them *should* be the first
|
||||
# waypoint for a flight.
|
||||
waypoint.waypoint_type = {
|
||||
PointAction.TurningPoint: FlightWaypointType.NAV,
|
||||
PointAction.FlyOverPoint: FlightWaypointType.NAV,
|
||||
PointAction.FromParkingArea: FlightWaypointType.TAKEOFF,
|
||||
PointAction.FromParkingAreaHot: FlightWaypointType.TAKEOFF,
|
||||
PointAction.FromRunway: FlightWaypointType.TAKEOFF,
|
||||
PointAction.FromGroundArea: FlightWaypointType.TAKEOFF,
|
||||
PointAction.FromGroundAreaHot: FlightWaypointType.TAKEOFF,
|
||||
}[point.action]
|
||||
if waypoint.waypoint_type == FlightWaypointType.NAV:
|
||||
waypoint.name = "NAV"
|
||||
waypoint.pretty_name = "Nav"
|
||||
waypoint.description = "Nav"
|
||||
else:
|
||||
waypoint.name = "TAKEOFF"
|
||||
waypoint.pretty_name = "Takeoff"
|
||||
waypoint.description = "Takeoff"
|
||||
waypoint.description = f"Takeoff from {from_cp.name}"
|
||||
return waypoint
|
||||
|
||||
|
||||
class FlightRoster:
|
||||
def __init__(self, squadron: Squadron, initial_size: int = 0) -> None:
|
||||
self.squadron = squadron
|
||||
self.pilots: list[Optional[Pilot]] = []
|
||||
self.resize(initial_size)
|
||||
|
||||
@property
|
||||
def max_size(self) -> int:
|
||||
return len(self.pilots)
|
||||
|
||||
@property
|
||||
def player_count(self) -> int:
|
||||
return len([p for p in self.pilots if p is not None and p.player])
|
||||
|
||||
@property
|
||||
def missing_pilots(self) -> int:
|
||||
return len([p for p in self.pilots if p is None])
|
||||
|
||||
def resize(self, new_size: int) -> None:
|
||||
if self.max_size > new_size:
|
||||
self.squadron.return_pilots(
|
||||
[p for p in self.pilots[new_size:] if p is not None]
|
||||
)
|
||||
self.pilots = self.pilots[:new_size]
|
||||
return
|
||||
self.pilots.extend(
|
||||
[
|
||||
self.squadron.claim_available_pilot()
|
||||
for _ in range(new_size - self.max_size)
|
||||
]
|
||||
)
|
||||
|
||||
def set_pilot(self, index: int, pilot: Optional[Pilot]) -> None:
|
||||
if pilot is not None:
|
||||
self.squadron.claim_pilot(pilot)
|
||||
if (current_pilot := self.pilots[index]) is not None:
|
||||
self.squadron.return_pilot(current_pilot)
|
||||
self.pilots[index] = pilot
|
||||
|
||||
def clear(self) -> None:
|
||||
self.squadron.return_pilots([p for p in self.pilots if p is not None])
|
||||
|
||||
|
||||
class Flight:
|
||||
def __init__(
|
||||
self,
|
||||
package: Package,
|
||||
country: str,
|
||||
squadron: Squadron,
|
||||
count: int,
|
||||
flight_type: FlightType,
|
||||
start_type: str,
|
||||
divert: Optional[ControlPoint],
|
||||
custom_name: Optional[str] = None,
|
||||
cargo: Optional[TransferOrder] = None,
|
||||
roster: Optional[FlightRoster] = None,
|
||||
) -> None:
|
||||
self.package = package
|
||||
self.country = country
|
||||
self.squadron = squadron
|
||||
self.squadron.claim_inventory(count)
|
||||
if roster is None:
|
||||
self.roster = FlightRoster(self.squadron, initial_size=count)
|
||||
else:
|
||||
self.roster = roster
|
||||
self.departure = self.squadron.location
|
||||
self.arrival = self.squadron.arrival
|
||||
self.divert = divert
|
||||
self.flight_type = flight_type
|
||||
# TODO: Replace with FlightPlan.
|
||||
self.targets: List[MissionTarget] = []
|
||||
self.loadout = Loadout.default_for(self)
|
||||
self.start_type = start_type
|
||||
self.use_custom_loadout = False
|
||||
self.custom_name = custom_name
|
||||
|
||||
# Only used by transport missions.
|
||||
self.cargo = cargo
|
||||
|
||||
# Will be replaced with a more appropriate FlightPlan by
|
||||
# FlightPlanBuilder, but an empty flight plan the flight begins with an
|
||||
# empty flight plan.
|
||||
from gen.flights.flightplan import CustomFlightPlan
|
||||
|
||||
self.flight_plan: FlightPlan = CustomFlightPlan(
|
||||
package=package, flight=self, custom_waypoints=[]
|
||||
)
|
||||
|
||||
@property
|
||||
def count(self) -> int:
|
||||
return self.roster.max_size
|
||||
|
||||
@property
|
||||
def client_count(self) -> int:
|
||||
return self.roster.player_count
|
||||
|
||||
@property
|
||||
def unit_type(self) -> AircraftType:
|
||||
return self.squadron.aircraft
|
||||
|
||||
@property
|
||||
def from_cp(self) -> ControlPoint:
|
||||
return self.departure
|
||||
|
||||
@property
|
||||
def points(self) -> List[FlightWaypoint]:
|
||||
return self.flight_plan.waypoints[1:]
|
||||
|
||||
def resize(self, new_size: int) -> None:
|
||||
self.squadron.claim_inventory(new_size - self.count)
|
||||
self.roster.resize(new_size)
|
||||
|
||||
def set_pilot(self, index: int, pilot: Optional[Pilot]) -> None:
|
||||
self.roster.set_pilot(index, pilot)
|
||||
|
||||
@property
|
||||
def missing_pilots(self) -> int:
|
||||
return self.roster.missing_pilots
|
||||
|
||||
def return_pilots_and_aircraft(self) -> None:
|
||||
self.roster.clear()
|
||||
self.squadron.claim_inventory(-self.count)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if self.custom_name:
|
||||
return f"{self.custom_name} {self.count} x {self.unit_type}"
|
||||
return f"[{self.flight_type}] {self.count} x {self.unit_type}"
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.custom_name:
|
||||
return f"{self.custom_name} {self.count} x {self.unit_type}"
|
||||
return f"[{self.flight_type}] {self.count} x {self.unit_type}"
|
||||
@@ -37,19 +37,23 @@ from game.theater.theatergroundobject import (
|
||||
NavalGroundObject,
|
||||
BuildingGroundObject,
|
||||
)
|
||||
from game.threatzones import ThreatZones
|
||||
from game.utils import Distance, Heading, Speed, feet, meters, nautical_miles, knots
|
||||
from .closestairfields import ObjectiveDistanceCache
|
||||
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
||||
from game.ato.flighttype import FlightType
|
||||
from game.ato.flightwaypointtype import FlightWaypointType
|
||||
from game.ato.flightwaypoint import FlightWaypoint
|
||||
from game.ato.flight import Flight
|
||||
from .traveltime import GroundSpeed, TravelTime
|
||||
from .waypointbuilder import StrikeTarget, WaypointBuilder
|
||||
from ..conflictgen import Conflict, FRONTLINE_LENGTH
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from gen.ato import Package
|
||||
from game.ato.package import Package
|
||||
from game.coalition import Coalition
|
||||
from game.threatzones import ThreatZones
|
||||
from game.transfers import Convoy
|
||||
|
||||
|
||||
INGRESS_TYPES = {
|
||||
FlightWaypointType.INGRESS_CAS,
|
||||
FlightWaypointType.INGRESS_ESCORT,
|
||||
@@ -1005,7 +1009,7 @@ class FlightPlanBuilder:
|
||||
raise PlanningError(f"{task} flight plan generation not implemented")
|
||||
|
||||
def regenerate_package_waypoints(self) -> None:
|
||||
from gen.ato import PackageWaypoints
|
||||
from game.ato.packagewaypoints import PackageWaypoints
|
||||
|
||||
package_airfield = self.package_airfield()
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from game.data.weapons import Weapon, Pylon, WeaponType
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from gen.flights.flight import Flight
|
||||
from game.ato.flight import Flight
|
||||
|
||||
|
||||
class Loadout:
|
||||
@@ -123,7 +123,7 @@ class Loadout:
|
||||
|
||||
@classmethod
|
||||
def default_loadout_names_for(cls, flight: Flight) -> Iterator[str]:
|
||||
from gen.flights.flight import FlightType
|
||||
from game.ato.flighttype import FlightType
|
||||
|
||||
# This is a list of mappings from the FlightType of a Flight to the type of
|
||||
# payload defined in the resources/payloads/UNIT_TYPE.lua file. A Flight has no
|
||||
|
||||
@@ -16,10 +16,10 @@ from game.utils import (
|
||||
mach,
|
||||
meters,
|
||||
)
|
||||
from gen.flights.flight import Flight
|
||||
from game.ato.flight import Flight
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from gen.ato import Package
|
||||
from game.ato.package import Package
|
||||
|
||||
|
||||
class GroundSpeed:
|
||||
|
||||
@@ -29,7 +29,9 @@ from game.theater import (
|
||||
TheaterGroundObject,
|
||||
)
|
||||
from game.utils import Distance, meters, nautical_miles
|
||||
from .flight import Flight, FlightWaypoint, FlightWaypointType
|
||||
from game.ato.flightwaypointtype import FlightWaypointType
|
||||
from game.ato.flightwaypoint import FlightWaypoint
|
||||
from game.ato.flight import Flight
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -45,7 +45,9 @@ from game.weather import Weather
|
||||
from .aircraft import FlightData
|
||||
from .airsupportgen import AwacsInfo, TankerInfo
|
||||
from .briefinggen import CommInfo, JtacInfo, MissionInfoGenerator
|
||||
from .flights.flight import FlightWaypoint, FlightWaypointType, FlightType
|
||||
from game.ato.flighttype import FlightType
|
||||
from game.ato.flightwaypointtype import FlightWaypointType
|
||||
from game.ato.flightwaypoint import FlightWaypoint
|
||||
from .radios import RadioFrequency
|
||||
from .runways import RunwayData
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.unittype import UnitType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from gen.flights.flight import Flight
|
||||
from game.ato.flight import Flight
|
||||
|
||||
ALPHA_MILITARY = [
|
||||
"Alpha",
|
||||
|
||||
Reference in New Issue
Block a user