Perform coalition-wide mission planning.

Mission planning on a per-control point basis lacked the context it
needed to make good decisions, and the ability to make larger missions
that pulled aircraft from multiple airfields.

The per-CP planners have been replaced in favor of a global planner
per coalition. The planner generates a list of potential missions in
order of priority and then allocates aircraft to the proposed flights
until no missions remain.

Mission planning behavior has changed:

* CAP flights will now only be generated for airfields within a
  predefined threat range of an enemy airfield.
* CAS, SEAD, and strike missions get escorts. Strike missions get a
  SEAD flight.
* CAS, SEAD, and strike missions will not be planned unless
  they have an escort available.
* Missions may originate from multiple airfields.

There's more to do:

* The range limitations imposed on the mission planner should take
  aircraft range limitations into account.
* Air superiority aircraft like the F-15 should be preferred for CAP
  over multi-role aircraft like the F/A-18 since otherwise we run the
  risk of running out of ground attack capable aircraft even though
  there are still unused aircraft.
* Mission priorities may need tuning.
* Target areas could be analyzed for potential threats, allowing
  escort flights to be optional or omitted if there is no threat to
  defend against. For example, late game a SEAD flight for a strike
  mission probably is not necessary.
* SAM threat should be judged by how close the extent of the SAM's
  range is to friendly locations, not the distance to the site itself.
  An SA-10 30 nm away is more threatening than an SA-6 25 nm away.
* Much of the planning behavior should be factored out into the
  coalition's doctrine.

But as-is this is an improvement over the existing behavior, so those
things can be follow ups.

The potential regression in behavior here is that we're no longer
planning multiple cycles of missions. Each objective will get one CAP.
I think this fits better with the turn cycle of the game, as a CAP
flight should be able to remain on station for the duration of the
turn (especially with refueling).

Note that this does break save compatibility as the old planner was a
part of the game object, and since that class is now gone it can't be
unpickled.
This commit is contained in:
Dan Albert
2020-09-26 13:17:34 -07:00
parent 1f240b02f4
commit 1e041b6249
13 changed files with 1070 additions and 814 deletions

View File

@@ -807,7 +807,7 @@ CARRIER_TAKEOFF_BAN = [
Units separated by country.
country : DCS Country name
"""
FACTIONS = {
FACTIONS: typing.Dict[str, typing.Dict[str, typing.Any]] = {
"Bluefor Modern": BLUEFOR_MODERN,
"Bluefor Cold War 1970s": BLUEFOR_COLDWAR,

View File

@@ -4,11 +4,12 @@ from game.db import REWARDS, PLAYER_BUDGET_BASE, sys
from game.inventory import GlobalAircraftInventory
from game.models.game_stats import GameStats
from gen.ato import AirTaskingOrder
from gen.flights.ai_flight_planner import FlightPlanner
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
from gen.ground_forces.ai_ground_planner import GroundPlanner
from .event import *
from .settings import Settings
COMMISION_UNIT_VARIETY = 4
COMMISION_LIMITS_SCALE = 1.5
COMMISION_LIMITS_FACTORS = {
@@ -70,7 +71,6 @@ class Game:
self.date = datetime(start_date.year, start_date.month, start_date.day)
self.game_stats = GameStats()
self.game_stats.update(self)
self.planners = {}
self.ground_planners = {}
self.informations = []
self.informations.append(Information("Game Start", "-" * 40, 0))
@@ -104,11 +104,11 @@ class Game:
self.enemy_country = "Russia"
@property
def player_faction(self):
def player_faction(self) -> Dict[str, Any]:
return db.FACTIONS[self.player_name]
@property
def enemy_faction(self):
def enemy_faction(self) -> Dict[str, Any]:
return db.FACTIONS[self.enemy_name]
def _roll(self, prob, mult):
@@ -244,16 +244,12 @@ class Game:
# Plan flights & combat for next turn
self.__culling_points = self.compute_conflicts_position()
self.planners = {}
self.ground_planners = {}
self.blue_ato.clear()
self.red_ato.clear()
CoalitionMissionPlanner(self, is_player=True).plan_missions()
CoalitionMissionPlanner(self, is_player=False).plan_missions()
for cp in self.theater.controlpoints:
if cp.has_runway():
planner = FlightPlanner(cp, self)
planner.plan_flights()
self.planners[cp.id] = planner
if cp.has_frontline:
gplanner = GroundPlanner(cp, self)
gplanner.plan_groundwar()

View File

@@ -189,15 +189,16 @@ class Operation:
side = cp.captured
if side:
country = self.current_mission.country(self.game.player_country)
ato = self.game.blue_ato
else:
country = self.current_mission.country(self.game.enemy_country)
if cp.id in self.game.planners.keys():
self.airgen.generate_flights(
cp,
country,
self.game.planners[cp.id],
self.groundobjectgen.runways
)
ato = self.game.red_ato
self.airgen.generate_flights(
cp,
country,
ato,
self.groundobjectgen.runways
)
# Generate ground units on frontline everywhere
jtacs: List[JtacInfo] = []