Merge remote-tracking branch 'khopa/develop' into helipads

# Conflicts:
#	changelog.md
This commit is contained in:
Khopa
2021-09-08 21:56:45 +02:00
132 changed files with 2856 additions and 648 deletions

View File

@@ -10,7 +10,6 @@ from PySide2.QtCore import (
)
from PySide2.QtGui import QStandardItemModel, QStandardItem, QIcon
from PySide2.QtWidgets import (
QAbstractItemView,
QDialog,
QListView,
QVBoxLayout,
@@ -32,38 +31,7 @@ from game.dcs.aircrafttype import AircraftType
from game.squadrons import AirWing, Pilot, Squadron
from game.theater import ControlPoint, ConflictTheater
from gen.flights.flight import FlightType
from qt_ui.models import AirWingModel, SquadronModel
from qt_ui.uiconstants import AIRCRAFT_ICONS
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):

View File

@@ -14,12 +14,14 @@ from PySide2.QtWidgets import (
QTableWidget,
QTableWidgetItem,
QWidget,
QHBoxLayout,
)
from game.squadrons import Squadron
from game.theater import ConflictTheater
from gen.flights.flight import Flight
from qt_ui.delegates import TwoColumnRowDelegate
from qt_ui.models import GameModel, AirWingModel, SquadronModel
from qt_ui.models import GameModel, AirWingModel, SquadronModel, AtoModel
from qt_ui.windows.SquadronDialog import SquadronDialog
@@ -56,9 +58,16 @@ class SquadronDelegate(TwoColumnRowDelegate):
class SquadronList(QListView):
"""List view for displaying the air wing's squadrons."""
def __init__(self, air_wing_model: AirWingModel) -> None:
def __init__(
self,
ato_model: AtoModel,
air_wing_model: AirWingModel,
theater: ConflictTheater,
) -> None:
super().__init__()
self.ato_model = ato_model
self.air_wing_model = air_wing_model
self.theater = theater
self.dialog: Optional[SquadronDialog] = None
self.setIconSize(QSize(91, 24))
@@ -76,7 +85,10 @@ class SquadronList(QListView):
if not index.isValid():
return
self.dialog = SquadronDialog(
SquadronModel(self.air_wing_model.squadron_at_index(index)), self
self.ato_model,
SquadronModel(self.air_wing_model.squadron_at_index(index)),
self.theater,
self,
)
self.dialog.show()
@@ -138,30 +150,47 @@ class AircraftInventoryData:
class AirInventoryView(QWidget):
def __init__(self, game_model: GameModel) -> None:
super().__init__()
self.game_model = game_model
self.country = self.game_model.game.country_for(player=True)
self.only_unallocated = False
self.enemy_info = False
layout = QVBoxLayout()
self.setLayout(layout)
self.only_unallocated_cb = QCheckBox("Unallocated Only?")
self.only_unallocated_cb.toggled.connect(self.update_table)
checkbox_row = QHBoxLayout()
layout.addLayout(checkbox_row)
layout.addWidget(self.only_unallocated_cb)
self.only_unallocated_cb = QCheckBox("Unallocated only")
self.only_unallocated_cb.toggled.connect(self.set_only_unallocated)
checkbox_row.addWidget(self.only_unallocated_cb)
self.enemy_info_cb = QCheckBox("Show enemy info")
self.enemy_info_cb.toggled.connect(self.set_enemy_info)
checkbox_row.addWidget(self.enemy_info_cb)
checkbox_row.addStretch()
self.table = QTableWidget()
layout.addWidget(self.table)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.verticalHeader().setVisible(False)
self.update_table(False)
self.set_only_unallocated(False)
def update_table(self, only_unallocated: bool) -> None:
def set_only_unallocated(self, value: bool) -> None:
self.only_unallocated = value
self.update_table()
def set_enemy_info(self, value: bool) -> None:
self.enemy_info = value
self.update_table()
def update_table(self) -> None:
self.table.setSortingEnabled(False)
self.table.clear()
inventory_rows = list(self.get_data(only_unallocated))
inventory_rows = list(self.get_data())
self.table.setRowCount(len(inventory_rows))
headers = AircraftInventoryData.headers()
self.table.setColumnCount(len(headers))
@@ -175,18 +204,19 @@ class AirInventoryView(QWidget):
self.table.setSortingEnabled(True)
def iter_allocated_aircraft(self) -> Iterator[AircraftInventoryData]:
for package in self.game_model.game.blue.ato.packages:
coalition = self.game_model.game.coalition_for(not self.enemy_info)
for package in coalition.ato.packages:
for flight in package.flights:
yield from AircraftInventoryData.from_flight(flight)
def iter_unallocated_aircraft(self) -> Iterator[AircraftInventoryData]:
game = self.game_model.game
for squadron in game.blue.air_wing.iter_squadrons():
coalition = self.game_model.game.coalition_for(not self.enemy_info)
for squadron in coalition.air_wing.iter_squadrons():
yield from AircraftInventoryData.each_untasked_from_squadron(squadron)
def get_data(self, only_unallocated: bool) -> Iterator[AircraftInventoryData]:
def get_data(self) -> Iterator[AircraftInventoryData]:
yield from self.iter_unallocated_aircraft()
if not only_unallocated:
if not self.only_unallocated:
yield from self.iter_allocated_aircraft()
@@ -194,7 +224,14 @@ class AirWingTabs(QTabWidget):
def __init__(self, game_model: GameModel) -> None:
super().__init__()
self.addTab(SquadronList(game_model.blue_air_wing_model), "Squadrons")
self.addTab(
SquadronList(
game_model.ato_model,
game_model.blue_air_wing_model,
game_model.game.theater,
),
"Squadrons",
)
self.addTab(AirInventoryView(game_model), "Inventory")

