Add a real CAS ingress point.

Putting the ingress point directly on one end of the FLOT means that AI
flights won't start searching and engaging targets until they reach that
point. If the front line has advanced toward the flight's departure
airfield, it might overfly targets on its way to the IP.

Instead, place an IP for CAS the same way we place any other IP. The AI
will fly to that and start searching from there.

This also:

* Removes the midpoint waypoint, since it didn't serve any real purpose
* Names the FLOT boundary waypoints for what they actually are

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2231.
This commit is contained in:
Dan Albert 2023-08-10 00:03:30 -07:00 committed by Raffson
parent c50dcaaafb
commit a2630fc75f
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
24 changed files with 92 additions and 51 deletions

View File

@ -213,6 +213,7 @@ BAI/ANTISHIP/DEAD/STRIKE/BARCAP/CAS/OCA/AIR-ASSAULT (main) missions
* **[Data]** Added support for the ARA Veinticinco de Mayo.
* **[Data]** Changed display name of the AI-only F-15E Strike Eagle for clarity.
* **[Flight Planning]** Improved IP selection for targets that are near the center of a threat zone.
* **[Flight Planning]** Moved CAS ingress point off the front line so that the AI begins their target search earlier.
* **[Flight Planning]** Loadouts and aircraft properties can now be set per-flight member. Warning: AI flights should not use mixed loadouts.
* **[Flight Planning]** Laser codes that are pre-assigned to weapons at mission start can now be chosen from a list in the loadout UI. This does not affect the aircraft's TGP, just the weapons. Currently only implemented for the F-15E S4+ and F-16C.
* **[Mission Generation]** Configured target and initial points for F-15E S4+.

View File

@ -92,5 +92,5 @@ class Builder(IBuilder[AewcFlightPlan, PatrollingLayout]):
bullseye=builder.bullseye(),
)
def build(self) -> AewcFlightPlan:
def build(self, dump_debug_info: bool = False) -> AewcFlightPlan:
return AewcFlightPlan(self.flight, self.layout())

View File

@ -172,7 +172,7 @@ class Builder(FormationAttackBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
refuel=None,
)
def build(self) -> AirAssaultFlightPlan:
def build(self, dump_debug_info: bool = False) -> AirAssaultFlightPlan:
return AirAssaultFlightPlan(self.flight, self.layout())
def _generate_ctld_pickup(self) -> Point:

View File

@ -169,7 +169,7 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
bullseye=builder.bullseye(),
)
def build(self) -> AirliftFlightPlan:
def build(self, dump_debug_info: bool = False) -> AirliftFlightPlan:
return AirliftFlightPlan(self.flight, self.layout())
def _generate_ctld_pickup(self) -> Point:

View File

@ -41,5 +41,5 @@ class Builder(FormationAttackBuilder[AntiShipFlightPlan, FormationAttackLayout])
def anti_ship_targets_for_tgo(tgo: NavalGroundObject) -> list[StrikeTarget]:
return [StrikeTarget(f"{g.group_name} at {tgo.name}", g) for g in tgo.groups]
def build(self) -> AntiShipFlightPlan:
def build(self, dump_debug_info: bool = False) -> AntiShipFlightPlan:
return AntiShipFlightPlan(self.flight, self.layout())

View File

@ -39,5 +39,5 @@ class Builder(FormationAttackBuilder[BaiFlightPlan, FormationAttackLayout]):
return self._build(FlightWaypointType.INGRESS_BAI, targets)
def build(self) -> BaiFlightPlan:
def build(self, dump_debug_info: bool = False) -> BaiFlightPlan:
return BaiFlightPlan(self.flight, self.layout())

View File

@ -66,5 +66,5 @@ class Builder(CapBuilder[BarCapFlightPlan, PatrollingLayout]):
bullseye=builder.bullseye(),
)
def build(self) -> BarCapFlightPlan:
def build(self, dump_debug_info: bool = False) -> BarCapFlightPlan:
return BarCapFlightPlan(self.flight, self.layout())

View File

@ -6,13 +6,16 @@ from datetime import timedelta
from typing import TYPE_CHECKING, Type
from game.theater import FrontLine
from game.utils import Distance, Speed, kph, feet, nautical_miles
from game.utils import Distance, Speed, kph, dcs_to_shapely_point
from game.utils import feet, nautical_miles
from .ibuilder import IBuilder
from .invalidobjectivelocation import InvalidObjectiveLocation
from .patrolling import PatrollingFlightPlan, PatrollingLayout
from .uizonedisplay import UiZone, UiZoneDisplay
from .waypointbuilder import WaypointBuilder
from ..flightwaypointtype import FlightWaypointType
from ...flightplan.ipsolver import IpSolver
from ...persistence.paths import waypoint_debug_directory
if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@ -20,13 +23,13 @@ if TYPE_CHECKING:
@dataclass
class CasLayout(PatrollingLayout):
target: FlightWaypoint
ingress: FlightWaypoint
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield from self.nav_to
yield self.ingress
yield self.patrol_start
yield self.target
yield self.patrol_end
yield from self.nav_from
yield self.arrival
@ -59,7 +62,7 @@ class CasFlightPlan(PatrollingFlightPlan[CasLayout], UiZoneDisplay):
@property
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
return {self.layout.patrol_start, self.layout.target, self.layout.patrol_end}
return {self.layout.ingress, self.layout.patrol_start, self.layout.patrol_end}
def request_escort_at(self) -> FlightWaypoint | None:
return self.layout.patrol_start
@ -68,14 +71,17 @@ class CasFlightPlan(PatrollingFlightPlan[CasLayout], UiZoneDisplay):
return self.layout.patrol_end
def ui_zone(self) -> UiZone:
midpoint = (
self.layout.patrol_start.position + self.layout.patrol_end.position
) / 2
return UiZone(
[self.layout.target.position],
[midpoint],
self.engagement_distance,
)
class Builder(IBuilder[CasFlightPlan, CasLayout]):
def layout(self) -> CasLayout:
def layout(self, dump_debug_info: bool) -> CasLayout:
location = self.package.target
if not isinstance(location, FrontLine):
@ -92,10 +98,10 @@ class Builder(IBuilder[CasFlightPlan, CasLayout]):
center = bounds.center
egress = bounds.right_position
ingress_distance = ingress.distance_to_point(self.flight.departure.position)
egress_distance = egress.distance_to_point(self.flight.departure.position)
if egress_distance < ingress_distance:
ingress, egress = egress, ingress
start_distance = ingress.distance_to_point(self.flight.departure.position)
end_distance = egress.distance_to_point(self.flight.departure.position)
if end_distance < start_distance:
patrol_start, patrol_end = ingress, egress
builder = WaypointBuilder(self.flight)
@ -105,31 +111,65 @@ class Builder(IBuilder[CasFlightPlan, CasLayout]):
if not is_helo
else feet(self.coalition.game.settings.heli_combat_alt_agl)
)
use_agl_ingress_egress = is_helo
use_agl_patrol_altitude = is_helo
ip_solver = IpSolver(
dcs_to_shapely_point(self.flight.departure.position),
dcs_to_shapely_point(ingress),
self.doctrine,
self.threat_zones.all,
)
ip_solver.set_debug_properties(
waypoint_debug_directory() / "IP", self.theater.terrain
)
ingress_point_shapely = ip_solver.solve()
if dump_debug_info:
ip_solver.dump_debug_info()
ingress_point = ingress.new_in_same_map(
ingress_point_shapely.x, ingress_point_shapely.y
)
patrol_start_waypoint = builder.nav(
ingress, ingress_egress_altitude, use_agl_patrol_altitude
)
patrol_start_waypoint.name = "FLOT START"
patrol_start_waypoint.pretty_name = "FLOT start"
patrol_start_waypoint.description = "FLOT boundary"
patrol_end_waypoint = builder.nav(
egress, ingress_egress_altitude, use_agl_patrol_altitude
)
patrol_end_waypoint.name = "FLOT END"
patrol_end_waypoint.pretty_name = "FLOT end"
patrol_end_waypoint.description = "FLOT boundary"
ingress = builder.ingress(
FlightWaypointType.INGRESS_CAS, ingress_point, location
)
ingress.description = f"Ingress to provide CAS at {location}"
return CasLayout(
departure=builder.takeoff(self.flight.departure),
nav_to=builder.nav_path(
self.flight.departure.position,
ingress,
ingress_egress_altitude,
use_agl_ingress_egress,
ingress_point,
patrol_altitude,
use_agl_patrol_altitude,
),
nav_from=builder.nav_path(
egress,
patrol_end,
self.flight.arrival.position,
ingress_egress_altitude,
use_agl_ingress_egress,
patrol_altitude,
use_agl_patrol_altitude,
),
patrol_start=builder.ingress(
FlightWaypointType.INGRESS_CAS, ingress, location
),
target=builder.cas(center),
patrol_end=builder.egress(egress, location),
ingress=ingress,
patrol_start=patrol_start_waypoint,
patrol_end=patrol_end_waypoint,
arrival=builder.land(self.flight.arrival),
divert=builder.divert(self.flight.divert),
bullseye=builder.bullseye(),
)
def build(self) -> CasFlightPlan:
return CasFlightPlan(self.flight, self.layout())
def build(self, dump_debug_info: bool = False) -> CasFlightPlan:
return CasFlightPlan(self.flight, self.layout(dump_debug_info))

