diff --git a/changelog.md b/changelog.md index 23eb1f62..fc2d62b6 100644 --- a/changelog.md +++ b/changelog.md @@ -135,6 +135,7 @@ Saves from 6.x are not compatible with 7.0. * **[Mission Generation]** Both A-10C modules now use separate radios for inter- and intra-flight comms (similar to other modern aircraft). * **[Modding]** Updated Community A-4E-C mod version support to 2.1.0 release. * **[Modding]** Add support for VSN F-4B and F-4C mod. +* **[Modding]** Aircraft task capabilities and preferred aircraft for each task are now moddable in the aircraft unit yaml files. Each aircraft has a weight per task. Higher weights are given higher preference. ## Fixes diff --git a/game/ato/ai_flight_planner_db.py b/game/ato/ai_flight_planner_db.py deleted file mode 100644 index 5eb888e7..00000000 --- a/game/ato/ai_flight_planner_db.py +++ /dev/null @@ -1,805 +0,0 @@ -import logging -from collections.abc import Sequence -from typing import Type - -from dcs.helicopters import ( - AH_1W, - AH_64A, - AH_64D, - AH_64D_BLK_II, - CH_47D, - CH_53E, - Ka_50, - Ka_50_3, - Mi_24P, - Mi_24V, - Mi_26, - Mi_28N, - Mi_8MT, - OH_58D, - SA342L, - SA342M, - SH_60B, - UH_1H, - UH_60A, -) -from dcs.planes import ( - AJS37, - AV8BNA, - A_10A, - A_10C, - A_10C_2, - A_20G, - A_50, - An_26B, - B_17G, - B_1B, - B_52H, - Bf_109K_4, - C_101CC, - C_130, - C_17A, - C_47, - E_2C, - E_3A, - FA_18C_hornet, - FW_190A8, - FW_190D9, - F_117A, - F_14A, - F_14A_135_GR, - F_14B, - F_15C, - F_15E, - F_16A, - F_16A_MLU, - F_16C_50, - F_4E, - F_5E_3, - F_86F_Sabre, - H_6J, - IL_76MD, - IL_78M, - I_16, - JF_17, - J_11A, - Ju_88A4, - KC130, - KC135MPRS, - KC_135, - KJ_2000, - L_39ZA, - MQ_9_Reaper, - M_2000C, - MiG_15bis, - MiG_19P, - MiG_21Bis, - MiG_23MLD, - MiG_25PD, - MiG_27K, - MiG_29A, - MiG_29G, - MiG_29S, - MiG_31, - Mirage_2000_5, - Mirage_F1B, - Mirage_F1BE, - Mirage_F1CE, - Mirage_F1EE, - Mirage_F1EQ, - Mirage_F1M_CE, - Mirage_F1M_EE, - Mirage_F1C_200, - Mirage_F1CT, - MosquitoFBMkVI, - P_47D_30, - P_47D_30bl1, - P_47D_40, - P_51D, - P_51D_30_NA, - RQ_1A_Predator, - S_3B, - S_3B_Tanker, - SpitfireLFMkIX, - SpitfireLFMkIXCW, - Su_17M4, - Su_24M, - Su_25, - Su_25T, - Su_25TM, - Su_27, - Su_30, - Su_33, - Su_34, - Tornado_GR4, - Tornado_IDS, - Tu_142, - Tu_160, - Tu_22M3, - Tu_95MS, - WingLoong_I, - Yak_40, - MB_339A, -) -from dcs.unittype import FlyingType - -from game.dcs.aircrafttype import AircraftType -from pydcs_extensions.a4ec.a4ec import A_4E_C -from pydcs_extensions.a6a.a6a import VSN_A6A -from pydcs_extensions.a7e.a7e import A_7E -from pydcs_extensions.SWPack.SWPack import ( - TIE, - HUNTER, - TIE_INTER, - AWING, - XWING, - YWING, - CORVETTE, - tie_bomber_2, - naboo_starfighter, -) -from pydcs_extensions.f100.f100 import VSN_F100 -from pydcs_extensions.f104.f104 import VSN_F104C, VSN_F104G, VSN_F104S, VSN_F104S_AG -from pydcs_extensions.f105.f105 import VSN_F105D, VSN_F105G -from pydcs_extensions.f15d.f15d import F_15D -from pydcs_extensions.f16i_idf.f16i_idf import ( - F_16D_50, - F_16D_52, - F_16D_50_NS, - F_16D_52_NS, - F_16D_Barak_30, - F_16D_Barak_40, - F_16I, -) -from pydcs_extensions.f22a.f22a import F_22A -from pydcs_extensions.f4b.f4b import VSN_F4B, VSN_F4C -from pydcs_extensions.f84g.f84g import VSN_F84G -from pydcs_extensions.fa18efg.fa18efg import FA_18E, FA_18F, EA_18G -from pydcs_extensions.hercules.hercules import Hercules -from pydcs_extensions.jas39.jas39 import JAS39Gripen, JAS39Gripen_BVR, JAS39Gripen_AG -from pydcs_extensions.su30.su30 import Su_30MKA, Su_30MKI, Su_30MKM, Su_30SM -from pydcs_extensions.su57.su57 import Su_57 -from pydcs_extensions.ov10a.ov10a import Bronco_OV_10A -from pydcs_extensions.uh60l.uh60l import KC130J, UH_60L -from .flighttype import FlightType - -# All aircraft lists are in priority order. Aircraft higher in the list will be -# preferred over those lower in the list. -# TODO: These lists really ought to be era (faction) dependent. -# Factions which have F-5s, F-86s, and A-4s will should prefer F-5s for CAP, but -# factions that also have F-4s should not. - -# Used for CAP, Escort, and intercept if there is not a specialised aircraft available -ESCORT_CAPABLE = [ - TIE, - HUNTER, - XWING, - AWING, - CORVETTE, - TIE_INTER, - naboo_starfighter, - Su_57, - F_22A, - F_15C, - F_15D, - F_14B, - F_14A_135_GR, - F_14A, - Su_33, - J_11A, - Su_30, - Su_30MKA, - Su_30MKI, - Su_30MKM, - Su_30SM, - Su_27, - MiG_29S, - F_16C_50, - F_16I, - F_16D_Barak_40, - F_16D_Barak_30, - F_16D_50, - F_16D_50_NS, - F_16D_52, - F_16D_52_NS, - FA_18E, - FA_18F, - FA_18C_hornet, - JF_17, - JAS39Gripen_BVR, - JAS39Gripen, - F_16A_MLU, - F_16A, - F_4E, - VSN_F4C, - VSN_F4B, - MiG_31, - MiG_25PD, - MiG_29G, - MiG_29A, - MiG_23MLD, - MiG_21Bis, - Mirage_2000_5, - Mirage_F1B, - Mirage_F1BE, - Mirage_F1CE, - Mirage_F1EE, - Mirage_F1EQ, - Mirage_F1M_CE, - Mirage_F1M_EE, - Mirage_F1C_200, - Mirage_F1CT, - F_15E, - M_2000C, - F_5E_3, - VSN_F104S, - VSN_F104G, - VSN_F104C, - MiG_19P, - VSN_F100, - A_4E_C, - F_86F_Sabre, - MiG_15bis, - C_101CC, - VSN_F84G, - P_51D_30_NA, - P_51D, - SpitfireLFMkIXCW, - SpitfireLFMkIX, - MosquitoFBMkVI, - Bf_109K_4, - FW_190D9, - FW_190A8, - P_47D_30, - P_47D_30bl1, - P_47D_40, - I_16, -] - -# Types to be skipped for escorts -CAP_CAPABLE = ESCORT_CAPABLE + [ - L_39ZA, -] - - -# Used for CAS (Close air support) and BAI (Battlefield Interdiction) -CAS_CAPABLE = [ - tie_bomber_2, - YWING, - A_10C_2, - A_10C, - Hercules, - Su_34, - Su_25TM, - Su_25T, - Su_25, - F_15D, - F_15E, - F_16C_50, - F_16I, - F_16D_Barak_40, - F_16D_Barak_30, - F_16D_50, - F_16D_50_NS, - F_16D_52, - F_16D_52_NS, - FA_18E, - FA_18F, - FA_18C_hornet, - Tornado_GR4, - Tornado_IDS, - JAS39Gripen_AG, - JF_17, - AV8BNA, - A_10A, - F_16A_MLU, - F_16A, - B_1B, - A_7E, - A_4E_C, - F_14B, - F_14A_135_GR, - AJS37, - Su_24M, - Su_17M4, - Su_33, - F_4E, - S_3B, - Su_30, - Su_30MKA, - Su_30MKI, - Su_30MKM, - Su_30SM, - MiG_29S, - MiG_27K, - MiG_29A, - MiG_21Bis, - AH_64D_BLK_II, - AH_64D, - AH_64A, - AH_1W, - OH_58D, - SA342M, - SA342L, - Ka_50, - Ka_50_3, - Mi_28N, - Mi_24P, - Mi_24V, - Mi_8MT, - H_6J, - MiG_19P, - MiG_15bis, - M_2000C, - Mirage_F1B, - Mirage_F1BE, - Mirage_F1CE, - Mirage_F1EE, - Mirage_F1EQ, - Mirage_F1M_CE, - Mirage_F1M_EE, - Mirage_F1CT, - F_5E_3, - F_86F_Sabre, - C_101CC, - L_39ZA, - MB_339A, - Bronco_OV_10A, - UH_1H, - A_20G, - Ju_88A4, - P_47D_40, - P_47D_30bl1, - P_47D_30, - P_51D_30_NA, - P_51D, - SpitfireLFMkIXCW, - SpitfireLFMkIX, - MosquitoFBMkVI, - I_16, - Bf_109K_4, - FW_190D9, - FW_190A8, - WingLoong_I, - MQ_9_Reaper, - RQ_1A_Predator, - VSN_A6A, - VSN_F4C, - VSN_F4B, - VSN_F100, - VSN_F105G, - VSN_F105D, - VSN_F104S_AG, - VSN_F104G, - VSN_F104C, - VSN_F84G, -] - - -# Aircraft used for SEAD and SEAD Escort tasks. Must be capable of the CAS DCS task. -SEAD_ESCORT_CAPABLE = [ - JF_17, - F_16C_50, - F_16I, - F_16D_Barak_40, - F_16D_Barak_30, - F_16D_50, - F_16D_50_NS, - F_16D_52, - F_16D_52_NS, - EA_18G, - FA_18E, - FA_18F, - FA_18C_hornet, - Tornado_IDS, - Su_25T, - Su_25TM, - F_4E, - A_7E, - A_4E_C, - JAS39Gripen_AG, - AV8BNA, - Su_24M, - Su_17M4, - Su_34, - Su_30, - Su_30MKA, - Su_30MKI, - Su_30MKM, - Su_30SM, - MiG_27K, - Tornado_GR4, - VSN_F105G, - VSN_F100, -] - - -SEAD_CAPABLE = SEAD_ESCORT_CAPABLE + [ - F_14B, - F_14A_135_GR, -] - -# Aircraft used for DEAD tasks. Must be capable of the CAS DCS task. -DEAD_CAPABLE = SEAD_CAPABLE + [ - YWING, - tie_bomber_2, - AJS37, - F_16A_MLU, - F_16A, - F_15E, - JAS39Gripen_AG, - B_1B, - B_52H, - Tu_160, - Tu_95MS, - H_6J, - A_20G, - Ju_88A4, - P_47D_40, - P_47D_30bl1, - P_47D_30, - P_51D_30_NA, - P_51D, - Bronco_OV_10A, - SpitfireLFMkIXCW, - SpitfireLFMkIX, - MosquitoFBMkVI, - Bf_109K_4, - FW_190D9, - FW_190A8, - VSN_A6A, - VSN_F105D, - VSN_F104S_AG, - VSN_F104G, - VSN_F104C, - VSN_F100, - VSN_F84G, -] - - -# Aircraft used for Strike mission -STRIKE_CAPABLE = [ - YWING, - tie_bomber_2, - F_117A, - B_1B, - B_52H, - Tu_160, - Tu_95MS, - Tu_22M3, - H_6J, - F_15D, - F_15E, - AJS37, - Tornado_GR4, - F_16C_50, - F_16I, - F_16D_Barak_40, - F_16D_Barak_30, - F_16D_50, - F_16D_50_NS, - F_16D_52, - F_16D_52_NS, - FA_18E, - FA_18F, - FA_18C_hornet, - AV8BNA, - JF_17, - F_16A_MLU, - F_16A, - F_14B, - F_14A_135_GR, - JAS39Gripen_AG, - Tornado_IDS, - Su_17M4, - Su_24M, - Su_25TM, - Su_25T, - Su_25, - Su_34, - Su_33, - Su_30, - Su_30MKA, - Su_30MKI, - Su_30MKM, - Su_30SM, - Su_27, - MiG_29S, - MiG_29G, - MiG_29A, - F_4E, - A_7E, - A_10C_2, - A_10C, - VSN_F4C, - VSN_F4B, - S_3B, - A_4E_C, - Bronco_OV_10A, - M_2000C, - Mirage_F1B, - Mirage_F1BE, - Mirage_F1CE, - Mirage_F1EE, - Mirage_F1EQ, - Mirage_F1M_CE, - Mirage_F1M_EE, - Mirage_F1CT, - MiG_27K, - MiG_21Bis, - MiG_15bis, - F_5E_3, - F_86F_Sabre, - C_101CC, - L_39ZA, - MB_339A, - B_17G, - A_20G, - Ju_88A4, - P_47D_40, - P_47D_30bl1, - P_47D_30, - P_51D_30_NA, - P_51D, - SpitfireLFMkIXCW, - SpitfireLFMkIX, - MosquitoFBMkVI, - Bf_109K_4, - FW_190D9, - FW_190A8, - VSN_A6A, - VSN_F100, - VSN_F104S_AG, - VSN_F104G, - VSN_F104C, - VSN_F105G, - VSN_F105D, - VSN_F84G, -] - - -ANTISHIP_CAPABLE = [ - AJS37, - Tu_142, - Tu_22M3, - H_6J, - FA_18E, - FA_18F, - FA_18C_hornet, - JAS39Gripen_AG, - F_16A_MLU, - F_16A, - Su_24M, - Su_17M4, - JF_17, - Su_34, - Su_30, - Su_30MKA, - Su_30MKI, - Su_30MKM, - Su_30SM, - Tornado_IDS, - Tornado_GR4, - AV8BNA, - S_3B, - A_7E, - A_20G, - Ju_88A4, - MosquitoFBMkVI, - C_101CC, - SH_60B, -] - - -# This list does not "inherit" from the strike list because some strike aircraft can -# only carry guided weapons, and the AI cannot do runway attack with dguided weapons. -# https://github.com/dcs-liberation/dcs_liberation/issues/1703 -RUNWAY_ATTACK_CAPABLE = [ - JF_17, - Tornado_IDS, - M_2000C, - Mirage_F1B, - Mirage_F1BE, - Mirage_F1CE, - Mirage_F1EE, - Mirage_F1EQ, - Mirage_F1M_CE, - Mirage_F1M_EE, - Mirage_F1CT, - H_6J, - B_1B, - B_52H, - Tu_22M3, - H_6J, - F_15E, - AJS37, - F_16C_50, - F_16I, - F_16D_Barak_40, - F_16D_Barak_30, - F_16D_50, - F_16D_50_NS, - F_16D_52, - F_16D_52_NS, - FA_18E, - FA_18F, - FA_18C_hornet, - AV8BNA, - JF_17, - F_16A_MLU, - F_16A, - F_14B, - F_14A_135_GR, - JAS39Gripen_AG, - Tornado_IDS, - Su_17M4, - Su_24M, - Su_25TM, - Su_25T, - Su_25, - Su_34, - Su_33, - Su_30, - Su_30MKA, - Su_30MKI, - Su_30MKM, - Su_30SM, - Su_27, - MiG_29S, - MiG_29G, - MiG_29A, - F_4E, - A_10C_2, - A_10C, - VSN_F4C, - VSN_F4B, - S_3B, - A_7E, - A_4E_C, - Bronco_OV_10A, - M_2000C, - MiG_27K, - MiG_21Bis, - MiG_15bis, - F_5E_3, - F_86F_Sabre, - C_101CC, - L_39ZA, - MB_339A, - B_17G, - A_20G, - Ju_88A4, - P_47D_40, - P_47D_30bl1, - P_47D_30, - P_51D_30_NA, - P_51D, - SpitfireLFMkIXCW, - SpitfireLFMkIX, - MosquitoFBMkVI, - Bf_109K_4, - FW_190D9, - FW_190A8, - VSN_A6A, - VSN_F105G, - VSN_F105D, - VSN_F104S_AG, - VSN_F104G, - VSN_F104C, - VSN_F100, -] - -# For any aircraft that isn't necessarily directly involved in strike -# missions in a direct combat sense, but can transport objects and infantry. -TRANSPORT_CAPABLE = [ - C_17A, - Hercules, - C_130, - C_47, - IL_76MD, - An_26B, - Yak_40, - CH_53E, - CH_47D, - UH_60L, - SH_60B, - UH_60A, - UH_1H, - Mi_8MT, - Mi_8MT, - Mi_26, -] - -AIR_ASSAULT_CAPABLE = [ - CH_53E, - CH_47D, - UH_60L, - SH_60B, - UH_60A, - UH_1H, - Mi_8MT, - Mi_26, - Mi_24P, - Mi_24V, - Hercules, -] - -DRONES = [MQ_9_Reaper, RQ_1A_Predator, WingLoong_I] - -AEWC_CAPABLE = [ - E_3A, - E_2C, - A_50, - KJ_2000, -] - -# Priority is given to the tankers that can carry the most fuel. -REFUELING_CAPABALE = [ - KC_135, - KC135MPRS, - IL_78M, - KC130J, - KC130, - S_3B_Tanker, -] - - -def dcs_types_for_task(task: FlightType) -> Sequence[Type[FlyingType]]: - cap_missions = ( - FlightType.BARCAP, - FlightType.INTERCEPTION, - FlightType.SWEEP, - FlightType.TARCAP, - ) - if task in cap_missions: - return CAP_CAPABLE - elif task == FlightType.ANTISHIP: - return ANTISHIP_CAPABLE - elif task == FlightType.BAI: - return CAS_CAPABLE - elif task == FlightType.CAS: - return CAS_CAPABLE - elif task == FlightType.SEAD: - return SEAD_CAPABLE - elif task == FlightType.SEAD_ESCORT: - return SEAD_ESCORT_CAPABLE - elif task == FlightType.DEAD: - return DEAD_CAPABLE - elif task == FlightType.OCA_AIRCRAFT: - return CAS_CAPABLE - elif task == FlightType.OCA_RUNWAY: - return RUNWAY_ATTACK_CAPABLE - elif task == FlightType.STRIKE: - return STRIKE_CAPABLE - elif task == FlightType.ESCORT: - return ESCORT_CAPABLE - elif task == FlightType.AEWC: - return AEWC_CAPABLE - elif task == FlightType.REFUELING: - return REFUELING_CAPABALE - elif task == FlightType.TRANSPORT: - return TRANSPORT_CAPABLE - elif task == FlightType.AIR_ASSAULT: - return AIR_ASSAULT_CAPABLE - else: - logging.error(f"Unplannable flight type: {task}") - return [] - - -def aircraft_for_task(task: FlightType) -> list[AircraftType]: - dcs_types = dcs_types_for_task(task) - types: list[AircraftType] = [] - for dcs_type in dcs_types: - types.extend(AircraftType.for_dcs_type(dcs_type)) - return types - - -def tasks_for_aircraft(aircraft: AircraftType) -> list[FlightType]: - tasks: list[FlightType] = [] - for task in FlightType: - if task is FlightType.FERRY: - # Not a plannable task, so skip it. - continue - if aircraft in aircraft_for_task(task): - tasks.append(task) - return tasks diff --git a/game/ato/flighttype.py b/game/ato/flighttype.py index 9660f3f2..18216ea3 100644 --- a/game/ato/flighttype.py +++ b/game/ato/flighttype.py @@ -24,8 +24,8 @@ class FlightType(Enum): * Implementations of MissionTarget.mission_types: A mission type can only be planned against compatible targets. The mission_types method of each target class defines which missions may target it. - * ai_flight_planner_db.py: Add the new mission type to aircraft_for_task that - returns the list of compatible aircraft in order of preference. + * resources/units/aircraft/*.yaml: Assign aircraft weight for the new task type in + the `tasks` dict for all capable aircraft. You may also need to update: diff --git a/game/campaignloader/squadrondefgenerator.py b/game/campaignloader/squadrondefgenerator.py index 0bf87ea7..52eb6726 100644 --- a/game/campaignloader/squadrondefgenerator.py +++ b/game/campaignloader/squadrondefgenerator.py @@ -4,7 +4,6 @@ import itertools import random from typing import Optional, TYPE_CHECKING -from game.ato.ai_flight_planner_db import aircraft_for_task, tasks_for_aircraft from game.ato.flighttype import FlightType from game.dcs.aircrafttype import AircraftType from game.squadrons.operatingbases import OperatingBases @@ -25,7 +24,7 @@ class SquadronDefGenerator: self, task: FlightType, control_point: ControlPoint ) -> Optional[SquadronDef]: aircraft_choice: Optional[AircraftType] = None - for aircraft in aircraft_for_task(task): + for aircraft in AircraftType.priority_list_for_task(task): if aircraft not in self.faction.aircrafts: continue if not control_point.can_operate(aircraft): @@ -48,7 +47,7 @@ class SquadronDefGenerator: role="Flying Squadron", aircraft=aircraft, livery=None, - mission_types=tuple(tasks_for_aircraft(aircraft)), + auto_assignable_mission_types=set(aircraft.iter_task_capabilities()), operating_bases=OperatingBases.default_for_aircraft(aircraft), female_pilot_percentage=6, pilot_pool=[], diff --git a/game/dcs/aircrafttype.py b/game/dcs/aircrafttype.py index 20db8f2e..78fc8cf0 100644 --- a/game/dcs/aircrafttype.py +++ b/game/dcs/aircrafttype.py @@ -364,6 +364,9 @@ class AircraftType(UnitType[Type[FlyingType]]): capable.append(aircraft) return list(reversed(sorted(capable, key=lambda a: a.task_priority(task)))) + def iter_task_capabilities(self) -> Iterator[FlightType]: + yield from self.task_priorities + @staticmethod def each_dcs_type() -> Iterator[Type[FlyingType]]: yield from helicopter_map.values() diff --git a/game/squadrons/airwing.py b/game/squadrons/airwing.py index de035b36..8aa9c560 100644 --- a/game/squadrons/airwing.py +++ b/game/squadrons/airwing.py @@ -4,7 +4,6 @@ import itertools from collections import defaultdict from typing import Sequence, Iterator, TYPE_CHECKING, Optional -from game.ato.ai_flight_planner_db import aircraft_for_task from game.ato.closestairfields import ObjectiveDistanceCache from game.dcs.aircrafttype import AircraftType from .squadrondefloader import SquadronDefLoader @@ -48,7 +47,7 @@ class AirWing: self, location: MissionTarget, task: FlightType, size: int, this_turn: bool ) -> list[Squadron]: airfield_cache = ObjectiveDistanceCache.get_closest_airfields(location) - best_aircraft = aircraft_for_task(task) + best_aircraft = AircraftType.priority_list_for_task(task) ordered: list[Squadron] = [] for control_point in airfield_cache.operational_airfields: if control_point.captured != self.player: @@ -79,7 +78,7 @@ class AirWing: def best_available_aircrafts_for(self, task: FlightType) -> list[AircraftType]: """Returns an ordered list of available aircrafts for the given task""" aircrafts = [] - best_aircraft_for_task = aircraft_for_task(task) + best_aircraft_for_task = AircraftType.priority_list_for_task(task) for aircraft, squadrons in self.squadrons.items(): for squadron in squadrons: if squadron.untasked_aircraft and task in squadron.mission_types: diff --git a/game/squadrons/squadron.py b/game/squadrons/squadron.py index 9fdc42c0..d5f163a1 100644 --- a/game/squadrons/squadron.py +++ b/game/squadrons/squadron.py @@ -256,6 +256,14 @@ class Squadron: def has_unfilled_pilot_slots(self) -> bool: return not self.pilot_limits_enabled or self._number_of_unfilled_pilot_slots > 0 + def capable_of(self, task: FlightType) -> bool: + """Returns True if the squadron is capable of performing the given task. + + A squadron may be capable of performing a task even if it will not be + automatically assigned to it. + """ + return self.aircraft.capable_of(task) + def can_auto_assign(self, task: FlightType) -> bool: return task in self.auto_assignable_mission_types diff --git a/game/squadrons/squadrondef.py b/game/squadrons/squadrondef.py index 712dd892..94abec92 100644 --- a/game/squadrons/squadrondef.py +++ b/game/squadrons/squadrondef.py @@ -25,16 +25,12 @@ class SquadronDef: role: str aircraft: AircraftType livery: Optional[str] - mission_types: tuple[FlightType, ...] + auto_assignable_mission_types: set[FlightType] operating_bases: OperatingBases female_pilot_percentage: int pilot_pool: list[Pilot] claimed: bool = False - auto_assignable_mission_types: set[FlightType] = field( - init=False, hash=False, compare=False - ) - def __post_init__(self) -> None: self.auto_assignable_mission_types = set(self.mission_types) @@ -48,7 +44,11 @@ class SquadronDef: self.auto_assignable_mission_types.intersection_update(self.mission_types) def can_auto_assign(self, task: FlightType) -> bool: - return task in self.auto_assignable_mission_types + """ + A squadron may be capable of performing a task even if it will not be + automatically assigned to it. + """ + return self.aircraft.capable_of(task) def operates_from(self, control_point: ControlPoint) -> bool: if not control_point.can_operate(self.aircraft): @@ -95,7 +95,7 @@ class SquadronDef: role=data["role"], aircraft=unit_type, livery=data.get("livery"), - mission_types=tuple(mission_types), + auto_assignable_mission_types=set(unit_type.iter_task_capabilities()), operating_bases=OperatingBases.from_yaml(unit_type, data.get("bases", {})), female_pilot_percentage=female_pilot_percentage, pilot_pool=pilots, diff --git a/game/transfers.py b/game/transfers.py index 7e1860d2..81a33a3c 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -40,7 +40,6 @@ from typing import Generic, Iterator, List, Optional, Sequence, TYPE_CHECKING, T from dcs.mapping import Point -from game.ato.ai_flight_planner_db import aircraft_for_task from game.ato.closestairfields import ObjectiveDistanceCache from game.ato.flight import Flight from game.ato.flighttype import FlightType @@ -270,7 +269,7 @@ class AirliftPlanner: def compatible_with_mission( self, unit_type: AircraftType, airfield: ControlPoint ) -> bool: - if unit_type not in aircraft_for_task(FlightType.TRANSPORT): + if not unit_type.capable_of(FlightType.TRANSPORT): return False if not self.transfer.origin.can_operate(unit_type): return False diff --git a/qt_ui/windows/QUnitInfoWindow.py b/qt_ui/windows/QUnitInfoWindow.py index d59f3bed..50765faa 100644 --- a/qt_ui/windows/QUnitInfoWindow.py +++ b/qt_ui/windows/QUnitInfoWindow.py @@ -10,8 +10,6 @@ from PySide2.QtWidgets import ( QFrame, ) -import game.ato.ai_flight_planner_db -from game.ato.flighttype import FlightType from game.dcs.aircrafttype import AircraftType from game.dcs.groundunittype import GroundUnitType from game.dcs.unittype import UnitType @@ -84,9 +82,8 @@ class QUnitInfoWindow(QDialog): # If it's an aircraft, include the task list. if isinstance(unit_type, AircraftType): - self.tasks_box = QLabel( - f"In-Game Tasks: {self.generateAircraftTasks()}" - ) + tasks = ", ".join(str(t) for t in unit_type.iter_task_capabilities()) + self.tasks_box = QLabel(f"In-Game Tasks: {tasks}") self.tasks_box.setProperty("style", "info-element") self.gridLayout.addWidget(self.tasks_box, 2, 0) @@ -101,30 +98,3 @@ class QUnitInfoWindow(QDialog): self.layout.addLayout(self.gridLayout, 1, 0) self.setLayout(self.layout) - - def generateAircraftTasks(self) -> str: - aircraft_tasks = "" - unit_type = self.unit_type.dcs_unit_type - if unit_type in game.ato.ai_flight_planner_db.CAP_CAPABLE: - aircraft_tasks = ( - aircraft_tasks - + f"{FlightType.BARCAP}, {FlightType.ESCORT}, {FlightType.INTERCEPTION}, {FlightType.SWEEP}, {FlightType.TARCAP}, " - ) - if unit_type in game.ato.ai_flight_planner_db.CAS_CAPABLE: - aircraft_tasks = ( - aircraft_tasks - + f"{FlightType.CAS}, {FlightType.BAI}, {FlightType.OCA_AIRCRAFT}, " - ) - if unit_type in game.ato.ai_flight_planner_db.SEAD_CAPABLE: - aircraft_tasks = aircraft_tasks + f"{FlightType.SEAD}, " - if unit_type in game.ato.ai_flight_planner_db.DEAD_CAPABLE: - aircraft_tasks = aircraft_tasks + f"{FlightType.DEAD}, " - if unit_type in game.ato.ai_flight_planner_db.ANTISHIP_CAPABLE: - aircraft_tasks = aircraft_tasks + f"{FlightType.ANTISHIP}, " - if unit_type in game.ato.ai_flight_planner_db.RUNWAY_ATTACK_CAPABLE: - aircraft_tasks = aircraft_tasks + f"{FlightType.OCA_RUNWAY}, " - if unit_type in game.ato.ai_flight_planner_db.STRIKE_CAPABLE: - aircraft_tasks = aircraft_tasks + f"{FlightType.STRIKE}, " - if unit_type in game.ato.ai_flight_planner_db.REFUELING_CAPABALE: - aircraft_tasks = aircraft_tasks + f"{FlightType.REFUELING}, " - return aircraft_tasks[:-2]