View File

@@ -24,6 +24,7 @@ from qt_ui import liberation_install
from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel
from qt_ui.uiconstants import URLS
from qt_ui.uncaughtexceptionhandler import UncaughtExceptionHandler
from qt_ui.widgets.QTopPanel import QTopPanel
from qt_ui.widgets.ato import QAirTaskingOrderPanel
from qt_ui.widgets.map.QLiberationMap import QLiberationMap
@@ -42,7 +43,9 @@ from qt_ui.windows.logs.QLogsWindow import QLogsWindow
class QLiberationWindow(QMainWindow):
def __init__(self, game: Optional[Game]) -> None:
super(QLiberationWindow, self).__init__()
super().__init__()
self._uncaught_exception_handler = UncaughtExceptionHandler(self)
self.game = game
self.game_model = GameModel(game)

View File

@@ -1,5 +1,5 @@
import logging
from typing import Callable
from typing import Callable, Iterator, Optional
from PySide2.QtCore import (
QItemSelectionModel,
@@ -16,12 +16,15 @@ from PySide2.QtWidgets import (
QHBoxLayout,
QLabel,
QCheckBox,
QComboBox,
)
from game.squadrons import Pilot
from game.squadrons import Pilot, Squadron
from game.theater import ControlPoint, ConflictTheater
from gen.flights.flight import FlightType
from qt_ui.delegates import TwoColumnRowDelegate
from qt_ui.models import SquadronModel
from qt_ui.errorreporter import report_errors
from qt_ui.models import SquadronModel, AtoModel
class PilotDelegate(TwoColumnRowDelegate):
@@ -90,12 +93,58 @@ class AutoAssignedTaskControls(QVBoxLayout):
self.squadron_model.set_auto_assignable(task, checked)
class SquadronDestinationComboBox(QComboBox):
def __init__(self, squadron: Squadron, theater: ConflictTheater) -> None:
super().__init__()
self.squadron = squadron
self.theater = theater
room = squadron.location.unclaimed_parking()
self.addItem(
f"Remain at {squadron.location} (room for {room} more aircraft)", None
)
selected_index: Optional[int] = None
for idx, destination in enumerate(sorted(self.iter_destinations(), key=str), 1):
if destination == squadron.destination:
selected_index = idx
room = destination.unclaimed_parking()
self.addItem(
f"Transfer to {destination} (room for {room} more aircraft)",
destination,
)
if squadron.destination is None:
selected_index = 0
if selected_index is not None:
self.setCurrentIndex(selected_index)
def iter_destinations(self) -> Iterator[ControlPoint]:
size = self.squadron.expected_size_next_turn
for control_point in self.theater.control_points_for(self.squadron.player):
if control_point == self:
continue
if not control_point.can_operate(self.squadron.aircraft):
continue
if control_point.unclaimed_parking() < size:
continue
yield control_point
class SquadronDialog(QDialog):
"""Dialog window showing a squadron."""
def __init__(self, squadron_model: SquadronModel, parent) -> None:
def __init__(
self,
ato_model: AtoModel,
squadron_model: SquadronModel,
theater: ConflictTheater,
parent,
) -> None:
super().__init__(parent)
self.ato_model = ato_model
self.squadron_model = squadron_model
self.theater = theater
self.setMinimumSize(1000, 440)
self.setWindowTitle(str(squadron_model.squadron))
@@ -117,6 +166,15 @@ class SquadronDialog(QDialog):
columns.addWidget(self.pilot_list)
button_panel = QHBoxLayout()
self.transfer_destination = SquadronDestinationComboBox(
squadron_model.squadron, theater
)
self.transfer_destination.currentIndexChanged.connect(
self.on_destination_changed
)
button_panel.addWidget(self.transfer_destination)
button_panel.addStretch()
layout.addLayout(button_panel)
@@ -132,6 +190,19 @@ class SquadronDialog(QDialog):
self.toggle_leave_button.clicked.connect(self.toggle_leave)
button_panel.addWidget(self.toggle_leave_button, alignment=Qt.AlignRight)
@property
def squadron(self) -> Squadron:
return self.squadron_model.squadron
def on_destination_changed(self, index: int) -> None:
with report_errors("Could not change squadron destination", self):
destination = self.transfer_destination.itemData(index)
if destination is None:
self.squadron.cancel_relocation()
else:
self.squadron.plan_relocation(destination, self.theater)
self.ato_model.replace_from_game(player=True)
def check_disabled_button_states(
self, button: QPushButton, index: QModelIndex
) -> bool:

View File

@@ -190,7 +190,7 @@ class QBaseMenu2(QDialog):
self.repair_button.setDisabled(True)
def update_intel_summary(self) -> None:
aircraft = self.cp.allocated_aircraft(self.game_model.game).total_present
aircraft = self.cp.allocated_aircraft().total_present
parking = self.cp.total_aircraft_parking
ground_unit_limit = self.cp.frontline_unit_count_limit
deployable_unit_info = ""

View File

@@ -1,6 +1,6 @@
from PySide2.QtWidgets import QTabWidget
from game.theater import ControlPoint, OffMapSpawn, Fob
from game.theater import ControlPoint, Fob
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.DepartingConvoysMenu import DepartingConvoysMenu
from qt_ui.windows.basemenu.airfield.QAirfieldCommand import QAirfieldCommand
@@ -13,7 +13,7 @@ class QBaseMenuTabs(QTabWidget):
super(QBaseMenuTabs, self).__init__()
if not cp.captured:
self.intel = QIntelInfo(cp, game_model.game)
self.intel = QIntelInfo(cp)
self.addTab(self.intel, "Intel")
self.departing_convoys = DepartingConvoysMenu(cp, game_model)

View File

@@ -273,6 +273,8 @@ class UnitTransactionFrame(QFrame, Generic[TransactionItemType]):
else:
return "Unit can not be sold."
def info(self, unit_type: UnitType) -> None:
self.info_window = QUnitInfoWindow(self.game_model.game, unit_type)
def info(self, item: TransactionItemType) -> None:
self.info_window = QUnitInfoWindow(
self.game_model.game, self.purchase_adapter.unit_type_of(item)
)
self.info_window.show()

View File

@@ -21,12 +21,7 @@ from game.purchaseadapter import AircraftPurchaseAdapter
class QAircraftRecruitmentMenu(UnitTransactionFrame[Squadron]):
def __init__(self, cp: ControlPoint, game_model: GameModel) -> None:
super().__init__(
game_model,
AircraftPurchaseAdapter(
cp, game_model.game.coalition_for(cp.captured), game_model.game
),
)
super().__init__(game_model, AircraftPurchaseAdapter(cp))
self.cp = cp
self.game_model = game_model
self.purchase_groups = {}
@@ -98,7 +93,7 @@ class QHangarStatus(QHBoxLayout):
self.setAlignment(Qt.AlignLeft)
def update_label(self) -> None:
next_turn = self.control_point.allocated_aircraft(self.game_model.game)
next_turn = self.control_point.allocated_aircraft()
max_amount = self.control_point.total_aircraft_parking
components = [f"{next_turn.total_present} present"]

View File

@@ -11,22 +11,20 @@ from PySide2.QtWidgets import (
QWidget,
)
from game import Game
from game.theater import ControlPoint
class QIntelInfo(QFrame):
def __init__(self, cp: ControlPoint, game: Game):
def __init__(self, cp: ControlPoint):
super(QIntelInfo, self).__init__()
self.cp = cp
self.game = game
layout = QVBoxLayout()
scroll_content = QWidget()
intel_layout = QVBoxLayout()
units_by_task: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
for unit_type, count in self.cp.allocated_aircraft(game).present.items():
for unit_type, count in self.cp.allocated_aircraft().present.items():
if count:
task_type = unit_type.dcs_unit_type.task_default.name
units_by_task[task_type][unit_type.name] += count

View File

@@ -77,7 +77,7 @@ class AircraftIntelLayout(IntelTableLayout):
total = 0
for control_point in game.theater.control_points_for(player):
allocation = control_point.allocated_aircraft(game)
allocation = control_point.allocated_aircraft()
base_total = allocation.total_present
total += base_total
if not base_total:

View File

@@ -85,7 +85,7 @@ class QFlightCreator(QDialog):
squadron, initial_size=self.flight_size_spinner.value()
)
self.roster_editor = FlightRosterEditor(roster)
self.flight_size_spinner.valueChanged.connect(self.resize_roster)
self.flight_size_spinner.valueChanged.connect(self.roster_editor.resize)
self.squadron_selector.currentIndexChanged.connect(self.on_squadron_changed)
roster_layout = QHBoxLayout()
layout.addLayout(roster_layout)
@@ -136,10 +136,6 @@ class QFlightCreator(QDialog):
def set_custom_name_text(self, text: str):
self.custom_name_text = text
def resize_roster(self, new_size: int) -> None:
self.roster_editor.roster.resize(new_size)
self.roster_editor.resize(new_size)
def verify_form(self) -> Optional[str]:
aircraft: Optional[Type[FlyingType]] = self.aircraft_selector.currentData()
squadron: Optional[Squadron] = self.squadron_selector.currentData()
@@ -182,8 +178,6 @@ class QFlightCreator(QDialog):
roster.max_size,
task,
self.start_type.currentText(),
squadron.location,
squadron.location,
divert,
custom_name=self.custom_name_text,
roster=roster,
@@ -198,7 +192,6 @@ class QFlightCreator(QDialog):
self.squadron_selector.update_items(
self.task_selector.currentData(), new_aircraft
)
self.departure.change_aircraft(new_aircraft)
self.divert.change_aircraft(new_aircraft)
def on_departure_changed(self, departure: ControlPoint) -> None:
@@ -223,6 +216,7 @@ class QFlightCreator(QDialog):
def on_squadron_changed(self, index: int) -> None:
squadron: Optional[Squadron] = self.squadron_selector.itemData(index)
self.update_max_size(self.squadron_selector.aircraft_available)
# Clear the roster first so we return the pilots to the pool. This way if we end
# up repopulating from the same squadron we'll get the same pilots back.
self.roster_editor.replace(None)
@@ -230,7 +224,7 @@ class QFlightCreator(QDialog):
self.roster_editor.replace(
FlightRoster(squadron, self.flight_size_spinner.value())
)
self.on_departure_changed(squadron.location)
self.on_departure_changed(squadron.location)
def update_max_size(self, available: int) -> None:
aircraft = self.aircraft_selector.currentData()

