Compare commits

...

12 Commits

Author SHA1 Message Date
dependabot[bot]
11c59a71d5 Bump tmp and msw in /client
Removes [tmp](https://github.com/raszi/node-tmp). It's no longer used after updating ancestor dependency [msw](https://github.com/mswjs/msw). These dependencies need to be updated together.


Removes `tmp`

Updates `msw` from 1.2.2 to 2.10.4
- [Release notes](https://github.com/mswjs/msw/releases)
- [Changelog](https://github.com/mswjs/msw/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mswjs/msw/compare/v1.2.2...v2.10.4)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 
  dependency-type: indirect
- dependency-name: msw
  dependency-version: 2.10.4
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-06 20:55:41 +00:00
dependabot[bot]
f6d536877b Bump on-headers and compression in /client (#3514)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/dcs-liberation/dcs_liberation/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-22 22:17:53 +10:00
dependabot[bot]
5f6bb8749e Bump electron from 22.3.25 to 28.3.2 in /client (#3513)
Bumps [electron](https://github.com/electron/electron) from 22.3.25 to
28.3.2.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="db688056d8"><code>db68805</code></a>
chore: cherry-pick 3 changes from 0-M125 (<a
href="https://redirect.github.com/electron/electron/issues/42221">#42221</a>)</li>
<li><a
href="cd25fbfd73"><code>cd25fbf</code></a>
chore: cherry-pick 1 changes from 0-M124 (<a
href="https://redirect.github.com/electron/electron/issues/41985">#41985</a>)</li>
<li><a
href="65d35309db"><code>65d3530</code></a>
fix: rename patches/devtools_frontend to patches/devtools-frontend (<a
href="https://redirect.github.com/electron/electron/issues/42212">#42212</a>)</li>
<li><a
href="1e054f3ef9"><code>1e054f3</code></a>
chore: cherry-pick b3c01ac1e60a from v8 (<a
href="https://redirect.github.com/electron/electron/issues/42176">#42176</a>)</li>
<li><a
href="c89546bc92"><code>c89546b</code></a>
chore: cherry-pick f320600cd1f4 from v8 (<a
href="https://redirect.github.com/electron/electron/issues/42124">#42124</a>)</li>
<li><a
href="19d3d66f46"><code>19d3d66</code></a>
chore: cherry-pick 19a4eebd05a7 from devtools-frontend (<a
href="https://redirect.github.com/electron/electron/issues/42103">#42103</a>)</li>
<li><a
href="196b6e66b8"><code>196b6e6</code></a>
chore: cherry-pick 1 changes from 3-M124 (<a
href="https://redirect.github.com/electron/electron/issues/42091">#42091</a>)</li>
<li><a
href="b5262e4700"><code>b5262e4</code></a>
chore: cherry-pick 013961609785 from chromium (<a
href="https://redirect.github.com/electron/electron/issues/42092">#42092</a>)</li>
<li><a
href="70cdc2e551"><code>70cdc2e</code></a>
chore: cherry-pick b2cc7b7ac538 from chromium (<a
href="https://redirect.github.com/electron/electron/issues/42098">#42098</a>)</li>
<li><a
href="b5eba900b5"><code>b5eba90</code></a>
chore: cherry-pick 8a3bfd4b7403 from devtools-frontend (<a
href="https://redirect.github.com/electron/electron/issues/42093">#42093</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/electron/electron/compare/v22.3.25...v28.3.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=electron&package-manager=npm_and_yarn&previous-version=22.3.25&new-version=28.3.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/dcs-liberation/dcs_liberation/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-12 08:16:28 +10:00
zhexu14
bcdd9e1807 Handle scenaio where flight that is not added to the ATO (flight in the process of being created) is deleted (#3512) 2025-06-30 19:53:10 +10:00
zhexu14
7b1c84d347 Recover combat instances from flight state when initializing aircraft simulation (#3511)
This PR recovers combat instances from the flight state when
initializing aircraft simulation. Flight states are serialized into a
game save but the state of the aircraft simulation is not. When
deserializing a game, the aircraft simulation would otherwise lose track
of the combat instances, leading the simulation never exiting combat in
some scenarios e.g. when at the mission IP.
2025-06-22 19:32:58 +10:00
zhexu14
9baebeebf8 Check if callsign is None before releasing to handle scenario where callsign was never generated correctly (#3510)
This PR is a workaround for modules where the standard callsigns (Ford,
Uzi etc) are not supported. Callsigns are not generated correctly and a
crash occurs when trying to release them if a package is released.
2025-06-14 18:15:48 +10:00
zhexu14
6da9dc7a49 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.
2025-06-14 08:16:56 +10:00
zhexu14
81d5f82090 pydcs update for DCS 2.9.16 (#3508) 2025-06-09 16:13:55 +10:00
zhexu14
b141320052 Remove package when last flight in package is cancelled (#3507)
This PR handles the scenario where the last flight in a package is
cancelled (deleted). Previously, the package would show "No mission".
With this PR, the package is cancelled (deleted).
2025-06-09 15:47:03 +10:00
zhexu14
3ed1f20233 Restore theater ground objects to full health when repairing via UI (#3506) 2025-06-07 21:47:04 +10:00
Starfire13
43be243711 Campaign inversion fix for Operation Vectron's Claw (#3505) 2025-06-05 21:59:32 +10:00
zhexu14
be5b201848 Update requirements.txt to latest pydcs version (#3502) 2025-05-04 21:18:47 +10:00
15 changed files with 680 additions and 1035 deletions

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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."""

View File

@@ -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(

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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