diff --git a/changelog.md b/changelog.md
index 6d871dac..b834bc31 100644
--- a/changelog.md
+++ b/changelog.md
@@ -4,6 +4,7 @@
* **[Mission Generation]** Given a CAS flight was planned, delay ground force attack until first CAS flight is on station
* **[Mission Generation]** Add option to switch ATFLIR to LITENING automatically for ground based F-18C flights
* **[Cheat Menu]** Option to instantly transfer squadrons across bases.
+* **[UI]** Add selectable units in faction overview during campaign generation.
## Fixes
* **[UI]** Removed deprecated options
diff --git a/game/factions/faction.py b/game/factions/faction.py
index 8ff00471..ddea0e76 100644
--- a/game/factions/faction.py
+++ b/game/factions/faction.py
@@ -60,7 +60,7 @@ class Faction:
description: str = field(default="")
# Available aircraft
- aircrafts: List[AircraftType] = field(default_factory=list)
+ aircraft: List[AircraftType] = field(default_factory=list)
# Available awacs aircraft
awacs: List[AircraftType] = field(default_factory=list)
@@ -178,6 +178,10 @@ class Faction:
)
return sorted(air_defenses)
+ @cached_property
+ def aircrafts(self) -> list[UnitType[Any]]:
+ return list(self.aircraft + self.awacs + self.tankers)
+
@classmethod
def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
faction = Faction(locales=json.get("locales"))
@@ -206,14 +210,10 @@ class Faction:
faction.authors = json.get("authors", "")
faction.description = json.get("description", "")
- faction.aircrafts = [AircraftType.named(n) for n in json.get("aircrafts", [])]
+ faction.aircraft = [AircraftType.named(n) for n in json.get("aircrafts", [])]
faction.awacs = [AircraftType.named(n) for n in json.get("awacs", [])]
faction.tankers = [AircraftType.named(n) for n in json.get("tankers", [])]
- faction.aircrafts = list(
- set(faction.aircrafts + faction.awacs + faction.tankers)
- )
-
faction.frontline_units = [
GroundUnitType.named(n) for n in json.get("frontline_units", [])
]
diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py
index b0efaf39..c774150c 100644
--- a/qt_ui/windows/newgame/QNewGameWizard.py
+++ b/qt_ui/windows/newgame/QNewGameWizard.py
@@ -1,16 +1,28 @@
from __future__ import unicode_literals
import logging
+import math
from datetime import datetime, timedelta
from typing import List
from PySide2 import QtGui, QtWidgets
from PySide2.QtCore import QDate, QItemSelectionModel, QPoint, Qt, Signal
-from PySide2.QtWidgets import QCheckBox, QLabel, QTextEdit, QVBoxLayout, QTextBrowser
+from PySide2.QtWidgets import (
+ QCheckBox,
+ QLabel,
+ QTextEdit,
+ QVBoxLayout,
+ QTextBrowser,
+ QWidget,
+ QGridLayout,
+ QScrollArea,
+ QSizePolicy,
+)
from jinja2 import Environment, FileSystemLoader, select_autoescape
from game.campaignloader.campaign import Campaign, DEFAULT_BUDGET
from game.dcs.aircrafttype import AircraftType
+from game.dcs.unittype import UnitType
from game.factions import FACTIONS, Faction
from game.settings import Settings
from game.theater.start_generator import GameGenerator, GeneratorSettings, ModSettings
@@ -236,6 +248,87 @@ class IntroPage(QtWidgets.QWizardPage):
self.setLayout(layout)
+class QFactionUnits(QScrollArea):
+ def __init__(self, faction: Faction, parent=None):
+ super().__init__()
+ self.setWidgetResizable(True)
+ self.content = QWidget()
+ self.setWidget(self.content)
+ self.parent = parent
+ self.faction = faction
+ self._create_checkboxes()
+
+ def _add_checkboxes(self, units: list, counter: int, grid: QGridLayout) -> int:
+ counter += 1
+ for i, v in enumerate(sorted(units, key=lambda x: x.name), counter):
+ cb = QCheckBox(v.name)
+ cb.setCheckState(Qt.CheckState.Checked)
+ self.checkboxes[v.name] = cb
+ grid.addWidget(cb, i, 1)
+ counter += 1
+ counter += 1
+ return counter
+
+ def _create_checkboxes(self):
+ counter = 0
+ self.checkboxes: dict[str, QCheckBox] = {}
+ grid = QGridLayout()
+ if len(self.faction.aircraft) > 0:
+ grid.addWidget(QLabel("Aircraft:"), counter, 0)
+ counter = self._add_checkboxes(self.faction.aircraft, counter, grid)
+ if len(self.faction.awacs) > 0:
+ grid.addWidget(QLabel("AWACS:"), counter, 0)
+ counter = self._add_checkboxes(self.faction.awacs, counter, grid)
+ if len(self.faction.tankers) > 0:
+ grid.addWidget(QLabel("Tankers:"), counter, 0)
+ counter = self._add_checkboxes(self.faction.tankers, counter, grid)
+ if len(self.faction.frontline_units) > 0:
+ grid.addWidget(QLabel("Frontlines vehicles:"), counter, 0)
+ counter = self._add_checkboxes(self.faction.frontline_units, counter, grid)
+ if len(self.faction.artillery_units) > 0:
+ grid.addWidget(QLabel("Artillery units:"), counter, 0)
+ counter = self._add_checkboxes(self.faction.artillery_units, counter, grid)
+ if len(self.faction.logistics_units) > 0:
+ grid.addWidget(QLabel("Logistics units:"), counter, 0)
+ counter = self._add_checkboxes(self.faction.logistics_units, counter, grid)
+ if len(self.faction.infantry_units) > 0:
+ grid.addWidget(QLabel("Infantry units:"), counter, 0)
+ counter = self._add_checkboxes(self.faction.infantry_units, counter, grid)
+ if len(self.faction.preset_groups) > 0:
+ grid.addWidget(QLabel("Preset groups:"), counter, 0)
+ counter = self._add_checkboxes(self.faction.preset_groups, counter, grid)
+ if len(self.faction.air_defense_units) > 0:
+ grid.addWidget(QLabel("Air defenses:"), counter, 0)
+ counter = self._add_checkboxes(
+ self.faction.air_defense_units, counter, grid
+ )
+ if len(self.faction.naval_units) > 0:
+ grid.addWidget(QLabel("Naval units:"), counter, 0)
+ counter = self._add_checkboxes(self.faction.naval_units, counter, grid)
+ if len(self.faction.missiles) > 0:
+ grid.addWidget(QLabel("Missile units:"), counter, 0)
+ self._add_checkboxes(self.faction.missiles, counter, grid)
+
+ self.content.setLayout(grid)
+
+ def updateFaction(self, faction: Faction):
+ self.faction = faction
+ self.content = QWidget()
+ self.setWidget(self.content)
+ self._create_checkboxes()
+ self.update()
+ if self.parent:
+ self.parent.update()
+
+ def updateFactionUnits(self, units: list):
+ deletes = []
+ for a in units:
+ if not self.checkboxes[a.name].isChecked():
+ deletes.append(a)
+ for d in deletes:
+ units.remove(d)
+
+
class FactionSelection(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super(FactionSelection, self).__init__(parent)
@@ -259,8 +352,6 @@ class FactionSelection(QtWidgets.QWizardPage):
blueFaction = QtWidgets.QLabel("Player Faction :")
self.blueFactionSelect = QtWidgets.QComboBox()
- for f in FACTIONS:
- self.blueFactionSelect.addItem(f)
blueFaction.setBuddy(self.blueFactionSelect)
redFaction = QtWidgets.QLabel("Enemy Faction :")
@@ -271,26 +362,41 @@ class FactionSelection(QtWidgets.QWizardPage):
self.blueFactionDescription = QTextBrowser()
self.blueFactionDescription.setReadOnly(True)
self.blueFactionDescription.setOpenExternalLinks(True)
+ self.blueFactionDescription.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+ self.blueFactionDescription.setMaximumHeight(120)
self.redFactionDescription = QTextBrowser()
self.redFactionDescription.setReadOnly(True)
self.redFactionDescription.setOpenExternalLinks(True)
+ self.redFactionDescription.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+ self.redFactionDescription.setMaximumHeight(120)
# Setup default selected factions
for i, r in enumerate(FACTIONS):
- self.redFactionSelect.addItem(r)
+ self.blueFactionSelect.addItem(r, FACTIONS[r])
+ self.redFactionSelect.addItem(r, FACTIONS[r])
if r == "Russia 1990":
self.redFactionSelect.setCurrentIndex(i)
if r == "USA 2005":
self.blueFactionSelect.setCurrentIndex(i)
+ # Faction units
+ self.blueFactionUnits = QFactionUnits(
+ self.blueFactionSelect.currentData(), self.blueGroupLayout
+ )
+ self.redFactionUnits = QFactionUnits(
+ self.redFactionSelect.currentData(), self.redGroupLayout
+ )
+
self.blueGroupLayout.addWidget(blueFaction, 0, 0)
self.blueGroupLayout.addWidget(self.blueFactionSelect, 0, 1)
self.blueGroupLayout.addWidget(self.blueFactionDescription, 1, 0, 1, 2)
+ self.blueGroupLayout.addWidget(self.blueFactionUnits, 2, 0, 1, 2)
self.redGroupLayout.addWidget(redFaction, 0, 0)
self.redGroupLayout.addWidget(self.redFactionSelect, 0, 1)
self.redGroupLayout.addWidget(self.redFactionDescription, 1, 0, 1, 2)
+ self.redGroupLayout.addWidget(self.redFactionUnits, 2, 0, 1, 2)
self.factionsGroupLayout.addLayout(self.blueGroupLayout)
self.factionsGroupLayout.addLayout(self.redGroupLayout)
@@ -348,13 +454,35 @@ class FactionSelection(QtWidgets.QWizardPage):
self.blueFactionDescription.setText(blue_faction_txt)
self.redFactionDescription.setText(red_faction_txt)
+ self.blueGroupLayout.removeWidget(self.blueFactionUnits)
+ self.blueFactionUnits.updateFaction(blue_faction)
+ self.blueGroupLayout.addWidget(self.blueFactionUnits, 2, 0, 1, 2)
+ self.redGroupLayout.removeWidget(self.redFactionUnits)
+ self.redFactionUnits.updateFaction(red_faction)
+ self.redGroupLayout.addWidget(self.redFactionUnits, 2, 0, 1, 2)
+
+ @staticmethod
+ def _filter_selected_units(qfu: QFactionUnits) -> Faction:
+ qfu.updateFactionUnits(qfu.faction.aircrafts)
+ qfu.updateFactionUnits(qfu.faction.awacs)
+ qfu.updateFactionUnits(qfu.faction.tankers)
+ qfu.updateFactionUnits(qfu.faction.frontline_units)
+ qfu.updateFactionUnits(qfu.faction.artillery_units)
+ qfu.updateFactionUnits(qfu.faction.logistics_units)
+ qfu.updateFactionUnits(qfu.faction.infantry_units)
+ qfu.updateFactionUnits(qfu.faction.preset_groups)
+ qfu.updateFactionUnits(qfu.faction.air_defense_units)
+ qfu.updateFactionUnits(qfu.faction.naval_units)
+ qfu.updateFactionUnits(qfu.faction.missiles)
+ return qfu.faction
+
@property
def selected_blue_faction(self) -> Faction:
- return FACTIONS[self.blueFactionSelect.currentText()]
+ return self._filter_selected_units(self.blueFactionUnits)
@property
def selected_red_faction(self) -> Faction:
- return FACTIONS[self.redFactionSelect.currentText()]
+ return self._filter_selected_units(self.redFactionUnits)
class TheaterConfiguration(QtWidgets.QWizardPage):
diff --git a/resources/factions/wrl_taskforcered.json b/resources/factions/wrl_taskforcered.json
index b68a0be2..0969c38e 100644
--- a/resources/factions/wrl_taskforcered.json
+++ b/resources/factions/wrl_taskforcered.json
@@ -76,7 +76,7 @@
"Paratrooper AKS",
"Paratrooper RPG-16"
],
-"missiles": [
+ "missiles": [
"SSM SS-1C Scud-B"
],
"preset_groups": [
diff --git a/resources/ui/templates/factiontemplate_EN.j2 b/resources/ui/templates/factiontemplate_EN.j2
index b48f3349..1e40e777 100644
--- a/resources/ui/templates/factiontemplate_EN.j2
+++ b/resources/ui/templates/factiontemplate_EN.j2
@@ -1,62 +1,4 @@
-{{ faction.description|safe }}
+Description: {{ faction.description|safe }}
Author(s): {{ faction.authors }}
-
-
-
-Potential aircraft:
-
- The aircraft that will be present in the game are specified by the campaign. - Only aircraft in this list will be allowed, but not all aircraft in this - list will necessarily be available. -
-- If the campaign you chose doesn't include the aircraft you want to fly, you - can mod the campaign by following the squadron section of the - - custom campaigns guide. -
-