mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Split aircraft spawning into its own class.
This commit is contained in:
parent
88b4039e47
commit
c8f30b3289
@ -1,22 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import random
|
||||
from functools import cached_property
|
||||
from typing import Any, Dict, List, TYPE_CHECKING, Type, Union
|
||||
from typing import Any, Dict, List, TYPE_CHECKING
|
||||
|
||||
from dcs import helicopters
|
||||
from dcs.country import Country
|
||||
from dcs.mapping import Point
|
||||
from dcs.mission import Mission, StartType as DcsStartType
|
||||
from dcs.planes import (
|
||||
Su_33,
|
||||
)
|
||||
from dcs.point import PointAction
|
||||
from dcs.ships import KUZNECOW
|
||||
from dcs.terrain.terrain import Airport, NoParkingSlotError
|
||||
from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup
|
||||
from dcs.unittype import FlyingType
|
||||
from dcs.mission import Mission
|
||||
from dcs.terrain.terrain import NoParkingSlotError
|
||||
from dcs.unitgroup import FlyingGroup, StaticGroup
|
||||
|
||||
from game.ato.airtaaskingorder import AirTaskingOrder
|
||||
from game.ato.flight import Flight
|
||||
@ -32,29 +23,18 @@ from game.settings import Settings
|
||||
from game.theater.controlpoint import (
|
||||
Airfield,
|
||||
ControlPoint,
|
||||
NavalControlPoint,
|
||||
OffMapSpawn,
|
||||
)
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import meters
|
||||
from gen.flights.traveltime import GroundSpeed
|
||||
from gen.naming import namegen
|
||||
from gen.runways import RunwayData
|
||||
from .aircraftpainter import AircraftPainter
|
||||
from .flightdata import FlightData
|
||||
from .flightgroupconfigurator import FlightGroupConfigurator
|
||||
from .flightgroupspawner import FlightGroupSpawner
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
from game.squadrons import Squadron
|
||||
|
||||
WARM_START_HELI_ALT = meters(500)
|
||||
WARM_START_ALTITUDE = meters(3000)
|
||||
|
||||
RTB_ALTITUDE = meters(800)
|
||||
RTB_DISTANCE = 5000
|
||||
HELI_ALT = 500
|
||||
|
||||
|
||||
class AircraftGenerator:
|
||||
def __init__(
|
||||
@ -95,171 +75,6 @@ class AircraftGenerator:
|
||||
total += flight.client_count
|
||||
return total
|
||||
|
||||
@staticmethod
|
||||
def _start_type(start_type: str) -> DcsStartType:
|
||||
if start_type == "Runway":
|
||||
return DcsStartType.Runway
|
||||
elif start_type == "Cold":
|
||||
return DcsStartType.Cold
|
||||
return DcsStartType.Warm
|
||||
|
||||
@staticmethod
|
||||
def _start_type_at_group(
|
||||
start_type: str,
|
||||
unit_type: Type[FlyingType],
|
||||
at: Union[ShipGroup, StaticGroup],
|
||||
) -> DcsStartType:
|
||||
group_units = at.units
|
||||
# Setting Su-33s starting from the non-supercarrier Kuznetsov to take off from runway
|
||||
# to work around a DCS AI issue preventing Su-33s from taking off when set to "Takeoff from ramp" (#1352)
|
||||
if (
|
||||
unit_type.id == Su_33.id
|
||||
and group_units[0] is not None
|
||||
and group_units[0].type == KUZNECOW.id
|
||||
):
|
||||
return DcsStartType.Runway
|
||||
else:
|
||||
return AircraftGenerator._start_type(start_type)
|
||||
|
||||
def _generate_at_airport(
|
||||
self,
|
||||
name: str,
|
||||
side: Country,
|
||||
unit_type: Type[FlyingType],
|
||||
count: int,
|
||||
start_type: str,
|
||||
airport: Airport,
|
||||
) -> FlyingGroup[Any]:
|
||||
assert count > 0
|
||||
|
||||
# TODO: Delayed runway starts should be converted to air starts for multiplayer.
|
||||
# Runway starts do not work with late activated aircraft in multiplayer. Instead
|
||||
# of spawning on the runway the aircraft will spawn on the taxiway, potentially
|
||||
# somewhere that they don't fit anyway. We should either upgrade these to air
|
||||
# starts or (less likely) downgrade to warm starts to avoid the issue when the
|
||||
# player is generating the mission for multiplayer (which would need a new
|
||||
# option).
|
||||
logging.info("airgen: {} for {} at {}".format(unit_type, side.id, airport))
|
||||
return self.m.flight_group_from_airport(
|
||||
country=side,
|
||||
name=name,
|
||||
aircraft_type=unit_type,
|
||||
airport=airport,
|
||||
maintask=None,
|
||||
start_type=self._start_type(start_type),
|
||||
group_size=count,
|
||||
parking_slots=None,
|
||||
)
|
||||
|
||||
def _generate_over_departure(
|
||||
self, name: str, side: Country, flight: Flight, origin: ControlPoint
|
||||
) -> FlyingGroup[Any]:
|
||||
assert flight.count > 0
|
||||
at = origin.position
|
||||
|
||||
alt_type = "RADIO"
|
||||
if isinstance(origin, OffMapSpawn):
|
||||
alt = flight.flight_plan.waypoints[0].alt
|
||||
alt_type = flight.flight_plan.waypoints[0].alt_type
|
||||
elif flight.unit_type in helicopters.helicopter_map.values():
|
||||
alt = WARM_START_HELI_ALT
|
||||
else:
|
||||
alt = WARM_START_ALTITUDE
|
||||
|
||||
speed = GroundSpeed.for_flight(flight, alt)
|
||||
|
||||
pos = Point(at.x + random.randint(100, 1000), at.y + random.randint(100, 1000))
|
||||
|
||||
logging.info(
|
||||
"airgen: {} for {} at {} at {}".format(
|
||||
flight.unit_type, side.id, alt, int(speed.kph)
|
||||
)
|
||||
)
|
||||
group = self.m.flight_group(
|
||||
country=side,
|
||||
name=name,
|
||||
aircraft_type=flight.unit_type.dcs_unit_type,
|
||||
airport=None,
|
||||
position=pos,
|
||||
altitude=alt.meters,
|
||||
speed=speed.kph,
|
||||
maintask=None,
|
||||
group_size=flight.count,
|
||||
)
|
||||
|
||||
group.points[0].alt_type = alt_type
|
||||
return group
|
||||
|
||||
def _generate_at_group(
|
||||
self,
|
||||
name: str,
|
||||
side: Country,
|
||||
unit_type: Type[FlyingType],
|
||||
count: int,
|
||||
start_type: str,
|
||||
at: Union[ShipGroup, StaticGroup],
|
||||
) -> FlyingGroup[Any]:
|
||||
assert count > 0
|
||||
|
||||
logging.info("airgen: {} for {} at unit {}".format(unit_type, side.id, at))
|
||||
return self.m.flight_group_from_unit(
|
||||
country=side,
|
||||
name=name,
|
||||
aircraft_type=unit_type,
|
||||
pad_group=at,
|
||||
maintask=None,
|
||||
start_type=self._start_type_at_group(start_type, unit_type, at),
|
||||
group_size=count,
|
||||
)
|
||||
|
||||
def _generate_at_cp_helipad(
|
||||
self,
|
||||
name: str,
|
||||
side: Country,
|
||||
unit_type: Type[FlyingType],
|
||||
count: int,
|
||||
start_type: str,
|
||||
cp: ControlPoint,
|
||||
) -> FlyingGroup[Any]:
|
||||
assert count > 0
|
||||
|
||||
logging.info(
|
||||
"airgen at cp's helipads : {} for {} at {}".format(
|
||||
unit_type, side.id, cp.name
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
helipad = self.helipads[cp].pop()
|
||||
except IndexError as ex:
|
||||
raise RuntimeError(f"Not enough helipads available at {cp}") from ex
|
||||
|
||||
group = self._generate_at_group(
|
||||
name=name,
|
||||
side=side,
|
||||
unit_type=unit_type,
|
||||
count=count,
|
||||
start_type=start_type,
|
||||
at=helipad,
|
||||
)
|
||||
|
||||
# Note : A bit dirty, need better support in pydcs
|
||||
group.points[0].action = PointAction.FromGroundArea
|
||||
group.points[0].type = "TakeOffGround"
|
||||
group.units[0].heading = helipad.units[0].heading
|
||||
if start_type != "Cold":
|
||||
group.points[0].action = PointAction.FromGroundAreaHot
|
||||
group.points[0].type = "TakeOffGroundHot"
|
||||
|
||||
for i in range(count - 1):
|
||||
try:
|
||||
helipad = self.helipads[cp].pop()
|
||||
group.units[1 + i].position = Point(helipad.x, helipad.y)
|
||||
group.units[1 + i].heading = helipad.units[0].heading
|
||||
except IndexError as ex:
|
||||
raise RuntimeError(f"Not enough helipads available at {cp}") from ex
|
||||
return group
|
||||
|
||||
def clear_parking_slots(self) -> None:
|
||||
for cp in self.game.theater.controlpoints:
|
||||
for parking_slot in cp.parking_slots:
|
||||
@ -330,23 +145,18 @@ class AircraftGenerator:
|
||||
divert=None,
|
||||
)
|
||||
|
||||
group = self._generate_at_airport(
|
||||
name=namegen.next_aircraft_name(country, flight.departure.id, flight),
|
||||
side=country,
|
||||
unit_type=squadron.aircraft.dcs_unit_type,
|
||||
count=1,
|
||||
start_type="Cold",
|
||||
airport=squadron.location.airport,
|
||||
)
|
||||
|
||||
group.uncontrolled = True
|
||||
group = FlightGroupSpawner(
|
||||
flight, country, self.m, self.helipads
|
||||
).create_idle_aircraft()
|
||||
AircraftPainter(flight, group).apply_livery()
|
||||
self.unit_map.add_aircraft(group, flight)
|
||||
|
||||
def create_and_configure_flight(
|
||||
self, flight: Flight, country: Country, dynamic_runways: Dict[str, RunwayData]
|
||||
) -> FlyingGroup[Any]:
|
||||
group = self.generate_planned_flight(country, flight)
|
||||
group = FlightGroupSpawner(
|
||||
flight, country, self.m, self.helipads
|
||||
).create_flight_group()
|
||||
self.flights.append(
|
||||
FlightGroupConfigurator(
|
||||
flight,
|
||||
@ -362,71 +172,3 @@ class AircraftGenerator:
|
||||
).configure()
|
||||
)
|
||||
return group
|
||||
|
||||
def generate_flight_at_departure(
|
||||
self, country: Country, flight: Flight, start_type: StartType
|
||||
) -> FlyingGroup[Any]:
|
||||
name = namegen.next_aircraft_name(country, flight.departure.id, flight)
|
||||
cp = flight.departure
|
||||
try:
|
||||
if start_type is StartType.IN_FLIGHT:
|
||||
group = self._generate_over_departure(
|
||||
name=name, side=country, flight=flight, origin=cp
|
||||
)
|
||||
return group
|
||||
elif isinstance(cp, NavalControlPoint):
|
||||
group_name = cp.get_carrier_group_name()
|
||||
carrier_group = self.m.find_group(group_name)
|
||||
if not isinstance(carrier_group, ShipGroup):
|
||||
raise RuntimeError(
|
||||
f"Carrier group {carrier_group} is a "
|
||||
"{carrier_group.__class__.__name__}, expected a ShipGroup"
|
||||
)
|
||||
return self._generate_at_group(
|
||||
name=name,
|
||||
side=country,
|
||||
unit_type=flight.unit_type.dcs_unit_type,
|
||||
count=flight.count,
|
||||
start_type=start_type.value,
|
||||
at=carrier_group,
|
||||
)
|
||||
else:
|
||||
# If the flight is an helicopter flight, then prioritize dedicated helipads
|
||||
if flight.unit_type.helicopter:
|
||||
return self._generate_at_cp_helipad(
|
||||
name=name,
|
||||
side=country,
|
||||
unit_type=flight.unit_type.dcs_unit_type,
|
||||
count=flight.count,
|
||||
start_type=start_type.value,
|
||||
cp=cp,
|
||||
)
|
||||
|
||||
if not isinstance(cp, Airfield):
|
||||
raise RuntimeError(
|
||||
f"Attempted to spawn at airfield for non-airfield {cp}"
|
||||
)
|
||||
return self._generate_at_airport(
|
||||
name=name,
|
||||
side=country,
|
||||
unit_type=flight.unit_type.dcs_unit_type,
|
||||
count=flight.count,
|
||||
start_type=start_type.value,
|
||||
airport=cp.airport,
|
||||
)
|
||||
except NoParkingSlotError:
|
||||
# Generated when there is no place on Runway or on Parking Slots
|
||||
logging.exception(
|
||||
"No room on runway or parking slots. Starting from the air."
|
||||
)
|
||||
flight.start_type = StartType.IN_FLIGHT
|
||||
group = self._generate_over_departure(
|
||||
name=name, side=country, flight=flight, origin=cp
|
||||
)
|
||||
group.points[0].alt = 1500
|
||||
return group
|
||||
|
||||
def generate_planned_flight(
|
||||
self, country: Country, flight: Flight
|
||||
) -> FlyingGroup[Any]:
|
||||
return self.generate_flight_at_departure(country, flight, flight.start_type)
|
||||
|
||||
211
game/missiongenerator/aircraft/flightgroupspawner.py
Normal file
211
game/missiongenerator/aircraft/flightgroupspawner.py
Normal file
@ -0,0 +1,211 @@
|
||||
import logging
|
||||
import random
|
||||
from typing import Any, Union
|
||||
|
||||
from dcs import Mission, Point
|
||||
from dcs.country import Country
|
||||
from dcs.mission import StartType as DcsStartType
|
||||
from dcs.planes import Su_33
|
||||
from dcs.point import PointAction
|
||||
from dcs.ships import KUZNECOW
|
||||
from dcs.terrain import Airport, NoParkingSlotError
|
||||
from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup
|
||||
|
||||
from game.ato import Flight
|
||||
from game.ato.starttype import StartType
|
||||
from game.theater import Airfield, ControlPoint, NavalControlPoint, OffMapSpawn
|
||||
from game.utils import meters
|
||||
from gen.flights.traveltime import GroundSpeed
|
||||
from gen.naming import namegen
|
||||
|
||||
WARM_START_HELI_ALT = meters(500)
|
||||
WARM_START_ALTITUDE = meters(3000)
|
||||
|
||||
RTB_ALTITUDE = meters(800)
|
||||
RTB_DISTANCE = 5000
|
||||
HELI_ALT = 500
|
||||
|
||||
|
||||
class FlightGroupSpawner:
|
||||
def __init__(
|
||||
self,
|
||||
flight: Flight,
|
||||
country: Country,
|
||||
mission: Mission,
|
||||
helipads: dict[ControlPoint, list[StaticGroup]],
|
||||
) -> None:
|
||||
self.flight = flight
|
||||
self.country = country
|
||||
self.mission = mission
|
||||
self.helipads = helipads
|
||||
|
||||
def create_flight_group(self) -> FlyingGroup[Any]:
|
||||
return self.generate_flight_at_departure()
|
||||
|
||||
def create_idle_aircraft(self) -> FlyingGroup[Any]:
|
||||
assert isinstance(self.flight.squadron.location, Airfield)
|
||||
group = self._generate_at_airport(
|
||||
name=namegen.next_aircraft_name(
|
||||
self.country, self.flight.departure.id, self.flight
|
||||
),
|
||||
airport=self.flight.squadron.location.airport,
|
||||
)
|
||||
|
||||
group.uncontrolled = True
|
||||
return group
|
||||
|
||||
def generate_flight_at_departure(self) -> FlyingGroup[Any]:
|
||||
name = namegen.next_aircraft_name(
|
||||
self.country, self.flight.departure.id, self.flight
|
||||
)
|
||||
cp = self.flight.departure
|
||||
try:
|
||||
if self.flight.start_type is StartType.IN_FLIGHT:
|
||||
group = self._generate_over_departure(name, cp)
|
||||
return group
|
||||
elif isinstance(cp, NavalControlPoint):
|
||||
group_name = cp.get_carrier_group_name()
|
||||
carrier_group = self.mission.find_group(group_name)
|
||||
if not isinstance(carrier_group, ShipGroup):
|
||||
raise RuntimeError(
|
||||
f"Carrier group {carrier_group} is a "
|
||||
"{carrier_group.__class__.__name__}, expected a ShipGroup"
|
||||
)
|
||||
return self._generate_at_group(name, carrier_group)
|
||||
else:
|
||||
# If the flight is an helicopter flight, then prioritize dedicated
|
||||
# helipads
|
||||
if self.flight.unit_type.helicopter:
|
||||
return self._generate_at_cp_helipad(name, cp)
|
||||
|
||||
if not isinstance(cp, Airfield):
|
||||
raise RuntimeError(
|
||||
f"Attempted to spawn at airfield for non-airfield {cp}"
|
||||
)
|
||||
return self._generate_at_airport(name, cp.airport)
|
||||
except NoParkingSlotError:
|
||||
# Generated when there is no place on Runway or on Parking Slots
|
||||
logging.exception(
|
||||
"No room on runway or parking slots. Starting from the air."
|
||||
)
|
||||
self.flight.start_type = StartType.IN_FLIGHT
|
||||
group = self._generate_over_departure(name, cp)
|
||||
group.points[0].alt = 1500
|
||||
return group
|
||||
|
||||
def _generate_at_airport(self, name: str, airport: Airport) -> FlyingGroup[Any]:
|
||||
# TODO: Delayed runway starts should be converted to air starts for multiplayer.
|
||||
# Runway starts do not work with late activated aircraft in multiplayer. Instead
|
||||
# of spawning on the runway the aircraft will spawn on the taxiway, potentially
|
||||
# somewhere that they don't fit anyway. We should either upgrade these to air
|
||||
# starts or (less likely) downgrade to warm starts to avoid the issue when the
|
||||
# player is generating the mission for multiplayer (which would need a new
|
||||
# option).
|
||||
return self.mission.flight_group_from_airport(
|
||||
country=self.country,
|
||||
name=name,
|
||||
aircraft_type=self.flight.unit_type.dcs_unit_type,
|
||||
airport=airport,
|
||||
maintask=None,
|
||||
start_type=self.dcs_start_type(),
|
||||
group_size=self.flight.count,
|
||||
parking_slots=None,
|
||||
)
|
||||
|
||||
def _generate_over_departure(
|
||||
self, name: str, origin: ControlPoint
|
||||
) -> FlyingGroup[Any]:
|
||||
at = origin.position
|
||||
|
||||
alt_type = "RADIO"
|
||||
if isinstance(origin, OffMapSpawn):
|
||||
alt = self.flight.flight_plan.waypoints[0].alt
|
||||
alt_type = self.flight.flight_plan.waypoints[0].alt_type
|
||||
elif self.flight.unit_type.helicopter:
|
||||
alt = WARM_START_HELI_ALT
|
||||
else:
|
||||
alt = WARM_START_ALTITUDE
|
||||
|
||||
speed = GroundSpeed.for_flight(self.flight, alt)
|
||||
pos = Point(at.x + random.randint(100, 1000), at.y + random.randint(100, 1000))
|
||||
|
||||
group = self.mission.flight_group(
|
||||
country=self.country,
|
||||
name=name,
|
||||
aircraft_type=self.flight.unit_type.dcs_unit_type,
|
||||
airport=None,
|
||||
position=pos,
|
||||
altitude=alt.meters,
|
||||
speed=speed.kph,
|
||||
maintask=None,
|
||||
group_size=self.flight.count,
|
||||
)
|
||||
|
||||
group.points[0].alt_type = alt_type
|
||||
return group
|
||||
|
||||
def _generate_at_group(
|
||||
self, name: str, at: Union[ShipGroup, StaticGroup]
|
||||
) -> FlyingGroup[Any]:
|
||||
return self.mission.flight_group_from_unit(
|
||||
country=self.country,
|
||||
name=name,
|
||||
aircraft_type=self.flight.unit_type.dcs_unit_type,
|
||||
pad_group=at,
|
||||
maintask=None,
|
||||
start_type=self._start_type_at_group(at),
|
||||
group_size=self.flight.count,
|
||||
)
|
||||
|
||||
def _generate_at_cp_helipad(self, name: str, cp: ControlPoint) -> FlyingGroup[Any]:
|
||||
try:
|
||||
helipad = self.helipads[cp].pop()
|
||||
except IndexError as ex:
|
||||
raise RuntimeError(f"Not enough helipads available at {cp}") from ex
|
||||
|
||||
group = self._generate_at_group(name, helipad)
|
||||
|
||||
# Note : A bit dirty, need better support in pydcs
|
||||
group.points[0].action = PointAction.FromGroundArea
|
||||
group.points[0].type = "TakeOffGround"
|
||||
group.units[0].heading = helipad.units[0].heading
|
||||
if self.flight.start_type != "Cold":
|
||||
group.points[0].action = PointAction.FromGroundAreaHot
|
||||
group.points[0].type = "TakeOffGroundHot"
|
||||
|
||||
for i in range(self.flight.count - 1):
|
||||
try:
|
||||
helipad = self.helipads[cp].pop()
|
||||
group.units[1 + i].position = Point(helipad.x, helipad.y)
|
||||
group.units[1 + i].heading = helipad.units[0].heading
|
||||
except IndexError as ex:
|
||||
raise RuntimeError(f"Not enough helipads available at {cp}") from ex
|
||||
return group
|
||||
|
||||
def dcs_start_type(self) -> DcsStartType:
|
||||
if self.flight.start_type is StartType.RUNWAY:
|
||||
return DcsStartType.Runway
|
||||
elif self.flight.start_type is StartType.COLD:
|
||||
return DcsStartType.Cold
|
||||
elif self.flight.start_type is StartType.WARM:
|
||||
return DcsStartType.Warm
|
||||
raise ValueError(
|
||||
f"There is no pydcs StartType matching {self.flight.start_type}"
|
||||
)
|
||||
|
||||
def _start_type_at_group(
|
||||
self,
|
||||
at: Union[ShipGroup, StaticGroup],
|
||||
) -> DcsStartType:
|
||||
group_units = at.units
|
||||
# Setting Su-33s starting from the non-supercarrier Kuznetsov to take off from
|
||||
# runway to work around a DCS AI issue preventing Su-33s from taking off when
|
||||
# set to "Takeoff from ramp" (#1352)
|
||||
if (
|
||||
self.flight.unit_type.dcs_unit_type == Su_33
|
||||
and group_units[0] is not None
|
||||
and group_units[0].type == KUZNECOW.id
|
||||
):
|
||||
return DcsStartType.Runway
|
||||
else:
|
||||
return self.dcs_start_type()
|
||||
Loading…
x
Reference in New Issue
Block a user