mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
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:
parent
d5a081a15f
commit
f6fad30852
@ -1,176 +1,209 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
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.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"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DebriefingDeadUnitInfo:
|
||||
country_id = -1
|
||||
player_unit = False
|
||||
type = None
|
||||
player_unit: bool
|
||||
type: Type[UnitType]
|
||||
|
||||
def __init__(self, country_id, player_unit , type):
|
||||
self.country_id = country_id
|
||||
self.player_unit = player_unit
|
||||
self.type = type
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.country_id) + " " + str(self.player_unit) + " " + str(self.type)
|
||||
@dataclass(frozen=True)
|
||||
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:
|
||||
def __init__(self, state_data, game):
|
||||
self.state_data = state_data
|
||||
self.killed_aircrafts = state_data["killed_aircrafts"]
|
||||
self.killed_ground_units = state_data["killed_ground_units"]
|
||||
self.weapons_fired = state_data["weapons_fired"]
|
||||
self.mission_ended = state_data["mission_ended"]
|
||||
self.destroyed_units = state_data["destroyed_objects_positions"]
|
||||
def __init__(self, state_data: Dict[str, Any], game: Game,
|
||||
unit_map: UnitMap) -> None:
|
||||
self.state_data = StateData.from_json(state_data)
|
||||
self.game = game
|
||||
self.unit_map = unit_map
|
||||
|
||||
self.__destroyed_units = []
|
||||
logging.info("--------------------------------")
|
||||
logging.info("Starting Debriefing preprocessing")
|
||||
logging.info("--------------------------------")
|
||||
logging.info(self.base_capture_events)
|
||||
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(self.state_data)
|
||||
logging.info("--------------------------------")
|
||||
|
||||
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.dead_aircraft = []
|
||||
self.dead_units = []
|
||||
self.dead_aaa_groups = []
|
||||
self.dead_buildings = []
|
||||
self.air_losses = self.dead_aircraft()
|
||||
self.dead_units: List[DebriefingDeadUnitInfo] = []
|
||||
self.dead_aaa_groups: List[DebriefingDeadUnitInfo] = []
|
||||
self.dead_buildings: List[DebriefingDeadBuildingInfo] = []
|
||||
|
||||
for aircraft in self.killed_aircrafts:
|
||||
for unit_name in self.state_data.killed_ground_units:
|
||||
try:
|
||||
country = int(aircraft.split("|")[1])
|
||||
type = db.unit_type_from_name(aircraft.split("|")[4])
|
||||
player_unit = (country == self.player_country_id)
|
||||
aircraft = DebriefingDeadUnitInfo(country, player_unit, type)
|
||||
if type is not None:
|
||||
self.dead_aircraft.append(aircraft)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
if isinstance(unit_name, int):
|
||||
# For some reason the state file will include many raw
|
||||
# integers in the list of destroyed units. These might be
|
||||
# from the smoke effects?
|
||||
continue
|
||||
country = int(unit_name.split("|")[1])
|
||||
unit_type = db.unit_type_from_name(unit_name.split("|")[4])
|
||||
if unit_type is None:
|
||||
logging.error(f"Could not determine type of {unit_name}")
|
||||
continue
|
||||
player_unit = country == self.player_country_id
|
||||
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:
|
||||
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 unit_name in self.state_data.killed_ground_units:
|
||||
for cp in game.theater.controlpoints:
|
||||
|
||||
logging.info(cp.name)
|
||||
logging.info(cp.captured)
|
||||
|
||||
if cp.captured:
|
||||
country = self.player_country_id
|
||||
else:
|
||||
country = self.enemy_country_id
|
||||
player_unit = (country == self.player_country_id)
|
||||
|
||||
for i, ground_object in enumerate(cp.ground_objects):
|
||||
logging.info(unit)
|
||||
logging.info(ground_object.group_name)
|
||||
if ground_object.is_same_group(unit):
|
||||
unit = DebriefingDeadUnitInfo(country, player_unit, ground_object.dcs_identifier)
|
||||
self.dead_buildings.append(unit)
|
||||
elif ground_object.dcs_identifier in ["AA", "CARRIER", "LHA"]:
|
||||
for ground_object in cp.ground_objects:
|
||||
# TODO: This seems to destroy an arbitrary building?
|
||||
if ground_object.is_same_group(unit_name):
|
||||
self.dead_buildings.append(
|
||||
DebriefingDeadBuildingInfo(ground_object))
|
||||
elif ground_object.dcs_identifier in ["AA", "CARRIER",
|
||||
"LHA"]:
|
||||
for g in ground_object.groups:
|
||||
for u in g.units:
|
||||
if u.name == unit:
|
||||
unit = DebriefingDeadUnitInfo(country, player_unit, db.unit_type_from_name(u.type))
|
||||
self.dead_units.append(unit)
|
||||
if u.name != unit_name:
|
||||
continue
|
||||
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.enemy_dead_aircraft = [a for a in self.dead_aircraft if a.country_id == self.enemy_country_id]
|
||||
self.player_dead_units = [a for a in self.dead_units if a.country_id == self.player_country_id]
|
||||
self.enemy_dead_units = [a for a in self.dead_units if a.country_id == self.enemy_country_id]
|
||||
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]
|
||||
self.player_dead_units = [a for a in self.dead_units if a.player_unit]
|
||||
self.enemy_dead_units = [a for a in self.dead_units if not a.player_unit]
|
||||
self.player_dead_buildings = [a for a in self.dead_buildings if a.player_unit]
|
||||
self.enemy_dead_buildings = [a for a in self.dead_buildings if not a.player_unit]
|
||||
|
||||
logging.info(self.player_dead_aircraft)
|
||||
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 = {}
|
||||
self.player_dead_units_dict: Dict[Type[UnitType], int] = defaultdict(int)
|
||||
for a in self.player_dead_units:
|
||||
if a.type in self.player_dead_units_dict.keys():
|
||||
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.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:
|
||||
if a.type in self.enemy_dead_units_dict.keys():
|
||||
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.enemy_dead_units_dict[a.type] += 1
|
||||
|
||||
self.player_dead_buildings_dict = {}
|
||||
for a in self.player_dead_buildings:
|
||||
if a.type in self.player_dead_buildings_dict.keys():
|
||||
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.player_dead_buildings_dict: Dict[str, int] = defaultdict(int)
|
||||
for b in self.player_dead_buildings:
|
||||
self.player_dead_buildings_dict[b.ground_object.dcs_identifier] += 1
|
||||
|
||||
self.enemy_dead_buildings_dict = {}
|
||||
for a in self.enemy_dead_buildings:
|
||||
if a.type in self.enemy_dead_buildings_dict.keys():
|
||||
self.enemy_dead_buildings_dict[a.type] = self.enemy_dead_buildings_dict[a.type] + 1
|
||||
else:
|
||||
self.enemy_dead_buildings_dict[a.type] = 1
|
||||
self.enemy_dead_buildings_dict: Dict[str, int] = defaultdict(int)
|
||||
for b in self.enemy_dead_buildings:
|
||||
self.enemy_dead_buildings_dict[b.ground_object.dcs_identifier] += 1
|
||||
|
||||
logging.info("--------------------------------")
|
||||
logging.info("Debriefing pre process results :")
|
||||
logging.info("--------------------------------")
|
||||
logging.info(self.player_dead_aircraft_dict)
|
||||
logging.info(self.enemy_dead_aircraft_dict)
|
||||
logging.info(self.air_losses)
|
||||
logging.info(self.player_dead_units_dict)
|
||||
logging.info(self.enemy_dead_units_dict)
|
||||
logging.info(self.player_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
|
||||
def base_capture_events(self):
|
||||
"""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]]
|
||||
"""Keeps only the last instance of a base capture event for each base ID."""
|
||||
reversed_captures = list(reversed(self.state_data.base_capture_events))
|
||||
last_base_cap_indexes = []
|
||||
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]:
|
||||
continue
|
||||
else:
|
||||
if base not in [x[1] for x in last_base_cap_indexes]:
|
||||
last_base_cap_indexes.append((idx, base))
|
||||
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
|
||||
regularly for the stopped() condition."""
|
||||
|
||||
def __init__(self, callback: typing.Callable, game):
|
||||
super(PollDebriefingFileThread, self).__init__()
|
||||
def __init__(self, callback: Callable[[Debriefing], None],
|
||||
game: Game, unit_map: UnitMap) -> None:
|
||||
super().__init__()
|
||||
self._stop_event = threading.Event()
|
||||
self.callback = callback
|
||||
self.game = game
|
||||
self.unit_map = unit_map
|
||||
|
||||
def stop(self):
|
||||
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:
|
||||
with open("state.json", "r") as 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)
|
||||
break
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def wait_for_debriefing(callback: typing.Callable, game)->PollDebriefingFileThread:
|
||||
thread = PollDebriefingFileThread(callback, game)
|
||||
def wait_for_debriefing(callback: Callable[[Debriefing], None],
|
||||
game: Game, unit_map) -> PollDebriefingFileThread:
|
||||
thread = PollDebriefingFileThread(callback, game, unit_map)
|
||||
thread.start()
|
||||
return thread
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ from game.debriefing import Debriefing
|
||||
from game.infos.information import Information
|
||||
from game.theater import ControlPoint
|
||||
from gen.ground_forces.combat_stance import CombatStance
|
||||
from ..unitmap import UnitMap
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..game import Game
|
||||
@ -85,17 +86,18 @@ class Event:
|
||||
def bonus(self) -> int:
|
||||
return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE)
|
||||
|
||||
def is_successfull(self, debriefing: Debriefing) -> bool:
|
||||
return self.operation.is_successfull(debriefing)
|
||||
def is_successful(self, debriefing: Debriefing) -> bool:
|
||||
return self.operation.is_successful(debriefing)
|
||||
|
||||
def generate(self):
|
||||
def generate(self) -> UnitMap:
|
||||
self.operation.is_awacs_enabled = self.is_awacs_enabled
|
||||
self.operation.ca_slots = self.ca_slots
|
||||
|
||||
self.operation.prepare(self.game)
|
||||
self.operation.generate()
|
||||
self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn.miz"))
|
||||
self.environment_settings = self.operation.environment_settings
|
||||
unit_map = self.operation.generate()
|
||||
self.operation.current_mission.save(
|
||||
persistency.mission_path_for("liberation_nextturn.miz"))
|
||||
return unit_map
|
||||
|
||||
def commit(self, debriefing: Debriefing):
|
||||
|
||||
@ -103,41 +105,39 @@ class Event:
|
||||
|
||||
# ------------------------------
|
||||
# Destroyed aircrafts
|
||||
cp_map = {cp.id: cp for cp in self.game.theater.controlpoints}
|
||||
for destroyed_aircraft in debriefing.killed_aircrafts:
|
||||
try:
|
||||
cpid = int(destroyed_aircraft.split("|")[3])
|
||||
aircraft = db.unit_type_from_name(
|
||||
destroyed_aircraft.split("|")[4])
|
||||
if cpid in cp_map:
|
||||
cp = cp_map[cpid]
|
||||
if aircraft in cp.base.aircraft:
|
||||
logging.info(f"Aircraft destroyed: {aircraft}")
|
||||
cp.base.aircraft[aircraft] = max(
|
||||
0, cp.base.aircraft[aircraft] - 1)
|
||||
except Exception:
|
||||
logging.exception("Failed to commit destroyed aircraft")
|
||||
for loss in debriefing.air_losses.losses:
|
||||
aircraft = loss.flight.unit_type
|
||||
cp = loss.flight.departure
|
||||
available = cp.base.total_units_of_type(aircraft)
|
||||
if available <= 0:
|
||||
logging.error(
|
||||
f"Found killed {aircraft} from {cp} but that airbase has "
|
||||
f"none available.")
|
||||
continue
|
||||
|
||||
logging.info(f"{aircraft} destroyed from {cp}")
|
||||
cp.base.aircraft[aircraft] -= 1
|
||||
|
||||
# ------------------------------
|
||||
# Destroyed ground units
|
||||
killed_unit_count_by_cp = {cp.id: 0 for cp in self.game.theater.controlpoints}
|
||||
cp_map = {cp.id: cp for cp in self.game.theater.controlpoints}
|
||||
for killed_ground_unit in debriefing.killed_ground_units:
|
||||
for killed_ground_unit in debriefing.state_data.killed_ground_units:
|
||||
try:
|
||||
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():
|
||||
killed_unit_count_by_cp[cpid] = killed_unit_count_by_cp[cpid] + 1
|
||||
cp = cp_map[cpid]
|
||||
if aircraft in cp.base.armor.keys():
|
||||
logging.info("Ground unit destroyed : " + str(aircraft))
|
||||
cp.base.armor[aircraft] = max(0, cp.base.armor[aircraft] - 1)
|
||||
if unit_type in cp.base.armor.keys():
|
||||
logging.info(f"Ground unit destroyed: {unit_type}")
|
||||
cp.base.armor[unit_type] = max(0, cp.base.armor[unit_type] - 1)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
# ------------------------------
|
||||
# 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:
|
||||
if not cp.ground_objects:
|
||||
continue
|
||||
@ -224,7 +224,7 @@ class Event:
|
||||
|
||||
# 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)
|
||||
|
||||
# -----------------------------------
|
||||
|
||||
@ -23,7 +23,7 @@ class FrontlineAttackEvent(Event):
|
||||
def __str__(self):
|
||||
return "Frontline attack"
|
||||
|
||||
def is_successfull(self, debriefing: Debriefing):
|
||||
def is_successful(self, debriefing: Debriefing):
|
||||
attackers_success = True
|
||||
if self.from_cp.captured:
|
||||
return attackers_success
|
||||
|
||||
@ -27,6 +27,7 @@ from .factions.faction import Faction
|
||||
from .infos.information import Information
|
||||
from .settings import Settings
|
||||
from .theater import ConflictTheater, ControlPoint, OffMapSpawn
|
||||
from .unitmap import UnitMap
|
||||
from .weather import Conditions, TimeOfDay
|
||||
|
||||
COMMISION_UNIT_VARIETY = 4
|
||||
@ -173,15 +174,15 @@ class Game:
|
||||
self.events.append(event)
|
||||
return event
|
||||
|
||||
def initiate_event(self, event: Event):
|
||||
def initiate_event(self, event: Event) -> UnitMap:
|
||||
#assert event in self.events
|
||||
logging.info("Generating {} (regular)".format(event))
|
||||
event.generate()
|
||||
return event.generate()
|
||||
|
||||
def finish_event(self, event: Event, debriefing: Debriefing):
|
||||
logging.info("Finishing event {}".format(event))
|
||||
event.commit(debriefing)
|
||||
if event.is_successfull(debriefing):
|
||||
if event.is_successful(debriefing):
|
||||
self.budget += event.bonus()
|
||||
|
||||
if event in self.events:
|
||||
|
||||
@ -34,6 +34,7 @@ from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
|
||||
|
||||
from .. import db
|
||||
from ..debriefing import Debriefing
|
||||
from ..unitmap import UnitMap
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@ -59,6 +60,7 @@ class Operation:
|
||||
is_quick = None
|
||||
is_awacs_enabled = False
|
||||
ca_slots = 0
|
||||
unit_map: UnitMap
|
||||
|
||||
def __init__(self,
|
||||
departure_cp: ControlPoint,
|
||||
@ -94,7 +96,7 @@ class Operation:
|
||||
def units_of(self, country_name: str) -> List[UnitType]:
|
||||
return []
|
||||
|
||||
def is_successfull(self, debriefing: Debriefing) -> bool:
|
||||
def is_successful(self, debriefing: Debriefing) -> bool:
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
@ -177,6 +179,10 @@ class Operation:
|
||||
gen.add_flight(flight)
|
||||
gen.generate()
|
||||
|
||||
@classmethod
|
||||
def create_unit_map(cls) -> None:
|
||||
cls.unit_map = UnitMap()
|
||||
|
||||
@classmethod
|
||||
def create_radio_registries(cls) -> None:
|
||||
unique_map_frequencies = set() # type: Set[RadioFrequency]
|
||||
@ -269,8 +275,9 @@ class Operation:
|
||||
dead=True,
|
||||
)
|
||||
|
||||
def generate(self):
|
||||
def generate(self) -> UnitMap:
|
||||
"""Build the final Mission to be exported"""
|
||||
self.create_unit_map()
|
||||
self.create_radio_registries()
|
||||
# Set mission time and weather conditions.
|
||||
EnvironmentGenerator(self.current_mission,
|
||||
@ -323,6 +330,8 @@ class Operation:
|
||||
self.airgen
|
||||
)
|
||||
|
||||
return self.unit_map
|
||||
|
||||
@classmethod
|
||||
def _generate_air_units(cls) -> None:
|
||||
"""Generate the air units for the Operation"""
|
||||
@ -339,7 +348,7 @@ class Operation:
|
||||
# Generate Aircraft Activity on the map
|
||||
cls.airgen = AircraftConflictGenerator(
|
||||
cls.current_mission, cls.game.settings, cls.game,
|
||||
cls.radio_registry)
|
||||
cls.radio_registry, cls.unit_map)
|
||||
|
||||
cls.airgen.generate_flights(
|
||||
cls.current_mission.country(cls.game.player_country),
|
||||
|
||||
23
game/unitmap.py
Normal file
23
game/unitmap.py
Normal 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)
|
||||
@ -76,6 +76,7 @@ from game.theater.controlpoint import (
|
||||
OffMapSpawn,
|
||||
)
|
||||
from game.theater.theatergroundobject import TheaterGroundObject
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import knots_to_kph, nm_to_meter
|
||||
from gen.airsupportgen import AirSupport
|
||||
from gen.ato import AirTaskingOrder, Package
|
||||
@ -647,12 +648,13 @@ AIRCRAFT_DATA["P-47D-30"] = AIRCRAFT_DATA["P-51D"]
|
||||
|
||||
|
||||
class AircraftConflictGenerator:
|
||||
def __init__(self, mission: Mission, settings: Settings,
|
||||
game: Game, radio_registry: RadioRegistry):
|
||||
def __init__(self, mission: Mission, settings: Settings, game: Game,
|
||||
radio_registry: RadioRegistry, unit_map: UnitMap) -> None:
|
||||
self.m = mission
|
||||
self.game = game
|
||||
self.settings = settings
|
||||
self.radio_registry = radio_registry
|
||||
self.unit_map = unit_map
|
||||
self.flights: List[FlightData] = []
|
||||
|
||||
@cached_property
|
||||
@ -927,6 +929,7 @@ class AircraftConflictGenerator:
|
||||
logging.info(f"Generating flight: {flight.unit_type}")
|
||||
group = self.generate_planned_flight(flight.from_cp, country,
|
||||
flight)
|
||||
self.unit_map.add_aircraft(group, flight)
|
||||
self.setup_flight_group(group, package, flight, dynamic_runways)
|
||||
self.create_waypoints(group, package, flight)
|
||||
|
||||
|
||||
@ -235,8 +235,9 @@ class QTopPanel(QFrame):
|
||||
game_event.departure_cp = self.game.theater.controlpoints[0]
|
||||
game_event.player_attacking()
|
||||
|
||||
self.game.initiate_event(game_event)
|
||||
waiting = QWaitingForMissionResultWindow(game_event, self.game)
|
||||
unit_map = self.game.initiate_event(game_event)
|
||||
waiting = QWaitingForMissionResultWindow(game_event, self.game,
|
||||
unit_map)
|
||||
waiting.show()
|
||||
|
||||
def budget_update(self, game:Game):
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
from PySide2.QtGui import QIcon, QPixmap
|
||||
from PySide2.QtWidgets import (
|
||||
QDialog,
|
||||
@ -55,29 +57,35 @@ class QDebriefingWindow(QDialog):
|
||||
lostUnits.setLayout(lostUnitsLayout)
|
||||
|
||||
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:
|
||||
lostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0)
|
||||
lostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
|
||||
lostUnitsLayout.addWidget(
|
||||
QLabel(db.unit_type_name(unit_type)), row, 0)
|
||||
lostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
|
||||
row += 1
|
||||
except:
|
||||
print("Issue adding " + str(unit_type) + " to debriefing information")
|
||||
except AttributeError:
|
||||
logging.exception(
|
||||
f"Issue adding {unit_type} to debriefing information")
|
||||
|
||||
for unit_type, count in self.debriefing.player_dead_units_dict.items():
|
||||
try:
|
||||
lostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0)
|
||||
lostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
|
||||
lostUnitsLayout.addWidget(
|
||||
QLabel(db.unit_type_name(unit_type)), row, 0)
|
||||
lostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
|
||||
row += 1
|
||||
except:
|
||||
print("Issue adding " + str(unit_type) + " to debriefing information")
|
||||
except AttributeError:
|
||||
logging.exception(
|
||||
f"Issue adding {unit_type} to debriefing information")
|
||||
|
||||
for building, count in self.debriefing.player_dead_buildings_dict.items():
|
||||
try:
|
||||
lostUnitsLayout.addWidget(QLabel(building, row, 0))
|
||||
lostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
|
||||
lostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
|
||||
row += 1
|
||||
except:
|
||||
print("Issue adding " + str(building) + " to debriefing information")
|
||||
except AttributeError:
|
||||
logging.exception(
|
||||
f"Issue adding {building} to debriefing information")
|
||||
|
||||
self.layout.addWidget(lostUnits)
|
||||
|
||||
@ -92,15 +100,16 @@ class QDebriefingWindow(QDialog):
|
||||
# enemylostUnitsLayout.addWidget(QLabel("{}".format(len(self.debriefing.destroyed_objects))), row, 1)
|
||||
# row += 1
|
||||
|
||||
for unit_type, count in self.debriefing.enemy_dead_aircraft_dict.items():
|
||||
if count == 0:
|
||||
continue
|
||||
enemy_air_losses = self.debriefing.air_losses.by_type(player=False)
|
||||
for unit_type, count in enemy_air_losses.items():
|
||||
try:
|
||||
enemylostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0)
|
||||
enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
|
||||
enemylostUnitsLayout.addWidget(
|
||||
QLabel(db.unit_type_name(unit_type)), row, 0)
|
||||
enemylostUnitsLayout.addWidget(QLabel(str(count)), row, 1)
|
||||
row += 1
|
||||
except:
|
||||
print("Issue adding " + str(unit_type) + " to debriefing information")
|
||||
except AttributeError:
|
||||
logging.exception(
|
||||
f"Issue adding {unit_type} to debriefing information")
|
||||
|
||||
for unit_type, count in self.debriefing.enemy_dead_units_dict.items():
|
||||
if count == 0:
|
||||
@ -114,8 +123,9 @@ class QDebriefingWindow(QDialog):
|
||||
enemylostUnitsLayout.addWidget(QLabel(building), row, 0)
|
||||
enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
|
||||
row += 1
|
||||
except:
|
||||
print("Issue adding " + str(building) + " to debriefing information")
|
||||
except AttributeError:
|
||||
logging.exception(
|
||||
f"Issue adding {building} to debriefing information")
|
||||
|
||||
self.layout.addWidget(enemylostUnits)
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
from game.debriefing import Debriefing, wait_for_debriefing
|
||||
from game.game import Event, Game, logging
|
||||
from game.persistency import base_path
|
||||
from game.unitmap import UnitMap
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
|
||||
|
||||
@ -39,22 +40,27 @@ class DebriefingFileWrittenSignal(QObject):
|
||||
def get_instance():
|
||||
return DebriefingFileWrittenSignal.instance
|
||||
|
||||
|
||||
DebriefingFileWrittenSignal()
|
||||
|
||||
|
||||
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__()
|
||||
self.setModal(True)
|
||||
self.gameEvent = gameEvent
|
||||
self.game = game
|
||||
self.unit_map = unit_map
|
||||
self.setWindowTitle("Waiting for mission completion.")
|
||||
self.setWindowIcon(QIcon("./resources/icon.png"))
|
||||
self.setMinimumHeight(570)
|
||||
|
||||
self.initUi()
|
||||
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):
|
||||
self.layout = QGridLayout()
|
||||
@ -119,17 +125,17 @@ class QWaitingForMissionResultWindow(QDialog):
|
||||
self.layout.addLayout(self.gridLayout, 1, 0)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def updateLayout(self, debriefing):
|
||||
def updateLayout(self, debriefing: Debriefing) -> None:
|
||||
updateBox = QGroupBox("Mission status")
|
||||
updateLayout = QGridLayout()
|
||||
updateBox.setLayout(updateLayout)
|
||||
self.debriefing = debriefing
|
||||
|
||||
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(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(str(len(debriefing.weapons_fired))), 2, 1)
|
||||
@ -142,28 +148,27 @@ class QWaitingForMissionResultWindow(QDialog):
|
||||
try:
|
||||
self.gridLayout.itemAt(i).widget().setParent(None)
|
||||
except:
|
||||
pass
|
||||
logging.exception("Failed to clear window")
|
||||
|
||||
# Set new window content
|
||||
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(self.actions, 2, 0)
|
||||
else:
|
||||
self.gridLayout.addWidget(QLabel("<b>Mission is over</b>"), 1, 0)
|
||||
self.gridLayout.addWidget(self.actions2, 2, 0)
|
||||
|
||||
|
||||
def on_debriefing_udpate(self, debriefing):
|
||||
def on_debriefing_update(self, debriefing: Debriefing) -> None:
|
||||
try:
|
||||
logging.info("On Debriefing update")
|
||||
print(debriefing)
|
||||
logging.debug(debriefing)
|
||||
DebriefingFileWrittenSignal.get_instance().sendDebriefing(debriefing)
|
||||
except Exception as e:
|
||||
logging.error("Got an error while sending debriefing")
|
||||
logging.error(e)
|
||||
self.wait_thread = wait_for_debriefing(lambda debriefing: self.on_debriefing_udpate(debriefing), self.game)
|
||||
except Exception:
|
||||
logging.exception("Got an error while sending debriefing")
|
||||
self.wait_thread = wait_for_debriefing(
|
||||
lambda d: self.on_debriefing_update(d), self.game, self.unit_map)
|
||||
|
||||
def process_debriefing(self):
|
||||
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:
|
||||
json_data = json.load(json_file)
|
||||
json_data["mission_ended"] = True
|
||||
debriefing = Debriefing(json_data, self.game)
|
||||
self.on_debriefing_udpate(debriefing)
|
||||
debriefing = Debriefing(json_data, self.game, self.unit_map)
|
||||
self.on_debriefing_update(debriefing)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
msg = QMessageBox()
|
||||
|
||||
@ -6,7 +6,6 @@ logger:info("Check that json.lua is loaded : json = "..tostring(json))
|
||||
|
||||
killed_aircrafts = {}
|
||||
killed_ground_units = {}
|
||||
weapons_fired = {}
|
||||
base_capture_events = {}
|
||||
destroyed_objects_positions = {}
|
||||
mission_ended = false
|
||||
@ -33,7 +32,6 @@ function write_state()
|
||||
local game_state = {
|
||||
["killed_aircrafts"] = killed_aircrafts,
|
||||
["killed_ground_units"] = killed_ground_units,
|
||||
["weapons_fired"] = weapons_fired,
|
||||
["base_capture_events"] = base_capture_events,
|
||||
["mission_ended"] = mission_ended,
|
||||
["destroyed_objects_positions"] = destroyed_objects_positions,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user