Don't plan BARCAPs so aggressively.

Limit the commit range of a BARCAP to halfway to the closest enemy
airbase so that they don't become offensive missions.

This has the side effect of largely reducing long retreats to hold
points from front line airfields, since the package can get much closer
without being at risk of engagement by an enemy BARCAP.

Fixes https://github.com/Khopa/dcs_liberation/issues/742.
This commit is contained in:
Dan Albert 2021-01-16 14:25:17 -08:00
parent 1a2475dc25
commit 5da4cace94
2 changed files with 66 additions and 16 deletions

View File

@ -1,7 +1,7 @@
from __future__ import annotations
from functools import singledispatchmethod
from typing import TYPE_CHECKING, Union
from typing import Optional, TYPE_CHECKING, Union
from dcs.mapping import Point as DcsPoint
from shapely.geometry import (
@ -13,7 +13,9 @@ from shapely.geometry import (
from shapely.geometry.base import BaseGeometry
from shapely.ops import nearest_points, unary_union
from game.utils import nautical_miles
from game.theater import ControlPoint
from game.utils import Distance, meters, nautical_miles
from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.flights.flight import Flight
if TYPE_CHECKING:
@ -78,6 +80,39 @@ class ThreatZones:
self.dcs_to_shapely_point(p.position) for p in flight.points
)))
@classmethod
def closest_enemy_airbase(cls, location: ControlPoint,
max_distance: Distance) -> Optional[ControlPoint]:
airfields = ObjectiveDistanceCache.get_closest_airfields(location)
for airfield in airfields.airfields_within(max_distance):
if airfield.captured != location.captured:
return airfield
return None
@classmethod
def barcap_threat_range(cls, game: Game,
control_point: ControlPoint) -> Distance:
doctrine = game.faction_for(control_point.captured).doctrine
cap_threat_range = (doctrine.cap_max_distance_from_cp +
doctrine.cap_engagement_range)
opposing_airfield = cls.closest_enemy_airbase(control_point,
cap_threat_range * 2)
if opposing_airfield is None:
return cap_threat_range
airfield_distance = meters(
opposing_airfield.position.distance_to_point(control_point.position)
)
# BARCAPs should not commit further than halfway to the closest enemy
# airfield (with some breathing room) to avoid those missions becoming
# offensive. For dissimilar doctrines we could weight this so that, as
# an example, modern US goes no closer than 70% of the way to the WW2
# German base, and the Germans go no closer than 30% of the way to the
# US base, but for now equal weighting is fine.
max_distance = airfield_distance * 0.45
return min(cap_threat_range, max_distance)
@classmethod
def for_faction(cls, game: Game, player: bool) -> ThreatZones:
"""Generates the threat zones projected by the given coalition.
@ -92,8 +127,6 @@ class ThreatZones:
zone belongs to the player, it is the zone that will be avoided by
the enemy and vice versa.
"""
doctrine = game.faction_for(player).doctrine
airbases = []
air_defenses = []
for control_point in game.theater.controlpoints:
@ -102,8 +135,7 @@ class ThreatZones:
if control_point.runway_is_operational():
point = ShapelyPoint(control_point.position.x,
control_point.position.y)
cap_threat_range = (doctrine.cap_max_distance_from_cp +
doctrine.cap_engagement_range)
cap_threat_range = cls.barcap_threat_range(game, control_point)
airbases.append(point.buffer(cap_threat_range.meters))
for tgo in control_point.ground_objects:

View File

@ -17,6 +17,7 @@ from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple
from dcs.mapping import Point
from dcs.unit import Unit
from shapely.geometry import Point as ShapelyPoint
from game.data.doctrine import Doctrine
from game.theater import (
@ -976,7 +977,7 @@ class FlightPlanBuilder:
if isinstance(location, FrontLine):
raise InvalidObjectiveLocation(flight.flight_type, location)
start, end = self.racetrack_for_objective(location)
start, end = self.racetrack_for_objective(location, barcap=True)
patrol_alt = meters(random.randint(
int(self.doctrine.min_patrol_altitude.meters),
int(self.doctrine.max_patrol_altitude.meters)
@ -1037,8 +1038,8 @@ class FlightPlanBuilder:
divert=builder.divert(flight.divert)
)
def racetrack_for_objective(self,
location: MissionTarget) -> Tuple[Point, Point]:
def racetrack_for_objective(self, location: MissionTarget,
barcap: bool) -> Tuple[Point, Point]:
closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
for airfield in closest_cache.closest_airfields:
# If the mission is a BARCAP of an enemy airfield, find the *next*
@ -1055,12 +1056,28 @@ class FlightPlanBuilder:
closest_airfield.position
)
min_distance_from_enemy = nautical_miles(20)
distance_to_airfield = meters(
closest_airfield.position.distance_to_point(
self.package.target.position
))
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
position = ShapelyPoint(self.package.target.position.x,
self.package.target.position.y)
if barcap:
# BARCAPs should remain far enough back from the enemy that their
# commit range does not enter the enemy's threat zone. Include a 5nm
# buffer.
distance_to_no_fly = meters(
position.distance(self.threat_zones.all)
) - self.doctrine.cap_engagement_range - nautical_miles(5)
else:
# Other race tracks (TARCAPs, currently) just try to keep some
# distance from the nearest enemy airbase, but since they are by
# definition in enemy territory they can't avoid the threat zone
# without being useless.
min_distance_from_enemy = nautical_miles(20)
distance_to_airfield = meters(
closest_airfield.position.distance_to_point(
self.package.target.position
))
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
min_cap_distance = min(self.doctrine.cap_min_distance_from_cp,
distance_to_no_fly)
max_cap_distance = min(self.doctrine.cap_max_distance_from_cp,
@ -1125,7 +1142,8 @@ class FlightPlanBuilder:
orbit0p, orbit1p = self.racetrack_for_frontline(
flight.departure.position, location)
else:
orbit0p, orbit1p = self.racetrack_for_objective(location)
orbit0p, orbit1p = self.racetrack_for_objective(location,
barcap=False)
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
return TarCapFlightPlan(