mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Allow configuration of the air wing at game start.
After completing the new game wizard but before initializing turn 0, open a dialog to allow the player to customize their air wing. With this they can remove squadrons from the game, rename them, add players, or change allowed mission types. *Adding* squadrons is not currently supported, nor is changing the squadron's livery (the data in pydcs is an arbitrary class hierarchy that can't be safely indexed by country). This only applies to the blue air wing for now. Future improvements: * Add squadron button. * Collapse disable squadrons to declutter? * Tabs on the side like the settings dialog to group by aircraft type. * Top tab bar to switch between red and blue air wings.
This commit is contained in:
parent
f2dc95b86d
commit
9bb8e00c3d
@ -150,6 +150,13 @@ class Coalition:
|
||||
# is handled correctly.
|
||||
self.transfers.perform_transfers()
|
||||
|
||||
def preinit_turn_0(self) -> None:
|
||||
"""Runs final Coalition initialization.
|
||||
|
||||
Final initialization occurs before Game.initialize_turn runs for turn 0.
|
||||
"""
|
||||
self.air_wing.populate_for_turn_0()
|
||||
|
||||
def initialize_turn(self) -> None:
|
||||
"""Processes coalition-specific turn initialization.
|
||||
|
||||
|
||||
@ -294,6 +294,8 @@ class Game:
|
||||
def begin_turn_0(self) -> None:
|
||||
"""Initialization for the first turn of the game."""
|
||||
self.turn = 0
|
||||
self.blue.preinit_turn_0()
|
||||
self.red.preinit_turn_0()
|
||||
self.initialize_turn()
|
||||
|
||||
def pass_turn(self, no_action: bool = False) -> None:
|
||||
|
||||
@ -101,9 +101,6 @@ class Squadron:
|
||||
settings: Settings = field(hash=False, compare=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if any(p.status is not PilotStatus.Active for p in self.pilot_pool):
|
||||
raise ValueError("Squadrons can only be created with active pilots.")
|
||||
self._recruit_pilots(self.settings.squadron_pilot_limit)
|
||||
self.auto_assignable_mission_types = set(self.mission_types)
|
||||
|
||||
def __str__(self) -> str:
|
||||
@ -181,6 +178,11 @@ class Squadron:
|
||||
self.current_roster.extend(new_pilots)
|
||||
self.available_pilots.extend(new_pilots)
|
||||
|
||||
def populate_for_turn_0(self) -> None:
|
||||
if any(p.status is not PilotStatus.Active for p in self.pilot_pool):
|
||||
raise ValueError("Squadrons can only be created with active pilots.")
|
||||
self._recruit_pilots(self.settings.squadron_pilot_limit)
|
||||
|
||||
def replenish_lost_pilots(self) -> None:
|
||||
if not self.pilot_limits_enabled:
|
||||
return
|
||||
@ -414,6 +416,10 @@ class AirWing:
|
||||
def squadron_at_index(self, index: int) -> Squadron:
|
||||
return list(self.iter_squadrons())[index]
|
||||
|
||||
def populate_for_turn_0(self) -> None:
|
||||
for squadron in self.iter_squadrons():
|
||||
squadron.populate_for_turn_0()
|
||||
|
||||
def replenish(self) -> None:
|
||||
for squadron in self.iter_squadrons():
|
||||
squadron.replenish_lost_pilots()
|
||||
|
||||
@ -123,7 +123,6 @@ class GameGenerator:
|
||||
|
||||
GroundObjectGenerator(game, self.generator_settings).generate()
|
||||
game.settings.version = VERSION
|
||||
game.begin_turn_0()
|
||||
return game
|
||||
|
||||
def prepare_theater(self) -> None:
|
||||
|
||||
@ -246,7 +246,9 @@ def create_game(
|
||||
high_digit_sams=False,
|
||||
),
|
||||
)
|
||||
return generator.generate()
|
||||
game = generator.generate()
|
||||
game.begin_turn_0()
|
||||
return game
|
||||
|
||||
|
||||
def lint_weapon_data() -> None:
|
||||
|
||||
220
qt_ui/windows/AirWingConfigurationDialog.py
Normal file
220
qt_ui/windows/AirWingConfigurationDialog.py
Normal file
@ -0,0 +1,220 @@
|
||||
import itertools
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from typing import Optional, Callable, Iterator
|
||||
|
||||
from PySide2.QtCore import (
|
||||
QItemSelectionModel,
|
||||
QModelIndex,
|
||||
QSize,
|
||||
Qt,
|
||||
)
|
||||
from PySide2.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QDialog,
|
||||
QListView,
|
||||
QVBoxLayout,
|
||||
QGroupBox,
|
||||
QGridLayout,
|
||||
QLabel,
|
||||
QWidget,
|
||||
QScrollArea,
|
||||
QLineEdit,
|
||||
QTextEdit,
|
||||
QCheckBox,
|
||||
QHBoxLayout,
|
||||
)
|
||||
|
||||
from game import Game
|
||||
from game.squadrons import Squadron, AirWing, Pilot
|
||||
from gen.flights.flight import FlightType
|
||||
from qt_ui.models import AirWingModel, SquadronModel
|
||||
from qt_ui.windows.AirWingDialog import SquadronDelegate
|
||||
from qt_ui.windows.SquadronDialog import SquadronDialog
|
||||
|
||||
|
||||
class SquadronList(QListView):
|
||||
"""List view for displaying the air wing's squadrons."""
|
||||
|
||||
def __init__(self, air_wing_model: AirWingModel) -> None:
|
||||
super().__init__()
|
||||
self.air_wing_model = air_wing_model
|
||||
self.dialog: Optional[SquadronDialog] = None
|
||||
|
||||
self.setIconSize(QSize(91, 24))
|
||||
self.setItemDelegate(SquadronDelegate(self.air_wing_model))
|
||||
self.setModel(self.air_wing_model)
|
||||
self.selectionModel().setCurrentIndex(
|
||||
self.air_wing_model.index(0, 0, QModelIndex()), QItemSelectionModel.Select
|
||||
)
|
||||
|
||||
# self.setIconSize(QSize(91, 24))
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectItems)
|
||||
self.doubleClicked.connect(self.on_double_click)
|
||||
|
||||
def on_double_click(self, index: QModelIndex) -> None:
|
||||
if not index.isValid():
|
||||
return
|
||||
self.dialog = SquadronDialog(
|
||||
SquadronModel(self.air_wing_model.squadron_at_index(index)), self
|
||||
)
|
||||
self.dialog.show()
|
||||
|
||||
|
||||
class AllowedMissionTypeControls(QVBoxLayout):
|
||||
def __init__(self, squadron: Squadron) -> None:
|
||||
super().__init__()
|
||||
self.squadron = squadron
|
||||
self.allowed_mission_types = set()
|
||||
|
||||
self.addWidget(QLabel("Allowed 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 FlightType:
|
||||
enabled = task in squadron.mission_types
|
||||
if enabled:
|
||||
self.allowed_mission_types.add(task)
|
||||
checkbox = QCheckBox(text=task.value)
|
||||
checkbox.setChecked(enabled)
|
||||
checkbox.toggled.connect(make_callback(task))
|
||||
self.addWidget(checkbox)
|
||||
|
||||
self.addStretch()
|
||||
|
||||
def on_toggled(self, task: FlightType, checked: bool) -> None:
|
||||
if checked:
|
||||
self.allowed_mission_types.add(task)
|
||||
else:
|
||||
self.allowed_mission_types.remove(task)
|
||||
|
||||
|
||||
class SquadronConfigurationBox(QGroupBox):
|
||||
def __init__(self, squadron: Squadron) -> None:
|
||||
super().__init__()
|
||||
self.setCheckable(True)
|
||||
self.squadron = squadron
|
||||
self.reset_title()
|
||||
|
||||
columns = QHBoxLayout()
|
||||
self.setLayout(columns)
|
||||
|
||||
left_column = QVBoxLayout()
|
||||
columns.addLayout(left_column)
|
||||
|
||||
left_column.addWidget(QLabel("Name:"))
|
||||
self.name_edit = QLineEdit(squadron.name)
|
||||
self.name_edit.textChanged.connect(self.on_name_changed)
|
||||
left_column.addWidget(self.name_edit)
|
||||
|
||||
left_column.addWidget(QLabel("Nickname:"))
|
||||
self.nickname_edit = QLineEdit(squadron.nickname)
|
||||
self.nickname_edit.textChanged.connect(self.on_nickname_changed)
|
||||
left_column.addWidget(self.nickname_edit)
|
||||
|
||||
left_column.addWidget(
|
||||
QLabel("Players (one per line, leave empty for an AI-only squadron):")
|
||||
)
|
||||
players = [p for p in squadron.available_pilots if p.player]
|
||||
for player in players:
|
||||
squadron.available_pilots.remove(player)
|
||||
self.player_list = QTextEdit("<br />".join(p.name for p in players))
|
||||
self.player_list.setAcceptRichText(False)
|
||||
left_column.addWidget(self.player_list)
|
||||
|
||||
left_column.addStretch()
|
||||
|
||||
self.allowed_missions = AllowedMissionTypeControls(squadron)
|
||||
columns.addLayout(self.allowed_missions)
|
||||
|
||||
def on_name_changed(self, text: str) -> None:
|
||||
self.squadron.name = text
|
||||
self.reset_title()
|
||||
|
||||
def on_nickname_changed(self, text: str) -> None:
|
||||
self.squadron.nickname = text
|
||||
|
||||
def reset_title(self) -> None:
|
||||
self.setTitle(f"{self.squadron.name} - {self.squadron.aircraft}")
|
||||
|
||||
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.mission_types = tuple(self.allowed_missions.allowed_mission_types)
|
||||
return self.squadron
|
||||
|
||||
|
||||
class AirWingConfigurationLayout(QVBoxLayout):
|
||||
def __init__(self, air_wing: AirWing) -> None:
|
||||
super().__init__()
|
||||
self.air_wing = air_wing
|
||||
self.squadron_configs = []
|
||||
|
||||
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<br />"
|
||||
"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<br />"
|
||||
"be added via the UI at this time. To add a custom squadron, see "
|
||||
f'<a style="color:#ffffff" href="{doc_url}">the wiki</a>.'
|
||||
)
|
||||
|
||||
doc_label.setOpenExternalLinks(True)
|
||||
self.addWidget(doc_label)
|
||||
for squadron in self.air_wing.iter_squadrons():
|
||||
squadron_config = SquadronConfigurationBox(squadron)
|
||||
self.squadron_configs.append(squadron_config)
|
||||
self.addWidget(squadron_config)
|
||||
|
||||
def apply(self) -> None:
|
||||
keep_squadrons = defaultdict(list)
|
||||
for squadron_config in self.squadron_configs:
|
||||
if squadron_config.isChecked():
|
||||
squadron = squadron_config.apply()
|
||||
keep_squadrons[squadron.aircraft].append(squadron)
|
||||
self.air_wing.squadrons = keep_squadrons
|
||||
|
||||
|
||||
class AirWingConfigurationDialog(QDialog):
|
||||
"""Dialog window for air wing configuration."""
|
||||
|
||||
def __init__(self, game: Game, parent) -> None:
|
||||
super().__init__(parent)
|
||||
self.air_wing = game.blue.air_wing
|
||||
|
||||
self.setMinimumSize(500, 800)
|
||||
self.setWindowTitle(f"Air Wing Configuration")
|
||||
# TODO: self.setWindowIcon()
|
||||
|
||||
self.air_wing_config = AirWingConfigurationLayout(self.air_wing)
|
||||
|
||||
scrolling_layout = QVBoxLayout()
|
||||
scrolling_widget = QWidget()
|
||||
scrolling_widget.setLayout(self.air_wing_config)
|
||||
|
||||
scrolling_area = QScrollArea()
|
||||
scrolling_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
scrolling_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
||||
scrolling_area.setWidgetResizable(True)
|
||||
scrolling_area.setWidget(scrolling_widget)
|
||||
|
||||
scrolling_layout.addWidget(scrolling_area)
|
||||
self.setLayout(scrolling_layout)
|
||||
|
||||
def reject(self) -> None:
|
||||
self.air_wing_config.apply()
|
||||
super().reject()
|
||||
@ -15,6 +15,7 @@ from game.theater.start_generator import GameGenerator, GeneratorSettings, ModSe
|
||||
from game.factions.faction import Faction
|
||||
from qt_ui.widgets.QLiberationCalendar import QLiberationCalendar
|
||||
from qt_ui.widgets.spinsliders import TenthsSpinSlider, TimeInputs, CurrencySpinner
|
||||
from qt_ui.windows.AirWingConfigurationDialog import AirWingConfigurationDialog
|
||||
from qt_ui.windows.newgame.QCampaignList import (
|
||||
Campaign,
|
||||
QCampaignList,
|
||||
@ -125,6 +126,10 @@ class NewGameWizard(QtWidgets.QWizard):
|
||||
)
|
||||
self.generatedGame = generator.generate()
|
||||
|
||||
AirWingConfigurationDialog(self.generatedGame, self).exec_()
|
||||
|
||||
self.generatedGame.begin_turn_0()
|
||||
|
||||
super(NewGameWizard, self).accept()
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user