diff --git a/game/debriefing.py b/game/debriefing.py index 1d534be2..fcfbca9f 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -87,6 +87,12 @@ class GroundLosses: enemy_airfields: List[Airfield] = field(default_factory=list) +@dataclass(frozen=True) +class BaseCaptureEvent: + control_point: ControlPoint + captured_by_player: bool + + @dataclass(frozen=True) class StateData: #: True if the mission ended. If False, the mission exited abnormally. @@ -122,6 +128,7 @@ class Debriefing: 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.player_country = game.player_country @@ -131,6 +138,7 @@ class Debriefing: self.air_losses = self.dead_aircraft() self.ground_losses = self.dead_ground_units() + self.base_captures = self.base_capture_events() @property def front_line_losses(self) -> Iterator[FrontLineUnit]: @@ -314,15 +322,35 @@ class Debriefing: return losses - @property - def base_capture_events(self): + def base_capture_events(self) -> List[BaseCaptureEvent]: """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 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] + blue_coalition_id = 2 + seen = set() + captures = [] + for capture in reversed(self.state_data.base_capture_events): + cp_id_str, new_owner_id_str, _name = capture.split("||") + cp_id = int(cp_id_str) + + # Only the most recent capture event matters. + if cp_id in seen: + continue + seen.add(cp_id) + + try: + control_point = self.game.theater.find_control_point_by_id(cp_id) + except KeyError: + # Captured base is not a part of the campaign. This happens when neutral + # bases are near the conflict. Nothing to do. + continue + + captured_by_player = int(new_owner_id_str) == blue_coalition_id + if control_point.is_friendly(to_player=captured_by_player): + # Base is currently friendly to the new owner. Was captured and + # recaptured in the same mission. Nothing to do. + continue + + captures.append(BaseCaptureEvent(control_point, captured_by_player)) + return captures class PollDebriefingFileThread(threading.Thread): diff --git a/game/event/event.py b/game/event/event.py index 4ca70790..33334100 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -222,6 +222,29 @@ class Event: for damaged_runway in debriefing.damaged_runways: damaged_runway.damage_runway() + def commit_captures(self, debriefing: Debriefing) -> None: + for captured in debriefing.base_captures: + try: + if captured.captured_by_player: + info = Information( + f"{captured.control_point} captured!", + f"We took control of {captured.control_point}.", + self.game.turn, + ) + else: + info = Information( + f"{captured.control_point} lost!", + f"The enemy took control of {captured.control_point}.", + self.game.turn, + ) + + self.game.informations.append(info) + captured.control_point.capture(self.game, captured.captured_by_player) + logging.info(f"Will run redeploy for {captured.control_point}") + self.redeploy_units(captured.control_point) + except Exception: + logging.exception(f"Could not process base capture {captured}") + def commit(self, debriefing: Debriefing): logging.info("Committing mission results") @@ -232,54 +255,7 @@ class Event: self.commit_ground_object_losses(debriefing) self.commit_building_losses(debriefing) self.commit_damaged_runways(debriefing) - - # ------------------------------ - # Captured bases - # if self.game.player_country in db.BLUEFOR_FACTIONS: - coalition = 2 # Value in DCS mission event for BLUE - # else: - # coalition = 1 # Value in DCS mission event for RED - - for captured in debriefing.base_capture_events: - try: - id = int(captured.split("||")[0]) - new_owner_coalition = int(captured.split("||")[1]) - - captured_cps = [] - for cp in self.game.theater.controlpoints: - if cp.id == id: - - if cp.captured and new_owner_coalition != coalition: - for_player = False - info = Information( - cp.name + " lost !", - "The ennemy took control of " - + cp.name - + "\nShame on us !", - self.game.turn, - ) - self.game.informations.append(info) - captured_cps.append(cp) - elif not (cp.captured) and new_owner_coalition == coalition: - for_player = True - info = Information( - cp.name + " captured !", - "We took control of " + cp.name + "! Great job !", - self.game.turn, - ) - self.game.informations.append(info) - captured_cps.append(cp) - else: - continue - - cp.capture(self.game, for_player) - - for cp in captured_cps: - logging.info("Will run redeploy for " + cp.name) - self.redeploy_units(cp) - except Exception: - logging.exception(f"Could not process base capture {captured}") - + self.commit_captures(debriefing) self.complete_aircraft_transfers(debriefing) # Destroyed units carcass diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 8d59f982..cedb5b6e 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -673,7 +673,7 @@ class ConflictTheater: for i in self.controlpoints: if i.id == id: return i - raise RuntimeError(f"Cannot find ControlPoint with ID {id}") + raise KeyError(f"Cannot find ControlPoint with ID {id}") def add_json_cp(self, theater, p: dict) -> ControlPoint: cp: ControlPoint diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py index 10219007..7f02f755 100644 --- a/qt_ui/windows/QWaitingForMissionResultWindow.py +++ b/qt_ui/windows/QWaitingForMissionResultWindow.py @@ -173,7 +173,7 @@ class QWaitingForMissionResultWindow(QDialog): "Buildings destroyed", list(debriefing.building_losses), update_layout ) self.add_update_row( - "Base capture events", list(debriefing.base_capture_events), update_layout + "Base capture events", debriefing.base_captures, update_layout ) # Clear previous content of the window