mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +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:
@@ -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)
|
||||
Reference in New Issue
Block a user