mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
debriefing log parsing
This commit is contained in:
parent
5f7724d44e
commit
481a5922b4
10
__init__.py
10
__init__.py
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import pickle
|
import sys
|
||||||
|
|
||||||
import theater.caucasus
|
import theater.caucasus
|
||||||
import ui.window
|
import ui.window
|
||||||
@ -9,6 +9,14 @@ from game.game import Game
|
|||||||
from theater import start_generator
|
from theater import start_generator
|
||||||
from userdata import persistency
|
from userdata import persistency
|
||||||
|
|
||||||
|
from dcs.lua.parse import loads
|
||||||
|
|
||||||
|
with open("/Users/sp/Downloads/won_cap.log", "r") as f:
|
||||||
|
s = f.read()
|
||||||
|
print(loads(s))
|
||||||
|
|
||||||
|
#sys.exit(0)
|
||||||
|
|
||||||
game = persistency.restore_game()
|
game = persistency.restore_game()
|
||||||
if not game:
|
if not game:
|
||||||
theater = theater.caucasus.CaucasusTheater()
|
theater = theater.caucasus.CaucasusTheater()
|
||||||
|
|||||||
@ -42,19 +42,20 @@ PRICES = {
|
|||||||
Armor.APC_BTR_80: 6,
|
Armor.APC_BTR_80: 6,
|
||||||
|
|
||||||
AirDefence.AAA_ZU_23_on_Ural_375: 4,
|
AirDefence.AAA_ZU_23_on_Ural_375: 4,
|
||||||
|
AirDefence.SAM_Avenger_M1097: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
UNIT_BY_TASK = {
|
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, Su_25, F_15C, MiG_15bis, MiG_21Bis, MiG_29A, F_A_18C, AV8BNA],
|
||||||
CAS: [Su_25T, A_10A, A_10C, ],
|
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, ],
|
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: [AirDefence.AAA_ZU_23_on_Ural_375, AirDefence.SAM_Avenger_M1097 ],
|
||||||
Transport: [IL_76MD, S_3B_Tanker, ],
|
Transport: [IL_76MD, S_3B_Tanker, ],
|
||||||
}
|
}
|
||||||
|
|
||||||
UNIT_BY_COUNTRY = {
|
UNIT_BY_COUNTRY = {
|
||||||
"Russia": [Su_25T, Su_27, Su_33, Su_25, MiG_15bis, MiG_21Bis, MiG_29A, AirDefence.AAA_ZU_23_on_Ural_375, Armor.APC_BTR_80, Armor.MBT_T_90, Armor.MBT_T_80U, Armor.MBT_T_55, IL_76MD, ],
|
"Russia": [Su_25T, Su_27, Su_33, Su_25, MiG_15bis, MiG_21Bis, MiG_29A, AirDefence.AAA_ZU_23_on_Ural_375, Armor.APC_BTR_80, Armor.MBT_T_90, Armor.MBT_T_80U, Armor.MBT_T_55, IL_76MD, ],
|
||||||
"USA": [F_15C, A_10C, F_A_18C, AV8BNA, Armor.MBT_M1A2_Abrams, Armor.MBT_M60A3_Patton, Armor.ATGM_M1134_Stryker, S_3B_Tanker],
|
"USA": [F_15C, A_10C, F_A_18C, AV8BNA, Armor.MBT_M1A2_Abrams, Armor.MBT_M60A3_Patton, Armor.ATGM_M1134_Stryker, S_3B_Tanker, AirDefence.SAM_Avenger_M1097],
|
||||||
}
|
}
|
||||||
|
|
||||||
UnitsDict = typing.Dict[UnitType, int]
|
UnitsDict = typing.Dict[UnitType, int]
|
||||||
|
|||||||
@ -56,12 +56,12 @@ class GroundInterceptEvent(Event):
|
|||||||
targets = None # type: db.ArmorDict
|
targets = None # type: db.ArmorDict
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Ground intercept from {} at {} ({})".format(self.from_cp, self.to_cp, "*" * self.difficulty)
|
return "Ground intercept from {} at {}".format(self.from_cp, self.to_cp)
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing):
|
def is_successfull(self, debriefing: Debriefing):
|
||||||
total_targets = sum(self.targets.values())
|
total_targets = sum(self.targets.values())
|
||||||
destroyed_targets = 0
|
destroyed_targets = 0
|
||||||
for unit, count in debriefing.destroyed_units[self.defender.name].items():
|
for unit, count in debriefing.destroyed_units[self.defender_name].items():
|
||||||
if unit in self.targets:
|
if unit in self.targets:
|
||||||
destroyed_targets += count
|
destroyed_targets += count
|
||||||
|
|
||||||
@ -113,10 +113,10 @@ class InterceptEvent(Event):
|
|||||||
transport_unit = None # type: FlyingType
|
transport_unit = None # type: FlyingType
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Intercept from {} at {} ({})".format(self.from_cp, self.to_cp, "*" * self.difficulty)
|
return "Intercept from {} at {}".format(self.from_cp, self.to_cp)
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing):
|
def is_successfull(self, debriefing: Debriefing):
|
||||||
intercepted = self.transport_unit in debriefing.destroyed_units[self.defender.name].keys()
|
intercepted = self.transport_unit in debriefing.destroyed_units[self.defender_name].keys()
|
||||||
if self.from_cp.captured:
|
if self.from_cp.captured:
|
||||||
return intercepted
|
return intercepted
|
||||||
else:
|
else:
|
||||||
@ -185,10 +185,10 @@ class CaptureEvent(Event):
|
|||||||
STRENGTH_RECOVERY = 0.35
|
STRENGTH_RECOVERY = 0.35
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Attack from {} to {} ({})".format(self.from_cp, self.to_cp, "*" * self.difficulty)
|
return "Attack from {} to {}".format(self.from_cp, self.to_cp)
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing):
|
def is_successfull(self, debriefing: Debriefing):
|
||||||
attackers_success = len(debriefing.destroyed_units[self.defender.name]) > len(debriefing.destroyed_units[self.attacker.name])
|
attackers_success = len(debriefing.destroyed_units[self.defender_name]) > len(debriefing.destroyed_units[self.attacker_name])
|
||||||
if self.from_cp.captured:
|
if self.from_cp.captured:
|
||||||
return attackers_success
|
return attackers_success
|
||||||
else:
|
else:
|
||||||
|
|||||||
19
game/game.py
19
game/game.py
@ -21,8 +21,8 @@ ENEMY_INTERCEPT_PROBABILITY_BASE = 10
|
|||||||
ENEMY_INTERCEPT_GLOBAL_PROBABILITY_BASE = 1
|
ENEMY_INTERCEPT_GLOBAL_PROBABILITY_BASE = 1
|
||||||
ENEMY_CAPTURE_PROBABILITY_BASE = 3
|
ENEMY_CAPTURE_PROBABILITY_BASE = 3
|
||||||
|
|
||||||
PLAYER_INTERCEPT_PROBABILITY_BASE = 30
|
PLAYER_INTERCEPT_PROBABILITY_BASE = 100
|
||||||
PLAYER_GROUNDINTERCEPT_PROBABILITY_BASE = 30
|
PLAYER_GROUNDINTERCEPT_PROBABILITY_BASE = 100
|
||||||
PLAYER_GLOBALINTERCEPT_PROBABILITY_BASE = 100
|
PLAYER_GLOBALINTERCEPT_PROBABILITY_BASE = 100
|
||||||
|
|
||||||
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 50
|
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 50
|
||||||
@ -40,8 +40,8 @@ class Game:
|
|||||||
def __init__(self, theater: ConflictTheater):
|
def __init__(self, theater: ConflictTheater):
|
||||||
self.events = []
|
self.events = []
|
||||||
self.theater = theater
|
self.theater = theater
|
||||||
self.player = "USA"
|
self.player = "Russia"
|
||||||
self.enemy = "Russia"
|
self.enemy = "USA"
|
||||||
|
|
||||||
def _roll(self, prob, mult):
|
def _roll(self, prob, mult):
|
||||||
return random.randint(0, 100) <= prob * mult
|
return random.randint(0, 100) <= prob * mult
|
||||||
@ -132,16 +132,6 @@ class Game:
|
|||||||
theater=self.theater))
|
theater=self.theater))
|
||||||
break
|
break
|
||||||
|
|
||||||
def _generate_global(self):
|
|
||||||
for cp in self.theater.player_points():
|
|
||||||
if cp.is_global:
|
|
||||||
if self._roll(PLAYER_GLOBALINTERCEPT_PROBABILITY_BASE, cp.base.strength):
|
|
||||||
enemy_points = list(set(self.theater.enemy_bases()) - set(self.theater.conflicts(False)))
|
|
||||||
self.events.append(InterceptEvent(attacker_name=self.player,
|
|
||||||
defender_name=self.enemy,
|
|
||||||
from_cp=cp,
|
|
||||||
to_cp=random.choice(enemy_points)))
|
|
||||||
|
|
||||||
def _commision_units(self, cp: ControlPoint):
|
def _commision_units(self, cp: ControlPoint):
|
||||||
for for_task in [CAP, CAS, FighterSweep, AirDefence]:
|
for for_task in [CAP, CAS, FighterSweep, AirDefence]:
|
||||||
limit = COMMISION_LIMITS_FACTORS[for_task] * math.pow(cp.importance, COMMISION_LIMITS_SCALE)
|
limit = COMMISION_LIMITS_FACTORS[for_task] * math.pow(cp.importance, COMMISION_LIMITS_SCALE)
|
||||||
@ -207,5 +197,4 @@ class Game:
|
|||||||
self._generate_interceptions()
|
self._generate_interceptions()
|
||||||
self._generate_globalinterceptions()
|
self._generate_globalinterceptions()
|
||||||
self._generate_groundinterceptions()
|
self._generate_groundinterceptions()
|
||||||
self._generate_global()
|
|
||||||
|
|
||||||
|
|||||||
@ -159,7 +159,8 @@ class GroundInterceptOperation(Operation):
|
|||||||
radials=ALL_RADIALS
|
radials=ALL_RADIALS
|
||||||
)
|
)
|
||||||
|
|
||||||
super(GroundInterceptOperation, self).__init__(mission, conflict)
|
self.initialize(mission=mission,
|
||||||
|
conflict=conflict)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
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.starting_position)
|
||||||
|
|||||||
@ -23,29 +23,33 @@ class EventMenu(Menu):
|
|||||||
self.window.clear_right_pane()
|
self.window.clear_right_pane()
|
||||||
row = 0
|
row = 0
|
||||||
|
|
||||||
def label(text):
|
def label(text, _row=None, _column=None):
|
||||||
nonlocal row
|
nonlocal row
|
||||||
Label(self.frame, text=text).grid()
|
Label(self.frame, text=text).grid(row=_row and _row or row, column=_column and _column or 0)
|
||||||
|
|
||||||
row += 1
|
if _row is None:
|
||||||
|
row += 1
|
||||||
|
|
||||||
def scrable_row(unit_type, unit_count):
|
def scrable_row(unit_type, unit_count):
|
||||||
nonlocal row
|
nonlocal row
|
||||||
Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count)).grid(row=row)
|
Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count)).grid(row=row, sticky=W)
|
||||||
scramble_entry = Entry(self.frame)
|
scramble_entry = Entry(self.frame, width=10)
|
||||||
scramble_entry.grid(column=1, row=row)
|
scramble_entry.grid(column=1, row=row)
|
||||||
|
scramble_entry.insert(0, "0")
|
||||||
self.aircraft_scramble_entries[unit_type] = scramble_entry
|
self.aircraft_scramble_entries[unit_type] = scramble_entry
|
||||||
|
|
||||||
client_entry = Entry(self.frame)
|
client_entry = Entry(self.frame, width=10)
|
||||||
client_entry.grid(column=2, row=row)
|
client_entry.grid(column=2, row=row)
|
||||||
|
client_entry.insert(0, "0")
|
||||||
self.aircraft_client_entries[unit_type] = client_entry
|
self.aircraft_client_entries[unit_type] = client_entry
|
||||||
|
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
def scramble_armor_row(unit_type, unit_count):
|
def scramble_armor_row(unit_type, unit_count):
|
||||||
nonlocal row
|
nonlocal row
|
||||||
Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count)).grid(row=row)
|
Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count)).grid(row=row, sticky=W)
|
||||||
scramble_entry = Entry(self.frame)
|
scramble_entry = Entry(self.frame, width=10)
|
||||||
|
scramble_entry.insert(0, "0")
|
||||||
scramble_entry.grid(column=1, row=row)
|
scramble_entry.grid(column=1, row=row)
|
||||||
self.armor_scramble_entries[unit_type] = scramble_entry
|
self.armor_scramble_entries[unit_type] = scramble_entry
|
||||||
|
|
||||||
@ -58,13 +62,23 @@ class EventMenu(Menu):
|
|||||||
base = self.event.to_cp.base
|
base = self.event.to_cp.base
|
||||||
|
|
||||||
label("Aircraft")
|
label("Aircraft")
|
||||||
|
label("Amount", row, 1)
|
||||||
|
label("Client slots", row, 2)
|
||||||
|
row+=1
|
||||||
|
|
||||||
for unit_type, count in base.aircraft.items():
|
for unit_type, count in base.aircraft.items():
|
||||||
scrable_row(unit_type, count)
|
scrable_row(unit_type, count)
|
||||||
|
|
||||||
|
if not base.total_planes:
|
||||||
|
label("None")
|
||||||
|
|
||||||
label("Armor")
|
label("Armor")
|
||||||
for unit_type, count in base.armor.items():
|
for unit_type, count in base.armor.items():
|
||||||
scramble_armor_row(unit_type, count)
|
scramble_armor_row(unit_type, count)
|
||||||
|
|
||||||
|
if not base.total_armor:
|
||||||
|
label("None")
|
||||||
|
|
||||||
Button(self.frame, text="Commit", command=self.start).grid(column=0, row=row)
|
Button(self.frame, text="Commit", command=self.start).grid(column=0, row=row)
|
||||||
Button(self.frame, text="Back", command=self.dismiss).grid(column=2, row=row)
|
Button(self.frame, text="Back", command=self.dismiss).grid(column=2, row=row)
|
||||||
|
|
||||||
|
|||||||
@ -21,14 +21,14 @@ class EventResultsMenu(Menu):
|
|||||||
self.window.clear_right_pane()
|
self.window.clear_right_pane()
|
||||||
|
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
Button(self.frame, text="no losses, succ", command=self.simulate_result(0, 1, True)).grid()
|
Button(self.frame, text="no losses, succ", command=self.simulate_result(0, 1)).grid()
|
||||||
Button(self.frame, text="no losses, fail", command=self.simulate_result(0, 1, False)).grid(row=1, column=1)
|
Button(self.frame, text="no losses, fail", command=self.simulate_result(0, 1)).grid(row=1, column=1)
|
||||||
|
|
||||||
Button(self.frame, text="half losses, succ", command=self.simulate_result(0.5, 0.5, True)).grid(row=2, )
|
Button(self.frame, text="half losses, succ", command=self.simulate_result(0.5, 0.5)).grid(row=2, )
|
||||||
Button(self.frame, text="half losses, fail", command=self.simulate_result(0.5, 0.5, False)).grid(row=2, column=1)
|
Button(self.frame, text="half losses, fail", command=self.simulate_result(0.5, 0.5)).grid(row=2, column=1)
|
||||||
|
|
||||||
Button(self.frame, text="full losses, succ", command=self.simulate_result(1, 0, True)).grid(row=3, )
|
Button(self.frame, text="full losses, succ", command=self.simulate_result(1, 0)).grid(row=3, )
|
||||||
Button(self.frame, text="full losses, fail", command=self.simulate_result(1, 0, False)).grid(row=3, column=1)
|
Button(self.frame, text="full losses, fail", command=self.simulate_result(1, 0)).grid(row=3, column=1)
|
||||||
|
|
||||||
Label(self.frame, text="Play the mission and save debriefing to {}".format(debriefing_directory_location())).grid(row=0, column=0)
|
Label(self.frame, text="Play the mission and save debriefing to {}".format(debriefing_directory_location())).grid(row=0, column=0)
|
||||||
else:
|
else:
|
||||||
@ -55,6 +55,10 @@ class EventResultsMenu(Menu):
|
|||||||
Button(self.frame, text="Okay", command=self.dismiss).grid(columnspan=1, row=row); row += 1
|
Button(self.frame, text="Okay", command=self.dismiss).grid(columnspan=1, row=row); row += 1
|
||||||
|
|
||||||
def process_debriefing(self, debriefing: Debriefing):
|
def process_debriefing(self, debriefing: Debriefing):
|
||||||
|
debriefing.calculate_destroyed_units(mission=self.event.operation.mission,
|
||||||
|
player_name=self.game.player,
|
||||||
|
enemy_name=self.game.enemy)
|
||||||
|
|
||||||
self.game.finish_event(event=self.event, debriefing=debriefing)
|
self.game.finish_event(event=self.event, debriefing=debriefing)
|
||||||
self.game.pass_turn()
|
self.game.pass_turn()
|
||||||
|
|
||||||
@ -63,7 +67,7 @@ class EventResultsMenu(Menu):
|
|||||||
self.enemy_losses = debriefing.destroyed_units.get(self.game.enemy, {})
|
self.enemy_losses = debriefing.destroyed_units.get(self.game.enemy, {})
|
||||||
self.display()
|
self.display()
|
||||||
|
|
||||||
def simulate_result(self, player_factor: float, enemy_factor: float, result: bool):
|
def simulate_result(self, player_factor: float, enemy_factor: float):
|
||||||
def action():
|
def action():
|
||||||
debriefing = Debriefing()
|
debriefing = Debriefing()
|
||||||
|
|
||||||
|
|||||||
@ -34,9 +34,9 @@ class MainMenu(Menu):
|
|||||||
def event_button(event):
|
def event_button(event):
|
||||||
nonlocal row
|
nonlocal row
|
||||||
Message(self.frame, text="{}{}".format(
|
Message(self.frame, text="{}{}".format(
|
||||||
event.defender.name == self.game.player and "Enemy attacking: " or "",
|
event.defender_name == self.game.player and "Enemy attacking: " or "",
|
||||||
event
|
event
|
||||||
), aspect=500).grid(column=0, row=row, sticky=NW)
|
), aspect=800).grid(column=0, row=row, sticky=NW)
|
||||||
Button(self.frame, text=">", command=self.start_event(event)).grid(column=0, row=row, sticky=NE+S)
|
Button(self.frame, text=">", command=self.start_event(event)).grid(column=0, row=row, sticky=NE+S)
|
||||||
row += 1
|
row += 1
|
||||||
Separator(self.frame, orient='horizontal').grid(row=row, sticky=EW); row += 1
|
Separator(self.frame, orient='horizontal').grid(row=row, sticky=EW); row += 1
|
||||||
|
|||||||
@ -1,26 +1,67 @@
|
|||||||
import typing
|
import typing
|
||||||
|
|
||||||
import json
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from datetime import datetime
|
from dcs.lua import parse
|
||||||
|
from dcs.mission import Mission
|
||||||
|
|
||||||
|
from dcs.unitgroup import FlyingGroup
|
||||||
|
from dcs.unit import UnitType
|
||||||
|
|
||||||
|
from game import db
|
||||||
|
|
||||||
DEBRIEFING_LOG_EXTENSION = "log"
|
DEBRIEFING_LOG_EXTENSION = "log"
|
||||||
|
|
||||||
|
|
||||||
class Debriefing:
|
class Debriefing:
|
||||||
def __init__(self):
|
def __init__(self, alive_units):
|
||||||
self.destroyed_units = {} # type: typing.Dict[str, typing.Dict[str, int]]
|
self.destroyed_units = {} # type: typing.Dict[str, typing.Dict[str, int]]
|
||||||
|
self.alive_units = alive_units # type: typing.Dict[str, typing.Dict[str, int]]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, path: str):
|
def parse(cls, path: str, mission: Mission):
|
||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
events = json.load(f)
|
table_string = f.read()
|
||||||
|
table = parse.loads(table_string)
|
||||||
|
units = table.get("debriefing", {}).get("world_state", {})
|
||||||
|
alive_units = {}
|
||||||
|
|
||||||
return Debriefing()
|
for unit in units:
|
||||||
|
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
|
||||||
|
|
||||||
|
return Debriefing(alive_units)
|
||||||
|
|
||||||
|
def calculate_destroyed_units(self, mission: Mission, player_name: str, enemy_name: str):
|
||||||
|
def count_groups(groups: typing.List[UnitType]) -> typing.Dict[UnitType, int]:
|
||||||
|
result = {}
|
||||||
|
for group in groups:
|
||||||
|
for unit in group.units:
|
||||||
|
result[unit.unit_type] = result.get(unit.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)]
|
||||||
|
return result
|
||||||
|
|
||||||
|
player = mission.country(player_name)
|
||||||
|
enemy = mission.country(enemy_name)
|
||||||
|
|
||||||
|
player_units = count_groups(player.plane_group + player.vehicle_group)
|
||||||
|
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]),
|
||||||
|
}
|
||||||
|
|
||||||
def debriefing_directory_location() -> str:
|
def debriefing_directory_location() -> str:
|
||||||
return "build/debriefing"
|
return "build/debriefing"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user