From 0c4e920af3030fbee3047d8731a55f8bb8327fa5 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 25 Nov 2020 13:10:48 -0800 Subject: [PATCH] Handle runway damage in the debrief. Apparently we were already getting this info because it's a unit like any other according to the event system, so if runways were actually sufficiently damaged we'd emit a ton of exceptions. This doesn't do anything yet, but tracks the damage state. Will add the ability to repair them next, and then finally make the damage render the runway inoperable. --- game/debriefing.py | 61 +++++++++++++++++++++++------------- game/event/event.py | 3 ++ game/operation/operation.py | 4 +++ game/theater/controlpoint.py | 1 + game/unitmap.py | 10 ++++++ 5 files changed, 58 insertions(+), 21 deletions(-) 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)