diff --git a/changelog.md b/changelog.md index d4560fcc..4a2c6557 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,7 @@ # Retribution 1.1.0 ## Features/Improvements +* **[Mission Generation]** Given a CAS flight was planned, delay ground force attack until first CAS flight is on station ## Fixes * **[UI]** Removed deprecated options diff --git a/game/missiongenerator/flotgenerator.py b/game/missiongenerator/flotgenerator.py index 62766bd7..6a84592f 100644 --- a/game/missiongenerator/flotgenerator.py +++ b/game/missiongenerator/flotgenerator.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging import math import random +from datetime import timedelta from typing import List, Optional, TYPE_CHECKING, Tuple from dcs import Mission @@ -15,7 +16,6 @@ from dcs.task import ( AFAC, AttackGroup, ControlledTask, - EPLRS, FAC, FireAtPoint, GoToWaypoint, @@ -47,6 +47,7 @@ from .frontlineconflictdescription import FrontLineConflictDescription from .groundforcepainter import GroundForcePainter from .lasercoderegistry import LaserCodeRegistry from .missiondata import JtacInfo, MissionData +from ..ato import FlightType if TYPE_CHECKING: from game import Game @@ -276,8 +277,29 @@ class FlotGenerator: vehicle = vg.units[0] GroundForcePainter(faction, vehicle).apply_livery() + def _earliest_tot_on_flot(self, player: bool) -> timedelta: + tots = [ + x.time_over_target + for x in self.game.ato_for(player).packages + if x.primary_task == FlightType.CAS + ] + return ( + timedelta() + if len(tots) == 0 + else min( + [ + x.time_over_target + for x in self.game.ato_for(player).packages + if x.primary_task == FlightType.CAS + ] + ) + ) + def _set_reform_waypoint( - self, dcs_group: VehicleGroup, forward_heading: Heading + self, + dcs_group: VehicleGroup, + forward_heading: Heading, + hold_duration: timedelta = timedelta(), ) -> None: """Setting a waypoint close to the spawn position allows the group to reform gracefully rather than spin @@ -285,7 +307,10 @@ class FlotGenerator: reform_point = dcs_group.position.point_from_heading( forward_heading.degrees, 50 ) - dcs_group.add_waypoint(reform_point) + rp = dcs_group.add_waypoint(reform_point) + hold = ControlledTask(Hold()) + hold.stop_after_duration(hold_duration.seconds) + rp.add_task(hold) def _plan_artillery_action( self, @@ -323,7 +348,7 @@ class FlotGenerator: # Hold position dcs_group.points[1].tasks.append(Hold()) retreat = self.find_retreat_point( - dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 3) + dcs_group, forward_heading, int(RETREAT_DISTANCE / 3) ) dcs_group.add_waypoint( dcs_group.position.point_from_heading(forward_heading.degrees, 1), @@ -370,7 +395,10 @@ class FlotGenerator: Handles adding the DCS tasks for tank and IFV groups for all combat stances. Returns True if tasking was added, returns False if the stance was not a combat stance. """ - self._set_reform_waypoint(dcs_group, forward_heading) + duration = timedelta() + if stance != CombatStance.RETREAT: + duration = self._earliest_tot_on_flot(not to_cp.coalition.player) + self._set_reform_waypoint(dcs_group, forward_heading, duration) if stance == CombatStance.AGGRESSIVE: # Attack nearest enemy if any # Then move forward OR Attack enemy base if it is not too far away @@ -457,7 +485,10 @@ class FlotGenerator: Handles adding the DCS tasks for APC and ATGM groups for all combat stances. Returns True if tasking was added, returns False if the stance was not a combat stance. """ - self._set_reform_waypoint(dcs_group, forward_heading) + duration = timedelta() + if stance != CombatStance.RETREAT: + duration = self._earliest_tot_on_flot(not to_cp.coalition.player) + self._set_reform_waypoint(dcs_group, forward_heading, duration) if stance in [ CombatStance.AGGRESSIVE, CombatStance.BREAKTHROUGH, @@ -496,8 +527,6 @@ class FlotGenerator: return for dcs_group, group in ally_groups: - if group.unit_type.eplrs_capable: - dcs_group.points[0].tasks.append(EPLRS(dcs_group.id)) if group.role == CombatGroupRole.ARTILLERY: if self.game.settings.perf_artillery: @@ -519,7 +548,7 @@ class FlotGenerator: if stance == CombatStance.RETREAT: # In retreat mode, the units will fall back - # If the ally base is close enough, the units will even regroup there + # If the allied base is close enough, the units will even regroup there if ( from_cp.position.distance_to_point(dcs_group.points[0].position) <= RETREAT_DISTANCE @@ -537,7 +566,7 @@ class FlotGenerator: self, dcs_group: VehicleGroup, forward_heading: Heading ) -> None: """ - This add a trigger to manage units fleeing whenever their group is hit hard, or being engaged by CAS + This adds a trigger to manage units fleeing whenever their group is hit hard, or being engaged by CAS """ if len(dcs_group.units) == 1: @@ -554,7 +583,7 @@ class FlotGenerator: # We add a new retreat waypoint dcs_group.add_waypoint( self.find_retreat_point( - dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8) + dcs_group, forward_heading, int(RETREAT_DISTANCE / 8) ), self.wpt_pointaction, ) @@ -684,7 +713,7 @@ class FlotGenerator: @staticmethod def get_artilery_group_distance_from_frontline(group: CombatGroup) -> int: """ - For artilery group, decide the distance from frontline with the range of the unit + For artillery group, decide the distance from frontline with the range of the unit """ rg = group.unit_type.dcs_unit_type.threat_range - 7500 if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]: