diff --git a/game/procurement.py b/game/procurement.py index 95b00b38..02a7967c 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -193,7 +193,7 @@ class ProcurementAi: continue for squadron in self.air_wing.squadrons_for(unit): - if task in squadron.mission_types: + if task in squadron.auto_assignable_mission_types: break else: continue diff --git a/game/squadrons.py b/game/squadrons.py index 8e8e4d77..4e550465 100644 --- a/game/squadrons.py +++ b/game/squadrons.py @@ -10,7 +10,6 @@ from pathlib import Path from typing import ( Type, Tuple, - List, TYPE_CHECKING, Optional, Iterator, @@ -82,9 +81,12 @@ class Squadron: role: str aircraft: Type[FlyingType] livery: Optional[str] - mission_types: Tuple[FlightType, ...] - pilots: List[Pilot] - available_pilots: List[Pilot] = field(init=False, hash=False, compare=False) + mission_types: tuple[FlightType, ...] + pilots: list[Pilot] + available_pilots: list[Pilot] = field(init=False, hash=False, compare=False) + auto_assignable_mission_types: set[FlightType] = field( + init=False, hash=False, compare=False + ) # We need a reference to the Game so that we can access the Faker without needing to # persist it to the save game, or having to reconstruct it (it's not cheap) each @@ -94,6 +96,7 @@ class Squadron: def __post_init__(self) -> None: self.available_pilots = list(self.active_pilots) + self.auto_assignable_mission_types = set(self.mission_types) def __str__(self) -> str: return f'{self.name} "{self.nickname}"' @@ -223,6 +226,12 @@ class Squadron: player=player, ) + def __setstate__(self, state) -> None: + # TODO: Remove save compat. + if "auto_assignable_mission_types" not in state: + state["auto_assignable_mission_types"] = set(state["mission_types"]) + self.__dict__.update(state) + class SquadronLoader: def __init__(self, game: Game, player: bool) -> None: diff --git a/game/transfers.py b/game/transfers.py index 25935d9c..d14e0d1b 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -238,7 +238,7 @@ class AirliftPlanner: for s in self.game.air_wing_for(self.for_player).squadrons_for( unit_type ) - if FlightType.TRANSPORT in s.mission_types + if FlightType.TRANSPORT in s.auto_assignable_mission_types ] if not squadrons: continue diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index b9b7c230..85f5209c 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -180,7 +180,7 @@ class AircraftAllocator: # Valid location with enough aircraft available. Find a squadron to fit # the role. for squadron in self.air_wing.squadrons_for(aircraft): - if task not in squadron.mission_types: + if task not in squadron.auto_assignable_mission_types: continue if len(squadron.available_pilots) >= flight.num_aircraft: inventory.remove_aircraft(aircraft, flight.num_aircraft) @@ -604,7 +604,7 @@ class CoalitionMissionPlanner: for squadron in self.game.air_wing_for(self.is_player).iter_squadrons(): if ( squadron.aircraft in all_compatible - and mission_type in squadron.mission_types + and mission_type in squadron.auto_assignable_mission_types ): return True return False diff --git a/qt_ui/models.py b/qt_ui/models.py index ba85d7e2..0bf5920f 100644 --- a/qt_ui/models.py +++ b/qt_ui/models.py @@ -18,7 +18,7 @@ from game.squadrons import Squadron, Pilot from game.theater.missiontarget import MissionTarget from game.transfers import TransferOrder from gen.ato import AirTaskingOrder, Package -from gen.flights.flight import Flight +from gen.flights.flight import Flight, FlightType from gen.flights.traveltime import TotEstimator from qt_ui.uiconstants import AIRCRAFT_ICONS @@ -467,6 +467,15 @@ class SquadronModel(QAbstractListModel): pilot.send_on_leave() self.endResetModel() + def is_auto_assignable(self, task: FlightType) -> bool: + return task in self.squadron.auto_assignable_mission_types + + def set_auto_assignable(self, task: FlightType, auto_assignable: bool) -> None: + if auto_assignable: + self.squadron.auto_assignable_mission_types.add(task) + else: + self.squadron.auto_assignable_mission_types.remove(task) + class GameModel: """A model for the Game object. diff --git a/qt_ui/windows/SquadronDialog.py b/qt_ui/windows/SquadronDialog.py index dc6d560d..31cf5587 100644 --- a/qt_ui/windows/SquadronDialog.py +++ b/qt_ui/windows/SquadronDialog.py @@ -1,4 +1,5 @@ import logging +from typing import Callable from PySide2.QtCore import ( QItemSelectionModel, @@ -13,9 +14,13 @@ from PySide2.QtWidgets import ( QVBoxLayout, QPushButton, QHBoxLayout, + QGridLayout, + QLabel, + QCheckBox, ) from game.squadrons import Pilot +from gen.flights.flight import FlightType from qt_ui.delegates import TwoColumnRowDelegate from qt_ui.models import SquadronModel @@ -61,6 +66,31 @@ class PilotList(QListView): self.setSelectionBehavior(QAbstractItemView.SelectItems) +class AutoAssignedTaskControls(QVBoxLayout): + def __init__(self, squadron_model: SquadronModel) -> None: + super().__init__() + self.squadron_model = squadron_model + + self.addWidget(QLabel("Auto-assignable mission types")) + + def make_callback(toggled_task: FlightType) -> Callable[[bool], None]: + def callback(checked: bool) -> None: + self.on_toggled(toggled_task, checked) + + return callback + + for task in squadron_model.squadron.mission_types: + checkbox = QCheckBox(text=task.value) + checkbox.setChecked(squadron_model.is_auto_assignable(task)) + checkbox.toggled.connect(make_callback(task)) + self.addWidget(checkbox) + + self.addStretch() + + def on_toggled(self, task: FlightType, checked: bool) -> None: + self.squadron_model.set_auto_assignable(task, checked) + + class SquadronDialog(QDialog): """Dialog window showing a squadron.""" @@ -75,11 +105,17 @@ class SquadronDialog(QDialog): layout = QVBoxLayout() self.setLayout(layout) + columns = QHBoxLayout() + layout.addLayout(columns) + + auto_assigned_tasks = AutoAssignedTaskControls(squadron_model) + columns.addLayout(auto_assigned_tasks) + self.pilot_list = PilotList(squadron_model) self.pilot_list.selectionModel().selectionChanged.connect( self.on_selection_changed ) - layout.addWidget(self.pilot_list) + columns.addWidget(self.pilot_list) button_panel = QHBoxLayout() button_panel.addStretch()