Handle game over.

The contents are completely uninteresting, but at least it's visible.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/978.
This commit is contained in:
Dan Albert 2023-06-12 22:40:28 -07:00
parent 8f0ca08b89
commit 9d43eb8f03
9 changed files with 108 additions and 34 deletions

View File

@ -7,6 +7,7 @@ Saves from 7.x are not compatible with 8.0.
* **[Engine]** Support for DCS 2.8.6.41066, including the new Sinai map. * **[Engine]** Support for DCS 2.8.6.41066, including the new Sinai map.
* **[UI]** Limited size of overfull airbase display and added scrollbar. * **[UI]** Limited size of overfull airbase display and added scrollbar.
* **[UI]** Moved air wing and transfer menus to the toolbar to improve UI fit on low resolution displays. * **[UI]** Moved air wing and transfer menus to the toolbar to improve UI fit on low resolution displays.
* **[UI]** Added basic game over dialog.
## Fixes ## Fixes

View File

@ -5,7 +5,6 @@ import logging
import math import math
from collections.abc import Iterator from collections.abc import Iterator
from datetime import date, datetime, time, timedelta from datetime import date, datetime, time, timedelta
from enum import Enum
from typing import Any, List, TYPE_CHECKING, Type, Union, cast from typing import Any, List, TYPE_CHECKING, Type, Union, cast
from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers
@ -37,6 +36,7 @@ from .theater.theatergroundobject import (
) )
from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from .timeofday import TimeOfDay from .timeofday import TimeOfDay
from .turnstate import TurnState
from .weather.conditions import Conditions from .weather.conditions import Conditions
if TYPE_CHECKING: if TYPE_CHECKING:
@ -81,12 +81,6 @@ AWACS_BUDGET_COST = 4
PLAYER_BUDGET_IMPORTANCE_LOG = 2 PLAYER_BUDGET_IMPORTANCE_LOG = 2
class TurnState(Enum):
WIN = 0
LOSS = 1
CONTINUE = 2
class Game: class Game:
def __init__( def __init__(
self, self,

9
game/turnstate.py Normal file
View File

@ -0,0 +1,9 @@
from __future__ import annotations
from enum import Enum
class TurnState(Enum):
WIN = 0
LOSS = 1
CONTINUE = 2

28
qt_ui/cheatcontext.py Normal file
View File

@ -0,0 +1,28 @@
from __future__ import annotations
from collections.abc import Iterator
from contextlib import contextmanager
from typing import TYPE_CHECKING
from game.server import EventStream
from game.turnstate import TurnState
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.gameoverdialog import GameOverDialog
if TYPE_CHECKING:
from game import Game
from game.sim import GameUpdateEvents
@contextmanager
def game_state_modifying_cheat_context(game: Game) -> Iterator[GameUpdateEvents]:
with EventStream.event_context() as events:
yield events
state = game.check_win_loss()
if state is not TurnState.CONTINUE:
dialog = GameOverDialog(won=state is TurnState.WIN)
dialog.exec()
else:
game.initialize_turn(events)
GameUpdateSignal.get_instance().updateGame(game)

View File

@ -24,6 +24,7 @@ from game.persistence import SaveManager
from game.server import EventStream, GameContext from game.server import EventStream, GameContext
from game.server.dependencies import QtCallbacks, QtContext from game.server.dependencies import QtCallbacks, QtContext
from game.theater import ControlPoint, MissionTarget, TheaterGroundObject from game.theater import ControlPoint, MissionTarget, TheaterGroundObject
from game.turnstate import TurnState
from qt_ui import liberation_install from qt_ui import liberation_install
from qt_ui.dialogs import Dialog from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel from qt_ui.models import GameModel
@ -39,6 +40,7 @@ from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.PendingTransfersDialog import PendingTransfersDialog from qt_ui.windows.PendingTransfersDialog import PendingTransfersDialog
from qt_ui.windows.QDebriefingWindow import QDebriefingWindow from qt_ui.windows.QDebriefingWindow import QDebriefingWindow
from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2 from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2
from qt_ui.windows.gameoverdialog import GameOverDialog
from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
from qt_ui.windows.infos.QInfoPanel import QInfoPanel from qt_ui.windows.infos.QInfoPanel import QInfoPanel
from qt_ui.windows.logs.QLogsWindow import QLogsWindow from qt_ui.windows.logs.QLogsWindow import QLogsWindow
@ -559,8 +561,13 @@ class QLiberationWindow(QMainWindow):
logging.info("On Debriefing") logging.info("On Debriefing")
self.debriefing = QDebriefingWindow(debrief) self.debriefing = QDebriefingWindow(debrief)
self.debriefing.exec() self.debriefing.exec()
self.game.pass_turn()
GameUpdateSignal.get_instance().updateGame(self.game) state = self.game.check_win_loss()
if state is not TurnState.CONTINUE:
GameOverDialog(won=state is TurnState.WIN, parent=self).exec()
else:
self.game.pass_turn()
GameUpdateSignal.get_instance().updateGame(self.game)
def open_tgo_info_dialog(self, tgo: TheaterGroundObject) -> None: def open_tgo_info_dialog(self, tgo: TheaterGroundObject) -> None:
QGroundObjectMenu(self, tgo, tgo.control_point, self.game).show() QGroundObjectMenu(self, tgo, tgo.control_point, self.game).show()

View File

@ -9,19 +9,17 @@ from PySide6.QtWidgets import (
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
) )
from dcs.ships import Stennis, KUZNECOW
from game import Game from game import Game
from game.ato.flighttype import FlightType from game.ato.flighttype import FlightType
from game.config import RUNWAY_REPAIR_COST from game.config import RUNWAY_REPAIR_COST
from game.server import EventStream
from game.sim import GameUpdateEvents
from game.theater import ( from game.theater import (
AMMO_DEPOT_FRONTLINE_UNIT_CONTRIBUTION, AMMO_DEPOT_FRONTLINE_UNIT_CONTRIBUTION,
ControlPoint, ControlPoint,
ControlPointType, ControlPointType,
FREE_FRONTLINE_UNIT_SUPPLY, FREE_FRONTLINE_UNIT_SUPPLY,
) )
from qt_ui.cheatcontext import game_state_modifying_cheat_context
from qt_ui.dialogs import Dialog from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel from qt_ui.models import GameModel
from qt_ui.uiconstants import EVENT_ICONS from qt_ui.uiconstants import EVENT_ICONS
@ -119,13 +117,11 @@ class QBaseMenu2(QDialog):
return self.game_model.game.settings.enable_base_capture_cheat return self.game_model.game.settings.enable_base_capture_cheat
def cheat_capture(self) -> None: def cheat_capture(self) -> None:
events = GameUpdateEvents() with game_state_modifying_cheat_context(self.game_model.game) as events:
self.cp.capture(self.game_model.game, events, for_player=not self.cp.captured) self.cp.capture(
# Reinitialized ground planners and the like. The ATO needs to be reset because self.game_model.game, events, for_player=not self.cp.captured
# missions planned against the flipped base are no longer valid. )
self.game_model.game.initialize_turn(events) self.close()
EventStream.put_nowait(events)
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
@property @property
def has_transfer_destinations(self) -> bool: def has_transfer_destinations(self) -> bool:

View File

@ -3,10 +3,8 @@ from collections.abc import Callable
from PySide6.QtWidgets import QGroupBox, QLabel, QPushButton, QVBoxLayout from PySide6.QtWidgets import QGroupBox, QLabel, QPushButton, QVBoxLayout
from game import Game from game import Game
from game.server import EventStream
from game.sim.gameupdateevents import GameUpdateEvents
from game.theater import ControlPoint from game.theater import ControlPoint
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.cheatcontext import game_state_modifying_cheat_context
from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import ( from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import (
QGroundForcesStrategySelector, QGroundForcesStrategySelector,
) )
@ -52,15 +50,12 @@ class QGroundForcesStrategy(QGroupBox):
self.setLayout(layout) self.setLayout(layout)
def cheat_alter_front_line(self, enemy_point: ControlPoint, advance: bool) -> None: def cheat_alter_front_line(self, enemy_point: ControlPoint, advance: bool) -> None:
amount = 0.2 with game_state_modifying_cheat_context(self.game) as events:
if not advance: amount = 0.2
amount *= -1 if not advance:
self.cp.base.affect_strength(amount) amount *= -1
enemy_point.base.affect_strength(-amount) self.cp.base.affect_strength(amount)
front_line = self.cp.front_line_with(enemy_point) enemy_point.base.affect_strength(-amount)
front_line.update_position() front_line = self.cp.front_line_with(enemy_point)
events = GameUpdateEvents().update_front_line(front_line) front_line.update_position()
# Clear the ATO to replan missions affected by the front line. events.update_front_line(front_line)
self.game.initialize_turn(events)
EventStream.put_nowait(events)
GameUpdateSignal.get_instance().updateGame(self.game)

View File

@ -0,0 +1,43 @@
from __future__ import annotations
from PySide6.QtWidgets import (
QDialog,
QVBoxLayout,
QLabel,
QHBoxLayout,
QPushButton,
QWidget,
)
from qt_ui.windows.newgame.QNewGameWizard import NewGameWizard
class GameOverDialog(QDialog):
def __init__(self, won: bool, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setModal(True)
self.setWindowTitle("Game Over")
layout = QVBoxLayout()
self.setLayout(layout)
layout.addWidget(
QLabel(
f"<strong>You {'won' if won else 'lost'}!</strong><br />"
"<br />"
"Click below to start a new game."
)
)
button_row = QHBoxLayout()
layout.addLayout(button_row)
button_row.addStretch()
new_game = QPushButton("New Game")
new_game.clicked.connect(self.on_new_game)
button_row.addWidget(new_game)
def on_new_game(self) -> None:
wizard = NewGameWizard(self)
wizard.show()
wizard.accepted.connect(self.accept)

View File

@ -95,6 +95,7 @@ def wrap_label_text(text: str, width: int = 100) -> str:
class NewGameWizard(QtWidgets.QWizard): class NewGameWizard(QtWidgets.QWizard):
def __init__(self, parent=None): def __init__(self, parent=None):
super(NewGameWizard, self).__init__(parent) super(NewGameWizard, self).__init__(parent)
self.setModal(True)
# The wizard should probably be refactored to edit this directly, but for now we # The wizard should probably be refactored to edit this directly, but for now we
# just create a Settings object so that we can load the player's preserved # just create a Settings object so that we can load the player's preserved