From 34d4ecd4e6164aa67aa78e2209372891302468a1 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 30 Jan 2021 15:04:23 -0800 Subject: [PATCH] Add an option for default start type. Changing this completely breaks OCA/Aircraft missions, but if the player doesn't care about those this can reduce airfield congestion. The UI warns about this. This also makes the AI start type selectable in the flight UI. Fixes https://github.com/Khopa/dcs_liberation/issues/387 Fixes https://github.com/Khopa/dcs_liberation/issues/729 --- changelog.md | 1 + game/settings.py | 4 +-- gen/flights/ai_flight_planner.py | 18 +++++----- .../windows/mission/flight/QFlightCreator.py | 9 +++-- .../flight/settings/QFlightSlotEditor.py | 4 +-- .../flight/settings/QFlightStartType.py | 25 ++++++++++---- .../settings/QGeneralFlightSettingsTab.py | 22 ++++-------- qt_ui/windows/settings/QSettingsWindow.py | 34 +++++++++++++++---- 8 files changed, 71 insertions(+), 46 deletions(-) diff --git a/changelog.md b/changelog.md index cadbc6c4..de7568b2 100644 --- a/changelog.md +++ b/changelog.md @@ -13,6 +13,7 @@ Saves from 2.3 are not compatible with 2.4. * **[Campaign AI]** Reserve aircraft will be ordered if needed to prioritize next turn's CAP/CAS over offensive missions. * **[Campaign AI]** Multiple rounds of CAP will be planned (roughly 90 minutes of coverage). Default starting budget has increased to account for the increased need for aircraft. * **[Mission Generator]** Multiple groups are created for complex SAM sites (SAMs with additional point defense or SHORADS), improving Skynet behavior. +* **[Mission Generator]** Default start type can now be chosen in the settings. This replaces the non-functional "AI Parking Start" option. **Selecting any type other than cold will break OCA/Aircraft missions.** * **[Cheat Menu]** Added ability to toggle base capture and frontline advance/retreat cheats. * **[Skynet]** Updated to 2.0.1. * **[Skynet]** Point defenses are now configured to remain on to protect the site they accompany. diff --git a/game/settings.py b/game/settings.py index a7253f25..d5872e91 100644 --- a/game/settings.py +++ b/game/settings.py @@ -19,11 +19,12 @@ class Settings: supercarrier: bool = False generate_marks: bool = True manpads: bool = True - cold_start: bool = False # Legacy parameter do not use version: Optional[str] = None player_income_multiplier: float = 1.0 enemy_income_multiplier: float = 1.0 + default_start_type: str = "Cold" + # Campaign management automate_runway_repair: bool = False automate_front_line_reinforcements: bool = False @@ -36,7 +37,6 @@ class Settings: perf_artillery: bool = True perf_moving_units: bool = True perf_infantry: bool = True - perf_ai_parking_start: bool = True perf_destroyed_units: bool = True # Performance culling diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index f4479cf5..2267fcb4 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -563,15 +563,22 @@ class CoalitionMissionPlanner: ]) for target in self.objective_finder.oca_targets(min_aircraft=20): - yield ProposedMission(target, [ - ProposedFlight(FlightType.OCA_AIRCRAFT, 2, self.MAX_OCA_RANGE), + flights = [ ProposedFlight(FlightType.OCA_RUNWAY, 2, self.MAX_OCA_RANGE), + ] + if self.game.settings.default_start_type == "Cold": + # Only schedule if the default start type is Cold. If the player + # has set anything else there are no targets to hit. + flights.append(ProposedFlight(FlightType.OCA_AIRCRAFT, 2, + self.MAX_OCA_RANGE)) + flights.extend([ # TODO: Max escort range. ProposedFlight(FlightType.ESCORT, 2, self.MAX_OCA_RANGE, EscortType.AirToAir), ProposedFlight(FlightType.SEAD, 2, self.MAX_OCA_RANGE, EscortType.Sead), ]) + yield ProposedMission(target, flights) # Plan strike missions. for target in self.objective_finder.strike_targets(): @@ -651,11 +658,6 @@ class CoalitionMissionPlanner: reserves: bool = False) -> None: """Allocates aircraft for a proposed mission and adds it to the ATO.""" - if self.game.settings.perf_ai_parking_start: - start_type = "Cold" - else: - start_type = "Warm" - if self.is_player: package_country = self.game.player_country else: @@ -667,7 +669,7 @@ class CoalitionMissionPlanner: self.game.aircraft_inventory, self.is_player, package_country, - start_type + self.game.settings.default_start_type ) # Attempt to plan all the main elements of the mission first. Escorts diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index 629dfad7..533ff71b 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -155,12 +155,11 @@ class QFlightCreator(QDialog): if isinstance(origin, OffMapSpawn): start_type = "In Flight" - elif self.game.settings.perf_ai_parking_start: - start_type = "Cold" else: - start_type = "Warm" - flight = Flight(self.package, self.country, aircraft, size, task, start_type, origin, - arrival, divert, custom_name=self.custom_name_text) + start_type = self.game.settings.default_start_type + flight = Flight(self.package, self.country, aircraft, size, task, + start_type, origin, arrival, divert, + custom_name=self.custom_name_text) flight.client_count = self.client_slots_spinner.value() # noinspection PyUnresolvedReferences diff --git a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py index 1ecb9a28..f21b07b0 100644 --- a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py +++ b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py @@ -27,14 +27,14 @@ class QFlightSlotEditor(QGroupBox): layout = QGridLayout() - self.aircraft_count = QLabel("Aircraft count :") + self.aircraft_count = QLabel("Aircraft count:") self.aircraft_count_spinner = QSpinBox() self.aircraft_count_spinner.setMinimum(1) self.aircraft_count_spinner.setMaximum(max_count) self.aircraft_count_spinner.setValue(flight.count) self.aircraft_count_spinner.valueChanged.connect(self._changed_aircraft_count) - self.client_count = QLabel("Client slots count :") + self.client_count = QLabel("Client slots count:") self.client_count_spinner = QSpinBox() self.client_count_spinner.setMinimum(0) self.client_count_spinner.setMaximum(max_count) diff --git a/qt_ui/windows/mission/flight/settings/QFlightStartType.py b/qt_ui/windows/mission/flight/settings/QFlightStartType.py index b49fb5d7..17fbe042 100644 --- a/qt_ui/windows/mission/flight/settings/QFlightStartType.py +++ b/qt_ui/windows/mission/flight/settings/QFlightStartType.py @@ -1,4 +1,10 @@ -from PySide2.QtWidgets import QComboBox, QGroupBox, QHBoxLayout, QLabel +from PySide2.QtWidgets import ( + QComboBox, + QGroupBox, + QHBoxLayout, + QLabel, + QVBoxLayout, +) from gen.flights.flight import Flight from qt_ui.models import PackageModel @@ -11,8 +17,9 @@ class QFlightStartType(QGroupBox): self.package_model = package_model self.flight = flight - self.layout = QHBoxLayout() - self.start_type_label = QLabel("Start type : ") + self.layout = QVBoxLayout() + self.main_row = QHBoxLayout() + self.start_type_label = QLabel("Start type:") self.start_type = QComboBox() for i, st in enumerate([b for b in ["Cold", "Warm", "Runway", "In Flight"]]): @@ -21,11 +28,17 @@ class QFlightStartType(QGroupBox): self.start_type.setCurrentIndex(i) self.start_type.currentTextChanged.connect(self._on_start_type_selected) - self.layout.addWidget(self.start_type_label) - self.layout.addWidget(self.start_type) + self.main_row.addWidget(self.start_type_label) + self.main_row.addWidget(self.start_type) + + self.layout.addLayout(self.main_row) + self.layout.addWidget(QLabel( + "Any option other than Cold will make this flight non-targetable " + + "by OCA/Aircraft missions. This will affect game balance." + )) self.setLayout(self.layout) def _on_start_type_selected(self): selected = self.start_type.currentData() self.flight.start_type = selected - self.package_model.update_tot() \ No newline at end of file + self.package_model.update_tot() diff --git a/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py b/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py index 5875a79d..05eaf1fa 100644 --- a/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py +++ b/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py @@ -2,7 +2,6 @@ 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.models import PackageModel from qt_ui.windows.mission.flight.settings.QFlightDepartureDisplay import \ @@ -24,21 +23,12 @@ class QGeneralFlightSettingsTab(QFrame): super().__init__() layout = QGridLayout() - flight_info = QFlightTypeTaskInfo(flight) - flight_departure = QFlightDepartureDisplay(package_model, flight) - flight_slots = QFlightSlotEditor(package_model, flight, game) - flight_start_type = QFlightStartType(package_model, flight) - flight_custom_name = QFlightCustomName(flight) - layout.addWidget(flight_info, 0, 0) - layout.addWidget(flight_departure, 1, 0) - layout.addWidget(flight_slots, 2, 0) - layout.addWidget(flight_start_type, 3, 0) - layout.addWidget(flight_custom_name, 4, 0) + layout.addWidget(QFlightTypeTaskInfo(flight), 0, 0) + layout.addWidget(QFlightDepartureDisplay(package_model, flight), 1, 0) + layout.addWidget(QFlightSlotEditor(package_model, flight, game), 2, 0) + layout.addWidget(QFlightStartType(package_model, flight), 3, 0) + layout.addWidget(QFlightCustomName(flight), 4, 0) vstretch = QVBoxLayout() vstretch.addStretch() - layout.addLayout(vstretch, 3, 0) + layout.addLayout(vstretch, 5, 0) self.setLayout(layout) - - flight_start_type.setEnabled(flight.client_count > 0) - flight_slots.changed.connect( - lambda: flight_start_type.setEnabled(flight.client_count > 0)) diff --git a/qt_ui/windows/settings/QSettingsWindow.py b/qt_ui/windows/settings/QSettingsWindow.py index d372214b..dad76e8d 100644 --- a/qt_ui/windows/settings/QSettingsWindow.py +++ b/qt_ui/windows/settings/QSettingsWindow.py @@ -23,6 +23,7 @@ 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 from qt_ui.widgets.QLabeledWidget import QLabeledWidget from qt_ui.widgets.spinsliders import TenthsSpinSlider from qt_ui.windows.GameUpdateSignal import GameUpdateSignal @@ -68,6 +69,21 @@ class CheatSettingsBox(QGroupBox): return self.base_capture_cheat_checkbox.isChecked() +START_TYPE_TOOLTIP = "Selects the start type used for AI aircraft." + + +class StartTypeComboBox(QComboBox): + def __init__(self, settings: Settings) -> None: + super().__init__() + self.settings = settings + self.addItems(["Cold", "Warm", "Runway", "In Flight"]) + self.currentTextChanged.connect(self.on_change) + self.setToolTip(START_TYPE_TOOLTIP) + + def on_change(self, value: str) -> None: + self.settings.default_start_type = value + + class QSettingsWindow(QDialog): def __init__(self, game: Game): @@ -370,6 +386,17 @@ class QSettingsWindow(QDialog): self.gameplayLayout.addWidget(self.never_delay_players, 2, 1, Qt.AlignRight) + start_type_label = QLabel( + "Default start type for AI aircraft:
Warning: " + + "Any option other than Cold breaks OCA/Aircraft missions." + ) + start_type_label.setToolTip(START_TYPE_TOOLTIP) + start_type = StartTypeComboBox(self.game.settings) + start_type.setCurrentText(self.game.settings.default_start_type) + + self.gameplayLayout.addWidget(start_type_label, 3, 0) + self.gameplayLayout.addWidget(start_type, 3, 1) + self.performance = QGroupBox("Performance") self.performanceLayout = QGridLayout() self.performanceLayout.setAlignment(Qt.AlignTop) @@ -395,10 +422,6 @@ class QSettingsWindow(QDialog): self.infantry.setChecked(self.game.settings.perf_infantry) self.infantry.toggled.connect(self.applySettings) - self.ai_parking_start = QCheckBox() - self.ai_parking_start.setChecked(self.game.settings.perf_ai_parking_start) - self.ai_parking_start.toggled.connect(self.applySettings) - self.destroyed_units = QCheckBox() self.destroyed_units.setChecked(self.game.settings.perf_destroyed_units) self.destroyed_units.toggled.connect(self.applySettings) @@ -427,8 +450,6 @@ class QSettingsWindow(QDialog): self.performanceLayout.addWidget(self.moving_units, 3, 1, alignment=Qt.AlignRight) self.performanceLayout.addWidget(QLabel("Generate infantry squads along vehicles"), 4, 0) self.performanceLayout.addWidget(self.infantry, 4, 1, alignment=Qt.AlignRight) - self.performanceLayout.addWidget(QLabel("AI planes parking start (AI starts in flight if disabled)"), 5, 0) - self.performanceLayout.addWidget(self.ai_parking_start, 5, 1, alignment=Qt.AlignRight) self.performanceLayout.addWidget(QLabel("Include destroyed units carcass"), 6, 0) self.performanceLayout.addWidget(self.destroyed_units, 6, 1, alignment=Qt.AlignRight) @@ -504,7 +525,6 @@ class QSettingsWindow(QDialog): self.game.settings.perf_artillery = self.arti.isChecked() self.game.settings.perf_moving_units = self.moving_units.isChecked() self.game.settings.perf_infantry = self.infantry.isChecked() - self.game.settings.perf_ai_parking_start = self.ai_parking_start.isChecked() self.game.settings.perf_destroyed_units = self.destroyed_units.isChecked() self.game.settings.perf_culling = self.culling.isChecked()