Convert front line units to UnitMap.

https://github.com/Khopa/dcs_liberation/issues/485
This commit is contained in:
Dan Albert 2020-12-01 01:18:44 -08:00
parent aef4316f72
commit 4f37610dfb
7 changed files with 131 additions and 70 deletions

View File

@ -12,7 +12,7 @@ from typing import Any, Callable, Dict, List, Type, TYPE_CHECKING
from dcs.unittype import FlyingType, UnitType from dcs.unittype import FlyingType, UnitType
from game import db from game import db
from game.theater import Airfield, TheaterGroundObject from game.theater import Airfield, ControlPoint, TheaterGroundObject
from game.unitmap import UnitMap from game.unitmap import UnitMap
from gen.flights.flight import Flight from gen.flights.flight import Flight
@ -38,6 +38,17 @@ class DebriefingDeadAircraftInfo:
return self.flight.departure.captured return self.flight.departure.captured
@dataclass(frozen=True)
class DebriefingDeadFrontLineUnitInfo:
#: The Flight that resulted in the generated unit.
unit_type: Type[UnitType]
control_point: ControlPoint
@property
def player_unit(self) -> bool:
return self.control_point.captured
@dataclass(frozen=True) @dataclass(frozen=True)
class DebriefingDeadBuildingInfo: class DebriefingDeadBuildingInfo:
#: The ground object this building was present at. #: The ground object this building was present at.
@ -58,7 +69,7 @@ class AirLosses:
if loss.flight.departure.captured != player: if loss.flight.departure.captured != player:
continue continue
losses_by_type[loss.flight.unit_type] += loss.flight.count losses_by_type[loss.flight.unit_type] += 1
return losses_by_type return losses_by_type
def surviving_flight_members(self, flight: Flight) -> int: def surviving_flight_members(self, flight: Flight) -> int:
@ -69,6 +80,20 @@ class AirLosses:
return flight.count - losses return flight.count - losses
@dataclass(frozen=True)
class FrontLineLosses:
losses: List[DebriefingDeadFrontLineUnitInfo]
def by_type(self, player: bool) -> Dict[Type[UnitType], int]:
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
for loss in self.losses:
if loss.control_point.captured != player:
continue
losses_by_type[loss.unit_type] += 1
return losses_by_type
@dataclass(frozen=True) @dataclass(frozen=True)
class StateData: class StateData:
#: True if the mission ended. If False, the mission exited abnormally. #: True if the mission ended. If False, the mission exited abnormally.
@ -116,7 +141,8 @@ class Debriefing:
self.enemy_country_id = db.country_id_from_name(game.enemy_country) self.enemy_country_id = db.country_id_from_name(game.enemy_country)
self.air_losses = self.dead_aircraft() self.air_losses = self.dead_aircraft()
self.dead_units = self.dead_ground_units() self.front_line_losses = self.dead_front_line_units()
self.dead_units = []
self.damaged_runways = self.find_damaged_runways() self.damaged_runways = self.find_damaged_runways()
self.dead_aaa_groups: List[DebriefingDeadUnitInfo] = [] self.dead_aaa_groups: List[DebriefingDeadUnitInfo] = []
self.dead_buildings: List[DebriefingDeadBuildingInfo] = [] self.dead_buildings: List[DebriefingDeadBuildingInfo] = []
@ -174,6 +200,7 @@ class Debriefing:
logging.info("Debriefing pre process results :") logging.info("Debriefing pre process results :")
logging.info("--------------------------------") logging.info("--------------------------------")
logging.info(self.air_losses) logging.info(self.air_losses)
logging.info(self.front_line_losses)
logging.info(self.player_dead_units_dict) logging.info(self.player_dead_units_dict)
logging.info(self.enemy_dead_units_dict) logging.info(self.enemy_dead_units_dict)
logging.info(self.player_dead_buildings_dict) logging.info(self.player_dead_buildings_dict)
@ -189,27 +216,17 @@ class Debriefing:
losses.append(DebriefingDeadAircraftInfo(flight)) losses.append(DebriefingDeadAircraftInfo(flight))
return AirLosses(losses) return AirLosses(losses)
def dead_ground_units(self) -> List[DebriefingDeadUnitInfo]: def dead_front_line_units(self) -> FrontLineLosses:
losses = [] losses = []
for unit_name in self.state_data.killed_ground_units: for unit_name in self.state_data.killed_ground_units:
try: unit = self.unit_map.front_line_unit(unit_name)
if isinstance(unit_name, int): if unit is None:
# For some reason the state file will include many raw # Killed "ground units" might also be runways or TGO units, so
# integers in the list of destroyed units. These might be # no need to log an error.
# from the smoke effects? continue
continue losses.append(
if self._is_airfield(unit_name): DebriefingDeadFrontLineUnitInfo(unit.unit_type, unit.origin))
continue return FrontLineLosses(losses)
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]: def find_damaged_runways(self) -> List[Airfield]:
losses = [] losses = []

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import logging import logging
import math import math
from collections import defaultdict
from typing import Dict, List, Optional, TYPE_CHECKING, Type from typing import Dict, List, Optional, TYPE_CHECKING, Type
from dcs.mapping import Point from dcs.mapping import Point
@ -113,15 +114,8 @@ class Event:
self._transfer_aircraft(self.game.red_ato, debriefing.air_losses, self._transfer_aircraft(self.game.red_ato, debriefing.air_losses,
for_player=False) for_player=False)
def commit(self, debriefing: Debriefing): @staticmethod
def commit_air_losses(debriefing: Debriefing) -> None:
logging.info("Commiting mission results")
for damaged_runway in debriefing.damaged_runways:
damaged_runway.damage_runway()
# ------------------------------
# Destroyed aircrafts
for loss in debriefing.air_losses.losses: for loss in debriefing.air_losses.losses:
aircraft = loss.flight.unit_type aircraft = loss.flight.unit_type
cp = loss.flight.departure cp = loss.flight.departure
@ -129,29 +123,38 @@ class Event:
if available <= 0: if available <= 0:
logging.error( logging.error(
f"Found killed {aircraft} from {cp} but that airbase has " f"Found killed {aircraft} from {cp} but that airbase has "
f"none available.") "none available.")
continue continue
logging.info(f"{aircraft} destroyed from {cp}") logging.info(f"{aircraft} destroyed from {cp}")
cp.base.aircraft[aircraft] -= 1 cp.base.aircraft[aircraft] -= 1
# ------------------------------ @staticmethod
# Destroyed ground units def commit_front_line_losses(debriefing: Debriefing) -> Dict[int, int]:
killed_unit_count_by_cp = {cp.id: 0 for cp in self.game.theater.controlpoints} killed_unit_count_by_cp: Dict[int, int] = defaultdict(int)
cp_map = {cp.id: cp for cp in self.game.theater.controlpoints} for loss in debriefing.front_line_losses.losses:
for killed_ground_unit in debriefing.state_data.killed_ground_units: unit_type = loss.unit_type
try: control_point = loss.control_point
cpid = int(killed_ground_unit.split("|")[3]) killed_unit_count_by_cp[control_point.id] += 1
unit_type = db.unit_type_from_name(killed_ground_unit.split("|")[4]) available = control_point.base.total_units_of_type(unit_type)
if cpid in cp_map.keys(): if available <= 0:
killed_unit_count_by_cp[cpid] = killed_unit_count_by_cp[cpid] + 1 logging.error(
cp = cp_map[cpid] f"Found killed {unit_type} from {control_point} but that "
if unit_type in cp.base.armor.keys(): "airbase has none available.")
logging.info(f"Ground unit destroyed: {unit_type}") continue
cp.base.armor[unit_type] = max(0, cp.base.armor[unit_type] - 1)
except Exception: logging.info(f"{unit_type} destroyed from {control_point}")
logging.exception( control_point.base.armor[unit_type] -= 1
f"Could not commit lost ground unit {killed_ground_unit}") return killed_unit_count_by_cp
def commit(self, debriefing: Debriefing):
logging.info("Committing mission results")
for damaged_runway in debriefing.damaged_runways:
damaged_runway.damage_runway()
self.commit_air_losses(debriefing)
killed_unit_count_by_cp = self.commit_front_line_losses(debriefing)
# ------------------------------ # ------------------------------
# Static ground objects # Static ground objects

