mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Clean some cruft out of FlightPlanBuilder.
This commit is contained in:
parent
6bbe583e82
commit
9cd511da3d
@ -3,10 +3,6 @@ from __future__ import annotations
|
|||||||
from typing import Any, TYPE_CHECKING, Type
|
from typing import Any, TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.ato import FlightType
|
from game.ato import FlightType
|
||||||
from game.ato.closestairfields import ObjectiveDistanceCache
|
|
||||||
from game.data.doctrine import Doctrine
|
|
||||||
from game.flightplan import IpZoneGeometry, JoinZoneGeometry
|
|
||||||
from game.flightplan.refuelzonegeometry import RefuelZoneGeometry
|
|
||||||
from .aewc import AewcFlightPlan
|
from .aewc import AewcFlightPlan
|
||||||
from .airassault import AirAssaultFlightPlan
|
from .airassault import AirAssaultFlightPlan
|
||||||
from .airlift import AirliftFlightPlan
|
from .airlift import AirliftFlightPlan
|
||||||
@ -27,13 +23,12 @@ from .strike import StrikeFlightPlan
|
|||||||
from .sweep import SweepFlightPlan
|
from .sweep import SweepFlightPlan
|
||||||
from .tarcap import TarCapFlightPlan
|
from .tarcap import TarCapFlightPlan
|
||||||
from .theaterrefueling import TheaterRefuelingFlightPlan
|
from .theaterrefueling import TheaterRefuelingFlightPlan
|
||||||
from .waypointbuilder import WaypointBuilder
|
from ..packagewaypoints import PackageWaypoints
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.ato import Flight, FlightWaypoint, Package
|
from game.ato import Flight, Package
|
||||||
from game.coalition import Coalition
|
from game.coalition import Coalition
|
||||||
from game.theater import ConflictTheater, ControlPoint, FrontLine
|
from game.theater import ConflictTheater, FrontLine
|
||||||
from game.threatzones import ThreatZones
|
|
||||||
|
|
||||||
|
|
||||||
class FlightPlanBuilder:
|
class FlightPlanBuilder:
|
||||||
@ -57,14 +52,6 @@ class FlightPlanBuilder:
|
|||||||
def is_player(self) -> bool:
|
def is_player(self) -> bool:
|
||||||
return self.coalition.player
|
return self.coalition.player
|
||||||
|
|
||||||
@property
|
|
||||||
def doctrine(self) -> Doctrine:
|
|
||||||
return self.coalition.doctrine
|
|
||||||
|
|
||||||
@property
|
|
||||||
def threat_zones(self) -> ThreatZones:
|
|
||||||
return self.coalition.opponent.threat_zone
|
|
||||||
|
|
||||||
def populate_flight_plan(self, flight: Flight) -> None:
|
def populate_flight_plan(self, flight: Flight) -> None:
|
||||||
"""Creates a default flight plan for the given mission."""
|
"""Creates a default flight plan for the given mission."""
|
||||||
if flight not in self.package.flights:
|
if flight not in self.package.flights:
|
||||||
@ -74,7 +61,9 @@ class FlightPlanBuilder:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if self.package.waypoints is None:
|
if self.package.waypoints is None:
|
||||||
self.regenerate_package_waypoints()
|
self.package.waypoints = PackageWaypoints.create(
|
||||||
|
self.package, self.coalition
|
||||||
|
)
|
||||||
flight.flight_plan = self.generate_flight_plan(flight)
|
flight.flight_plan = self.generate_flight_plan(flight)
|
||||||
except NavMeshError as ex:
|
except NavMeshError as ex:
|
||||||
color = "blue" if self.is_player else "red"
|
color = "blue" if self.is_player else "red"
|
||||||
@ -121,76 +110,3 @@ class FlightPlanBuilder:
|
|||||||
)
|
)
|
||||||
layout = plan_type.builder_type()(flight, self.theater).build()
|
layout = plan_type.builder_type()(flight, self.theater).build()
|
||||||
return plan_type(flight, layout)
|
return plan_type(flight, layout)
|
||||||
|
|
||||||
def regenerate_flight_plans(self) -> None:
|
|
||||||
new_flights: list[Flight] = []
|
|
||||||
for old_flight in self.package.flights:
|
|
||||||
old_flight.flight_plan = self.generate_flight_plan(old_flight)
|
|
||||||
new_flights.append(old_flight)
|
|
||||||
self.package.flights = new_flights
|
|
||||||
|
|
||||||
def regenerate_package_waypoints(self) -> None:
|
|
||||||
from game.ato.packagewaypoints import PackageWaypoints
|
|
||||||
|
|
||||||
package_airfield = self.package_airfield()
|
|
||||||
|
|
||||||
# Start by picking the best IP for the attack.
|
|
||||||
ingress_point = IpZoneGeometry(
|
|
||||||
self.package.target.position,
|
|
||||||
package_airfield.position,
|
|
||||||
self.coalition,
|
|
||||||
).find_best_ip()
|
|
||||||
|
|
||||||
join_point = JoinZoneGeometry(
|
|
||||||
self.package.target.position,
|
|
||||||
package_airfield.position,
|
|
||||||
ingress_point,
|
|
||||||
self.coalition,
|
|
||||||
).find_best_join_point()
|
|
||||||
|
|
||||||
refuel_point = RefuelZoneGeometry(
|
|
||||||
package_airfield.position,
|
|
||||||
join_point,
|
|
||||||
self.coalition,
|
|
||||||
).find_best_refuel_point()
|
|
||||||
|
|
||||||
# And the split point based on the best route from the IP. Since that's no
|
|
||||||
# different than the best route *to* the IP, this is the same as the join point.
|
|
||||||
# TODO: Estimate attack completion point based on the IP and split from there?
|
|
||||||
self.package.waypoints = PackageWaypoints(
|
|
||||||
WaypointBuilder.perturb(join_point),
|
|
||||||
ingress_point,
|
|
||||||
WaypointBuilder.perturb(join_point),
|
|
||||||
refuel_point,
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: Make a model for the waypoint builder and use that in the UI.
|
|
||||||
def generate_rtb_waypoint(
|
|
||||||
self, flight: Flight, arrival: ControlPoint
|
|
||||||
) -> FlightWaypoint:
|
|
||||||
"""Generate RTB landing point.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
flight: The flight to generate the landing waypoint for.
|
|
||||||
arrival: Arrival airfield or carrier.
|
|
||||||
"""
|
|
||||||
builder = WaypointBuilder(flight, self.coalition)
|
|
||||||
return builder.land(arrival)
|
|
||||||
|
|
||||||
def package_airfield(self) -> ControlPoint:
|
|
||||||
# We'll always have a package, but if this is being planned via the UI
|
|
||||||
# it could be the first flight in the package.
|
|
||||||
if not self.package.flights:
|
|
||||||
raise PlanningError(
|
|
||||||
"Cannot determine source airfield for package with no flights"
|
|
||||||
)
|
|
||||||
|
|
||||||
# The package airfield is either the flight's airfield (when there is no
|
|
||||||
# package) or the closest airfield to the objective that is the
|
|
||||||
# departure airfield for some flight in the package.
|
|
||||||
cache = ObjectiveDistanceCache.get_closest_airfields(self.package.target)
|
|
||||||
for airfield in cache.operational_airfields:
|
|
||||||
for flight in self.package.flights:
|
|
||||||
if flight.departure == airfield:
|
|
||||||
return airfield
|
|
||||||
raise PlanningError("Could not find any airfield assigned to this package")
|
|
||||||
|
|||||||
@ -6,16 +6,17 @@ from dataclasses import dataclass, field
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Dict, List, Optional, TYPE_CHECKING
|
from typing import Dict, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from .flightplans.formation import FormationFlightPlan
|
|
||||||
from game.db import Database
|
from game.db import Database
|
||||||
from game.utils import Speed
|
from game.utils import Speed
|
||||||
|
from .closestairfields import ObjectiveDistanceCache
|
||||||
from .flight import Flight
|
from .flight import Flight
|
||||||
|
from .flightplans.formation import FormationFlightPlan
|
||||||
from .flighttype import FlightType
|
from .flighttype import FlightType
|
||||||
from .packagewaypoints import PackageWaypoints
|
from .packagewaypoints import PackageWaypoints
|
||||||
from .traveltime import TotEstimator
|
from .traveltime import TotEstimator
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.theater import MissionTarget
|
from game.theater import ControlPoint, MissionTarget
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -193,6 +194,24 @@ class Package:
|
|||||||
return "OCA Strike"
|
return "OCA Strike"
|
||||||
return str(task)
|
return str(task)
|
||||||
|
|
||||||
|
def departure_closest_to_target(self) -> ControlPoint:
|
||||||
|
# We'll always have a package, but if this is being planned via the UI
|
||||||
|
# it could be the first flight in the package.
|
||||||
|
if not self.flights:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Cannot determine source airfield for package with no flights"
|
||||||
|
)
|
||||||
|
|
||||||
|
# The package airfield is either the flight's airfield (when there is no
|
||||||
|
# package) or the closest airfield to the objective that is the
|
||||||
|
# departure airfield for some flight in the package.
|
||||||
|
cache = ObjectiveDistanceCache.get_closest_airfields(self.target)
|
||||||
|
for airfield in cache.operational_airfields:
|
||||||
|
for flight in self.flights:
|
||||||
|
if flight.departure == airfield:
|
||||||
|
return airfield
|
||||||
|
raise RuntimeError("Could not find any airfield assigned to this package")
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
# TODO: Far from perfect. Number packages?
|
# TODO: Far from perfect. Number packages?
|
||||||
return hash(self.target.name)
|
return hash(self.target.name)
|
||||||
|
|||||||
@ -1,8 +1,18 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from dcs import Point
|
from dcs import Point
|
||||||
|
|
||||||
|
from game.ato.flightplans.waypointbuilder import WaypointBuilder
|
||||||
|
from game.flightplan import IpZoneGeometry, JoinZoneGeometry
|
||||||
|
from game.flightplan.refuelzonegeometry import RefuelZoneGeometry
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game.ato import Package
|
||||||
|
from game.coalition import Coalition
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class PackageWaypoints:
|
class PackageWaypoints:
|
||||||
@ -10,3 +20,37 @@ class PackageWaypoints:
|
|||||||
ingress: Point
|
ingress: Point
|
||||||
split: Point
|
split: Point
|
||||||
refuel: Point
|
refuel: Point
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create(package: Package, coalition: Coalition) -> PackageWaypoints:
|
||||||
|
origin = package.departure_closest_to_target()
|
||||||
|
|
||||||
|
# Start by picking the best IP for the attack.
|
||||||
|
ingress_point = IpZoneGeometry(
|
||||||
|
package.target.position,
|
||||||
|
origin.position,
|
||||||
|
coalition,
|
||||||
|
).find_best_ip()
|
||||||
|
|
||||||
|
join_point = JoinZoneGeometry(
|
||||||
|
package.target.position,
|
||||||
|
origin.position,
|
||||||
|
ingress_point,
|
||||||
|
coalition,
|
||||||
|
).find_best_join_point()
|
||||||
|
|
||||||
|
refuel_point = RefuelZoneGeometry(
|
||||||
|
origin.position,
|
||||||
|
join_point,
|
||||||
|
coalition,
|
||||||
|
).find_best_refuel_point()
|
||||||
|
|
||||||
|
# And the split point based on the best route from the IP. Since that's no
|
||||||
|
# different than the best route *to* the IP, this is the same as the join point.
|
||||||
|
# TODO: Estimate attack completion point based on the IP and split from there?
|
||||||
|
return PackageWaypoints(
|
||||||
|
WaypointBuilder.perturb(join_point),
|
||||||
|
ingress_point,
|
||||||
|
WaypointBuilder.perturb(join_point),
|
||||||
|
refuel_point,
|
||||||
|
)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Iterable, List, Optional, Any
|
from typing import Iterable, List, Optional
|
||||||
|
|
||||||
from PySide2.QtCore import Signal
|
from PySide2.QtCore import Signal
|
||||||
from PySide2.QtWidgets import (
|
from PySide2.QtWidgets import (
|
||||||
@ -14,10 +14,10 @@ from PySide2.QtWidgets import (
|
|||||||
from game import Game
|
from game import Game
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
from game.ato.flightplans.custom import CustomFlightPlan, CustomLayout
|
from game.ato.flightplans.custom import CustomFlightPlan, CustomLayout
|
||||||
from game.ato.flightplans.flightplan import FlightPlan
|
|
||||||
from game.ato.flightplans.flightplanbuilder import FlightPlanBuilder
|
from game.ato.flightplans.flightplanbuilder import FlightPlanBuilder
|
||||||
from game.ato.flightplans.formationattack import FormationAttackFlightPlan
|
from game.ato.flightplans.formationattack import FormationAttackFlightPlan
|
||||||
from game.ato.flightplans.planningerror import PlanningError
|
from game.ato.flightplans.planningerror import PlanningError
|
||||||
|
from game.ato.flightplans.waypointbuilder import WaypointBuilder
|
||||||
from game.ato.flighttype import FlightType
|
from game.ato.flighttype import FlightType
|
||||||
from game.ato.flightwaypoint import FlightWaypoint
|
from game.ato.flightwaypoint import FlightWaypoint
|
||||||
from game.ato.loadouts import Loadout
|
from game.ato.loadouts import Loadout
|
||||||
@ -139,7 +139,7 @@ class QFlightWaypointTab(QFrame):
|
|||||||
self.on_change()
|
self.on_change()
|
||||||
|
|
||||||
def on_rtb_waypoint(self):
|
def on_rtb_waypoint(self):
|
||||||
rtb = self.planner.generate_rtb_waypoint(self.flight, self.flight.from_cp)
|
rtb = WaypointBuilder(self.flight, self.coalition).land(self.flight.arrival)
|
||||||
self.degrade_to_custom_flight_plan()
|
self.degrade_to_custom_flight_plan()
|
||||||
assert isinstance(self.flight.flight_plan, CustomFlightPlan)
|
assert isinstance(self.flight.flight_plan, CustomFlightPlan)
|
||||||
self.flight.flight_plan.layout.custom_waypoints.append(rtb)
|
self.flight.flight_plan.layout.custom_waypoints.append(rtb)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user