mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +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:
|
||||
yield from [
|
||||
FlightType.OCA_STRIKE,
|
||||
FlightType.RUNWAY_ATTACK,
|
||||
# TODO: FlightType.OCA_STRIKE
|
||||
]
|
||||
yield from super().mission_types(for_player)
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -147,6 +147,7 @@ class Package:
|
||||
FlightType.CAS,
|
||||
FlightType.STRIKE,
|
||||
FlightType.ANTISHIP,
|
||||
FlightType.OCA_STRIKE,
|
||||
FlightType.RUNWAY_ATTACK,
|
||||
FlightType.BAI,
|
||||
FlightType.DEAD,
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user