diff --git a/game/debriefing.py b/game/debriefing.py
index 6bf15569..09a76958 100644
--- a/game/debriefing.py
+++ b/game/debriefing.py
@@ -1,176 +1,209 @@
+from __future__ import annotations
+
import json
import logging
import os
import threading
import time
-import typing
+from collections import defaultdict
+from dataclasses import dataclass
+from typing import Any, Callable, Dict, List, Type, TYPE_CHECKING
+
+from dcs.unittype import FlyingType, UnitType
from game import db
+from game.theater import TheaterGroundObject
+from game.unitmap import UnitMap
+from gen.flights.flight import Flight
+
+if TYPE_CHECKING:
+ from game import Game
DEBRIEFING_LOG_EXTENSION = "log"
+
+@dataclass(frozen=True)
class DebriefingDeadUnitInfo:
- country_id = -1
- player_unit = False
- type = None
+ player_unit: bool
+ type: Type[UnitType]
- def __init__(self, country_id, player_unit , type):
- self.country_id = country_id
- self.player_unit = player_unit
- self.type = type
- def __repr__(self):
- return str(self.country_id) + " " + str(self.player_unit) + " " + str(self.type)
+@dataclass(frozen=True)
+class DebriefingDeadAircraftInfo:
+ #: The Flight that resulted in the generated unit.
+ flight: Flight
+
+ @property
+ def player_unit(self) -> bool:
+ return self.flight.departure.captured
+
+
+@dataclass(frozen=True)
+class DebriefingDeadBuildingInfo:
+ #: The ground object this building was present at.
+ ground_object: TheaterGroundObject
+
+ @property
+ def player_unit(self) -> bool:
+ return self.ground_object.control_point.captured
+
+
+@dataclass(frozen=True)
+class AirLosses:
+ losses: List[DebriefingDeadAircraftInfo]
+
+ def by_type(self, player: bool) -> Dict[Type[FlyingType], int]:
+ losses_by_type: Dict[Type[FlyingType], int] = defaultdict(int)
+ for loss in self.losses:
+ if loss.flight.departure.captured != player:
+ continue
+
+ losses_by_type[loss.flight.unit_type] += loss.flight.count
+ return losses_by_type
+
+
+@dataclass(frozen=True)
+class StateData:
+ #: True if the mission ended. If False, the mission exited abnormally.
+ mission_ended: bool
+
+ #: Names of aircraft units that were killed during the mission.
+ killed_aircraft: List[str]
+
+ #: Names of vehicle (and ship) units that were killed during the mission.
+ killed_ground_units: List[str]
+
+ #: Names of static units that were destroyed during the mission.
+ destroyed_statics: List[str]
+
+ #: Mangled names of bases that were captured during the mission.
+ base_capture_events: List[str]
+
+ @classmethod
+ def from_json(cls, data: Dict[str, Any]) -> StateData:
+ return cls(
+ mission_ended=data["mission_ended"],
+ killed_aircraft=data["killed_aircrafts"],
+ killed_ground_units=data["killed_ground_units"],
+ destroyed_statics=data["destroyed_objects_positions"],
+ base_capture_events=data["base_capture_events"]
+ )
+
class Debriefing:
- def __init__(self, state_data, game):
- self.state_data = state_data
- self.killed_aircrafts = state_data["killed_aircrafts"]
- self.killed_ground_units = state_data["killed_ground_units"]
- self.weapons_fired = state_data["weapons_fired"]
- self.mission_ended = state_data["mission_ended"]
- self.destroyed_units = state_data["destroyed_objects_positions"]
+ def __init__(self, state_data: Dict[str, Any], game: Game,
+ unit_map: UnitMap) -> None:
+ self.state_data = StateData.from_json(state_data)
+ self.game = game
+ self.unit_map = unit_map
- self.__destroyed_units = []
logging.info("--------------------------------")
logging.info("Starting Debriefing preprocessing")
logging.info("--------------------------------")
- logging.info(self.base_capture_events)
- logging.info(self.killed_aircrafts)
- logging.info(self.killed_ground_units)
- logging.info(self.weapons_fired)
- logging.info(self.mission_ended)
- logging.info(self.destroyed_units)
+ logging.info(self.state_data)
logging.info("--------------------------------")
self.player_country_id = db.country_id_from_name(game.player_country)
self.enemy_country_id = db.country_id_from_name(game.enemy_country)
- self.dead_aircraft = []
- self.dead_units = []
- self.dead_aaa_groups = []
- self.dead_buildings = []
+ self.air_losses = self.dead_aircraft()
+ self.dead_units: List[DebriefingDeadUnitInfo] = []
+ self.dead_aaa_groups: List[DebriefingDeadUnitInfo] = []
+ self.dead_buildings: List[DebriefingDeadBuildingInfo] = []
- for aircraft in self.killed_aircrafts:
+ for unit_name in self.state_data.killed_ground_units:
try:
- country = int(aircraft.split("|")[1])
- type = db.unit_type_from_name(aircraft.split("|")[4])
- player_unit = (country == self.player_country_id)
- aircraft = DebriefingDeadUnitInfo(country, player_unit, type)
- if type is not None:
- self.dead_aircraft.append(aircraft)
- except Exception as e:
- logging.error(e)
+ if isinstance(unit_name, int):
+ # For some reason the state file will include many raw
+ # integers in the list of destroyed units. These might be
+ # from the smoke effects?
+ continue
+ country = int(unit_name.split("|")[1])
+ unit_type = db.unit_type_from_name(unit_name.split("|")[4])
+ if unit_type is None:
+ logging.error(f"Could not determine type of {unit_name}")
+ continue
+ player_unit = country == self.player_country_id
+ self.dead_units.append(
+ DebriefingDeadUnitInfo(player_unit, unit_type))
+ except Exception:
+ logging.exception(f"Failed to process dead unit {unit_name}")
- for unit in self.killed_ground_units:
- try:
- country = int(unit.split("|")[1])
- type = db.unit_type_from_name(unit.split("|")[4])
- player_unit = (country == self.player_country_id)
- unit = DebriefingDeadUnitInfo(country, player_unit, type)
- if type is not None:
- self.dead_units.append(unit)
- except Exception as e:
- logging.error(e)
-
- for unit in self.killed_ground_units:
+ for unit_name in self.state_data.killed_ground_units:
for cp in game.theater.controlpoints:
-
- logging.info(cp.name)
- logging.info(cp.captured)
-
if cp.captured:
country = self.player_country_id
else:
country = self.enemy_country_id
player_unit = (country == self.player_country_id)
- for i, ground_object in enumerate(cp.ground_objects):
- logging.info(unit)
- logging.info(ground_object.group_name)
- if ground_object.is_same_group(unit):
- unit = DebriefingDeadUnitInfo(country, player_unit, ground_object.dcs_identifier)
- self.dead_buildings.append(unit)
- elif ground_object.dcs_identifier in ["AA", "CARRIER", "LHA"]:
+ for ground_object in cp.ground_objects:
+ # TODO: This seems to destroy an arbitrary building?
+ if ground_object.is_same_group(unit_name):
+ self.dead_buildings.append(
+ DebriefingDeadBuildingInfo(ground_object))
+ elif ground_object.dcs_identifier in ["AA", "CARRIER",
+ "LHA"]:
for g in ground_object.groups:
for u in g.units:
- if u.name == unit:
- unit = DebriefingDeadUnitInfo(country, player_unit, db.unit_type_from_name(u.type))
- self.dead_units.append(unit)
+ if u.name != unit_name:
+ continue
+ unit_type = db.unit_type_from_name(u.type)
+ if unit_type is None:
+ logging.error(
+ f"Could not determine type of %s",
+ unit_name)
+ continue
+ self.dead_units.append(DebriefingDeadUnitInfo(
+ player_unit, unit_type))
- self.player_dead_aircraft = [a for a in self.dead_aircraft if a.country_id == self.player_country_id]
- self.enemy_dead_aircraft = [a for a in self.dead_aircraft if a.country_id == self.enemy_country_id]
- self.player_dead_units = [a for a in self.dead_units if a.country_id == self.player_country_id]
- self.enemy_dead_units = [a for a in self.dead_units if a.country_id == self.enemy_country_id]
- self.player_dead_buildings = [a for a in self.dead_buildings if a.country_id == self.player_country_id]
- self.enemy_dead_buildings = [a for a in self.dead_buildings if a.country_id == self.enemy_country_id]
+ self.player_dead_units = [a for a in self.dead_units if a.player_unit]
+ self.enemy_dead_units = [a for a in self.dead_units if not a.player_unit]
+ self.player_dead_buildings = [a for a in self.dead_buildings if a.player_unit]
+ self.enemy_dead_buildings = [a for a in self.dead_buildings if not a.player_unit]
- logging.info(self.player_dead_aircraft)
- logging.info(self.enemy_dead_aircraft)
- logging.info(self.player_dead_units)
- logging.info(self.enemy_dead_units)
-
- self.player_dead_aircraft_dict = {}
- for a in self.player_dead_aircraft:
- if a.type in self.player_dead_aircraft_dict.keys():
- self.player_dead_aircraft_dict[a.type] = self.player_dead_aircraft_dict[a.type] + 1
- else:
- self.player_dead_aircraft_dict[a.type] = 1
-
- self.enemy_dead_aircraft_dict = {}
- for a in self.enemy_dead_aircraft:
- if a.type in self.enemy_dead_aircraft_dict.keys():
- self.enemy_dead_aircraft_dict[a.type] = self.enemy_dead_aircraft_dict[a.type] + 1
- else:
- self.enemy_dead_aircraft_dict[a.type] = 1
-
- self.player_dead_units_dict = {}
+ self.player_dead_units_dict: Dict[Type[UnitType], int] = defaultdict(int)
for a in self.player_dead_units:
- if a.type in self.player_dead_units_dict.keys():
- self.player_dead_units_dict[a.type] = self.player_dead_units_dict[a.type] + 1
- else:
- self.player_dead_units_dict[a.type] = 1
+ self.player_dead_units_dict[a.type] += 1
- self.enemy_dead_units_dict = {}
+ self.enemy_dead_units_dict: Dict[Type[UnitType], int] = defaultdict(int)
for a in self.enemy_dead_units:
- if a.type in self.enemy_dead_units_dict.keys():
- self.enemy_dead_units_dict[a.type] = self.enemy_dead_units_dict[a.type] + 1
- else:
- self.enemy_dead_units_dict[a.type] = 1
+ self.enemy_dead_units_dict[a.type] += 1
- self.player_dead_buildings_dict = {}
- for a in self.player_dead_buildings:
- if a.type in self.player_dead_buildings_dict.keys():
- self.player_dead_buildings_dict[a.type] = self.player_dead_buildings_dict[a.type] + 1
- else:
- self.player_dead_buildings_dict[a.type] = 1
+ self.player_dead_buildings_dict: Dict[str, int] = defaultdict(int)
+ for b in self.player_dead_buildings:
+ self.player_dead_buildings_dict[b.ground_object.dcs_identifier] += 1
- self.enemy_dead_buildings_dict = {}
- for a in self.enemy_dead_buildings:
- if a.type in self.enemy_dead_buildings_dict.keys():
- self.enemy_dead_buildings_dict[a.type] = self.enemy_dead_buildings_dict[a.type] + 1
- else:
- self.enemy_dead_buildings_dict[a.type] = 1
+ self.enemy_dead_buildings_dict: Dict[str, int] = defaultdict(int)
+ for b in self.enemy_dead_buildings:
+ self.enemy_dead_buildings_dict[b.ground_object.dcs_identifier] += 1
logging.info("--------------------------------")
logging.info("Debriefing pre process results :")
logging.info("--------------------------------")
- logging.info(self.player_dead_aircraft_dict)
- logging.info(self.enemy_dead_aircraft_dict)
+ logging.info(self.air_losses)
logging.info(self.player_dead_units_dict)
logging.info(self.enemy_dead_units_dict)
logging.info(self.player_dead_buildings_dict)
logging.info(self.enemy_dead_buildings_dict)
+ def dead_aircraft(self) -> AirLosses:
+ losses = []
+ for unit_name in self.state_data.killed_aircraft:
+ flight = self.unit_map.flight(unit_name)
+ if flight is None:
+ logging.error(f"Could not find Flight matching {unit_name}")
+ continue
+ losses.append(DebriefingDeadAircraftInfo(flight))
+ return AirLosses(losses)
+
@property
def base_capture_events(self):
- """Keeps only the last instance of a base capture event for each base ID"""
- reversed_captures = [i for i in self.state_data["base_capture_events"][::-1]]
+ """Keeps only the last instance of a base capture event for each base ID."""
+ reversed_captures = list(reversed(self.state_data.base_capture_events))
last_base_cap_indexes = []
for idx, base in enumerate(i.split("||")[0] for i in reversed_captures):
- if base in [x[1] for x in last_base_cap_indexes]:
- continue
- else:
+ if base not in [x[1] for x in last_base_cap_indexes]:
last_base_cap_indexes.append((idx, base))
return [reversed_captures[idx[0]] for idx in last_base_cap_indexes]
@@ -179,11 +212,13 @@ 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: typing.Callable, game):
- super(PollDebriefingFileThread, self).__init__()
+ def __init__(self, callback: Callable[[Debriefing], None],
+ game: Game, unit_map: UnitMap) -> None:
+ super().__init__()
self._stop_event = threading.Event()
self.callback = callback
self.game = game
+ self.unit_map = unit_map
def stop(self):
self._stop_event.set()
@@ -200,14 +235,14 @@ class PollDebriefingFileThread(threading.Thread):
if os.path.isfile("state.json") and os.path.getmtime("state.json") > last_modified:
with open("state.json", "r") as json_file:
json_data = json.load(json_file)
- debriefing = Debriefing(json_data, self.game)
+ debriefing = Debriefing(json_data, self.game, self.unit_map)
self.callback(debriefing)
break
time.sleep(5)
-def wait_for_debriefing(callback: typing.Callable, game)->PollDebriefingFileThread:
- thread = PollDebriefingFileThread(callback, game)
+def wait_for_debriefing(callback: Callable[[Debriefing], None],
+ game: Game, unit_map) -> PollDebriefingFileThread:
+ thread = PollDebriefingFileThread(callback, game, unit_map)
thread.start()
return thread
-
diff --git a/game/event/event.py b/game/event/event.py
index acc929fe..11bb8124 100644
--- a/game/event/event.py
+++ b/game/event/event.py
@@ -13,6 +13,7 @@ from game.debriefing import Debriefing
from game.infos.information import Information
from game.theater import ControlPoint
from gen.ground_forces.combat_stance import CombatStance
+from ..unitmap import UnitMap
if TYPE_CHECKING:
from ..game import Game
@@ -85,17 +86,18 @@ class Event:
def bonus(self) -> int:
return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE)
- def is_successfull(self, debriefing: Debriefing) -> bool:
- return self.operation.is_successfull(debriefing)
+ def is_successful(self, debriefing: Debriefing) -> bool:
+ return self.operation.is_successful(debriefing)
- def generate(self):
+ def generate(self) -> UnitMap:
self.operation.is_awacs_enabled = self.is_awacs_enabled
self.operation.ca_slots = self.ca_slots
self.operation.prepare(self.game)
- self.operation.generate()
- self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn.miz"))
- self.environment_settings = self.operation.environment_settings
+ unit_map = self.operation.generate()
+ self.operation.current_mission.save(
+ persistency.mission_path_for("liberation_nextturn.miz"))
+ return unit_map
def commit(self, debriefing: Debriefing):
@@ -103,41 +105,39 @@ class Event:
# ------------------------------
# Destroyed aircrafts
- cp_map = {cp.id: cp for cp in self.game.theater.controlpoints}
- for destroyed_aircraft in debriefing.killed_aircrafts:
- try:
- cpid = int(destroyed_aircraft.split("|")[3])
- aircraft = db.unit_type_from_name(
- destroyed_aircraft.split("|")[4])
- if cpid in cp_map:
- cp = cp_map[cpid]
- if aircraft in cp.base.aircraft:
- logging.info(f"Aircraft destroyed: {aircraft}")
- cp.base.aircraft[aircraft] = max(
- 0, cp.base.aircraft[aircraft] - 1)
- except Exception:
- logging.exception("Failed to commit destroyed aircraft")
+ for loss in debriefing.air_losses.losses:
+ aircraft = loss.flight.unit_type
+ cp = loss.flight.departure
+ available = cp.base.total_units_of_type(aircraft)
+ if available <= 0:
+ logging.error(
+ f"Found killed {aircraft} from {cp} but that airbase has "
+ f"none available.")
+ continue
+
+ logging.info(f"{aircraft} destroyed from {cp}")
+ cp.base.aircraft[aircraft] -= 1
# ------------------------------
# Destroyed ground units
killed_unit_count_by_cp = {cp.id: 0 for cp in self.game.theater.controlpoints}
cp_map = {cp.id: cp for cp in self.game.theater.controlpoints}
- for killed_ground_unit in debriefing.killed_ground_units:
+ for killed_ground_unit in debriefing.state_data.killed_ground_units:
try:
cpid = int(killed_ground_unit.split("|")[3])
- aircraft = db.unit_type_from_name(killed_ground_unit.split("|")[4])
+ unit_type = db.unit_type_from_name(killed_ground_unit.split("|")[4])
if cpid in cp_map.keys():
killed_unit_count_by_cp[cpid] = killed_unit_count_by_cp[cpid] + 1
cp = cp_map[cpid]
- if aircraft in cp.base.armor.keys():
- logging.info("Ground unit destroyed : " + str(aircraft))
- cp.base.armor[aircraft] = max(0, cp.base.armor[aircraft] - 1)
+ if unit_type in cp.base.armor.keys():
+ logging.info(f"Ground unit destroyed: {unit_type}")
+ cp.base.armor[unit_type] = max(0, cp.base.armor[unit_type] - 1)
except Exception as e:
print(e)
# ------------------------------
# Static ground objects
- for destroyed_ground_unit_name in debriefing.killed_ground_units:
+ for destroyed_ground_unit_name in debriefing.state_data.killed_ground_units:
for cp in self.game.theater.controlpoints:
if not cp.ground_objects:
continue
@@ -224,7 +224,7 @@ class Event:
# Destroyed units carcass
# -------------------------
- for destroyed_unit in debriefing.destroyed_units:
+ for destroyed_unit in debriefing.state_data.destroyed_statics:
self.game.add_destroyed_units(destroyed_unit)
# -----------------------------------
diff --git a/game/event/frontlineattack.py b/game/event/frontlineattack.py
index fa3d3416..4fa6d201 100644
--- a/game/event/frontlineattack.py
+++ b/game/event/frontlineattack.py
@@ -23,7 +23,7 @@ class FrontlineAttackEvent(Event):
def __str__(self):
return "Frontline attack"
- def is_successfull(self, debriefing: Debriefing):
+ def is_successful(self, debriefing: Debriefing):
attackers_success = True
if self.from_cp.captured:
return attackers_success
diff --git a/game/game.py b/game/game.py
index 889e5c66..28e19756 100644
--- a/game/game.py
+++ b/game/game.py
@@ -27,6 +27,7 @@ from .factions.faction import Faction
from .infos.information import Information
from .settings import Settings
from .theater import ConflictTheater, ControlPoint, OffMapSpawn
+from .unitmap import UnitMap
from .weather import Conditions, TimeOfDay
COMMISION_UNIT_VARIETY = 4
@@ -173,15 +174,15 @@ class Game:
self.events.append(event)
return event
- def initiate_event(self, event: Event):
+ def initiate_event(self, event: Event) -> UnitMap:
#assert event in self.events
logging.info("Generating {} (regular)".format(event))
- event.generate()
+ return event.generate()
def finish_event(self, event: Event, debriefing: Debriefing):
logging.info("Finishing event {}".format(event))
event.commit(debriefing)
- if event.is_successfull(debriefing):
+ if event.is_successful(debriefing):
self.budget += event.bonus()
if event in self.events:
diff --git a/game/operation/operation.py b/game/operation/operation.py
index ec1ee774..5f015b10 100644
--- a/game/operation/operation.py
+++ b/game/operation/operation.py
@@ -34,6 +34,7 @@ from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
from .. import db
from ..debriefing import Debriefing
+from ..unitmap import UnitMap
if TYPE_CHECKING:
from game import Game
@@ -59,6 +60,7 @@ class Operation:
is_quick = None
is_awacs_enabled = False
ca_slots = 0
+ unit_map: UnitMap
def __init__(self,
departure_cp: ControlPoint,
@@ -94,7 +96,7 @@ class Operation:
def units_of(self, country_name: str) -> List[UnitType]:
return []
- def is_successfull(self, debriefing: Debriefing) -> bool:
+ def is_successful(self, debriefing: Debriefing) -> bool:
return True
@classmethod
@@ -177,6 +179,10 @@ class Operation:
gen.add_flight(flight)
gen.generate()
+ @classmethod
+ def create_unit_map(cls) -> None:
+ cls.unit_map = UnitMap()
+
@classmethod
def create_radio_registries(cls) -> None:
unique_map_frequencies = set() # type: Set[RadioFrequency]
@@ -269,8 +275,9 @@ class Operation:
dead=True,
)
- def generate(self):
+ def generate(self) -> UnitMap:
"""Build the final Mission to be exported"""
+ self.create_unit_map()
self.create_radio_registries()
# Set mission time and weather conditions.
EnvironmentGenerator(self.current_mission,
@@ -323,6 +330,8 @@ class Operation:
self.airgen
)
+ return self.unit_map
+
@classmethod
def _generate_air_units(cls) -> None:
"""Generate the air units for the Operation"""
@@ -339,7 +348,7 @@ class Operation:
# Generate Aircraft Activity on the map
cls.airgen = AircraftConflictGenerator(
cls.current_mission, cls.game.settings, cls.game,
- cls.radio_registry)
+ cls.radio_registry, cls.unit_map)
cls.airgen.generate_flights(
cls.current_mission.country(cls.game.player_country),
diff --git a/game/unitmap.py b/game/unitmap.py
new file mode 100644
index 00000000..f08fbad6
--- /dev/null
+++ b/game/unitmap.py
@@ -0,0 +1,23 @@
+"""Maps generated units back to their Liberation types."""
+from typing import Dict, Optional
+
+from dcs.unitgroup import FlyingGroup
+
+from gen.flights.flight import Flight
+
+
+class UnitMap:
+ def __init__(self) -> None:
+ self.aircraft: Dict[str, Flight] = {}
+
+ def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None:
+ for unit in group.units:
+ # The actual name is a String (the pydcs translatable string), which
+ # doesn't define __eq__.
+ name = str(unit.name)
+ if name in self.aircraft:
+ raise RuntimeError(f"Duplicate unit name: {name}")
+ self.aircraft[name] = flight
+
+ def flight(self, group_name: str) -> Optional[Flight]:
+ return self.aircraft.get(group_name, None)
diff --git a/gen/aircraft.py b/gen/aircraft.py
index ca970958..edf9dcb8 100644
--- a/gen/aircraft.py
+++ b/gen/aircraft.py
@@ -76,6 +76,7 @@ from game.theater.controlpoint import (
OffMapSpawn,
)
from game.theater.theatergroundobject import TheaterGroundObject
+from game.unitmap import UnitMap
from game.utils import knots_to_kph, nm_to_meter
from gen.airsupportgen import AirSupport
from gen.ato import AirTaskingOrder, Package
@@ -647,12 +648,13 @@ AIRCRAFT_DATA["P-47D-30"] = AIRCRAFT_DATA["P-51D"]
class AircraftConflictGenerator:
- def __init__(self, mission: Mission, settings: Settings,
- game: Game, radio_registry: RadioRegistry):
+ def __init__(self, mission: Mission, settings: Settings, game: Game,
+ radio_registry: RadioRegistry, unit_map: UnitMap) -> None:
self.m = mission
self.game = game
self.settings = settings
self.radio_registry = radio_registry
+ self.unit_map = unit_map
self.flights: List[FlightData] = []
@cached_property
@@ -927,6 +929,7 @@ class AircraftConflictGenerator:
logging.info(f"Generating flight: {flight.unit_type}")
group = self.generate_planned_flight(flight.from_cp, country,
flight)
+ self.unit_map.add_aircraft(group, flight)
self.setup_flight_group(group, package, flight, dynamic_runways)
self.create_waypoints(group, package, flight)
diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py
index bb010d32..38fdc3ab 100644
--- a/qt_ui/widgets/QTopPanel.py
+++ b/qt_ui/widgets/QTopPanel.py
@@ -235,8 +235,9 @@ class QTopPanel(QFrame):
game_event.departure_cp = self.game.theater.controlpoints[0]
game_event.player_attacking()
- self.game.initiate_event(game_event)
- waiting = QWaitingForMissionResultWindow(game_event, self.game)
+ unit_map = self.game.initiate_event(game_event)
+ waiting = QWaitingForMissionResultWindow(game_event, self.game,
+ unit_map)
waiting.show()
def budget_update(self, game:Game):
diff --git a/qt_ui/windows/QDebriefingWindow.py b/qt_ui/windows/QDebriefingWindow.py
index e0ecce57..5dc826f1 100644
--- a/qt_ui/windows/QDebriefingWindow.py
+++ b/qt_ui/windows/QDebriefingWindow.py
@@ -1,3 +1,5 @@
+import logging
+
from PySide2.QtGui import QIcon, QPixmap
from PySide2.QtWidgets import (
QDialog,
@@ -55,29 +57,35 @@ class QDebriefingWindow(QDialog):
lostUnits.setLayout(lostUnitsLayout)
row = 0
- for unit_type, count in self.debriefing.player_dead_aircraft_dict.items():
+ player_air_losses = self.debriefing.air_losses.by_type(player=True)
+ for unit_type, count in player_air_losses.items():
try:
- lostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0)
- lostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
+ lostUnitsLayout.addWidget(
+ QLabel(db.unit_type_name(unit_type)), row, 0)
+ lostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
row += 1
- except:
- print("Issue adding " + str(unit_type) + " to debriefing information")
+ except AttributeError:
+ logging.exception(
+ f"Issue adding {unit_type} to debriefing information")
for unit_type, count in self.debriefing.player_dead_units_dict.items():
try:
- lostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0)
- lostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
+ lostUnitsLayout.addWidget(
+ QLabel(db.unit_type_name(unit_type)), row, 0)
+ lostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
row += 1
- except:
- print("Issue adding " + str(unit_type) + " to debriefing information")
+ except AttributeError:
+ logging.exception(
+ f"Issue adding {unit_type} to debriefing information")
for building, count in self.debriefing.player_dead_buildings_dict.items():
try:
lostUnitsLayout.addWidget(QLabel(building, row, 0))
- lostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
+ lostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
row += 1
- except:
- print("Issue adding " + str(building) + " to debriefing information")
+ except AttributeError:
+ logging.exception(
+ f"Issue adding {building} to debriefing information")
self.layout.addWidget(lostUnits)
@@ -92,15 +100,16 @@ class QDebriefingWindow(QDialog):
# enemylostUnitsLayout.addWidget(QLabel("{}".format(len(self.debriefing.destroyed_objects))), row, 1)
# row += 1
- for unit_type, count in self.debriefing.enemy_dead_aircraft_dict.items():
- if count == 0:
- continue
+ enemy_air_losses = self.debriefing.air_losses.by_type(player=False)
+ for unit_type, count in enemy_air_losses.items():
try:
- enemylostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0)
- enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
+ enemylostUnitsLayout.addWidget(
+ QLabel(db.unit_type_name(unit_type)), row, 0)
+ enemylostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
row += 1
- except:
- print("Issue adding " + str(unit_type) + " to debriefing information")
+ except AttributeError:
+ logging.exception(
+ f"Issue adding {unit_type} to debriefing information")
for unit_type, count in self.debriefing.enemy_dead_units_dict.items():
if count == 0:
@@ -114,8 +123,9 @@ class QDebriefingWindow(QDialog):
enemylostUnitsLayout.addWidget(QLabel(building), row, 0)
enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
row += 1
- except:
- print("Issue adding " + str(building) + " to debriefing information")
+ except AttributeError:
+ logging.exception(
+ f"Issue adding {building} to debriefing information")
self.layout.addWidget(enemylostUnits)
diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py
index cf186829..c26db853 100644
--- a/qt_ui/windows/QWaitingForMissionResultWindow.py
+++ b/qt_ui/windows/QWaitingForMissionResultWindow.py
@@ -20,6 +20,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
from game.debriefing import Debriefing, wait_for_debriefing
from game.game import Event, Game, logging
from game.persistency import base_path
+from game.unitmap import UnitMap
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
@@ -39,22 +40,27 @@ class DebriefingFileWrittenSignal(QObject):
def get_instance():
return DebriefingFileWrittenSignal.instance
+
DebriefingFileWrittenSignal()
+
class QWaitingForMissionResultWindow(QDialog):
- def __init__(self, gameEvent: Event, game: Game):
+ def __init__(self, gameEvent: Event, game: Game, unit_map: UnitMap) -> None:
super(QWaitingForMissionResultWindow, self).__init__()
self.setModal(True)
self.gameEvent = gameEvent
self.game = game
+ self.unit_map = unit_map
self.setWindowTitle("Waiting for mission completion.")
self.setWindowIcon(QIcon("./resources/icon.png"))
self.setMinimumHeight(570)
self.initUi()
DebriefingFileWrittenSignal.get_instance().debriefingReceived.connect(self.updateLayout)
- self.wait_thread = wait_for_debriefing(lambda debriefing: self.on_debriefing_udpate(debriefing), self.game)
+ self.wait_thread = wait_for_debriefing(
+ lambda debriefing: self.on_debriefing_update(debriefing), self.game,
+ self.unit_map)
def initUi(self):
self.layout = QGridLayout()
@@ -119,17 +125,17 @@ class QWaitingForMissionResultWindow(QDialog):
self.layout.addLayout(self.gridLayout, 1, 0)
self.setLayout(self.layout)
- def updateLayout(self, debriefing):
+ def updateLayout(self, debriefing: Debriefing) -> None:
updateBox = QGroupBox("Mission status")
updateLayout = QGridLayout()
updateBox.setLayout(updateLayout)
self.debriefing = debriefing
updateLayout.addWidget(QLabel("Aircraft destroyed"), 0, 0)
- updateLayout.addWidget(QLabel(str(len(debriefing.killed_aircrafts))), 0, 1)
+ updateLayout.addWidget(QLabel(str(len(debriefing.air_losses.losses))), 0, 1)
updateLayout.addWidget(QLabel("Ground units destroyed"), 1, 0)
- updateLayout.addWidget(QLabel(str(len(debriefing.killed_ground_units))), 1, 1)
+ updateLayout.addWidget(QLabel(str(len(debriefing.dead_units))), 1, 1)
#updateLayout.addWidget(QLabel("Weapons fired"), 2, 0)
#updateLayout.addWidget(QLabel(str(len(debriefing.weapons_fired))), 2, 1)
@@ -142,28 +148,27 @@ class QWaitingForMissionResultWindow(QDialog):
try:
self.gridLayout.itemAt(i).widget().setParent(None)
except:
- pass
+ logging.exception("Failed to clear window")
# Set new window content
self.gridLayout.addWidget(updateBox, 0, 0)
- if not debriefing.mission_ended:
+ if not debriefing.state_data.mission_ended:
self.gridLayout.addWidget(QLabel("Mission is being played"), 1, 0)
self.gridLayout.addWidget(self.actions, 2, 0)
else:
self.gridLayout.addWidget(QLabel("Mission is over"), 1, 0)
self.gridLayout.addWidget(self.actions2, 2, 0)
-
- def on_debriefing_udpate(self, debriefing):
+ def on_debriefing_update(self, debriefing: Debriefing) -> None:
try:
logging.info("On Debriefing update")
- print(debriefing)
+ logging.debug(debriefing)
DebriefingFileWrittenSignal.get_instance().sendDebriefing(debriefing)
- except Exception as e:
- logging.error("Got an error while sending debriefing")
- logging.error(e)
- self.wait_thread = wait_for_debriefing(lambda debriefing: self.on_debriefing_udpate(debriefing), self.game)
+ except Exception:
+ logging.exception("Got an error while sending debriefing")
+ self.wait_thread = wait_for_debriefing(
+ lambda d: self.on_debriefing_update(d), self.game, self.unit_map)
def process_debriefing(self):
self.game.finish_event(event=self.gameEvent, debriefing=self.debriefing)
@@ -187,8 +192,8 @@ class QWaitingForMissionResultWindow(QDialog):
with open(file[0], "r") as json_file:
json_data = json.load(json_file)
json_data["mission_ended"] = True
- debriefing = Debriefing(json_data, self.game)
- self.on_debriefing_udpate(debriefing)
+ debriefing = Debriefing(json_data, self.game, self.unit_map)
+ self.on_debriefing_update(debriefing)
except Exception as e:
logging.error(e)
msg = QMessageBox()
diff --git a/resources/plugins/base/dcs_liberation.lua b/resources/plugins/base/dcs_liberation.lua
index 722440a6..765edc61 100644
--- a/resources/plugins/base/dcs_liberation.lua
+++ b/resources/plugins/base/dcs_liberation.lua
@@ -6,7 +6,6 @@ logger:info("Check that json.lua is loaded : json = "..tostring(json))
killed_aircrafts = {}
killed_ground_units = {}
-weapons_fired = {}
base_capture_events = {}
destroyed_objects_positions = {}
mission_ended = false
@@ -33,7 +32,6 @@ function write_state()
local game_state = {
["killed_aircrafts"] = killed_aircrafts,
["killed_ground_units"] = killed_ground_units,
- ["weapons_fired"] = weapons_fired,
["base_capture_events"] = base_capture_events,
["mission_ended"] = mission_ended,
["destroyed_objects_positions"] = destroyed_objects_positions,