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 game import db
from game.theater import Airfield, TheaterGroundObject
from game.theater import Airfield, ControlPoint, TheaterGroundObject
from game.unitmap import UnitMap
from gen.flights.flight import Flight
@ -38,6 +38,17 @@ class DebriefingDeadAircraftInfo:
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)
class DebriefingDeadBuildingInfo:
#: The ground object this building was present at.
@ -58,7 +69,7 @@ class AirLosses:
if loss.flight.departure.captured != player:
continue
losses_by_type[loss.flight.unit_type] += loss.flight.count
losses_by_type[loss.flight.unit_type] += 1
return losses_by_type
def surviving_flight_members(self, flight: Flight) -> int:
@ -69,6 +80,20 @@ class AirLosses:
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)
class StateData:
#: 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.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.dead_aaa_groups: List[DebriefingDeadUnitInfo] = []
self.dead_buildings: List[DebriefingDeadBuildingInfo] = []
@ -174,6 +200,7 @@ class Debriefing:
logging.info("Debriefing pre process results :")
logging.info("--------------------------------")
logging.info(self.air_losses)
logging.info(self.front_line_losses)
logging.info(self.player_dead_units_dict)
logging.info(self.enemy_dead_units_dict)
logging.info(self.player_dead_buildings_dict)
@ -189,27 +216,17 @@ class Debriefing:
losses.append(DebriefingDeadAircraftInfo(flight))
return AirLosses(losses)
def dead_ground_units(self) -> List[DebriefingDeadUnitInfo]:
def dead_front_line_units(self) -> FrontLineLosses:
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?
unit = self.unit_map.front_line_unit(unit_name)
if unit is None:
# Killed "ground units" might also be runways or TGO units, so
# no need to log an error.
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
losses.append(
DebriefingDeadFrontLineUnitInfo(unit.unit_type, unit.origin))
return FrontLineLosses(losses)
def find_damaged_runways(self) -> List[Airfield]:
losses = []

View File

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

View File

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

View File

@ -1,16 +1,26 @@
"""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
@dataclass
class FrontLineUnit:
unit_type: Type[UnitType]
origin: ControlPoint
class UnitMap:
def __init__(self) -> None:
self.aircraft: Dict[str, Flight] = {}
self.airfields: Dict[str, Airfield] = {}
self.front_line_units: Dict[str, FrontLineUnit] = {}
def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None:
for unit in group.units:
@ -31,3 +41,18 @@ class UnitMap:
def airfield(self, name: str) -> Optional[Airfield]:
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 game import db
from game.unitmap import UnitMap
from .naming import namegen
from gen.ground_forces.ai_ground_planner import (
CombatGroup, CombatGroupRole,
@ -78,8 +79,8 @@ class GroundConflictGenerator:
game: Game,
player_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.conflict = conflict
self.enemy_planned_combat_groups = enemy_planned_combat_groups
@ -87,6 +88,7 @@ class GroundConflictGenerator:
self.player_stance = CombatStance(player_stance)
self.enemy_stance = self._enemy_stance()
self.game = game
self.unit_map = unit_map
self.jtacs: List[JtacInfo] = []
def _enemy_stance(self):
@ -530,6 +532,8 @@ class GroundConflictGenerator:
heading=heading,
move_formation=move_formation)
self.unit_map.add_front_line_units(group, cp)
for c in range(count):
vehicle: Vehicle = group.units[c]
vehicle.player_can_drive = True

View File

@ -68,7 +68,10 @@ class QDebriefingWindow(QDialog):
logging.exception(
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:
lostUnitsLayout.addWidget(
QLabel(db.unit_type_name(unit_type)), row, 0)
@ -111,7 +114,10 @@ class QDebriefingWindow(QDialog):
logging.exception(
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:
continue
enemylostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0)

View File

@ -132,16 +132,21 @@ class QWaitingForMissionResultWindow(QDialog):
self.debriefing = debriefing
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(QLabel(str(len(debriefing.dead_units))), 1, 1)
updateLayout.addWidget(
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(QLabel(str(len(debriefing.weapons_fired))), 2, 1)
updateLayout.addWidget(
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(str(len(debriefing.base_capture_events))), 2, 1)
updateLayout.addWidget(QLabel("<b>Base Capture Events</b>"), 3, 0)
updateLayout.addWidget(
QLabel(str(len(debriefing.base_capture_events))), 3, 1)
# Clear previous content of the window
for i in reversed(range(self.gridLayout.count())):