Move mission range data into the aircraft type.

The doctrine/task limits were capturing a reasonable average for the
era, but it did a bad job for cases like the Harrier vs the Hornet,
which perform similar missions but have drastically different max
ranges. It also forced us into limiting CAS missions (even those flown
by long range aircraft like the A-10) to 50nm since helicopters could
commonly be fragged to them.

This should allow us to design campaigns without needing airfields to be
a max of ~50-100nm apart.
This commit is contained in:
Dan Albert
2021-07-17 15:31:52 -07:00
parent 04a8040292
commit c65ac5a7cf
43 changed files with 120 additions and 143 deletions

View File

@@ -3,7 +3,8 @@ from typing import Optional, Tuple
from game.commander.missionproposals import ProposedFlight
from game.inventory import GlobalAircraftInventory
from game.squadrons import AirWing, Squadron
from game.theater import ControlPoint
from game.theater import ControlPoint, MissionTarget
from game.utils import meters
from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import ClosestAirfields
from gen.flights.flight import FlightType
@@ -25,7 +26,7 @@ class AircraftAllocator:
self.is_player = is_player
def find_squadron_for_flight(
self, flight: ProposedFlight
self, target: MissionTarget, flight: ProposedFlight
) -> Optional[Tuple[ControlPoint, Squadron]]:
"""Finds aircraft suitable for the given mission.
@@ -45,17 +46,13 @@ class AircraftAllocator:
on subsequent calls. If the found aircraft are not used, the caller is
responsible for returning them to the inventory.
"""
return self.find_aircraft_for_task(flight, flight.task)
return self.find_aircraft_for_task(target, flight, flight.task)
def find_aircraft_for_task(
self, flight: ProposedFlight, task: FlightType
self, target: MissionTarget, flight: ProposedFlight, task: FlightType
) -> Optional[Tuple[ControlPoint, Squadron]]:
types = aircraft_for_task(task)
airfields_in_range = self.closest_airfields.operational_airfields_within(
flight.max_distance
)
for airfield in airfields_in_range:
for airfield in self.closest_airfields.operational_airfields:
if not airfield.is_friendly(self.is_player):
continue
inventory = self.global_inventory.for_control_point(airfield)
@@ -64,6 +61,9 @@ class AircraftAllocator:
continue
if inventory.available(aircraft) < flight.num_aircraft:
continue
distance_to_target = meters(target.distance_to(airfield))
if distance_to_target > aircraft.max_mission_range:
continue
# Valid location with enough aircraft available. Find a squadron to fit
# the role.
squadrons = self.air_wing.auto_assignable_for_task_with_type(

View File

@@ -3,7 +3,6 @@ from enum import Enum, auto
from typing import Optional
from game.theater import MissionTarget
from game.utils import Distance
from gen.flights.flight import FlightType
@@ -27,9 +26,6 @@ class ProposedFlight:
#: The number of aircraft required.
num_aircraft: int
#: The maximum distance between the objective and the departure airfield.
max_distance: Distance
#: The type of threat this flight defends against if it is an escort. Escort
#: flights will be pruned if the rest of the package is not threatened by
#: the threat they defend against. If this flight is not an escort, this

View File

@@ -44,7 +44,7 @@ class PackageBuilder:
caller should return any previously planned flights to the inventory
using release_planned_aircraft.
"""
assignment = self.allocator.find_squadron_for_flight(plan)
assignment = self.allocator.find_squadron_for_flight(self.package.target, plan)
if assignment is None:
return False
airfield, squadron = assignment

View File

@@ -83,7 +83,6 @@ class PackageFulfiller:
missing_types.add(flight.task)
purchase_order = AircraftProcurementRequest(
near=mission.location,
range=flight.max_distance,
task_capability=flight.task,
number=flight.num_aircraft,
)

View File

@@ -59,28 +59,23 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
coalition.ato.add_package(self.package)
@abstractmethod
def propose_flights(self, doctrine: Doctrine) -> None:
def propose_flights(self) -> None:
...
def propose_flight(
self,
task: FlightType,
num_aircraft: int,
max_distance: Optional[Distance],
escort_type: Optional[EscortType] = None,
) -> None:
if max_distance is None:
max_distance = Distance.inf()
self.flights.append(
ProposedFlight(task, num_aircraft, max_distance, escort_type)
)
self.flights.append(ProposedFlight(task, num_aircraft, escort_type))
@property
def asap(self) -> bool:
return False
def fulfill_mission(self, state: TheaterState) -> bool:
self.propose_flights(state.context.coalition.doctrine)
self.propose_flights()
fulfiller = PackageFulfiller(
state.context.coalition,
state.context.theater,
@@ -92,20 +87,9 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
)
return self.package is not None
def propose_common_escorts(self, doctrine: Doctrine) -> None:
self.propose_flight(
FlightType.SEAD_ESCORT,
2,
doctrine.mission_ranges.offensive,
EscortType.Sead,
)
self.propose_flight(
FlightType.ESCORT,
2,
doctrine.mission_ranges.offensive,
EscortType.AirToAir,
)
def propose_common_escorts(self) -> None:
self.propose_flight(FlightType.SEAD_ESCORT, 2, EscortType.Sead)
self.propose_flight(FlightType.ESCORT, 2, EscortType.AirToAir)
def iter_iads_ranges(
self, state: TheaterState, range_type: RangeType

View File

@@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater import MissionTarget
from gen.flights.flight import FlightType
@@ -19,8 +18,8 @@ class PlanAewc(PackagePlanningTask[MissionTarget]):
def apply_effects(self, state: TheaterState) -> None:
state.aewc_targets.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.AEWC, 1, doctrine.mission_ranges.aewc)
def propose_flights(self) -> None:
self.propose_flight(FlightType.AEWC, 1)
@property
def asap(self) -> bool:

View File

@@ -5,7 +5,6 @@ from dataclasses import dataclass
from game.commander.missionproposals import EscortType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater.theatergroundobject import NavalGroundObject
from gen.flights.flight import FlightType
@@ -22,11 +21,6 @@ class PlanAntiShip(PackagePlanningTask[NavalGroundObject]):
def apply_effects(self, state: TheaterState) -> None:
state.eliminate_ship(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.ANTISHIP, 2, doctrine.mission_ranges.offensive)
self.propose_flight(
FlightType.ESCORT,
2,
doctrine.mission_ranges.offensive,
EscortType.AirToAir,
)
def propose_flights(self) -> None:
self.propose_flight(FlightType.ANTISHIP, 2)
self.propose_flight(FlightType.ESCORT, 2, EscortType.AirToAir)

View File

@@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.transfers import CargoShip
from gen.flights.flight import FlightType
@@ -21,6 +20,6 @@ class PlanAntiShipping(PackagePlanningTask[CargoShip]):
def apply_effects(self, state: TheaterState) -> None:
state.enemy_shipping.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.ANTISHIP, 2, doctrine.mission_ranges.offensive)
self.propose_common_escorts(doctrine)
def propose_flights(self) -> None:
self.propose_flight(FlightType.ANTISHIP, 2)
self.propose_common_escorts()

View File

@@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater.theatergroundobject import VehicleGroupGroundObject
from gen.flights.flight import FlightType
@@ -21,6 +20,6 @@ class PlanBai(PackagePlanningTask[VehicleGroupGroundObject]):
def apply_effects(self, state: TheaterState) -> None:
state.eliminate_garrison(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.BAI, 2, doctrine.mission_ranges.offensive)
self.propose_common_escorts(doctrine)
def propose_flights(self) -> None:
self.propose_flight(FlightType.BAI, 2)
self.propose_common_escorts()

View File

@@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater import ControlPoint
from gen.flights.flight import FlightType
@@ -19,5 +18,5 @@ class PlanBarcap(PackagePlanningTask[ControlPoint]):
def apply_effects(self, state: TheaterState) -> None:
state.barcaps_needed[self.target] -= 1
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.BARCAP, 2, doctrine.mission_ranges.cap)
def propose_flights(self) -> None:
self.propose_flight(FlightType.BARCAP, 2)

View File

@@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater import FrontLine
from gen.flights.flight import FlightType
@@ -19,6 +18,6 @@ class PlanCas(PackagePlanningTask[FrontLine]):
def apply_effects(self, state: TheaterState) -> None:
state.vulnerable_front_lines.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.CAS, 2, doctrine.mission_ranges.cas)
self.propose_flight(FlightType.TARCAP, 2, doctrine.mission_ranges.cap)
def propose_flights(self) -> None:
self.propose_flight(FlightType.CAS, 2)
self.propose_flight(FlightType.TARCAP, 2)

View File

@@ -21,6 +21,6 @@ class PlanConvoyInterdiction(PackagePlanningTask[Convoy]):
def apply_effects(self, state: TheaterState) -> None:
state.enemy_convoys.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.BAI, 2, doctrine.mission_ranges.offensive)
self.propose_common_escorts(doctrine)
def propose_flights(self) -> None:
self.propose_flight(FlightType.BAI, 2)
self.propose_common_escorts()

View File

@@ -5,7 +5,6 @@ from dataclasses import dataclass
from game.commander.missionproposals import EscortType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater.theatergroundobject import IadsGroundObject
from gen.flights.flight import FlightType
@@ -25,8 +24,8 @@ class PlanDead(PackagePlanningTask[IadsGroundObject]):
def apply_effects(self, state: TheaterState) -> None:
state.eliminate_air_defense(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.DEAD, 2, doctrine.mission_ranges.offensive)
def propose_flights(self) -> None:
self.propose_flight(FlightType.DEAD, 2)
# Only include SEAD against SAMs that still have emitters. No need to
# suppress an EWR, and SEAD isn't useful against a SAM that no longer has a
@@ -41,18 +40,7 @@ class PlanDead(PackagePlanningTask[IadsGroundObject]):
# package is *only* threatened by the target though. Could be improved, but
# needs a decent refactor to the escort planning to do so.
if self.target.has_live_radar_sam:
self.propose_flight(FlightType.SEAD, 2, doctrine.mission_ranges.offensive)
self.propose_flight(FlightType.SEAD, 2)
else:
self.propose_flight(
FlightType.SEAD_ESCORT,
2,
doctrine.mission_ranges.offensive,
EscortType.Sead,
)
self.propose_flight(
FlightType.ESCORT,
2,
doctrine.mission_ranges.offensive,
EscortType.AirToAir,
)
self.propose_flight(FlightType.SEAD_ESCORT, 2, EscortType.Sead)
self.propose_flight(FlightType.ESCORT, 2, EscortType.AirToAir)

View File

@@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater import ControlPoint
from gen.flights.flight import FlightType
@@ -23,10 +22,8 @@ class PlanOcaStrike(PackagePlanningTask[ControlPoint]):
def apply_effects(self, state: TheaterState) -> None:
state.oca_targets.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.OCA_RUNWAY, 2, doctrine.mission_ranges.offensive)
def propose_flights(self) -> None:
self.propose_flight(FlightType.OCA_RUNWAY, 2)
if self.aircraft_cold_start:
self.propose_flight(
FlightType.OCA_AIRCRAFT, 2, doctrine.mission_ranges.offensive
)
self.propose_common_escorts(doctrine)
self.propose_flight(FlightType.OCA_AIRCRAFT, 2)
self.propose_common_escorts()

View File

@@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater import MissionTarget
from gen.flights.flight import FlightType
@@ -19,5 +18,5 @@ class PlanRefueling(PackagePlanningTask[MissionTarget]):
def apply_effects(self, state: TheaterState) -> None:
state.refueling_targets.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.REFUELING, 1, doctrine.mission_ranges.refueling)
def propose_flights(self) -> None:
self.propose_flight(FlightType.REFUELING, 1)

View File

@@ -5,7 +5,6 @@ from typing import Any
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater.theatergroundobject import TheaterGroundObject
from gen.flights.flight import FlightType
@@ -22,6 +21,6 @@ class PlanStrike(PackagePlanningTask[TheaterGroundObject[Any]]):
def apply_effects(self, state: TheaterState) -> None:
state.strike_targets.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.STRIKE, 2, doctrine.mission_ranges.offensive)
self.propose_common_escorts(doctrine)
def propose_flights(self) -> None:
self.propose_flight(FlightType.STRIKE, 2)
self.propose_common_escorts()