View File

@@ -176,6 +176,8 @@ class FlightRosterEditor(QVBoxLayout):
def resize(self, new_size: int) -> None:
if new_size > self.MAX_PILOTS:
raise ValueError("A flight may not have more than four pilots.")
if self.roster is not None:
self.roster.resize(new_size)
for controls in self.pilot_controls[:new_size]:
controls.enable_and_reset()
for controls in self.pilot_controls[new_size:]:

View File

@@ -65,6 +65,8 @@ class NewGameWizard(QtWidgets.QWizard):
logging.info("======================")
campaign = self.field("selectedCampaign")
if campaign is None:
campaign = self.theater_page.campaignList.selected_campaign
if campaign is None:
campaign = self.campaigns[0]
@@ -94,7 +96,6 @@ class NewGameWizard(QtWidgets.QWizard):
enemy_budget=int(self.field("enemy_starting_money")),
# QSlider forces integers, so we use 1 to 50 and divide by 10 to
# give 0.1 to 5.0.
midgame=False,
inverted=self.field("invertMap"),
no_carrier=self.field("no_carrier"),
no_lha=self.field("no_lha"),
@@ -300,13 +301,13 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
text="Show incompatible campaigns"
)
show_incompatible_campaigns_checkbox.setChecked(False)
campaignList = QCampaignList(
self.campaignList = QCampaignList(
campaigns, show_incompatible_campaigns_checkbox.isChecked()
)
show_incompatible_campaigns_checkbox.toggled.connect(
lambda checked: campaignList.setup_content(show_incompatible=checked)
lambda checked: self.campaignList.setup_content(show_incompatible=checked)
)
self.registerField("selectedCampaign", campaignList)
self.registerField("selectedCampaign", self.campaignList)
# Faction description
self.campaignMapDescription = QTextEdit("")
@@ -366,7 +367,7 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
template_perf = jinja_env.get_template(
"campaign_performance_template_EN.j2"
)
campaign = campaignList.selected_campaign
campaign = self.campaignList.selected_campaign
self.setField("selectedCampaign", campaign)
if campaign is None:
self.campaignMapDescription.setText("No campaign selected")
@@ -379,11 +380,13 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
template_perf.render({"performance": campaign.performance})
)
campaignList.selectionModel().setCurrentIndex(
campaignList.indexAt(QPoint(1, 1)), QItemSelectionModel.Rows
self.campaignList.selectionModel().setCurrentIndex(
self.campaignList.indexAt(QPoint(1, 1)), QItemSelectionModel.Rows
)
campaignList.selectionModel().selectionChanged.connect(on_campaign_selected)
self.campaignList.selectionModel().selectionChanged.connect(
on_campaign_selected
)
on_campaign_selected()
docsText = QtWidgets.QLabel(
@@ -410,7 +413,7 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
layout = QtWidgets.QGridLayout()
layout.setColumnMinimumWidth(0, 20)
layout.addWidget(campaignList, 0, 0, 5, 1)
layout.addWidget(self.campaignList, 0, 0, 5, 1)
layout.addWidget(show_incompatible_campaigns_checkbox, 5, 0, 1, 1)
layout.addWidget(docsText, 6, 0, 1, 1)
layout.addWidget(self.campaignMapDescription, 0, 1, 1, 1)

View File

@@ -22,7 +22,6 @@ from dcs.forcedoptions import ForcedOptions
import qt_ui.uiconstants as CONST
from game.game import Game
from game.infos.information import Information
from game.settings import Settings, AutoAtoBehavior
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
from qt_ui.widgets.spinsliders import TenthsSpinSlider, TimeInputs
@@ -894,18 +893,6 @@ class QSettingsWindow(QDialog):
def cheatMoney(self, amount):
logging.info("CHEATING FOR AMOUNT : " + str(amount) + "M")
self.game.blue.budget += amount
if amount > 0:
self.game.informations.append(
Information(
"CHEATER",
"You are a cheater and you should feel bad",
self.game.turn,
)
)
else:
self.game.informations.append(
Information("CHEATER", "You are still a cheater !", self.game.turn)
)
GameUpdateSignal.get_instance().updateGame(self.game)
def applySettings(self):