diff --git a/changelog.md b/changelog.md index cc88f99a..50252a96 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ Saves from 2.5 are not compatible with 3.0. * **[Campaign AI]** Every 30 minutes the AI will plan a CAP, so players can customize their mission better. * **[Campaign AI]** AI now considers Ju-88s for CAS, strike, and DEAD missions. * **[Campaign AI]** Fix purchase of aircraft by priority (the faction's list was being used as the priority list rather than the game's). +* **[Flight Planner]** AI strike flight plans now include the correct target actions for building groups. * **[UI]** Added new web based map UI. This is mostly functional but many of the old display options are a WIP. Revert to the old map with --old-map. * **[UI]** Campaigns generated for an older or newer version of the game will now be marked as incompatible. They can still be played, but bugs may be present. * **[UI]** DCS loadouts are now selectable in the loadout setup menu. diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 414d18b6..f2e96a88 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -1,5 +1,4 @@ from __future__ import annotations -from game.scenery_group import SceneryGroup import heapq import itertools @@ -9,7 +8,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass, field from enum import Enum from functools import total_ordering -from typing import Any, Dict, Iterator, List, Optional, Set, TYPE_CHECKING, Type +from typing import Any, Dict, Iterator, List, Optional, Set, TYPE_CHECKING, Type, Union from dcs.mapping import Point from dcs.ships import ( @@ -19,10 +18,12 @@ from dcs.ships import ( Type_071_Amphibious_Transport_Dock, ) from dcs.terrain.terrain import Airport, ParkingSlot +from dcs.unit import Unit from dcs.unittype import FlyingType from game import db from game.point_with_heading import PointWithHeading +from game.scenery_group import SceneryGroup from gen.flights.closestairfields import ObjectiveDistanceCache from gen.ground_forces.ai_ground_planner_db import TYPE_SHORAD from gen.ground_forces.combat_stance import CombatStance @@ -781,6 +782,10 @@ class ControlPoint(MissionTarget, ABC): return self.captured != other.captured + @property + def strike_targets(self) -> List[Union[MissionTarget, Unit]]: + return [] + class Airfield(ControlPoint): def __init__( diff --git a/game/theater/missiontarget.py b/game/theater/missiontarget.py index 9a5fe8cd..8d026625 100644 --- a/game/theater/missiontarget.py +++ b/game/theater/missiontarget.py @@ -1,8 +1,9 @@ from __future__ import annotations -from typing import Iterator, TYPE_CHECKING +from typing import Iterator, TYPE_CHECKING, List, Union from dcs.mapping import Point +from dcs.unit import Unit if TYPE_CHECKING: from gen.flights.flight import FlightType @@ -42,3 +43,7 @@ class MissionTarget: # TODO: FlightType.EWAR, # TODO: FlightType.RECON, ] + + @property + def strike_targets(self) -> List[Union[MissionTarget, Unit]]: + raise NotImplementedError diff --git a/game/theater/theatergroundobject.py b/game/theater/theatergroundobject.py index f768992d..18131a42 100644 --- a/game/theater/theatergroundobject.py +++ b/game/theater/theatergroundobject.py @@ -2,7 +2,7 @@ from __future__ import annotations import itertools import logging -from typing import Iterator, List, TYPE_CHECKING +from typing import Iterator, List, TYPE_CHECKING, Union from dcs.mapping import Point from dcs.triggers import TriggerZone @@ -185,6 +185,10 @@ class TheaterGroundObject(MissionTarget): """True if this TGO is the group for the control point itself (CVs and FOBs).""" return False + @property + def strike_targets(self) -> List[Union[MissionTarget, Unit]]: + return self.units + class BuildingGroundObject(TheaterGroundObject): def __init__( @@ -233,6 +237,15 @@ class BuildingGroundObject(TheaterGroundObject): def kill(self) -> None: self._dead = True + def iter_building_group(self) -> Iterator[TheaterGroundObject]: + for tgo in self.control_point.ground_objects: + if tgo.obj_name == self.obj_name and not tgo.is_dead: + yield tgo + + @property + def strike_targets(self) -> List[Union[MissionTarget, Unit]]: + return list(self.iter_building_group()) + class SceneryGroundObject(BuildingGroundObject): def __init__( diff --git a/gen/aircraft.py b/gen/aircraft.py index 63e9ca1d..92cead03 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -5,7 +5,7 @@ import random from dataclasses import dataclass from datetime import timedelta from functools import cached_property -from typing import Dict, List, Optional, TYPE_CHECKING, Type, Union +from typing import Dict, List, Optional, TYPE_CHECKING, Type, Union, Iterable from dcs import helicopters from dcs.action import AITaskPush, ActivateGroup @@ -70,6 +70,7 @@ from dcs.task import ( ) from dcs.terrain.terrain import Airport, NoParkingSlotError from dcs.triggers import Event, TriggerOnce, TriggerRule +from dcs.unit import Unit from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup from dcs.unittype import FlyingType, UnitType @@ -85,6 +86,7 @@ from game.theater.controlpoint import ( NavalControlPoint, OffMapSpawn, ) +from game.theater.missiontarget import MissionTarget from game.theater.theatergroundobject import TheaterGroundObject from game.transfers import MultiGroupTransport from game.unitmap import UnitMap @@ -1629,7 +1631,9 @@ class PydcsWaypointBuilder: else: return False - def register_special_waypoints(self, targets) -> None: + def register_special_waypoints( + self, targets: Iterable[Union[MissionTarget, Unit]] + ) -> None: """Create special target waypoints for various aircraft""" for i, t in enumerate(targets): if self.group.units[0].unit_type == JF_17 and i < 4: @@ -1850,29 +1854,16 @@ class StrikeIngressBuilder(PydcsWaypointBuilder): def build_strike(self) -> MovingPoint: waypoint = super().build() for target in self.waypoint.targets: + bombing = Bombing(target.position) + # If there is only one target, drop all ordnance in one pass. + if len(self.waypoint.targets) == 1: + bombing.params["expend"] = "All" + bombing.params["weaponType"] = WeaponType.Auto.value + bombing.params["groupAttack"] = True + waypoint.tasks.append(bombing) - targets = [target] - # If the target type is a group of units, - # then target each unit in the group with a Bombing task on their position - # (It is not perfect, we should have an engage Group task instead, - # but we don't have the group ref in the model there) - # TODO : for building group, engage all the buildings as well - if isinstance(target, TheaterGroundObject): - if len(target.units) > 0: - targets = target.units - - for t in targets: - bombing = Bombing(t.position) - # If there is only one target, drop all ordnance in one pass - if len(self.waypoint.targets) == 1 and len(targets) == 1: - bombing.params["expend"] = "All" - bombing.params["weaponType"] = WeaponType.Auto.value - bombing.params["groupAttack"] = True - waypoint.tasks.append(bombing) - print(bombing) - - # Register special waypoints - self.register_special_waypoints(targets) + # Register special waypoints + self.register_special_waypoints(self.waypoint.targets) return waypoint diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 1e3c4d4d..397399ed 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -2,10 +2,11 @@ from __future__ import annotations from datetime import timedelta from enum import Enum -from typing import List, Optional, TYPE_CHECKING, Type +from typing import List, Optional, TYPE_CHECKING, Type, Union from dcs.mapping import Point from dcs.point import MovingPoint, PointAction +from dcs.unit import Unit from dcs.unittype import FlyingType from game import db @@ -107,7 +108,7 @@ class FlightWaypoint: # Only used in the waypoint list in the flight edit page. No sense # having three names. A short and long form is enough. self.description = "" - self.targets: List[MissionTarget] = [] + self.targets: List[Union[MissionTarget, Unit]] = [] self.obj_name = "" self.pretty_name = "" self.only_for_player = False diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index d125ac9e..ffb1e6ab 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -202,8 +202,7 @@ class WaypointBuilder: waypoint.pretty_name = "INGRESS on " + objective.name waypoint.description = "INGRESS on " + objective.name waypoint.name = "INGRESS" - # TODO: This seems wrong, but it's what was there before. - waypoint.targets.append(objective) + waypoint.targets = objective.strike_targets return waypoint def egress(self, position: Point, target: MissionTarget) -> FlightWaypoint: diff --git a/gen/kneeboard.py b/gen/kneeboard.py index d1de5456..231c00ca 100644 --- a/gen/kneeboard.py +++ b/gen/kneeboard.py @@ -26,7 +26,7 @@ import datetime from collections import defaultdict from dataclasses import dataclass from pathlib import Path -from typing import Dict, List, Optional, TYPE_CHECKING, Tuple +from typing import Dict, List, Optional, TYPE_CHECKING, Tuple, Iterator from PIL import Image, ImageDraw, ImageFont from dcs.mission import Mission @@ -38,7 +38,7 @@ from game.utils import meters from .aircraft import AIRCRAFT_DATA, FlightData from .airsupportgen import AwacsInfo, TankerInfo from .briefinggen import CommInfo, JtacInfo, MissionInfoGenerator -from .flights.flight import FlightWaypoint, FlightWaypointType +from .flights.flight import FlightWaypoint, FlightWaypointType, FlightType from .radios import RadioFrequency from .runways import RunwayData