diff --git a/.idea/dcs_pmcliberation.iml b/.idea/dcs_pmcliberation.iml index 1f377c84..9eedabcf 100644 --- a/.idea/dcs_pmcliberation.iml +++ b/.idea/dcs_pmcliberation.iml @@ -4,7 +4,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index 65531ca9..e524f659 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/game/db.py b/game/db.py index 49476504..7419ab19 100644 --- a/game/db.py +++ b/game/db.py @@ -15,7 +15,7 @@ PRICES = { A_10A: 18, A_10C: 20, - F_A_18C: 18, + FA_18C_hornet: 18, AV8BNA: 15, Su_27: 30, @@ -23,13 +23,15 @@ PRICES = { F_15C: 30, M_2000C: 11, - MiG_15bis: 6, MiG_21Bis: 13, MiG_29A: 23, IL_76MD: 13, S_3B_Tanker: 13, + A_50: 8, + E_3A: 8, + # armor Armor.MBT_T_55: 4, @@ -47,12 +49,13 @@ PRICES = { } UNIT_BY_TASK = { - FighterSweep: [Su_27, Su_33, Su_25, F_15C, MiG_15bis, MiG_21Bis, MiG_29A, F_A_18C, AV8BNA], + FighterSweep: [Su_27, Su_33, FA_18C_hornet, F_15C, MiG_21Bis, MiG_29A, F_A_18C, AV8BNA], CAS: [Su_25T, A_10A, A_10C, ], CAP: [Armor.MBT_T_90, Armor.MBT_T_80U, Armor.MBT_T_55, Armor.MBT_M1A2_Abrams, Armor.MBT_M60A3_Patton, Armor.ATGM_M1134_Stryker, Armor.APC_BTR_80, ], AirDefence: [AirDefence.AAA_ZU_23_on_Ural_375, AirDefence.SAM_Avenger_M1097 ], Transport: [IL_76MD, S_3B_Tanker, ], Carriage: [CVN_74_John_C__Stennis, CV_1143_5_Admiral_Kuznetsov, ], + AWACS: [E_3A, A_50, ], } UNIT_BY_COUNTRY = { @@ -61,9 +64,9 @@ UNIT_BY_COUNTRY = { Su_27, Su_33, Su_25, - MiG_15bis, MiG_21Bis, MiG_29A, + A_50, AirDefence.AAA_ZU_23_on_Ural_375, Armor.APC_BTR_80, Armor.MBT_T_90, @@ -74,8 +77,9 @@ UNIT_BY_COUNTRY = { "USA": [F_15C, A_10C, - F_A_18C, + FA_18C_hornet, AV8BNA, + E_3A, Armor.MBT_M1A2_Abrams, Armor.MBT_M60A3_Patton, Armor.ATGM_M1134_Stryker, diff --git a/game/event.py b/game/event.py index e435bd68..4d6ee5fd 100644 --- a/game/event.py +++ b/game/event.py @@ -10,12 +10,12 @@ class Event: difficulty = 1 # type: int BONUS_BASE = 0 - def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): + def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game): self.attacker_name = attacker_name self.defender_name = defender_name self.to_cp = to_cp self.from_cp = from_cp - self.theater = theater + self.game = game def bonus(self) -> int: return math.ceil(math.log(self.difficulty, DIFFICULTY_LOG_BASE) * self.BONUS_BASE) @@ -28,6 +28,7 @@ class Event: self.operation.generate() self.operation.mission.save("build/nextturn.miz") + def generate_quick(self): self.operation.prepare(is_quick=True) self.operation.generate() self.operation.mission.save('build/nextturn_quick.miz') @@ -91,7 +92,7 @@ class GroundInterceptEvent(Event): typecount = max(math.floor(self.difficulty * self.TARGET_AMOUNT_FACTOR), 1) self.targets = {unittype: typecount for unittype in unittypes} - op = GroundInterceptOperation(theater=self.theater, + op = GroundInterceptOperation(game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, attacker_clients=clients, @@ -128,7 +129,7 @@ class InterceptEvent(Event): if self.is_successfull(debriefing): if self.from_cp.is_global: - for cp in self.theater.enemy_points(): + for cp in self.game.theater.enemy_points(): cp.base.affect_strength(-self.GLOBAL_STRENGTH_INFLUENCE) else: self.to_cp.base.affect_strength(self.STRENGTH_INFLUENCE * float(self.from_cp.captured and -1 or 1)) @@ -146,7 +147,7 @@ class InterceptEvent(Event): airdefense_unit = db.find_unittype(AirDefence, self.defender_name)[0] - op = InterceptOperation(theater=self.theater, + op = InterceptOperation(game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, attacker_clients=clients, @@ -166,7 +167,7 @@ class InterceptEvent(Event): self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name)) assert self.transport_unit is not None - op = InterceptOperation(theater=self.theater, + op = InterceptOperation(game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, attacker_clients={}, @@ -191,7 +192,9 @@ class CaptureEvent(Event): return "Attack from {} to {}".format(self.from_cp, self.to_cp) def is_successfull(self, debriefing: Debriefing): - attackers_success = len(debriefing.destroyed_units[self.defender_name]) > len(debriefing.destroyed_units[self.attacker_name]) + alive_attackers = sum(debriefing.alive_units[self.attacker_name].values()) + alive_defenders = sum(debriefing.alive_units[self.defender_name].values()) + attackers_success = alive_attackers > alive_defenders if self.from_cp.captured: return attackers_success else: @@ -219,7 +222,7 @@ class CaptureEvent(Event): escort = self.from_cp.base.scramble_sweep(self.to_cp) attackers = self.from_cp.base.assemble_cap(self.to_cp) - op = CaptureOperation(theater=self.theater, + op = CaptureOperation(game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, attacker_clients={}, @@ -239,7 +242,7 @@ class CaptureEvent(Event): def player_attacking(self, cas: db.PlaneDict, escort: db.PlaneDict, armor: db.ArmorDict, clients: db.PlaneDict): interceptors = self.to_cp.base.scramble_sweep(for_target=self.to_cp) - op = CaptureOperation(theater=self.theater, + op = CaptureOperation(game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, attacker_clients=clients, @@ -261,12 +264,12 @@ class UnitsDeliveryEvent(Event): informational = True units = None # type: typing.Dict[UnitType, int] - def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): + def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game): super(UnitsDeliveryEvent, self).__init__(attacker_name=attacker_name, defender_name=defender_name, from_cp=from_cp, to_cp=to_cp, - theater=theater) + game=game) self.units = {} diff --git a/game/game.py b/game/game.py index 3e677190..845dcffe 100644 --- a/game/game.py +++ b/game/game.py @@ -27,7 +27,7 @@ PLAYER_GROUNDINTERCEPT_PROBABILITY_BASE = 30 PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 50 PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG = 2 -PLAYER_BUDGET_INITIAL = 60 +PLAYER_BUDGET_INITIAL = 90 PLAYER_BUDGET_BASE = 20 PLAYER_BUDGET_IMPORTANCE_LOG = 2 @@ -53,7 +53,7 @@ class Game: defender_name=self.enemy, from_cp=from_cp, to_cp=to_cp, - theater=self.theater)) + game=self)) def _generate_enemy_caps(self): for from_cp, to_cp in self.theater.conflicts(False): @@ -65,7 +65,7 @@ class Game: defender_name=self.player, from_cp=from_cp, to_cp=to_cp, - theater=self.theater)) + game=self)) break def _generate_interceptions(self): @@ -79,7 +79,7 @@ class Game: defender_name=self.player, from_cp=from_cp, to_cp=to_cp, - theater=self.theater)) + game=self)) enemy_interception = True break @@ -87,6 +87,9 @@ class Game: if enemy_interception: break + if to_cp.is_global: + continue + if to_cp in self.theater.conflicts(False): continue @@ -97,7 +100,7 @@ class Game: defender_name=self.player, from_cp=from_cp, to_cp=to_cp, - theater=self.theater)) + game=self)) enemy_interception = True break @@ -107,7 +110,7 @@ class Game: defender_name=self.enemy, from_cp=from_cp, to_cp=to_cp, - theater=self.theater)) + game=self)) break def _generate_groundinterceptions(self): @@ -117,7 +120,7 @@ class Game: defender_name=self.enemy, from_cp=from_cp, to_cp=to_cp, - theater=self.theater)) + game=self)) break def _generate_globalinterceptions(self): @@ -129,7 +132,7 @@ class Game: defender_name=self.enemy, from_cp=from_cp, to_cp=to_cp, - theater=self.theater)) + game=self)) break def _commision_units(self, cp: ControlPoint): @@ -160,7 +163,7 @@ class Game: defender_name=self.player, from_cp=to_cp, to_cp=to_cp, - theater=self.theater) + game=self) self.events.append(event) return event @@ -170,7 +173,9 @@ class Game: def initiate_event(self, event: Event): assert event in self.events + event.generate() + event.generate_quick() def finish_event(self, event: Event, debriefing: Debriefing): event.commit(debriefing) diff --git a/game/operation.py b/game/operation.py index 8c2491d6..1aa23ab5 100644 --- a/game/operation.py +++ b/game/operation.py @@ -11,7 +11,8 @@ from gen.envsettingsgen import * class Operation: - starting_position = None # type: db.StartingPosition + attackers_starting_position = None # type: db.StartingPosition + defenders_starting_position = None # type: db.StartingPosition mission = None # type: dcs.Mission conflict = None # type: Conflict armorgen = None # type: ArmorConflictGenerator @@ -22,14 +23,14 @@ class Operation: envgen = None # type: EnvironmentSettingsGenerator def __init__(self, - theater: ConflictTheater, + game, attacker_name: str, defender_name: str, attacker_clients: db.PlaneDict, defender_clients: db.PlaneDict, from_cp: ControlPoint, to_cp: ControlPoint = None): - self.theater = theater + self.game = game self.attacker_name = attacker_name self.defender_name = defender_name self.attacker_clients = attacker_clients @@ -45,14 +46,19 @@ class Operation: self.airgen = AircraftConflictGenerator(mission, conflict) self.aagen = AAConflictGenerator(mission, conflict) self.shipgen = ShipGenerator(mission, conflict) - self.envgen = EnvironmentSettingsGenerator(mission) + self.envgen = EnvironmentSettingsGenerator(mission, self.game) player_name = self.from_cp.captured and self.attacker_name or self.defender_name enemy_name = self.from_cp.captured and self.defender_name or self.attacker_name - self.extra_aagen = ExtraAAConflictGenerator(mission, conflict, self.theater, player_name, enemy_name) + self.extra_aagen = ExtraAAConflictGenerator(mission, conflict, self.game, player_name, enemy_name) def prepare(self, is_quick: bool): - self.starting_position = is_quick and self.from_cp.at or None + if is_quick: + self.attackers_starting_position = None + self.defenders_starting_position = None + else: + self.attackers_starting_position = self.from_cp.at + self.defenders_starting_position = self.to_cp and self.to_cp.at or None def generate(self): self.extra_aagen.generate() @@ -97,13 +103,14 @@ class CaptureOperation(Operation): mission.country(self.defender_name))) def generate(self): - super(CaptureOperation, self).generate() + self.envgen.generate() self.armorgen.generate(self.attack, self.defense) self.aagen.generate(self.aa) - self.airgen.generate_defense(self.intercept, clients=self.defender_clients) - self.airgen.generate_cas(self.cas, clients=self.attacker_clients, at=self.starting_position) - self.airgen.generate_cas_escort(self.escort, clients=self.attacker_clients, at=self.starting_position) + self.airgen.generate_defense(self.intercept, clients=self.defender_clients, at=self.defenders_starting_position) + + self.airgen.generate_cas(self.cas, clients=self.attacker_clients, at=self.attackers_starting_position) + self.airgen.generate_cas_escort(self.escort, clients=self.attacker_clients, at=self.attackers_starting_position) class InterceptOperation(Operation): @@ -126,15 +133,11 @@ class InterceptOperation(Operation): super(InterceptOperation, self).prepare(is_quick) mission = dcs.Mission() - heading = self.from_cp.position.heading_between_point(self.to_cp.position) - distance = self.from_cp.position.distance_to_point(self.to_cp.position) - position = self.from_cp.position.point_from_heading(heading, distance/2) conflict = Conflict.intercept_conflict( attacker=mission.country(self.attacker_name), defender=mission.country(self.defender_name), - position=position, - heading=randint(0, 360), - radials=ALL_RADIALS + from_cp=self.from_cp, + to_cp=self.to_cp ) self.initialize(mission=mission, @@ -144,14 +147,13 @@ class InterceptOperation(Operation): super(InterceptOperation, self).generate() self.airgen.generate_transport(self.transport, self.to_cp.at) self.airgen.generate_transport_escort(self.escort, clients=self.defender_clients) - self.aagen.generate(self.airdefense) if self.from_cp.is_global: ship = self.shipgen.generate(type=db.find_unittype(Carriage, self.attacker_name)[0], at=self.from_cp.at) self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=ship) else: - self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.starting_position) + self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position) class GroundInterceptOperation(Operation): @@ -167,7 +169,7 @@ class GroundInterceptOperation(Operation): super(GroundInterceptOperation, self).prepare(is_quick) mission = dcs.Mission() conflict = Conflict.ground_intercept_conflict( - attacker=mission.country(self.defender_name), + attacker=mission.country(self.attacker_name), defender=mission.country(self.defender_name), position=self.position, heading=randint(0, 360), @@ -179,5 +181,5 @@ class GroundInterceptOperation(Operation): def generate(self): super(GroundInterceptOperation, self).generate() - self.airgen.generate_cas(self.strikegroup, clients=self.attacker_clients, at=self.starting_position) + self.airgen.generate_cas(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position) self.armorgen.generate({}, self.target) diff --git a/gen/aaa.py b/gen/aaa.py index ab93c373..ed4fe0bd 100644 --- a/gen/aaa.py +++ b/gen/aaa.py @@ -29,15 +29,18 @@ class AAConflictGenerator: class ExtraAAConflictGenerator: - def __init__(self, mission: Mission, conflict: Conflict, theater: ConflictTheater, player_name: Country, enemy_name: Country): + def __init__(self, mission: Mission, conflict: Conflict, game, player_name: Country, enemy_name: Country): self.mission = mission - self.theater = theater + self.game = game self.conflict = conflict self.player_name = player_name self.enemy_name = enemy_name def generate(self): - for cp in self.theater.controlpoints: + for cp in self.game.theater.controlpoints: + if cp.is_global: + continue + if cp.position.distance_to_point(self.conflict.position) > EXTRA_AA_MIN_DISTANCE: country_name = cp.captured and self.player_name or self.enemy_name diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 85e0eb32..4a4e6897 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -24,6 +24,7 @@ INTERCEPT_ATTACKERS_HEADING = -45, 45 INTERCEPT_DEFENDERS_HEADING = -10, 10 INTERCEPT_ATTACKERS_DISTANCE = 60000 INTERCEPT_DEFENDERS_DISTANCE = 30000 +INTERCEPT_MAX_DISTANCE = 45000 class Conflict: @@ -56,8 +57,13 @@ class Conflict: return instance @classmethod - def intercept_conflict(self, attacker: Country, defender: Country, position: Point, heading: int, radials: typing.List[int]): + def intercept_conflict(self, attacker: Country, defender: Country, from_cp, to_cp): from theater.conflicttheater import SIZE_REGULAR + from theater.conflicttheater import ALL_RADIALS + + heading = from_cp.position.heading_between_point(to_cp.position) + distance = min(from_cp.position.distance_to_point(to_cp.position) / 2, INTERCEPT_MAX_DISTANCE) + position = from_cp.position.point_from_heading(heading, distance) instance = self() instance.attackers_side = attacker @@ -65,7 +71,7 @@ class Conflict: instance.position = position instance.size = SIZE_REGULAR - instance.radials = radials + instance.radials = ALL_RADIALS instance.air_attackers_location = instance.position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, INTERCEPT_ATTACKERS_DISTANCE) instance.air_defenders_location = instance.position.point_from_heading(random.randint(*INTERCEPT_DEFENDERS_HEADING) + heading, INTERCEPT_DEFENDERS_DISTANCE) diff --git a/gen/envsettingsgen.py b/gen/envsettingsgen.py index bf596a47..53bef2d9 100644 --- a/gen/envsettingsgen.py +++ b/gen/envsettingsgen.py @@ -7,22 +7,34 @@ from theater.weatherforecast import WeatherForecast RANDOM_TIME = { - "night": 5, - "dusk": 25, - "down": 50, + "night": 0, + "dusk": 5, + "dawn": 35, "noon": 75, "day": 100, } class EnvironmentSettingsGenerator: - def __init__(self, mission: Mission): + def __init__(self, mission: Mission, game): self.mission = mission + self.game = game def generate(self): - self.mission.random_weather = True - time_roll = random.randint(0, 100) - time_period = [k for k, v in RANDOM_TIME.items() if v > time_roll][-1] - self.mission.random_daytime(time_period) + time_period = None + for k, v in RANDOM_TIME.items(): + if v >= time_roll: + time_period = k + break + self.mission.random_daytime(time_period) + self.mission.weather.random(self.mission.start_time, self.mission.terrain) + + for cp in self.game.theater.controlpoints: + if cp.is_global: + continue + + player_coalition = self.game.player == "USA" and "blue" or "red" + enemy_coalition = player_coalition == "blue" and "red" or "blue" + self.mission.terrain.airport_by_id(cp.at.id).set_coalition(cp.captured and player_coalition or enemy_coalition) diff --git a/theater/base.py b/theater/base.py index 1349cb5c..b0dcecd2 100644 --- a/theater/base.py +++ b/theater/base.py @@ -137,7 +137,7 @@ class Base: else: continue - target_array[unit_type] = target_array[unit_type] - count + target_array[unit_type] = max(target_array[unit_type] - count, 0) if target_array[unit_type] == 0: del target_array[unit_type] diff --git a/theater/conflicttheater.py b/theater/conflicttheater.py index 43b1fd61..1a84327f 100644 --- a/theater/conflicttheater.py +++ b/theater/conflicttheater.py @@ -20,6 +20,7 @@ COAST_HORIZONTAL = [315, 0, 45, ] class ConflictTheater: + terrain = None # type: dcs.terrain.Terrain controlpoints = None # type: typing.Collection[ControlPoint] reference_points = None # type: typing.Dict diff --git a/ui/eventresultsmenu.py b/ui/eventresultsmenu.py index 711aae88..0a0f5af7 100644 --- a/ui/eventresultsmenu.py +++ b/ui/eventresultsmenu.py @@ -55,6 +55,7 @@ class EventResultsMenu(Menu): Button(self.frame, text="Okay", command=self.dismiss).grid(columnspan=1, row=row); row += 1 def process_debriefing(self, debriefing: Debriefing): + self.debriefing = debriefing debriefing.calculate_destroyed_units(mission=self.event.operation.mission, player_name=self.game.player, enemy_name=self.game.enemy) diff --git a/ui/overviewcanvas.py b/ui/overviewcanvas.py index be58a70d..2aa12ebb 100644 --- a/ui/overviewcanvas.py +++ b/ui/overviewcanvas.py @@ -44,8 +44,10 @@ class OverviewCanvas: title = cp.name font = ("Helvetica", 13) - self.canvas.create_text(coords[0]+1, coords[1]+1, text=title, fill='white', font=font) - self.canvas.create_text(coords[0], coords[1], text=title, font=font) + id = self.canvas.create_text(coords[0]+1, coords[1]+1, text=title, fill='white', font=font) + self.canvas.tag_bind(id, "", self.display(cp)) + id = self.canvas.create_text(coords[0], coords[1], text=title, font=font) + self.canvas.tag_bind(id, "", self.display(cp)) def update(self): self.canvas.delete(ALL) @@ -66,7 +68,7 @@ class OverviewCanvas: for cp in self.game.theater.controlpoints: coords = self.cp_coordinates(cp) - arc_size = 18 * math.pow(cp.importance, 1) + arc_size = 28 * math.pow(cp.importance, 1) extent = max(cp.base.strength * 180, 10) start = (180 - extent) / 2 color = cp.captured and 'blue' or 'red' @@ -78,7 +80,7 @@ class OverviewCanvas: start=start, extent=extent) self.canvas.tag_bind(cp_id, "", self.display(cp)) - self.create_cp_title((coords[0] + arc_size/2, coords[1] + arc_size/2), cp) + self.create_cp_title((coords[0] + arc_size/4, coords[1] + arc_size/4), cp) units_title = "{}/{}/{}".format(cp.base.total_planes, cp.base.total_armor, cp.base.total_aa) self.canvas.create_text(coords[0], coords[1] - arc_size / 1.5, text=units_title, font=("Helvetica", 10)) diff --git a/userdata/debriefing.py b/userdata/debriefing.py index 4ec1d1be..ac6c2360 100644 --- a/userdata/debriefing.py +++ b/userdata/debriefing.py @@ -7,6 +7,8 @@ from dcs.lua import parse from dcs.mission import Mission from dcs.unitgroup import FlyingGroup +from dcs.unit import Vehicle, VehicleType +from dcs.vehicles import vehicle_map from dcs.unit import UnitType from game import db @@ -20,20 +22,21 @@ class Debriefing: self.alive_units = alive_units # type: typing.Dict[str, typing.Dict[str, int]] @classmethod - def parse(cls, path: str, mission: Mission): + def parse(cls, path: str): with open(path, "r") as f: table_string = f.read() table = parse.loads(table_string) units = table.get("debriefing", {}).get("world_state", {}) alive_units = {} - for unit in units: - type = unit["type"] # type: str + for unit in units.values(): + unit_type = unit["type"] # type: str country_id = int(unit["country"]) - if type: - country_dict = alive_units.get(unit[country_id], {}) - country_dict[type] = country_dict.get(type, 0) + 1 - alive_units[unit[country_id]] = country_dict + + if type(unit_type) == str: + country_dict = alive_units.get(country_id, {}) + country_dict[unit_type] = country_dict.get(unit_type, 0) + 1 + alive_units[country_id] = country_dict return Debriefing(alive_units) @@ -42,14 +45,20 @@ class Debriefing: result = {} for group in groups: for unit in group.units: - result[unit.unit_type] = result.get(unit.unit_type, 0) + 1 + unit_type = None + if isinstance(unit, Vehicle): + unit_type = vehicle_map[unit.type] + else: + unit_type = unit.unit_type + + result[unit_type] = result.get(unit_type, 0) + 1 return result def calculate_losses(all_units: typing.Dict[UnitType, int], alive_units: typing.Dict[str, int]) -> typing.Dict[UnitType, int]: result = {} for t, count in all_units.items(): - result[t] = count - alive_units[db.unit_type_name(t)] + result[t] = max(count - alive_units.get(db.unit_type_name(t), 0), 0) return result player = mission.country(player_name) @@ -59,8 +68,8 @@ class Debriefing: enemy_units = count_groups(enemy.plane_group + enemy.vehicle_group) self.destroyed_units = { - player.name: calculate_losses(player_units, self.alive_units[player.id]), - enemy.name: calculate_losses(enemy_units, self.alive_units[enemy.id]), + player.name: calculate_losses(player_units, self.alive_units.get(player.id, {})), + enemy.name: calculate_losses(enemy_units, self.alive_units.get(enemy.id, {})), } def debriefing_directory_location() -> str: