From 6e37cadb84d1612da8597436f5d8830a6e0bbba7 Mon Sep 17 00:00:00 2001 From: MetalStormGhost <89945461+MetalStormGhost@users.noreply.github.com> Date: Sun, 2 Jul 2023 00:54:27 +0300 Subject: [PATCH] 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. --- changelog.md | 14 +- game/ato/flightplans/aewc.py | 4 +- game/ato/flightplans/capbuilder.py | 4 +- game/ato/flightplans/cas.py | 2 +- game/ato/flightplans/theaterrefueling.py | 4 +- game/commander/objectivefinder.py | 16 ++- game/commander/tasks/primitive/dead.py | 2 + game/commander/tasks/primitive/oca.py | 2 + game/commander/tasks/primitive/strike.py | 2 + game/commander/theaterstate.py | 6 +- game/factions/faction.py | 8 +- .../frontlineconflictdescription.py | 6 +- game/settings/settings.py | 123 +++++++++++++++--- game/version.py | 2 +- qt_ui/uiconstants.py | 1 + .../campaigns/Operation-Desert-Sabre.yaml | 2 +- .../campaigns/WRL_AssaultonDamascus.yaml | 2 +- resources/campaigns/WRL_Battle4Georgia.yaml | 2 +- resources/campaigns/WRL_Battle4area51.yaml | 2 +- resources/campaigns/WRL_Kutaisi2Vaziani.yaml | 2 +- resources/campaigns/WRL_PG_Wargames.yaml | 2 +- 21 files changed, 168 insertions(+), 40 deletions(-) diff --git a/changelog.md b/changelog.md index 0b3d4f7d..5de55c00 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,17 @@ * **[Campaign Design]** Ability to define SCENERY REMOVE OBJECTS ZONE triggers with the roadbase objects in campaign miz. This might not work reliably in multiplayer due to DCS issues. FARPs can be used to remove scenery objects in multiplayer. * **[Campaign Management]** Improved squadron retreat logic at longer ranges. * **[Options]** Ability to load & save your settings. +* **[Options]** 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) +* **[Options]** Improved the option to configure OPFOR autoplanner aggressiveness. The AI might now take even more risks and plan missions against defended targets. +* 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 * **[UI]** Added fuel selector in flight's edit window. * **[Plugins]** Expose Splash Damage's "game_messages" option and set its default to false. * **[Mission Generation]** Improved AI SEAD capabilities, allowing for mixed loadouts using Decoys, ARMs & ASMs. @@ -33,13 +44,14 @@ * **[Plugins]** Added "EWR Jammer" plugin (only for humans, may change in the future). * **[Campaign]** New campaign (Operation Desert Sabre) by Chimiste * **[Plugins]** Updated CTLD to latest released version +* **[Options]** Renamed Maximum frontline length -> Maximum frontline width. ## Fixes * **[New Game Wizard]** Settings would not persist when going back to a previous page (obsolete due to overhaul). * **[Mission Generation]** Unused aircraft are no longer claimed, fixing a bug where these aircraft would no longer be available after aborting the mission. * **[Mission Generation]** Fixed (potential) bug in helipad assignments at FOBs/FARPs. * **[Mission Generation]** Fix AI immediately returning to base when forced to air-start due to insufficient parking space. - +* **[Modding]** Fixed a bug where F-16Ds were not correctly removed from the faction when the F-16I/F-16D mod was not selected # Retribution v1.1.1 (hotfix) diff --git a/game/ato/flightplans/aewc.py b/game/ato/flightplans/aewc.py index 6da848e5..fef75af0 100644 --- a/game/ato/flightplans/aewc.py +++ b/game/ato/flightplans/aewc.py @@ -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: diff --git a/game/ato/flightplans/capbuilder.py b/game/ato/flightplans/capbuilder.py index 0d8ad965..c121fc61 100644 --- a/game/ato/flightplans/capbuilder.py +++ b/game/ato/flightplans/capbuilder.py @@ -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 diff --git a/game/ato/flightplans/cas.py b/game/ato/flightplans/cas.py index 0423b1ca..0011bd9a 100644 --- a/game/ato/flightplans/cas.py +++ b/game/ato/flightplans/cas.py @@ -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 diff --git a/game/ato/flightplans/theaterrefueling.py b/game/ato/flightplans/theaterrefueling.py index 7ed81932..d9d151bc 100644 --- a/game/ato/flightplans/theaterrefueling.py +++ b/game/ato/flightplans/theaterrefueling.py @@ -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: diff --git a/game/commander/objectivefinder.py b/game/commander/objectivefinder.py index 9ad4712f..bb9aca1c 100644 --- a/game/commander/objectivefinder.py +++ b/game/commander/objectivefinder.py @@ -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: diff --git a/game/commander/tasks/primitive/dead.py b/game/commander/tasks/primitive/dead.py index 0ad9b7ed..6991066e 100644 --- a/game/commander/tasks/primitive/dead.py +++ b/game/commander/tasks/primitive/dead.py @@ -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) diff --git a/game/commander/tasks/primitive/oca.py b/game/commander/tasks/primitive/oca.py index 298fae1f..34767073 100644 --- a/game/commander/tasks/primitive/oca.py +++ b/game/commander/tasks/primitive/oca.py @@ -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) diff --git a/game/commander/tasks/primitive/strike.py b/game/commander/tasks/primitive/strike.py index 4e142e16..cb537634 100644 --- a/game/commander/tasks/primitive/strike.py +++ b/game/commander/tasks/primitive/strike.py @@ -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) diff --git a/game/commander/theaterstate.py b/game/commander/theaterstate.py index 3bd05f73..ca60c311 100644 --- a/game/commander/theaterstate.py +++ b/game/commander/theaterstate.py @@ -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), diff --git a/game/factions/faction.py b/game/factions/faction.py index 377d5a1b..ebaac9d7 100644 --- a/game/factions/faction.py +++ b/game/factions/faction.py @@ -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: diff --git a/game/missiongenerator/frontlineconflictdescription.py b/game/missiongenerator/frontlineconflictdescription.py index b9e9b2d1..2b92c36b 100644 --- a/game/missiongenerator/frontlineconflictdescription.py +++ b/game/missiongenerator/frontlineconflictdescription.py @@ -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, ) diff --git a/game/settings/settings.py b/game/settings/settings.py index 17405774..24189737 100644 --- a/game/settings/settings.py +++ b/game/settings/settings.py @@ -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, diff --git a/game/version.py b/game/version.py index 9eb3fa54..752ad65f 100644 --- a/game/version.py +++ b/game/version.py @@ -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 diff --git a/qt_ui/uiconstants.py b/qt_ui/uiconstants.py index 72d77703..1f53cd05 100644 --- a/qt_ui/uiconstants.py +++ b/qt_ui/uiconstants.py @@ -77,6 +77,7 @@ def load_icons(): "./resources/ui/misc/" + get_theme_icons() + "/money_icon.png" ) ICONS["Campaign Management"] = ICONS["Money"] + ICONS["Campaign Doctrine"] = QPixmap("./resources/ui/misc/blue-sam.png") ICONS["PassTurn"] = QPixmap( "./resources/ui/misc/" + get_theme_icons() + "/hourglass.png" ) diff --git a/resources/campaigns/Operation-Desert-Sabre.yaml b/resources/campaigns/Operation-Desert-Sabre.yaml index 0f766a5e..ac4dc1ec 100644 --- a/resources/campaigns/Operation-Desert-Sabre.yaml +++ b/resources/campaigns/Operation-Desert-Sabre.yaml @@ -15,7 +15,7 @@ recommended_player_income_multiplier: 1.0 recommended_enemy_income_multiplier: 1.0 advanced_iads: false settings: - max_frontline_length: 30 + max_frontline_width: 30 player_income_multiplier: 1.5 squadrons: CVN-74 John Stennis: diff --git a/resources/campaigns/WRL_AssaultonDamascus.yaml b/resources/campaigns/WRL_AssaultonDamascus.yaml index 2b5c8333..f125c765 100644 --- a/resources/campaigns/WRL_AssaultonDamascus.yaml +++ b/resources/campaigns/WRL_AssaultonDamascus.yaml @@ -15,7 +15,7 @@ recommended_player_income_multiplier: 5.0 recommended_enemy_income_multiplier: 1.0 advanced_iads: false settings: - max_frontline_length: 30 + max_frontline_width: 30 squadrons: CVN-74 John Stennis: - primary: BARCAP diff --git a/resources/campaigns/WRL_Battle4Georgia.yaml b/resources/campaigns/WRL_Battle4Georgia.yaml index 5d9922f8..a13b5b31 100644 --- a/resources/campaigns/WRL_Battle4Georgia.yaml +++ b/resources/campaigns/WRL_Battle4Georgia.yaml @@ -15,7 +15,7 @@ recommended_player_income_multiplier: 5.0 recommended_enemy_income_multiplier: 1.0 advanced_iads: false settings: - max_frontline_length: 30 + max_frontline_width: 30 squadrons: Blue CV-1: - primary: BARCAP diff --git a/resources/campaigns/WRL_Battle4area51.yaml b/resources/campaigns/WRL_Battle4area51.yaml index ef55ef50..c36cbf52 100644 --- a/resources/campaigns/WRL_Battle4area51.yaml +++ b/resources/campaigns/WRL_Battle4area51.yaml @@ -14,7 +14,7 @@ recommended_enemy_money: 2000 recommended_player_income_multiplier: 5.0 recommended_enemy_income_multiplier: 1.0 settings: - max_frontline_length: 20 + max_frontline_width: 20 advanced_iads: true # Campaign has connection_nodes / power_sources / command_centers iads_config: #COMMAND diff --git a/resources/campaigns/WRL_Kutaisi2Vaziani.yaml b/resources/campaigns/WRL_Kutaisi2Vaziani.yaml index c59690f2..642aa673 100644 --- a/resources/campaigns/WRL_Kutaisi2Vaziani.yaml +++ b/resources/campaigns/WRL_Kutaisi2Vaziani.yaml @@ -14,7 +14,7 @@ recommended_enemy_money: 2000 recommended_player_income_multiplier: 5.0 recommended_enemy_income_multiplier: 1.0 settings: - max_frontline_length: 30 + max_frontline_width: 30 advanced_iads: true # Campaign has connection_nodes / power_sources / command_centers iads_config: #REDFOR BASE DEFENSE diff --git a/resources/campaigns/WRL_PG_Wargames.yaml b/resources/campaigns/WRL_PG_Wargames.yaml index 0bbb30f1..d66ea10c 100644 --- a/resources/campaigns/WRL_PG_Wargames.yaml +++ b/resources/campaigns/WRL_PG_Wargames.yaml @@ -15,7 +15,7 @@ recommended_player_income_multiplier: 5.0 recommended_enemy_income_multiplier: 1.0 advanced_iads: false settings: - max_frontline_length: 30 + max_frontline_width: 30 squadrons: #Bandar Abbas Intl 2: