Implemented spawning of Pretense cargo aircraft. To support that, implemented a separate flight plan called PretenseCargoFlightPlan. Also, will now automatically generate transport squadrons for factions which don't have pre-defined squadrons for it, but have access to transport aircraft.

This commit is contained in:
MetalStormGhost 2023-09-16 14:36:59 +03:00
parent 4ad87aef3e
commit d965f90bb4
5 changed files with 225 additions and 22 deletions

View File

@ -18,6 +18,7 @@ from .ocaaircraft import OcaAircraftFlightPlan
from .ocarunway import OcaRunwayFlightPlan
from .packagerefueling import PackageRefuelingFlightPlan
from .planningerror import PlanningError
from .pretensecargo import PretenseCargoFlightPlan
from .sead import SeadFlightPlan
from .seadsweep import SeadSweepFlightPlan
from .strike import StrikeFlightPlan
@ -60,6 +61,7 @@ class FlightPlanBuilderTypes:
FlightType.TRANSPORT: AirliftFlightPlan.builder_type(),
FlightType.FERRY: FerryFlightPlan.builder_type(),
FlightType.AIR_ASSAULT: AirAssaultFlightPlan.builder_type(),
FlightType.PRETENSE_CARGO: PretenseCargoFlightPlan.builder_type(),
}
try:
return builder_dict[flight.flight_type]

View File

@ -0,0 +1,99 @@
from __future__ import annotations
from collections.abc import Iterator
from dataclasses import dataclass
from datetime import timedelta
from typing import TYPE_CHECKING, Type
from game.utils import feet
from .ferry import FerryLayout
from .ibuilder import IBuilder
from .planningerror import PlanningError
from .standard import StandardFlightPlan, StandardLayout
from .waypointbuilder import WaypointBuilder
if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
PRETENSE_CARGO_FLIGHT_DISTANCE = 50000
class PretenseCargoFlightPlan(StandardFlightPlan[FerryLayout]):
@staticmethod
def builder_type() -> Type[Builder]:
return Builder
@property
def tot_waypoint(self) -> FlightWaypoint:
return self.layout.arrival
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
# TOT planning isn't really useful for ferries. 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) -> timedelta | None:
return None
@property
def mission_departure_time(self) -> timedelta:
return self.package.time_over_target
class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]):
def layout(self) -> FerryLayout:
# Find the spawn location for off-map transport planes
distance_to_flot = 0
heading_from_flot = 0.0
offmap_transport_cp_id = self.flight.departure.id
for front_line_cp in self.coalition.game.theater.controlpoints:
for front_line in self.coalition.game.theater.conflicts():
if front_line_cp.captured == self.flight.coalition.player:
if (
front_line_cp.position.distance_to_point(front_line.position)
> distance_to_flot
):
distance_to_flot = front_line_cp.position.distance_to_point(
front_line.position
)
heading_from_flot = front_line.position.heading_between_point(
front_line_cp.position
)
offmap_transport_cp_id = front_line_cp.id
offmap_transport_cp = self.coalition.game.theater.find_control_point_by_id(
offmap_transport_cp_id
)
offmap_transport_spawn = offmap_transport_cp.position.point_from_heading(
heading_from_flot, PRETENSE_CARGO_FLIGHT_DISTANCE
)
altitude_is_agl = self.flight.unit_type.dcs_unit_type.helicopter
altitude = (
feet(1500)
if altitude_is_agl
else self.flight.unit_type.preferred_patrol_altitude
)
builder = WaypointBuilder(self.flight, self.coalition)
ferry_layout = FerryLayout(
departure=builder.join(offmap_transport_spawn),
nav_to=builder.nav_path(
offmap_transport_spawn,
self.flight.arrival.position,
altitude,
altitude_is_agl,
),
arrival=builder.land(self.flight.arrival),
divert=builder.divert(self.flight.divert),
bullseye=builder.bullseye(),
nav_from=[],
)
ferry_layout.departure = builder.join(offmap_transport_spawn)
ferry_layout.nav_to.append(builder.join(offmap_transport_spawn))
ferry_layout.nav_from.append(builder.join(offmap_transport_spawn))
print(ferry_layout)
return ferry_layout
def build(self) -> PretenseCargoFlightPlan:
return PretenseCargoFlightPlan(self.flight, self.layout())

View File

@ -29,11 +29,8 @@ class Navigating(InFlight):
events.update_flight_position(self.flight, self.estimate_position())
def progress(self) -> float:
# if next waypoint is very close, assume we reach it immediately to avoid divide
# by zero error
if self.total_time_to_next_waypoint.total_seconds() < 1:
return 1.0
if self.total_time_to_next_waypoint.total_seconds() == 0.0:
return 99.9
return (
self.elapsed_time.total_seconds()
/ self.total_time_to_next_waypoint.total_seconds()

View File

@ -58,6 +58,9 @@ class FlightType(Enum):
FERRY = "Ferry"
AIR_ASSAULT = "Air Assault"
SEAD_SWEEP = "SEAD Sweep" # Reintroduce legacy "engage-whatever-you-can-find" SEAD
PRETENSE_CARGO = (
"Cargo Transport" # Flight type for Pretense campaign AI cargo planes
)
def __str__(self) -> str:
return self.value
@ -121,5 +124,6 @@ class FlightType(Enum):
FlightType.SWEEP: AirEntity.FIGHTER,
FlightType.TARCAP: AirEntity.FIGHTER,
FlightType.TRANSPORT: AirEntity.UTILITY,
FlightType.PRETENSE_CARGO: AirEntity.UTILITY,
FlightType.AIR_ASSAULT: AirEntity.ROTARY_WING,
}.get(self, AirEntity.UNSPECIFIED)

View File

@ -5,20 +5,16 @@ import random
from datetime import datetime
from functools import cached_property
from typing import Any, Dict, List, TYPE_CHECKING, Tuple
from uuid import UUID
from dcs import Point
from dcs.action import AITaskPush
from dcs.condition import FlagIsTrue, GroupDead, Or, FlagIsFalse
from dcs.country import Country
from dcs.mission import Mission
from dcs.terrain.terrain import NoParkingSlotError
from dcs.triggers import TriggerOnce, Event
from dcs.unit import Skill
from dcs.unitgroup import FlyingGroup, StaticGroup
from game.ato.airtaaskingorder import AirTaskingOrder
from game.ato.flight import Flight
from game.ato.flightstate import Completed, WaitingForStart
from game.ato.flightstate import Completed, WaitingForStart, Navigating
from game.ato.flighttype import FlightType
from game.ato.package import Package
from game.ato.starttype import StartType
@ -32,10 +28,9 @@ from game.radio.tacan import TacanRegistry
from game.runways import RunwayData
from game.settings import Settings
from game.theater.controlpoint import (
Airfield,
ControlPoint,
Fob,
OffMapSpawn,
ParkingType,
)
from game.unitmap import UnitMap
from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter
@ -44,7 +39,9 @@ from game.data.weapons import WeaponType
if TYPE_CHECKING:
from game import Game
from game.squadrons import Squadron
PRETENSE_SQUADRON_DEF_RETRIES = 100
class PretenseAircraftGenerator:
@ -108,6 +105,8 @@ class PretenseAircraftGenerator:
ato: AirTaskingOrder,
dynamic_runways: Dict[str, RunwayData],
) -> None:
from game.squadrons import Squadron
"""Adds aircraft to the mission for every flight in the ATO.
Aircraft generation is done by walking the ATO and spawning each flight in turn.
@ -125,6 +124,89 @@ class PretenseAircraftGenerator:
num_of_strike = 0
num_of_cap = 0
# Find locations for off-map transport planes
distance_to_flot = 0
offmap_transport_cp_id = cp.id
parking_type = ParkingType(
fixed_wing=True, fixed_wing_stol=True, rotary_wing=False
)
for front_line_cp in self.game.theater.controlpoints:
for front_line in self.game.theater.conflicts():
if front_line_cp.captured == cp.captured:
if (
front_line_cp.total_aircraft_parking(parking_type) > 0
and front_line_cp.position.distance_to_point(
front_line.position
)
> distance_to_flot
):
distance_to_flot = front_line_cp.position.distance_to_point(
front_line.position
)
offmap_transport_cp_id = front_line_cp.id
offmap_transport_cp = self.game.theater.find_control_point_by_id(
offmap_transport_cp_id
)
# Ensure that the faction has at least one transport helicopter and one cargo plane squadron
autogenerate_transport_helicopter_squadron = True
autogenerate_cargo_plane_squadron = True
for aircraft_type in cp.coalition.air_wing.squadrons:
for squadron in cp.coalition.air_wing.squadrons[aircraft_type]:
mission_types = squadron.auto_assignable_mission_types
if squadron.aircraft.helicopter and (
FlightType.TRANSPORT in mission_types
or FlightType.AIR_ASSAULT in mission_types
):
autogenerate_transport_helicopter_squadron = False
elif not squadron.aircraft.helicopter and (
FlightType.TRANSPORT in mission_types
or FlightType.AIR_ASSAULT in mission_types
):
autogenerate_cargo_plane_squadron = False
if autogenerate_transport_helicopter_squadron:
flight_type = FlightType.AIR_ASSAULT
squadron_def = (
cp.coalition.air_wing.squadron_def_generator.generate_for_task(
flight_type, offmap_transport_cp
)
)
squadron = Squadron.create_from(
squadron_def,
flight_type,
2,
offmap_transport_cp,
cp.coalition,
self.game,
)
cp.coalition.air_wing.squadrons[squadron.aircraft] = list()
cp.coalition.air_wing.add_squadron(squadron)
if autogenerate_cargo_plane_squadron:
flight_type = FlightType.TRANSPORT
squadron_def = (
cp.coalition.air_wing.squadron_def_generator.generate_for_task(
flight_type, offmap_transport_cp
)
)
for retries in range(PRETENSE_SQUADRON_DEF_RETRIES):
if squadron_def.aircraft.helicopter:
squadron_def = (
cp.coalition.air_wing.squadron_def_generator.generate_for_task(
flight_type, offmap_transport_cp
)
)
squadron = Squadron.create_from(
squadron_def,
flight_type,
2,
offmap_transport_cp,
cp.coalition,
self.game,
)
cp.coalition.air_wing.squadrons[squadron.aircraft] = list()
cp.coalition.air_wing.add_squadron(squadron)
for squadron in cp.squadrons:
# Intentionally don't spawn anything at OffMapSpawns in Pretense
if isinstance(squadron.location, OffMapSpawn):
@ -134,11 +216,16 @@ class PretenseAircraftGenerator:
squadron.untasked_aircraft += 1
package = Package(cp, squadron.flight_db, auto_asap=False)
mission_types = squadron.auto_assignable_mission_types
if (
if squadron.aircraft.helicopter and (
FlightType.TRANSPORT in mission_types
or FlightType.AIR_ASSAULT in mission_types
):
flight_type = FlightType.AIR_ASSAULT
elif not squadron.aircraft.helicopter and (
FlightType.TRANSPORT in mission_types
or FlightType.AIR_ASSAULT in mission_types
):
flight_type = FlightType.TRANSPORT
elif (
FlightType.SEAD in mission_types
or FlightType.SEAD_SWEEP in mission_types
@ -170,13 +257,27 @@ class PretenseAircraftGenerator:
num_of_cap += 1
else:
flight_type = random.choice(list(mission_types))
flight = Flight(
package, squadron, 1, flight_type, StartType.COLD, divert=cp
)
flight.state = WaitingForStart(
flight, self.game.settings, self.game.conditions.start_time
)
package.add_flight(flight)
if flight_type == FlightType.TRANSPORT:
flight = Flight(
package,
squadron,
1,
FlightType.PRETENSE_CARGO,
StartType.IN_FLIGHT,
divert=cp,
)
package.add_flight(flight)
flight.state = Navigating(flight, self.game.settings, waypoint_index=1)
else:
flight = Flight(
package, squadron, 1, flight_type, StartType.COLD, divert=cp
)
package.add_flight(flight)
flight.state = WaitingForStart(
flight, self.game.settings, self.game.conditions.start_time
)
ato.add_package(package)
self._reserve_frequencies_and_tacan(ato)