View File

@ -400,7 +400,8 @@ class Operation:
cls.current_mission, cls.current_mission,
conflict, cls.game, conflict, cls.game,
player_gp, enemy_gp, player_gp, enemy_gp,
player_cp.stances[enemy_cp.id] player_cp.stances[enemy_cp.id],
cls.unit_map
) )
ground_conflict_gen.generate() ground_conflict_gen.generate()
cls.jtacs.extend(ground_conflict_gen.jtacs) cls.jtacs.extend(ground_conflict_gen.jtacs)

View File

@ -1,16 +1,26 @@
"""Maps generated units back to their Liberation types.""" """Maps generated units back to their Liberation types."""
from typing import Dict, Optional from dataclasses import dataclass
from typing import Dict, Optional, Type
from dcs.unitgroup import FlyingGroup from dcs.unitgroup import FlyingGroup, Group
from dcs.unittype import UnitType
from game.theater import Airfield from game import db
from game.theater import Airfield, ControlPoint
from gen.flights.flight import Flight from gen.flights.flight import Flight
@dataclass
class FrontLineUnit:
unit_type: Type[UnitType]
origin: ControlPoint
class UnitMap: class UnitMap:
def __init__(self) -> None: def __init__(self) -> None:
self.aircraft: Dict[str, Flight] = {} self.aircraft: Dict[str, Flight] = {}
self.airfields: Dict[str, Airfield] = {} self.airfields: Dict[str, Airfield] = {}
self.front_line_units: Dict[str, FrontLineUnit] = {}
def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None: def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None:
for unit in group.units: for unit in group.units:
@ -31,3 +41,18 @@ class UnitMap:
def airfield(self, name: str) -> Optional[Airfield]: def airfield(self, name: str) -> Optional[Airfield]:
return self.airfields.get(name, None) return self.airfields.get(name, None)
def add_front_line_units(self, group: Group, origin: ControlPoint) -> 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.front_line_units:
raise RuntimeError(f"Duplicate front line unit: {name}")
unit_type = db.unit_type_from_name(unit.type)
if unit_type is None:
raise RuntimeError(f"Unknown unit type: {unit.type}")
self.front_line_units[name] = FrontLineUnit(unit_type, origin)
def front_line_unit(self, name: str) -> Optional[FrontLineUnit]:
return self.front_line_units.get(name, None)

