mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Implement OCA strike missions.
https://github.com/Khopa/dcs_liberation/issues/349
This commit is contained in:
parent
6e0af7c144
commit
b0317055e7
@ -409,8 +409,8 @@ class Airfield(ControlPoint):
|
|||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
yield from [
|
yield from [
|
||||||
|
FlightType.OCA_STRIKE,
|
||||||
FlightType.RUNWAY_ATTACK,
|
FlightType.RUNWAY_ATTACK,
|
||||||
# TODO: FlightType.OCA_STRIKE
|
|
||||||
]
|
]
|
||||||
yield from super().mission_types(for_player)
|
yield from super().mission_types(for_player)
|
||||||
|
|
||||||
|
|||||||
@ -1175,8 +1175,18 @@ class AircraftConflictGenerator:
|
|||||||
self, group: FlyingGroup, package: Package, flight: Flight,
|
self, group: FlyingGroup, package: Package, flight: Flight,
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
group.task = RunwayAttack.name
|
group.task = RunwayAttack.name
|
||||||
self._setup_group(group, RunwayAttack, package, flight,
|
self._setup_group(group, RunwayAttack, package, flight, dynamic_runways)
|
||||||
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(
|
self.configure_behavior(
|
||||||
group,
|
group,
|
||||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||||
@ -1223,6 +1233,8 @@ class AircraftConflictGenerator:
|
|||||||
elif flight_type == FlightType.RUNWAY_ATTACK:
|
elif flight_type == FlightType.RUNWAY_ATTACK:
|
||||||
self.configure_runway_attack(group, package, flight,
|
self.configure_runway_attack(group, package, flight,
|
||||||
dynamic_runways)
|
dynamic_runways)
|
||||||
|
elif flight_type == FlightType.OCA_STRIKE:
|
||||||
|
self.configure_oca_strike(group, package, flight, dynamic_runways)
|
||||||
else:
|
else:
|
||||||
self.configure_unknown_task(group, flight)
|
self.configure_unknown_task(group, flight)
|
||||||
|
|
||||||
@ -1340,6 +1352,9 @@ class PydcsWaypointBuilder:
|
|||||||
waypoint = self.group.add_waypoint(
|
waypoint = self.group.add_waypoint(
|
||||||
Point(self.waypoint.x, self.waypoint.y), self.waypoint.alt)
|
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.alt_type = self.waypoint.alt_type
|
||||||
waypoint.name = String(self.waypoint.name)
|
waypoint.name = String(self.waypoint.name)
|
||||||
tot = self.flight.flight_plan.tot_for_waypoint(self.waypoint)
|
tot = self.flight.flight_plan.tot_for_waypoint(self.waypoint)
|
||||||
@ -1362,14 +1377,15 @@ class PydcsWaypointBuilder:
|
|||||||
FlightWaypointType.INGRESS_BAI: BaiIngressBuilder,
|
FlightWaypointType.INGRESS_BAI: BaiIngressBuilder,
|
||||||
FlightWaypointType.INGRESS_CAS: CasIngressBuilder,
|
FlightWaypointType.INGRESS_CAS: CasIngressBuilder,
|
||||||
FlightWaypointType.INGRESS_DEAD: DeadIngressBuilder,
|
FlightWaypointType.INGRESS_DEAD: DeadIngressBuilder,
|
||||||
|
FlightWaypointType.INGRESS_OCA_STRIKE: OcaStrikeIngressBuilder,
|
||||||
FlightWaypointType.INGRESS_RUNWAY_BOMBING: RunwayBombingIngressBuilder,
|
FlightWaypointType.INGRESS_RUNWAY_BOMBING: RunwayBombingIngressBuilder,
|
||||||
FlightWaypointType.INGRESS_SEAD: SeadIngressBuilder,
|
FlightWaypointType.INGRESS_SEAD: SeadIngressBuilder,
|
||||||
FlightWaypointType.INGRESS_STRIKE: StrikeIngressBuilder,
|
FlightWaypointType.INGRESS_STRIKE: StrikeIngressBuilder,
|
||||||
|
FlightWaypointType.INGRESS_SWEEP: SweepIngressBuilder,
|
||||||
FlightWaypointType.JOIN: JoinPointBuilder,
|
FlightWaypointType.JOIN: JoinPointBuilder,
|
||||||
FlightWaypointType.LANDING_POINT: LandingPointBuilder,
|
FlightWaypointType.LANDING_POINT: LandingPointBuilder,
|
||||||
FlightWaypointType.LOITER: HoldPointBuilder,
|
FlightWaypointType.LOITER: HoldPointBuilder,
|
||||||
FlightWaypointType.PATROL_TRACK: RaceTrackBuilder,
|
FlightWaypointType.PATROL_TRACK: RaceTrackBuilder,
|
||||||
FlightWaypointType.INGRESS_SWEEP: SweepIngressBuilder,
|
|
||||||
}
|
}
|
||||||
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
|
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
|
||||||
return builder(waypoint, group, package, flight, mission)
|
return builder(waypoint, group, package, flight, mission)
|
||||||
@ -1498,6 +1514,32 @@ class DeadIngressBuilder(PydcsWaypointBuilder):
|
|||||||
return waypoint
|
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):
|
class RunwayBombingIngressBuilder(PydcsWaypointBuilder):
|
||||||
def build(self) -> MovingPoint:
|
def build(self) -> MovingPoint:
|
||||||
waypoint = super().build()
|
waypoint = super().build()
|
||||||
|
|||||||
@ -147,6 +147,7 @@ class Package:
|
|||||||
FlightType.CAS,
|
FlightType.CAS,
|
||||||
FlightType.STRIKE,
|
FlightType.STRIKE,
|
||||||
FlightType.ANTISHIP,
|
FlightType.ANTISHIP,
|
||||||
|
FlightType.OCA_STRIKE,
|
||||||
FlightType.RUNWAY_ATTACK,
|
FlightType.RUNWAY_ATTACK,
|
||||||
FlightType.BAI,
|
FlightType.BAI,
|
||||||
FlightType.DEAD,
|
FlightType.DEAD,
|
||||||
|
|||||||
@ -162,6 +162,8 @@ class AircraftAllocator:
|
|||||||
return CAS_PREFERRED
|
return CAS_PREFERRED
|
||||||
elif task in (FlightType.DEAD, FlightType.SEAD):
|
elif task in (FlightType.DEAD, FlightType.SEAD):
|
||||||
return SEAD_PREFERRED
|
return SEAD_PREFERRED
|
||||||
|
elif task == FlightType.OCA_STRIKE:
|
||||||
|
return CAS_PREFERRED
|
||||||
elif task == FlightType.RUNWAY_ATTACK:
|
elif task == FlightType.RUNWAY_ATTACK:
|
||||||
return RUNWAY_ATTACK_PREFERRED
|
return RUNWAY_ATTACK_PREFERRED
|
||||||
elif task == FlightType.STRIKE:
|
elif task == FlightType.STRIKE:
|
||||||
@ -184,6 +186,8 @@ class AircraftAllocator:
|
|||||||
return CAS_CAPABLE
|
return CAS_CAPABLE
|
||||||
elif task in (FlightType.DEAD, FlightType.SEAD):
|
elif task in (FlightType.DEAD, FlightType.SEAD):
|
||||||
return SEAD_CAPABLE
|
return SEAD_CAPABLE
|
||||||
|
elif task == FlightType.OCA_STRIKE:
|
||||||
|
return CAS_CAPABLE
|
||||||
elif task == FlightType.RUNWAY_ATTACK:
|
elif task == FlightType.RUNWAY_ATTACK:
|
||||||
return RUNWAY_ATTACK_CAPABLE
|
return RUNWAY_ATTACK_CAPABLE
|
||||||
elif task == FlightType.STRIKE:
|
elif task == FlightType.STRIKE:
|
||||||
|
|||||||
@ -29,6 +29,7 @@ class FlightType(Enum):
|
|||||||
BAI = "BAI"
|
BAI = "BAI"
|
||||||
SWEEP = "Fighter sweep"
|
SWEEP = "Fighter sweep"
|
||||||
RUNWAY_ATTACK = "Runway attack"
|
RUNWAY_ATTACK = "Runway attack"
|
||||||
|
OCA_STRIKE = "OCA Strike"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
@ -60,6 +61,7 @@ class FlightWaypointType(Enum):
|
|||||||
INGRESS_BAI = 22
|
INGRESS_BAI = 22
|
||||||
DIVERT = 23
|
DIVERT = 23
|
||||||
INGRESS_RUNWAY_BOMBING = 24
|
INGRESS_RUNWAY_BOMBING = 24
|
||||||
|
INGRESS_OCA_STRIKE = 25
|
||||||
|
|
||||||
|
|
||||||
class FlightWaypoint:
|
class FlightWaypoint:
|
||||||
@ -86,6 +88,7 @@ class FlightWaypoint:
|
|||||||
self.obj_name = ""
|
self.obj_name = ""
|
||||||
self.pretty_name = ""
|
self.pretty_name = ""
|
||||||
self.only_for_player = False
|
self.only_for_player = False
|
||||||
|
self.flyover = False
|
||||||
|
|
||||||
# These are set very late by the air conflict generator (part of mission
|
# 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
|
# generation). We do it late so that we don't need to propagate changes
|
||||||
|
|||||||
@ -39,7 +39,6 @@ if TYPE_CHECKING:
|
|||||||
from game import Game
|
from game import Game
|
||||||
from gen.ato import Package
|
from gen.ato import Package
|
||||||
|
|
||||||
|
|
||||||
INGRESS_TYPES = {
|
INGRESS_TYPES = {
|
||||||
FlightWaypointType.INGRESS_CAS,
|
FlightWaypointType.INGRESS_CAS,
|
||||||
FlightWaypointType.INGRESS_ESCORT,
|
FlightWaypointType.INGRESS_ESCORT,
|
||||||
@ -55,6 +54,7 @@ class PlanningError(RuntimeError):
|
|||||||
|
|
||||||
class InvalidObjectiveLocation(PlanningError):
|
class InvalidObjectiveLocation(PlanningError):
|
||||||
"""Raised when the objective location is invalid for the mission type."""
|
"""Raised when the objective location is invalid for the mission type."""
|
||||||
|
|
||||||
def __init__(self, task: FlightType, location: MissionTarget) -> None:
|
def __init__(self, task: FlightType, location: MissionTarget) -> None:
|
||||||
super().__init__(f"{location.name} is not valid for {task} missions.")
|
super().__init__(f"{location.name} is not valid for {task} missions.")
|
||||||
|
|
||||||
@ -649,6 +649,8 @@ class FlightPlanBuilder:
|
|||||||
return self.generate_dead(flight, custom_targets)
|
return self.generate_dead(flight, custom_targets)
|
||||||
elif task == FlightType.ESCORT:
|
elif task == FlightType.ESCORT:
|
||||||
return self.generate_escort(flight)
|
return self.generate_escort(flight)
|
||||||
|
elif task == FlightType.OCA_STRIKE:
|
||||||
|
return self.generate_oca_strike(flight)
|
||||||
elif task == FlightType.RUNWAY_ATTACK:
|
elif task == FlightType.RUNWAY_ATTACK:
|
||||||
return self.generate_runway_attack(flight)
|
return self.generate_runway_attack(flight)
|
||||||
elif task == FlightType.SEAD:
|
elif task == FlightType.SEAD:
|
||||||
@ -931,7 +933,8 @@ class FlightPlanBuilder:
|
|||||||
is_ewr = isinstance(location, EwrGroundObject)
|
is_ewr = isinstance(location, EwrGroundObject)
|
||||||
is_sam = isinstance(location, SamGroundObject)
|
is_sam = isinstance(location, SamGroundObject)
|
||||||
if not is_ewr and not is_sam:
|
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)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
# TODO: Unify these.
|
# TODO: Unify these.
|
||||||
@ -946,6 +949,23 @@ class FlightPlanBuilder:
|
|||||||
return self.strike_flightplan(flight, location,
|
return self.strike_flightplan(flight, location,
|
||||||
FlightWaypointType.INGRESS_DEAD, targets)
|
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:
|
def generate_runway_attack(self, flight: Flight) -> StrikeFlightPlan:
|
||||||
"""Generate a runway attack flight plan at a given location.
|
"""Generate a runway attack flight plan at a given location.
|
||||||
|
|
||||||
@ -1059,6 +1079,8 @@ class FlightPlanBuilder:
|
|||||||
return builder.dead_area(location)
|
return builder.dead_area(location)
|
||||||
elif flight.flight_type == FlightType.SEAD:
|
elif flight.flight_type == FlightType.SEAD:
|
||||||
return builder.sead_area(location)
|
return builder.sead_area(location)
|
||||||
|
elif flight.flight_type == FlightType.OCA_STRIKE:
|
||||||
|
return builder.oca_strike_area(location)
|
||||||
else:
|
else:
|
||||||
return builder.strike_area(location)
|
return builder.strike_area(location)
|
||||||
|
|
||||||
|
|||||||
@ -239,8 +239,12 @@ class WaypointBuilder:
|
|||||||
def dead_area(self, target: MissionTarget) -> FlightWaypoint:
|
def dead_area(self, target: MissionTarget) -> FlightWaypoint:
|
||||||
return self._target_area(f"DEAD on {target.name}", target)
|
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
|
@staticmethod
|
||||||
def _target_area(name: str, location: MissionTarget) -> FlightWaypoint:
|
def _target_area(name: str, location: MissionTarget,
|
||||||
|
flyover: bool = False) -> FlightWaypoint:
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
location.position.x,
|
location.position.x,
|
||||||
@ -251,9 +255,17 @@ class WaypointBuilder:
|
|||||||
waypoint.pretty_name = name
|
waypoint.pretty_name = name
|
||||||
waypoint.name = name
|
waypoint.name = name
|
||||||
waypoint.alt_type = "RADIO"
|
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
|
# the target are set on the ingress point so they begin their attack
|
||||||
# *before* reaching the target.
|
# *before* reaching the target.
|
||||||
|
#
|
||||||
|
# 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
|
waypoint.only_for_player = True
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user