Add unit name -> Liberation object map.

Generated units are added to this during mission generation so we can
map destroyed units back to the data that generated them. Currently only
implemented for aircraft as a proof of concept.
This commit is contained in:
Dan Albert 2020-11-21 13:26:47 -08:00
parent d5a081a15f
commit f6fad30852
11 changed files with 285 additions and 200 deletions

View File

@ -1,176 +1,209 @@
from __future__ import annotations
import json import json
import logging import logging
import os import os
import threading import threading
import time import time
import typing from collections import defaultdict
from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Type, TYPE_CHECKING
from dcs.unittype import FlyingType, UnitType
from game import db from game import db
from game.theater import TheaterGroundObject
from game.unitmap import UnitMap
from gen.flights.flight import Flight
if TYPE_CHECKING:
from game import Game
DEBRIEFING_LOG_EXTENSION = "log" DEBRIEFING_LOG_EXTENSION = "log"
@dataclass(frozen=True)
class DebriefingDeadUnitInfo: class DebriefingDeadUnitInfo:
country_id = -1 player_unit: bool
player_unit = False type: Type[UnitType]
type = None
def __init__(self, country_id, player_unit , type):
self.country_id = country_id
self.player_unit = player_unit
self.type = type
def __repr__(self): @dataclass(frozen=True)
return str(self.country_id) + " " + str(self.player_unit) + " " + str(self.type) class DebriefingDeadAircraftInfo:
#: The Flight that resulted in the generated unit.
flight: Flight
@property
def player_unit(self) -> bool:
return self.flight.departure.captured
@dataclass(frozen=True)
class DebriefingDeadBuildingInfo:
#: The ground object this building was present at.
ground_object: TheaterGroundObject
@property
def player_unit(self) -> bool:
return self.ground_object.control_point.captured
@dataclass(frozen=True)
class AirLosses:
losses: List[DebriefingDeadAircraftInfo]
def by_type(self, player: bool) -> Dict[Type[FlyingType], int]:
losses_by_type: Dict[Type[FlyingType], int] = defaultdict(int)
for loss in self.losses:
if loss.flight.departure.captured != player:
continue
losses_by_type[loss.flight.unit_type] += loss.flight.count
return losses_by_type
@dataclass(frozen=True)
class StateData:
#: True if the mission ended. If False, the mission exited abnormally.
mission_ended: bool
#: Names of aircraft units that were killed during the mission.
killed_aircraft: List[str]
#: Names of vehicle (and ship) units that were killed during the mission.
killed_ground_units: List[str]
#: Names of static units that were destroyed during the mission.
destroyed_statics: List[str]
#: Mangled names of bases that were captured during the mission.
base_capture_events: List[str]
@classmethod
def from_json(cls, data: Dict[str, Any]) -> StateData:
return cls(
mission_ended=data["mission_ended"],
killed_aircraft=data["killed_aircrafts"],
killed_ground_units=data["killed_ground_units"],
destroyed_statics=data["destroyed_objects_positions"],
base_capture_events=data["base_capture_events"]
)
class Debriefing: class Debriefing:
def __init__(self, state_data, game): def __init__(self, state_data: Dict[str, Any], game: Game,
self.state_data = state_data unit_map: UnitMap) -> None:
self.killed_aircrafts = state_data["killed_aircrafts"] self.state_data = StateData.from_json(state_data)
self.killed_ground_units = state_data["killed_ground_units"] self.game = game
self.weapons_fired = state_data["weapons_fired"] self.unit_map = unit_map
self.mission_ended = state_data["mission_ended"]
self.destroyed_units = state_data["destroyed_objects_positions"]
self.__destroyed_units = []
logging.info("--------------------------------") logging.info("--------------------------------")
logging.info("Starting Debriefing preprocessing") logging.info("Starting Debriefing preprocessing")
logging.info("--------------------------------") logging.info("--------------------------------")
logging.info(self.base_capture_events) logging.info(self.state_data)
logging.info(self.killed_aircrafts)
logging.info(self.killed_ground_units)
logging.info(self.weapons_fired)
logging.info(self.mission_ended)
logging.info(self.destroyed_units)
logging.info("--------------------------------") logging.info("--------------------------------")
self.player_country_id = db.country_id_from_name(game.player_country) self.player_country_id = db.country_id_from_name(game.player_country)
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.dead_aircraft = [] self.air_losses = self.dead_aircraft()
self.dead_units = [] self.dead_units: List[DebriefingDeadUnitInfo] = []
self.dead_aaa_groups = [] self.dead_aaa_groups: List[DebriefingDeadUnitInfo] = []
self.dead_buildings = [] self.dead_buildings: List[DebriefingDeadBuildingInfo] = []
for aircraft in self.killed_aircrafts: for unit_name in self.state_data.killed_ground_units:
try: try:
country = int(aircraft.split("|")[1]) if isinstance(unit_name, int):
type = db.unit_type_from_name(aircraft.split("|")[4]) # For some reason the state file will include many raw
player_unit = (country == self.player_country_id) # integers in the list of destroyed units. These might be
aircraft = DebriefingDeadUnitInfo(country, player_unit, type) # from the smoke effects?
if type is not None: continue
self.dead_aircraft.append(aircraft) country = int(unit_name.split("|")[1])
except Exception as e: unit_type = db.unit_type_from_name(unit_name.split("|")[4])
logging.error(e) 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 in self.killed_ground_units: for unit_name in self.state_data.killed_ground_units:
try:
country = int(unit.split("|")[1])
type = db.unit_type_from_name(unit.split("|")[4])
player_unit = (country == self.player_country_id)
unit = DebriefingDeadUnitInfo(country, player_unit, type)
if type is not None:
self.dead_units.append(unit)
except Exception as e:
logging.error(e)
for unit in self.killed_ground_units:
for cp in game.theater.controlpoints: for cp in game.theater.controlpoints:
logging.info(cp.name)
logging.info(cp.captured)
if cp.captured: if cp.captured:
country = self.player_country_id country = self.player_country_id
else: else:
country = self.enemy_country_id country = self.enemy_country_id
player_unit = (country == self.player_country_id) player_unit = (country == self.player_country_id)
for i, ground_object in enumerate(cp.ground_objects): for ground_object in cp.ground_objects:
logging.info(unit) # TODO: This seems to destroy an arbitrary building?
logging.info(ground_object.group_name) if ground_object.is_same_group(unit_name):
if ground_object.is_same_group(unit): self.dead_buildings.append(
unit = DebriefingDeadUnitInfo(country, player_unit, ground_object.dcs_identifier) DebriefingDeadBuildingInfo(ground_object))
self.dead_buildings.append(unit) elif ground_object.dcs_identifier in ["AA", "CARRIER",
elif ground_object.dcs_identifier in ["AA", "CARRIER", "LHA"]: "LHA"]:
for g in ground_object.groups: for g in ground_object.groups:
for u in g.units: for u in g.units:
if u.name == unit: if u.name != unit_name:
unit = DebriefingDeadUnitInfo(country, player_unit, db.unit_type_from_name(u.type)) continue
self.dead_units.append(unit) unit_type = db.unit_type_from_name(u.type)
if unit_type is None:
logging.error(
f"Could not determine type of %s",
unit_name)
continue
self.dead_units.append(DebriefingDeadUnitInfo(
player_unit, unit_type))
self.player_dead_aircraft = [a for a in self.dead_aircraft if a.country_id == self.player_country_id] self.player_dead_units = [a for a in self.dead_units if a.player_unit]
self.enemy_dead_aircraft = [a for a in self.dead_aircraft if a.country_id == self.enemy_country_id] self.enemy_dead_units = [a for a in self.dead_units if not a.player_unit]
self.player_dead_units = [a for a in self.dead_units if a.country_id == self.player_country_id] self.player_dead_buildings = [a for a in self.dead_buildings if a.player_unit]
self.enemy_dead_units = [a for a in self.dead_units if a.country_id == self.enemy_country_id] self.enemy_dead_buildings = [a for a in self.dead_buildings if not a.player_unit]
self.player_dead_buildings = [a for a in self.dead_buildings if a.country_id == self.player_country_id]
self.enemy_dead_buildings = [a for a in self.dead_buildings if a.country_id == self.enemy_country_id]
logging.info(self.player_dead_aircraft) self.player_dead_units_dict: Dict[Type[UnitType], int] = defaultdict(int)
logging.info(self.enemy_dead_aircraft)
logging.info(self.player_dead_units)
logging.info(self.enemy_dead_units)
self.player_dead_aircraft_dict = {}
for a in self.player_dead_aircraft:
if a.type in self.player_dead_aircraft_dict.keys():
self.player_dead_aircraft_dict[a.type] = self.player_dead_aircraft_dict[a.type] + 1
else:
self.player_dead_aircraft_dict[a.type] = 1
self.enemy_dead_aircraft_dict = {}
for a in self.enemy_dead_aircraft:
if a.type in self.enemy_dead_aircraft_dict.keys():
self.enemy_dead_aircraft_dict[a.type] = self.enemy_dead_aircraft_dict[a.type] + 1
else:
self.enemy_dead_aircraft_dict[a.type] = 1
self.player_dead_units_dict = {}
for a in self.player_dead_units: for a in self.player_dead_units:
if a.type in self.player_dead_units_dict.keys(): self.player_dead_units_dict[a.type] += 1
self.player_dead_units_dict[a.type] = self.player_dead_units_dict[a.type] + 1
else:
self.player_dead_units_dict[a.type] = 1
self.enemy_dead_units_dict = {} self.enemy_dead_units_dict: Dict[Type[UnitType], int] = defaultdict(int)
for a in self.enemy_dead_units: for a in self.enemy_dead_units:
if a.type in self.enemy_dead_units_dict.keys(): self.enemy_dead_units_dict[a.type] += 1
self.enemy_dead_units_dict[a.type] = self.enemy_dead_units_dict[a.type] + 1
else:
self.enemy_dead_units_dict[a.type] = 1
self.player_dead_buildings_dict = {} self.player_dead_buildings_dict: Dict[str, int] = defaultdict(int)
for a in self.player_dead_buildings: for b in self.player_dead_buildings:
if a.type in self.player_dead_buildings_dict.keys(): self.player_dead_buildings_dict[b.ground_object.dcs_identifier] += 1
self.player_dead_buildings_dict[a.type] = self.player_dead_buildings_dict[a.type] + 1
else:
self.player_dead_buildings_dict[a.type] = 1
self.enemy_dead_buildings_dict = {} self.enemy_dead_buildings_dict: Dict[str, int] = defaultdict(int)
for a in self.enemy_dead_buildings: for b in self.enemy_dead_buildings:
if a.type in self.enemy_dead_buildings_dict.keys(): self.enemy_dead_buildings_dict[b.ground_object.dcs_identifier] += 1
self.enemy_dead_buildings_dict[a.type] = self.enemy_dead_buildings_dict[a.type] + 1
else:
self.enemy_dead_buildings_dict[a.type] = 1
logging.info("--------------------------------") logging.info("--------------------------------")
logging.info("Debriefing pre process results :") logging.info("Debriefing pre process results :")
logging.info("--------------------------------") logging.info("--------------------------------")
logging.info(self.player_dead_aircraft_dict) logging.info(self.air_losses)
logging.info(self.enemy_dead_aircraft_dict)
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)
logging.info(self.enemy_dead_buildings_dict) logging.info(self.enemy_dead_buildings_dict)
def dead_aircraft(self) -> AirLosses:
losses = []
for unit_name in self.state_data.killed_aircraft:
flight = self.unit_map.flight(unit_name)
if flight is None:
logging.error(f"Could not find Flight matching {unit_name}")
continue
losses.append(DebriefingDeadAircraftInfo(flight))
return AirLosses(losses)
@property @property
def base_capture_events(self): def base_capture_events(self):
"""Keeps only the last instance of a base capture event for each base ID""" """Keeps only the last instance of a base capture event for each base ID."""
reversed_captures = [i for i in self.state_data["base_capture_events"][::-1]] reversed_captures = list(reversed(self.state_data.base_capture_events))
last_base_cap_indexes = [] last_base_cap_indexes = []
for idx, base in enumerate(i.split("||")[0] for i in reversed_captures): for idx, base in enumerate(i.split("||")[0] for i in reversed_captures):
if base in [x[1] for x in last_base_cap_indexes]: if base not in [x[1] for x in last_base_cap_indexes]:
continue
else:
last_base_cap_indexes.append((idx, base)) last_base_cap_indexes.append((idx, base))
return [reversed_captures[idx[0]] for idx in last_base_cap_indexes] return [reversed_captures[idx[0]] for idx in last_base_cap_indexes]
@ -179,11 +212,13 @@ class PollDebriefingFileThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check """Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition.""" regularly for the stopped() condition."""
def __init__(self, callback: typing.Callable, game): def __init__(self, callback: Callable[[Debriefing], None],
super(PollDebriefingFileThread, self).__init__() game: Game, unit_map: UnitMap) -> None:
super().__init__()
self._stop_event = threading.Event() self._stop_event = threading.Event()
self.callback = callback self.callback = callback
self.game = game self.game = game
self.unit_map = unit_map
def stop(self): def stop(self):
self._stop_event.set() self._stop_event.set()
@ -200,14 +235,14 @@ class PollDebriefingFileThread(threading.Thread):
if os.path.isfile("state.json") and os.path.getmtime("state.json") > last_modified: if os.path.isfile("state.json") and os.path.getmtime("state.json") > last_modified:
with open("state.json", "r") as json_file: with open("state.json", "r") as json_file:
json_data = json.load(json_file) json_data = json.load(json_file)
debriefing = Debriefing(json_data, self.game) debriefing = Debriefing(json_data, self.game, self.unit_map)
self.callback(debriefing) self.callback(debriefing)
break break
time.sleep(5) time.sleep(5)
def wait_for_debriefing(callback: typing.Callable, game)->PollDebriefingFileThread: def wait_for_debriefing(callback: Callable[[Debriefing], None],
thread = PollDebriefingFileThread(callback, game) game: Game, unit_map) -> PollDebriefingFileThread:
thread = PollDebriefingFileThread(callback, game, unit_map)
thread.start() thread.start()
return thread return thread

View File

@ -13,6 +13,7 @@ from game.debriefing import Debriefing
from game.infos.information import Information from game.infos.information import Information
from game.theater import ControlPoint from game.theater import ControlPoint
from gen.ground_forces.combat_stance import CombatStance from gen.ground_forces.combat_stance import CombatStance
from ..unitmap import UnitMap
if TYPE_CHECKING: if TYPE_CHECKING:
from ..game import Game from ..game import Game
@ -85,17 +86,18 @@ class Event:
def bonus(self) -> int: def bonus(self) -> int:
return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE) return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE)
def is_successfull(self, debriefing: Debriefing) -> bool: def is_successful(self, debriefing: Debriefing) -> bool:
return self.operation.is_successfull(debriefing) return self.operation.is_successful(debriefing)
def generate(self): def generate(self) -> UnitMap:
self.operation.is_awacs_enabled = self.is_awacs_enabled self.operation.is_awacs_enabled = self.is_awacs_enabled
self.operation.ca_slots = self.ca_slots self.operation.ca_slots = self.ca_slots
self.operation.prepare(self.game) self.operation.prepare(self.game)
self.operation.generate() unit_map = self.operation.generate()
self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn.miz")) self.operation.current_mission.save(
self.environment_settings = self.operation.environment_settings persistency.mission_path_for("liberation_nextturn.miz"))
return unit_map
def commit(self, debriefing: Debriefing): def commit(self, debriefing: Debriefing):
@ -103,41 +105,39 @@ class Event:
# ------------------------------ # ------------------------------
# Destroyed aircrafts # Destroyed aircrafts
cp_map = {cp.id: cp for cp in self.game.theater.controlpoints} for loss in debriefing.air_losses.losses:
for destroyed_aircraft in debriefing.killed_aircrafts: aircraft = loss.flight.unit_type
try: cp = loss.flight.departure
cpid = int(destroyed_aircraft.split("|")[3]) available = cp.base.total_units_of_type(aircraft)
aircraft = db.unit_type_from_name( if available <= 0:
destroyed_aircraft.split("|")[4]) logging.error(
if cpid in cp_map: f"Found killed {aircraft} from {cp} but that airbase has "
cp = cp_map[cpid] f"none available.")
if aircraft in cp.base.aircraft: continue
logging.info(f"Aircraft destroyed: {aircraft}")
cp.base.aircraft[aircraft] = max( logging.info(f"{aircraft} destroyed from {cp}")
0, cp.base.aircraft[aircraft] - 1) cp.base.aircraft[aircraft] -= 1
except Exception:
logging.exception("Failed to commit destroyed aircraft")
# ------------------------------ # ------------------------------
# Destroyed ground units # Destroyed ground units
killed_unit_count_by_cp = {cp.id: 0 for cp in self.game.theater.controlpoints} 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} cp_map = {cp.id: cp for cp in self.game.theater.controlpoints}
for killed_ground_unit in debriefing.killed_ground_units: for killed_ground_unit in debriefing.state_data.killed_ground_units:
try: try:
cpid = int(killed_ground_unit.split("|")[3]) cpid = int(killed_ground_unit.split("|")[3])
aircraft = db.unit_type_from_name(killed_ground_unit.split("|")[4]) unit_type = db.unit_type_from_name(killed_ground_unit.split("|")[4])
if cpid in cp_map.keys(): if cpid in cp_map.keys():
killed_unit_count_by_cp[cpid] = killed_unit_count_by_cp[cpid] + 1 killed_unit_count_by_cp[cpid] = killed_unit_count_by_cp[cpid] + 1
cp = cp_map[cpid] cp = cp_map[cpid]
if aircraft in cp.base.armor.keys(): if unit_type in cp.base.armor.keys():
logging.info("Ground unit destroyed : " + str(aircraft)) logging.info(f"Ground unit destroyed: {unit_type}")
cp.base.armor[aircraft] = max(0, cp.base.armor[aircraft] - 1) cp.base.armor[unit_type] = max(0, cp.base.armor[unit_type] - 1)
except Exception as e: except Exception as e:
print(e) print(e)
# ------------------------------ # ------------------------------
# Static ground objects # Static ground objects
for destroyed_ground_unit_name in debriefing.killed_ground_units: for destroyed_ground_unit_name in debriefing.state_data.killed_ground_units:
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
if not cp.ground_objects: if not cp.ground_objects:
continue continue
@ -224,7 +224,7 @@ class Event:
# Destroyed units carcass # Destroyed units carcass
# ------------------------- # -------------------------
for destroyed_unit in debriefing.destroyed_units: for destroyed_unit in debriefing.state_data.destroyed_statics:
self.game.add_destroyed_units(destroyed_unit) self.game.add_destroyed_units(destroyed_unit)
# ----------------------------------- # -----------------------------------

View File

@ -23,7 +23,7 @@ class FrontlineAttackEvent(Event):
def __str__(self): def __str__(self):
return "Frontline attack" return "Frontline attack"
def is_successfull(self, debriefing: Debriefing): def is_successful(self, debriefing: Debriefing):
attackers_success = True attackers_success = True
if self.from_cp.captured: if self.from_cp.captured:
return attackers_success return attackers_success

View File

@ -27,6 +27,7 @@ from .factions.faction import Faction
from .infos.information import Information from .infos.information import Information
from .settings import Settings from .settings import Settings
from .theater import ConflictTheater, ControlPoint, OffMapSpawn from .theater import ConflictTheater, ControlPoint, OffMapSpawn
from .unitmap import UnitMap
from .weather import Conditions, TimeOfDay from .weather import Conditions, TimeOfDay
COMMISION_UNIT_VARIETY = 4 COMMISION_UNIT_VARIETY = 4
@ -173,15 +174,15 @@ class Game:
self.events.append(event) self.events.append(event)
return event return event
def initiate_event(self, event: Event): def initiate_event(self, event: Event) -> UnitMap:
#assert event in self.events #assert event in self.events
logging.info("Generating {} (regular)".format(event)) logging.info("Generating {} (regular)".format(event))
event.generate() return event.generate()
def finish_event(self, event: Event, debriefing: Debriefing): def finish_event(self, event: Event, debriefing: Debriefing):
logging.info("Finishing event {}".format(event)) logging.info("Finishing event {}".format(event))
event.commit(debriefing) event.commit(debriefing)
if event.is_successfull(debriefing): if event.is_successful(debriefing):
self.budget += event.bonus() self.budget += event.bonus()
if event in self.events: if event in self.events:

View File

@ -34,6 +34,7 @@ from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
from .. import db from .. import db
from ..debriefing import Debriefing from ..debriefing import Debriefing
from ..unitmap import UnitMap
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -59,6 +60,7 @@ class Operation:
is_quick = None is_quick = None
is_awacs_enabled = False is_awacs_enabled = False
ca_slots = 0 ca_slots = 0
unit_map: UnitMap
def __init__(self, def __init__(self,
departure_cp: ControlPoint, departure_cp: ControlPoint,
@ -94,7 +96,7 @@ class Operation:
def units_of(self, country_name: str) -> List[UnitType]: def units_of(self, country_name: str) -> List[UnitType]:
return [] return []
def is_successfull(self, debriefing: Debriefing) -> bool: def is_successful(self, debriefing: Debriefing) -> bool:
return True return True
@classmethod @classmethod
@ -177,6 +179,10 @@ class Operation:
gen.add_flight(flight) gen.add_flight(flight)
gen.generate() gen.generate()
@classmethod
def create_unit_map(cls) -> None:
cls.unit_map = UnitMap()
@classmethod @classmethod
def create_radio_registries(cls) -> None: def create_radio_registries(cls) -> None:
unique_map_frequencies = set() # type: Set[RadioFrequency] unique_map_frequencies = set() # type: Set[RadioFrequency]
@ -269,8 +275,9 @@ class Operation:
dead=True, dead=True,
) )
def generate(self): def generate(self) -> UnitMap:
"""Build the final Mission to be exported""" """Build the final Mission to be exported"""
self.create_unit_map()
self.create_radio_registries() self.create_radio_registries()
# Set mission time and weather conditions. # Set mission time and weather conditions.
EnvironmentGenerator(self.current_mission, EnvironmentGenerator(self.current_mission,
@ -323,6 +330,8 @@ class Operation:
self.airgen self.airgen
) )
return self.unit_map
@classmethod @classmethod
def _generate_air_units(cls) -> None: def _generate_air_units(cls) -> None:
"""Generate the air units for the Operation""" """Generate the air units for the Operation"""
@ -339,7 +348,7 @@ class Operation:
# Generate Aircraft Activity on the map # Generate Aircraft Activity on the map
cls.airgen = AircraftConflictGenerator( cls.airgen = AircraftConflictGenerator(
cls.current_mission, cls.game.settings, cls.game, cls.current_mission, cls.game.settings, cls.game,
cls.radio_registry) cls.radio_registry, cls.unit_map)
cls.airgen.generate_flights( cls.airgen.generate_flights(
cls.current_mission.country(cls.game.player_country), cls.current_mission.country(cls.game.player_country),

23
game/unitmap.py Normal file
View File

@ -0,0 +1,23 @@
"""Maps generated units back to their Liberation types."""
from typing import Dict, Optional
from dcs.unitgroup import FlyingGroup
from gen.flights.flight import Flight
class UnitMap:
def __init__(self) -> None:
self.aircraft: Dict[str, Flight] = {}
def add_aircraft(self, group: FlyingGroup, flight: Flight) -> 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.aircraft:
raise RuntimeError(f"Duplicate unit name: {name}")
self.aircraft[name] = flight
def flight(self, group_name: str) -> Optional[Flight]:
return self.aircraft.get(group_name, None)

View File

@ -76,6 +76,7 @@ from game.theater.controlpoint import (
OffMapSpawn, OffMapSpawn,
) )
from game.theater.theatergroundobject import TheaterGroundObject from game.theater.theatergroundobject import TheaterGroundObject
from game.unitmap import UnitMap
from game.utils import knots_to_kph, nm_to_meter from game.utils import knots_to_kph, nm_to_meter
from gen.airsupportgen import AirSupport from gen.airsupportgen import AirSupport
from gen.ato import AirTaskingOrder, Package from gen.ato import AirTaskingOrder, Package
@ -647,12 +648,13 @@ AIRCRAFT_DATA["P-47D-30"] = AIRCRAFT_DATA["P-51D"]
class AircraftConflictGenerator: class AircraftConflictGenerator:
def __init__(self, mission: Mission, settings: Settings, def __init__(self, mission: Mission, settings: Settings, game: Game,
game: Game, radio_registry: RadioRegistry): radio_registry: RadioRegistry, unit_map: UnitMap) -> None:
self.m = mission self.m = mission
self.game = game self.game = game
self.settings = settings self.settings = settings
self.radio_registry = radio_registry self.radio_registry = radio_registry
self.unit_map = unit_map
self.flights: List[FlightData] = [] self.flights: List[FlightData] = []
@cached_property @cached_property
@ -927,6 +929,7 @@ class AircraftConflictGenerator:
logging.info(f"Generating flight: {flight.unit_type}") logging.info(f"Generating flight: {flight.unit_type}")
group = self.generate_planned_flight(flight.from_cp, country, group = self.generate_planned_flight(flight.from_cp, country,
flight) flight)
self.unit_map.add_aircraft(group, flight)
self.setup_flight_group(group, package, flight, dynamic_runways) self.setup_flight_group(group, package, flight, dynamic_runways)
self.create_waypoints(group, package, flight) self.create_waypoints(group, package, flight)

View File

@ -235,8 +235,9 @@ class QTopPanel(QFrame):
game_event.departure_cp = self.game.theater.controlpoints[0] game_event.departure_cp = self.game.theater.controlpoints[0]
game_event.player_attacking() game_event.player_attacking()
self.game.initiate_event(game_event) unit_map = self.game.initiate_event(game_event)
waiting = QWaitingForMissionResultWindow(game_event, self.game) waiting = QWaitingForMissionResultWindow(game_event, self.game,
unit_map)
waiting.show() waiting.show()
def budget_update(self, game:Game): def budget_update(self, game:Game):

View File

@ -1,3 +1,5 @@
import logging
from PySide2.QtGui import QIcon, QPixmap from PySide2.QtGui import QIcon, QPixmap
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
QDialog, QDialog,
@ -55,29 +57,35 @@ class QDebriefingWindow(QDialog):
lostUnits.setLayout(lostUnitsLayout) lostUnits.setLayout(lostUnitsLayout)
row = 0 row = 0
for unit_type, count in self.debriefing.player_dead_aircraft_dict.items(): player_air_losses = self.debriefing.air_losses.by_type(player=True)
for unit_type, count in player_air_losses.items():
try: try:
lostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0) lostUnitsLayout.addWidget(
lostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1) QLabel(db.unit_type_name(unit_type)), row, 0)
lostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
row += 1 row += 1
except: except AttributeError:
print("Issue adding " + str(unit_type) + " to debriefing information") logging.exception(
f"Issue adding {unit_type} to debriefing information")
for unit_type, count in self.debriefing.player_dead_units_dict.items(): for unit_type, count in self.debriefing.player_dead_units_dict.items():
try: try:
lostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0) lostUnitsLayout.addWidget(
lostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1) QLabel(db.unit_type_name(unit_type)), row, 0)
lostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
row += 1 row += 1
except: except AttributeError:
print("Issue adding " + str(unit_type) + " to debriefing information") logging.exception(
f"Issue adding {unit_type} to debriefing information")
for building, count in self.debriefing.player_dead_buildings_dict.items(): for building, count in self.debriefing.player_dead_buildings_dict.items():
try: try:
lostUnitsLayout.addWidget(QLabel(building, row, 0)) lostUnitsLayout.addWidget(QLabel(building, row, 0))
lostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1) lostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
row += 1 row += 1
except: except AttributeError:
print("Issue adding " + str(building) + " to debriefing information") logging.exception(
f"Issue adding {building} to debriefing information")
self.layout.addWidget(lostUnits) self.layout.addWidget(lostUnits)
@ -92,15 +100,16 @@ class QDebriefingWindow(QDialog):
# enemylostUnitsLayout.addWidget(QLabel("{}".format(len(self.debriefing.destroyed_objects))), row, 1) # enemylostUnitsLayout.addWidget(QLabel("{}".format(len(self.debriefing.destroyed_objects))), row, 1)
# row += 1 # row += 1
for unit_type, count in self.debriefing.enemy_dead_aircraft_dict.items(): enemy_air_losses = self.debriefing.air_losses.by_type(player=False)
if count == 0: for unit_type, count in enemy_air_losses.items():
continue
try: try:
enemylostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0) enemylostUnitsLayout.addWidget(
enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1) QLabel(db.unit_type_name(unit_type)), row, 0)
enemylostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
row += 1 row += 1
except: except AttributeError:
print("Issue adding " + str(unit_type) + " to debriefing information") logging.exception(
f"Issue adding {unit_type} to debriefing information")
for unit_type, count in self.debriefing.enemy_dead_units_dict.items(): for unit_type, count in self.debriefing.enemy_dead_units_dict.items():
if count == 0: if count == 0:
@ -114,8 +123,9 @@ class QDebriefingWindow(QDialog):
enemylostUnitsLayout.addWidget(QLabel(building), row, 0) enemylostUnitsLayout.addWidget(QLabel(building), row, 0)
enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1) enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
row += 1 row += 1
except: except AttributeError:
print("Issue adding " + str(building) + " to debriefing information") logging.exception(
f"Issue adding {building} to debriefing information")
self.layout.addWidget(enemylostUnits) self.layout.addWidget(enemylostUnits)

View File

@ -20,6 +20,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
from game.debriefing import Debriefing, wait_for_debriefing from game.debriefing import Debriefing, wait_for_debriefing
from game.game import Event, Game, logging from game.game import Event, Game, logging
from game.persistency import base_path from game.persistency import base_path
from game.unitmap import UnitMap
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
@ -39,22 +40,27 @@ class DebriefingFileWrittenSignal(QObject):
def get_instance(): def get_instance():
return DebriefingFileWrittenSignal.instance return DebriefingFileWrittenSignal.instance
DebriefingFileWrittenSignal() DebriefingFileWrittenSignal()
class QWaitingForMissionResultWindow(QDialog): class QWaitingForMissionResultWindow(QDialog):
def __init__(self, gameEvent: Event, game: Game): def __init__(self, gameEvent: Event, game: Game, unit_map: UnitMap) -> None:
super(QWaitingForMissionResultWindow, self).__init__() super(QWaitingForMissionResultWindow, self).__init__()
self.setModal(True) self.setModal(True)
self.gameEvent = gameEvent self.gameEvent = gameEvent
self.game = game self.game = game
self.unit_map = unit_map
self.setWindowTitle("Waiting for mission completion.") self.setWindowTitle("Waiting for mission completion.")
self.setWindowIcon(QIcon("./resources/icon.png")) self.setWindowIcon(QIcon("./resources/icon.png"))
self.setMinimumHeight(570) self.setMinimumHeight(570)
self.initUi() self.initUi()
DebriefingFileWrittenSignal.get_instance().debriefingReceived.connect(self.updateLayout) DebriefingFileWrittenSignal.get_instance().debriefingReceived.connect(self.updateLayout)
self.wait_thread = wait_for_debriefing(lambda debriefing: self.on_debriefing_udpate(debriefing), self.game) self.wait_thread = wait_for_debriefing(
lambda debriefing: self.on_debriefing_update(debriefing), self.game,
self.unit_map)
def initUi(self): def initUi(self):
self.layout = QGridLayout() self.layout = QGridLayout()
@ -119,17 +125,17 @@ class QWaitingForMissionResultWindow(QDialog):
self.layout.addLayout(self.gridLayout, 1, 0) self.layout.addLayout(self.gridLayout, 1, 0)
self.setLayout(self.layout) self.setLayout(self.layout)
def updateLayout(self, debriefing): def updateLayout(self, debriefing: Debriefing) -> None:
updateBox = QGroupBox("Mission status") updateBox = QGroupBox("Mission status")
updateLayout = QGridLayout() updateLayout = QGridLayout()
updateBox.setLayout(updateLayout) updateBox.setLayout(updateLayout)
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.killed_aircrafts))), 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("<b>Ground units destroyed</b>"), 1, 0)
updateLayout.addWidget(QLabel(str(len(debriefing.killed_ground_units))), 1, 1) updateLayout.addWidget(QLabel(str(len(debriefing.dead_units))), 1, 1)
#updateLayout.addWidget(QLabel("<b>Weapons fired</b>"), 2, 0) #updateLayout.addWidget(QLabel("<b>Weapons fired</b>"), 2, 0)
#updateLayout.addWidget(QLabel(str(len(debriefing.weapons_fired))), 2, 1) #updateLayout.addWidget(QLabel(str(len(debriefing.weapons_fired))), 2, 1)
@ -142,28 +148,27 @@ class QWaitingForMissionResultWindow(QDialog):
try: try:
self.gridLayout.itemAt(i).widget().setParent(None) self.gridLayout.itemAt(i).widget().setParent(None)
except: except:
pass logging.exception("Failed to clear window")
# Set new window content # Set new window content
self.gridLayout.addWidget(updateBox, 0, 0) self.gridLayout.addWidget(updateBox, 0, 0)
if not debriefing.mission_ended: if not debriefing.state_data.mission_ended:
self.gridLayout.addWidget(QLabel("<b>Mission is being played</b>"), 1, 0) self.gridLayout.addWidget(QLabel("<b>Mission is being played</b>"), 1, 0)
self.gridLayout.addWidget(self.actions, 2, 0) self.gridLayout.addWidget(self.actions, 2, 0)
else: else:
self.gridLayout.addWidget(QLabel("<b>Mission is over</b>"), 1, 0) self.gridLayout.addWidget(QLabel("<b>Mission is over</b>"), 1, 0)
self.gridLayout.addWidget(self.actions2, 2, 0) self.gridLayout.addWidget(self.actions2, 2, 0)
def on_debriefing_update(self, debriefing: Debriefing) -> None:
def on_debriefing_udpate(self, debriefing):
try: try:
logging.info("On Debriefing update") logging.info("On Debriefing update")
print(debriefing) logging.debug(debriefing)
DebriefingFileWrittenSignal.get_instance().sendDebriefing(debriefing) DebriefingFileWrittenSignal.get_instance().sendDebriefing(debriefing)
except Exception as e: except Exception:
logging.error("Got an error while sending debriefing") logging.exception("Got an error while sending debriefing")
logging.error(e) self.wait_thread = wait_for_debriefing(
self.wait_thread = wait_for_debriefing(lambda debriefing: self.on_debriefing_udpate(debriefing), self.game) lambda d: self.on_debriefing_update(d), self.game, self.unit_map)
def process_debriefing(self): def process_debriefing(self):
self.game.finish_event(event=self.gameEvent, debriefing=self.debriefing) self.game.finish_event(event=self.gameEvent, debriefing=self.debriefing)
@ -187,8 +192,8 @@ class QWaitingForMissionResultWindow(QDialog):
with open(file[0], "r") as json_file: with open(file[0], "r") as json_file:
json_data = json.load(json_file) json_data = json.load(json_file)
json_data["mission_ended"] = True json_data["mission_ended"] = True
debriefing = Debriefing(json_data, self.game) debriefing = Debriefing(json_data, self.game, self.unit_map)
self.on_debriefing_udpate(debriefing) self.on_debriefing_update(debriefing)
except Exception as e: except Exception as e:
logging.error(e) logging.error(e)
msg = QMessageBox() msg = QMessageBox()

View File

@ -6,7 +6,6 @@ logger:info("Check that json.lua is loaded : json = "..tostring(json))
killed_aircrafts = {} killed_aircrafts = {}
killed_ground_units = {} killed_ground_units = {}
weapons_fired = {}
base_capture_events = {} base_capture_events = {}
destroyed_objects_positions = {} destroyed_objects_positions = {}
mission_ended = false mission_ended = false
@ -33,7 +32,6 @@ function write_state()
local game_state = { local game_state = {
["killed_aircrafts"] = killed_aircrafts, ["killed_aircrafts"] = killed_aircrafts,
["killed_ground_units"] = killed_ground_units, ["killed_ground_units"] = killed_ground_units,
["weapons_fired"] = weapons_fired,
["base_capture_events"] = base_capture_events, ["base_capture_events"] = base_capture_events,
["mission_ended"] = mission_ended, ["mission_ended"] = mission_ended,
["destroyed_objects_positions"] = destroyed_objects_positions, ["destroyed_objects_positions"] = destroyed_objects_positions,