Implement OCA strike missions.

https://github.com/Khopa/dcs_liberation/issues/349
This commit is contained in:
Dan Albert 2020-11-23 17:58:53 -08:00
parent 6e0af7c144
commit b0317055e7
7 changed files with 98 additions and 14 deletions

View File

@ -409,8 +409,8 @@ class Airfield(ControlPoint):
]
else:
yield from [
FlightType.OCA_STRIKE,
FlightType.RUNWAY_ATTACK,
# TODO: FlightType.OCA_STRIKE
]
yield from super().mission_types(for_player)

View File

@ -1175,8 +1175,18 @@ class AircraftConflictGenerator:
self, group: FlyingGroup, package: Package, flight: Flight,
dynamic_runways: Dict[str, RunwayData]) -> None:
group.task = RunwayAttack.name
self._setup_group(group, RunwayAttack, package, flight,
dynamic_runways)
self._setup_group(group, RunwayAttack, package, flight, dynamic_runways)
self.configure_behavior(
group,
react_on_threat=OptReactOnThreat.Values.EvadeFire,
roe=OptROE.Values.OpenFire,
restrict_jettison=True)
def configure_oca_strike(
self, group: FlyingGroup, package: Package, flight: Flight,
dynamic_runways: Dict[str, RunwayData]) -> None:
group.task = CAS.name
self._setup_group(group, CAS, package, flight, dynamic_runways)
self.configure_behavior(
group,
react_on_threat=OptReactOnThreat.Values.EvadeFire,
@ -1223,6 +1233,8 @@ class AircraftConflictGenerator:
elif flight_type == FlightType.RUNWAY_ATTACK:
self.configure_runway_attack(group, package, flight,
dynamic_runways)
elif flight_type == FlightType.OCA_STRIKE:
self.configure_oca_strike(group, package, flight, dynamic_runways)
else:
self.configure_unknown_task(group, flight)
@ -1340,6 +1352,9 @@ class PydcsWaypointBuilder:
waypoint = self.group.add_waypoint(
Point(self.waypoint.x, self.waypoint.y), self.waypoint.alt)
if self.waypoint.flyover:
waypoint.type = PointAction.FlyOverPoint.value
waypoint.alt_type = self.waypoint.alt_type
waypoint.name = String(self.waypoint.name)
tot = self.flight.flight_plan.tot_for_waypoint(self.waypoint)
@ -1362,14 +1377,15 @@ class PydcsWaypointBuilder:
FlightWaypointType.INGRESS_BAI: BaiIngressBuilder,
FlightWaypointType.INGRESS_CAS: CasIngressBuilder,
FlightWaypointType.INGRESS_DEAD: DeadIngressBuilder,
FlightWaypointType.INGRESS_OCA_STRIKE: OcaStrikeIngressBuilder,
FlightWaypointType.INGRESS_RUNWAY_BOMBING: RunwayBombingIngressBuilder,
FlightWaypointType.INGRESS_SEAD: SeadIngressBuilder,
FlightWaypointType.INGRESS_STRIKE: StrikeIngressBuilder,
FlightWaypointType.INGRESS_SWEEP: SweepIngressBuilder,
FlightWaypointType.JOIN: JoinPointBuilder,
FlightWaypointType.LANDING_POINT: LandingPointBuilder,
FlightWaypointType.LOITER: HoldPointBuilder,
FlightWaypointType.PATROL_TRACK: RaceTrackBuilder,
FlightWaypointType.INGRESS_SWEEP: SweepIngressBuilder,
}
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
return builder(waypoint, group, package, flight, mission)
@ -1498,6 +1514,32 @@ class DeadIngressBuilder(PydcsWaypointBuilder):
return waypoint
class OcaStrikeIngressBuilder(PydcsWaypointBuilder):
def build(self) -> MovingPoint:
waypoint = super().build()
target = self.package.target
if not isinstance(target, Airfield):
logging.error(
"Unexpected target type for OCA Strike mission: %s",
target.__class__.__name__)
return waypoint
task = EngageTargetsInZone(
position=target.position,
# Al Dhafra is 4 nm across at most. Add a little wiggle room in case
# the airport position from DCS is not centered.
radius=nm_to_meter(3),
targets=[Targets.All.Air]
)
task.params["attackQtyLimit"] = False
task.params["directionEnabled"] = False
task.params["altitudeEnabled"] = False
task.params["groupAttack"] = True
waypoint.tasks.append(task)
return waypoint
class RunwayBombingIngressBuilder(PydcsWaypointBuilder):
def build(self) -> MovingPoint:
waypoint = super().build()

View File

@ -147,6 +147,7 @@ class Package:
FlightType.CAS,
FlightType.STRIKE,
FlightType.ANTISHIP,
FlightType.OCA_STRIKE,
FlightType.RUNWAY_ATTACK,
FlightType.BAI,
FlightType.DEAD,

View File

@ -162,6 +162,8 @@ class AircraftAllocator:
return CAS_PREFERRED
elif task in (FlightType.DEAD, FlightType.SEAD):
return SEAD_PREFERRED
elif task == FlightType.OCA_STRIKE:
return CAS_PREFERRED
elif task == FlightType.RUNWAY_ATTACK:
return RUNWAY_ATTACK_PREFERRED
elif task == FlightType.STRIKE:
@ -184,6 +186,8 @@ class AircraftAllocator:
return CAS_CAPABLE
elif task in (FlightType.DEAD, FlightType.SEAD):
return SEAD_CAPABLE
elif task == FlightType.OCA_STRIKE:
return CAS_CAPABLE
elif task == FlightType.RUNWAY_ATTACK:
return RUNWAY_ATTACK_CAPABLE
elif task == FlightType.STRIKE:

