diff --git a/changelog.md b/changelog.md index 124472c5..6b13d6d2 100644 --- a/changelog.md +++ b/changelog.md @@ -4,7 +4,7 @@ Saves from 3.x are not compatible with 4.0. ## Features/Improvements -* **[Campaign]** Squadrons now have a maximum size and killed pilots replenish at a limited rate. +* **[Campaign]** Squadrons now (optionally, off by default) have a maximum size and killed pilots replenish at a limited rate. * **[Campaign]** Added an option to disable levelling up of AI pilots. * **[Campaign]** Added Russian Intervention 2015 campaign on Syria, for a small and somewhat realistic Russian COIN scenario. * **[Campaign]** Added Operation Atilla campaign on Syria, for a reasonably large invasion of Cyprus scenario. diff --git a/game/settings.py b/game/settings.py index 622ebde0..0e056d2a 100644 --- a/game/settings.py +++ b/game/settings.py @@ -34,11 +34,14 @@ class Settings: player_income_multiplier: float = 1.0 enemy_income_multiplier: float = 1.0 + #: Feature flag for squadron limits. + enable_squadron_pilot_limits: bool = False + #: The maximum number of pilots a squadron can have at one time. Changing this after #: the campaign has started will have no immediate effect; pilots already in the #: squadron will not be removed if the limit is lowered and pilots will not be #: immediately created if the limit is raised. - squadron_pilot_limit: int = 24 + squadron_pilot_limit: int = 4 #: The number of pilots a squadron can replace per turn. squadron_replenishment_rate: int = 4 diff --git a/game/squadrons.py b/game/squadrons.py index 4e465903..9b13eed3 100644 --- a/game/squadrons.py +++ b/game/squadrons.py @@ -112,9 +112,19 @@ class Squadron: return self.name return f'{self.name} "{self.nickname}"' + @property + def pilot_limits_enabled(self) -> bool: + return self.game.settings.enable_squadron_pilot_limits + + def claim_new_pilot_if_allowed(self) -> Optional[Pilot]: + if self.pilot_limits_enabled: + return None + self._recruit_pilots(1) + return self.available_pilots.pop() + def claim_available_pilot(self) -> Optional[Pilot]: if not self.available_pilots: - return None + return self.claim_new_pilot_if_allowed() # For opfor, so player/AI option is irrelevant. if not self.player: @@ -140,7 +150,7 @@ class Squadron: # If they only *prefer* players and we're out of players, just return an AI # pilot. if not prefer_players: - return None + return self.claim_new_pilot_if_allowed() return self.available_pilots.pop() def claim_pilot(self, pilot: Pilot) -> None: @@ -169,9 +179,12 @@ class Squadron: self.available_pilots.extend(new_pilots) def replenish_lost_pilots(self) -> None: + if not self.pilot_limits_enabled: + return + replenish_count = min( self.game.settings.squadron_replenishment_rate, - self.number_of_unfilled_pilot_slots, + self._number_of_unfilled_pilot_slots, ) if replenish_count > 0: self._recruit_pilots(replenish_count) @@ -213,20 +226,23 @@ class Squadron: return len(self.current_roster) @property - def number_of_unfilled_pilot_slots(self) -> int: + def _number_of_unfilled_pilot_slots(self) -> int: return self.game.settings.squadron_pilot_limit - len(self.active_pilots) @property def number_of_available_pilots(self) -> int: return len(self.available_pilots) + def can_provide_pilots(self, count: int) -> bool: + return not self.pilot_limits_enabled or self.number_of_available_pilots >= count + @property def has_available_pilots(self) -> bool: - return bool(self.available_pilots) + return not self.pilot_limits_enabled or bool(self.available_pilots) @property def has_unfilled_pilot_slots(self) -> bool: - return self.number_of_unfilled_pilot_slots > 0 + return not self.pilot_limits_enabled or self._number_of_unfilled_pilot_slots > 0 def can_auto_assign(self, task: FlightType) -> bool: return task in self.auto_assignable_mission_types diff --git a/game/transfers.py b/game/transfers.py index 3c6f2cee..5cd28a32 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -268,8 +268,12 @@ class AirliftPlanner: required, available_aircraft, squadron.aircraft.dcs_unit_type.group_size_max, - squadron.number_of_available_pilots, ) + # TODO: Use number_of_available_pilots directly once feature flag is gone. + # The number of currently available pilots is not relevant when pilot limits + # are disabled. + if not squadron.can_provide_pilots(flight_size): + flight_size = squadron.number_of_available_pilots capacity = flight_size * capacity_each if capacity < self.transfer.size: diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 77e5916f..7bbee129 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -178,7 +178,7 @@ class AircraftAllocator: aircraft, task ) for squadron in squadrons: - if squadron.number_of_available_pilots >= flight.num_aircraft: + if squadron.can_provide_pilots(flight.num_aircraft): inventory.remove_aircraft(aircraft, flight.num_aircraft) return airfield, squadron return None diff --git a/qt_ui/windows/settings/QSettingsWindow.py b/qt_ui/windows/settings/QSettingsWindow.py index dc1392c4..b60920ba 100644 --- a/qt_ui/windows/settings/QSettingsWindow.py +++ b/qt_ui/windows/settings/QSettingsWindow.py @@ -179,6 +179,116 @@ class HqAutomationSettingsBox(QGroupBox): self.game.settings.auto_ato_player_missions_asap = value +class PilotSettingsBox(QGroupBox): + def __init__(self, game: Game) -> None: + super().__init__("Pilots and Squadrons") + self.game = game + + layout = QGridLayout() + self.setLayout(layout) + + self.ai_pilot_levelling = QCheckBox() + self.ai_pilot_levelling.setChecked(self.game.settings.ai_pilot_levelling) + self.ai_pilot_levelling.toggled.connect(self.set_ai_pilot_leveling) + + ai_pilot_levelling_info = ( + "Set whether or not AI pilots will level up after completing a number of" + " sorties. Since pilot level affects the AI skill, you may wish to disable" + " this, lest you face an Ace!" + ) + + self.ai_pilot_levelling.setToolTip(ai_pilot_levelling_info) + ai_pilot_levelling_label = QLabel("Allow AI pilot levelling") + ai_pilot_levelling_label.setToolTip(ai_pilot_levelling_info) + + layout.addWidget(ai_pilot_levelling_label, 0, 0) + layout.addWidget(self.ai_pilot_levelling, 0, 1, Qt.AlignRight) + + enable_squadron_pilot_limits_info = ( + "If set, squadrons will be limited to a maximum number of pilots and dead " + "pilots will replenish at a fixed rate, each defined with the settings" + "below. Auto-purchase may buy aircraft for which there are no pilots" + "available, so this feature is still a work-in-progress." + ) + + enable_squadron_pilot_limits_label = QLabel( + "Enable per-squadron pilot limtits (WIP)" + ) + enable_squadron_pilot_limits_label.setToolTip(enable_squadron_pilot_limits_info) + enable_squadron_pilot_limits = QCheckBox() + enable_squadron_pilot_limits.setToolTip(enable_squadron_pilot_limits_info) + enable_squadron_pilot_limits.setChecked( + self.game.settings.enable_squadron_pilot_limits + ) + enable_squadron_pilot_limits.toggled.connect( + self.set_enable_squadron_pilot_limits + ) + + layout.addWidget(enable_squadron_pilot_limits_label, 1, 0) + layout.addWidget(enable_squadron_pilot_limits, 1, 1, Qt.AlignRight) + + self.pilot_limit = QSpinBox() + self.pilot_limit.setMinimum(12) + self.pilot_limit.setMaximum(72) + self.pilot_limit.setValue(self.game.settings.squadron_pilot_limit) + self.pilot_limit.setEnabled(self.game.settings.enable_squadron_pilot_limits) + self.pilot_limit.valueChanged.connect(self.set_squadron_pilot_limit) + + pilot_limit_info = ( + "Sets the maximum number of pilots a squadron may have active. " + "Changing this value will not have an immediate effect, but will alter " + "replenishment for future turns." + ) + + self.pilot_limit.setToolTip(pilot_limit_info) + pilot_limit_label = QLabel("Maximum number of pilots per squadron") + pilot_limit_label.setToolTip(pilot_limit_info) + + layout.addWidget(pilot_limit_label, 2, 0) + layout.addWidget(self.pilot_limit, 2, 1, Qt.AlignRight) + + self.squadron_replenishment_rate = QSpinBox() + self.squadron_replenishment_rate.setMinimum(1) + self.squadron_replenishment_rate.setMaximum(20) + self.squadron_replenishment_rate.setValue( + self.game.settings.squadron_replenishment_rate + ) + self.squadron_replenishment_rate.setEnabled( + self.game.settings.enable_squadron_pilot_limits + ) + self.squadron_replenishment_rate.valueChanged.connect( + self.set_squadron_replenishment_rate + ) + + squadron_replenishment_rate_info = ( + "Sets the maximum number of pilots that will be recruited to each squadron " + "at the end of each turn. Squadrons will not recruit new pilots beyond the " + "pilot limit, but each squadron with room for more pilots will recruit " + "this many pilots each turn up to the limit." + ) + + self.squadron_replenishment_rate.setToolTip(squadron_replenishment_rate_info) + squadron_replenishment_rate_label = QLabel("Squadron pilot replenishment rate") + squadron_replenishment_rate_label.setToolTip(squadron_replenishment_rate_info) + + layout.addWidget(squadron_replenishment_rate_label, 3, 0) + layout.addWidget(self.squadron_replenishment_rate, 3, 1, Qt.AlignRight) + + def set_enable_squadron_pilot_limits(self, checked: bool) -> None: + self.game.settings.enable_squadron_pilot_limits = checked + self.pilot_limit.setEnabled(checked) + self.squadron_replenishment_rate.setEnabled(checked) + + def set_squadron_pilot_limit(self, value: int) -> None: + self.game.settings.squadron_pilot_limit = value + + def set_squadron_replenishment_rate(self, value: int) -> None: + self.game.settings.squadron_replenishment_rate = value + + def set_ai_pilot_leveling(self, checked: bool) -> None: + self.game.settings.ai_pilot_levelling = checked + + START_TYPE_TOOLTIP = "Selects the start type used for AI aircraft." @@ -516,72 +626,7 @@ class QSettingsWindow(QDialog): general_layout.addWidget(old_tanker_label, 2, 0) general_layout.addWidget(old_tanker, 2, 1, Qt.AlignRight) - def set_squadron_pilot_limit(value: int) -> None: - self.game.settings.squadron_pilot_limit = value - - pilot_limit = QSpinBox() - pilot_limit.setMinimum(12) - pilot_limit.setMaximum(72) - pilot_limit.setValue(self.game.settings.squadron_pilot_limit) - pilot_limit.valueChanged.connect(set_squadron_pilot_limit) - - pilot_limit_info = ( - "Sets the maximum number of pilots a squadron may have active. " - "Changing this value will not have an immediate effect, but will alter " - "replenishment for future turns." - ) - - pilot_limit.setToolTip(pilot_limit_info) - pilot_limit_label = QLabel("Maximum number of pilots per squadron") - pilot_limit_label.setToolTip(pilot_limit_info) - - general_layout.addWidget(pilot_limit_label, 3, 0) - general_layout.addWidget(pilot_limit, 3, 1, Qt.AlignRight) - - def set_squadron_replenishment_rate(value: int) -> None: - self.game.settings.squadron_replenishment_rate = value - - squadron_replenishment_rate = QSpinBox() - squadron_replenishment_rate.setMinimum(1) - squadron_replenishment_rate.setMaximum(20) - squadron_replenishment_rate.setValue( - self.game.settings.squadron_replenishment_rate - ) - squadron_replenishment_rate.valueChanged.connect( - set_squadron_replenishment_rate - ) - - squadron_replenishment_rate_info = ( - "Sets the maximum number of pilots that will be recruited to each squadron " - "at the end of each turn. Squadrons will not recruit new pilots beyond the " - "pilot limit, but each squadron with room for more pilots will recruit " - "this many pilots each turn up to the limit." - ) - - squadron_replenishment_rate.setToolTip(squadron_replenishment_rate_info) - squadron_replenishment_rate_label = QLabel("Squadron pilot replenishment rate") - squadron_replenishment_rate_label.setToolTip(squadron_replenishment_rate_info) - - general_layout.addWidget(squadron_replenishment_rate_label, 4, 0) - general_layout.addWidget(squadron_replenishment_rate, 4, 1, Qt.AlignRight) - - ai_pilot_levelling = QCheckBox() - ai_pilot_levelling.setChecked(self.game.settings.ai_pilot_levelling) - ai_pilot_levelling.toggled.connect(self.applySettings) - - ai_pilot_levelling_info = ( - "Set whether or not AI pilots will level up after completing a number of" - " sorties. Since pilot level affects the AI skill, you may wish to disable" - " this, lest you face an Ace!" - ) - - ai_pilot_levelling.setToolTip(ai_pilot_levelling_info) - ai_pilot_levelling_label = QLabel("Allow AI pilot levelling") - ai_pilot_levelling_label.setToolTip(ai_pilot_levelling_info) - - general_layout.addWidget(ai_pilot_levelling_label, 5, 0) - general_layout.addWidget(ai_pilot_levelling, 5, 1, Qt.AlignRight) - + campaign_layout.addWidget(PilotSettingsBox(self.game)) campaign_layout.addWidget(HqAutomationSettingsBox(self.game)) def initGeneratorLayout(self):