Merge 'upstream/develop' into new-plugin-system

This commit is contained in:
David Pierron
2020-10-20 22:10:08 +02:00
47 changed files with 783 additions and 415 deletions

View File

@@ -24,8 +24,8 @@ class GameUpdateSignal(QObject):
debriefingReceived = Signal(DebriefingSignal)
flight_paths_changed = Signal()
package_selection_changed = Signal(int) # Optional[int]
flight_selection_changed = Signal(int) # Optional[int]
package_selection_changed = Signal(int) # -1 indicates no selection.
flight_selection_changed = Signal(int) # -1 indicates no selection.
def __init__(self):
super(GameUpdateSignal, self).__init__()
@@ -33,11 +33,11 @@ class GameUpdateSignal(QObject):
def select_package(self, index: Optional[int]) -> None:
# noinspection PyUnresolvedReferences
self.package_selection_changed.emit(index)
self.package_selection_changed.emit(-1 if index is None else index)
def select_flight(self, index: Optional[int]) -> None:
# noinspection PyUnresolvedReferences
self.flight_selection_changed.emit(index)
self.flight_selection_changed.emit(-1 if index is None else index)
def redraw_flight_paths(self) -> None:
# noinspection PyUnresolvedReferences

View File

@@ -43,6 +43,7 @@ class QLiberationWindow(QMainWindow):
Dialog.set_game(self.game_model)
self.ato_panel = None
self.info_panel = None
self.liberation_map = None
self.setGame(persistency.restore_game())
self.setGeometry(300, 100, 270, 100)
@@ -224,9 +225,11 @@ class QLiberationWindow(QMainWindow):
if game is not None:
game.on_load()
self.game = game
if self.info_panel:
if self.info_panel is not None:
self.info_panel.setGame(game)
self.game_model.set(self.game)
if self.liberation_map is not None:
self.liberation_map.setGame(game)
def showAboutDialog(self):
text = "<h3>DCS Liberation " + CONST.VERSION_STRING + "</h3>" + \

View File

@@ -1,15 +1,16 @@
from typing import Optional
from typing import Optional, Set
from PySide2.QtCore import Qt
from PySide2.QtWidgets import (
QFrame,
QGridLayout,
QScrollArea,
QVBoxLayout,
QHBoxLayout,
QLabel,
QScrollArea,
QVBoxLayout,
QWidget,
)
from dcs.unittype import UnitType
from game.event.event import UnitsDeliveryEvent
from qt_ui.models import GameModel
@@ -48,26 +49,27 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
def init_ui(self):
main_layout = QVBoxLayout()
units = {
CAP: db.find_unittype(CAP, self.game_model.game.player_name),
CAS: db.find_unittype(CAS, self.game_model.game.player_name),
}
tasks = [CAP, CAS]
scroll_content = QWidget()
task_box_layout = QGridLayout()
row = 0
for task_type in units.keys():
units_column = list(set(units[task_type]))
if len(units_column) == 0:
unit_types: Set[UnitType] = set()
for task in tasks:
units = db.find_unittype(task, self.game_model.game.player_name)
if not units:
continue
units_column.sort(key=lambda x: db.PRICES[x])
for unit_type in units_column:
if self.cp.is_carrier and not unit_type in db.CARRIER_CAPABLE:
for unit in units:
if self.cp.is_carrier and unit not in db.CARRIER_CAPABLE:
continue
if self.cp.is_lha and not unit_type in db.LHA_CAPABLE:
if self.cp.is_lha and unit not in db.LHA_CAPABLE:
continue
row = self.add_purchase_row(unit_type, task_box_layout, row)
unit_types.add(unit)
sorted_units = sorted(unit_types, key=lambda u: db.unit_type_name_2(u))
for unit_type in sorted_units:
row = self.add_purchase_row(unit_type, task_box_layout, row)
stretch = QVBoxLayout()
stretch.addStretch()
task_box_layout.addLayout(stretch, row, 0)

View File

@@ -16,6 +16,7 @@ from game.game import Game
from gen.ato import Package
from gen.flights.flight import Flight
from gen.flights.flightplan import FlightPlanBuilder
from gen.flights.traveltime import TotEstimator
from qt_ui.models import AtoModel, PackageModel
from qt_ui.uiconstants import EVENT_ICONS
from qt_ui.widgets.ato import QFlightList
@@ -34,12 +35,6 @@ class QPackageDialog(QDialog):
#: Emitted when a change is made to the package.
package_changed = Signal()
#: Emitted when a flight is added to the package.
flight_added = Signal(Flight)
#: Emitted when a flight is removed from the package.
flight_removed = Signal(Flight)
def __init__(self, game: Game, model: PackageModel) -> None:
super().__init__()
self.game = game
@@ -77,20 +72,20 @@ class QPackageDialog(QDialog):
self.tot_label = QLabel("Time Over Target:")
self.tot_column.addWidget(self.tot_label)
if self.package_model.package.time_over_target is None:
time = None
else:
delay = self.package_model.package.time_over_target
hours = delay // 3600
minutes = delay // 60 % 60
seconds = delay % 60
time = QTime(hours, minutes, seconds)
self.tot_spinner = QTimeEdit(time)
self.tot_spinner = QTimeEdit(self.tot_qtime())
self.tot_spinner.setMinimumTime(QTime(0, 0))
self.tot_spinner.setDisplayFormat("T+hh:mm:ss")
self.tot_spinner.timeChanged.connect(self.save_tot)
self.tot_column.addWidget(self.tot_spinner)
self.reset_tot_button = QPushButton("Reset TOT")
self.reset_tot_button.setToolTip(
"Sets the package TOT to the earliest time that all flights can "
"arrive at the target."
)
self.reset_tot_button.clicked.connect(self.reset_tot)
self.tot_column.addWidget(self.reset_tot_button)
self.package_view = QFlightList(self.package_model)
self.package_view.selectionModel().selectionChanged.connect(
self.on_selection_changed
@@ -114,17 +109,40 @@ class QPackageDialog(QDialog):
self.setLayout(self.layout)
self.accepted.connect(self.on_save)
self.finished.connect(self.on_close)
self.rejected.connect(self.on_cancel)
def tot_qtime(self) -> QTime:
delay = self.package_model.package.time_over_target
hours = delay // 3600
minutes = delay // 60 % 60
seconds = delay % 60
return QTime(hours, minutes, seconds)
def on_cancel(self) -> None:
pass
@staticmethod
def on_close(_result) -> None:
GameUpdateSignal.get_instance().redraw_flight_paths()
def on_save(self) -> None:
self.save_tot()
def save_tot(self) -> None:
time = self.tot_spinner.time()
seconds = time.hour() * 3600 + time.minute() * 60 + time.second()
self.package_model.update_tot(seconds)
def reset_tot(self) -> None:
if not list(self.package_model.flights):
self.package_model.update_tot(0)
else:
self.package_model.update_tot(
TotEstimator(self.package_model.package).earliest_tot())
self.tot_spinner.setTime(self.tot_qtime())
def on_selection_changed(self, selected: QItemSelection,
_deselected: QItemSelection) -> None:
"""Updates the state of the delete button."""
@@ -139,14 +157,13 @@ class QPackageDialog(QDialog):
def add_flight(self, flight: Flight) -> None:
"""Adds the new flight to the package."""
self.game.aircraft_inventory.claim_for_flight(flight)
self.package_model.add_flight(flight)
planner = FlightPlanBuilder(self.game, self.package_model.package,
is_player=True)
planner.populate_flight_plan(flight)
# noinspection PyUnresolvedReferences
self.package_changed.emit()
# noinspection PyUnresolvedReferences
self.flight_added.emit(flight)
def on_delete_flight(self) -> None:
"""Removes the selected flight from the package."""
@@ -154,11 +171,10 @@ class QPackageDialog(QDialog):
if flight is None:
logging.error(f"Cannot delete flight when no flight is selected.")
return
self.game.aircraft_inventory.return_from_flight(flight)
self.package_model.delete_flight(flight)
# noinspection PyUnresolvedReferences
self.package_changed.emit()
# noinspection PyUnresolvedReferences
self.flight_removed.emit(flight)
class QNewPackageDialog(QPackageDialog):
@@ -174,22 +190,22 @@ class QNewPackageDialog(QPackageDialog):
self.save_button = QPushButton("Save")
self.save_button.setProperty("style", "start-button")
self.save_button.clicked.connect(self.on_save)
self.save_button.clicked.connect(self.accept)
self.button_layout.addWidget(self.save_button)
self.delete_flight_button.clicked.connect(self.on_delete_flight)
def on_save(self) -> None:
"""Saves the created package.
Empty packages may be created. They can be modified later, and will have
no effect if empty when the mission is generated.
"""
self.save_tot()
super().on_save()
self.ato_model.add_package(self.package_model.package)
def on_cancel(self) -> None:
super().on_cancel()
for flight in self.package_model.package.flights:
self.game.aircraft_inventory.claim_for_flight(flight)
self.close()
self.game_model.game.aircraft_inventory.return_from_flight(flight)
class QEditPackageDialog(QPackageDialog):
@@ -210,30 +226,9 @@ class QEditPackageDialog(QPackageDialog):
self.done_button = QPushButton("Done")
self.done_button.setProperty("style", "start-button")
self.done_button.clicked.connect(self.on_done)
self.done_button.clicked.connect(self.accept)
self.button_layout.addWidget(self.done_button)
# noinspection PyUnresolvedReferences
self.flight_added.connect(self.on_flight_added)
# noinspection PyUnresolvedReferences
self.flight_removed.connect(self.on_flight_removed)
# TODO: Make the new package dialog do this too, return on cancel.
# Not claiming the aircraft when they are added to the planner means that
# inventory counts are not updated until after the new package is updated,
# so you can add an infinite number of aircraft to a new package in the UI,
# which will crash when the flight package is saved.
def on_flight_added(self, flight: Flight) -> None:
self.game.aircraft_inventory.claim_for_flight(flight)
def on_flight_removed(self, flight: Flight) -> None:
self.game.aircraft_inventory.return_from_flight(flight)
def on_done(self) -> None:
"""Closes the window."""
self.save_tot()
self.close()
def on_delete(self) -> None:
"""Removes the viewed package from the ATO."""
# The ATO model returns inventory for us when deleting a package.

View File

@@ -19,11 +19,15 @@ class QFlightPlanner(QTabWidget):
def __init__(self, package: Package, flight: Flight, game: Game):
super().__init__()
self.general_settings_tab = QGeneralFlightSettingsTab(game, flight)
self.general_settings_tab = QGeneralFlightSettingsTab(
game, package, flight
)
# noinspection PyUnresolvedReferences
self.general_settings_tab.on_flight_settings_changed.connect(
lambda: self.on_planned_flight_changed.emit())
self.payload_tab = QFlightPayloadTab(flight, game)
self.waypoint_tab = QFlightWaypointTab(game, package, flight)
# noinspection PyUnresolvedReferences
self.waypoint_tab.on_flight_changed.connect(
lambda: self.on_planned_flight_changed.emit())
self.addTab(self.general_settings_tab, "General Flight settings")

View File

@@ -0,0 +1,32 @@
import datetime
from PySide2.QtWidgets import QLabel, QHBoxLayout, QGroupBox, QVBoxLayout
from gen.ato import Package
from gen.flights.flight import Flight
from gen.flights.traveltime import TotEstimator
# TODO: Remove?
class QFlightDepartureDisplay(QGroupBox):
def __init__(self, package: Package, flight: Flight):
super().__init__("Departure")
layout = QVBoxLayout()
departure_row = QHBoxLayout()
layout.addLayout(departure_row)
estimator = TotEstimator(package)
delay = datetime.timedelta(seconds=estimator.mission_start_time(flight))
departure_row.addWidget(QLabel(
f"Departing from <b>{flight.from_cp.name}</b>"
))
departure_row.addWidget(QLabel(f"At T+{delay}"))
layout.addWidget(QLabel("Determined based on the package TOT. Edit the "
"package to adjust the TOT."))
self.setLayout(layout)

View File

@@ -1,31 +0,0 @@
from PySide2.QtWidgets import QLabel, QHBoxLayout, QGroupBox, QSpinBox
# TODO: Remove?
class QFlightDepartureEditor(QGroupBox):
def __init__(self, flight):
super(QFlightDepartureEditor, self).__init__("Departure")
self.flight = flight
layout = QHBoxLayout()
self.depart_from = QLabel("Departing from <b>" + self.flight.from_cp.name + "</b>")
self.depart_at_t = QLabel("At T +")
self.minutes = QLabel(" minutes")
self.departure_delta = QSpinBox(self)
self.departure_delta.setMinimum(0)
self.departure_delta.setMaximum(120)
self.departure_delta.setValue(self.flight.scheduled_in // 60)
self.departure_delta.valueChanged.connect(self.change_scheduled)
layout.addWidget(self.depart_from)
layout.addWidget(self.depart_at_t)
layout.addWidget(self.departure_delta)
layout.addWidget(self.minutes)
self.setLayout(layout)
self.changed = self.departure_delta.valueChanged
def change_scheduled(self):
self.flight.scheduled_in = int(self.departure_delta.value() * 60)

View File

@@ -2,26 +2,29 @@ from PySide2.QtCore import Signal
from PySide2.QtWidgets import QFrame, QGridLayout, QVBoxLayout
from game import Game
from gen.ato import Package
from gen.flights.flight import Flight
from qt_ui.windows.mission.flight.settings.QFlightDepartureEditor import QFlightDepartureEditor
from qt_ui.windows.mission.flight.settings.QFlightSlotEditor import QFlightSlotEditor
from qt_ui.windows.mission.flight.settings.QFlightStartType import QFlightStartType
from qt_ui.windows.mission.flight.settings.QFlightTypeTaskInfo import QFlightTypeTaskInfo
from qt_ui.windows.mission.flight.settings.QFlightDepartureDisplay import \
QFlightDepartureDisplay
from qt_ui.windows.mission.flight.settings.QFlightSlotEditor import \
QFlightSlotEditor
from qt_ui.windows.mission.flight.settings.QFlightStartType import \
QFlightStartType
from qt_ui.windows.mission.flight.settings.QFlightTypeTaskInfo import \
QFlightTypeTaskInfo
class QGeneralFlightSettingsTab(QFrame):
on_flight_settings_changed = Signal()
def __init__(self, game: Game, flight: Flight):
super(QGeneralFlightSettingsTab, self).__init__()
self.flight = flight
self.game = game
def __init__(self, game: Game, package: Package, flight: Flight):
super().__init__()
layout = QGridLayout()
flight_info = QFlightTypeTaskInfo(self.flight)
flight_departure = QFlightDepartureEditor(self.flight)
flight_slots = QFlightSlotEditor(self.flight, self.game)
flight_start_type = QFlightStartType(self.flight)
flight_info = QFlightTypeTaskInfo(flight)
flight_departure = QFlightDepartureDisplay(package, flight)
flight_slots = QFlightSlotEditor(flight, game)
flight_start_type = QFlightStartType(flight)
layout.addWidget(flight_info, 0, 0)
layout.addWidget(flight_departure, 1, 0)
layout.addWidget(flight_slots, 2, 0)
@@ -31,8 +34,6 @@ class QGeneralFlightSettingsTab(QFrame):
layout.addLayout(vstretch, 3, 0)
self.setLayout(layout)
flight_start_type.setEnabled(self.flight.client_count > 0)
flight_start_type.setEnabled(flight.client_count > 0)
flight_slots.changed.connect(
lambda: flight_start_type.setEnabled(self.flight.client_count > 0))
flight_departure.changed.connect(
lambda: self.on_flight_settings_changed.emit())
lambda: flight_start_type.setEnabled(flight.client_count > 0))

View File

@@ -54,6 +54,7 @@ class QFlightWaypointTab(QFrame):
rlayout.addWidget(QLabel("<strong>Generator :</strong>"))
rlayout.addWidget(QLabel("<small>AI compatible</small>"))
# TODO: Filter by objective type.
self.recreate_buttons.clear()
recreate_types = [
FlightType.CAS,
@@ -137,13 +138,16 @@ class QFlightWaypointTab(QFrame):
QMessageBox.Yes
)
if result == QMessageBox.Yes:
# TODO: These should all be just CAP.
# TODO: Should be buttons for both BARCAP and TARCAP.
# BARCAP and TARCAP behave differently. TARCAP arrives a few minutes
# ahead of the rest of the package and stays until the package
# departs, whereas BARCAP usually isn't part of a strike package and
# has a fixed mission time.
if task == FlightType.CAP:
if isinstance(self.package.target, FrontLine):
task = FlightType.TARCAP
elif isinstance(self.package.target, ControlPoint):
if self.package.target.is_fleet:
task = FlightType.BARCAP
task = FlightType.BARCAP
self.flight.flight_type = task
self.planner.populate_flight_plan(self.flight)
self.flight_waypoint_list.update_list()

View File

@@ -1,18 +1,50 @@
import logging
from typing import Callable
from PySide2.QtCore import QSize, Qt, QItemSelectionModel, QPoint
from PySide2.QtGui import QStandardItemModel, QStandardItem
from PySide2.QtWidgets import QLabel, QDialog, QGridLayout, QListView, QStackedLayout, QComboBox, QWidget, \
QAbstractItemView, QPushButton, QGroupBox, QCheckBox, QVBoxLayout, QSpinBox
from PySide2.QtWidgets import (
QLabel,
QDialog,
QGridLayout,
QListView,
QStackedLayout,
QComboBox,
QWidget,
QAbstractItemView,
QPushButton,
QGroupBox,
QCheckBox,
QVBoxLayout,
QSpinBox,
)
from dcs.forcedoptions import ForcedOptions
import qt_ui.uiconstants as CONST
from game.game import Game
from game.infos.information import Information
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.finances.QFinancesMenu import QHorizontalSeparationLine
from plugin import LuaPluginManager
class CheatSettingsBox(QGroupBox):
def __init__(self, game: Game, apply_settings: Callable[[], None]) -> None:
super().__init__("Cheat Settings")
self.main_layout = QVBoxLayout()
self.setLayout(self.main_layout)
self.red_ato_checkbox = QCheckBox()
self.red_ato_checkbox.setChecked(game.settings.show_red_ato)
self.red_ato_checkbox.toggled.connect(apply_settings)
self.red_ato = QLabeledWidget("Show Red ATO:", self.red_ato_checkbox)
self.main_layout.addLayout(self.red_ato)
@property
def show_red_ato(self) -> bool:
return self.red_ato_checkbox.isChecked()
class QSettingsWindow(QDialog):
def __init__(self, game: Game):
@@ -262,9 +294,12 @@ class QSettingsWindow(QDialog):
def initCheatLayout(self):
self.cheatPage = QWidget()
self.cheatLayout = QGridLayout()
self.cheatLayout = QVBoxLayout()
self.cheatPage.setLayout(self.cheatLayout)
self.cheat_options = CheatSettingsBox(self.game, self.applySettings)
self.cheatLayout.addWidget(self.cheat_options)
self.moneyCheatBox = QGroupBox("Money Cheat")
self.moneyCheatBox.setAlignment(Qt.AlignTop)
self.moneyCheatBoxLayout = QGridLayout()
@@ -280,7 +315,7 @@ class QSettingsWindow(QDialog):
btn.setProperty("style", "btn-danger")
btn.clicked.connect(self.cheatLambda(amount))
self.moneyCheatBoxLayout.addWidget(btn, i/2, i%2)
self.cheatLayout.addWidget(self.moneyCheatBox, 0, 0)
self.cheatLayout.addWidget(self.moneyCheatBox, stretch=1)
def initPluginsLayout(self):
uiPrepared = False
@@ -332,8 +367,6 @@ class QSettingsWindow(QDialog):
self.game.settings.external_views_allowed = self.ext_views.isChecked()
self.game.settings.generate_marks = self.generate_marks.isChecked()
print(self.game.settings.map_coalition_visibility)
self.game.settings.supercarrier = self.supercarrier.isChecked()
self.game.settings.perf_red_alert_state = self.red_alert.isChecked()
@@ -347,6 +380,8 @@ class QSettingsWindow(QDialog):
self.game.settings.perf_culling = self.culling.isChecked()
self.game.settings.perf_culling_distance = int(self.culling_distance.value())
self.game.settings.show_red_ato = self.cheat_options.show_red_ato
GameUpdateSignal.get_instance().updateGame(self.game)
def onSelectionChanged(self):