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.
* **[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]** Added basic game over dialog.
## Fixes

View File

@ -5,7 +5,6 @@ import logging
import math
from collections.abc import Iterator
from datetime import date, datetime, time, timedelta
from enum import Enum
from typing import Any, List, TYPE_CHECKING, Type, Union, cast
from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers
@ -37,6 +36,7 @@ from .theater.theatergroundobject import (
)
from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from .timeofday import TimeOfDay
from .turnstate import TurnState
from .weather.conditions import Conditions
if TYPE_CHECKING:
@ -81,12 +81,6 @@ AWACS_BUDGET_COST = 4
PLAYER_BUDGET_IMPORTANCE_LOG = 2
class TurnState(Enum):
WIN = 0
LOSS = 1
CONTINUE = 2
class Game:
def __init__(
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.dependencies import QtCallbacks, QtContext
from game.theater import ControlPoint, MissionTarget, TheaterGroundObject
from game.turnstate import TurnState
from qt_ui import liberation_install
from qt_ui.dialogs import Dialog
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.QDebriefingWindow import QDebriefingWindow
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.infos.QInfoPanel import QInfoPanel
from qt_ui.windows.logs.QLogsWindow import QLogsWindow
@ -559,8 +561,13 @@ class QLiberationWindow(QMainWindow):
logging.info("On Debriefing")
self.debriefing = QDebriefingWindow(debrief)
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:
QGroundObjectMenu(self, tgo, tgo.control_point, self.game).show()

View File

@ -9,19 +9,17 @@ from PySide6.QtWidgets import (
QVBoxLayout,
QWidget,
)
from dcs.ships import Stennis, KUZNECOW
from game import Game
from game.ato.flighttype import FlightType
from game.config import RUNWAY_REPAIR_COST
from game.server import EventStream
from game.sim import GameUpdateEvents
from game.theater import (
AMMO_DEPOT_FRONTLINE_UNIT_CONTRIBUTION,
ControlPoint,
ControlPointType,
FREE_FRONTLINE_UNIT_SUPPLY,
)
from qt_ui.cheatcontext import game_state_modifying_cheat_context
from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel
from qt_ui.uiconstants import EVENT_ICONS
@ -119,13 +117,11 @@ class QBaseMenu2(QDialog):
return self.game_model.game.settings.enable_base_capture_cheat
def cheat_capture(self) -> None:
events = GameUpdateEvents()
self.cp.capture(self.game_model.game, events, for_player=not self.cp.captured)
# Reinitialized ground planners and the like. The ATO needs to be reset because
# missions planned against the flipped base are no longer valid.
self.game_model.game.initialize_turn(events)
EventStream.put_nowait(events)
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
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.close()
@property
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 game import Game
from game.server import EventStream
from game.sim.gameupdateevents import GameUpdateEvents
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 (
QGroundForcesStrategySelector,
)
@ -52,15 +50,12 @@ class QGroundForcesStrategy(QGroupBox):
self.setLayout(layout)
def cheat_alter_front_line(self, enemy_point: ControlPoint, advance: bool) -> None:
amount = 0.2
if not advance:
amount *= -1
self.cp.base.affect_strength(amount)
enemy_point.base.affect_strength(-amount)
front_line = self.cp.front_line_with(enemy_point)
front_line.update_position()
events = GameUpdateEvents().update_front_line(front_line)
# Clear the ATO to replan missions affected by the front line.
self.game.initialize_turn(events)
EventStream.put_nowait(events)
GameUpdateSignal.get_instance().updateGame(self.game)
with game_state_modifying_cheat_context(self.game) as events:
amount = 0.2
if not advance:
amount *= -1
self.cp.base.affect_strength(amount)
enemy_point.base.affect_strength(-amount)
front_line = self.cp.front_line_with(enemy_point)
front_line.update_position()
events.update_front_line(front_line)

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):
def __init__(self, parent=None):
super(NewGameWizard, self).__init__(parent)
self.setModal(True)
# 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