View File

@ -29,6 +29,7 @@ class FlightType(Enum):
BAI = "BAI"
SWEEP = "Fighter sweep"
RUNWAY_ATTACK = "Runway attack"
OCA_STRIKE = "OCA Strike"
def __str__(self) -> str:
return self.value
@ -60,6 +61,7 @@ class FlightWaypointType(Enum):
INGRESS_BAI = 22
DIVERT = 23
INGRESS_RUNWAY_BOMBING = 24
INGRESS_OCA_STRIKE = 25
class FlightWaypoint:
@ -86,6 +88,7 @@ class FlightWaypoint:
self.obj_name = ""
self.pretty_name = ""
self.only_for_player = False
self.flyover = False
# These are set very late by the air conflict generator (part of mission
# generation). We do it late so that we don't need to propagate changes

View File

@ -39,7 +39,6 @@ if TYPE_CHECKING:
from game import Game
from gen.ato import Package
INGRESS_TYPES = {
FlightWaypointType.INGRESS_CAS,
FlightWaypointType.INGRESS_ESCORT,
@ -55,6 +54,7 @@ class PlanningError(RuntimeError):
class InvalidObjectiveLocation(PlanningError):
"""Raised when the objective location is invalid for the mission type."""
def __init__(self, task: FlightType, location: MissionTarget) -> None:
super().__init__(f"{location.name} is not valid for {task} missions.")
@ -412,7 +412,7 @@ class StrikeFlightPlan(FormationFlightPlan):
self.ingress
]
yield from self.targets
yield from[
yield from [
self.egress,
self.split,
self.land,
@ -423,9 +423,9 @@ class StrikeFlightPlan(FormationFlightPlan):
@property
def package_speed_waypoints(self) -> Set[FlightWaypoint]:
return {
self.ingress,
self.egress,
self.split,
self.ingress,
self.egress,
self.split,
} | set(self.targets)
def speed_between_waypoints(self, a: FlightWaypoint,
@ -649,6 +649,8 @@ class FlightPlanBuilder:
return self.generate_dead(flight, custom_targets)
elif task == FlightType.ESCORT:
return self.generate_escort(flight)
elif task == FlightType.OCA_STRIKE:
return self.generate_oca_strike(flight)
elif task == FlightType.RUNWAY_ATTACK:
return self.generate_runway_attack(flight)
elif task == FlightType.SEAD:
@ -876,7 +878,7 @@ class FlightPlanBuilder:
if combat_width < 35000:
combat_width = 35000
radius = combat_width*1.25
radius = combat_width * 1.25
orbit0p = orbit_center.point_from_heading(heading, radius)
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
@ -931,7 +933,8 @@ class FlightPlanBuilder:
is_ewr = isinstance(location, EwrGroundObject)
is_sam = isinstance(location, SamGroundObject)
if not is_ewr and not is_sam:
logging.exception(f"Invalid Objective Location for DEAD flight {flight=} at {location=}")
logging.exception(
f"Invalid Objective Location for DEAD flight {flight=} at {location=}")
raise InvalidObjectiveLocation(flight.flight_type, location)
# TODO: Unify these.
@ -946,6 +949,23 @@ class FlightPlanBuilder:
return self.strike_flightplan(flight, location,
FlightWaypointType.INGRESS_DEAD, targets)
def generate_oca_strike(self, flight: Flight) -> StrikeFlightPlan:
"""Generate an OCA Strike flight plan at a given location.
Args:
flight: The flight to generate the flight plan for.
"""
location = self.package.target
if not isinstance(location, Airfield):
logging.exception(
f"Invalid Objective Location for OCA Strike flight "
f"{flight=} at {location=}.")
raise InvalidObjectiveLocation(flight.flight_type, location)
return self.strike_flightplan(flight, location,
FlightWaypointType.INGRESS_OCA_STRIKE)
def generate_runway_attack(self, flight: Flight) -> StrikeFlightPlan:
"""Generate a runway attack flight plan at a given location.
@ -1059,6 +1079,8 @@ class FlightPlanBuilder:
return builder.dead_area(location)
elif flight.flight_type == FlightType.SEAD:
return builder.sead_area(location)
elif flight.flight_type == FlightType.OCA_STRIKE:
return builder.oca_strike_area(location)
else:
return builder.strike_area(location)

View File

@ -239,8 +239,12 @@ class WaypointBuilder:
def dead_area(self, target: MissionTarget) -> FlightWaypoint:
return self._target_area(f"DEAD on {target.name}", target)
def oca_strike_area(self, target: MissionTarget) -> FlightWaypoint:
return self._target_area(f"ATTACK {target.name}", target, flyover=True)
@staticmethod
def _target_area(name: str, location: MissionTarget) -> FlightWaypoint:
def _target_area(name: str, location: MissionTarget,
flyover: bool = False) -> FlightWaypoint:
waypoint = FlightWaypoint(
FlightWaypointType.TARGET_GROUP_LOC,
location.position.x,
@ -251,10 +255,18 @@ class WaypointBuilder:
waypoint.pretty_name = name
waypoint.name = name
waypoint.alt_type = "RADIO"
# The target waypoints are only for the player's benefit. AI tasks for
# Most target waypoints are only for the player's benefit. AI tasks for
# the target are set on the ingress point so they begin their attack
# *before* reaching the target.
waypoint.only_for_player = True
#
# The exception is for flight plans that require passing over the
# target. For example, OCA strikes need to get close enough to detect
# the targets in their engagement zone or they will RTB immediately.
if flyover:
waypoint.flyover = True
else:
waypoint.only_for_player = True
return waypoint
def cas(self, position: Point) -> FlightWaypoint: