mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Persist fast forward in save game file (#3509)
This PR allows persisting of the game state, in particular the flight state and simulation time, after a fast forward.
This commit is contained in:
parent
81d5f82090
commit
6da9dc7a49
@ -5,11 +5,12 @@ Saves from 13.x are not compatible with 14.0.0.
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for DCS 2.9.16.10973
|
||||
* **[UI]** Allow saving after fast forwarding manually with sim speed controls (--show-sim-speed-controls option).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Campaign]** Units are restored to full health when repaired.
|
||||
* **[UI]** Air Wing and Transfers buttons disabled when no game is loaded as pressing them without a game loaded resulted in a crash.
|
||||
* **[UI]** Units are restored to full health when repaired.
|
||||
* **[UI]** A package is cancelled (deleted) when the last flight in the package is cancelled instead of showing "No mission".
|
||||
|
||||
# 13.0.0
|
||||
|
||||
@ -109,19 +109,6 @@ class Flight(SidcDescribable):
|
||||
waypoint.actions.clear()
|
||||
waypoint.options.clear()
|
||||
|
||||
def __getstate__(self) -> dict[str, Any]:
|
||||
state = self.__dict__.copy()
|
||||
# Avoid persisting the flight state since that's not (currently) used outside
|
||||
# mission generation. This is a bit of a hack for the moment and in the future
|
||||
# we will need to persist the flight state, but for now keep it out of save
|
||||
# compat (it also contains a generator that cannot be pickled).
|
||||
del state["state"]
|
||||
return state
|
||||
|
||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||
state["state"] = Uninitialized(self, state["squadron"].settings)
|
||||
self.__dict__.update(state)
|
||||
|
||||
@property
|
||||
def blue(self) -> bool:
|
||||
return self.squadron.player
|
||||
|
||||
@ -21,8 +21,14 @@ class FlightState(ABC):
|
||||
self.settings = settings
|
||||
self.avoid_further_combat = False
|
||||
|
||||
def reinitialize(self, now: datetime) -> None:
|
||||
from game.ato.flightstate import WaitingForStart
|
||||
def initialize(self, now: datetime) -> None:
|
||||
from game.ato.flightstate import Uninitialized, WaitingForStart
|
||||
|
||||
# Flight objects are created with Uninitialized state. However when the simulation runs
|
||||
# the flight state changes and may be serialized. We only want to initialize the state
|
||||
# for newly created flights and not ones deserialized from a save file.
|
||||
if type(self.flight.state) != Uninitialized:
|
||||
return
|
||||
|
||||
if self.flight.flight_plan.startup_time() <= now:
|
||||
self._set_active_flight_state(now)
|
||||
|
||||
@ -20,7 +20,7 @@ class Uninitialized(FlightState):
|
||||
def on_game_tick(
|
||||
self, events: GameUpdateEvents, time: datetime, duration: timedelta
|
||||
) -> None:
|
||||
self.reinitialize(time)
|
||||
self.initialize(time)
|
||||
self.flight.state.on_game_tick(events, time, duration)
|
||||
|
||||
@property
|
||||
|
||||
@ -125,7 +125,11 @@ class Game:
|
||||
self.time_of_day_offset_for_start_time = list(TimeOfDay).index(
|
||||
self.theater.daytime_map.best_guess_time_of_day_at(start_time)
|
||||
)
|
||||
# self.conditions.start_time is the time at the start of a turn and does not change within a turn.
|
||||
# self.simulation_time tracks time progression within a turn and is synchronized with the
|
||||
# MissionSimulation object.
|
||||
self.conditions = self.generate_conditions(forced_time=start_time)
|
||||
self.simulation_time = self.conditions.start_time
|
||||
|
||||
self.sanitize_sides(player_faction, enemy_faction)
|
||||
self.blue = Coalition(self, player_faction, player_budget, player=True)
|
||||
@ -291,6 +295,7 @@ class Game:
|
||||
# turn 1.
|
||||
if self.turn > 1:
|
||||
self.conditions = self.generate_conditions()
|
||||
self.simulation_time = self.conditions.start_time
|
||||
|
||||
def begin_turn_0(self) -> None:
|
||||
"""Initialization for the first turn of the game."""
|
||||
|
||||
@ -27,7 +27,6 @@ class AircraftSimulation:
|
||||
self.results = SimulationResults()
|
||||
|
||||
def begin_simulation(self) -> None:
|
||||
self.reset()
|
||||
self.set_initial_flight_states()
|
||||
|
||||
def on_game_tick(
|
||||
@ -72,14 +71,9 @@ class AircraftSimulation:
|
||||
events.complete_simulation()
|
||||
|
||||
def set_initial_flight_states(self) -> None:
|
||||
now = self.game.conditions.start_time
|
||||
now = self.game.simulation_time
|
||||
for flight in self.iter_flights():
|
||||
flight.state.reinitialize(now)
|
||||
|
||||
def reset(self) -> None:
|
||||
for flight in self.iter_flights():
|
||||
flight.set_state(Uninitialized(flight, self.game.settings))
|
||||
self.combats = []
|
||||
flight.state.initialize(now)
|
||||
|
||||
def iter_flights(self) -> Iterator[Flight]:
|
||||
packages = itertools.chain(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
@ -31,14 +31,15 @@ class MissionSimulation:
|
||||
self.unit_map: Optional[UnitMap] = None
|
||||
self.aircraft_simulation = AircraftSimulation(self.game)
|
||||
self.completed = False
|
||||
self.time = self.game.conditions.start_time
|
||||
self.time = self.game.simulation_time
|
||||
|
||||
def begin_simulation(self) -> None:
|
||||
self.time = self.game.conditions.start_time
|
||||
self.time = self.game.simulation_time
|
||||
self.aircraft_simulation.begin_simulation()
|
||||
|
||||
def tick(self, events: GameUpdateEvents) -> GameUpdateEvents:
|
||||
self.time += TICK
|
||||
self.game.simulation_time = self.time
|
||||
if self.completed:
|
||||
raise RuntimeError("Simulation already completed")
|
||||
self.aircraft_simulation.on_game_tick(events, self.time, TICK)
|
||||
|
||||
@ -65,14 +65,17 @@ class QTimeTurnWidget(QGroupBox):
|
||||
else:
|
||||
self.set_date_and_time(time)
|
||||
|
||||
def set_current_turn(self, turn: int, conditions: Conditions) -> None:
|
||||
def set_current_turn(
|
||||
self, turn: int, conditions: Conditions, time: datetime
|
||||
) -> None:
|
||||
"""Sets the turn information display.
|
||||
|
||||
:arg turn Current turn number.
|
||||
:arg conditions Current time and weather conditions.
|
||||
:arg conditions Current weather conditions.
|
||||
:arg time Current time.
|
||||
"""
|
||||
self.daytime_icon.setPixmap(self.icons[conditions.time_of_day])
|
||||
self.set_date_and_time(conditions.start_time)
|
||||
self.set_date_and_time(time)
|
||||
self.setTitle(f"Turn {turn}")
|
||||
|
||||
def set_date_and_time(self, time: datetime) -> None:
|
||||
@ -305,12 +308,15 @@ class QConditionsWidget(QFrame):
|
||||
self.weather_widget.hide()
|
||||
self.layout.addWidget(self.weather_widget, 0, 1)
|
||||
|
||||
def setCurrentTurn(self, turn: int, conditions: Conditions) -> None:
|
||||
def setCurrentTurn(
|
||||
self, turn: int, conditions: Conditions, time: datetime | None
|
||||
) -> None:
|
||||
"""Sets the turn information display.
|
||||
|
||||
:arg turn Current turn number.
|
||||
:arg conditions Current time and weather conditions.
|
||||
:arg conditions Current weather conditions.
|
||||
:arg time Current time.
|
||||
"""
|
||||
self.time_turn_widget.set_current_turn(turn, conditions)
|
||||
self.time_turn_widget.set_current_turn(turn, conditions, time)
|
||||
self.weather_widget.setCurrentTurn(turn, conditions)
|
||||
self.weather_widget.show()
|
||||
|
||||
@ -104,7 +104,9 @@ class QTopPanel(QFrame):
|
||||
if game is None:
|
||||
return
|
||||
|
||||
self.conditionsWidget.setCurrentTurn(game.turn, game.conditions)
|
||||
self.conditionsWidget.setCurrentTurn(
|
||||
game.turn, game.conditions, game.simulation_time
|
||||
)
|
||||
|
||||
if game.conditions.weather.clouds:
|
||||
base_m = game.conditions.weather.clouds.base
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user