Finish implementation of add/remove squadrons from AirWingConfigurationDialog

Users can now add and remove squadrons with specific buttons. this also allows new aircraft types to be added as well.

- rebased existing PR to develop
- reverted observable and changed to signals
- changed the general concept so that changes only affect the ui data model and not the game directly. Game will only be updated on apply
- removed unused code
- adopt to review comments
- allow user to choose a predefined squadron preset (also alow none value to use the random generator)
- Reuse the squadron defs from the default assigner in the AirWing class
- allow user to re-roll the squadron nickname (also added new ui icons for the button)
This commit is contained in:
RndName 2021-11-16 23:16:19 +01:00
parent e4ba9a8b72
commit d2f7785f9f
No known key found for this signature in database
GPG Key ID: 5EF516FD9537F7C0
14 changed files with 342 additions and 382 deletions

View File

@ -5,10 +5,8 @@ from typing import Optional, TYPE_CHECKING
from game.squadrons import Squadron
from game.squadrons.squadrondef import SquadronDef
from game.squadrons.squadrondefloader import SquadronDefLoader
from ..ato.flighttype import FlightType
from .campaignairwingconfig import CampaignAirWingConfig, SquadronConfig
from .squadrondefgenerator import SquadronDefGenerator
from ..dcs.aircrafttype import AircraftType
from ..theater import ControlPoint
@ -25,14 +23,6 @@ class DefaultSquadronAssigner:
self.game = game
self.coalition = coalition
self.air_wing = coalition.air_wing
self.squadron_defs = SquadronDefLoader(game, coalition).load()
self.squadron_def_generator = SquadronDefGenerator(self.coalition)
def claim_squadron_def(self, squadron: SquadronDef) -> None:
try:
self.squadron_defs[squadron.aircraft].remove(squadron)
except ValueError:
pass
def assign(self) -> None:
for control_point in self.game.theater.control_points_for(
@ -47,7 +37,6 @@ class DefaultSquadronAssigner:
)
continue
self.claim_squadron_def(squadron_def)
squadron = Squadron.create_from(
squadron_def, control_point, self.coalition, self.game
)
@ -74,7 +63,7 @@ class DefaultSquadronAssigner:
# If we can't find any squadron matching the requirement, we should
# create one.
return self.squadron_def_generator.generate_for_task(
return self.air_wing.squadron_def_generator.generate_for_task(
config.primary, control_point
)
@ -105,7 +94,7 @@ class DefaultSquadronAssigner:
# No premade squadron available for this aircraft that meets the requirements,
# so generate one if possible.
return self.squadron_def_generator.generate_for_aircraft(aircraft)
return self.air_wing.squadron_def_generator.generate_for_aircraft(aircraft)
@staticmethod
def squadron_compatible_with(
@ -121,18 +110,24 @@ class DefaultSquadronAssigner:
def find_squadron_for_airframe(
self, aircraft: AircraftType, task: FlightType, control_point: ControlPoint
) -> Optional[SquadronDef]:
for squadron in self.squadron_defs[aircraft]:
if self.squadron_compatible_with(squadron, task, control_point):
for squadron in self.air_wing.squadron_defs[aircraft]:
if not squadron.claimed and self.squadron_compatible_with(
squadron, task, control_point
):
return squadron
return None
def find_squadron_by_name(
self, name: str, task: FlightType, control_point: ControlPoint
) -> Optional[SquadronDef]:
for squadrons in self.squadron_defs.values():
for squadrons in self.air_wing.squadron_defs.values():
for squadron in squadrons:
if squadron.name == name and self.squadron_compatible_with(
squadron, task, control_point, ignore_base_preference=True
if (
not squadron.claimed
and squadron.name == name
and self.squadron_compatible_with(
squadron, task, control_point, ignore_base_preference=True
)
):
return squadron
return None
@ -140,8 +135,10 @@ class DefaultSquadronAssigner:
def find_squadron_for_task(
self, task: FlightType, control_point: ControlPoint
) -> Optional[SquadronDef]:
for squadrons in self.squadron_defs.values():
for squadrons in self.air_wing.squadron_defs.values():
for squadron in squadrons:
if self.squadron_compatible_with(squadron, task, control_point):
if not squadron.claimed and self.squadron_compatible_with(
squadron, task, control_point
):
return squadron
return None

View File

@ -12,12 +12,12 @@ from gen.flights.ai_flight_planner_db import aircraft_for_task, tasks_for_aircra
from game.ato.flighttype import FlightType
if TYPE_CHECKING:
from game.coalition import Coalition
from game.factions.faction import Faction
class SquadronDefGenerator:
def __init__(self, coalition: Coalition) -> None:
self.coalition = coalition
def __init__(self, faction: Faction) -> None:
self.faction = faction
self.count = itertools.count(1)
self.used_nicknames: set[str] = set()
@ -26,7 +26,7 @@ class SquadronDefGenerator:
) -> Optional[SquadronDef]:
aircraft_choice: Optional[AircraftType] = None
for aircraft in aircraft_for_task(task):
if aircraft not in self.coalition.faction.aircrafts:
if aircraft not in self.faction.aircrafts:
continue
if not control_point.can_operate(aircraft):
continue
@ -44,7 +44,7 @@ class SquadronDefGenerator:
return SquadronDef(
name=f"Squadron {next(self.count):03}",
nickname=self.random_nickname(),
country=self.coalition.country_name,
country=self.faction.country,
role="Flying Squadron",
aircraft=aircraft,
livery=None,

View File

@ -40,7 +40,7 @@ class Coalition:
self.procurement_requests: OrderedSet[AircraftProcurementRequest] = OrderedSet()
self.bullseye = Bullseye(Point(0, 0))
self.faker = Faker(self.faction.locales)
self.air_wing = AirWing(player)
self.air_wing = AirWing(player, game, self.faction)
self.transfers = PendingTransfers(game, player)
# Late initialized because the two coalitions in the game are mutually

View File

@ -1,21 +0,0 @@
class Event(object):
pass
class Observable(object):
def __init__(self) -> None:
self.callbacks = []
def subscribe(self, callback) -> None:
self.callbacks.append(callback)
def unsubscribe(self, callback) -> None:
self.callbacks.remove(callback)
def fire(self, **attrs) -> None:
e = Event()
e.source = self
for k, v in attrs.items():
setattr(e, k, v)
for fn in self.callbacks:
fn(e)

View File

@ -7,41 +7,34 @@ from typing import Sequence, Iterator, TYPE_CHECKING, Optional
from game.dcs.aircrafttype import AircraftType
from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import ObjectiveDistanceCache
from .squadrondef import SquadronDef
from .squadrondefloader import SquadronDefLoader
from ..campaignloader.squadrondefgenerator import SquadronDefGenerator
from ..factions.faction import Faction
from ..theater import ControlPoint, MissionTarget
from ..observer import Observable
if TYPE_CHECKING:
from game.game import Game
from ..ato.flighttype import FlightType
from .squadron import Squadron
class AirWing(Observable):
def __init__(self, player: bool) -> None:
super().__init__()
class AirWing:
def __init__(self, player: bool, game: Game, faction: Faction) -> None:
self.player = player
self.squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list)
self.squadron_defs = SquadronDefLoader(game, faction).load()
self.squadron_def_generator = SquadronDefGenerator(faction)
def unclaim_squadron_def(self, squadron: Squadron) -> None:
if squadron.aircraft in self.squadron_defs:
for squadron_def in self.squadron_defs[squadron.aircraft]:
if squadron_def.claimed and squadron_def.name == squadron.name:
squadron_def.claimed = False
def add_squadron(self, squadron: Squadron) -> None:
new_aircraft_type = squadron.aircraft not in self.squadrons
self.squadrons[squadron.aircraft].append(squadron)
if new_aircraft_type:
self.fire(type="add_aircraft_type", obj=squadron.aircraft)
self.fire(type="add_squadron", obj=squadron)
def remove_squadron(self, toRemove: Squadron) -> None:
if toRemove.aircraft in self.squadrons:
self.squadrons[toRemove.aircraft].remove(toRemove)
self.fire(type="remove_squadron", obj=toRemove)
if len(self.squadrons[toRemove.aircraft]) == 0:
self.remove_aircraft_type(toRemove.aircraft)
def remove_aircraft_type(self, toRemove: AircraftType) -> None:
self.squadrons.pop(toRemove)
self.fire(type="remove_aircraft_type", obj=toRemove)
def squadrons_for(self, aircraft: AircraftType) -> Sequence[Squadron]:
return self.squadrons[aircraft]

View File

@ -418,6 +418,7 @@ class Squadron:
coalition: Coalition,
game: Game,
) -> Squadron:
squadron_def.claimed = True
return Squadron(
squadron_def.name,
squadron_def.nickname,

View File

@ -28,6 +28,7 @@ class SquadronDef:
mission_types: tuple[FlightType, ...]
operating_bases: OperatingBases
pilot_pool: list[Pilot]
claimed: bool = False
auto_assignable_mission_types: set[FlightType] = field(
init=False, hash=False, compare=False

View File

@ -10,13 +10,13 @@ from .squadrondef import SquadronDef
if TYPE_CHECKING:
from game import Game
from game.coalition import Coalition
from ..factions.faction import Faction
class SquadronDefLoader:
def __init__(self, game: Game, coalition: Coalition) -> None:
def __init__(self, game: Game, faction: Faction) -> None:
self.game = game
self.coalition = coalition
self.faction = faction
@staticmethod
def squadron_directories() -> Iterator[Path]:
@ -27,8 +27,8 @@ class SquadronDefLoader:
def load(self) -> dict[AircraftType, list[SquadronDef]]:
squadrons: dict[AircraftType, list[SquadronDef]] = defaultdict(list)
country = self.coalition.country_name
faction = self.coalition.faction
country = self.faction.country
faction = self.faction
any_country = country.startswith("Combined Joint Task Forces ")
for directory in self.squadron_directories():
for path, squadron_def in self.load_squadrons_from(directory):

View File

@ -111,6 +111,9 @@ def load_icons():
"./resources/ui/misc/" + get_theme_icons() + "/pluginsoptions.png"
)
ICONS["Notes"] = QPixmap("./resources/ui/misc/" + get_theme_icons() + "/notes.png")
ICONS["Reload"] = QPixmap(
"./resources/ui/misc/" + get_theme_icons() + "/reload.png"
)
ICONS["TaskCAS"] = QPixmap("./resources/ui/tasks/cas.png")
ICONS["TaskCAP"] = QPixmap("./resources/ui/tasks/cap.png")

View File

@ -26,6 +26,7 @@ from PySide2.QtWidgets import (
QCheckBox,
QPushButton,
QGridLayout,
QToolButton,
)
from game import Game
@ -33,10 +34,9 @@ from game.ato.flighttype import FlightType
from game.coalition import Coalition
from game.dcs.aircrafttype import AircraftType
from game.squadrons import AirWing, Pilot, Squadron
from game.squadrons.squadrondef import SquadronDef
from game.theater import ConflictTheater, ControlPoint
from qt_ui.uiconstants import AIRCRAFT_ICONS
from qt_ui.windows.SquadronConfigPopup import SquadronConfigPopup
from qt_ui.uiconstants import AIRCRAFT_ICONS, ICONS
class AllowedMissionTypeControls(QVBoxLayout):
@ -72,7 +72,6 @@ class AllowedMissionTypeControls(QVBoxLayout):
self.allowed_mission_types.add(task)
else:
self.allowed_mission_types.remove(task)
self.squadron.set_allowed_mission_types(self.allowed_mission_types)
class SquadronBaseSelector(QComboBox):
@ -85,29 +84,39 @@ class SquadronBaseSelector(QComboBox):
def __init__(
self,
bases: Iterable[ControlPoint],
squadron: Squadron,
selected_base: Optional[ControlPoint],
aircraft_type: Optional[AircraftType],
) -> None:
super().__init__()
self.bases = list(bases)
self.squadron = squadron
self.setSizeAdjustPolicy(self.AdjustToContents)
self.bases = list(bases)
self.set_aircraft_type(aircraft_type)
for base in self.bases:
if not base.can_operate(self.squadron.aircraft):
continue
self.addItem(base.name, base)
self.model().sort(0)
self.setCurrentText(self.squadron.location.name)
if selected_base:
self.setCurrentText(selected_base.name)
# TODO can we get a prefered base if none is selected?
def set_aircraft_type(self, aircraft_type: Optional[AircraftType]):
self.clear()
if aircraft_type:
for base in self.bases:
if not base.can_operate(aircraft_type):
continue
self.addItem(base.name, base)
self.model().sort(0)
self.setEnabled(True)
else:
self.addItem("Select aircraft type first", None)
self.setEnabled(False)
self.update()
class SquadronConfigurationBox(QGroupBox):
def __init__(
self, squadron: Squadron, theater: ConflictTheater, air_wing: AirWing
) -> None:
remove_squadron_signal = Signal(Squadron)
def __init__(self, squadron: Squadron, theater: ConflictTheater) -> None:
super().__init__()
self.setCheckable(False)
self.squadron = squadron
self.air_wing = air_wing
self.reset_title()
columns = QHBoxLayout()
@ -121,14 +130,24 @@ class SquadronConfigurationBox(QGroupBox):
self.name_edit.textChanged.connect(self.on_name_changed)
left_column.addWidget(self.name_edit)
left_column.addWidget(QLabel("Nickname:"))
nickname_edit_layout = QGridLayout()
left_column.addLayout(nickname_edit_layout)
nickname_edit_layout.addWidget(QLabel("Nickname:"), 0, 0, 1, 2)
self.nickname_edit = QLineEdit(squadron.nickname)
self.nickname_edit.textChanged.connect(self.on_nickname_changed)
left_column.addWidget(self.nickname_edit)
nickname_edit_layout.addWidget(self.nickname_edit, 1, 0, Qt.AlignTop)
reroll_nickname_button = QToolButton()
reroll_nickname_button.setIcon(QIcon(ICONS["Reload"]))
reroll_nickname_button.setToolTip("Re-roll nickname")
reroll_nickname_button.clicked.connect(self.reroll_nickname)
nickname_edit_layout.addWidget(reroll_nickname_button, 1, 1, Qt.AlignTop)
left_column.addWidget(QLabel("Base:"))
self.base_selector = SquadronBaseSelector(
theater.control_points_for(squadron.player), squadron
theater.control_points_for(squadron.player),
squadron.location,
squadron.aircraft,
)
self.base_selector.currentIndexChanged.connect(self.on_base_changed)
left_column.addWidget(self.base_selector)
@ -150,20 +169,18 @@ class SquadronConfigurationBox(QGroupBox):
self.player_list.setAcceptRichText(False)
self.player_list.setEnabled(squadron.player)
left_column.addWidget(self.player_list)
self.player_list.textChanged.connect(self.on_pilots_changed)
delete_button = QPushButton("Remove")
delete_button.setMaximumWidth(80)
delete_button.clicked.connect(
lambda state: self.air_wing.remove_squadron(self.squadron)
)
delete_button = QPushButton("Remove Squadron")
delete_button.setMaximumWidth(140)
delete_button.clicked.connect(self.remove_from_squadron_config)
left_column.addWidget(delete_button)
left_column.addStretch()
self.allowed_missions = AllowedMissionTypeControls(squadron)
columns.addLayout(self.allowed_missions)
def remove_from_squadron_config(self) -> None:
self.remove_squadron_signal.emit(self.squadron)
def on_name_changed(self, text: str) -> None:
self.squadron.name = text
self.reset_title()
@ -180,56 +197,66 @@ class SquadronConfigurationBox(QGroupBox):
def reset_title(self) -> None:
self.setTitle(f"{self.squadron.name} - {self.squadron.aircraft}")
def on_pilots_changed(self) -> None:
def reroll_nickname(self) -> None:
self.nickname_edit.setText(
self.squadron.coalition.air_wing.squadron_def_generator.random_nickname()
)
def apply(self) -> Squadron:
player_names = self.player_list.toPlainText().splitlines()
# Prepend player pilots so they get set active first.
self.squadron.pilot_pool = [
Pilot(n, player=True) for n in player_names
] + self.squadron.pilot_pool
self.squadron.set_allowed_mission_types(
self.allowed_missions.allowed_mission_types
)
return self.squadron
class SquadronConfigurationLayout(QVBoxLayout):
def __init__(
self, squadrons: list[Squadron], theater: ConflictTheater, air_wing: AirWing
) -> None:
config_changed = Signal(AircraftType)
def __init__(self, squadrons: list[Squadron], theater: ConflictTheater) -> None:
super().__init__()
self.squadron_configs = []
self.theater = theater
self.air_wing = air_wing
self.squadron_configs: dict[Squadron, SquadronConfigurationBox] = {}
for squadron in squadrons:
squadron_config = SquadronConfigurationBox(
squadron, self.theater, self.air_wing
)
self.squadron_configs[squadron] = squadron_config
self.addWidget(squadron_config)
self.add_squadron(squadron)
def addSquadron(self, squadron: Squadron) -> None:
if squadron not in self.squadron_configs:
squadron_config = SquadronConfigurationBox(
squadron, self.theater, self.air_wing
)
self.squadron_configs[squadron] = squadron_config
self.addWidget(squadron_config)
self.update()
def apply(self) -> list[Squadron]:
keep_squadrons = []
for squadron_config in self.squadron_configs:
keep_squadrons.append(squadron_config.apply())
return keep_squadrons
def removeSquadron(self, squadron: Squadron) -> None:
if squadron in self.squadron_configs:
self.removeWidget(self.squadron_configs[squadron])
self.squadron_configs.pop(squadron)
self.update()
def remove_squadron(self, squadron: Squadron) -> None:
for squadron_config in self.squadron_configs:
if squadron_config.squadron == squadron:
squadron_config.deleteLater()
self.squadron_configs.remove(squadron_config)
squadron.coalition.air_wing.unclaim_squadron_def(squadron)
self.update()
self.config_changed.emit(squadron.aircraft)
return
def add_squadron(self, squadron: Squadron) -> None:
squadron_config = SquadronConfigurationBox(squadron, self.theater)
squadron_config.remove_squadron_signal.connect(self.remove_squadron)
self.squadron_configs.append(squadron_config)
self.addWidget(squadron_config)
class AircraftSquadronsPage(QWidget):
def __init__(
self, squadrons: list[Squadron], theater: ConflictTheater, air_wing: AirWing
) -> None:
remove_squadron_page = Signal(AircraftType)
def __init__(self, squadrons: list[Squadron], theater: ConflictTheater) -> None:
super().__init__()
layout = QVBoxLayout()
self.setLayout(layout)
self.squadrons_config = SquadronConfigurationLayout(
squadrons, theater, air_wing
)
self.squadrons_config = SquadronConfigurationLayout(squadrons, theater)
self.squadrons_config.config_changed.connect(self.on_squadron_config_changed)
scrolling_widget = QWidget()
scrolling_widget.setLayout(self.squadrons_config)
@ -242,51 +269,59 @@ class AircraftSquadronsPage(QWidget):
layout.addWidget(scrolling_area)
def addSquadron(self, squadron: Squadron) -> None:
self.squadrons_config.addSquadron(squadron)
def on_squadron_config_changed(self, aircraft_type: AircraftType):
if len(self.squadrons_config.squadron_configs) == 0:
self.remove_squadron_page.emit(aircraft_type)
def removeSquadron(self, squadron: Squadron) -> None:
self.squadrons_config.removeSquadron(squadron)
def add_squadron_to_page(self, squadron: Squadron):
self.squadrons_config.add_squadron(squadron)
def apply(self) -> list[Squadron]:
return self.squadrons_config.apply()
class AircraftSquadronsPanel(QStackedLayout):
page_removed = Signal(AircraftType)
def __init__(self, air_wing: AirWing, theater: ConflictTheater) -> None:
super().__init__()
self.air_wing = air_wing
self.theater = theater
self.air_wing.subscribe(self.handleChanges)
self.squadrons_pages: dict[AircraftType, AircraftSquadronsPage] = {}
for aircraft, squadrons in self.air_wing.squadrons.items():
page = AircraftSquadronsPage(squadrons, self.theater, self.air_wing)
self.addWidget(page)
self.squadrons_pages[aircraft] = page
self.new_page_for_type(aircraft, squadrons)
def __del__(self) -> None:
self.air_wing.unsubscribe(self.handleChanges)
def handleChanges(self, event) -> None:
if event.type == "add_aircraft_type":
aircraft_type = event.obj
if aircraft_type not in self.squadrons_pages:
page = AircraftSquadronsPage(
self.air_wing.squadrons[aircraft_type], self.theater, self.air_wing
)
self.addWidget(page)
self.squadrons_pages[aircraft_type] = page
elif event.type == "remove_aircraft_type":
aircraft_type = event.obj
if aircraft_type in self.squadrons_pages:
self.removeWidget(self.squadrons_pages[aircraft_type])
self.squadrons_pages.pop(aircraft_type)
elif event.type == "add_squadron":
squadron = event.obj
self.squadrons_pages[squadron.aircraft].addSquadron(squadron)
elif event.type == "remove_squadron":
squadron = event.obj
self.squadrons_pages[squadron.aircraft].removeSquadron(squadron)
def remove_page_for_type(self, aircraft_type: AircraftType):
page = self.squadrons_pages[aircraft_type]
self.removeWidget(page)
page.deleteLater()
self.squadrons_pages.pop(aircraft_type)
self.page_removed.emit(aircraft_type)
self.update()
def new_page_for_type(
self, aircraft_type: AircraftType, squadrons: list[Squadron]
) -> None:
page = AircraftSquadronsPage(squadrons, self.theater)
page.remove_squadron_page.connect(self.remove_page_for_type)
self.addWidget(page)
self.squadrons_pages[aircraft_type] = page
def add_squadron_to_panel(self, squadron: Squadron):
# Find existing page or add new one
if squadron.aircraft in self.squadrons_pages:
page = self.squadrons_pages[squadron.aircraft]
page.add_squadron_to_page(squadron)
else:
self.new_page_for_type(squadron.aircraft, [squadron])
self.update()
def apply(self) -> None:
self.air_wing.squadrons = {}
for aircraft, page in self.squadrons_pages.items():
self.air_wing.squadrons[aircraft] = page.apply()
class AircraftTypeList(QListView):
page_index_changed = Signal(int)
@ -296,47 +331,29 @@ class AircraftTypeList(QListView):
self.setIconSize(QSize(91, 24))
self.setMinimumWidth(300)
self.air_wing = air_wing
self.item_model = QStandardItemModel(self)
self.setModel(self.item_model)
for aircraft in self.air_wing.squadrons:
aircraft_item = QStandardItem(aircraft.name)
icon = self.icon_for(aircraft)
if icon is not None:
aircraft_item.setIcon(icon)
aircraft_item.setEditable(False)
aircraft_item.setSelectable(True)
self.item_model.appendRow(aircraft_item)
self.selectionModel().setCurrentIndex(
self.item_model.index(0, 0), QItemSelectionModel.Select
)
self.selectionModel().selectionChanged.connect(self.on_selection_changed)
for aircraft in air_wing.squadrons:
self.add_aircraft_type(aircraft)
self.air_wing.subscribe(self.handleChanges)
def remove_aircraft_type(self, aircraft: AircraftType):
for item in self.item_model.findItems(aircraft.name):
self.item_model.removeRow(item.row())
self.page_index_changed.emit(self.selectionModel().currentIndex().row())
def __del__(self) -> None:
self.air_wing.unsubscribe(self.handleChanges)
def handleChanges(self, event) -> None:
if event.type == "remove_aircraft_type":
aircraft_type = event.obj
items = self.item_model.findItems(aircraft_type.name)
if len(items) == 1:
for item in items:
self.item_model.takeRow(item.row())
elif event.type == "add_aircraft_type":
aircraft_type = event.obj
aircraft_item = QStandardItem(aircraft_type.name)
icon = self.icon_for(aircraft_type)
if icon is not None:
aircraft_item.setIcon(icon)
aircraft_item.setEditable(False)
aircraft_item.setSelectable(True)
self.item_model.appendRow(aircraft_item)
self.update()
def add_aircraft_type(self, aircraft: AircraftType):
aircraft_item = QStandardItem(aircraft.name)
icon = self.icon_for(aircraft)
if icon is not None:
aircraft_item.setIcon(icon)
aircraft_item.setEditable(False)
aircraft_item.setSelectable(True)
self.item_model.appendRow(aircraft_item)
def on_selection_changed(
self, selected: QItemSelection, _deselected: QItemSelection
@ -348,18 +365,6 @@ class AircraftTypeList(QListView):
return
self.page_index_changed.emit(indexes[0].row())
def deleteSelectedType(self) -> None:
if self.selectionModel().currentIndex().isValid():
aircraftName = str(self.selectionModel().currentIndex().data())
to_remove = None
for type in self.air_wing.squadrons:
if str(type) == aircraftName:
to_remove = type
if to_remove != None:
self.air_wing.remove_aircraft_type(to_remove)
else:
raise RuntimeError("No aircraft was selected for removal")
@staticmethod
def icon_for(aircraft: AircraftType) -> Optional[QIcon]:
name = aircraft.dcs_id
@ -369,37 +374,69 @@ class AircraftTypeList(QListView):
class AirWingConfigurationTab(QWidget):
def __init__(
self, coalition: Coalition, theater: ConflictTheater, game: Game
) -> None:
def __init__(self, coalition: Coalition, game: Game) -> None:
super().__init__()
self.game = game
self.theater = theater
self.coalition = coalition
self.air_wing = coalition.air_wing
layout = QGridLayout()
self.setLayout(layout)
self.game = game
self.coalition = coalition
self.type_list = AircraftTypeList(self.air_wing)
self.type_list = AircraftTypeList(coalition.air_wing)
layout.addWidget(self.type_list, 1, 1, 1, 2)
add_button = QPushButton("Add Aircraft/Squadron")
add_button.clicked.connect(lambda state: self.addAircraftType())
add_button = QPushButton("Add Squadron")
add_button.clicked.connect(lambda state: self.add_squadron())
layout.addWidget(add_button, 2, 1, 1, 1)
remove_button = QPushButton("Remove Aircraft")
remove_button.clicked.connect(lambda state: self.type_list.deleteSelectedType())
layout.addWidget(remove_button, 2, 2, 1, 1)
self.squadrons_panel = AircraftSquadronsPanel(self.air_wing, self.theater)
self.squadrons_panel = AircraftSquadronsPanel(coalition.air_wing, game.theater)
self.squadrons_panel.page_removed.connect(self.type_list.remove_aircraft_type)
layout.addLayout(self.squadrons_panel, 1, 3, 2, 1)
self.type_list.page_index_changed.connect(self.squadrons_panel.setCurrentIndex)
def addAircraftType(self) -> None:
SquadronConfigPopup(self.coalition, self.theater, self.game).exec_()
def add_squadron(self) -> None:
selected_aircraft = None
if self.type_list.selectionModel().currentIndex().row() >= 0:
selected_aircraft = self.type_list.item_model.item(
self.type_list.selectionModel().currentIndex().row()
).text()
popup = SquadronConfigPopup(
selected_aircraft,
self.coalition.faction.aircrafts,
list(self.game.theater.control_points_for(self.coalition.player)),
self.coalition.air_wing.squadron_defs,
)
if popup.exec_() != QDialog.Accepted:
return
selected_type = popup.aircraft_type_selector.currentData()
selected_base = popup.squadron_base_selector.currentData()
selected_def = popup.squadron_def_selector.currentData()
# Let user choose the preset or generate one
squadron_def = (
selected_def
or self.coalition.air_wing.squadron_def_generator.generate_for_aircraft(
selected_type
)
)
squadron = Squadron.create_from(
squadron_def, selected_base, self.coalition, self.game
)
# Add Squadron
if not self.type_list.item_model.findItems(selected_type.name):
self.type_list.add_aircraft_type(selected_type)
# TODO Select the newly added type
self.squadrons_panel.add_squadron_to_panel(squadron)
self.update()
def apply(self) -> None:
self.squadrons_panel.apply()
class AirWingConfigurationDialog(QDialog):
@ -414,22 +451,13 @@ class AirWingConfigurationDialog(QDialog):
layout = QVBoxLayout()
self.setLayout(layout)
doc_url = (
"https://github.com/dcs-liberation/dcs_liberation/wiki/Squadrons-and-pilots"
)
doc_label = QLabel(
"Use this opportunity to customize the squadrons available to your "
"coalition. <strong>This is your only opportunity to make changes.</strong>"
"<br /><br />"
"To accept your changes and continue, close this window.<br />"
"<br />"
"To remove a squadron from the game, uncheck the box in the title. New "
"squadrons cannot be added via the UI at this time. To add a custom "
"squadron,<br />"
f'see <a style="color:#ffffff" href="{doc_url}">the wiki</a>.'
"To accept your changes and continue, close this window."
)
doc_label.setOpenExternalLinks(True)
layout.addWidget(doc_label)
tab_widget = QTabWidget()
@ -437,10 +465,113 @@ class AirWingConfigurationDialog(QDialog):
self.tabs = []
for coalition in game.coalitions:
coalition_tab = AirWingConfigurationTab(coalition, game.theater, game)
coalition_tab = AirWingConfigurationTab(coalition, game)
name = "Blue" if coalition.player else "Red"
tab_widget.addTab(coalition_tab, name)
self.tabs.append(coalition_tab)
def reject(self) -> None:
for tab in self.tabs:
tab.apply()
super().reject()
class SquadronAircraftTypeSelector(QComboBox):
def __init__(
self, types: list[AircraftType], selected_aircraft: Optional[str]
) -> None:
super().__init__()
self.setSizeAdjustPolicy(self.AdjustToContents)
for type in sorted(types, key=lambda type: type.name):
self.addItem(type.name, type)
if selected_aircraft:
self.setCurrentText(selected_aircraft)
class SquadronDefSelector(QComboBox):
def __init__(
self,
squadron_defs: dict[AircraftType, list[SquadronDef]],
aircraft: Optional[AircraftType],
) -> None:
super().__init__()
self.setSizeAdjustPolicy(self.AdjustToContents)
self.squadron_defs = squadron_defs
self.set_aircraft_type(aircraft)
def set_aircraft_type(self, aircraft: Optional[AircraftType]):
self.clear()
self.addItem("None (Random)", None)
if aircraft and aircraft in self.squadron_defs:
for squadron_def in sorted(
self.squadron_defs[aircraft], key=lambda squadron_def: squadron_def.name
):
if not squadron_def.claimed:
squadron_name = squadron_def.name
if squadron_def.nickname:
squadron_name += " (" + squadron_def.nickname + ")"
self.addItem(squadron_name, squadron_def)
self.setCurrentText("None (Random)")
class SquadronConfigPopup(QDialog):
def __init__(
self,
selected_aircraft: Optional[str],
types: list[AircraftType],
bases: list[ControlPoint],
squadron_defs: dict[AircraftType, list[SquadronDef]],
) -> None:
super().__init__()
self.setWindowTitle(f"Add new Squadron")
self.column = QVBoxLayout()
self.setLayout(self.column)
self.bases = bases
self.column.addWidget(QLabel("Aircraft:"))
self.aircraft_type_selector = SquadronAircraftTypeSelector(
types, selected_aircraft
)
self.aircraft_type_selector.currentIndexChanged.connect(
self.on_aircraft_selection
)
self.column.addWidget(self.aircraft_type_selector)
self.column.addWidget(QLabel("Base:"))
self.squadron_base_selector = SquadronBaseSelector(
bases, None, self.aircraft_type_selector.currentData()
)
self.column.addWidget(self.squadron_base_selector)
self.column.addWidget(QLabel("Preset:"))
self.squadron_def_selector = SquadronDefSelector(
squadron_defs, self.aircraft_type_selector.currentData()
)
self.column.addWidget(self.squadron_def_selector)
self.column.addStretch()
self.button_layout = QHBoxLayout()
self.column.addLayout(self.button_layout)
self.accept_button = QPushButton("Accept")
self.accept_button.clicked.connect(lambda state: self.accept())
self.button_layout.addWidget(self.accept_button)
self.cancel_button = QPushButton("Cancel")
self.cancel_button.clicked.connect(lambda state: self.reject())
self.button_layout.addWidget(self.cancel_button)
def on_aircraft_selection(self) -> None:
self.squadron_base_selector.set_aircraft_type(
self.aircraft_type_selector.currentData()
)
self.squadron_def_selector.set_aircraft_type(
self.aircraft_type_selector.currentData()
)
self.update()

View File

@ -1,145 +0,0 @@
from typing import Optional, Callable, Iterable
from PySide2.QtWidgets import (
QDialog,
QPushButton,
QVBoxLayout,
QLabel,
QLineEdit,
QTextEdit,
QCheckBox,
QHBoxLayout,
QComboBox,
)
from game.dcs.aircrafttype import AircraftType
from game.game import Game
from game.squadrons import Squadron
from game.theater import ConflictTheater, ControlPoint
from game.coalition import Coalition
from game.factions.faction import Faction
from game.campaignloader.squadrondefgenerator import SquadronDefGenerator
from gen.flights.flight import FlightType
class AircraftTypeSelector(QComboBox):
def __init__(self, faction: Faction) -> None:
super().__init__()
self.types = faction.aircrafts
self.setSizeAdjustPolicy(self.AdjustToContents)
self.addItem("Select aircraft type...", None)
self.setCurrentText("Select aircraft type...")
self.types.sort(key=str)
for type in self.types:
self.addItem(type.name, type)
class SquadronConfigPopup(QDialog):
def __init__(
self, coalition: Coalition, theater: ConflictTheater, game: Game
) -> None:
super().__init__()
self.game = game
self.coalition = coalition
self.theater = theater
self.squadron = None
# self.setMinimumSize(500, 800)
self.setWindowTitle(f"Add new Squadron")
self.column = QVBoxLayout()
self.setLayout(self.column)
self.aircraft_type_selector = AircraftTypeSelector(coalition.faction)
self.column.addWidget(self.aircraft_type_selector)
self.column.addWidget(QLabel("Name:"))
self.name_edit = QLineEdit("---")
self.name_edit.setEnabled(False)
self.name_edit.textChanged.connect(self.on_name_changed)
self.column.addWidget(self.name_edit)
self.column.addWidget(QLabel("Nickname:"))
self.nickname_edit = QLineEdit("---")
self.nickname_edit.setEnabled(False)
self.nickname_edit.textChanged.connect(self.on_nickname_changed)
self.column.addWidget(self.nickname_edit)
self.column.addStretch()
self.aircraft_type_selector.currentIndexChanged.connect(
self.on_aircraft_selection
)
self.button_layout = QHBoxLayout()
self.column.addLayout(self.button_layout)
self.accept_button = QPushButton("Accept")
self.accept_button.clicked.connect(lambda state: self.accept())
self.accept_button.setEnabled(False)
self.button_layout.addWidget(self.accept_button)
self.cancel_button = QPushButton("Cancel")
self.cancel_button.clicked.connect(lambda state: self.cancel())
self.button_layout.addWidget(self.cancel_button)
def create_Squadron(
self, aircraft_type: AircraftType, base: ControlPoint
) -> Squadron:
squadron_def = SquadronDefGenerator(self.coalition).generate_for_aircraft(
aircraft_type
)
squadron = Squadron(
squadron_def.name,
squadron_def.nickname,
squadron_def.country,
squadron_def.role,
squadron_def.aircraft,
squadron_def.livery,
squadron_def.mission_types,
squadron_def.operating_bases,
squadron_def.pilot_pool,
self.coalition,
self.game.settings,
base,
)
return squadron
def on_aircraft_selection(self, index: int) -> None:
aircraft_type_name = self.aircraft_type_selector.currentText()
aircraft_type = None
for aircraft in self.coalition.faction.aircrafts:
if str(aircraft) == aircraft_type_name:
aircraft_type = aircraft
if aircraft != None:
self.squadron = self.create_Squadron(
aircraft_type,
next(self.theater.control_points_for(self.coalition.player)),
)
self.name_edit.setText(self.squadron.name)
self.name_edit.setEnabled(True)
self.nickname_edit.setText(self.squadron.nickname)
self.nickname_edit.setEnabled(True)
self.accept_button.setStyleSheet("background-color: green")
self.accept_button.setEnabled(True)
self.update()
def on_name_changed(self, text: str) -> None:
self.squadron.name = text
def on_nickname_changed(self, text: str) -> None:
self.squadron.nickname = text
def accept(self) -> None:
self.coalition.air_wing.add_squadron(self.squadron)
return super().accept()
def cancel(self) -> None:
return super().reject()

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB