mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Refactor flight plan generation.
This commit is contained in:
parent
8b717c4f4c
commit
cc7c2cc707
@ -5,6 +5,7 @@ from game.inventory import GlobalAircraftInventory
|
|||||||
from game.models.game_stats import GameStats
|
from game.models.game_stats import GameStats
|
||||||
from gen.ato import AirTaskingOrder
|
from gen.ato import AirTaskingOrder
|
||||||
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
|
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
|
||||||
|
from gen.flights.closestairfields import ObjectiveDistanceCache
|
||||||
from gen.ground_forces.ai_ground_planner import GroundPlanner
|
from gen.ground_forces.ai_ground_planner import GroundPlanner
|
||||||
from .event import *
|
from .event import *
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
@ -204,6 +205,7 @@ class Game:
|
|||||||
return event and event.name and event.name == self.player_name
|
return event and event.name and event.name == self.player_name
|
||||||
|
|
||||||
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint] = None):
|
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint] = None):
|
||||||
|
ObjectiveDistanceCache.set_theater(self.theater)
|
||||||
|
|
||||||
logging.info("Pass turn")
|
logging.info("Pass turn")
|
||||||
self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0))
|
self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0))
|
||||||
|
|||||||
@ -185,20 +185,16 @@ class Operation:
|
|||||||
self.airsupportgen.generate(self.is_awacs_enabled)
|
self.airsupportgen.generate(self.is_awacs_enabled)
|
||||||
|
|
||||||
# Generate Activity on the map
|
# Generate Activity on the map
|
||||||
for cp in self.game.theater.controlpoints:
|
self.airgen.generate_flights(
|
||||||
side = cp.captured
|
self.current_mission.country(self.game.player_country),
|
||||||
if side:
|
self.game.blue_ato,
|
||||||
country = self.current_mission.country(self.game.player_country)
|
self.groundobjectgen.runways
|
||||||
ato = self.game.blue_ato
|
)
|
||||||
else:
|
self.airgen.generate_flights(
|
||||||
country = self.current_mission.country(self.game.enemy_country)
|
self.current_mission.country(self.game.enemy_country),
|
||||||
ato = self.game.red_ato
|
self.game.red_ato,
|
||||||
self.airgen.generate_flights(
|
self.groundobjectgen.runways
|
||||||
cp,
|
)
|
||||||
country,
|
|
||||||
ato,
|
|
||||||
self.groundobjectgen.runways
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate ground units on frontline everywhere
|
# Generate ground units on frontline everywhere
|
||||||
jtacs: List[JtacInfo] = []
|
jtacs: List[JtacInfo] = []
|
||||||
|
|||||||
@ -757,7 +757,7 @@ class AircraftConflictGenerator:
|
|||||||
for parking_slot in cp.airport.parking_slots:
|
for parking_slot in cp.airport.parking_slots:
|
||||||
parking_slot.unit_id = None
|
parking_slot.unit_id = None
|
||||||
|
|
||||||
def generate_flights(self, cp, country, ato: AirTaskingOrder,
|
def generate_flights(self, country, ato: AirTaskingOrder,
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
self.clear_parking_slots()
|
self.clear_parking_slots()
|
||||||
|
|
||||||
@ -768,7 +768,8 @@ class AircraftConflictGenerator:
|
|||||||
logging.info("Flight not generated: culled")
|
logging.info("Flight not generated: culled")
|
||||||
continue
|
continue
|
||||||
logging.info(f"Generating flight: {flight.unit_type}")
|
logging.info(f"Generating flight: {flight.unit_type}")
|
||||||
group = self.generate_planned_flight(cp, country, flight)
|
group = self.generate_planned_flight(flight.from_cp, country,
|
||||||
|
flight)
|
||||||
self.setup_flight_group(group, flight, flight.flight_type,
|
self.setup_flight_group(group, flight, flight.flight_type,
|
||||||
dynamic_runways)
|
dynamic_runways)
|
||||||
self.setup_group_activation_trigger(flight, group)
|
self.setup_group_activation_trigger(flight, group)
|
||||||
|
|||||||
@ -106,7 +106,7 @@ class BriefingGenerator(MissionInfoGenerator):
|
|||||||
aircraft = flight.aircraft_type
|
aircraft = flight.aircraft_type
|
||||||
flight_unit_name = db.unit_type_name(aircraft)
|
flight_unit_name = db.unit_type_name(aircraft)
|
||||||
self.description += "-" * 50 + "\n"
|
self.description += "-" * 50 + "\n"
|
||||||
self.description += f"{flight_unit_name} x {flight.size + 2}\n\n"
|
self.description += f"{flight_unit_name} x {flight.size}\n\n"
|
||||||
|
|
||||||
for i, wpt in enumerate(flight.waypoints):
|
for i, wpt in enumerate(flight.waypoints):
|
||||||
self.description += f"#{i + 1} -- {wpt.name} : {wpt.description}\n"
|
self.description += f"#{i + 1} -- {wpt.name} : {wpt.description}\n"
|
||||||
|
|||||||
@ -19,6 +19,10 @@ from gen.flights.ai_flight_planner_db import (
|
|||||||
SEAD_CAPABLE,
|
SEAD_CAPABLE,
|
||||||
STRIKE_CAPABLE,
|
STRIKE_CAPABLE,
|
||||||
)
|
)
|
||||||
|
from gen.flights.closestairfields import (
|
||||||
|
ClosestAirfields,
|
||||||
|
ObjectiveDistanceCache,
|
||||||
|
)
|
||||||
from gen.flights.flight import (
|
from gen.flights.flight import (
|
||||||
Flight,
|
Flight,
|
||||||
FlightType,
|
FlightType,
|
||||||
@ -37,29 +41,6 @@ if TYPE_CHECKING:
|
|||||||
from game.inventory import GlobalAircraftInventory
|
from game.inventory import GlobalAircraftInventory
|
||||||
|
|
||||||
|
|
||||||
class ClosestAirfields:
|
|
||||||
"""Precalculates which control points are closes to the given target."""
|
|
||||||
|
|
||||||
def __init__(self, target: MissionTarget,
|
|
||||||
all_control_points: List[ControlPoint]) -> None:
|
|
||||||
self.target = target
|
|
||||||
self.closest_airfields: List[ControlPoint] = sorted(
|
|
||||||
all_control_points, key=lambda c: self.target.distance_to(c)
|
|
||||||
)
|
|
||||||
|
|
||||||
def airfields_within(self, meters: int) -> Iterator[ControlPoint]:
|
|
||||||
"""Iterates over all airfields within the given range of the target.
|
|
||||||
|
|
||||||
Note that this iterates over *all* airfields, not just friendly
|
|
||||||
airfields.
|
|
||||||
"""
|
|
||||||
for cp in self.closest_airfields:
|
|
||||||
if cp.distance_to(self.target) < meters:
|
|
||||||
yield cp
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ProposedFlight:
|
class ProposedFlight:
|
||||||
"""A flight outline proposed by the mission planner.
|
"""A flight outline proposed by the mission planner.
|
||||||
@ -208,11 +189,6 @@ class ObjectiveFinder:
|
|||||||
def __init__(self, game: Game, is_player: bool) -> None:
|
def __init__(self, game: Game, is_player: bool) -> None:
|
||||||
self.game = game
|
self.game = game
|
||||||
self.is_player = is_player
|
self.is_player = is_player
|
||||||
# TODO: Cache globally at startup to avoid generating twice per turn?
|
|
||||||
self.closest_airfields: Dict[str, ClosestAirfields] = {
|
|
||||||
t.name: ClosestAirfields(t, self.game.theater.controlpoints)
|
|
||||||
for t in self.all_possible_targets()
|
|
||||||
}
|
|
||||||
|
|
||||||
def enemy_sams(self) -> Iterator[TheaterGroundObject]:
|
def enemy_sams(self) -> Iterator[TheaterGroundObject]:
|
||||||
"""Iterates over all enemy SAM sites."""
|
"""Iterates over all enemy SAM sites."""
|
||||||
@ -303,7 +279,7 @@ class ObjectiveFinder:
|
|||||||
CP.
|
CP.
|
||||||
"""
|
"""
|
||||||
for cp in self.friendly_control_points():
|
for cp in self.friendly_control_points():
|
||||||
airfields_in_proximity = self.closest_airfields[cp.name]
|
airfields_in_proximity = self.closest_airfields_to(cp)
|
||||||
airfields_in_threat_range = airfields_in_proximity.airfields_within(
|
airfields_in_threat_range = airfields_in_proximity.airfields_within(
|
||||||
self.AIRFIELD_THREAT_RANGE
|
self.AIRFIELD_THREAT_RANGE
|
||||||
)
|
)
|
||||||
@ -336,7 +312,7 @@ class ObjectiveFinder:
|
|||||||
|
|
||||||
def closest_airfields_to(self, location: MissionTarget) -> ClosestAirfields:
|
def closest_airfields_to(self, location: MissionTarget) -> ClosestAirfields:
|
||||||
"""Returns the closest airfields to the given location."""
|
"""Returns the closest airfields to the given location."""
|
||||||
return self.closest_airfields[location.name]
|
return ObjectiveDistanceCache.get_closest_airfields(location)
|
||||||
|
|
||||||
|
|
||||||
class CoalitionMissionPlanner:
|
class CoalitionMissionPlanner:
|
||||||
|
|||||||
51
gen/flights/closestairfields.py
Normal file
51
gen/flights/closestairfields.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""Objective adjacency lists."""
|
||||||
|
from typing import Dict, Iterator, List, Optional
|
||||||
|
|
||||||
|
from theater import ConflictTheater, ControlPoint, MissionTarget
|
||||||
|
|
||||||
|
|
||||||
|
class ClosestAirfields:
|
||||||
|
"""Precalculates which control points are closes to the given target."""
|
||||||
|
|
||||||
|
def __init__(self, target: MissionTarget,
|
||||||
|
all_control_points: List[ControlPoint]) -> None:
|
||||||
|
self.target = target
|
||||||
|
self.closest_airfields: List[ControlPoint] = sorted(
|
||||||
|
all_control_points, key=lambda c: self.target.distance_to(c)
|
||||||
|
)
|
||||||
|
|
||||||
|
def airfields_within(self, meters: int) -> Iterator[ControlPoint]:
|
||||||
|
"""Iterates over all airfields within the given range of the target.
|
||||||
|
|
||||||
|
Note that this iterates over *all* airfields, not just friendly
|
||||||
|
airfields.
|
||||||
|
"""
|
||||||
|
for cp in self.closest_airfields:
|
||||||
|
if cp.distance_to(self.target) < meters:
|
||||||
|
yield cp
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectiveDistanceCache:
|
||||||
|
theater: Optional[ConflictTheater] = None
|
||||||
|
closest_airfields: Dict[str, ClosestAirfields] = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_theater(cls, theater: ConflictTheater) -> None:
|
||||||
|
if cls.theater is not None:
|
||||||
|
cls.closest_airfields = {}
|
||||||
|
cls.theater = theater
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_closest_airfields(cls, location: MissionTarget) -> ClosestAirfields:
|
||||||
|
if cls.theater is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Call ObjectiveDistanceCache.set_theater before using"
|
||||||
|
)
|
||||||
|
|
||||||
|
if location.name not in cls.closest_airfields:
|
||||||
|
cls.closest_airfields[location.name] = ClosestAirfields(
|
||||||
|
location, cls.theater.controlpoints
|
||||||
|
)
|
||||||
|
return cls.closest_airfields[location.name]
|
||||||
@ -11,13 +11,15 @@ import logging
|
|||||||
import random
|
import random
|
||||||
from typing import List, Optional, TYPE_CHECKING
|
from typing import List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from game.data.doctrine import Doctrine, MODERN_DOCTRINE
|
|
||||||
from .flight import Flight, FlightType, FlightWaypointType, FlightWaypoint
|
|
||||||
from ..conflictgen import Conflict
|
|
||||||
from theater import ControlPoint, FrontLine, MissionTarget, TheaterGroundObject
|
|
||||||
from game.utils import nm_to_meter
|
|
||||||
from dcs.unit import Unit
|
from dcs.unit import Unit
|
||||||
|
|
||||||
|
from game.data.doctrine import Doctrine, MODERN_DOCTRINE
|
||||||
|
from game.utils import nm_to_meter
|
||||||
|
from theater import ControlPoint, FrontLine, MissionTarget, TheaterGroundObject
|
||||||
|
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
||||||
|
from .waypointbuilder import WaypointBuilder
|
||||||
|
from ..conflictgen import Conflict
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
|
|
||||||
@ -103,108 +105,54 @@ class FlightPlanBuilder:
|
|||||||
|
|
||||||
# TODO: Stop clobbering flight type.
|
# TODO: Stop clobbering flight type.
|
||||||
flight.flight_type = FlightType.STRIKE
|
flight.flight_type = FlightType.STRIKE
|
||||||
ascend = self.generate_ascend_point(flight.from_cp)
|
|
||||||
flight.points.append(ascend)
|
|
||||||
|
|
||||||
heading = flight.from_cp.position.heading_between_point(
|
heading = flight.from_cp.position.heading_between_point(
|
||||||
location.position
|
location.position
|
||||||
)
|
)
|
||||||
ingress_heading = heading - 180 + 25
|
ingress_heading = heading - 180 + 25
|
||||||
egress_heading = heading - 180 - 25
|
|
||||||
|
|
||||||
ingress_pos = location.position.point_from_heading(
|
ingress_pos = location.position.point_from_heading(
|
||||||
ingress_heading, self.doctrine.ingress_egress_distance
|
ingress_heading, self.doctrine.ingress_egress_distance
|
||||||
)
|
)
|
||||||
ingress_point = FlightWaypoint(
|
|
||||||
FlightWaypointType.INGRESS_STRIKE,
|
|
||||||
ingress_pos.x,
|
|
||||||
ingress_pos.y,
|
|
||||||
self.doctrine.ingress_altitude
|
|
||||||
)
|
|
||||||
ingress_point.pretty_name = "INGRESS on " + location.name
|
|
||||||
ingress_point.description = "INGRESS on " + location.name
|
|
||||||
ingress_point.name = "INGRESS"
|
|
||||||
flight.points.append(ingress_point)
|
|
||||||
|
|
||||||
if len(location.groups) > 0 and location.dcs_identifier == "AA":
|
|
||||||
for g in location.groups:
|
|
||||||
for j, u in enumerate(g.units):
|
|
||||||
point = FlightWaypoint(
|
|
||||||
FlightWaypointType.TARGET_POINT,
|
|
||||||
u.position.x,
|
|
||||||
u.position.y,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
point.description = (
|
|
||||||
f"STRIKE [{location.name}] : {u.type} #{j}"
|
|
||||||
)
|
|
||||||
point.pretty_name = (
|
|
||||||
f"STRIKE [{location.name}] : {u.type} #{j}"
|
|
||||||
)
|
|
||||||
point.name = f"{location.name} #{j}"
|
|
||||||
point.only_for_player = True
|
|
||||||
ingress_point.targets.append(location)
|
|
||||||
flight.points.append(point)
|
|
||||||
else:
|
|
||||||
if hasattr(location, "obj_name"):
|
|
||||||
buildings = self.game.theater.find_ground_objects_by_obj_name(
|
|
||||||
location.obj_name
|
|
||||||
)
|
|
||||||
for building in buildings:
|
|
||||||
if building.is_dead:
|
|
||||||
continue
|
|
||||||
|
|
||||||
point = FlightWaypoint(
|
|
||||||
FlightWaypointType.TARGET_POINT,
|
|
||||||
building.position.x,
|
|
||||||
building.position.y,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
point.description = (
|
|
||||||
f"STRIKE on {building.obj_name} {building.category} "
|
|
||||||
f"[{building.dcs_identifier}]"
|
|
||||||
)
|
|
||||||
point.pretty_name = (
|
|
||||||
f"STRIKE on {building.obj_name} {building.category} "
|
|
||||||
f"[{building.dcs_identifier}]"
|
|
||||||
)
|
|
||||||
point.name = building.obj_name
|
|
||||||
point.only_for_player = True
|
|
||||||
ingress_point.targets.append(building)
|
|
||||||
flight.points.append(point)
|
|
||||||
else:
|
|
||||||
point = FlightWaypoint(
|
|
||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
|
||||||
location.position.x,
|
|
||||||
location.position.y,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
point.description = "STRIKE on " + location.name
|
|
||||||
point.pretty_name = "STRIKE on " + location.name
|
|
||||||
point.name = location.name
|
|
||||||
point.only_for_player = True
|
|
||||||
ingress_point.targets.append(location)
|
|
||||||
flight.points.append(point)
|
|
||||||
|
|
||||||
|
egress_heading = heading - 180 - 25
|
||||||
egress_pos = location.position.point_from_heading(
|
egress_pos = location.position.point_from_heading(
|
||||||
egress_heading, self.doctrine.ingress_egress_distance
|
egress_heading, self.doctrine.ingress_egress_distance
|
||||||
)
|
)
|
||||||
egress_point = FlightWaypoint(
|
|
||||||
FlightWaypointType.EGRESS,
|
|
||||||
egress_pos.x,
|
|
||||||
egress_pos.y,
|
|
||||||
self.doctrine.egress_altitude
|
|
||||||
)
|
|
||||||
egress_point.name = "EGRESS"
|
|
||||||
egress_point.pretty_name = "EGRESS from " + location.name
|
|
||||||
egress_point.description = "EGRESS from " + location.name
|
|
||||||
flight.points.append(egress_point)
|
|
||||||
|
|
||||||
descend = self.generate_descend_point(flight.from_cp)
|
builder = WaypointBuilder(self.doctrine)
|
||||||
flight.points.append(descend)
|
builder.ascent(flight.from_cp)
|
||||||
|
builder.ingress_strike(ingress_pos, location)
|
||||||
|
|
||||||
rtb = self.generate_rtb_waypoint(flight.from_cp)
|
if len(location.groups) > 0 and location.dcs_identifier == "AA":
|
||||||
flight.points.append(rtb)
|
# TODO: Replace with DEAD?
|
||||||
|
# Strike missions on SEAD targets target units.
|
||||||
|
for g in location.groups:
|
||||||
|
for j, u in enumerate(g.units):
|
||||||
|
builder.strike_point(u, f"{u.type} #{j}", location)
|
||||||
|
else:
|
||||||
|
# TODO: Does this actually happen?
|
||||||
|
# ConflictTheater is built with the belief that multiple ground
|
||||||
|
# objects have the same name. If that's the case,
|
||||||
|
# TheaterGroundObject needs some refactoring because it behaves very
|
||||||
|
# differently for SAM sites than it does for strike targets.
|
||||||
|
buildings = self.game.theater.find_ground_objects_by_obj_name(
|
||||||
|
location.obj_name
|
||||||
|
)
|
||||||
|
for building in buildings:
|
||||||
|
if building.is_dead:
|
||||||
|
continue
|
||||||
|
|
||||||
|
builder.strike_point(
|
||||||
|
building,
|
||||||
|
f"{building.obj_name} {building.category}",
|
||||||
|
location
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.egress(egress_pos, location)
|
||||||
|
builder.rtb(flight.from_cp)
|
||||||
|
|
||||||
|
flight.points = builder.build()
|
||||||
|
|
||||||
def generate_barcap(self, flight: Flight, location: MissionTarget) -> None:
|
def generate_barcap(self, flight: Flight, location: MissionTarget) -> None:
|
||||||
"""Generate a BARCAP flight at a given location.
|
"""Generate a BARCAP flight at a given location.
|
||||||
@ -239,39 +187,11 @@ class FlightPlanBuilder:
|
|||||||
orbit0p = loc.point_from_heading(hdg - 90, radius)
|
orbit0p = loc.point_from_heading(hdg - 90, radius)
|
||||||
orbit1p = loc.point_from_heading(hdg + 90, radius)
|
orbit1p = loc.point_from_heading(hdg + 90, radius)
|
||||||
|
|
||||||
# Create points
|
builder = WaypointBuilder(self.doctrine)
|
||||||
ascend = self.generate_ascend_point(flight.from_cp)
|
builder.ascent(flight.from_cp)
|
||||||
flight.points.append(ascend)
|
builder.race_track(orbit0p, orbit1p, patrol_alt)
|
||||||
|
builder.rtb(flight.from_cp)
|
||||||
orbit0 = FlightWaypoint(
|
flight.points = builder.build()
|
||||||
FlightWaypointType.PATROL_TRACK,
|
|
||||||
orbit0p.x,
|
|
||||||
orbit0p.y,
|
|
||||||
patrol_alt
|
|
||||||
)
|
|
||||||
orbit0.name = "ORBIT 0"
|
|
||||||
orbit0.description = "Standby between this point and the next one"
|
|
||||||
orbit0.pretty_name = "Race-track start"
|
|
||||||
flight.points.append(orbit0)
|
|
||||||
|
|
||||||
orbit1 = FlightWaypoint(
|
|
||||||
FlightWaypointType.PATROL,
|
|
||||||
orbit1p.x,
|
|
||||||
orbit1p.y,
|
|
||||||
patrol_alt
|
|
||||||
)
|
|
||||||
orbit1.name = "ORBIT 1"
|
|
||||||
orbit1.description = "Standby between this point and the previous one"
|
|
||||||
orbit1.pretty_name = "Race-track end"
|
|
||||||
flight.points.append(orbit1)
|
|
||||||
|
|
||||||
orbit0.targets.append(location)
|
|
||||||
|
|
||||||
descend = self.generate_descend_point(flight.from_cp)
|
|
||||||
flight.points.append(descend)
|
|
||||||
|
|
||||||
rtb = self.generate_rtb_waypoint(flight.from_cp)
|
|
||||||
flight.points.append(rtb)
|
|
||||||
|
|
||||||
def generate_frontline_cap(self, flight: Flight,
|
def generate_frontline_cap(self, flight: Flight,
|
||||||
location: MissionTarget) -> None:
|
location: MissionTarget) -> None:
|
||||||
@ -309,40 +229,11 @@ class FlightPlanBuilder:
|
|||||||
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
|
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
|
||||||
|
|
||||||
# Create points
|
# Create points
|
||||||
ascend = self.generate_ascend_point(flight.from_cp)
|
builder = WaypointBuilder(self.doctrine)
|
||||||
flight.points.append(ascend)
|
builder.ascent(flight.from_cp)
|
||||||
|
builder.race_track(orbit0p, orbit1p, patrol_alt)
|
||||||
orbit0 = FlightWaypoint(
|
builder.rtb(flight.from_cp)
|
||||||
FlightWaypointType.PATROL_TRACK,
|
flight.points = builder.build()
|
||||||
orbit0p.x,
|
|
||||||
orbit0p.y,
|
|
||||||
patrol_alt
|
|
||||||
)
|
|
||||||
orbit0.name = "ORBIT 0"
|
|
||||||
orbit0.description = "Standby between this point and the next one"
|
|
||||||
orbit0.pretty_name = "Race-track start"
|
|
||||||
flight.points.append(orbit0)
|
|
||||||
|
|
||||||
orbit1 = FlightWaypoint(
|
|
||||||
FlightWaypointType.PATROL,
|
|
||||||
orbit1p.x,
|
|
||||||
orbit1p.y,
|
|
||||||
patrol_alt
|
|
||||||
)
|
|
||||||
orbit1.name = "ORBIT 1"
|
|
||||||
orbit1.description = "Standby between this point and the previous one"
|
|
||||||
orbit1.pretty_name = "Race-track end"
|
|
||||||
flight.points.append(orbit1)
|
|
||||||
|
|
||||||
# Note: Targets of PATROL TRACK waypoints are the points to be defended.
|
|
||||||
orbit0.targets.append(flight.from_cp)
|
|
||||||
orbit0.targets.append(center)
|
|
||||||
|
|
||||||
descend = self.generate_descend_point(flight.from_cp)
|
|
||||||
flight.points.append(descend)
|
|
||||||
|
|
||||||
rtb = self.generate_rtb_waypoint(flight.from_cp)
|
|
||||||
flight.points.append(rtb)
|
|
||||||
|
|
||||||
def generate_sead(self, flight: Flight, location: MissionTarget,
|
def generate_sead(self, flight: Flight, location: MissionTarget,
|
||||||
custom_targets: Optional[List[Unit]] = None) -> None:
|
custom_targets: Optional[List[Unit]] = None) -> None:
|
||||||
@ -359,33 +250,30 @@ class FlightPlanBuilder:
|
|||||||
if custom_targets is None:
|
if custom_targets is None:
|
||||||
custom_targets = []
|
custom_targets = []
|
||||||
|
|
||||||
flight.points = []
|
|
||||||
flight.flight_type = random.choice([FlightType.SEAD, FlightType.DEAD])
|
flight.flight_type = random.choice([FlightType.SEAD, FlightType.DEAD])
|
||||||
|
|
||||||
ascend = self.generate_ascend_point(flight.from_cp)
|
|
||||||
flight.points.append(ascend)
|
|
||||||
|
|
||||||
heading = flight.from_cp.position.heading_between_point(
|
heading = flight.from_cp.position.heading_between_point(
|
||||||
location.position
|
location.position
|
||||||
)
|
)
|
||||||
ingress_heading = heading - 180 + 25
|
ingress_heading = heading - 180 + 25
|
||||||
egress_heading = heading - 180 - 25
|
|
||||||
|
|
||||||
ingress_pos = location.position.point_from_heading(
|
ingress_pos = location.position.point_from_heading(
|
||||||
ingress_heading, self.doctrine.ingress_egress_distance
|
ingress_heading, self.doctrine.ingress_egress_distance
|
||||||
)
|
)
|
||||||
ingress_point = FlightWaypoint(
|
|
||||||
FlightWaypointType.INGRESS_SEAD,
|
|
||||||
ingress_pos.x,
|
|
||||||
ingress_pos.y,
|
|
||||||
self.doctrine.ingress_altitude
|
|
||||||
)
|
|
||||||
ingress_point.name = "INGRESS"
|
|
||||||
ingress_point.pretty_name = "INGRESS on " + location.name
|
|
||||||
ingress_point.description = "INGRESS on " + location.name
|
|
||||||
flight.points.append(ingress_point)
|
|
||||||
|
|
||||||
if len(custom_targets) > 0:
|
egress_heading = heading - 180 - 25
|
||||||
|
egress_pos = location.position.point_from_heading(
|
||||||
|
egress_heading, self.doctrine.ingress_egress_distance
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.doctrine)
|
||||||
|
builder.ascent(flight.from_cp)
|
||||||
|
builder.ingress_sead(ingress_pos, location)
|
||||||
|
|
||||||
|
# TODO: Unify these.
|
||||||
|
# There doesn't seem to be any reason to treat the UI fragged missions
|
||||||
|
# different from the automatic missions.
|
||||||
|
if custom_targets:
|
||||||
for target in custom_targets:
|
for target in custom_targets:
|
||||||
point = FlightWaypoint(
|
point = FlightWaypoint(
|
||||||
FlightWaypointType.TARGET_POINT,
|
FlightWaypointType.TARGET_POINT,
|
||||||
@ -395,55 +283,19 @@ class FlightPlanBuilder:
|
|||||||
)
|
)
|
||||||
point.alt_type = "RADIO"
|
point.alt_type = "RADIO"
|
||||||
if flight.flight_type == FlightType.DEAD:
|
if flight.flight_type == FlightType.DEAD:
|
||||||
point.description = "DEAD on " + target.type
|
builder.dead_point(target, location.name, location)
|
||||||
point.pretty_name = "DEAD on " + location.name
|
|
||||||
point.only_for_player = True
|
|
||||||
else:
|
else:
|
||||||
point.description = "SEAD on " + location.name
|
builder.sead_point(target, location.name, location)
|
||||||
point.pretty_name = "SEAD on " + location.name
|
|
||||||
point.only_for_player = True
|
|
||||||
flight.points.append(point)
|
|
||||||
ingress_point.targets.append(location)
|
|
||||||
ingress_point.targetGroup = location
|
|
||||||
else:
|
else:
|
||||||
point = FlightWaypoint(
|
|
||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
|
||||||
location.position.x,
|
|
||||||
location.position.y,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
point.alt_type = "RADIO"
|
|
||||||
if flight.flight_type == FlightType.DEAD:
|
if flight.flight_type == FlightType.DEAD:
|
||||||
point.description = "DEAD on " + location.name
|
builder.dead_area(location)
|
||||||
point.pretty_name = "DEAD on " + location.name
|
|
||||||
point.only_for_player = True
|
|
||||||
else:
|
else:
|
||||||
point.description = "SEAD on " + location.name
|
builder.sead_area(location)
|
||||||
point.pretty_name = "SEAD on " + location.name
|
|
||||||
point.only_for_player = True
|
|
||||||
ingress_point.targets.append(location)
|
|
||||||
ingress_point.targetGroup = location
|
|
||||||
flight.points.append(point)
|
|
||||||
|
|
||||||
egress_pos = location.position.point_from_heading(
|
builder.egress(egress_pos, location)
|
||||||
egress_heading, self.doctrine.ingress_egress_distance
|
builder.rtb(flight.from_cp)
|
||||||
)
|
|
||||||
egress_point = FlightWaypoint(
|
|
||||||
FlightWaypointType.EGRESS,
|
|
||||||
egress_pos.x,
|
|
||||||
egress_pos.y,
|
|
||||||
self.doctrine.egress_altitude
|
|
||||||
)
|
|
||||||
egress_point.name = "EGRESS"
|
|
||||||
egress_point.pretty_name = "EGRESS from " + location.name
|
|
||||||
egress_point.description = "EGRESS from " + location.name
|
|
||||||
flight.points.append(egress_point)
|
|
||||||
|
|
||||||
descend = self.generate_descend_point(flight.from_cp)
|
flight.points = builder.build()
|
||||||
flight.points.append(descend)
|
|
||||||
|
|
||||||
rtb = self.generate_rtb_waypoint(flight.from_cp)
|
|
||||||
flight.points.append(rtb)
|
|
||||||
|
|
||||||
def generate_cas(self, flight: Flight, location: MissionTarget) -> None:
|
def generate_cas(self, flight: Flight, location: MissionTarget) -> None:
|
||||||
"""Generate a CAS flight plan for the given target.
|
"""Generate a CAS flight plan for the given target.
|
||||||
@ -455,89 +307,36 @@ class FlightPlanBuilder:
|
|||||||
if not isinstance(location, FrontLine):
|
if not isinstance(location, FrontLine):
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
from_cp, location = location.control_points
|
|
||||||
is_helo = getattr(flight.unit_type, "helicopter", False)
|
is_helo = getattr(flight.unit_type, "helicopter", False)
|
||||||
cap_alt = 1000
|
cap_alt = 500 if is_helo else 1000
|
||||||
flight.points = []
|
|
||||||
flight.flight_type = FlightType.CAS
|
flight.flight_type = FlightType.CAS
|
||||||
|
|
||||||
ingress, heading, distance = Conflict.frontline_vector(
|
ingress, heading, distance = Conflict.frontline_vector(
|
||||||
from_cp, location, self.game.theater
|
location.control_points[0], location.control_points[1],
|
||||||
|
self.game.theater
|
||||||
)
|
)
|
||||||
center = ingress.point_from_heading(heading, distance / 2)
|
center = ingress.point_from_heading(heading, distance / 2)
|
||||||
egress = ingress.point_from_heading(heading, distance)
|
egress = ingress.point_from_heading(heading, distance)
|
||||||
|
|
||||||
ascend = self.generate_ascend_point(flight.from_cp)
|
builder = WaypointBuilder(self.doctrine)
|
||||||
if is_helo:
|
builder.ascent(flight.from_cp, is_helo)
|
||||||
cap_alt = 500
|
builder.ingress_cas(ingress, location)
|
||||||
ascend.alt = 500
|
builder.cas(center, cap_alt)
|
||||||
flight.points.append(ascend)
|
builder.egress(egress, location)
|
||||||
|
builder.rtb(flight.from_cp, is_helo)
|
||||||
|
|
||||||
ingress_point = FlightWaypoint(
|
flight.points = builder.build()
|
||||||
FlightWaypointType.INGRESS_CAS,
|
|
||||||
ingress.x,
|
|
||||||
ingress.y,
|
|
||||||
cap_alt
|
|
||||||
)
|
|
||||||
ingress_point.alt_type = "RADIO"
|
|
||||||
ingress_point.name = "INGRESS"
|
|
||||||
ingress_point.pretty_name = "INGRESS"
|
|
||||||
ingress_point.description = "Ingress into CAS area"
|
|
||||||
flight.points.append(ingress_point)
|
|
||||||
|
|
||||||
center_point = FlightWaypoint(
|
|
||||||
FlightWaypointType.CAS,
|
|
||||||
center.x,
|
|
||||||
center.y,
|
|
||||||
cap_alt
|
|
||||||
)
|
|
||||||
center_point.alt_type = "RADIO"
|
|
||||||
center_point.description = "Provide CAS"
|
|
||||||
center_point.name = "CAS"
|
|
||||||
center_point.pretty_name = "CAS"
|
|
||||||
flight.points.append(center_point)
|
|
||||||
|
|
||||||
egress_point = FlightWaypoint(
|
|
||||||
FlightWaypointType.EGRESS,
|
|
||||||
egress.x,
|
|
||||||
egress.y,
|
|
||||||
cap_alt
|
|
||||||
)
|
|
||||||
egress_point.alt_type = "RADIO"
|
|
||||||
egress_point.description = "Egress from CAS area"
|
|
||||||
egress_point.name = "EGRESS"
|
|
||||||
egress_point.pretty_name = "EGRESS"
|
|
||||||
flight.points.append(egress_point)
|
|
||||||
|
|
||||||
descend = self.generate_descend_point(flight.from_cp)
|
|
||||||
if is_helo:
|
|
||||||
descend.alt = 300
|
|
||||||
flight.points.append(descend)
|
|
||||||
|
|
||||||
rtb = self.generate_rtb_waypoint(flight.from_cp)
|
|
||||||
flight.points.append(rtb)
|
|
||||||
|
|
||||||
|
# TODO: Make a model for the waypoint builder and use that in the UI.
|
||||||
def generate_ascend_point(self, departure: ControlPoint) -> FlightWaypoint:
|
def generate_ascend_point(self, departure: ControlPoint) -> FlightWaypoint:
|
||||||
"""Generate ascend point.
|
"""Generate ascend point.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
departure: Departure airfield or carrier.
|
departure: Departure airfield or carrier.
|
||||||
"""
|
"""
|
||||||
ascend_heading = departure.heading
|
builder = WaypointBuilder(self.doctrine)
|
||||||
pos_ascend = departure.position.point_from_heading(
|
builder.ascent(departure)
|
||||||
ascend_heading, 10000
|
return builder.build()[0]
|
||||||
)
|
|
||||||
ascend = FlightWaypoint(
|
|
||||||
FlightWaypointType.ASCEND_POINT,
|
|
||||||
pos_ascend.x,
|
|
||||||
pos_ascend.y,
|
|
||||||
self.doctrine.pattern_altitude
|
|
||||||
)
|
|
||||||
ascend.name = "ASCEND"
|
|
||||||
ascend.alt_type = "RADIO"
|
|
||||||
ascend.description = "Ascend"
|
|
||||||
ascend.pretty_name = "Ascend"
|
|
||||||
return ascend
|
|
||||||
|
|
||||||
def generate_descend_point(self, arrival: ControlPoint) -> FlightWaypoint:
|
def generate_descend_point(self, arrival: ControlPoint) -> FlightWaypoint:
|
||||||
"""Generate approach/descend point.
|
"""Generate approach/descend point.
|
||||||
@ -545,21 +344,9 @@ class FlightPlanBuilder:
|
|||||||
Args:
|
Args:
|
||||||
arrival: Arrival airfield or carrier.
|
arrival: Arrival airfield or carrier.
|
||||||
"""
|
"""
|
||||||
ascend_heading = arrival.heading
|
builder = WaypointBuilder(self.doctrine)
|
||||||
descend = arrival.position.point_from_heading(
|
builder.descent(arrival)
|
||||||
ascend_heading - 180, 10000
|
return builder.build()[0]
|
||||||
)
|
|
||||||
descend = FlightWaypoint(
|
|
||||||
FlightWaypointType.DESCENT_POINT,
|
|
||||||
descend.x,
|
|
||||||
descend.y,
|
|
||||||
self.doctrine.pattern_altitude
|
|
||||||
)
|
|
||||||
descend.name = "DESCEND"
|
|
||||||
descend.alt_type = "RADIO"
|
|
||||||
descend.description = "Descend to pattern alt"
|
|
||||||
descend.pretty_name = "Descend to pattern alt"
|
|
||||||
return descend
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_rtb_waypoint(arrival: ControlPoint) -> FlightWaypoint:
|
def generate_rtb_waypoint(arrival: ControlPoint) -> FlightWaypoint:
|
||||||
@ -568,15 +355,6 @@ class FlightPlanBuilder:
|
|||||||
Args:
|
Args:
|
||||||
arrival: Arrival airfield or carrier.
|
arrival: Arrival airfield or carrier.
|
||||||
"""
|
"""
|
||||||
rtb = arrival.position
|
builder = WaypointBuilder(self.doctrine)
|
||||||
rtb = FlightWaypoint(
|
builder.land(arrival)
|
||||||
FlightWaypointType.LANDING_POINT,
|
return builder.build()[0]
|
||||||
rtb.x,
|
|
||||||
rtb.y,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
rtb.name = "LANDING"
|
|
||||||
rtb.alt_type = "RADIO"
|
|
||||||
rtb.description = "RTB"
|
|
||||||
rtb.pretty_name = "RTB"
|
|
||||||
return rtb
|
|
||||||
|
|||||||
270
gen/flights/waypointbuilder.py
Normal file
270
gen/flights/waypointbuilder.py
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
|
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 ControlPoint, MissionTarget, TheaterGroundObject
|
||||||
|
from .flight import FlightWaypoint, FlightWaypointType
|
||||||
|
|
||||||
|
|
||||||
|
class WaypointBuilder:
|
||||||
|
def __init__(self, doctrine: Doctrine) -> None:
|
||||||
|
self.doctrine = doctrine
|
||||||
|
self.waypoints: List[FlightWaypoint] = []
|
||||||
|
self.ingress_point: Optional[FlightWaypoint] = None
|
||||||
|
|
||||||
|
def build(self) -> List[FlightWaypoint]:
|
||||||
|
return self.waypoints
|
||||||
|
|
||||||
|
def ascent(self, departure: ControlPoint, is_helo: bool = False) -> None:
|
||||||
|
"""Create ascent waypoint for the given departure airfield or carrier.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
departure: Departure airfield or carrier.
|
||||||
|
"""
|
||||||
|
# TODO: Pick runway based on wind direction.
|
||||||
|
heading = departure.heading
|
||||||
|
position = departure.position.point_from_heading(
|
||||||
|
heading, nm_to_meter(5)
|
||||||
|
)
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.ASCEND_POINT,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
500 if is_helo else self.doctrine.pattern_altitude
|
||||||
|
)
|
||||||
|
waypoint.name = "ASCEND"
|
||||||
|
waypoint.alt_type = "RADIO"
|
||||||
|
waypoint.description = "Ascend"
|
||||||
|
waypoint.pretty_name = "Ascend"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def descent(self, arrival: ControlPoint, is_helo: bool = False) -> None:
|
||||||
|
"""Create descent waypoint for the given arrival airfield or carrier.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
arrival: Arrival airfield or carrier.
|
||||||
|
"""
|
||||||
|
# TODO: Pick runway based on wind direction.
|
||||||
|
# ControlPoint.heading is the departure heading.
|
||||||
|
heading = (arrival.heading + 180) % 360
|
||||||
|
position = arrival.position.point_from_heading(
|
||||||
|
heading, nm_to_meter(5)
|
||||||
|
)
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.DESCENT_POINT,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
300 if is_helo else self.doctrine.pattern_altitude
|
||||||
|
)
|
||||||
|
waypoint.name = "DESCEND"
|
||||||
|
waypoint.alt_type = "RADIO"
|
||||||
|
waypoint.description = "Descend to pattern altitude"
|
||||||
|
waypoint.pretty_name = "Ascend"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def land(self, arrival: ControlPoint) -> None:
|
||||||
|
"""Create descent waypoint for the given arrival airfield or carrier.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
arrival: Arrival airfield or carrier.
|
||||||
|
"""
|
||||||
|
position = arrival.position
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.LANDING_POINT,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
waypoint.name = "LANDING"
|
||||||
|
waypoint.alt_type = "RADIO"
|
||||||
|
waypoint.description = "Land"
|
||||||
|
waypoint.pretty_name = "Land"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def ingress_cas(self, position: Point, objective: MissionTarget) -> None:
|
||||||
|
self._ingress(FlightWaypointType.INGRESS_CAS, position, objective)
|
||||||
|
|
||||||
|
def ingress_sead(self, position: Point, objective: MissionTarget) -> None:
|
||||||
|
self._ingress(FlightWaypointType.INGRESS_SEAD, position, objective)
|
||||||
|
|
||||||
|
def ingress_strike(self, position: Point, objective: MissionTarget) -> None:
|
||||||
|
self._ingress(FlightWaypointType.INGRESS_STRIKE, position, objective)
|
||||||
|
|
||||||
|
def _ingress(self, ingress_type: FlightWaypointType, position: Point,
|
||||||
|
objective: MissionTarget) -> None:
|
||||||
|
if self.ingress_point is not None:
|
||||||
|
raise RuntimeError("A flight plan can have only one ingress point.")
|
||||||
|
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
ingress_type,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
self.doctrine.ingress_altitude
|
||||||
|
)
|
||||||
|
waypoint.pretty_name = "INGRESS on " + objective.name
|
||||||
|
waypoint.description = "INGRESS on " + objective.name
|
||||||
|
waypoint.name = "INGRESS"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
self.ingress_point = waypoint
|
||||||
|
|
||||||
|
def egress(self, position: Point, target: MissionTarget) -> None:
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.EGRESS,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
self.doctrine.ingress_altitude
|
||||||
|
)
|
||||||
|
waypoint.pretty_name = "EGRESS from " + target.name
|
||||||
|
waypoint.description = "EGRESS from " + target.name
|
||||||
|
waypoint.name = "EGRESS"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def dead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
|
location: MissionTarget) -> None:
|
||||||
|
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
|
||||||
|
location)
|
||||||
|
# TODO: Seems fishy.
|
||||||
|
self.ingress_point.targetGroup = location
|
||||||
|
|
||||||
|
def sead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
|
location: MissionTarget) -> None:
|
||||||
|
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
|
||||||
|
location)
|
||||||
|
# TODO: Seems fishy.
|
||||||
|
self.ingress_point.targetGroup = location
|
||||||
|
|
||||||
|
def strike_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
|
location: MissionTarget) -> None:
|
||||||
|
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
|
||||||
|
location)
|
||||||
|
|
||||||
|
def _target_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
|
description: str, location: MissionTarget) -> None:
|
||||||
|
if self.ingress_point is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"An ingress point must be added before target points."
|
||||||
|
)
|
||||||
|
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.TARGET_POINT,
|
||||||
|
target.position.x,
|
||||||
|
target.position.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
waypoint.description = description
|
||||||
|
waypoint.pretty_name = description
|
||||||
|
waypoint.name = name
|
||||||
|
waypoint.only_for_player = True
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
# TODO: This seems wrong, but it's what was there before.
|
||||||
|
self.ingress_point.targets.append(location)
|
||||||
|
|
||||||
|
def sead_area(self, target: MissionTarget) -> None:
|
||||||
|
self._target_area(f"SEAD on {target.name}", target)
|
||||||
|
# TODO: Seems fishy.
|
||||||
|
self.ingress_point.targetGroup = target
|
||||||
|
|
||||||
|
def dead_area(self, target: MissionTarget) -> None:
|
||||||
|
self._target_area(f"DEAD on {target.name}", target)
|
||||||
|
# TODO: Seems fishy.
|
||||||
|
self.ingress_point.targetGroup = target
|
||||||
|
|
||||||
|
def _target_area(self, name: str, location: MissionTarget) -> None:
|
||||||
|
if self.ingress_point is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"An ingress point must be added before target points."
|
||||||
|
)
|
||||||
|
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
|
location.position.x,
|
||||||
|
location.position.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
waypoint.description = name
|
||||||
|
waypoint.pretty_name = name
|
||||||
|
waypoint.name = name
|
||||||
|
waypoint.only_for_player = True
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
# TODO: This seems wrong, but it's what was there before.
|
||||||
|
self.ingress_point.targets.append(location)
|
||||||
|
|
||||||
|
def cas(self, position: Point, altitude: int) -> None:
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.CAS,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
altitude
|
||||||
|
)
|
||||||
|
waypoint.alt_type = "RADIO"
|
||||||
|
waypoint.description = "Provide CAS"
|
||||||
|
waypoint.name = "CAS"
|
||||||
|
waypoint.pretty_name = "CAS"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def race_track_start(self, position: Point, altitude: int) -> None:
|
||||||
|
"""Creates a racetrack start waypoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position: Position of the waypoint.
|
||||||
|
altitude: Altitude of the racetrack in meters.
|
||||||
|
"""
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.PATROL_TRACK,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
altitude
|
||||||
|
)
|
||||||
|
waypoint.name = "RACETRACK START"
|
||||||
|
waypoint.description = "Orbit between this point and the next point"
|
||||||
|
waypoint.pretty_name = "Race-track start"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
# TODO: Does this actually do anything?
|
||||||
|
# orbit0.targets.append(location)
|
||||||
|
# Note: Targets of PATROL TRACK waypoints are the points to be defended.
|
||||||
|
# orbit0.targets.append(flight.from_cp)
|
||||||
|
# orbit0.targets.append(center)
|
||||||
|
|
||||||
|
def race_track_end(self, position: Point, altitude: int) -> None:
|
||||||
|
"""Creates a racetrack end waypoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position: Position of the waypoint.
|
||||||
|
altitude: Altitude of the racetrack in meters.
|
||||||
|
"""
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.PATROL,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
altitude
|
||||||
|
)
|
||||||
|
waypoint.name = "RACETRACK END"
|
||||||
|
waypoint.description = "Orbit between this point and the previous point"
|
||||||
|
waypoint.pretty_name = "Race-track end"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def race_track(self, start: Point, end: Point, altitude: int) -> None:
|
||||||
|
"""Creates two waypoint for a racetrack orbit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start: The beginning racetrack waypoint.
|
||||||
|
end: The ending racetrack waypoint.
|
||||||
|
altitude: The racetrack altitude.
|
||||||
|
"""
|
||||||
|
self.race_track_start(start, altitude)
|
||||||
|
self.race_track_end(end, altitude)
|
||||||
|
|
||||||
|
def rtb(self, arrival: ControlPoint, is_helo: bool = False) -> None:
|
||||||
|
"""Creates descent ant landing waypoints for the given control point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
arrival: Arrival airfield or carrier.
|
||||||
|
"""
|
||||||
|
self.descent(arrival, is_helo)
|
||||||
|
self.land(arrival)
|
||||||
Loading…
x
Reference in New Issue
Block a user