mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Show parking capacities in air wing config.
This does show the theoretical parking use of full squadrons even when the new rules are not enabled. Since limits can be enabled manually later in the game, it's still useful information, even if it's a bit misleading. https://github.com/dcs-liberation/dcs_liberation/issues/2910
This commit is contained in:
parent
56f93c76eb
commit
cb61dfccc4
@ -17,6 +17,7 @@ Saves from 7.0.0 are compatible with 7.1.0
|
|||||||
* **[Mission Generation]** Added option to prevent scud and V2 sites from firing at the start of the mission.
|
* **[Mission Generation]** Added option to prevent scud and V2 sites from firing at the start of the mission.
|
||||||
* **[Mission Generation]** Added settings for controlling number of tactical commander, observer, JTAC, and game master slots.
|
* **[Mission Generation]** Added settings for controlling number of tactical commander, observer, JTAC, and game master slots.
|
||||||
* **[Mission Planning]** Per-flight TOT offsets can now be set in the flight details UI. This allows individual flights to be scheduled ahead of or behind the rest of the package.
|
* **[Mission Planning]** Per-flight TOT offsets can now be set in the flight details UI. This allows individual flights to be scheduled ahead of or behind the rest of the package.
|
||||||
|
* **[UI]** Parking capacity of each squadron's base is now shown during air wing configuration to avoid overcrowding bases when beginning the game with full squadrons.
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import random
|
|||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, Sequence, TYPE_CHECKING
|
from typing import Optional, Sequence, TYPE_CHECKING, Any
|
||||||
|
from uuid import uuid4, UUID
|
||||||
|
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ from game.ato import Flight, FlightType, Package
|
|||||||
from game.settings import AutoAtoBehavior, Settings
|
from game.settings import AutoAtoBehavior, Settings
|
||||||
from .pilot import Pilot, PilotStatus
|
from .pilot import Pilot, PilotStatus
|
||||||
from ..db.database import Database
|
from ..db.database import Database
|
||||||
|
from ..savecompat import has_save_compat_for
|
||||||
from ..utils import meters
|
from ..utils import meters
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -26,6 +28,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Squadron:
|
class Squadron:
|
||||||
|
id: UUID = field(init=False, default_factory=uuid4)
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
nickname: Optional[str]
|
nickname: Optional[str]
|
||||||
country: str
|
country: str
|
||||||
@ -61,21 +65,24 @@ class Squadron:
|
|||||||
untasked_aircraft: int = field(init=False, hash=False, compare=False, default=0)
|
untasked_aircraft: int = field(init=False, hash=False, compare=False, default=0)
|
||||||
pending_deliveries: int = field(init=False, hash=False, compare=False, default=0)
|
pending_deliveries: int = field(init=False, hash=False, compare=False, default=0)
|
||||||
|
|
||||||
|
@has_save_compat_for(7)
|
||||||
|
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||||
|
if "id" not in state:
|
||||||
|
state["id"] = uuid4()
|
||||||
|
self.__dict__.update(state)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
if self.nickname is None:
|
if self.nickname is None:
|
||||||
return self.name
|
return self.name
|
||||||
return f'{self.name} "{self.nickname}"'
|
return f'{self.name} "{self.nickname}"'
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
return hash(
|
return hash(self.id)
|
||||||
(
|
|
||||||
self.name,
|
def __eq__(self, other: object) -> bool:
|
||||||
self.nickname,
|
if not isinstance(other, Squadron):
|
||||||
self.country,
|
return False
|
||||||
self.role,
|
return self.id == other.id
|
||||||
self.aircraft,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def player(self) -> bool:
|
def player(self) -> bool:
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from collections import defaultdict
|
||||||
from typing import Iterable, Iterator, Optional
|
from typing import Iterable, Iterator, Optional
|
||||||
|
|
||||||
from PySide6.QtCore import (
|
from PySide6.QtCore import (
|
||||||
@ -150,6 +151,41 @@ class SquadronSizeSpinner(QSpinBox):
|
|||||||
# return size
|
# return size
|
||||||
|
|
||||||
|
|
||||||
|
class AirWingConfigParkingTracker(QWidget):
|
||||||
|
allocation_changed = Signal()
|
||||||
|
|
||||||
|
def __init__(self, squadrons: Iterable[Squadron]) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.by_cp: dict[ControlPoint, set[Squadron]] = defaultdict(set)
|
||||||
|
for squadron in squadrons:
|
||||||
|
self.add_squadron(squadron)
|
||||||
|
|
||||||
|
def add_squadron(self, squadron: Squadron) -> None:
|
||||||
|
self.by_cp[squadron.location].add(squadron)
|
||||||
|
self.signal_change()
|
||||||
|
|
||||||
|
def remove_squadron(self, squadron: Squadron) -> None:
|
||||||
|
self.by_cp[squadron.location].remove(squadron)
|
||||||
|
self.signal_change()
|
||||||
|
|
||||||
|
def relocate_squadron(
|
||||||
|
self,
|
||||||
|
squadron: Squadron,
|
||||||
|
prior_location: ControlPoint,
|
||||||
|
new_location: ControlPoint,
|
||||||
|
) -> None:
|
||||||
|
self.by_cp[prior_location].remove(squadron)
|
||||||
|
self.by_cp[new_location].add(squadron)
|
||||||
|
squadron.relocate_to(new_location)
|
||||||
|
self.signal_change()
|
||||||
|
|
||||||
|
def used_parking_at(self, control_point: ControlPoint) -> int:
|
||||||
|
return sum(s.max_size for s in self.by_cp[control_point])
|
||||||
|
|
||||||
|
def signal_change(self) -> None:
|
||||||
|
self.allocation_changed.emit()
|
||||||
|
|
||||||
|
|
||||||
class SquadronConfigurationBox(QGroupBox):
|
class SquadronConfigurationBox(QGroupBox):
|
||||||
remove_squadron_signal = Signal(Squadron)
|
remove_squadron_signal = Signal(Squadron)
|
||||||
|
|
||||||
@ -158,11 +194,13 @@ class SquadronConfigurationBox(QGroupBox):
|
|||||||
game: Game,
|
game: Game,
|
||||||
coalition: Coalition,
|
coalition: Coalition,
|
||||||
squadron: Squadron,
|
squadron: Squadron,
|
||||||
|
parking_tracker: AirWingConfigParkingTracker,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.game = game
|
self.game = game
|
||||||
self.coalition = coalition
|
self.coalition = coalition
|
||||||
self.squadron = squadron
|
self.squadron = squadron
|
||||||
|
self.parking_tracker = parking_tracker
|
||||||
|
|
||||||
columns = QHBoxLayout()
|
columns = QHBoxLayout()
|
||||||
self.setLayout(columns)
|
self.setLayout(columns)
|
||||||
@ -200,6 +238,7 @@ class SquadronConfigurationBox(QGroupBox):
|
|||||||
left_column.addLayout(size_column)
|
left_column.addLayout(size_column)
|
||||||
size_column.addWidget(QLabel("Max size:"))
|
size_column.addWidget(QLabel("Max size:"))
|
||||||
self.max_size_selector = SquadronSizeSpinner(self.squadron.max_size, self)
|
self.max_size_selector = SquadronSizeSpinner(self.squadron.max_size, self)
|
||||||
|
self.max_size_selector.valueChanged.connect(self.update_max_size)
|
||||||
size_column.addWidget(self.max_size_selector)
|
size_column.addWidget(self.max_size_selector)
|
||||||
|
|
||||||
task_column = QVBoxLayout()
|
task_column = QVBoxLayout()
|
||||||
@ -214,8 +253,14 @@ class SquadronConfigurationBox(QGroupBox):
|
|||||||
squadron.location,
|
squadron.location,
|
||||||
squadron.aircraft,
|
squadron.aircraft,
|
||||||
)
|
)
|
||||||
|
self.base_selector.currentIndexChanged.connect(self.relocate_squadron)
|
||||||
left_column.addWidget(self.base_selector)
|
left_column.addWidget(self.base_selector)
|
||||||
|
|
||||||
|
self.parking_label = QLabel()
|
||||||
|
self.update_parking_label()
|
||||||
|
self.parking_tracker.allocation_changed.connect(self.update_parking_label)
|
||||||
|
left_column.addWidget(self.parking_label)
|
||||||
|
|
||||||
if not squadron.player and squadron.aircraft.flyable:
|
if not squadron.player and squadron.aircraft.flyable:
|
||||||
player_label = QLabel("Player slots not available for opfor")
|
player_label = QLabel("Player slots not available for opfor")
|
||||||
elif not squadron.aircraft.flyable:
|
elif not squadron.aircraft.flyable:
|
||||||
@ -266,9 +311,26 @@ class SquadronConfigurationBox(QGroupBox):
|
|||||||
self.player_list.setText(
|
self.player_list.setText(
|
||||||
"<br />".join(p.name for p in self.claim_players_from_squadron())
|
"<br />".join(p.name for p in self.claim_players_from_squadron())
|
||||||
)
|
)
|
||||||
|
self.update_parking_label()
|
||||||
finally:
|
finally:
|
||||||
self.blockSignals(old_state)
|
self.blockSignals(old_state)
|
||||||
|
|
||||||
|
def update_parking_label(self) -> None:
|
||||||
|
self.parking_label.setText(
|
||||||
|
f"{self.parking_tracker.used_parking_at(self.squadron.location)}/"
|
||||||
|
f"{self.squadron.location.total_aircraft_parking}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_max_size(self) -> None:
|
||||||
|
self.squadron.max_size = self.max_size_selector.value()
|
||||||
|
self.parking_tracker.signal_change()
|
||||||
|
|
||||||
|
def relocate_squadron(self) -> None:
|
||||||
|
location = self.base_selector.currentData()
|
||||||
|
self.parking_tracker.relocate_squadron(
|
||||||
|
self.squadron, self.squadron.location, location
|
||||||
|
)
|
||||||
|
|
||||||
def remove_from_squadron_config(self) -> None:
|
def remove_from_squadron_config(self) -> None:
|
||||||
self.remove_squadron_signal.emit(self.squadron)
|
self.remove_squadron_signal.emit(self.squadron)
|
||||||
|
|
||||||
@ -321,6 +383,7 @@ class SquadronConfigurationBox(QGroupBox):
|
|||||||
self.squadron = new_squadron
|
self.squadron = new_squadron
|
||||||
self.bind_data()
|
self.bind_data()
|
||||||
self.mission_types.replace_squadron(self.squadron)
|
self.mission_types.replace_squadron(self.squadron)
|
||||||
|
self.parking_tracker.signal_change()
|
||||||
|
|
||||||
def reset_title(self) -> None:
|
def reset_title(self) -> None:
|
||||||
self.setTitle(f"{self.name_edit.text()} - {self.squadron.aircraft}")
|
self.setTitle(f"{self.name_edit.text()} - {self.squadron.aircraft}")
|
||||||
@ -361,11 +424,13 @@ class SquadronConfigurationLayout(QVBoxLayout):
|
|||||||
game: Game,
|
game: Game,
|
||||||
coalition: Coalition,
|
coalition: Coalition,
|
||||||
squadrons: list[Squadron],
|
squadrons: list[Squadron],
|
||||||
|
parking_tracker: AirWingConfigParkingTracker,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.game = game
|
self.game = game
|
||||||
self.coalition = coalition
|
self.coalition = coalition
|
||||||
self.squadron_configs = []
|
self.squadron_configs = []
|
||||||
|
self.parking_tracker = parking_tracker
|
||||||
for squadron in squadrons:
|
for squadron in squadrons:
|
||||||
self.add_squadron(squadron)
|
self.add_squadron(squadron)
|
||||||
|
|
||||||
@ -376,6 +441,7 @@ class SquadronConfigurationLayout(QVBoxLayout):
|
|||||||
return keep_squadrons
|
return keep_squadrons
|
||||||
|
|
||||||
def remove_squadron(self, squadron: Squadron) -> None:
|
def remove_squadron(self, squadron: Squadron) -> None:
|
||||||
|
self.parking_tracker.remove_squadron(squadron)
|
||||||
for squadron_config in self.squadron_configs:
|
for squadron_config in self.squadron_configs:
|
||||||
if squadron_config.squadron == squadron:
|
if squadron_config.squadron == squadron:
|
||||||
squadron_config.deleteLater()
|
squadron_config.deleteLater()
|
||||||
@ -386,23 +452,32 @@ class SquadronConfigurationLayout(QVBoxLayout):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def add_squadron(self, squadron: Squadron) -> None:
|
def add_squadron(self, squadron: Squadron) -> None:
|
||||||
squadron_config = SquadronConfigurationBox(self.game, self.coalition, squadron)
|
squadron_config = SquadronConfigurationBox(
|
||||||
|
self.game, self.coalition, squadron, self.parking_tracker
|
||||||
|
)
|
||||||
squadron_config.remove_squadron_signal.connect(self.remove_squadron)
|
squadron_config.remove_squadron_signal.connect(self.remove_squadron)
|
||||||
self.squadron_configs.append(squadron_config)
|
self.squadron_configs.append(squadron_config)
|
||||||
self.addWidget(squadron_config)
|
self.addWidget(squadron_config)
|
||||||
|
self.parking_tracker.add_squadron(squadron)
|
||||||
|
|
||||||
|
|
||||||
class AircraftSquadronsPage(QWidget):
|
class AircraftSquadronsPage(QWidget):
|
||||||
remove_squadron_page = Signal(AircraftType)
|
remove_squadron_page = Signal(AircraftType)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, game: Game, coalition: Coalition, squadrons: list[Squadron]
|
self,
|
||||||
|
game: Game,
|
||||||
|
coalition: Coalition,
|
||||||
|
squadrons: list[Squadron],
|
||||||
|
parking_tracker: AirWingConfigParkingTracker,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
self.squadrons_config = SquadronConfigurationLayout(game, coalition, squadrons)
|
self.squadrons_config = SquadronConfigurationLayout(
|
||||||
|
game, coalition, squadrons, parking_tracker
|
||||||
|
)
|
||||||
self.squadrons_config.config_changed.connect(self.on_squadron_config_changed)
|
self.squadrons_config.config_changed.connect(self.on_squadron_config_changed)
|
||||||
|
|
||||||
scrolling_widget = QWidget()
|
scrolling_widget = QWidget()
|
||||||
@ -430,10 +505,16 @@ class AircraftSquadronsPage(QWidget):
|
|||||||
class AircraftSquadronsPanel(QStackedLayout):
|
class AircraftSquadronsPanel(QStackedLayout):
|
||||||
page_removed = Signal(AircraftType)
|
page_removed = Signal(AircraftType)
|
||||||
|
|
||||||
def __init__(self, game: Game, coalition: Coalition) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
game: Game,
|
||||||
|
coalition: Coalition,
|
||||||
|
parking_tracker: AirWingConfigParkingTracker,
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.game = game
|
self.game = game
|
||||||
self.coalition = coalition
|
self.coalition = coalition
|
||||||
|
self.parking_tracker = parking_tracker
|
||||||
self.squadrons_pages: dict[AircraftType, AircraftSquadronsPage] = {}
|
self.squadrons_pages: dict[AircraftType, AircraftSquadronsPage] = {}
|
||||||
for aircraft, squadrons in self.air_wing.squadrons.items():
|
for aircraft, squadrons in self.air_wing.squadrons.items():
|
||||||
self.new_page_for_type(aircraft, squadrons)
|
self.new_page_for_type(aircraft, squadrons)
|
||||||
@ -453,7 +534,9 @@ class AircraftSquadronsPanel(QStackedLayout):
|
|||||||
def new_page_for_type(
|
def new_page_for_type(
|
||||||
self, aircraft_type: AircraftType, squadrons: list[Squadron]
|
self, aircraft_type: AircraftType, squadrons: list[Squadron]
|
||||||
) -> None:
|
) -> None:
|
||||||
page = AircraftSquadronsPage(self.game, self.coalition, squadrons)
|
page = AircraftSquadronsPage(
|
||||||
|
self.game, self.coalition, squadrons, self.parking_tracker
|
||||||
|
)
|
||||||
page.remove_squadron_page.connect(self.remove_page_for_type)
|
page.remove_squadron_page.connect(self.remove_page_for_type)
|
||||||
self.addWidget(page)
|
self.addWidget(page)
|
||||||
self.squadrons_pages[aircraft_type] = page
|
self.squadrons_pages[aircraft_type] = page
|
||||||
@ -547,6 +630,9 @@ class AirWingConfigurationTab(QWidget):
|
|||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
self.game = game
|
self.game = game
|
||||||
self.coalition = coalition
|
self.coalition = coalition
|
||||||
|
self.parking_tracker = AirWingConfigParkingTracker(
|
||||||
|
coalition.air_wing.iter_squadrons()
|
||||||
|
)
|
||||||
|
|
||||||
self.type_list = AircraftTypeList(coalition.air_wing)
|
self.type_list = AircraftTypeList(coalition.air_wing)
|
||||||
|
|
||||||
@ -556,7 +642,9 @@ class AirWingConfigurationTab(QWidget):
|
|||||||
add_button.clicked.connect(lambda state: self.add_squadron())
|
add_button.clicked.connect(lambda state: self.add_squadron())
|
||||||
layout.addWidget(add_button, 2, 1, 1, 1)
|
layout.addWidget(add_button, 2, 1, 1, 1)
|
||||||
|
|
||||||
self.squadrons_panel = AircraftSquadronsPanel(game, coalition)
|
self.squadrons_panel = AircraftSquadronsPanel(
|
||||||
|
game, coalition, self.parking_tracker
|
||||||
|
)
|
||||||
self.squadrons_panel.page_removed.connect(self.type_list.remove_aircraft_type)
|
self.squadrons_panel.page_removed.connect(self.type_list.remove_aircraft_type)
|
||||||
layout.addLayout(self.squadrons_panel, 1, 3, 2, 1)
|
layout.addLayout(self.squadrons_panel, 1, 3, 2, 1)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user