mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Convert front line units to UnitMap.
https://github.com/Khopa/dcs_liberation/issues/485
This commit is contained in:
parent
aef4316f72
commit
4f37610dfb
@ -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 = []
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
20
gen/armor.py
20
gen/armor.py
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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())):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user