mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
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:
parent
4ad87aef3e
commit
d965f90bb4
@ -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]
|
||||
|
||||
99
game/ato/flightplans/pretensecargo.py
Normal file
99
game/ato/flightplans/pretensecargo.py
Normal 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())
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user