Refactor Templates to Layouts, Review and Cleanup

- 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
This commit is contained in:
RndName
2022-02-10 12:23:16 +01:00
parent 1ae6503ceb
commit 2c17a9a52e
138 changed files with 1985 additions and 3096 deletions

View File

@@ -1,11 +1,10 @@
from __future__ import annotations
import logging
from collections import defaultdict
from dataclasses import dataclass
from functools import cached_property
from pathlib import Path
from typing import Any, ClassVar, Iterator, Optional, TYPE_CHECKING, Type
from typing import Any, Iterator, Optional, TYPE_CHECKING, Type
import yaml
from dcs.helicopters import helicopter_map
@@ -397,5 +396,5 @@ class AircraftType(UnitType[Type[FlyingType]]):
channel_namer=radio_config.channel_namer,
kneeboard_units=units,
utc_kneeboard=data.get("utc_kneeboard", False),
unit_class=UnitClass.Plane,
unit_class=UnitClass.PLANE,
)

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import logging
from dataclasses import dataclass
from pathlib import Path
from typing import Type, Optional, Iterator
from typing import Type, Iterator
import yaml
from dcs.unittype import VehicleType
@@ -55,8 +55,11 @@ class GroundUnitType(UnitType[Type[VehicleType]]):
introduction = "No data."
class_name = data.get("class")
# TODO Exception handling for missing classes
unit_class = UnitClass(class_name) if class_name else UnitClass.Unknown
if class_name is None:
logging.warning(f"{vehicle.id} has no class")
unit_class = UnitClass.UNKNOWN
else:
unit_class = UnitClass(class_name)
for variant in data.get("variants", [vehicle.id]):
yield GroundUnitType(

View File

@@ -1,15 +1,13 @@
from __future__ import annotations
import logging
from collections import defaultdict
from dataclasses import dataclass
from pathlib import Path
from typing import Type, Optional, ClassVar, Iterator
from typing import Type, Iterator
import yaml
from dcs.ships import ship_map
from dcs.unittype import VehicleType, ShipType
from dcs.vehicles import vehicle_map
from dcs.unittype import ShipType
from game.data.units import UnitClass
from game.dcs.unittype import UnitType
@@ -70,5 +68,5 @@ class ShipUnitType(UnitType[Type[ShipType]]):
country_of_origin=data.get("origin", "No data."),
manufacturer=data.get("manufacturer", "No data."),
role=data.get("role", "No data."),
price=data.get("price", 1),
price=data.get("price"),
)

View File

