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,