View File

@ -28,6 +28,7 @@ from dcs.unittype import VehicleType
from dcs.unitgroup import VehicleGroup from dcs.unitgroup import VehicleGroup
from game import db from game import db
from game.unitmap import UnitMap
from .naming import namegen from .naming import namegen
from gen.ground_forces.ai_ground_planner import ( from gen.ground_forces.ai_ground_planner import (
CombatGroup, CombatGroupRole, CombatGroup, CombatGroupRole,
@ -72,14 +73,14 @@ class JtacInfo:
class GroundConflictGenerator: class GroundConflictGenerator:
def __init__( def __init__(
self, self,
mission: Mission, mission: Mission,
conflict: Conflict, conflict: Conflict,
game: Game, game: Game,
player_planned_combat_groups: List[CombatGroup], player_planned_combat_groups: List[CombatGroup],
enemy_planned_combat_groups: List[CombatGroup], enemy_planned_combat_groups: List[CombatGroup],
player_stance: CombatStance player_stance: CombatStance,
): unit_map: UnitMap) -> None:
self.mission = mission self.mission = mission
self.conflict = conflict self.conflict = conflict
self.enemy_planned_combat_groups = enemy_planned_combat_groups self.enemy_planned_combat_groups = enemy_planned_combat_groups
@ -87,6 +88,7 @@ class GroundConflictGenerator:
self.player_stance = CombatStance(player_stance) self.player_stance = CombatStance(player_stance)
self.enemy_stance = self._enemy_stance() self.enemy_stance = self._enemy_stance()
self.game = game self.game = game
self.unit_map = unit_map
self.jtacs: List[JtacInfo] = [] self.jtacs: List[JtacInfo] = []
def _enemy_stance(self): def _enemy_stance(self):
@ -530,6 +532,8 @@ class GroundConflictGenerator:
heading=heading, heading=heading,
move_formation=move_formation) move_formation=move_formation)
self.unit_map.add_front_line_units(group, cp)
for c in range(count): for c in range(count):
vehicle: Vehicle = group.units[c] vehicle: Vehicle = group.units[c]
vehicle.player_can_drive = True vehicle.player_can_drive = True

View File

@ -68,7 +68,10 @@ class QDebriefingWindow(QDialog):
logging.exception( logging.exception(
f"Issue adding {unit_type} to debriefing information") f"Issue adding {unit_type} to debriefing information")
for unit_type, count in self.debriefing.player_dead_units_dict.items(): front_line_losses = self.debriefing.front_line_losses.by_type(
player=True
)
for unit_type, count in front_line_losses.items():
try: try:
lostUnitsLayout.addWidget( lostUnitsLayout.addWidget(
QLabel(db.unit_type_name(unit_type)), row, 0) QLabel(db.unit_type_name(unit_type)), row, 0)
@ -111,7 +114,10 @@ class QDebriefingWindow(QDialog):
logging.exception( logging.exception(
f"Issue adding {unit_type} to debriefing information") f"Issue adding {unit_type} to debriefing information")
for unit_type, count in self.debriefing.enemy_dead_units_dict.items(): front_line_losses = self.debriefing.front_line_losses.by_type(
player=False
)
for unit_type, count in front_line_losses.items():
if count == 0: if count == 0:
continue continue
enemylostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0) enemylostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0)

View File

@ -132,16 +132,21 @@ class QWaitingForMissionResultWindow(QDialog):
self.debriefing = debriefing self.debriefing = debriefing
updateLayout.addWidget(QLabel("<b>Aircraft destroyed</b>"), 0, 0) updateLayout.addWidget(QLabel("<b>Aircraft destroyed</b>"), 0, 0)
updateLayout.addWidget(QLabel(str(len(debriefing.air_losses.losses))), 0, 1) updateLayout.addWidget(
QLabel(str(len(debriefing.air_losses.losses))), 0, 1)
updateLayout.addWidget(QLabel("<b>Ground units destroyed</b>"), 1, 0) updateLayout.addWidget(
updateLayout.addWidget(QLabel(str(len(debriefing.dead_units))), 1, 1) QLabel("<b>Front line units destroyed</b>"), 1, 0)
updateLayout.addWidget(
QLabel(str(len(debriefing.front_line_losses.losses))), 1, 1)
#updateLayout.addWidget(QLabel("<b>Weapons fired</b>"), 2, 0) updateLayout.addWidget(
#updateLayout.addWidget(QLabel(str(len(debriefing.weapons_fired))), 2, 1) QLabel("<b>Other ground units destroyed</b>"), 2, 0)
updateLayout.addWidget(QLabel(str(len(debriefing.dead_units))), 2, 1)
updateLayout.addWidget(QLabel("<b>Base Capture Events</b>"), 2, 0) updateLayout.addWidget(QLabel("<b>Base Capture Events</b>"), 3, 0)
updateLayout.addWidget(QLabel(str(len(debriefing.base_capture_events))), 2, 1) updateLayout.addWidget(
QLabel(str(len(debriefing.base_capture_events))), 3, 1)
# Clear previous content of the window # Clear previous content of the window
for i in reversed(range(self.gridLayout.count())): for i in reversed(range(self.gridLayout.count())):