@@ -1,157 +0,0 @@
from __future__ import annotations
import copy
import itertools
import random
from dataclasses import dataclass, field
from pathlib import Path
from typing import ClassVar, TYPE_CHECKING, Any, Iterator
import yaml
from game.data.groups import GroupRole, GroupTask
from game.dcs.groundunittype import GroundUnitType
from game.dcs.shipunittype import ShipUnitType
from game.dcs.unittype import UnitType
from game.point_with_heading import PointWithHeading
from gen.templates import GroundObjectTemplate
if TYPE_CHECKING:
from game import Game
from game.factions.faction import Faction
from game.theater import TheaterGroundObject, ControlPoint
@dataclass
class UnitGroup:
name: str
ground_units: list[GroundUnitType]
ship_units: list[ShipUnitType]
statics: list[str]
role: GroupRole
tasks: list[GroupTask] = field(default_factory=list)
template_names: list[str] = field(default_factory=list)
_by_name: ClassVar[dict[str, UnitGroup]] = {}
_by_role: ClassVar[dict[GroupRole, list[UnitGroup]]] = {}
_loaded: bool = False
_templates: list[GroundObjectTemplate] = field(default_factory=list)
def __str__(self) -> str:
return self.name
def update_from_unit_group(self, unit_group: UnitGroup) -> None:
# Update tasking and templates
self.tasks.extend([task for task in unit_group.tasks if task not in self.tasks])
self._templates.extend(
[
template
for template in unit_group.templates
if template not in self.templates
]
)
@property
def templates(self) -> list[GroundObjectTemplate]:
return self._templates
def add_template(self, faction_template: GroundObjectTemplate) -> None:
template = copy.deepcopy(faction_template)
updated_groups = []
for group in template.groups:
unit_types = list(
itertools.chain(
[u.dcs_id for u in self.ground_units if group.can_use_unit(u)],
[s.dcs_id for s in self.ship_units if group.can_use_unit(s)],
[s for s in self.statics if group.can_use_unit_type(s)],
)
)
if unit_types:
group.set_possible_types(unit_types)
updated_groups.append(group)
template.groups = updated_groups
self._templates.append(template)
def load_templates(self, faction: Faction) -> None:
self._templates = []
if self.template_names:
# Preferred templates
for template_name in self.template_names:
template = faction.templates.by_name(template_name)
if template:
self.add_template(template)
if not self._templates:
# Find all matching templates if no preferred set or available
for template in list(
faction.templates.for_role_and_tasks(self.role, self.tasks)
):
if any(self.has_unit_type(unit) for unit in template.units):
self.add_template(template)
def set_templates(self, templates: list[GroundObjectTemplate]) -> None:
self._templates = templates
def has_unit_type(self, unit_type: UnitType[Any]) -> bool:
return unit_type in self.ground_units or unit_type in self.ship_units
@property
def unit_types(self) -> Iterator[str]:
for unit in self.ground_units:
yield unit.dcs_id
for ship in self.ship_units:
yield ship.dcs_id
for static in self.statics:
yield static
@classmethod
def named(cls, name: str) -> UnitGroup:
if not cls._loaded:
cls._load_all()
return cls._by_name[name]
def generate(
self,
name: str,
position: PointWithHeading,
control_point: ControlPoint,
game: Game,
) -> TheaterGroundObject:
template = random.choice(self.templates)
return template.generate(name, position, control_point, game)
@classmethod
def _load_all(cls) -> None:
for file in Path("resources/units/unit_groups").glob("*.yaml"):
if not file.is_file():
continue
with file.open(encoding="utf-8") as data_file:
data = yaml.safe_load(data_file)
group_role = GroupRole(data.get("role"))
group_tasks = [GroupTask(n) for n in data.get("tasks", [])]
ground_units = [
GroundUnitType.named(n) for n in data.get("ground_units", [])
]
ship_units = [ShipUnitType.named(n) for n in data.get("ship_units", [])]
unit_group = UnitGroup(
name=data.get("name"),
ground_units=ground_units,
ship_units=ship_units,
statics=data.get("statics", []),
role=group_role,
tasks=group_tasks,
template_names=data.get("templates", []),
)
cls._by_name[unit_group.name] = unit_group
if group_role in cls._by_role:
cls._by_role[group_role].append(unit_group)
else:
cls._by_role[group_role] = [unit_group]
cls._loaded = True

View File

@@ -4,7 +4,7 @@ from abc import ABC
from collections import defaultdict
from dataclasses import dataclass
from functools import cached_property
from typing import TypeVar, Generic, Type, ClassVar, Any, Iterator, Optional
from typing import TypeVar, Generic, Type, ClassVar, Any, Iterator
from dcs.unittype import UnitType as DcsUnitType
@@ -26,7 +26,9 @@ class UnitType(ABC, Generic[DcsUnitTypeT]):
unit_class: UnitClass
_by_name: ClassVar[dict[str, UnitType[Any]]] = {}
_by_unit_type: ClassVar[dict[DcsUnitTypeT, list[UnitType[Any]]]] = defaultdict(list)
_by_unit_type: ClassVar[dict[Type[DcsUnitType], list[UnitType[Any]]]] = defaultdict(
list
)
_loaded: ClassVar[bool] = False
def __str__(self) -> str:
@@ -43,7 +45,7 @@ class UnitType(ABC, Generic[DcsUnitTypeT]):
@classmethod
def named(cls, name: str) -> UnitType[Any]:
raise NotImplementedError
return cls._by_name[name]
@classmethod
def for_dcs_type(cls, dcs_unit_type: DcsUnitTypeT) -> Iterator[UnitType[Any]]: