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