Improve AI strike targeting.

We were setting up all the correct *target* waypoints but the AI doesn't
use the target waypoints; they use the targets property of the ingress
waypoint. This meant that the flight plan looked correct in the UI and
was correct for players but the tasks were set up incorrectly for the AI
because building TGOs are aggravatingly multiple TGOs with the same name
in the implementation.

Mission targets now enumerate their own strike targets so that this
mistake is harder to make in the future.

This won't be perfect, the AI is still not able to parallelize tasks and
since buildings aren't groups they can only attack one structure at a
time, but they'll now at least switch to the next target after hitting
the first one.

As a bonus, stop bombing the dead buildings.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/235
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/244
This commit is contained in:
Dan Albert 2021-05-19 23:26:59 -07:00
parent 04ebe4c68a
commit 2a77f57aa4
8 changed files with 49 additions and 34 deletions

View File

@ -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.

View File

@ -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__(

View File

@ -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

View File

@ -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__(

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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