mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Compare commits
12 Commits
zhexu14-pa
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11c59a71d5 | ||
|
|
f6d536877b | ||
|
|
5f6bb8749e | ||
|
|
bcdd9e1807 | ||
|
|
7b1c84d347 | ||
|
|
9baebeebf8 | ||
|
|
6da9dc7a49 | ||
|
|
81d5f82090 | ||
|
|
b141320052 | ||
|
|
3ed1f20233 | ||
|
|
43be243711 | ||
|
|
be5b201848 |
@@ -4,11 +4,14 @@ Saves from 13.x are not compatible with 14.0.0.
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for DCS 2.9.13.6818.
|
||||
* **[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]** A package is cancelled (deleted) when the last flight in the package is cancelled instead of showing "No mission".
|
||||
|
||||
# 13.0.0
|
||||
|
||||
|
||||
1601
client/package-lock.json
generated
1601
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -66,12 +66,12 @@
|
||||
"@types/leaflet": "^1.8.0",
|
||||
"@types/redux-logger": "^3.0.9",
|
||||
"@types/websocket": "^1.0.5",
|
||||
"electron": "^22.3.25",
|
||||
"electron": "^28.3.2",
|
||||
"electron-is-dev": "^2.0.0",
|
||||
"generate-license-file": "^2.0.0",
|
||||
"jest-transform-stub": "^2.0.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"msw": "^1.2.2",
|
||||
"msw": "^2.10.4",
|
||||
"react-scripts": "5.0.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"wait-on": "^8.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."""
|
||||
|
||||
@@ -7,9 +7,7 @@ from datetime import datetime, timedelta
|
||||
|
||||
from typing_extensions import TYPE_CHECKING
|
||||
|
||||
from game.ato.flightstate import (
|
||||
Uninitialized,
|
||||
)
|
||||
from game.ato.flightstate import Uninitialized, Completed, InCombat
|
||||
from game.settings.settings import FastForwardStopCondition, CombatResolutionMethod
|
||||
from .combat import CombatInitiator, FrozenCombat
|
||||
from .gameupdateevents import GameUpdateEvents
|
||||
@@ -27,7 +25,6 @@ class AircraftSimulation:
|
||||
self.results = SimulationResults()
|
||||
|
||||
def begin_simulation(self) -> None:
|
||||
self.reset()
|
||||
self.set_initial_flight_states()
|
||||
|
||||
def on_game_tick(
|
||||
@@ -72,14 +69,19 @@ class AircraftSimulation:
|
||||
events.complete_simulation()
|
||||
|
||||
def set_initial_flight_states(self) -> None:
|
||||
now = self.game.conditions.start_time
|
||||
# Initialize flights in Uninitialized state
|
||||
now = self.game.simulation_time
|
||||
for flight in self.iter_flights():
|
||||
flight.state.reinitialize(now)
|
||||
flight.state.initialize(now)
|
||||
|
||||
def reset(self) -> None:
|
||||
# Recover combat instances from flight states. Flight state information is serialized
|
||||
# when saving a game but the aircraft simulation state is not. Combat instances are
|
||||
# de-duplicated as multiple flights can be involved in a single combat instance.
|
||||
combats = set()
|
||||
for flight in self.iter_flights():
|
||||
flight.set_state(Uninitialized(flight, self.game.settings))
|
||||
self.combats = []
|
||||
if type(flight.state) == InCombat:
|
||||
combats.add(flight.state.combat)
|
||||
self.combats = list(combats)
|
||||
|
||||
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)
|
||||
|
||||
@@ -172,6 +172,17 @@ class PackageModel(QAbstractListModel):
|
||||
else:
|
||||
flight.abort()
|
||||
EventStream.put_nowait(GameUpdateEvents().update_flight(flight))
|
||||
# If there are no more flights in the package, delete the package.
|
||||
if len(self.package.flights) == 0:
|
||||
# Check if package is in ATO first as flight may still be "tentative" i.e.
|
||||
# not added to the ATO yet.
|
||||
if flight.package in flight.squadron.coalition.ato.packages:
|
||||
flight.squadron.coalition.ato.remove_package(flight.package)
|
||||
# Update ATO model so packages list is refreshed.
|
||||
is_player = flight.squadron.coalition.player
|
||||
self.game_model.ato_model_for(is_player).replace_from_game(
|
||||
is_player
|
||||
)
|
||||
|
||||
def delete_flight(self, flight: Flight) -> None:
|
||||
"""Removes the given flight from the package."""
|
||||
@@ -306,9 +317,12 @@ class AtoModel(QAbstractListModel):
|
||||
index = self.ato.packages.index(package)
|
||||
self.beginRemoveRows(QModelIndex(), index, index)
|
||||
for flight in package.flights:
|
||||
self.game_model.game.blue.callsign_generator.release_callsign(
|
||||
flight.callsign
|
||||
)
|
||||
# Check if flight.callsign is None to handle case where callsign was not generated.
|
||||
# This can happen when a module does not support the standard callsigns e.g. Kiowa.
|
||||
if flight.callsign is not None:
|
||||
self.game_model.game.blue.callsign_generator.release_callsign(
|
||||
flight.callsign
|
||||
)
|
||||
self.ato.remove_package(package)
|
||||
self.endRemoveRows()
|
||||
# noinspection PyUnresolvedReferences
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,6 +23,7 @@ from game.theater import ControlPoint, TheaterGroundObject
|
||||
from game.theater.theatergroundobject import (
|
||||
BuildingGroundObject,
|
||||
)
|
||||
from game.theater.theatergroup import TheaterUnit
|
||||
from game.utils import Heading
|
||||
from qt_ui.uiconstants import EVENT_ICONS, ICONS
|
||||
from qt_ui.widgets.QBudgetBox import QBudgetBox
|
||||
@@ -229,10 +230,11 @@ class QGroundObjectMenu(QDialog):
|
||||
if self.sell_all_button is not None:
|
||||
self.sell_all_button.setText("Disband (+$" + str(self.total_value) + "M)")
|
||||
|
||||
def repair_unit(self, unit, price):
|
||||
def repair_unit(self, unit: TheaterUnit, price: float):
|
||||
if self.game.blue.budget > price:
|
||||
self.game.blue.budget -= price
|
||||
unit.alive = True
|
||||
unit.hit_points = unit.unit_type.hit_points # Restore unit health to full
|
||||
GameUpdateSignal.get_instance().updateGame(self.game)
|
||||
|
||||
# Remove destroyed units in the vicinity
|
||||
|
||||
@@ -36,7 +36,7 @@ pre-commit==3.5.0
|
||||
pydantic==2.5.2
|
||||
pydantic-settings==2.1.0
|
||||
pydantic_core==2.14.5
|
||||
pydcs @ git+https://github.com/dcs-liberation/dcs@d1c8e9557aa1113908f9705fbe084e7cf08d6000
|
||||
pydcs @ git+https://github.com/dcs-liberation/dcs@38989596a8ec20d4095b9d84541d7a7bafa36704
|
||||
pyinstaller==5.13.1
|
||||
pyinstaller-hooks-contrib==2023.6
|
||||
pyproj==3.6.1
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user