Merge branch 'develop' into helipads

# Conflicts:
#	game/game.py
#	game/operation/operation.py
#	game/theater/conflicttheater.py
#	game/theater/controlpoint.py
#	gen/groundobjectsgen.py
#	resources/campaigns/golan_heights_lite.miz
This commit is contained in:
Khopa
2021-08-02 19:34:05 +02:00
408 changed files with 9630 additions and 5172 deletions

View File

@@ -0,0 +1,326 @@
from typing import Optional, Callable
from PySide2.QtCore import (
QItemSelectionModel,
QModelIndex,
QSize,
Qt,
QItemSelection,
Signal,
)
from PySide2.QtGui import QStandardItemModel, QStandardItem, QIcon
from PySide2.QtWidgets import (
QAbstractItemView,
QDialog,
QListView,
QVBoxLayout,
QGroupBox,
QLabel,
QWidget,
QScrollArea,
QLineEdit,
QTextEdit,
QCheckBox,
QHBoxLayout,
QStackedLayout,
QTabWidget,
)
from game import Game
from game.dcs.aircrafttype import AircraftType
from game.squadrons import Squadron, AirWing, Pilot
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):
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)
if squadron.player:
player_label = QLabel(
"Players (one per line, leave empty for an AI-only squadron):"
)
else:
player_label = QLabel("Player slots not available for opfor")
left_column.addWidget(player_label)
players = [p for p in squadron.pilot_pool if p.player]
for player in players:
squadron.pilot_pool.remove(player)
if not squadron.player:
players = []
self.player_list = QTextEdit("<br />".join(p.name for p in players))
self.player_list.setAcceptRichText(False)
self.player_list.setEnabled(squadron.player)
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 SquadronConfigurationLayout(QVBoxLayout):
def __init__(self, squadrons: list[Squadron]) -> None:
super().__init__()
self.squadron_configs = []
for squadron in squadrons:
squadron_config = SquadronConfigurationBox(squadron)
self.squadron_configs.append(squadron_config)
self.addWidget(squadron_config)
def apply(self) -> list[Squadron]:
keep_squadrons = []
for squadron_config in self.squadron_configs:
if squadron_config.isChecked():
keep_squadrons.append(squadron_config.apply())
return keep_squadrons
class AircraftSquadronsPage(QWidget):
def __init__(self, squadrons: list[Squadron]) -> None:
super().__init__()
layout = QVBoxLayout()
self.setLayout(layout)
self.squadrons_config = SquadronConfigurationLayout(squadrons)
scrolling_widget = QWidget()
scrolling_widget.setLayout(self.squadrons_config)
scrolling_area = QScrollArea()
scrolling_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scrolling_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scrolling_area.setWidgetResizable(True)
scrolling_area.setWidget(scrolling_widget)
layout.addWidget(scrolling_area)
def apply(self) -> list[Squadron]:
return self.squadrons_config.apply()
class AircraftSquadronsPanel(QStackedLayout):
def __init__(self, air_wing: AirWing) -> None:
super().__init__()
self.air_wing = air_wing
self.squadrons_pages: dict[AircraftType, AircraftSquadronsPage] = {}
for aircraft, squadrons in self.air_wing.squadrons.items():
page = AircraftSquadronsPage(squadrons)
self.addWidget(page)
self.squadrons_pages[aircraft] = page
def apply(self) -> None:
for aircraft, page in self.squadrons_pages.items():
self.air_wing.squadrons[aircraft] = page.apply()
class AircraftTypeList(QListView):
page_index_changed = Signal(int)
def __init__(self, air_wing: AirWing) -> None:
super().__init__()
self.setIconSize(QSize(91, 24))
self.setMinimumWidth(300)
model = QStandardItemModel(self)
self.setModel(model)
self.selectionModel().setCurrentIndex(
model.index(0, 0), QItemSelectionModel.Select
)
self.selectionModel().selectionChanged.connect(self.on_selection_changed)
for aircraft in 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)
model.appendRow(aircraft_item)
def on_selection_changed(
self, selected: QItemSelection, _deselected: QItemSelection
) -> None:
indexes = selected.indexes()
if len(indexes) > 1:
raise RuntimeError("Aircraft list should not allow multi-selection")
if not indexes:
return
self.page_index_changed.emit(indexes[0].row())
@staticmethod
def icon_for(aircraft: AircraftType) -> Optional[QIcon]:
name = aircraft.dcs_id
if name in AIRCRAFT_ICONS:
return QIcon(AIRCRAFT_ICONS[name])
return None
class AirWingConfigurationTab(QWidget):
def __init__(self, air_wing: AirWing) -> None:
super().__init__()
layout = QHBoxLayout()
self.setLayout(layout)
type_list = AircraftTypeList(air_wing)
type_list.page_index_changed.connect(self.on_aircraft_changed)
layout.addWidget(type_list)
self.squadrons_panel = AircraftSquadronsPanel(air_wing)
layout.addLayout(self.squadrons_panel)
def apply(self) -> None:
self.squadrons_panel.apply()
def on_aircraft_changed(self, index: QModelIndex) -> None:
self.squadrons_panel.setCurrentIndex(index)
class AirWingConfigurationDialog(QDialog):
"""Dialog window for air wing configuration."""
def __init__(self, game: Game, parent) -> None:
super().__init__(parent)
self.setMinimumSize(500, 800)
self.setWindowTitle(f"Air Wing Configuration")
# TODO: self.setWindowIcon()
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>.'
)
doc_label.setOpenExternalLinks(True)
layout.addWidget(doc_label)
tab_widget = QTabWidget()
layout.addWidget(tab_widget)
self.tabs = []
for coalition in game.coalitions:
coalition_tab = AirWingConfigurationTab(coalition.air_wing)
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()

