mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Add play/pause features to the sim.
There's no UI feedback for this yet other than the log messages. https://github.com/dcs-liberation/dcs_liberation/issues/1704
This commit is contained in:
parent
748d80ff3b
commit
87bf3110c8
@ -1,17 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
@ -19,6 +13,7 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from game.ato.flight import Flight
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.theater import Airfield, ControlPoint
|
||||
@ -27,16 +22,14 @@ from game.unitmap import (
|
||||
AirliftUnits,
|
||||
Building,
|
||||
ConvoyUnit,
|
||||
FlyingUnit,
|
||||
FrontLineUnit,
|
||||
GroundObjectUnit,
|
||||
UnitMap,
|
||||
FlyingUnit,
|
||||
)
|
||||
from game.ato.flight import Flight
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
from game.sim import MissionSimulation
|
||||
|
||||
DEBRIEFING_LOG_EXTENSION = "log"
|
||||
|
||||
@ -356,54 +349,3 @@ class Debriefing:
|
||||
|
||||
captures.append(BaseCaptureEvent(control_point, captured_by_player))
|
||||
return captures
|
||||
|
||||
|
||||
class PollDebriefingFileThread(threading.Thread):
|
||||
"""Thread class with a stop() method. The thread itself has to check
|
||||
regularly for the stopped() condition."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[Debriefing], None],
|
||||
mission_simulation: MissionSimulation,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._stop_event = threading.Event()
|
||||
self.callback = callback
|
||||
self.mission_sim = mission_simulation
|
||||
|
||||
def stop(self) -> None:
|
||||
self._stop_event.set()
|
||||
|
||||
def stopped(self) -> bool:
|
||||
return self._stop_event.is_set()
|
||||
|
||||
def run(self) -> None:
|
||||
if os.path.isfile("state.json"):
|
||||
last_modified = os.path.getmtime("state.json")
|
||||
else:
|
||||
last_modified = 0
|
||||
while not self.stopped():
|
||||
try:
|
||||
if (
|
||||
os.path.isfile("state.json")
|
||||
and os.path.getmtime("state.json") > last_modified
|
||||
):
|
||||
self.callback(
|
||||
self.mission_sim.debrief_current_state(Path("state.json"))
|
||||
)
|
||||
break
|
||||
except json.JSONDecodeError:
|
||||
logging.exception(
|
||||
"Failed to decode state.json. Probably attempted read while DCS "
|
||||
"was still writing the file. Will retry in 5 seconds."
|
||||
)
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def wait_for_debriefing(
|
||||
callback: Callable[[Debriefing], None], mission_simulation: MissionSimulation
|
||||
) -> PollDebriefingFileThread:
|
||||
thread = PollDebriefingFileThread(callback, mission_simulation)
|
||||
thread.start()
|
||||
return thread
|
||||
|
||||
56
game/polldebriefingfilethread.py
Normal file
56
game/polldebriefingfilethread.py
Normal file
@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from threading import Event, Thread
|
||||
from typing import Callable, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.debriefing import Debriefing
|
||||
from game.sim import MissionSimulation
|
||||
|
||||
|
||||
class PollDebriefingFileThread(Thread):
|
||||
"""Thread class with a stop() method. The thread itself has to check
|
||||
regularly for the stopped() condition."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[Debriefing], None],
|
||||
mission_sim: MissionSimulation,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._stop_event = Event()
|
||||
self.callback = callback
|
||||
self.mission_sim = mission_sim
|
||||
|
||||
def stop(self) -> None:
|
||||
self._stop_event.set()
|
||||
|
||||
def stopped(self) -> bool:
|
||||
return self._stop_event.is_set()
|
||||
|
||||
def run(self) -> None:
|
||||
if os.path.isfile("state.json"):
|
||||
last_modified = os.path.getmtime("state.json")
|
||||
else:
|
||||
last_modified = 0
|
||||
while not self.stopped():
|
||||
try:
|
||||
if (
|
||||
os.path.isfile("state.json")
|
||||
and os.path.getmtime("state.json") > last_modified
|
||||
):
|
||||
self.callback(
|
||||
self.mission_sim.debrief_current_state(Path("state.json"))
|
||||
)
|
||||
break
|
||||
except json.JSONDecodeError:
|
||||
logging.exception(
|
||||
"Failed to decode state.json. Probably attempted read while DCS "
|
||||
"was still writing the file. Will retry in 5 seconds."
|
||||
)
|
||||
time.sleep(5)
|
||||
@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
from collections import Iterator
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
@ -24,30 +23,17 @@ if TYPE_CHECKING:
|
||||
from game import Game
|
||||
|
||||
|
||||
TICK = timedelta(seconds=1)
|
||||
|
||||
|
||||
class AircraftSimulation:
|
||||
def __init__(self, game: Game) -> None:
|
||||
self.game = game
|
||||
self.time = self.game.conditions.start_time
|
||||
|
||||
def run(self) -> None:
|
||||
def begin_simulation(self) -> None:
|
||||
self.reset()
|
||||
self.set_initial_flight_states()
|
||||
if self.game.settings.fast_forward_to_first_contact:
|
||||
self.simulate_until_first_contact()
|
||||
logging.info(f"Mission simulation completed at {self.time}")
|
||||
|
||||
def simulate_until_first_contact(self) -> None:
|
||||
while True:
|
||||
self.time += TICK
|
||||
if self.tick():
|
||||
return
|
||||
|
||||
def tick(self) -> bool:
|
||||
def on_game_tick(self, time: datetime, duration: timedelta) -> bool:
|
||||
for flight in self.iter_flights():
|
||||
flight.on_game_tick(self.time, TICK)
|
||||
flight.on_game_tick(time, duration)
|
||||
|
||||
# Finish updating all flights before computing engagement zones so that the new
|
||||
# positions are used.
|
||||
@ -83,7 +69,6 @@ class AircraftSimulation:
|
||||
raise ValueError(f"Unknown start type {flight.start_type} for {flight}")
|
||||
|
||||
def reset(self) -> None:
|
||||
self.time = self.game.conditions.start_time
|
||||
for flight in self.iter_flights():
|
||||
flight.set_state(Uninitialized(flight, self.game.settings))
|
||||
|
||||
|
||||
73
game/sim/gameloop.py
Normal file
73
game/sim/gameloop.py
Normal file
@ -0,0 +1,73 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Callable, TYPE_CHECKING
|
||||
|
||||
from .gamelooptimer import GameLoopTimer
|
||||
from .missionsimulation import MissionSimulation, SimulationAlreadyCompletedError
|
||||
from .simspeedsetting import SimSpeedSetting
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
from game.debriefing import Debriefing
|
||||
|
||||
|
||||
class GameLoop:
|
||||
def __init__(self, game: Game, on_complete: Callable[[], None]) -> None:
|
||||
self.game = game
|
||||
self.on_complete = on_complete
|
||||
self.timer = GameLoopTimer(self.tick)
|
||||
self.sim = MissionSimulation(self.game)
|
||||
self.started = False
|
||||
self.completed = False
|
||||
|
||||
def start(self) -> None:
|
||||
if self.started:
|
||||
raise RuntimeError("Cannot start game loop because it has already started")
|
||||
self.started = True
|
||||
self.sim.begin_simulation()
|
||||
|
||||
def pause(self) -> None:
|
||||
self.set_simulation_speed(SimSpeedSetting.PAUSED)
|
||||
|
||||
def set_simulation_speed(self, simulation_speed: SimSpeedSetting) -> None:
|
||||
self.timer.stop()
|
||||
if simulation_speed != self.timer.simulation_speed:
|
||||
logging.info(f"Speed changed to {simulation_speed}")
|
||||
if not self.started:
|
||||
self.start()
|
||||
self.timer.set_speed(simulation_speed)
|
||||
|
||||
def run_to_first_contact(self) -> None:
|
||||
self.pause()
|
||||
logging.info("Running sim to first contact")
|
||||
while not self.completed:
|
||||
self.tick()
|
||||
|
||||
def pause_and_generate_miz(self, output: Path) -> None:
|
||||
self.pause()
|
||||
self.sim.generate_miz(output)
|
||||
|
||||
def pause_and_debrief(self, state_path: Path, force_end: bool) -> Debriefing:
|
||||
self.pause()
|
||||
return self.sim.debrief_current_state(state_path, force_end)
|
||||
|
||||
def complete_with_results(self, debriefing: Debriefing) -> None:
|
||||
self.pause()
|
||||
self.sim.process_results(debriefing)
|
||||
self.completed = True
|
||||
|
||||
def tick(self) -> None:
|
||||
if not self.started:
|
||||
raise RuntimeError("Attempted to tick game loop before initialization")
|
||||
try:
|
||||
self.completed = self.sim.tick()
|
||||
if self.completed:
|
||||
self.pause()
|
||||
logging.info(f"Simulation completed at {self.sim.time}")
|
||||
self.on_complete()
|
||||
else:
|
||||
logging.info(f"Simulation continued at {self.sim.time}")
|
||||
except SimulationAlreadyCompletedError:
|
||||
logging.exception("Attempted to tick already completed sim")
|
||||
40
game/sim/gamelooptimer.py
Normal file
40
game/sim/gamelooptimer.py
Normal file
@ -0,0 +1,40 @@
|
||||
from threading import Lock, Timer
|
||||
from typing import Callable, Optional
|
||||
|
||||
from .simspeedsetting import SimSpeedSetting
|
||||
|
||||
|
||||
class GameLoopTimer:
|
||||
def __init__(self, callback: Callable[[], None]) -> None:
|
||||
self.callback = callback
|
||||
self.simulation_speed = SimSpeedSetting.PAUSED
|
||||
self._timer: Optional[Timer] = None
|
||||
self._timer_lock = Lock()
|
||||
|
||||
def set_speed(self, simulation_speed: SimSpeedSetting) -> None:
|
||||
with self._timer_lock:
|
||||
self._stop()
|
||||
self.simulation_speed = simulation_speed
|
||||
self._recreate_timer()
|
||||
|
||||
def stop(self) -> None:
|
||||
with self._timer_lock:
|
||||
self._stop()
|
||||
|
||||
def _stop(self) -> None:
|
||||
if self._timer is not None:
|
||||
self._timer.cancel()
|
||||
|
||||
def _recreate_timer(self) -> None:
|
||||
self._stop()
|
||||
factor = self.simulation_speed.speed_factor
|
||||
if not factor:
|
||||
self._timer = None
|
||||
return None
|
||||
self._timer = Timer(1 / factor, self._tick)
|
||||
self._timer.start()
|
||||
|
||||
def _tick(self) -> None:
|
||||
self.callback()
|
||||
with self._timer_lock:
|
||||
self._recreate_timer()
|
||||
@ -1,29 +1,45 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from game.debriefing import Debriefing
|
||||
from game.missiongenerator import MissionGenerator
|
||||
from game.sim.aircraftsimulation import AircraftSimulation
|
||||
from game.sim.missionresultsprocessor import MissionResultsProcessor
|
||||
from game.unitmap import UnitMap
|
||||
from .aircraftsimulation import AircraftSimulation
|
||||
from .missionresultsprocessor import MissionResultsProcessor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
|
||||
|
||||
TICK = timedelta(seconds=1)
|
||||
|
||||
|
||||
class SimulationAlreadyCompletedError(RuntimeError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Simulation already completed")
|
||||
|
||||
|
||||
class MissionSimulation:
|
||||
def __init__(self, game: Game) -> None:
|
||||
self.game = game
|
||||
self.unit_map: Optional[UnitMap] = None
|
||||
self.time = game.conditions.start_time
|
||||
self.aircraft_simulation = AircraftSimulation(self.game)
|
||||
self.completed = False
|
||||
self.time = self.game.conditions.start_time
|
||||
|
||||
def run(self) -> None:
|
||||
sim = AircraftSimulation(self.game)
|
||||
sim.run()
|
||||
self.time = sim.time
|
||||
def begin_simulation(self) -> None:
|
||||
self.aircraft_simulation.begin_simulation()
|
||||
|
||||
def tick(self) -> bool:
|
||||
self.time += TICK
|
||||
if self.completed:
|
||||
raise RuntimeError("Simulation already completed")
|
||||
self.completed = self.aircraft_simulation.on_game_tick(self.time, TICK)
|
||||
return self.completed
|
||||
|
||||
def generate_miz(self, output: Path) -> None:
|
||||
self.unit_map = MissionGenerator(self.game, self.time).generate_miz(output)
|
||||
|
||||
28
game/sim/simspeedsetting.py
Normal file
28
game/sim/simspeedsetting.py
Normal file
@ -0,0 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum, unique
|
||||
|
||||
|
||||
@unique
|
||||
class SimSpeedSetting(Enum):
|
||||
PAUSED = (0, "Paused")
|
||||
X1 = (1, "1x")
|
||||
X2 = (2, "2x")
|
||||
X5 = (5, "5x")
|
||||
X10 = (10, "10x")
|
||||
X100 = (100, "100x")
|
||||
X1000 = (1000, "1000x")
|
||||
|
||||
def __init__(self, speed_factor: int, text: str) -> None:
|
||||
self.speed_factor = speed_factor
|
||||
self.text = text
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.text
|
||||
|
||||
@classmethod
|
||||
def from_factor(cls, speed_factor: int) -> SimSpeedSetting:
|
||||
for setting in SimSpeedSetting:
|
||||
if setting.speed_factor == speed_factor:
|
||||
return setting
|
||||
raise ValueError
|
||||
0
qt_ui/__init__.py
Normal file
0
qt_ui/__init__.py
Normal file
75
qt_ui/simcontroller.py
Normal file
75
qt_ui/simcontroller.py
Normal file
@ -0,0 +1,75 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional, TYPE_CHECKING
|
||||
|
||||
from PySide2.QtCore import QObject, Signal
|
||||
|
||||
from game.sim.gameloop import GameLoop
|
||||
from game.sim.simspeedsetting import SimSpeedSetting
|
||||
from game.polldebriefingfilethread import PollDebriefingFileThread
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
from game.debriefing import Debriefing
|
||||
|
||||
|
||||
class SimController(QObject):
|
||||
sim_speed_reset = Signal(SimSpeedSetting)
|
||||
simulation_complete = Signal()
|
||||
|
||||
def __init__(self, game: Optional[Game]) -> None:
|
||||
super().__init__()
|
||||
self.game_loop: Optional[GameLoop] = None
|
||||
self.recreate_game_loop(game)
|
||||
self.started = False
|
||||
|
||||
@property
|
||||
def completed(self) -> bool:
|
||||
return self.game_loop.completed
|
||||
|
||||
def set_game(self, game: Optional[Game]) -> None:
|
||||
self.recreate_game_loop(game)
|
||||
self.sim_speed_reset.emit(SimSpeedSetting.PAUSED)
|
||||
|
||||
def recreate_game_loop(self, game: Optional[Game]) -> None:
|
||||
if self.game_loop is not None:
|
||||
self.game_loop.pause()
|
||||
if game is not None:
|
||||
self.game_loop = GameLoop(game, self.on_simulation_complete)
|
||||
self.started = False
|
||||
|
||||
def set_simulation_speed(self, simulation_speed: SimSpeedSetting) -> None:
|
||||
if self.game_loop.completed and simulation_speed is not SimSpeedSetting.PAUSED:
|
||||
logging.debug("Cannot unpause sim: already complete")
|
||||
return
|
||||
if not self.started and simulation_speed is not SimSpeedSetting.PAUSED:
|
||||
self.game_loop.start()
|
||||
self.started = True
|
||||
self.game_loop.set_simulation_speed(simulation_speed)
|
||||
|
||||
def run_to_first_contact(self) -> None:
|
||||
self.game_loop.run_to_first_contact()
|
||||
|
||||
def generate_miz(self, output: Path) -> None:
|
||||
self.game_loop.pause_and_generate_miz(output)
|
||||
|
||||
def wait_for_debriefing(
|
||||
self, callback: Callable[[Debriefing], None]
|
||||
) -> PollDebriefingFileThread:
|
||||
thread = PollDebriefingFileThread(callback, self.game_loop.sim)
|
||||
thread.start()
|
||||
return thread
|
||||
|
||||
def debrief_current_state(
|
||||
self, state_path: Path, force_end: bool = False
|
||||
) -> Debriefing:
|
||||
return self.game_loop.pause_and_debrief(state_path, force_end)
|
||||
|
||||
def process_results(self, debriefing: Debriefing) -> None:
|
||||
return self.game_loop.complete_with_results(debriefing)
|
||||
|
||||
def on_simulation_complete(self) -> None:
|
||||
logging.debug("Simulation complete")
|
||||
self.simulation_complete.emit()
|
||||
@ -13,15 +13,16 @@ import qt_ui.uiconstants as CONST
|
||||
from game import Game, persistency
|
||||
from game.ato.package import Package
|
||||
from game.profiling import logged_duration
|
||||
from game.sim import MissionSimulation
|
||||
from game.utils import meters
|
||||
from gen.flights.traveltime import TotEstimator
|
||||
from qt_ui.models import GameModel
|
||||
from qt_ui.simcontroller import SimController
|
||||
from qt_ui.widgets.QBudgetBox import QBudgetBox
|
||||
from qt_ui.widgets.QConditionsWidget import QConditionsWidget
|
||||
from qt_ui.widgets.QFactionsInfos import QFactionsInfos
|
||||
from qt_ui.widgets.QIntelBox import QIntelBox
|
||||
from qt_ui.widgets.clientslots import MaxPlayerCount
|
||||
from qt_ui.widgets.simspeedcontrols import SimSpeedControls
|
||||
from qt_ui.windows.AirWingDialog import AirWingDialog
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
from qt_ui.windows.PendingTransfersDialog import PendingTransfersDialog
|
||||
@ -29,21 +30,14 @@ from qt_ui.windows.QWaitingForMissionResultWindow import QWaitingForMissionResul
|
||||
|
||||
|
||||
class QTopPanel(QFrame):
|
||||
def __init__(self, game_model: GameModel):
|
||||
def __init__(self, game_model: GameModel, sim_controller: SimController) -> None:
|
||||
super(QTopPanel, self).__init__()
|
||||
self.game_model = game_model
|
||||
self.sim_controller = sim_controller
|
||||
self.dialog: Optional[QDialog] = None
|
||||
|
||||
self.setMaximumHeight(70)
|
||||
self.init_ui()
|
||||
GameUpdateSignal.get_instance().gameupdated.connect(self.setGame)
|
||||
GameUpdateSignal.get_instance().budgetupdated.connect(self.budget_update)
|
||||
|
||||
@property
|
||||
def game(self) -> Optional[Game]:
|
||||
return self.game_model.game
|
||||
|
||||
def init_ui(self):
|
||||
self.conditionsWidget = QConditionsWidget()
|
||||
self.budgetBox = QBudgetBox(self.game)
|
||||
|
||||
@ -86,6 +80,7 @@ class QTopPanel(QFrame):
|
||||
|
||||
self.proceedBox = QGroupBox("Proceed")
|
||||
self.proceedBoxLayout = QHBoxLayout()
|
||||
self.proceedBoxLayout.addLayout(SimSpeedControls(sim_controller))
|
||||
self.proceedBoxLayout.addLayout(MaxPlayerCount(self.game_model.ato_model))
|
||||
self.proceedBoxLayout.addWidget(self.passTurnButton)
|
||||
self.proceedBoxLayout.addWidget(self.proceedButton)
|
||||
@ -105,6 +100,13 @@ class QTopPanel(QFrame):
|
||||
|
||||
self.setLayout(self.layout)
|
||||
|
||||
GameUpdateSignal.get_instance().gameupdated.connect(self.setGame)
|
||||
GameUpdateSignal.get_instance().budgetupdated.connect(self.budget_update)
|
||||
|
||||
@property
|
||||
def game(self) -> Optional[Game]:
|
||||
return self.game_model.game
|
||||
|
||||
def setGame(self, game: Optional[Game]):
|
||||
if game is None:
|
||||
return
|
||||
@ -277,11 +279,13 @@ class QTopPanel(QFrame):
|
||||
if not self.confirm_negative_start_time(negative_starts):
|
||||
return
|
||||
|
||||
sim = MissionSimulation(self.game)
|
||||
sim.run()
|
||||
sim.generate_miz(persistency.mission_path_for("liberation_nextturn.miz"))
|
||||
if self.game.settings.fast_forward_to_first_contact:
|
||||
self.sim_controller.run_to_first_contact()
|
||||
self.sim_controller.generate_miz(
|
||||
persistency.mission_path_for("liberation_nextturn.miz")
|
||||
)
|
||||
|
||||
waiting = QWaitingForMissionResultWindow(self.game, sim, self)
|
||||
waiting = QWaitingForMissionResultWindow(self.game, self.sim_controller, self)
|
||||
waiting.exec_()
|
||||
|
||||
def budget_update(self, game: Game):
|
||||
|
||||
35
qt_ui/widgets/simspeedcontrols.py
Normal file
35
qt_ui/widgets/simspeedcontrols.py
Normal file
@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from PySide2.QtWidgets import QButtonGroup, QHBoxLayout, QPushButton, QWidget
|
||||
|
||||
from game.sim.simspeedsetting import SimSpeedSetting
|
||||
from qt_ui.simcontroller import SimController
|
||||
|
||||
|
||||
class SimSpeedControls(QHBoxLayout):
|
||||
def __init__(
|
||||
self, sim_controller: SimController, parent: Optional[QWidget] = None
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.sim_controller = sim_controller
|
||||
self.button_group = QButtonGroup(self)
|
||||
self.buttons: dict[SimSpeedSetting, QPushButton] = {}
|
||||
|
||||
for speed_setting in SimSpeedSetting:
|
||||
button = QPushButton(speed_setting.text)
|
||||
button.setCheckable(True) # TODO: CSS
|
||||
self.button_group.addButton(button, id=speed_setting.speed_factor)
|
||||
self.addWidget(button)
|
||||
self.buttons[speed_setting] = button
|
||||
|
||||
self.button_group.idPressed.connect(self.speed_changed)
|
||||
self.sim_controller.sim_speed_reset.connect(self.on_sim_speed_reset)
|
||||
|
||||
def speed_changed(self, speed_factor: int) -> None:
|
||||
setting = SimSpeedSetting.from_factor(speed_factor)
|
||||
self.sim_controller.set_simulation_speed(setting)
|
||||
|
||||
def on_sim_speed_reset(self, speed_setting: SimSpeedSetting) -> None:
|
||||
self.buttons[speed_setting].setChecked(True)
|
||||
@ -23,6 +23,7 @@ from game.debriefing import Debriefing
|
||||
from qt_ui import liberation_install
|
||||
from qt_ui.dialogs import Dialog
|
||||
from qt_ui.models import GameModel
|
||||
from qt_ui.simcontroller import SimController
|
||||
from qt_ui.uiconstants import URLS
|
||||
from qt_ui.uncaughtexceptionhandler import UncaughtExceptionHandler
|
||||
from qt_ui.widgets.QTopPanel import QTopPanel
|
||||
@ -31,14 +32,14 @@ from qt_ui.widgets.map.QLiberationMap import QLiberationMap
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
from qt_ui.windows.QDebriefingWindow import QDebriefingWindow
|
||||
from qt_ui.windows.infos.QInfoPanel import QInfoPanel
|
||||
from qt_ui.windows.logs.QLogsWindow import QLogsWindow
|
||||
from qt_ui.windows.newgame.QNewGameWizard import NewGameWizard
|
||||
from qt_ui.windows.notes.QNotesWindow import QNotesWindow
|
||||
from qt_ui.windows.preferences.QLiberationPreferencesWindow import (
|
||||
QLiberationPreferencesWindow,
|
||||
)
|
||||
from qt_ui.windows.settings.QSettingsWindow import QSettingsWindow
|
||||
from qt_ui.windows.stats.QStatsWindow import QStatsWindow
|
||||
from qt_ui.windows.notes.QNotesWindow import QNotesWindow
|
||||
from qt_ui.windows.logs.QLogsWindow import QLogsWindow
|
||||
|
||||
|
||||
class QLiberationWindow(QMainWindow):
|
||||
@ -50,6 +51,7 @@ class QLiberationWindow(QMainWindow):
|
||||
self.game = game
|
||||
self.game_model = GameModel(game)
|
||||
Dialog.set_game(self.game_model)
|
||||
self.sim_controller = SimController(self.game)
|
||||
self.ato_panel = QAirTaskingOrderPanel(self.game_model)
|
||||
self.info_panel = QInfoPanel(self.game)
|
||||
self.liberation_map = QLiberationMap(self.game_model, self)
|
||||
@ -99,7 +101,7 @@ class QLiberationWindow(QMainWindow):
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
vbox.setMargin(0)
|
||||
vbox.addWidget(QTopPanel(self.game_model))
|
||||
vbox.addWidget(QTopPanel(self.game_model, self.sim_controller))
|
||||
vbox.addWidget(hbox)
|
||||
|
||||
central_widget = QWidget()
|
||||
@ -314,6 +316,7 @@ class QLiberationWindow(QMainWindow):
|
||||
self.game = game
|
||||
if self.info_panel is not None:
|
||||
self.info_panel.setGame(game)
|
||||
self.sim_controller.set_game(game)
|
||||
self.game_model.set(self.game)
|
||||
self.liberation_map.set_game(game)
|
||||
except AttributeError:
|
||||
|
||||
@ -22,10 +22,10 @@ from PySide2.QtWidgets import (
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
from game import Game
|
||||
from game.debriefing import Debriefing, wait_for_debriefing
|
||||
from game.debriefing import Debriefing
|
||||
from game.persistency import base_path
|
||||
from game.profiling import logged_duration
|
||||
from game.sim import MissionSimulation
|
||||
from qt_ui.simcontroller import SimController
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
|
||||
|
||||
@ -53,13 +53,13 @@ class QWaitingForMissionResultWindow(QDialog):
|
||||
def __init__(
|
||||
self,
|
||||
game: Game,
|
||||
mission_simulation: MissionSimulation,
|
||||
sim_controller: SimController,
|
||||
parent: Optional[QWidget] = None,
|
||||
) -> None:
|
||||
super(QWaitingForMissionResultWindow, self).__init__(parent=parent)
|
||||
self.setWindowModality(QtCore.Qt.WindowModal)
|
||||
self.game = game
|
||||
self.mission_sim = mission_simulation
|
||||
self.sim_controller = sim_controller
|
||||
self.setWindowTitle("Waiting for mission completion.")
|
||||
self.setWindowIcon(QIcon("./resources/icon.png"))
|
||||
self.setMinimumHeight(570)
|
||||
@ -68,8 +68,8 @@ class QWaitingForMissionResultWindow(QDialog):
|
||||
DebriefingFileWrittenSignal.get_instance().debriefingReceived.connect(
|
||||
self.updateLayout
|
||||
)
|
||||
self.wait_thread = wait_for_debriefing(
|
||||
lambda debriefing: self.on_debriefing_update(debriefing), self.mission_sim
|
||||
self.wait_thread = sim_controller.wait_for_debriefing(
|
||||
lambda debriefing: self.on_debriefing_update(debriefing)
|
||||
)
|
||||
|
||||
def initUi(self):
|
||||
@ -204,13 +204,13 @@ class QWaitingForMissionResultWindow(QDialog):
|
||||
DebriefingFileWrittenSignal.get_instance().sendDebriefing(debriefing)
|
||||
except Exception:
|
||||
logging.exception("Got an error while sending debriefing")
|
||||
self.wait_thread = wait_for_debriefing(
|
||||
lambda d: self.on_debriefing_update(d), self.mission_sim
|
||||
self.wait_thread = self.sim_controller.wait_for_debriefing(
|
||||
lambda d: self.on_debriefing_update(d)
|
||||
)
|
||||
|
||||
def process_debriefing(self):
|
||||
with logged_duration("Turn processing"):
|
||||
self.mission_sim.process_results(self.debriefing)
|
||||
self.sim_controller.process_results(self.debriefing)
|
||||
self.game.pass_turn()
|
||||
|
||||
GameUpdateSignal.get_instance().sendDebriefing(self.debriefing)
|
||||
@ -231,5 +231,5 @@ class QWaitingForMissionResultWindow(QDialog):
|
||||
)
|
||||
logging.debug("Processing manually submitted %s", file[0])
|
||||
self.on_debriefing_update(
|
||||
self.mission_sim.debrief_current_state(Path(file[0], force_end=True))
|
||||
self.sim_controller.debrief_current_state(Path(file[0], force_end=True))
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user