diff --git a/changelog.md b/changelog.md index 8d5dcb5b..ac1aa096 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,7 @@ * **[Options]** Ability to load & save your settings. * **[UI]** Added fuel selector in flight's edit window. * **[Plugins]** Expose Splash Damage's "game_messages" option and set its default to false. +* **[Mission Generation]** Improved AI SEAD capabilities, allowing for mixed loadouts using Decoys, ARMs & ASMs. ## Fixes * **[New Game Wizard]** Settings would not persist when going back to a previous page. diff --git a/game/ato/flightplans/formationattack.py b/game/ato/flightplans/formationattack.py index 84d96d12..435c0c06 100644 --- a/game/ato/flightplans/formationattack.py +++ b/game/ato/flightplans/formationattack.py @@ -190,6 +190,10 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC): ingress_type, self.package.waypoints.ingress, self.package.target ) + initial = None + if ingress_type == FlightWaypointType.INGRESS_SEAD: + initial = builder.sead_search(self.package.target) + return FormationAttackLayout( departure=builder.takeoff(self.flight.departure), hold=hold, @@ -198,6 +202,7 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC): ), join=join, ingress=ingress, + initial=initial, targets=target_waypoints, split=split, refuel=refuel, diff --git a/game/ato/flightplans/waypointbuilder.py b/game/ato/flightplans/waypointbuilder.py index 640ddb0b..93fc9fdd 100644 --- a/game/ato/flightplans/waypointbuilder.py +++ b/game/ato/flightplans/waypointbuilder.py @@ -290,12 +290,9 @@ class WaypointBuilder: return self._target_area(f"STRIKE {target.name}", target) def sead_area(self, target: MissionTarget) -> FlightWaypoint: - # Set flyover with ingress altitude to allow the flight to search and engage - # the target group at the ingress alt without suicide dive return self._target_area( f"SEAD on {target.name}", target, - flyover=True, altitude=self.doctrine.ingress_altitude, alt_type="BARO", ) @@ -316,8 +313,8 @@ class WaypointBuilder: # plan. return self._target_area(f"ASSAULT {target.name}", target) - @staticmethod def _target_area( + self, name: str, location: MissionTarget, flyover: bool = False, @@ -425,6 +422,29 @@ class WaypointBuilder: pretty_name="Orbit", ) + def sead_search(self, target: MissionTarget) -> FlightWaypoint: + """Creates custom waypoint for AI SEAD flights + to avoid having them fly all the way to the SAM site. + Args: + target: Target information. + """ + # Use the threat range as offset distance to avoid flying all the way to the SAM site + assert self.flight.package.waypoints + ingress = self.flight.package.waypoints.ingress + threat_range = 1.1 * max([x.threat_range for x in target.strike_targets]).meters + hdg = target.position.heading_between_point(ingress) + hold = target.position.point_from_heading(hdg, threat_range) + + return FlightWaypoint( + "SEAD Search", + FlightWaypointType.CUSTOM, + hold, + self.doctrine.ingress_altitude, + alt_type="BARO", + description="Anchor and search from this point", + pretty_name="SEAD Search", + ) + @staticmethod def escort_hold(start: Point, altitude: Distance) -> FlightWaypoint: """Creates custom waypoint for escort flights that need to hold. diff --git a/game/missiongenerator/aircraft/waypoints/seadingress.py b/game/missiongenerator/aircraft/waypoints/seadingress.py index 5c9f52b4..48503011 100644 --- a/game/missiongenerator/aircraft/waypoints/seadingress.py +++ b/game/missiongenerator/aircraft/waypoints/seadingress.py @@ -7,6 +7,7 @@ from dcs.task import ( Expend, OptECMUsing, WeaponType as DcsWeaponType, + OptRestrictAfterburner, ) from game.data.weapons import WeaponType @@ -29,6 +30,11 @@ class SeadIngressBuilder(PydcsWaypointBuilder): # Preemptively use ECM to better avoid getting swatted. ecm_option = OptECMUsing(value=OptECMUsing.Values.UseIfDetectedLockByRadar) waypoint.tasks.append(ecm_option) + + # Avoid having AI burn all of its fuel while loitering until next weapon release + burn_restrict = OptRestrictAfterburner(True) + waypoint.tasks.append(burn_restrict) + for group in target.groups: miz_group = self.mission.find_group(group.group_name) if miz_group is None: @@ -43,7 +49,7 @@ class SeadIngressBuilder(PydcsWaypointBuilder): weapon_type=DcsWeaponType.Decoy, group_attack=True, expend=Expend.All, - altitude=waypoint.alt, + altitude=round(waypoint.alt * 1.5), # 50% increase to force a climb ) waypoint.tasks.append(attack_task) @@ -54,8 +60,16 @@ class SeadIngressBuilder(PydcsWaypointBuilder): # when skynet is enabled and the Radar is not emitting. They dive # into the SAM instead of waiting for it to come alive engage_task = EngageGroup(miz_group.id) - engage_task.params["weaponType"] = DcsWeaponType.Guided.value - engage_task.params["groupAttack"] = True - engage_task.params["expend"] = Expend.All.value + engage_task.params["weaponType"] = DcsWeaponType.ARM.value waypoint.tasks.append(engage_task) + # Use other Air-to-Surface Missiles at last + attack_task = AttackGroup( + miz_group.id, + weapon_type=DcsWeaponType.ASM, + altitude=waypoint.alt, # flight loses alt with AB restriction + ) + waypoint.tasks.append(attack_task) + + burn_free = OptRestrictAfterburner(False) + waypoint.tasks.append(burn_free)