View File

@@ -3,12 +3,7 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import Optional, Iterator
from PySide2.QtCore import (
QItemSelectionModel,
QModelIndex,
Qt,
QSize,
)
from PySide2.QtCore import QItemSelectionModel, QModelIndex, QSize
from PySide2.QtWidgets import (
QAbstractItemView,
QCheckBox,
@@ -183,7 +178,7 @@ class AirInventoryView(QWidget):
self.table.setSortingEnabled(True)
def iter_allocated_aircraft(self) -> Iterator[AircraftInventoryData]:
for package in self.game_model.game.blue_ato.packages:
for package in self.game_model.game.blue.ato.packages:
for flight in package.flights:
yield from AircraftInventoryData.from_flight(flight)

View File

@@ -36,6 +36,8 @@ from qt_ui.windows.preferences.QLiberationPreferencesWindow import (
)
from qt_ui.windows.settings.QSettingsWindow import QSettingsWindow
from qt_ui.windows.stats.QStatsWindow import QStatsWindow
from qt_ui.windows.notes.QNotesWindow import QNotesWindow
from qt_ui.windows.logs.QLogsWindow import QLogsWindow
class QLiberationWindow(QMainWindow):
@@ -150,6 +152,9 @@ class QLiberationWindow(QMainWindow):
)
)
self.openLogsAction = QAction("Show &logs", self)
self.openLogsAction.triggered.connect(self.showLogsDialog)
self.openSettingsAction = QAction("Settings", self)
self.openSettingsAction.setIcon(CONST.ICONS["Settings"])
self.openSettingsAction.triggered.connect(self.showSettingsDialog)
@@ -158,6 +163,10 @@ class QLiberationWindow(QMainWindow):
self.openStatsAction.setIcon(CONST.ICONS["Statistics"])
self.openStatsAction.triggered.connect(self.showStatsDialog)
self.openNotesAction = QAction("Notes", self)
self.openNotesAction.setIcon(CONST.ICONS["Notes"])
self.openNotesAction.triggered.connect(self.showNotesDialog)
def initToolbar(self):
self.tool_bar = self.addToolBar("File")
self.tool_bar.addAction(self.newGameAction)
@@ -171,6 +180,7 @@ class QLiberationWindow(QMainWindow):
self.actions_bar = self.addToolBar("Actions")
self.actions_bar.addAction(self.openSettingsAction)
self.actions_bar.addAction(self.openStatsAction)
self.actions_bar.addAction(self.openNotesAction)
def initMenuBar(self):
self.menu = self.menuBar()
@@ -204,6 +214,7 @@ class QLiberationWindow(QMainWindow):
help_menu.addAction(
"Report an &issue", lambda: webbrowser.open_new_tab(URLS["Issues"])
)
help_menu.addAction(self.openLogsAction)
help_menu.addSeparator()
help_menu.addAction(self.showAboutDialogAction)
@@ -351,6 +362,14 @@ class QLiberationWindow(QMainWindow):
self.dialog = QStatsWindow(self.game)
self.dialog.show()
def showNotesDialog(self):
self.dialog = QNotesWindow(self.game)
self.dialog.show()
def showLogsDialog(self):
self.dialog = QLogsWindow()
self.dialog.show()
def onDebriefing(self, debrief: Debriefing):
logging.info("On Debriefing")
self.debriefing = QDebriefingWindow(debrief)

