mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
- Fix tgogenerator - Fix UI for ForceGroup and Layouts - Fix ammo depot handling - Split bigger files in smaller meaningful files (TGO, layouts, forces) - Renamed Template to Layout - Renamed GroundGroup to TheaterGroup and GroundUnit to TheaterUnit - Reorganize Layouts and UnitGroups to a ArmedForces class and ForceGroup similar to the AirWing and Squadron - Reworded the UnitClass, GroupRole, GroupTask (adopted to PEP8) and reworked the connection from Role and Task - added comments - added missing unit classes - added temp workaround for missing classes - add repariable property to TheaterUnit - Review and Cleanup Added serialization for loaded templates Loading the templates from the .miz files takes a lot of computation time and in the future there will be more templates added to the system. Therefore a local pickle serialization for the loaded templates was re-added: - The pickle will be created the first time the TemplateLoader will be accessed - Pickle is stored in Liberation SaveDir - Added UI option to (re-)import templates
204 lines
7.9 KiB
Python
204 lines
7.9 KiB
Python
from __future__ import annotations
|
|
|
|
import itertools
|
|
import logging
|
|
import pickle
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from pathlib import Path
|
|
from typing import Iterator
|
|
|
|
import dcs
|
|
import yaml
|
|
from dcs import Point
|
|
from dcs.unitgroup import StaticGroup
|
|
|
|
from game import persistency
|
|
from game.data.groups import GroupRole, GroupTask
|
|
from game.layout.layout import (
|
|
TheaterLayout,
|
|
GroupLayout,
|
|
LayoutUnit,
|
|
AntiAirLayout,
|
|
BuildingLayout,
|
|
NavalLayout,
|
|
GroundForceLayout,
|
|
DefensesLayout,
|
|
)
|
|
from game.layout.layoutmapping import GroupLayoutMapping, LayoutMapping
|
|
from game.profiling import logged_duration
|
|
from game.version import VERSION
|
|
|
|
TEMPLATE_DIR = "resources/layouts/"
|
|
TEMPLATE_DUMP = "Liberation/layouts.p"
|
|
|
|
TEMPLATE_TYPES = {
|
|
GroupRole.AIR_DEFENSE: AntiAirLayout,
|
|
GroupRole.BUILDING: BuildingLayout,
|
|
GroupRole.NAVAL: NavalLayout,
|
|
GroupRole.GROUND_FORCE: GroundForceLayout,
|
|
GroupRole.DEFENSES: DefensesLayout,
|
|
}
|
|
|
|
|
|
class LayoutLoader:
|
|
# list of layouts per category. e.g. AA or similar
|
|
_templates: dict[str, TheaterLayout] = {}
|
|
|
|
def __init__(self) -> None:
|
|
self._templates = {}
|
|
|
|
def initialize(self) -> None:
|
|
if not self._templates:
|
|
with logged_duration("Loading layouts"):
|
|
self.load_templates()
|
|
|
|
@property
|
|
def layouts(self) -> Iterator[TheaterLayout]:
|
|
self.initialize()
|
|
yield from self._templates.values()
|
|
|
|
def load_templates(self) -> None:
|
|
"""This will load all pre-loaded layouts from a pickle file.
|
|
If pickle can not be loaded it will import and dump the layouts"""
|
|
# We use a pickle for performance reasons. Importing takes many seconds
|
|
file = Path(persistency.base_path()) / TEMPLATE_DUMP
|
|
if file.is_file():
|
|
# Load from pickle if existing
|
|
with file.open("rb") as f:
|
|
try:
|
|
version, self._templates = pickle.load(f)
|
|
# Check if the game version of the dump is identical to the current
|
|
if version == VERSION:
|
|
return
|
|
except Exception as e:
|
|
logging.error(f"Error {e} reading layouts dump. Recreating.")
|
|
# If no dump is available or game version is different create a new dump
|
|
self.import_templates()
|
|
|
|
def import_templates(self) -> None:
|
|
"""This will import all layouts from the template folder
|
|
and dumps them to a pickle"""
|
|
mappings: dict[str, list[LayoutMapping]] = {}
|
|
with logged_duration("Parsing mapping yamls"):
|
|
for file in Path(TEMPLATE_DIR).rglob("*.yaml"):
|
|
if not file.is_file():
|
|
continue
|
|
with file.open("r", encoding="utf-8") as f:
|
|
mapping_dict = yaml.safe_load(f)
|
|
|
|
template_map = LayoutMapping.from_dict(mapping_dict, f.name)
|
|
|
|
if template_map.layout_file in mappings:
|
|
mappings[template_map.layout_file].append(template_map)
|
|
else:
|
|
mappings[template_map.layout_file] = [template_map]
|
|
|
|
with logged_duration(f"Parsing all layout miz multithreaded"):
|
|
with ThreadPoolExecutor() as exe:
|
|
for miz, maps in mappings.items():
|
|
exe.submit(self._load_from_miz, miz, maps)
|
|
|
|
logging.info(f"Imported {len(self._templates)} layouts")
|
|
self._dump_templates()
|
|
|
|
def _dump_templates(self) -> None:
|
|
file = Path(persistency.base_path()) / TEMPLATE_DUMP
|
|
dump = (VERSION, self._templates)
|
|
with file.open("wb") as fdata:
|
|
pickle.dump(dump, fdata)
|
|
|
|
@staticmethod
|
|
def mapping_for_group(
|
|
mappings: list[LayoutMapping], group_name: str
|
|
) -> tuple[LayoutMapping, int, GroupLayoutMapping]:
|
|
for mapping in mappings:
|
|
for g_id, group_mapping in enumerate(mapping.groups):
|
|
if (
|
|
group_mapping.name == group_name
|
|
or group_name in group_mapping.statics
|
|
):
|
|
return mapping, g_id, group_mapping
|
|
raise KeyError
|
|
|
|
def _load_from_miz(self, miz: str, mappings: list[LayoutMapping]) -> None:
|
|
template_position: dict[str, Point] = {}
|
|
temp_mis = dcs.Mission()
|
|
with logged_duration(f"Parsing {miz}"):
|
|
# The load_file takes a lot of time to compute. That's why the layouts
|
|
# are written to a pickle and can be reloaded from the ui
|
|
# Example the whole routine: 0:00:00.934417,
|
|
# the .load_file() method: 0:00:00.920409
|
|
temp_mis.load_file(miz)
|
|
|
|
for country in itertools.chain(
|
|
temp_mis.coalition["red"].countries.values(),
|
|
temp_mis.coalition["blue"].countries.values(),
|
|
):
|
|
for dcs_group in itertools.chain(
|
|
temp_mis.country(country.name).vehicle_group,
|
|
temp_mis.country(country.name).ship_group,
|
|
temp_mis.country(country.name).static_group,
|
|
):
|
|
try:
|
|
mapping, group_id, group_mapping = self.mapping_for_group(
|
|
mappings, dcs_group.name
|
|
)
|
|
except KeyError:
|
|
logging.warning(f"No mapping for dcs group {dcs_group.name}")
|
|
continue
|
|
|
|
template = self._templates.get(mapping.name, None)
|
|
if template is None:
|
|
# Create a new template
|
|
template = TEMPLATE_TYPES[mapping.role](
|
|
mapping.name, mapping.role, mapping.description
|
|
)
|
|
template.generic = mapping.generic
|
|
template.tasks = mapping.tasks
|
|
self._templates[template.name] = template
|
|
|
|
for i, unit in enumerate(dcs_group.units):
|
|
group_template = None
|
|
for group in template.groups:
|
|
if group.name == group_mapping.name:
|
|
# We already have a layoutgroup for this dcs_group
|
|
group_template = group
|
|
if not group_template:
|
|
group_template = GroupLayout(
|
|
group_mapping.name,
|
|
[],
|
|
group_mapping.group,
|
|
group_mapping.unit_count,
|
|
group_mapping.unit_types,
|
|
group_mapping.unit_classes,
|
|
group_mapping.alternative_classes,
|
|
)
|
|
group_template.optional = group_mapping.optional
|
|
# Add the group at the correct position
|
|
template.add_group(group_template, group_id)
|
|
unit_template = LayoutUnit.from_unit(unit)
|
|
if i == 0 and template.name not in template_position:
|
|
template_position[template.name] = unit.position
|
|
unit_template.position = (
|
|
unit_template.position - template_position[template.name]
|
|
)
|
|
group_template.units.append(unit_template)
|
|
|
|
def by_name(self, template_name: str) -> Iterator[TheaterLayout]:
|
|
for template in self.layouts:
|
|
if template.name == template_name:
|
|
yield template
|
|
|
|
def by_task(self, group_task: GroupTask) -> Iterator[TheaterLayout]:
|
|
for template in self.layouts:
|
|
if not group_task or group_task in template.tasks:
|
|
yield template
|
|
|
|
def by_tasks(self, group_tasks: list[GroupTask]) -> Iterator[TheaterLayout]:
|
|
unique_templates = []
|
|
for group_task in group_tasks:
|
|
for template in self.by_task(group_task):
|
|
if template not in unique_templates:
|
|
unique_templates.append(template)
|
|
yield from unique_templates
|