View File

@ -72,5 +72,5 @@ class Builder(IBuilder[CustomFlightPlan, CustomLayout]):
builder = WaypointBuilder(self.flight)
return CustomLayout(builder.takeoff(self.flight.departure), self.waypoints)
def build(self) -> CustomFlightPlan:
def build(self, dump_debug_info: bool = False) -> CustomFlightPlan:
return CustomFlightPlan(self.flight, self.layout())

View File

@ -37,5 +37,5 @@ class Builder(FormationAttackBuilder[DeadFlightPlan, FormationAttackLayout]):
return self._build(FlightWaypointType.INGRESS_DEAD)
def build(self) -> DeadFlightPlan:
def build(self, dump_debug_info: bool = False) -> DeadFlightPlan:
return DeadFlightPlan(self.flight, self.layout())

View File

@ -117,5 +117,5 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
bullseye=builder.bullseye(),
)
def build(self) -> EscortFlightPlan:
def build(self, dump_debug_info: bool = False) -> EscortFlightPlan:
return EscortFlightPlan(self.flight, self.layout())

View File

@ -78,5 +78,5 @@ class Builder(IBuilder[FerryFlightPlan, FerryLayout]):
nav_from=[],
)
def build(self) -> FerryFlightPlan:
def build(self, dump_debug_info: bool = False) -> FerryFlightPlan:
return FerryFlightPlan(self.flight, self.layout())

View File

