diff --git a/game/db.py b/game/db.py index 91b1c138..980f1461 100644 --- a/game/db.py +++ b/game/db.py @@ -1,8 +1,8 @@ +import json from datetime import datetime from enum import Enum -from typing import Dict, List, Optional, Tuple, Type, Union -import json from pathlib import Path +from typing import Dict, List, Optional, Tuple, Type, Union from dcs.countries import country_dict from dcs.helicopters import ( @@ -62,7 +62,6 @@ from dcs.planes import ( F_4E, F_5E_3, F_86F_Sabre, - F_A_18C, IL_76MD, IL_78M, JF_17, @@ -72,7 +71,6 @@ from dcs.planes import ( KC_135, KC135MPRS, KJ_2000, - L_39C, L_39ZA, MQ_9_Reaper, M_2000C, @@ -92,7 +90,6 @@ from dcs.planes import ( P_47D_40, P_51D, P_51D_30_NA, - PlaneType, RQ_1A_Predator, S_3B, S_3B_Tanker, @@ -103,7 +100,6 @@ from dcs.planes import ( Su_24MR, Su_25, Su_25T, - Su_25TM, Su_27, Su_30, Su_33, @@ -1178,141 +1174,6 @@ COMMON_OVERRIDE = { AWACS: "AEW&C", } -""" -This is a list of mappings from the FlightType of a Flight to the type of payload defined in the -resources/payloads/UNIT_TYPE.lua file. A Flight has no concept of a PyDCS task, so COMMON_OVERRIDE cannot be -used here. This is used in the payload editor, for setting the default loadout of an object. -The left element is the FlightType name, and the right element is a tuple containing what is used in the lua file. -Some aircraft differ from the standard loadout names, so those have been included here too. -The priority goes from first to last - the first element in the tuple will be tried first, then the second, etc. -""" - -EXPANDED_TASK_PAYLOAD_OVERRIDE = { - "TARCAP": ("CAP HEAVY", "CAP"), - "BARCAP": ("CAP HEAVY", "CAP"), - "CAS": ("CAS MAVERICK F", "CAS"), - "INTERCEPTION": ("CAP HEAVY", "CAP"), - "STRIKE": ("STRIKE",), - "ANTISHIP": ("ANTISHIP",), - "SEAD": ("SEAD",), - "DEAD": ("SEAD",), - "ESCORT": ("CAP HEAVY", "CAP"), - "BAI": ("BAI", "CAS MAVERICK F", "CAS"), - "SWEEP": ("CAP HEAVY", "CAP"), - "OCA_RUNWAY": ("RUNWAY_ATTACK", "RUNWAY_STRIKE", "STRIKE"), - "OCA_AIRCRAFT": ("OCA", "CAS MAVERICK F", "CAS"), - "TRANSPORT": (), -} - -PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = { - B_1B: COMMON_OVERRIDE, - B_52H: COMMON_OVERRIDE, - F_117A: COMMON_OVERRIDE, - F_15E: COMMON_OVERRIDE, - FA_18C_hornet: { - CAP: "CAP HEAVY", - Intercept: "CAP HEAVY", - CAS: "CAS MAVERICK F", - PinpointStrike: "STRIKE", - SEAD: "SEAD", - AntishipStrike: "ANTISHIP", - GroundAttack: "STRIKE", - Escort: "CAP HEAVY", - FighterSweep: "CAP HEAVY", - }, - F_A_18C: { - CAP: "CAP HEAVY", - Intercept: "CAP HEAVY", - CAS: "CAS MAVERICK F", - PinpointStrike: "STRIKE", - SEAD: "SEAD", - AntishipStrike: "ANTISHIP", - GroundAttack: "STRIKE", - Escort: "CAP HEAVY", - FighterSweep: "CAP HEAVY", - }, - Tu_160: { - PinpointStrike: "Kh-65*12", - }, - Tu_22M3: COMMON_OVERRIDE, - Tu_95MS: COMMON_OVERRIDE, - A_10A: COMMON_OVERRIDE, - A_10C: COMMON_OVERRIDE, - A_10C_2: COMMON_OVERRIDE, - AV8BNA: COMMON_OVERRIDE, - C_101CC: COMMON_OVERRIDE, - F_5E_3: COMMON_OVERRIDE, - F_14A_135_GR: COMMON_OVERRIDE, - F_14B: COMMON_OVERRIDE, - F_15C: COMMON_OVERRIDE, - F_22A: COMMON_OVERRIDE, - F_16C_50: COMMON_OVERRIDE, - JF_17: COMMON_OVERRIDE, - M_2000C: COMMON_OVERRIDE, - MiG_15bis: COMMON_OVERRIDE, - MiG_19P: COMMON_OVERRIDE, - MiG_21Bis: COMMON_OVERRIDE, - AJS37: COMMON_OVERRIDE, - Su_25T: COMMON_OVERRIDE, - Su_25: COMMON_OVERRIDE, - Su_27: COMMON_OVERRIDE, - Su_33: COMMON_OVERRIDE, - MiG_29A: COMMON_OVERRIDE, - MiG_29G: COMMON_OVERRIDE, - MiG_29S: COMMON_OVERRIDE, - Su_24M: COMMON_OVERRIDE, - Su_30: COMMON_OVERRIDE, - Su_34: COMMON_OVERRIDE, - Su_57: COMMON_OVERRIDE, - MiG_23MLD: COMMON_OVERRIDE, - MiG_27K: COMMON_OVERRIDE, - Tornado_GR4: COMMON_OVERRIDE, - Tornado_IDS: COMMON_OVERRIDE, - Mirage_2000_5: COMMON_OVERRIDE, - MiG_31: COMMON_OVERRIDE, - S_3B: COMMON_OVERRIDE, - SA342M: COMMON_OVERRIDE, - SA342L: COMMON_OVERRIDE, - SA342Mistral: COMMON_OVERRIDE, - Mi_8MT: COMMON_OVERRIDE, - Mi_24V: COMMON_OVERRIDE, - Mi_28N: COMMON_OVERRIDE, - Ka_50: COMMON_OVERRIDE, - L_39ZA: COMMON_OVERRIDE, - L_39C: COMMON_OVERRIDE, - Su_17M4: COMMON_OVERRIDE, - F_4E: COMMON_OVERRIDE, - P_47D_30: COMMON_OVERRIDE, - P_47D_30bl1: COMMON_OVERRIDE, - P_47D_40: COMMON_OVERRIDE, - B_17G: COMMON_OVERRIDE, - P_51D: COMMON_OVERRIDE, - P_51D_30_NA: COMMON_OVERRIDE, - FW_190D9: COMMON_OVERRIDE, - FW_190A8: COMMON_OVERRIDE, - Bf_109K_4: COMMON_OVERRIDE, - I_16: COMMON_OVERRIDE, - SpitfireLFMkIXCW: COMMON_OVERRIDE, - SpitfireLFMkIX: COMMON_OVERRIDE, - A_20G: COMMON_OVERRIDE, - A_4E_C: COMMON_OVERRIDE, - MB_339PAN: COMMON_OVERRIDE, - OH_58D: COMMON_OVERRIDE, - F_16A: COMMON_OVERRIDE, - MQ_9_Reaper: COMMON_OVERRIDE, - RQ_1A_Predator: COMMON_OVERRIDE, - WingLoong_I: COMMON_OVERRIDE, - AH_1W: COMMON_OVERRIDE, - AH_64D: COMMON_OVERRIDE, - AH_64A: COMMON_OVERRIDE, - SH_60B: COMMON_OVERRIDE, - Hercules: COMMON_OVERRIDE, - F_86F_Sabre: COMMON_OVERRIDE, - Su_25TM: { - SEAD: "Kh-31P*2_Kh-25ML*4_R-73*2_L-081_MPS410", - }, -} - """ Aircraft livery overrides. Syntax as follows: diff --git a/gen/aircraft.py b/gen/aircraft.py index cce4cdb2..b1d35486 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -61,12 +61,10 @@ from dcs.task import ( OptReactOnThreat, OptRestrictJettison, OrbitAction, - PinpointStrike, RunwayAttack, SEAD, StartCommand, Targets, - Task, Transport, WeaponType, ) @@ -77,7 +75,7 @@ from dcs.unittype import FlyingType, UnitType from game import db from game.data.cap_capabilities_db import GUNFIGHTERS -from game.data.weapons import Pylon, Weapon +from game.data.weapons import Pylon from game.factions.faction import Faction from game.settings import Settings from game.theater.controlpoint import ( @@ -728,43 +726,13 @@ class AircraftConflictGenerator: def _setup_group( self, group: FlyingGroup, - loadout_for_task: Type[Task], package: Package, flight: Flight, dynamic_runways: Dict[str, RunwayData], ) -> None: - did_load_loadout = False unit_type = group.units[0].unit_type - if unit_type in db.PLANE_PAYLOAD_OVERRIDES: - # Clear pylons - for p in group.units: - p.pylons.clear() - - # Now load loadout - if loadout_for_task in db.PLANE_PAYLOAD_OVERRIDES[unit_type]: - payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][loadout_for_task] - group.load_loadout(payload_name) - if not group.units[0].pylons and loadout_for_task == RunwayAttack: - if PinpointStrike in db.PLANE_PAYLOAD_OVERRIDES[unit_type]: - logging.warning( - 'No loadout for "Runway Attack" for the {}, defaulting to Strike loadout'.format( - str(unit_type) - ) - ) - payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][ - PinpointStrike - ] - group.load_loadout(payload_name) - did_load_loadout = True - logging.info( - "Loaded overridden payload for {} - {} for task {}".format( - unit_type, payload_name, loadout_for_task - ) - ) - - if not did_load_loadout: - group.load_task_default_loadout(loadout_for_task) + self._setup_payload(flight, group) if unit_type in db.PLANE_LIVERY_OVERRIDES: for unit_instance in group.units: @@ -976,39 +944,20 @@ class AircraftConflictGenerator: else: assert False - @staticmethod - def _setup_custom_payload(flight: Flight, group: FlyingGroup) -> None: - if not flight.use_custom_loadout: - return - - logging.info("Custom loadout for flight : " + flight.__repr__()) + def _setup_payload(self, flight: Flight, group: FlyingGroup) -> None: for p in group.units: p.pylons.clear() - for pylon_number, weapon in flight.loadout.items(): + loadout = flight.loadout + if self.game.settings.restrict_weapons_by_date: + loadout = loadout.degrade_for_date(flight.unit_type, self.game.date) + + for pylon_number, weapon in loadout.pylons.items(): if weapon is None: continue pylon = Pylon.for_aircraft(flight.unit_type, pylon_number) pylon.equip(group, weapon) - def _degrade_payload_to_era(self, flight: Flight, group: FlyingGroup) -> None: - loadout = dict(group.units[0].pylons) - for pylon_number, clsid in loadout.items(): - weapon = Weapon.from_clsid(clsid["CLSID"]) - if weapon is None: - logging.error(f"Could not find weapon for clsid {clsid}") - continue - - if not weapon.available_on(self.game.date): - pylon = Pylon.for_aircraft(flight.unit_type, pylon_number) - for fallback in weapon.fallbacks: - if not pylon.can_equip(fallback): - continue - if not fallback.available_on(self.game.date): - continue - pylon.equip(group, fallback) - break - def clear_parking_slots(self) -> None: for cp in self.game.theater.controlpoints: for parking_slot in cp.parking_slots: @@ -1237,7 +1186,7 @@ class AircraftConflictGenerator: dynamic_runways: Dict[str, RunwayData], ) -> None: group.task = CAP.name - self._setup_group(group, CAP, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) if flight.unit_type not in GUNFIGHTERS: ammo_type = OptRTBOnOutOfAmmo.Values.AAM @@ -1254,7 +1203,7 @@ class AircraftConflictGenerator: dynamic_runways: Dict[str, RunwayData], ) -> None: group.task = FighterSweep.name - self._setup_group(group, FighterSweep, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) if flight.unit_type not in GUNFIGHTERS: ammo_type = OptRTBOnOutOfAmmo.Values.AAM @@ -1271,7 +1220,7 @@ class AircraftConflictGenerator: dynamic_runways: Dict[str, RunwayData], ) -> None: group.task = CAS.name - self._setup_group(group, CAS, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) self.configure_behavior( group, react_on_threat=OptReactOnThreat.Values.EvadeFire, @@ -1295,7 +1244,7 @@ class AircraftConflictGenerator: # waypoint actions the group may perform. group.task = CAS.name # But we still use the SEAD *loadout*. - self._setup_group(group, SEAD, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) self.configure_behavior( group, react_on_threat=OptReactOnThreat.Values.EvadeFire, @@ -1312,7 +1261,7 @@ class AircraftConflictGenerator: dynamic_runways: Dict[str, RunwayData], ) -> None: group.task = SEAD.name - self._setup_group(group, SEAD, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) self.configure_behavior( group, react_on_threat=OptReactOnThreat.Values.EvadeFire, @@ -1329,7 +1278,7 @@ class AircraftConflictGenerator: dynamic_runways: Dict[str, RunwayData], ) -> None: group.task = GroundAttack.name - self._setup_group(group, GroundAttack, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) self.configure_behavior( group, react_on_threat=OptReactOnThreat.Values.EvadeFire, @@ -1345,7 +1294,7 @@ class AircraftConflictGenerator: dynamic_runways: Dict[str, RunwayData], ) -> None: group.task = AntishipStrike.name - self._setup_group(group, AntishipStrike, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) self.configure_behavior( group, react_on_threat=OptReactOnThreat.Values.EvadeFire, @@ -1361,7 +1310,7 @@ class AircraftConflictGenerator: dynamic_runways: Dict[str, RunwayData], ) -> None: group.task = RunwayAttack.name - self._setup_group(group, RunwayAttack, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) self.configure_behavior( group, react_on_threat=OptReactOnThreat.Values.EvadeFire, @@ -1377,7 +1326,7 @@ class AircraftConflictGenerator: dynamic_runways: Dict[str, RunwayData], ) -> None: group.task = CAS.name - self._setup_group(group, CAS, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) self.configure_behavior( group, react_on_threat=OptReactOnThreat.Values.EvadeFire, @@ -1400,7 +1349,7 @@ class AircraftConflictGenerator: ) return - self._setup_group(group, AWACS, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) # Awacs task action self.configure_behavior( @@ -1423,7 +1372,7 @@ class AircraftConflictGenerator: # Search Then Engage task, which we have to use instead of the Escort # task for the reasons explained in JoinPointBuilder. group.task = CAP.name - self._setup_group(group, CAP, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) self.configure_behavior( group, roe=OptROE.Values.OpenFire, restrict_jettison=True ) @@ -1439,7 +1388,7 @@ class AircraftConflictGenerator: # Search Then Engage task, which we have to use instead of the Escort # task for the reasons explained in JoinPointBuilder. group.task = Transport.name - self._setup_group(group, Transport, package, flight, dynamic_runways) + self._setup_group(group, package, flight, dynamic_runways) self.configure_behavior( group, react_on_threat=OptReactOnThreat.Values.EvadeFire, @@ -1540,9 +1489,6 @@ class AircraftConflictGenerator: # Set here rather than when the FlightData is created so they waypoints # have their TOTs set. self.flights[-1].waypoints = [takeoff_point] + flight.points - self._setup_custom_payload(flight, group) - if self.game.settings.restrict_weapons_by_date: - self._degrade_payload_to_era(flight, group) def should_delay_flight(self, flight: Flight, start_time: timedelta) -> bool: if start_time.total_seconds() <= 0: diff --git a/gen/flights/flight.py b/gen/flights/flight.py index bab73cb4..1e3c4d4d 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -2,19 +2,19 @@ from __future__ import annotations from datetime import timedelta from enum import Enum -from typing import Dict, List, Optional, TYPE_CHECKING, Type +from typing import List, Optional, TYPE_CHECKING, Type from dcs.mapping import Point from dcs.point import MovingPoint, PointAction from dcs.unittype import FlyingType from game import db -from game.data.weapons import Weapon from game.theater.controlpoint import ControlPoint, MissionTarget from game.utils import Distance, meters +from gen.flights.loadouts import Loadout if TYPE_CHECKING: - from game.transfers import Airlift, TransferOrder + from game.transfers import TransferOrder from gen.ato import Package from gen.flights.flightplan import FlightPlan @@ -179,7 +179,7 @@ class Flight: self.flight_type = flight_type # TODO: Replace with FlightPlan. self.targets: List[MissionTarget] = [] - self.loadout: Dict[int, Optional[Weapon]] = {} + self.loadout = Loadout.default_for(self) self.start_type = start_type self.use_custom_loadout = False self.client_count = 0 diff --git a/gen/flights/loadouts.py b/gen/flights/loadouts.py new file mode 100644 index 00000000..0ba756db --- /dev/null +++ b/gen/flights/loadouts.py @@ -0,0 +1,121 @@ +from __future__ import annotations + +import datetime +from typing import Optional, List, Iterator, Type, TYPE_CHECKING, Mapping + +from dcs.unittype import FlyingType + +from game.data.weapons import Weapon, Pylon + +if TYPE_CHECKING: + from gen.flights.flight import Flight + + +class Loadout: + def __init__( + self, + name: str, + pylons: Mapping[int, Optional[Weapon]], + date: Optional[datetime.date], + is_custom: bool = False, + ) -> None: + self.name = name + self.pylons = {k: v for k, v in pylons.items() if v is not None} + self.date = date + self.is_custom = is_custom + + def derive_custom(self, name: str) -> Loadout: + return Loadout(name, self.pylons, self.date, is_custom=True) + + def degrade_for_date( + self, unit_type: Type[FlyingType], date: datetime.date + ) -> Loadout: + if self.date is not None and self.date <= date: + return Loadout(self.name, self.pylons, self.date) + + new_pylons = dict(self.pylons) + for pylon_number, weapon in self.pylons.items(): + if not weapon.available_on(date): + pylon = Pylon.for_aircraft(unit_type, pylon_number) + for fallback in weapon.fallbacks: + if not pylon.can_equip(fallback): + continue + if not fallback.available_on(date): + continue + new_pylons[pylon_number] = fallback + break + return Loadout(f"{self.name} ({date.year})", new_pylons, date) + + @classmethod + def iter_for(cls, flight: Flight) -> Iterator[Loadout]: + # Dict of payload ID (numeric) to: + # + # { + # "name": The name the user set in the ME + # "pylons": List (as a dict) of dicts of: + # {"CLSID": class ID, "num": pylon number} + # "tasks": List (as a dict) of task IDs the payload is used by. + # } + payloads = flight.unit_type.load_payloads() + for payload in payloads["payloads"].values(): + name = payload["name"] + pylons = payload["pylons"] + yield Loadout( + name, + {p["num"]: Weapon.from_clsid(p["CLSID"]) for p in pylons.values()}, + date=None, + ) + + @classmethod + def all_for(cls, flight: Flight) -> List[Loadout]: + return list(cls.iter_for(flight)) + + @classmethod + def default_loadout_names_for(cls, flight: Flight) -> Iterator[str]: + from gen.flights.flight import FlightType + + # This is a list of mappings from the FlightType of a Flight to the type of + # payload defined in the resources/payloads/UNIT_TYPE.lua file. A Flight has no + # concept of a PyDCS task, so COMMON_OVERRIDE cannot be used here. This is used + # in the payload editor, for setting the default loadout of an object. The left + # element is the FlightType name, and the right element is a tuple containing + # what is used in the lua file. Some aircraft differ from the standard loadout + # names, so those have been included here too. The priority goes from first to + # last - the first element in the tuple will be tried first, then the second, + # etc. + yield from { + FlightType.TARCAP: ("CAP HEAVY", "CAP"), + FlightType.BARCAP: ("CAP HEAVY", "CAP"), + FlightType.CAS: ("CAS MAVERICK F", "CAS"), + FlightType.INTERCEPTION: ("CAP HEAVY", "CAP"), + FlightType.STRIKE: ("STRIKE",), + FlightType.ANTISHIP: ("ANTISHIP",), + FlightType.SEAD: ("SEAD",), + FlightType.DEAD: ("SEAD",), + FlightType.ESCORT: ("CAP HEAVY", "CAP"), + FlightType.BAI: ("BAI", "CAS MAVERICK F", "CAS"), + FlightType.SWEEP: ("CAP HEAVY", "CAP"), + FlightType.OCA_RUNWAY: ("RUNWAY_ATTACK", "RUNWAY_STRIKE", "STRIKE"), + FlightType.OCA_AIRCRAFT: ("OCA", "CAS MAVERICK F", "CAS"), + FlightType.TRANSPORT: (), + FlightType.AEWC: (), + }.get(flight.flight_type, []) + + @classmethod + def default_for(cls, flight: Flight) -> Loadout: + # Iterate through each possible payload type for a given aircraft. + # Some aircraft have custom loadouts that in aren't the standard set. + for name in cls.default_loadout_names_for(flight): + # This operation is cached, but must be called before load_by_name will + # work. + flight.unit_type.load_payloads() + payload = flight.unit_type.loadout_by_name(name) + if payload is not None: + return Loadout( + name, + {i: Weapon.from_clsid(d["clsid"]) for i, d in payload}, + date=None, + ) + + # TODO: Try group.load_task_default_loadout(loadout_for_task) + return Loadout("Empty", {}, date=None) diff --git a/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py b/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py index 131802dd..2da14527 100644 --- a/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py +++ b/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py @@ -1,8 +1,9 @@ -from PySide2.QtWidgets import QFrame, QGridLayout, QLabel from PySide2.QtCore import Qt +from PySide2.QtWidgets import QFrame, QLabel, QVBoxLayout from game import Game from gen.flights.flight import Flight +from gen.flights.loadouts import Loadout from qt_ui.windows.mission.flight.payload.QLoadoutEditor import QLoadoutEditor @@ -11,10 +12,8 @@ class QFlightPayloadTab(QFrame): super(QFlightPayloadTab, self).__init__() self.flight = flight self.payload_editor = QLoadoutEditor(flight, game) - self.init_ui() - def init_ui(self): - layout = QGridLayout() + layout = QVBoxLayout() # Docs Link docsText = QLabel( @@ -23,7 +22,15 @@ class QFlightPayloadTab(QFrame): docsText.setAlignment(Qt.AlignCenter) docsText.setOpenExternalLinks(True) + self.payload_editor.toggled.connect(self.on_custom_toggled) layout.addWidget(self.payload_editor) layout.addWidget(docsText) self.setLayout(layout) + + def on_custom_toggled(self, use_custom: bool) -> None: + if use_custom: + self.flight.loadout = self.flight.loadout.derive_custom("Custom") + else: + self.flight.loadout = Loadout.default_for(self.flight) + self.payload_editor.reset_pylons() diff --git a/qt_ui/windows/mission/flight/payload/QLoadoutEditor.py b/qt_ui/windows/mission/flight/payload/QLoadoutEditor.py index 9d70e82a..ea779c33 100644 --- a/qt_ui/windows/mission/flight/payload/QLoadoutEditor.py +++ b/qt_ui/windows/mission/flight/payload/QLoadoutEditor.py @@ -18,11 +18,9 @@ class QLoadoutEditor(QGroupBox): self.flight = flight self.game = game self.setCheckable(True) - self.setChecked(flight.use_custom_loadout) + self.setChecked(flight.loadout.is_custom) - self.toggled.connect(self.on_toggle) - - hboxLayout = QVBoxLayout(self) + vbox = QVBoxLayout(self) layout = QGridLayout(self) for i, pylon in enumerate(Pylon.iter_pylons(self.flight.unit_type)): @@ -31,16 +29,15 @@ class QLoadoutEditor(QGroupBox): layout.addWidget(label, i, 0) layout.addWidget(QPylonEditor(game, flight, pylon), i, 1) - hboxLayout.addLayout(layout) - hboxLayout.addStretch() - self.setLayout(hboxLayout) + vbox.addLayout(layout) + vbox.addStretch() + self.setLayout(vbox) - if not self.isChecked(): - for i in self.findChildren(QPylonEditor): - i.default_loadout() + for i in self.findChildren(QPylonEditor): + i.set_from(self.flight.loadout) - def on_toggle(self): + def reset_pylons(self) -> None: self.flight.use_custom_loadout = self.isChecked() if not self.isChecked(): for i in self.findChildren(QPylonEditor): - i.default_loadout() + i.set_from(self.flight.loadout) diff --git a/qt_ui/windows/mission/flight/payload/QPylonEditor.py b/qt_ui/windows/mission/flight/payload/QPylonEditor.py index 8591b7d6..9470e2e0 100644 --- a/qt_ui/windows/mission/flight/payload/QPylonEditor.py +++ b/qt_ui/windows/mission/flight/payload/QPylonEditor.py @@ -4,10 +4,10 @@ from typing import Optional from PySide2.QtWidgets import QComboBox -from game import Game, db +from game import Game from game.data.weapons import Pylon, Weapon from gen.flights.flight import Flight -from dcs import weapons_data +from gen.flights.loadouts import Loadout class QPylonEditor(QComboBox): @@ -17,7 +17,7 @@ class QPylonEditor(QComboBox): self.pylon = pylon self.game = game - current = self.flight.loadout.get(self.pylon.number) + current = self.flight.loadout.pylons.get(self.pylon.number) self.addItem("None", None) if self.game.settings.restrict_weapons_by_date: @@ -34,57 +34,29 @@ class QPylonEditor(QComboBox): def on_pylon_change(self): selected: Optional[Weapon] = self.currentData() - self.flight.loadout[self.pylon.number] = selected + self.flight.loadout.pylons[self.pylon.number] = selected if selected is None: logging.debug(f"Pylon {self.pylon.number} emptied") else: logging.debug(f"Pylon {self.pylon.number} changed to {selected.name}") - def default_loadout(self) -> None: - self.flight.unit_type.load_payloads() - self.setCurrentText("None") - pylon_default_weapon = None - historical_weapon = None - loadout = None - # Iterate through each possible payload type for a given aircraft. - # Some aircraft have custom loadouts that in aren't the standard set. - for payload_override in db.EXPANDED_TASK_PAYLOAD_OVERRIDE.get( - self.flight.flight_type.name - ): - if loadout is None: - loadout = self.flight.unit_type.loadout_by_name(payload_override) - if loadout is not None: - for i in loadout: - if i[0] == self.pylon.number: - pylon_default_weapon = i[1]["clsid"] - # TODO: Handle removed pylons better. - if pylon_default_weapon == "": - pylon_default_weapon = None - if pylon_default_weapon is not None: - if self.game.settings.restrict_weapons_by_date: - orig_weapon = Weapon.from_clsid(pylon_default_weapon) - if not orig_weapon.available_on(self.game.date): - for fallback in orig_weapon.fallbacks: - if historical_weapon is None: - if not self.pylon.can_equip(fallback): - continue - if not fallback.available_on(self.game.date): - continue - historical_weapon = fallback - else: - historical_weapon = orig_weapon - if historical_weapon is not None: - self.setCurrentText( - weapons_data.weapon_ids.get(historical_weapon.cls_id).get( - "name" - ) - ) - else: - weapon = weapons_data.weapon_ids.get(pylon_default_weapon) - if weapon is not None: - self.setCurrentText( - weapons_data.weapon_ids.get(pylon_default_weapon).get("name") - ) - else: - self.setCurrentText(pylon_default_weapon) + def weapon_from_loadout(self, loadout: Loadout) -> Optional[Weapon]: + weapon = loadout.pylons.get(self.pylon.number) + if weapon is None: + return None + # TODO: Handle removed pylons better. + if weapon.cls_id == "": + return None + return weapon + + def matching_weapon_name(self, loadout: Loadout) -> str: + if self.game.settings.restrict_weapons_by_date: + loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) + weapon = self.weapon_from_loadout(loadout) + if weapon is None: + return "" + return weapon.name + + def set_from(self, loadout: Loadout) -> None: + self.setCurrentText(self.matching_weapon_name(loadout))