diff --git a/game/debriefing.py b/game/debriefing.py index 3aae88d6..114232ec 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -12,7 +12,7 @@ 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.theater import Airfield, TheaterGroundObject from game.unitmap import UnitMap from gen.flights.flight import Flight @@ -91,7 +91,9 @@ class StateData: return cls( mission_ended=data["mission_ended"], killed_aircraft=data["killed_aircrafts"], - killed_ground_units=data["killed_ground_units"], + # Airfields emit a new "dead" event every time a bomb is dropped on + # them when they've already dead. Dedup. + killed_ground_units=list(set(data["killed_ground_units"])), destroyed_statics=data["destroyed_objects_positions"], base_capture_events=data["base_capture_events"] ) @@ -114,28 +116,11 @@ class Debriefing: self.enemy_country_id = db.country_id_from_name(game.enemy_country) self.air_losses = self.dead_aircraft() - self.dead_units: List[DebriefingDeadUnitInfo] = [] + self.dead_units = self.dead_ground_units() + self.damaged_runways = self.find_damaged_runways() self.dead_aaa_groups: List[DebriefingDeadUnitInfo] = [] self.dead_buildings: List[DebriefingDeadBuildingInfo] = [] - for unit_name in self.state_data.killed_ground_units: - try: - 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_name in self.state_data.killed_ground_units: for cp in game.theater.controlpoints: if cp.captured: @@ -204,6 +189,40 @@ class Debriefing: losses.append(DebriefingDeadAircraftInfo(flight)) return AirLosses(losses) + def dead_ground_units(self) -> List[DebriefingDeadUnitInfo]: + losses = [] + for unit_name in self.state_data.killed_ground_units: + try: + 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 + if self._is_airfield(unit_name): + 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 + losses.append(DebriefingDeadUnitInfo(player_unit, unit_type)) + except Exception: + logging.exception(f"Failed to process dead unit {unit_name}") + return losses + + def find_damaged_runways(self) -> List[Airfield]: + losses = [] + for name in self.state_data.killed_ground_units: + airfield = self.unit_map.airfield(name) + if airfield is None: + continue + losses.append(airfield) + return losses + + def _is_airfield(self, unit_name: str) -> bool: + return self.unit_map.airfield(unit_name) is not None + @property def base_capture_events(self): """Keeps only the last instance of a base capture event for each base ID.""" diff --git a/game/event/event.py b/game/event/event.py index 4f9db729..d6a5763b 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -117,6 +117,9 @@ class Event: logging.info("Commiting mission results") + for damaged_runway in debriefing.damaged_runways: + damaged_runway.damaged = True + # ------------------------------ # Destroyed aircrafts for loss in debriefing.air_losses.losses: diff --git a/game/operation/operation.py b/game/operation/operation.py index 31683002..1e586be0 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -32,6 +32,7 @@ from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator from .. import db from ..debriefing import Debriefing +from ..theater import Airfield from ..unitmap import UnitMap if TYPE_CHECKING: @@ -173,6 +174,9 @@ class Operation: @classmethod def create_unit_map(cls) -> None: cls.unit_map = UnitMap() + for control_point in cls.game.theater.controlpoints: + if isinstance(control_point, Airfield): + cls.unit_map.add_airfield(control_point) @classmethod def create_radio_registries(cls) -> None: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 134f49a0..81b2dc94 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -376,6 +376,7 @@ class Airfield(ControlPoint): size, importance, has_frontline, cptype=ControlPointType.AIRBASE) self.airport = airport + self.damaged = False def can_land(self, aircraft: FlyingType) -> bool: return True diff --git a/game/unitmap.py b/game/unitmap.py index 0ca34fe5..6e229f43 100644 --- a/game/unitmap.py +++ b/game/unitmap.py @@ -3,12 +3,14 @@ from typing import Dict, Optional from dcs.unitgroup import FlyingGroup +from game.theater import Airfield from gen.flights.flight import Flight class UnitMap: def __init__(self) -> None: self.aircraft: Dict[str, Flight] = {} + self.airfields: Dict[str, Airfield] = {} def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None: for unit in group.units: @@ -21,3 +23,11 @@ class UnitMap: def flight(self, unit_name: str) -> Optional[Flight]: return self.aircraft.get(unit_name, None) + + def add_airfield(self, airfield: Airfield) -> None: + if airfield.name in self.airfields: + raise RuntimeError(f"Duplicate airfield: {airfield.name}") + self.airfields[airfield.name] = airfield + + def airfield(self, name: str) -> Optional[Airfield]: + return self.airfields.get(name, None)