Merge branch 'develop' into frontline

This commit is contained in:
walterroach
2020-11-20 20:32:50 -06:00
51 changed files with 411 additions and 303 deletions

View File

@@ -70,6 +70,12 @@ from dcs.unittype import FlyingType, UnitType
from game import db
from game.data.cap_capabilities_db import GUNFIGHTERS
from game.settings import Settings
from game.theater.controlpoint import (
ControlPoint,
ControlPointType,
OffMapSpawn,
)
from game.theater.theatergroundobject import TheaterGroundObject
from game.utils import knots_to_kph, nm_to_meter
from gen.airsupportgen import AirSupport
from gen.ato import AirTaskingOrder, Package
@@ -83,12 +89,6 @@ from gen.flights.flight import (
)
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
from gen.runways import RunwayData
from theater import TheaterGroundObject
from game.theater.controlpoint import (
ControlPoint,
ControlPointType,
OffMapSpawn,
)
from .conflictgen import Conflict
from .flights.flightplan import (
CasFlightPlan,
@@ -695,6 +695,18 @@ class AircraftConflictGenerator:
return StartType.Cold
return StartType.Warm
def determine_runway(self, cp: ControlPoint, dynamic_runways) -> RunwayData:
fallback = RunwayData(cp.full_name, runway_heading=0, runway_name="")
if cp.cptype == ControlPointType.AIRBASE:
assigner = RunwayAssigner(self.game.conditions)
return assigner.get_preferred_runway(cp.airport)
elif cp.is_fleet:
return dynamic_runways.get(cp.name, fallback)
else:
logging.warning(
f"Unhandled departure/arrival control point: {cp.cptype}")
return fallback
def _setup_group(self, group: FlyingGroup, for_task: Type[Task],
package: Package, flight: Flight,
dynamic_runways: Dict[str, RunwayData]) -> None:
@@ -752,19 +764,9 @@ class AircraftConflictGenerator:
channel = self.get_intra_flight_channel(unit_type)
group.set_frequency(channel.mhz)
# TODO: Support for different departure/arrival airfields.
cp = flight.from_cp
fallback_runway = RunwayData(cp.full_name, runway_heading=0,
runway_name="")
if cp.cptype == ControlPointType.AIRBASE:
assigner = RunwayAssigner(self.game.conditions)
departure_runway = assigner.get_preferred_runway(
flight.from_cp.airport)
elif cp.is_fleet:
departure_runway = dynamic_runways.get(cp.name, fallback_runway)
else:
logging.warning(f"Unhandled departure control point: {cp.cptype}")
departure_runway = fallback_runway
divert = None
if flight.divert is not None:
divert = self.determine_runway(flight.divert, dynamic_runways)
self.flights.append(FlightData(
package=package,
@@ -774,10 +776,9 @@ class AircraftConflictGenerator:
friendly=flight.from_cp.captured,
# Set later.
departure_delay=timedelta(),
departure=departure_runway,
arrival=departure_runway,
# TODO: Support for divert airfields.
divert=None,
departure=self.determine_runway(flight.departure, dynamic_runways),
arrival=self.determine_runway(flight.arrival, dynamic_runways),
divert=divert,
# Waypoints are added later, after they've had their TOTs set.
waypoints=[],
intra_flight_channel=channel

View File

@@ -5,7 +5,8 @@ from typing import Tuple
from dcs.country import Country
from dcs.mapping import Point
from theater import ConflictTheater, ControlPoint, FrontLine
from game.theater.conflicttheater import ConflictTheater, FrontLine
from game.theater.controlpoint import ControlPoint
AIR_DISTANCE = 40000

View File

@@ -16,11 +16,24 @@ from typing import (
Type,
)
from dcs.unittype import FlyingType, UnitType
from dcs.unittype import FlyingType
from game import db
from game.data.radar_db import UNITS_WITH_RADAR
from game.infos.information import Information
from game.theater import (
ControlPoint,
FrontLine,
MissionTarget,
OffMapSpawn,
SamGroundObject,
TheaterGroundObject,
)
# Avoid importing some types that cause circular imports unless type checking.
from game.theater.theatergroundobject import (
EwrGroundObject,
NavalGroundObject, VehicleGroupGroundObject,
)
from game.utils import nm_to_meter
from gen import Conflict
from gen.ato import Package
@@ -46,19 +59,6 @@ from gen.flights.flight import (
)
from gen.flights.flightplan import FlightPlanBuilder
from gen.flights.traveltime import TotEstimator
from theater import (
ControlPoint,
FrontLine,
MissionTarget,
OffMapSpawn, TheaterGroundObject,
SamGroundObject,
)
# Avoid importing some types that cause circular imports unless type checking.
from game.theater.theatergroundobject import (
EwrGroundObject,
NavalGroundObject, VehicleGroupGroundObject,
)
if TYPE_CHECKING:
from game import Game
@@ -119,7 +119,7 @@ class AircraftAllocator:
def find_aircraft_for_flight(
self, flight: ProposedFlight
) -> Optional[Tuple[ControlPoint, UnitType]]:
) -> Optional[Tuple[ControlPoint, FlyingType]]:
"""Finds aircraft suitable for the given mission.
Searches for aircraft capable of performing the given mission within the
@@ -190,7 +190,7 @@ class AircraftAllocator:
def find_aircraft_of_type(
self, flight: ProposedFlight, types: List[Type[FlyingType]],
) -> Optional[Tuple[ControlPoint, UnitType]]:
) -> Optional[Tuple[ControlPoint, FlyingType]]:
airfields_in_range = self.closest_airfields.airfields_within(
flight.max_distance
)
@@ -214,6 +214,8 @@ class PackageBuilder:
global_inventory: GlobalAircraftInventory,
is_player: bool,
start_type: str) -> None:
self.closest_airfields = closest_airfields
self.is_player = is_player
self.package = Package(location)
self.allocator = AircraftAllocator(closest_airfields, global_inventory,
is_player)
@@ -236,11 +238,28 @@ class PackageBuilder:
start_type = "In Flight"
else:
start_type = self.start_type
flight = Flight(self.package, aircraft, plan.num_aircraft, airfield,
plan.task, start_type)
flight = Flight(self.package, aircraft, plan.num_aircraft, plan.task,
start_type, departure=airfield, arrival=airfield,
divert=self.find_divert_field(aircraft, airfield))
self.package.add_flight(flight)
return True
def find_divert_field(self, aircraft: FlyingType,
arrival: ControlPoint) -> Optional[ControlPoint]:
divert_limit = nm_to_meter(150)
for airfield in self.closest_airfields.airfields_within(divert_limit):
if airfield.captured != self.is_player:
continue
if airfield == arrival:
continue
if not airfield.can_land(aircraft):
continue
if isinstance(airfield, OffMapSpawn):
continue
return airfield
return None
def build(self) -> Package:
"""Returns the built package."""
return self.package

View File

@@ -1,7 +1,7 @@
"""Objective adjacency lists."""
from typing import Dict, Iterator, List, Optional
from theater import ConflictTheater, ControlPoint, MissionTarget
from game.theater import ConflictTheater, ControlPoint, MissionTarget
class ClosestAirfields:

View File

@@ -65,6 +65,7 @@ class FlightWaypointType(Enum):
INGRESS_DEAD = 20
INGRESS_SWEEP = 21
INGRESS_BAI = 22
DIVERT = 23
class FlightWaypoint:
@@ -133,12 +134,15 @@ class FlightWaypoint:
class Flight:
def __init__(self, package: Package, unit_type: FlyingType, count: int,
from_cp: ControlPoint, flight_type: FlightType,
start_type: str) -> None:
flight_type: FlightType, start_type: str,
departure: ControlPoint, arrival: ControlPoint,
divert: Optional[ControlPoint]) -> None:
self.package = package
self.unit_type = unit_type
self.count = count
self.from_cp = from_cp
self.departure = departure
self.arrival = arrival
self.divert = divert
self.flight_type = flight_type
# TODO: Replace with FlightPlan.
self.targets: List[MissionTarget] = []
@@ -157,6 +161,10 @@ class Flight:
custom_waypoints=[]
)
@property
def from_cp(self) -> ControlPoint:
return self.departure
@property
def points(self) -> List[FlightWaypoint]:
return self.flight_plan.waypoints[1:]

View File

@@ -7,20 +7,19 @@ generating the waypoints for the mission.
"""
from __future__ import annotations
import math
from datetime import timedelta
from functools import cached_property
import logging
import math
import random
from dataclasses import dataclass
from datetime import timedelta
from functools import cached_property
from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple
from dcs.mapping import Point
from dcs.unit import Unit
from game.data.doctrine import Doctrine
from game.utils import nm_to_meter
from theater import (
from game.theater import (
ControlPoint,
FrontLine,
MissionTarget,
@@ -28,6 +27,7 @@ from theater import (
TheaterGroundObject,
)
from game.theater.theatergroundobject import EwrGroundObject
from game.utils import nm_to_meter
from .closestairfields import ObjectiveDistanceCache
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
from .traveltime import GroundSpeed, TravelTime
@@ -68,6 +68,10 @@ class FlightPlan:
@property
def waypoints(self) -> List[FlightWaypoint]:
"""A list of all waypoints in the flight plan, in order."""
return list(self.iter_waypoints())
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
"""Iterates over all waypoints in the flight plan, in order."""
raise NotImplementedError
@property
@@ -166,8 +170,7 @@ class FlightPlan:
class LoiterFlightPlan(FlightPlan):
hold: FlightWaypoint
@property
def waypoints(self) -> List[FlightWaypoint]:
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
raise NotImplementedError
@property
@@ -193,8 +196,7 @@ class FormationFlightPlan(LoiterFlightPlan):
join: FlightWaypoint
split: FlightWaypoint
@property
def waypoints(self) -> List[FlightWaypoint]:
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
raise NotImplementedError
@property
@@ -295,8 +297,7 @@ class PatrollingFlightPlan(FlightPlan):
return self.patrol_end_time
return None
@property
def waypoints(self) -> List[FlightWaypoint]:
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
raise NotImplementedError
@property
@@ -312,15 +313,17 @@ class PatrollingFlightPlan(FlightPlan):
class BarCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint
land: FlightWaypoint
divert: Optional[FlightWaypoint]
@property
def waypoints(self) -> List[FlightWaypoint]:
return [
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield from [
self.takeoff,
self.patrol_start,
self.patrol_end,
self.land,
]
if self.divert is not None:
yield self.divert
@dataclass(frozen=True)
@@ -328,16 +331,18 @@ class CasFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint
target: FlightWaypoint
land: FlightWaypoint
divert: Optional[FlightWaypoint]
@property
def waypoints(self) -> List[FlightWaypoint]:
return [
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield from [
self.takeoff,
self.patrol_start,
self.target,
self.patrol_end,
self.land,
]
if self.divert is not None:
yield self.divert
def request_escort_at(self) -> Optional[FlightWaypoint]:
return self.patrol_start
@@ -350,16 +355,18 @@ class CasFlightPlan(PatrollingFlightPlan):
class TarCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint
land: FlightWaypoint
divert: Optional[FlightWaypoint]
lead_time: timedelta
@property
def waypoints(self) -> List[FlightWaypoint]:
return [
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield from [
self.takeoff,
self.patrol_start,
self.patrol_end,
self.land,
]
if self.divert is not None:
yield self.divert
@property
def tot_offset(self) -> timedelta:
@@ -386,10 +393,6 @@ class TarCapFlightPlan(PatrollingFlightPlan):
return super().patrol_end_time
# TODO: Remove when breaking save compat.
FrontLineCapFlightPlan = TarCapFlightPlan
@dataclass(frozen=True)
class StrikeFlightPlan(FormationFlightPlan):
takeoff: FlightWaypoint
@@ -400,19 +403,23 @@ class StrikeFlightPlan(FormationFlightPlan):
egress: FlightWaypoint
split: FlightWaypoint
land: FlightWaypoint
divert: Optional[FlightWaypoint]
@property
def waypoints(self) -> List[FlightWaypoint]:
return [
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield from [
self.takeoff,
self.hold,
self.join,
self.ingress
] + self.targets + [
]
yield from self.targets
yield from[
self.egress,
self.split,
self.land,
]
if self.divert is not None:
yield self.divert
@property
def package_speed_waypoints(self) -> Set[FlightWaypoint]:
@@ -511,17 +518,19 @@ class SweepFlightPlan(LoiterFlightPlan):
sweep_start: FlightWaypoint
sweep_end: FlightWaypoint
land: FlightWaypoint
divert: Optional[FlightWaypoint]
lead_time: timedelta
@property
def waypoints(self) -> List[FlightWaypoint]:
return [
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield from [
self.takeoff,
self.hold,
self.sweep_start,
self.sweep_end,
self.land,
]
if self.divert is not None:
yield self.divert
@property
def tot_waypoint(self) -> Optional[FlightWaypoint]:
@@ -567,9 +576,8 @@ class SweepFlightPlan(LoiterFlightPlan):
class CustomFlightPlan(FlightPlan):
custom_waypoints: List[FlightWaypoint]
@property
def waypoints(self) -> List[FlightWaypoint]:
return self.custom_waypoints
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield from self.custom_waypoints
@property
def tot_waypoint(self) -> Optional[FlightWaypoint]:
@@ -774,10 +782,11 @@ class FlightPlanBuilder:
package=self.package,
flight=flight,
patrol_duration=self.doctrine.cap_duration,
takeoff=builder.takeoff(flight.from_cp),
takeoff=builder.takeoff(flight.departure),
patrol_start=start,
patrol_end=end,
land=builder.land(flight.from_cp)
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
)
def generate_sweep(self, flight: Flight) -> SweepFlightPlan:
@@ -800,11 +809,12 @@ class FlightPlanBuilder:
package=self.package,
flight=flight,
lead_time=timedelta(minutes=5),
takeoff=builder.takeoff(flight.from_cp),
takeoff=builder.takeoff(flight.departure),
hold=builder.hold(self._hold_point(flight)),
sweep_start=start,
sweep_end=end,
land=builder.land(flight.from_cp)
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
)
def racetrack_for_objective(self,
@@ -900,10 +910,11 @@ class FlightPlanBuilder:
# requests an escort the CAP flight will remain on station for the
# duration of the escorted mission, or until it is winchester/bingo.
patrol_duration=self.doctrine.cap_duration,
takeoff=builder.takeoff(flight.from_cp),
takeoff=builder.takeoff(flight.departure),
patrol_start=start,
patrol_end=end,
land=builder.land(flight.from_cp)
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
)
def generate_dead(self, flight: Flight,
@@ -965,14 +976,15 @@ class FlightPlanBuilder:
return StrikeFlightPlan(
package=self.package,
flight=flight,
takeoff=builder.takeoff(flight.from_cp),
takeoff=builder.takeoff(flight.departure),
hold=builder.hold(self._hold_point(flight)),
join=builder.join(self.package.waypoints.join),
ingress=ingress,
targets=[target],
egress=egress,
split=builder.split(self.package.waypoints.split),
land=builder.land(flight.from_cp)
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
)
def generate_cas(self, flight: Flight) -> CasFlightPlan:
@@ -999,11 +1011,12 @@ class FlightPlanBuilder:
package=self.package,
flight=flight,
patrol_duration=self.doctrine.cas_duration,
takeoff=builder.takeoff(flight.from_cp),
takeoff=builder.takeoff(flight.departure),
patrol_start=builder.ingress_cas(ingress, location),
target=builder.cas(center),
patrol_end=builder.egress(egress, location),
land=builder.land(flight.from_cp)
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
)
@staticmethod
@@ -1030,7 +1043,7 @@ class FlightPlanBuilder:
def _hold_point(self, flight: Flight) -> Point:
assert self.package.waypoints is not None
origin = flight.from_cp.position
origin = flight.departure.position
target = self.package.target.position
join = self.package.waypoints.join
origin_to_target = origin.distance_to_point(target)
@@ -1118,14 +1131,15 @@ class FlightPlanBuilder:
return StrikeFlightPlan(
package=self.package,
flight=flight,
takeoff=builder.takeoff(flight.from_cp),
takeoff=builder.takeoff(flight.departure),
hold=builder.hold(self._hold_point(flight)),
join=builder.join(self.package.waypoints.join),
ingress=ingress,
targets=target_waypoints,
egress=builder.egress(self.package.waypoints.egress, location),
split=builder.split(self.package.waypoints.split),
land=builder.land(flight.from_cp)
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
)
def _retreating_rendezvous_point(self, attack_transition: Point) -> Point:
@@ -1201,7 +1215,7 @@ class FlightPlanBuilder:
)
for airfield in cache.closest_airfields:
for flight in self.package.flights:
if flight.from_cp == airfield:
if flight.departure == airfield:
return airfield
raise RuntimeError(
"Could not find any airfield assigned to this package"

View File

@@ -8,14 +8,13 @@ from dcs.unit import Unit
from dcs.unitgroup import VehicleGroup
from game.data.doctrine import Doctrine
from game.utils import feet_to_meter
from game.weather import Conditions
from theater import (
from game.theater import (
ControlPoint,
MissionTarget,
OffMapSpawn,
TheaterGroundObject,
)
from game.weather import Conditions
from .flight import Flight, FlightWaypoint, FlightWaypointType
@@ -104,6 +103,40 @@ class WaypointBuilder:
waypoint.pretty_name = "Land"
return waypoint
def divert(self,
divert: Optional[ControlPoint]) -> Optional[FlightWaypoint]:
"""Create divert waypoint for the given arrival airfield or carrier.
Args:
divert: Divert airfield or carrier.
"""
if divert is None:
return None
position = divert.position
if isinstance(divert, OffMapSpawn):
if self.is_helo:
altitude = 500
else:
altitude = self.doctrine.rendezvous_altitude
altitude_type = "BARO"
else:
altitude = 0
altitude_type = "RADIO"
waypoint = FlightWaypoint(
FlightWaypointType.DIVERT,
position.x,
position.y,
altitude
)
waypoint.alt_type = altitude_type
waypoint.name = "DIVERT"
waypoint.description = "Divert"
waypoint.pretty_name = "Divert"
waypoint.only_for_player = True
return waypoint
def hold(self, position: Point) -> FlightWaypoint:
waypoint = FlightWaypoint(
FlightWaypointType.LOITER,

View File

@@ -2,12 +2,12 @@ import random
from enum import Enum
from typing import Dict, List
from dcs.vehicles import Armor, Artillery, Infantry, Unarmed
from dcs.unittype import VehicleType
from dcs.vehicles import Armor, Artillery, Infantry, Unarmed
import pydcs_extensions.frenchpack.frenchpack as frenchpack
from game.theater import ControlPoint
from gen.ground_forces.combat_stance import CombatStance
from theater import ControlPoint
TYPE_TANKS = [
Armor.MBT_T_55,

View File

@@ -20,14 +20,14 @@ from dcs.task import (
EPLRS,
OptAlarmState,
)
from dcs.unit import Ship, Vehicle, Unit
from dcs.unit import Ship, Unit, Vehicle
from dcs.unitgroup import Group, ShipGroup, StaticGroup
from dcs.unittype import StaticType, UnitType
from game import db
from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
from game.db import unit_type_from_name
from theater import ControlPoint, TheaterGroundObject
from game.theater import ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import (
BuildingGroundObject, CarrierGroundObject,
GenericCarrierGroundObject,

View File

@@ -7,8 +7,8 @@ from typing import Iterator, Optional
from dcs.terrain.terrain import Airport
from game.theater import ControlPoint, ControlPointType
from game.weather import Conditions
from theater import ControlPoint, ControlPointType
from .airfields import AIRFIELD_DATA
from .radios import RadioFrequency
from .tacan import TacanChannel