@ -35,7 +35,7 @@ class IBuilder(ABC, Generic[FlightPlanT, LayoutT]):
def regenerate(self, dump_debug_info: bool = False) -> None:
try:
self._generate_package_waypoints_if_needed(dump_debug_info)
self._flight_plan = self.build()
self._flight_plan = self.build(dump_debug_info)
except NavMeshError as ex:
color = "blue" if self.flight.squadron.player else "red"
raise PlanningError(
@ -59,11 +59,7 @@ class IBuilder(ABC, Generic[FlightPlanT, LayoutT]):
return self.flight.departure.theater
@abstractmethod
def layout(self) -> LayoutT:
...
@abstractmethod
def build(self) -> FlightPlanT:
def build(self, dump_debug_info: bool = False) -> FlightPlanT:
...
@property

View File

@ -32,5 +32,5 @@ class Builder(FormationAttackBuilder[OcaAircraftFlightPlan, FormationAttackLayou
return self._build(FlightWaypointType.INGRESS_OCA_AIRCRAFT)
def build(self) -> OcaAircraftFlightPlan:
def build(self, dump_debug_info: bool = False) -> OcaAircraftFlightPlan:
return OcaAircraftFlightPlan(self.flight, self.layout())

View File

@ -32,5 +32,5 @@ class Builder(FormationAttackBuilder[OcaRunwayFlightPlan, FormationAttackLayout]
return self._build(FlightWaypointType.INGRESS_OCA_RUNWAY)
def build(self) -> OcaRunwayFlightPlan:
def build(self, dump_debug_info: bool = False) -> OcaRunwayFlightPlan:
return OcaRunwayFlightPlan(self.flight, self.layout())

View File

@ -121,5 +121,5 @@ class Builder(IBuilder[PackageRefuelingFlightPlan, PatrollingLayout]):
bullseye=builder.bullseye(),
)
def build(self) -> PackageRefuelingFlightPlan:
def build(self, dump_debug_info: bool = False) -> PackageRefuelingFlightPlan:
return PackageRefuelingFlightPlan(self.flight, self.layout())

View File

@ -89,5 +89,5 @@ class Builder(IBuilder[RtbFlightPlan, RtbLayout]):
nav_from=[],
)
def build(self) -> RtbFlightPlan:
def build(self, dump_debug_info: bool = False) -> RtbFlightPlan:
return RtbFlightPlan(self.flight, self.layout())

View File

@ -24,5 +24,5 @@ class Builder(FormationAttackBuilder[SeadFlightPlan, FormationAttackLayout]):
def layout(self) -> FormationAttackLayout:
return self._build(FlightWaypointType.INGRESS_SEAD)
def build(self) -> SeadFlightPlan:
def build(self, dump_debug_info: bool = False) -> SeadFlightPlan:
return SeadFlightPlan(self.flight, self.layout())

View File

@ -32,5 +32,5 @@ class Builder(FormationAttackBuilder[StrikeFlightPlan, FormationAttackLayout]):
return self._build(FlightWaypointType.INGRESS_STRIKE, targets)
def build(self) -> StrikeFlightPlan:
def build(self, dump_debug_info: bool = False) -> StrikeFlightPlan:
return StrikeFlightPlan(self.flight, self.layout())

View File

@ -134,5 +134,5 @@ class Builder(IBuilder[SweepFlightPlan, SweepLayout]):
target, origin, ip, join, self.coalition, self.theater
).find_best_hold_point()
def build(self) -> SweepFlightPlan:
def build(self, dump_debug_info: bool = False) -> SweepFlightPlan:
return SweepFlightPlan(self.flight, self.layout())

View File

@ -130,5 +130,5 @@ class Builder(CapBuilder[TarCapFlightPlan, TarCapLayout]):
bullseye=builder.bullseye(),
)
def build(self) -> TarCapFlightPlan:
def build(self, dump_debug_info: bool = False) -> TarCapFlightPlan:
return TarCapFlightPlan(self.flight, self.layout())

View File

@ -81,5 +81,5 @@ class Builder(IBuilder[TheaterRefuelingFlightPlan, PatrollingLayout]):
bullseye=builder.bullseye(),
)
def build(self) -> TheaterRefuelingFlightPlan:
def build(self, dump_debug_info: bool = False) -> TheaterRefuelingFlightPlan:
return TheaterRefuelingFlightPlan(self.flight, self.layout())

View File

@ -25,7 +25,7 @@ class FlightWaypointType(IntEnum):
INGRESS_STRIKE = 5 # Ingress strike (For generator, means that this should have bombing on next TARGET_POINT points)
INGRESS_SEAD = 6 # Ingress sead (For generator, means that this should attack groups on TARGET_GROUP_LOC points)
INGRESS_CAS = 7 # Ingress cas (should start CAS task)
CAS = 8 # Should do CAS there
CAS = 8 # Unused.
EGRESS = 9 # Should stop attack
DESCENT_POINT = 10 # Should start descending to pattern alt
LANDING_POINT = 11 # Should land there

View File

@ -11,9 +11,13 @@ from .pydcswaypointbuilder import PydcsWaypointBuilder
class CasIngressBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
if isinstance(self.flight.flight_plan, CasFlightPlan):
patrol_center = (
self.flight.flight_plan.layout.patrol_start.position
+ self.flight.flight_plan.layout.patrol_end.position
) / 2
waypoint.add_task(
EngageTargetsInZone(
position=self.flight.flight_plan.layout.target.position,
position=patrol_center,
radius=int(self.flight.flight_plan.engagement_distance.meters),
targets=[
Targets.All.GroundUnits.GroundVehicles,