View File

@@ -94,6 +94,9 @@ class QUnitInfoWindow(QDialog):
self.details_text = QTextBrowser()
self.details_text.setProperty("style", "info-desc")
self.details_text.setText(unit_type.description)
self.details_text.setOpenExternalLinks(
True
) # in aircrafttype.py and groundunittype.py, for the descriptions, if No Data. including a google search link
self.gridLayout.addWidget(self.details_text, 3, 0)
self.layout.addLayout(self.gridLayout, 1, 0)

View File

@@ -73,11 +73,15 @@ class DepartingConvoysList(QFrame):
task_box_layout = QGridLayout()
scroll_content.setLayout(task_box_layout)
for convoy in game_model.game.transfers.convoys.departing_from(cp):
for convoy in game_model.game.coalition_for(
cp.captured
).transfers.convoys.departing_from(cp):
group_info = DepartingConvoyInfo(convoy)
task_box_layout.addWidget(group_info)
for cargo_ship in game_model.game.transfers.cargo_ships.departing_from(cp):
for cargo_ship in game_model.game.coalition_for(
cp.captured
).transfers.cargo_ships.departing_from(cp):
group_info = DepartingConvoyInfo(cargo_ship)
task_box_layout.addWidget(group_info)

View File

@@ -108,7 +108,7 @@ class QBaseMenu2(QDialog):
capture_button.clicked.connect(self.cheat_capture)
self.budget_display = QLabel(
QRecruitBehaviour.BUDGET_FORMAT.format(self.game_model.game.budget)
QRecruitBehaviour.BUDGET_FORMAT.format(self.game_model.game.blue.budget)
)
self.budget_display.setAlignment(Qt.AlignRight | Qt.AlignBottom)
self.budget_display.setProperty("style", "budget-label")
@@ -124,7 +124,6 @@ class QBaseMenu2(QDialog):
self.cp.capture(self.game_model.game, for_player=not self.cp.captured)
# Reinitialized ground planners and the like. The ATO needs to be reset because
# missions planned against the flipped base are no longer valid.
self.game_model.game.reset_ato()
self.game_model.game.initialize_turn()
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
@@ -140,7 +139,7 @@ class QBaseMenu2(QDialog):
@property
def can_afford_runway_repair(self) -> bool:
return self.game_model.game.budget >= db.RUNWAY_REPAIR_COST
return self.game_model.game.blue.budget >= db.RUNWAY_REPAIR_COST
def begin_runway_repair(self) -> None:
if not self.can_afford_runway_repair:
@@ -148,7 +147,7 @@ class QBaseMenu2(QDialog):
self,
"Cannot repair runway",
f"Runway repair costs ${db.RUNWAY_REPAIR_COST}M but you have "
f"only ${self.game_model.game.budget}M available.",
f"only ${self.game_model.game.blue.budget}M available.",
QMessageBox.Ok,
)
return
@@ -162,7 +161,7 @@ class QBaseMenu2(QDialog):
return
self.cp.begin_runway_repair()
self.game_model.game.budget -= db.RUNWAY_REPAIR_COST
self.game_model.game.blue.budget -= db.RUNWAY_REPAIR_COST
self.update_repair_button()
self.update_intel_summary()
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
@@ -196,7 +195,9 @@ class QBaseMenu2(QDialog):
ground_unit_limit = self.cp.frontline_unit_count_limit
deployable_unit_info = ""
allocated = self.cp.allocated_ground_units(self.game_model.game.transfers)
allocated = self.cp.allocated_ground_units(
self.game_model.game.coalition_for(self.cp.captured).transfers
)
unit_overage = max(
allocated.total_present - self.cp.frontline_unit_count_limit, 0
)
@@ -256,4 +257,6 @@ class QBaseMenu2(QDialog):
NewUnitTransferDialog(self.game_model, self.cp, parent=self.window()).show()
def update_budget(self, game: Game) -> None:
self.budget_display.setText(QRecruitBehaviour.BUDGET_FORMAT.format(game.budget))
self.budget_display.setText(
QRecruitBehaviour.BUDGET_FORMAT.format(game.blue.budget)
)

View File

@@ -103,11 +103,11 @@ class QRecruitBehaviour:
@property
def budget(self) -> float:
return self.game_model.game.budget
return self.game_model.game.blue.budget
@budget.setter
def budget(self, value: int) -> None:
self.game_model.game.budget = value
self.game_model.game.blue.budget = value
def add_purchase_row(
self,
@@ -209,8 +209,6 @@ class QRecruitBehaviour:
if self.pending_deliveries.available_next_turn(unit_type) > 0:
self.budget += unit_type.price
self.pending_deliveries.sell({unit_type: 1})
if self.pending_deliveries.units[unit_type] == 0:
del self.pending_deliveries.units[unit_type]
self.update_purchase_controls()
self.update_available_budget()
return True

View File

@@ -45,7 +45,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
row = 0
unit_types: Set[AircraftType] = set()
for unit_type in self.game_model.game.player_faction.aircrafts:
for unit_type in self.game_model.game.blue.faction.aircrafts:
if self.cp.is_carrier and not unit_type.carrier_capable:
continue
if self.cp.is_lha and not unit_type.lha_capable:

View File

@@ -56,6 +56,5 @@ class QGroundForcesStrategy(QGroupBox):
self.cp.base.affect_strength(amount)
enemy_point.base.affect_strength(-amount)
# Clear the ATO to replan missions affected by the front line.
self.game.reset_ato()
self.game.initialize_turn()
GameUpdateSignal.get_instance().updateGame(self.game)

View File

@@ -57,10 +57,7 @@ class FinancesLayout(QGridLayout):
middle=f"Income multiplier: {income.multiplier:.1f}",
right=f"<b>{income.total}M</b>",
)
if player:
budget = game.budget
else:
budget = game.enemy_budget
budget = game.coalition_for(player).budget
self.add_row(middle="Balance", right=f"<b>{budget}M</b>")
self.setRowStretch(next(self.row), 1)

View File

@@ -1,7 +1,6 @@
import logging
from typing import List, Optional
from PySide2 import QtCore
from PySide2.QtGui import Qt
from PySide2.QtWidgets import (
QComboBox,
@@ -238,8 +237,8 @@ class QGroundObjectMenu(QDialog):
self.total_value = total_value
def repair_unit(self, group, unit, price):
if self.game.budget > price:
self.game.budget -= price
if self.game.blue.budget > price:
self.game.blue.budget -= price
group.units_losts = [u for u in group.units_losts if u.id != unit.id]
group.units.append(unit)
GameUpdateSignal.get_instance().updateGame(self.game)
@@ -257,8 +256,16 @@ class QGroundObjectMenu(QDialog):
def sell_all(self):
self.update_total_value()
self.game.budget = self.game.budget + self.total_value
self.game.blue.budget = self.game.blue.budget + self.total_value
self.ground_object.groups = []
# Replan if the tgo was a target of the redfor
if any(
package.target == self.ground_object
for package in self.game.ato_for(player=False).packages
):
self.game.initialize_turn(for_red=True, for_blue=False)
self.do_refresh_layout()
GameUpdateSignal.get_instance().updateGame(self.game)
@@ -299,14 +306,17 @@ class QBuyGroupForGroundObjectDialog(QDialog):
self.buySamBox = QGroupBox("Buy SAM site :")
self.buyArmorBox = QGroupBox("Buy defensive position :")
faction = self.game.player_faction
faction = self.game.blue.faction
# Sams
possible_sams = get_faction_possible_sams_generator(faction)
for sam in possible_sams:
# Pre Generate SAM to get the real price
generator = sam(self.game, self.ground_object)
generator.generate()
self.samCombo.addItem(
sam.name + " [$" + str(sam.price) + "M]", userData=sam
generator.name + " [$" + str(generator.price) + "M]", userData=generator
)
self.samCombo.currentIndexChanged.connect(self.samComboChanged)
@@ -331,8 +341,12 @@ class QBuyGroupForGroundObjectDialog(QDialog):
buy_ewr_layout.addWidget(self.ewr_selector, 0, 1, alignment=Qt.AlignRight)
ewr_types = get_faction_possible_ewrs_generator(faction)
for ewr_type in ewr_types:
# Pre Generate to get the real price
generator = ewr_type(self.game, self.ground_object)
generator.generate()
self.ewr_selector.addItem(
f"{ewr_type.name()} [${ewr_type.price()}M]", ewr_type
generator.name() + " [$" + str(generator.price) + "M]",
userData=generator,
)
self.ewr_selector.currentIndexChanged.connect(self.on_ewr_selection_changed)
@@ -402,7 +416,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
def on_ewr_selection_changed(self, index):
ewr = self.ewr_selector.itemData(index)
self.buy_ewr_button.setText(
f"Buy [${ewr.price()}M][-${self.current_group_value}M]"
f"Buy [${ewr.price}M][-${self.current_group_value}M]"
)
def armorComboChanged(self, index):
@@ -419,12 +433,12 @@ class QBuyGroupForGroundObjectDialog(QDialog):
logging.info("Buying Armor ")
utype = self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
price = utype.price * self.amount.value() - self.current_group_value
if price > self.game.budget:
if price > self.game.blue.budget:
self.error_money()
self.close()
return
else:
self.game.budget -= price
self.game.blue.budget -= price
# Generate Armor
group = generate_armor_group_of_type_and_size(
@@ -432,36 +446,40 @@ class QBuyGroupForGroundObjectDialog(QDialog):
)
self.ground_object.groups = [group]
# Replan redfor missions
self.game.initialize_turn(for_red=True, for_blue=False)
GameUpdateSignal.get_instance().updateGame(self.game)
def buySam(self):
sam_generator = self.samCombo.itemData(self.samCombo.currentIndex())
price = sam_generator.price - self.current_group_value
if price > self.game.budget:
if price > self.game.blue.budget:
self.error_money()
return
else:
self.game.budget -= price
self.game.blue.budget -= price
# Generate SAM
generator = sam_generator(self.game, self.ground_object)
generator.generate()
self.ground_object.groups = list(generator.groups)
self.ground_object.groups = list(sam_generator.groups)
# Replan redfor missions
self.game.initialize_turn(for_red=True, for_blue=False)
GameUpdateSignal.get_instance().updateGame(self.game)
def buy_ewr(self):
ewr_generator = self.ewr_selector.itemData(self.ewr_selector.currentIndex())
price = ewr_generator.price() - self.current_group_value
if price > self.game.budget:
price = ewr_generator.price - self.current_group_value
if price > self.game.blue.budget:
self.error_money()
return
else:
self.game.budget -= price
self.game.blue.budget -= price
generator = ewr_generator(self.game, self.ground_object)
generator.generate()
self.ground_object.groups = [generator.vg]
self.ground_object.groups = [ewr_generator.vg]
# Replan redfor missions
self.game.initialize_turn(for_red=True, for_blue=False)
GameUpdateSignal.get_instance().updateGame(self.game)

View File

@@ -0,0 +1,67 @@
import logging
import typing
from PySide2.QtWidgets import (
QDialog,
QPlainTextEdit,
QVBoxLayout,
QPushButton,
)
from PySide2.QtGui import QTextCursor
from qt_ui.logging_handler import HookableInMemoryHandler
class QLogsWindow(QDialog):
vbox: QVBoxLayout
textbox: QPlainTextEdit
clear_button: QPushButton
_logging_handler: typing.Optional[HookableInMemoryHandler]
def __init__(self):
super().__init__()
self.setWindowTitle("Logs")
self.setMinimumSize(400, 100)
self.resize(1000, 450)
self.vbox = QVBoxLayout()
self.setLayout(self.vbox)
self.textbox = QPlainTextEdit(self)
self.textbox.setReadOnly(True)
self.textbox.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
self.textbox.move(10, 10)
self.textbox.resize(1000, 450)
self.textbox.setStyleSheet(
"font-family: 'Courier New', monospace; background: #1D2731;"
)
self.vbox.addWidget(self.textbox)
self.clear_button = QPushButton(self)
self.clear_button.setText("CLEAR")
self.clear_button.setProperty("style", "btn-primary")
self.clear_button.clicked.connect(self.clearLogs)
self.vbox.addWidget(self.clear_button)
self._logging_handler = None
logger = logging.getLogger()
for handler in logger.handlers:
if isinstance(handler, HookableInMemoryHandler):
self._logging_handler = handler
break
if self._logging_handler is not None:
self.textbox.setPlainText(self._logging_handler.log)
self.textbox.moveCursor(QTextCursor.End)
self._logging_handler.setHook(self.appendLog)
else:
self.textbox.setPlainText("WARNING: logging not initialized!")
def clearLogs(self) -> None:
if self._logging_handler is not None:
self._logging_handler.clearLog()
self.textbox.setPlainText("")
def appendLog(self, msg: str):
self.textbox.appendPlainText(msg)
self.textbox.moveCursor(QTextCursor.End)

View File

@@ -180,7 +180,7 @@ class QPackageDialog(QDialog):
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
self.package_model.package, self.game.blue, self.game.theater
)
try:
planner.populate_flight_plan(flight)

View File

@@ -38,7 +38,7 @@ class QFlightCreator(QDialog):
self.game = game
self.package = package
self.custom_name_text = None
self.country = self.game.player_country
self.country = self.game.blue.country_name
self.setWindowTitle("Create flight")
self.setWindowIcon(EVENT_ICONS["strike"])
@@ -52,7 +52,6 @@ class QFlightCreator(QDialog):
self.aircraft_selector = QAircraftTypeSelector(
self.game.aircraft_inventory.available_types_for_player,
self.game.player_country,
self.task_selector.currentData(),
)
self.aircraft_selector.setCurrentIndex(0)

View File

@@ -47,8 +47,20 @@ class QFlightPayloadTab(QFrame):
def reload_from_flight(self) -> None:
self.loadout_selector.setCurrentText(self.flight.loadout.name)
def loadout_at(self, index: int) -> Loadout:
loadout = self.loadout_selector.itemData(index)
if loadout is None:
return Loadout.empty_loadout()
return loadout
def current_loadout(self) -> Loadout:
loadout = self.loadout_selector.currentData()
if loadout is None:
return Loadout.empty_loadout()
return loadout
def on_new_loadout(self, index: int) -> None:
self.flight.loadout = self.loadout_selector.itemData(index)
self.flight.loadout = self.loadout_at(index)
self.payload_editor.reset_pylons()
def on_custom_toggled(self, use_custom: bool) -> None:
@@ -56,5 +68,5 @@ class QFlightPayloadTab(QFrame):
if use_custom:
self.flight.loadout = self.flight.loadout.derive_custom("Custom")
else:
self.flight.loadout = self.loadout_selector.currentData()
self.flight.loadout = self.current_loadout()
self.payload_editor.reset_pylons()

View File

@@ -56,7 +56,7 @@ class QPylonEditor(QComboBox):
#
# A similar hack exists in Pylon to support forcibly equipping this even when
# it's not known to be compatible.
if weapon.cls_id == "<CLEAN>":
if weapon.clsid == "<CLEAN>":
if not self.has_added_clean_item:
self.addItem("Clean", weapon)
self.has_added_clean_item = True

View File

@@ -100,6 +100,6 @@ class FlightAirfieldDisplay(QGroupBox):
def update_flight_plan(self) -> None:
planner = FlightPlanBuilder(
self.game, self.package_model.package, is_player=True
self.package_model.package, self.game.blue, self.game.theater
)
planner.populate_flight_plan(self.flight)

View File

@@ -37,7 +37,7 @@ class QFlightWaypointTab(QFrame):
self.game = game
self.package = package
self.flight = flight
self.planner = FlightPlanBuilder(self.game, package, is_player=True)
self.planner = FlightPlanBuilder(package, game.blue, game.theater)
self.flight_waypoint_list: Optional[QFlightWaypointList] = None
self.rtb_waypoint: Optional[QPushButton] = None

View File

@@ -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()

View File

@@ -0,0 +1,67 @@
from PySide2.QtWidgets import (
QDialog,
QPlainTextEdit,
QVBoxLayout,
QHBoxLayout,
QPushButton,
QLabel,
)
from PySide2.QtGui import QTextCursor
from PySide2.QtCore import QTimer
import qt_ui.uiconstants as CONST
from game.game import Game
from time import sleep
class QNotesWindow(QDialog):
def __init__(self, game: Game):
super(QNotesWindow, self).__init__()
self.game = game
self.setWindowTitle("Notes")
self.setWindowIcon(CONST.ICONS["Notes"])
self.setMinimumSize(400, 100)
self.resize(600, 450)
self.vbox = QVBoxLayout()
self.setLayout(self.vbox)
self.vbox.addWidget(
QLabel("Saved notes are available as a page in your kneeboard.")
)
self.textbox = QPlainTextEdit(self)
try:
self.textbox.setPlainText(self.game.notes)
self.textbox.moveCursor(QTextCursor.End)
except AttributeError: # old save may not have game.notes
pass
self.textbox.move(10, 10)
self.textbox.resize(600, 450)
self.textbox.setStyleSheet("background: #1D2731;")
self.vbox.addWidget(self.textbox)
self.button_row = QHBoxLayout()
self.vbox.addLayout(self.button_row)
self.clear_button = QPushButton(self)
self.clear_button.setText("CLEAR")
self.clear_button.setProperty("style", "btn-primary")
self.clear_button.clicked.connect(self.clearNotes)
self.button_row.addWidget(self.clear_button)
self.save_button = QPushButton(self)
self.save_button.setText("SAVE")
self.save_button.setProperty("style", "btn-success")
self.save_button.clicked.connect(self.saveNotes)
self.button_row.addWidget(self.save_button)
def clearNotes(self) -> None:
self.textbox.setPlainText("")
def saveNotes(self) -> None:
self.game.notes = self.textbox.toPlainText()
self.save_button.setText("SAVED")
QTimer.singleShot(5000, lambda: self.save_button.setText("SAVE"))

View File

@@ -58,6 +58,12 @@ class QLiberationFirstStartWindow(QDialog):
<p>As you click on the button below, the file will be replaced in your DCS installation directory.</p>
<br/>
<p>If you leave the DCS Installation Directory empty, DCS Liberation can not automatically replace the MissionScripting.lua and will therefore not work correctly!
In this case, you need to edit the file yourself. The easiest way to do it is to replace the original file with the file in dcs-liberation distribution (&lt;dcs_liberation_installation&gt;/resources/scripts/MissionScripting.lua).
<br/><br/>You can find more information on how to manually change this file in the Liberation Wiki (Page: Dedicated Server Guide) on GitHub.</p>
<br/><br/>
<strong>Thank you for reading !</strong>

View File

@@ -22,6 +22,7 @@ class QLiberationPreferences(QFrame):
super(QLiberationPreferences, self).__init__()
self.saved_game_dir = ""
self.dcs_install_dir = ""
self.install_dir_ignore_warning = False
self.dcs_install_dir = liberation_install.get_dcs_install_directory()
self.saved_game_dir = liberation_install.get_saved_game_dir()
@@ -102,17 +103,38 @@ class QLiberationPreferences(QFrame):
error_dialog.exec_()
return False
if not os.path.isdir(self.dcs_install_dir):
if self.install_dir_ignore_warning and self.dcs_install_dir == "":
warning_dialog = QMessageBox.warning(
self,
"The DCS Installation directory was not set",
"You set an empty DCS Installation directory! "
"<br/><br/>Without this directory, DCS Liberation can not replace the MissionScripting.lua for you and will not work properly. "
"In this case, you need to edit the MissionScripting.lua yourself. The easiest way to do it is to replace the original file (&lt;dcs_installation_directory&gt;/Scripts/MissionScripting.lua) with the file in dcs-liberation distribution (&lt;dcs_liberation_installation&gt;/resources/scripts/MissionScripting.lua)."
"<br/><br/>You can find more information on how to manually change this file in the Liberation Wiki (Page: Dedicated Server Guide) on GitHub.</p>"
"<br/><br/>Are you sure that you want to leave the installation directory empty?"
"<br/><br/><strong>This is only recommended for expert users!</strong>",
QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No,
)
if warning_dialog == QMessageBox.No:
return False
elif not os.path.isdir(self.dcs_install_dir):
error_dialog = QMessageBox.critical(
self,
"Wrong DCS installation directory.",
self.dcs_install_dir + " is not a valid directory",
self.dcs_install_dir
+ " is not a valid directory. DCS Liberation requires the installation directory to replace the MissionScripting.lua"
"<br/><br/>If you ignore this Error, DCS Liberation can not work properly and needs your attention. "
"In this case, you need to edit the MissionScripting.lua yourself. The easiest way to do it is to replace the original file (&lt;dcs_installation_directory&gt;/Scripts/MissionScripting.lua) with the file in dcs-liberation distribution (&lt;dcs_liberation_installation&gt;/resources/scripts/MissionScripting.lua)."
"<br/><br/>You can find more information on how to manually change this file in the Liberation Wiki (Page: Dedicated Server Guide) on GitHub.</p>"
"<br/><br/><strong>This is only recommended for expert users!</strong>",
QMessageBox.StandardButton.Ignore,
QMessageBox.StandardButton.Ok,
)
error_dialog.exec_()
if error_dialog == QMessageBox.Ignore:
self.install_dir_ignore_warning = True
return False
if not os.path.isdir(
elif not os.path.isdir(
os.path.join(self.dcs_install_dir, "Scripts")
) and os.path.isfile(os.path.join(self.dcs_install_dir, "bin", "DCS.exe")):
error_dialog = QMessageBox.critical(

View File

@@ -101,7 +101,7 @@ class HqAutomationSettingsBox(QGroupBox):
front_line = QCheckBox()
front_line.setChecked(self.game.settings.automate_front_line_reinforcements)
front_line.toggled.connect(self.set_front_line_automation)
front_line.toggled.connect(self.set_front_line_reinforcement_automation)
layout.addWidget(QLabel("Automate front-line purchases"), 1, 0)
layout.addWidget(front_line, 1, 1, Qt.AlignRight)
@@ -147,12 +147,30 @@ class HqAutomationSettingsBox(QGroupBox):
)
layout.addWidget(self.auto_ato_player_missions_asap, 4, 1, Qt.AlignRight)
self.automate_front_line_stance = QCheckBox()
self.automate_front_line_stance.setChecked(
self.game.settings.automate_front_line_stance
)
self.automate_front_line_stance.toggled.connect(
self.set_front_line_stance_automation
)
layout.addWidget(
QLabel("Automatically manage front line stances"),
5,
0,
)
layout.addWidget(self.automate_front_line_stance, 5, 1, Qt.AlignRight)
def set_runway_automation(self, value: bool) -> None:
self.game.settings.automate_runway_repair = value
def set_front_line_automation(self, value: bool) -> None:
def set_front_line_reinforcement_automation(self, value: bool) -> None:
self.game.settings.automate_front_line_reinforcements = value
def set_front_line_stance_automation(self, value: bool) -> None:
self.game.settings.automate_front_line_stance = value
def set_aircraft_automation(self, value: bool) -> None:
self.game.settings.automate_aircraft_reinforcements = value
@@ -855,7 +873,7 @@ class QSettingsWindow(QDialog):
def cheatMoney(self, amount):
logging.info("CHEATING FOR AMOUNT : " + str(amount) + "M")
self.game.budget += amount
self.game.blue.budget += amount
if amount > 0:
self.game.informations.append(
Information(

View File

@@ -42,10 +42,16 @@ class QAircraftChart(QFrame):
self.chart.setTitle("Aircraft forces over time")
self.chart.createDefaultAxes()
self.chart.axisX().setTitleText("Turn")
self.chart.axisX().setLabelFormat("%i")
self.chart.axisX().setRange(0, len(self.alliedAircraft))
self.chart.axisX().applyNiceNumbers()
self.chart.axisY().setLabelFormat("%i")
self.chart.axisY().setRange(
0, max(max(self.alliedAircraft), max(self.enemyAircraft)) + 10
)
self.chart.axisY().applyNiceNumbers()
self.chartView = QtCharts.QChartView(self.chart)
self.chartView.setRenderHint(QPainter.Antialiasing)

View File

@@ -42,10 +42,16 @@ class QArmorChart(QFrame):
self.chart.setTitle("Combat vehicles over time")
self.chart.createDefaultAxes()
self.chart.axisX().setTitleText("Turn")
self.chart.axisX().setLabelFormat("%i")
self.chart.axisX().setRange(0, len(self.alliedArmor))
self.chart.axisX().applyNiceNumbers()
self.chart.axisY().setLabelFormat("%i")
self.chart.axisY().setRange(
0, max(max(self.alliedArmor), max(self.enemyArmor)) + 10
)
self.chart.axisY().applyNiceNumbers()
self.chartView = QtCharts.QChartView(self.chart)
self.chartView.setRenderHint(QPainter.Antialiasing)

View File

@@ -14,7 +14,7 @@ class QStatsWindow(QDialog):
self.setModal(True)
self.setWindowTitle("Stats")
self.setWindowIcon(CONST.ICONS["Statistics"])
self.setMinimumSize(600, 250)
self.setMinimumSize(600, 300)
self.layout = QGridLayout()
self.aircraft_charts = QAircraftChart(self.game)