diff --git a/game/event/event.py b/game/event/event.py index a3aaeb96..d3bc1adb 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -126,7 +126,7 @@ class Event: not loss.pilot.player or not self.game.settings.invulnerable_player_pilots ): - loss.pilot.alive = False + loss.pilot.kill() aircraft = loss.flight.unit_type cp = loss.flight.departure available = cp.base.total_units_of_type(aircraft) diff --git a/game/squadrons.py b/game/squadrons.py index 5b87fba1..3444f24c 100644 --- a/game/squadrons.py +++ b/game/squadrons.py @@ -5,6 +5,7 @@ import logging import random from collections import defaultdict from dataclasses import dataclass, field +from enum import unique, Enum from pathlib import Path from typing import Type, Tuple, List, TYPE_CHECKING, Optional, Iterable, Iterator @@ -25,13 +26,41 @@ class PilotRecord: missions_flown: int = field(default=0) +@unique +class PilotStatus(Enum): + Active = "Active" + OnLeave = "On leave" + Dead = "Dead" + + @dataclass class Pilot: name: str player: bool = field(default=False) - alive: bool = field(default=True) + status: PilotStatus = field(default=PilotStatus.Active) record: PilotRecord = field(default_factory=PilotRecord) + @property + def alive(self) -> bool: + return self.status is not PilotStatus.Dead + + @property + def on_leave(self) -> bool: + return self.status is PilotStatus.OnLeave + + def send_on_leave(self) -> None: + if self.status is not PilotStatus.Active: + raise RuntimeError("Only active pilots may be sent on leave") + self.status = PilotStatus.OnLeave + + def return_from_leave(self) -> None: + if self.status is not PilotStatus.OnLeave: + raise RuntimeError("Only pilots on leave may be returned from leave") + self.status = PilotStatus.Active + + def kill(self) -> None: + self.status = PilotStatus.Dead + @classmethod def random(cls, faker: Faker) -> Pilot: return Pilot(faker.name()) @@ -119,13 +148,20 @@ class Squadron: def faker(self) -> Faker: return self.game.faker_for(self.player) + def _pilots_with_status(self, status: PilotStatus) -> list[Pilot]: + return [p for p in self.pilots if p.status == status] + @property def active_pilots(self) -> list[Pilot]: - return [p for p in self.pilots if p.alive] + return self._pilots_with_status(PilotStatus.Active) + + @property + def pilots_on_leave(self) -> list[Pilot]: + return self._pilots_with_status(PilotStatus.OnLeave) @property def size(self) -> int: - return len(self.active_pilots) + return len(self.active_pilots) + len(self.pilots_on_leave) def pilot_at_index(self, index: int) -> Pilot: return self.pilots[index] diff --git a/qt_ui/models.py b/qt_ui/models.py index df28f034..9887aa05 100644 --- a/qt_ui/models.py +++ b/qt_ui/models.py @@ -458,6 +458,15 @@ class SquadronModel(QAbstractListModel): pilot.player = not pilot.player self.endResetModel() + def toggle_leave_state(self, index: QModelIndex) -> None: + pilot = self.pilot_at_index(index) + self.beginResetModel() + if pilot.on_leave: + pilot.return_from_leave() + else: + pilot.send_on_leave() + self.endResetModel() + class GameModel: """A model for the Game object. diff --git a/qt_ui/windows/AirWingDialog.py b/qt_ui/windows/AirWingDialog.py index bf7e434d..17e4dcc1 100644 --- a/qt_ui/windows/AirWingDialog.py +++ b/qt_ui/windows/AirWingDialog.py @@ -41,8 +41,9 @@ class SquadronDelegate(TwoColumnRowDelegate): return self.squadron(index).nickname elif (row, column) == (1, 1): squadron = self.squadron(index) + active = len(squadron.active_pilots) available = len(squadron.available_pilots) - return f"{squadron.size} active pilots, {available} available" + return f"{squadron.size} pilots, {active} active, {available} unassigned" return "" diff --git a/qt_ui/windows/SquadronDialog.py b/qt_ui/windows/SquadronDialog.py index 5aac3f70..dc6d560d 100644 --- a/qt_ui/windows/SquadronDialog.py +++ b/qt_ui/windows/SquadronDialog.py @@ -12,6 +12,7 @@ from PySide2.QtWidgets import ( QListView, QVBoxLayout, QPushButton, + QHBoxLayout, ) from game.squadrons import Pilot @@ -39,7 +40,7 @@ class PilotDelegate(TwoColumnRowDelegate): elif (row, column) == (1, 0): return "Player" if pilot.player else "AI" elif (row, column) == (1, 1): - return "Alive" if pilot.alive else "Dead" + return pilot.status.value return "" @@ -80,11 +81,35 @@ class SquadronDialog(QDialog): ) layout.addWidget(self.pilot_list) + button_panel = QHBoxLayout() + button_panel.addStretch() + layout.addLayout(button_panel) + self.toggle_ai_button = QPushButton() - self.reset_button_state(self.pilot_list.currentIndex()) + self.reset_ai_toggle_state(self.pilot_list.currentIndex()) self.toggle_ai_button.setProperty("style", "start-button") self.toggle_ai_button.clicked.connect(self.toggle_ai) - layout.addWidget(self.toggle_ai_button, alignment=Qt.AlignRight) + button_panel.addWidget(self.toggle_ai_button, alignment=Qt.AlignRight) + + self.toggle_leave_button = QPushButton() + self.reset_leave_toggle_state(self.pilot_list.currentIndex()) + self.toggle_leave_button.setProperty("style", "start-button") + self.toggle_leave_button.clicked.connect(self.toggle_leave) + button_panel.addWidget(self.toggle_leave_button, alignment=Qt.AlignRight) + + def check_disabled_button_states( + self, button: QPushButton, index: QModelIndex + ) -> bool: + if not index.isValid(): + button.setText("No pilot selected") + button.setDisabled(True) + return True + pilot = self.squadron_model.pilot_at_index(index) + if not pilot.alive: + button.setText("Pilot is dead") + button.setDisabled(True) + return True + return False def toggle_ai(self) -> None: index = self.pilot_list.currentIndex() @@ -93,23 +118,38 @@ class SquadronDialog(QDialog): return self.squadron_model.toggle_ai_state(index) - def reset_button_state(self, index: QModelIndex) -> None: + def reset_ai_toggle_state(self, index: QModelIndex) -> None: + if self.check_disabled_button_states(self.toggle_ai_button, index): + return if not self.squadron_model.squadron.aircraft.flyable: self.toggle_ai_button.setText("Not flyable") self.toggle_ai_button.setDisabled(True) return - if not index.isValid(): - self.toggle_ai_button.setText("No pilot selected") - self.toggle_ai_button.setDisabled(True) - return self.toggle_ai_button.setEnabled(True) pilot = self.squadron_model.pilot_at_index(index) self.toggle_ai_button.setText( "Convert to AI" if pilot.player else "Convert to player" ) + def toggle_leave(self) -> None: + index = self.pilot_list.currentIndex() + if not index.isValid(): + logging.error("Cannot toggle on leave state: no pilot is selected") + return + self.squadron_model.toggle_leave_state(index) + + def reset_leave_toggle_state(self, index: QModelIndex) -> None: + if self.check_disabled_button_states(self.toggle_leave_button, index): + return + pilot = self.squadron_model.pilot_at_index(index) + self.toggle_leave_button.setEnabled(True) + self.toggle_leave_button.setText( + "Return from leave" if pilot.on_leave else "Send on leave" + ) + def on_selection_changed( self, selected: QItemSelection, _deselected: QItemSelection ) -> None: index = selected.indexes()[0] - self.reset_button_state(index) + self.reset_ai_toggle_state(index) + self.reset_leave_toggle_state(index)