Settings doctrine page + streamlining (#156)

* Added a separate Doctrine page in settings with the following new options:
- Minimum number of aircraft for autoplanner to plan OCA packages against
- Airbase threat range (nmi)
- TARCAP threat buffer distance (nmi)
- AEW&C threat buffer distance (nmi)
- Theater tanker threat buffer distance (nmi)

Implemented handling for the OPFOR autoplanner aggressiveness in objectivefinder.py vulnerable_control_points().

* * Added three new options in Settings:
- Autoplanner plans refueling flights for Strike packages
- Autoplanner plans refueling flights for OCA packages
- Autoplanner plans refueling flights for DEAD packages

Fixed a bug in faction.py where F-16Ds were not correctly removed from the faction when the F-16I/F-16D mod was not selected.

* Renamed Maximum frontline length -> Maximum frontline width.
This commit is contained in:
MetalStormGhost
2023-07-02 00:54:27 +03:00
committed by GitHub
parent 4b4ec8d9ad
commit 6e37cadb84
21 changed files with 168 additions and 40 deletions

View File

@@ -48,7 +48,9 @@ class Builder(IBuilder[AewcFlightPlan, PatrollingLayout]):
orbit_heading = heading_to_threat_boundary
# Station 80nm outside the threat zone.
threat_buffer = nautical_miles(80)
threat_buffer = nautical_miles(
self.flight.coalition.game.settings.aewc_threat_buffer_min_distance
)
if self.threat_zones.threatened(location.position):
orbit_distance = distance_to_threat + threat_buffer
else:

View File

@@ -60,7 +60,9 @@ class CapBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
# 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)
min_distance_from_enemy = nautical_miles(
self.coalition.game.settings.tarcap_threat_buffer_min_distance
)
distance_to_airfield = meters(
closest_airfield.position.distance_to_point(
self.package.target.position

View File

@@ -53,7 +53,7 @@ class CasFlightPlan(PatrollingFlightPlan[CasLayout], UiZoneDisplay):
@property
def engagement_distance(self) -> Distance:
max_length = self.flight.coalition.game.settings.max_frontline_length * 1000
max_length = self.flight.coalition.game.settings.max_frontline_width * 1000
return meters(max_length) / 2
@property

View File

@@ -36,7 +36,9 @@ class Builder(IBuilder[TheaterRefuelingFlightPlan, PatrollingLayout]):
orbit_heading = heading_to_threat_boundary
# Station 70nm outside the threat zone.
threat_buffer = nautical_miles(70)
threat_buffer = nautical_miles(
self.flight.coalition.game.settings.tanker_threat_buffer_min_distance
)
if self.threat_zones.threatened(location.position):
orbit_distance = distance_to_threat + threat_buffer
else:

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import math
import operator
from collections.abc import Iterable, Iterator
from random import randint
from typing import TYPE_CHECKING, TypeVar
from game.ato.closestairfields import ClosestAirfields, ObjectiveDistanceCache
@@ -33,10 +34,6 @@ MissionTargetType = TypeVar("MissionTargetType", bound=MissionTarget)
class ObjectiveFinder:
"""Identifies potential objectives for the mission planner."""
# TODO: Merge into doctrine.
AIRFIELD_THREAT_RANGE = nautical_miles(150)
SAM_THREAT_RANGE = nautical_miles(100)
def __init__(self, game: Game, is_player: bool) -> None:
self.game = game
self.is_player = is_player
@@ -151,9 +148,18 @@ class ObjectiveFinder:
# Off-map spawn locations don't need protection.
continue
airfields_in_proximity = self.closest_airfields_to(cp)
airbase_threat_range = self.game.settings.airbase_threat_range
if (
not self.is_player
and randint(1, 100)
> self.game.settings.opfor_autoplanner_aggressiveness
):
# Chance that the airfield threat range will be evaluated as zero,
# causing the OPFOR autoplanner to plan offensively
airbase_threat_range = 0
airfields_in_threat_range = (
airfields_in_proximity.operational_airfields_within(
self.AIRFIELD_THREAT_RANGE
nautical_miles(airbase_threat_range)
)
)
for airfield in airfields_in_threat_range:

View File

@@ -44,3 +44,5 @@ class PlanDead(PackagePlanningTask[IadsGroundObject]):
self.propose_flight(FlightType.SEAD, 2)
self.propose_flight(FlightType.SEAD_ESCORT, 2, EscortType.Sead)
self.propose_flight(FlightType.ESCORT, 2, EscortType.AirToAir)
if self.target.control_point.coalition.game.settings.autoplan_tankers_for_dead:
self.propose_flight(FlightType.REFUELING, 1)

View File

@@ -29,3 +29,5 @@ class PlanOcaStrike(PackagePlanningTask[ControlPoint]):
if self.aircraft_cold_start:
self.propose_flight(FlightType.OCA_AIRCRAFT, 2)
self.propose_common_escorts()
if self.target.coalition.game.settings.autoplan_tankers_for_oca:
self.propose_flight(FlightType.REFUELING, 1)

View File

@@ -24,3 +24,5 @@ class PlanStrike(PackagePlanningTask[TheaterGroundObject]):
tgt_count = self.target.alive_unit_count
self.propose_flight(FlightType.STRIKE, min(4, (tgt_count // 2) + 1))
self.propose_common_escorts()
if self.target.coalition.game.settings.autoplan_tankers_for_strike:
self.propose_flight(FlightType.REFUELING, 1)

View File

@@ -173,7 +173,11 @@ class TheaterState(WorldState["TheaterState"]):
cp: BattlePositions.for_control_point(cp)
for cp in ordered_capturable_points
},
oca_targets=list(finder.oca_targets(min_aircraft=20)),
oca_targets=list(
finder.oca_targets(
min_aircraft=game.settings.oca_target_autoplanner_min_aircraft_count
)
),
strike_targets=list(finder.strike_targets()),
enemy_barcaps=list(game.theater.control_points_for(not player)),
threat_zones=game.threat_zone_for(not player),

View File

@@ -330,10 +330,10 @@ class Faction:
self.remove_aircraft("F-15D")
if not mod_settings.f_16_idf:
self.remove_aircraft("F-16I")
self.remove_aircraft("F_16D_52")
self.remove_aircraft("F_16D_50")
self.remove_aircraft("F_16D_50_NS")
self.remove_aircraft("F_16D_52_NS")
self.remove_aircraft("F-16D_52")
self.remove_aircraft("F-16D_50")
self.remove_aircraft("F-16D_50_NS")
self.remove_aircraft("F-16D_52_NS")
self.remove_aircraft("F-16D_Barak_30")
self.remove_aircraft("F-16D_Barak_40")
else:

View File

@@ -64,7 +64,7 @@ class FrontLineConflictDescription:
attack_heading = frontline.blue_forward_heading
position = cls.find_ground_position(
frontline.position,
settings.max_frontline_length * 1000,
settings.max_frontline_width * 1000,
attack_heading.right,
theater,
)
@@ -82,13 +82,13 @@ class FrontLineConflictDescription:
right_heading = heading.right
left_position = cls.extend_ground_position(
center_position,
int(settings.max_frontline_length * 1000 / 2),
int(settings.max_frontline_width * 1000 / 2),
left_heading,
theater,
)
right_position = cls.extend_ground_position(
center_position,
int(settings.max_frontline_length * 1000 / 2),
int(settings.max_frontline_width * 1000 / 2),
right_heading,
theater,
)

View File

@@ -46,6 +46,9 @@ PILOTS_AND_SQUADRONS_SECTION = "Pilots and Squadrons"
HQ_AUTOMATION_SECTION = "HQ Automation"
FLIGHT_PLANNER_AUTOMATION = "Flight Planner Automation"
CAMPAIGN_DOCTRINE_PAGE = "Campaign Doctrine"
DOCTRINE_DISTANCES_SECTION = "Doctrine distances"
MISSION_GENERATOR_PAGE = "Mission Generator"
GAMEPLAY_SECTION = "Gameplay"
@@ -194,6 +197,109 @@ class Settings:
"assigned to their primary task."
),
)
autoplan_tankers_for_strike: bool = boolean_option(
"Autoplanner plans refueling flights for Strike packages",
page=CAMPAIGN_DOCTRINE_PAGE,
section=GENERAL_SECTION,
default=True,
invert=False,
detail=(
"If checked, the autoplanner will include tankers in Strike packages, "
"provided the faction has access to them."
),
)
autoplan_tankers_for_oca: bool = boolean_option(
"Autoplanner plans refueling flights for OCA packages",
page=CAMPAIGN_DOCTRINE_PAGE,
section=GENERAL_SECTION,
default=True,
invert=False,
detail=(
"If checked, the autoplanner will include tankers in OCA packages, "
"provided the faction has access to them."
),
)
autoplan_tankers_for_dead: bool = boolean_option(
"Autoplanner plans refueling flights for DEAD packages",
page=CAMPAIGN_DOCTRINE_PAGE,
section=GENERAL_SECTION,
default=True,
invert=False,
detail=(
"If checked, the autoplanner will include tankers in DEAD packages, "
"provided the faction has access to them."
),
)
oca_target_autoplanner_min_aircraft_count: int = bounded_int_option(
"Minimum number of aircraft (at vulnerable airfields) for autoplanner to plan OCA packages against",
page=CAMPAIGN_DOCTRINE_PAGE,
section=GENERAL_SECTION,
default=20,
min=0,
max=100,
detail=(
"How many aircraft there has to be at an airfield for "
"the autoplanner to plan an OCA strike against it."
),
)
opfor_autoplanner_aggressiveness: int = bounded_int_option(
"OPFOR autoplanner aggressiveness (%)",
page=CAMPAIGN_DOCTRINE_PAGE,
section=GENERAL_SECTION,
default=20,
min=0,
max=100,
detail=(
"Chance (larger number -> higher chance) that the OPFOR AI "
"autoplanner will take risks and plan flights against targets "
"within threatened airspace."
),
)
airbase_threat_range: int = bounded_int_option(
"Airbase threat range (nmi)",
page=CAMPAIGN_DOCTRINE_PAGE,
section=DOCTRINE_DISTANCES_SECTION,
default=100,
min=0,
max=300,
detail=(
"Will impact both defensive (BARCAP) and offensive flights. Also has a performance impact,"
"lower threat range generally means less BARCAPs are planned."
),
)
tarcap_threat_buffer_min_distance: int = bounded_int_option(
"TARCAP threat buffer distance (nmi)",
page=CAMPAIGN_DOCTRINE_PAGE,
section=DOCTRINE_DISTANCES_SECTION,
default=20,
min=0,
max=100,
detail=("How close to known threats will the TARCAP racetrack extend."),
)
aewc_threat_buffer_min_distance: int = bounded_int_option(
"AEW&C threat buffer distance (nmi)",
page=CAMPAIGN_DOCTRINE_PAGE,
section=DOCTRINE_DISTANCES_SECTION,
default=80,
min=0,
max=300,
detail=(
"How far, at minimum, will AEW&C racetracks be planned"
"to known threat zones."
),
)
tanker_threat_buffer_min_distance: int = bounded_int_option(
"Theater tanker threat buffer distance (nmi)",
page=CAMPAIGN_DOCTRINE_PAGE,
section=DOCTRINE_DISTANCES_SECTION,
default=70,
min=0,
max=300,
detail=(
"How far, at minimum, will theater tanker racetracks be "
"planned to known threat zones."
),
)
# Pilots and Squadrons
ai_pilot_levelling: bool = boolean_option(
"Allow AI pilot leveling",
@@ -496,27 +602,14 @@ class Settings:
max=150,
)
# Mission specific
max_frontline_length: int = bounded_int_option(
"Maximum frontline length (km)",
max_frontline_width: int = bounded_int_option(
"Maximum frontline width (km)",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=80,
min=1,
max=100,
)
opfor_autoplanner_aggressiveness: int = bounded_int_option(
"OPFOR autoplanner aggressiveness (%)",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=20,
min=0,
max=100,
detail=(
"Chance (larger number -> higher chance) that the OPFOR AI "
"autoplanner will take risks and plan flights against targets "
"within threatened airspace."
),
)
game_masters_count: int = bounded_int_option(
"Number of game masters",
page=MISSION_GENERATOR_PAGE,

View File

@@ -172,7 +172,7 @@ VERSION = _build_version_string()
#: * Support for scenery objectives defined by quad zones.
#: * Campaign designers can now define almost all settings:
#: `settings:`
#: ` max_frontline_length: 25` (in km)
#: ` max_frontline_width: 25` (in km)
#: ` perf_culling_distance: 35` (in km)
#:
#: Version 10.6