def _plan_apc_atgm_action( self, stance: CombatStance, dcs_group: VehicleGroup, forward_heading: int, to_cp: ControlPoint, ) -> bool: """ 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) if stance in [ CombatStance.AGGRESSIVE, CombatStance.BREAKTHROUGH, CombatStance.ELIMINATION, ]: # APC & ATGM will never move too much forward, but will follow along any offensive if ( to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE ): attack_point = self.conflict.theater.nearest_land_pos( to_cp.position.random_point_within(500, 0) ) else: attack_point = self.find_offensive_point( dcs_group, forward_heading, AGGRESIVE_MOVE_DISTANCE ) dcs_group.add_waypoint(attack_point, PointAction.OffRoad) if stance != CombatStance.RETREAT: self.add_morale_trigger(dcs_group, forward_heading) return True return False
def _set_reform_waypoint( self, dcs_group: VehicleGroup, forward_heading: int ) -> None: """Setting a waypoint close to the spawn position allows the group to reform gracefully rather than spin """ reform_point = dcs_group.position.point_from_heading(forward_heading, 50) dcs_group.add_waypoint(reform_point)
def add_morale_trigger(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 """ if len(dcs_group.units) == 1: return # Units should hold position on last waypoint dcs_group.points[len(dcs_group.points) - 1].tasks.append(Hold()) # Force unit heading for unit in dcs_group.units: unit.heading = forward_heading.degrees dcs_group.manualHeading = True # We add a new retreat waypoint dcs_group.add_waypoint( self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)), PointAction.OffRoad, ) # Fallback task task = ControlledTask(GoToWaypoint(to_index=len(dcs_group.points))) task.enabled = False dcs_group.add_trigger_action(Hold()) dcs_group.add_trigger_action(task) # Create trigger fallback = TriggerOnce(Event.NoEvent, "Morale manager #" + str(dcs_group.id)) # Usually more than 50% casualties = RETREAT fallback.add_condition( GroupLifeLess(dcs_group.id, random.randint(51, 76))) # Do retreat to the configured retreat waypoint fallback.add_action(AITaskPush(dcs_group.id, len(dcs_group.tasks))) self.mission.triggerrules.triggers.append(fallback)
def _plan_tank_ifv_action( self, stance: CombatStance, enemy_groups: List[Tuple[VehicleGroup, CombatGroup]], dcs_group: VehicleGroup, forward_heading: int, to_cp: ControlPoint, ) -> bool: """ 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) if stance == CombatStance.AGGRESSIVE: # Attack nearest enemy if any # Then move forward OR Attack enemy base if it is not too far away target = self.find_nearest_enemy_group(dcs_group, enemy_groups) if target is not None: rand_offset = Point( random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK), random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK), ) target_point = self.conflict.theater.nearest_land_pos( target.points[0].position + rand_offset ) dcs_group.add_waypoint(target_point) dcs_group.points[2].tasks.append(AttackGroup(target.id)) if ( to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE ): attack_point = self.conflict.theater.nearest_land_pos( to_cp.position.random_point_within(500, 0) ) else: # We use an offset heading here because DCS doesn't always # force vehicles to move if there's no heading change. offset_heading = forward_heading - 2 if offset_heading < 0: offset_heading = 358 attack_point = self.find_offensive_point( dcs_group, offset_heading, AGGRESIVE_MOVE_DISTANCE ) dcs_group.add_waypoint(attack_point, PointAction.OffRoad) elif stance == CombatStance.BREAKTHROUGH: # In breakthrough mode, the units will move forward # If the enemy base is close enough, the units will attack the base if ( to_cp.position.distance_to_point(dcs_group.points[0].position) <= BREAKTHROUGH_OFFENSIVE_DISTANCE ): attack_point = self.conflict.theater.nearest_land_pos( to_cp.position.random_point_within(500, 0) ) else: # We use an offset heading here because DCS doesn't always # force vehicles to move if there's no heading change. offset_heading = forward_heading - 1 if offset_heading < 0: offset_heading = 359 attack_point = self.find_offensive_point( dcs_group, offset_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE ) dcs_group.add_waypoint(attack_point, PointAction.OffRoad) elif stance == CombatStance.ELIMINATION: # In elimination mode, the units focus on destroying as much enemy groups as possible targets = self.find_n_nearest_enemy_groups(dcs_group, enemy_groups, 3) for i, target in enumerate(targets, start=1): rand_offset = Point( random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK), random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK), ) target_point = self.conflict.theater.nearest_land_pos( target.points[0].position + rand_offset ) dcs_group.add_waypoint(target_point, PointAction.OffRoad) dcs_group.points[i + 1].tasks.append(AttackGroup(target.id)) if ( to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE ): attack_point = self.conflict.theater.nearest_land_pos( to_cp.position.random_point_within(500, 0) ) dcs_group.add_waypoint(attack_point) if stance != CombatStance.RETREAT: self.add_morale_trigger(dcs_group, forward_heading) return True return False
def _plan_artillery_action( self, stance: CombatStance, gen_group: CombatGroup, dcs_group: VehicleGroup, forward_heading: int, target: Point, ) -> bool: """ Handles adding the DCS tasks for artillery 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) if stance != CombatStance.RETREAT: hold_task = Hold() hold_task.number = 1 dcs_group.add_trigger_action(hold_task) # Artillery strike random start artillery_trigger = TriggerOnce( Event.NoEvent, "ArtilleryFireTask #" + str(dcs_group.id) ) artillery_trigger.add_condition(TimeAfter(seconds=random.randint(1, 45) * 60)) # TODO: Update to fire at group instead of point fire_task = FireAtPoint(target, gen_group.size * 10, 100) fire_task.number = 2 if stance != CombatStance.RETREAT else 1 dcs_group.add_trigger_action(fire_task) artillery_trigger.add_action(AITaskPush(dcs_group.id, len(dcs_group.tasks))) self.mission.triggerrules.triggers.append(artillery_trigger) # Artillery will fall back when under attack if stance != CombatStance.RETREAT: # Hold position dcs_group.points[1].tasks.append(Hold()) retreat = self.find_retreat_point( dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 3) ) dcs_group.add_waypoint( dcs_group.position.point_from_heading(forward_heading, 1), PointAction.OffRoad, ) dcs_group.points[2].tasks.append(Hold()) dcs_group.add_waypoint(retreat, PointAction.OffRoad) artillery_fallback = TriggerOnce( Event.NoEvent, "ArtilleryRetreat #" + str(dcs_group.id) ) for i, u in enumerate(dcs_group.units): artillery_fallback.add_condition(UnitDamaged(u.id)) if i < len(dcs_group.units) - 1: artillery_fallback.add_condition(Or()) hold_2 = Hold() hold_2.number = 3 dcs_group.add_trigger_action(hold_2) retreat_task = GoToWaypoint(to_index=3) retreat_task.number = 4 dcs_group.add_trigger_action(retreat_task) artillery_fallback.add_action( AITaskPush(dcs_group.id, len(dcs_group.tasks)) ) self.mission.triggerrules.triggers.append(artillery_fallback) for u in dcs_group.units: u.heading = forward_heading + random.randint(-5, 5) return True return False