mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Decoupling and generalization of templates
Improvement for factions and templates which will allow decoupling of the templates from the actual units - Implement UnitGroup class which matches unit_types and possible templates as the needed abstraction layer for decoupling. - Refactor UnitType, Add ShipUnitType and all ships we currently use - Remove serialized template.json and migrated to multiple yaml templates (one for each template) and multiple .miz - Reorganized a lot of templates and started with generalization of many types (AAA, Flak, SHORAD, Navy) - Fixed a lot of bugs from the previous reworks (group name generation, strike targets...) - Reorganized the faction file completly. removed redundant lists, added presets for complex groups / families of units like sams - Reworked the building template handling. Some templates are unused like "village" - Reworked how groups from templates can be merged again for the dcs group creation (e.g. the skynet plugin requires them to be in the same group) - Allow to define alternative tasks
This commit is contained in:
@@ -48,6 +48,7 @@ from .theatergroundobject import (
|
||||
GroundUnit,
|
||||
)
|
||||
from ..ato.starttype import StartType
|
||||
from ..data.units import UnitClass
|
||||
from ..dcs.aircrafttype import AircraftType
|
||||
from ..dcs.groundunittype import GroundUnitType
|
||||
from ..utils import nautical_miles
|
||||
@@ -521,20 +522,13 @@ class ControlPoint(MissionTarget, ABC):
|
||||
ControlPointType.LHA_GROUP,
|
||||
]:
|
||||
for g in self.ground_objects:
|
||||
if isinstance(g, CarrierGroundObject):
|
||||
for group in g.groups:
|
||||
for u in group.units:
|
||||
if unit_type_from_name(u.type) in [
|
||||
Forrestal,
|
||||
Stennis,
|
||||
KUZNECOW,
|
||||
]:
|
||||
return group.name
|
||||
elif isinstance(g, LhaGroundObject):
|
||||
for group in g.groups:
|
||||
for u in group.units:
|
||||
if unit_type_from_name(u.type) in [LHA_Tarawa]:
|
||||
return group.name
|
||||
for group in g.groups:
|
||||
for u in group.units:
|
||||
if u.unit_type and u.unit_type.unit_class in [
|
||||
UnitClass.AircraftCarrier,
|
||||
UnitClass.HelicopterCarrier,
|
||||
]:
|
||||
return group.group_name
|
||||
return None
|
||||
|
||||
# TODO: Should be Airbase specific.
|
||||
@@ -668,6 +662,7 @@ class ControlPoint(MissionTarget, ABC):
|
||||
self._retreat_squadron(game, squadron)
|
||||
|
||||
def depopulate_uncapturable_tgos(self) -> None:
|
||||
# TODO Rework this.
|
||||
for tgo in self.connected_objectives:
|
||||
if not tgo.capturable:
|
||||
tgo.clear()
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from game import Game
|
||||
from game.factions.faction import Faction
|
||||
@@ -18,7 +18,7 @@ from game.theater.theatergroundobject import (
|
||||
)
|
||||
from game.utils import Heading
|
||||
from game.version import VERSION
|
||||
from gen.templates import GroundObjectTemplates, TemplateCategory, GroundObjectTemplate
|
||||
from gen.templates import GroundObjectTemplates, GroundObjectTemplate
|
||||
from gen.naming import namegen
|
||||
from . import (
|
||||
ConflictTheater,
|
||||
@@ -28,6 +28,9 @@ from . import (
|
||||
OffMapSpawn,
|
||||
)
|
||||
from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
|
||||
from ..data.units import UnitClass
|
||||
from ..data.groups import GroupRole, GroupTask, ROLE_TASKINGS
|
||||
from ..dcs.unitgroup import UnitGroup
|
||||
from ..profiling import logged_duration
|
||||
from ..settings import Settings
|
||||
|
||||
@@ -68,15 +71,15 @@ class GameGenerator:
|
||||
generator_settings: GeneratorSettings,
|
||||
mod_settings: ModSettings,
|
||||
) -> None:
|
||||
self.player = player.apply_mod_settings(mod_settings)
|
||||
self.enemy = enemy.apply_mod_settings(mod_settings)
|
||||
self.player = player
|
||||
self.enemy = enemy
|
||||
self.theater = theater
|
||||
self.air_wing_config = air_wing_config
|
||||
self.settings = settings
|
||||
self.generator_settings = generator_settings
|
||||
|
||||
with logged_duration(f"Initializing templates"):
|
||||
self.load_templates()
|
||||
with logged_duration(f"Initializing faction and templates"):
|
||||
self.initialize_factions(mod_settings)
|
||||
|
||||
def generate(self) -> Game:
|
||||
with logged_duration("TGO population"):
|
||||
@@ -123,12 +126,11 @@ class GameGenerator:
|
||||
for cp in to_remove:
|
||||
self.theater.controlpoints.remove(cp)
|
||||
|
||||
def load_templates(self) -> None:
|
||||
templates = GroundObjectTemplates.from_json(
|
||||
"resources/templates/templates.json"
|
||||
)
|
||||
self.player.load_templates(templates)
|
||||
self.enemy.load_templates(templates)
|
||||
def initialize_factions(self, mod_settings: ModSettings) -> None:
|
||||
with logged_duration("Loading Templates from mapping"):
|
||||
templates = GroundObjectTemplates.from_folder("resources/templates/")
|
||||
self.player.initialize(templates, mod_settings)
|
||||
self.enemy.initialize(templates, mod_settings)
|
||||
|
||||
|
||||
class ControlPointGroundObjectGenerator:
|
||||
@@ -152,21 +154,23 @@ class ControlPointGroundObjectGenerator:
|
||||
|
||||
def generate(self) -> bool:
|
||||
self.control_point.connected_objectives = []
|
||||
if self.faction.navy_generators:
|
||||
# Even airbases can generate navies if they are close enough to the water.
|
||||
self.generate_navy()
|
||||
|
||||
self.generate_navy()
|
||||
return True
|
||||
|
||||
def generate_random_from_templates(
|
||||
self, templates: list[GroundObjectTemplate], position: PointWithHeading
|
||||
def generate_random_ground_object(
|
||||
self, unit_groups: list[UnitGroup], position: PointWithHeading
|
||||
) -> None:
|
||||
self.generate_ground_object_from_group(random.choice(unit_groups), position)
|
||||
|
||||
def generate_ground_object_from_group(
|
||||
self, unit_group: UnitGroup, position: PointWithHeading
|
||||
) -> None:
|
||||
try:
|
||||
template = random.choice(templates)
|
||||
with logged_duration(
|
||||
f"Ground Object generation from template {template.name}"
|
||||
f"Ground Object generation for unit_group "
|
||||
f"{unit_group.name} ({unit_group.role.value})"
|
||||
):
|
||||
ground_object = template.generate(
|
||||
ground_object = unit_group.generate(
|
||||
namegen.random_objective_name(),
|
||||
position,
|
||||
self.control_point,
|
||||
@@ -176,23 +180,23 @@ class ControlPointGroundObjectGenerator:
|
||||
except NotImplementedError:
|
||||
logging.error("Template Generator not implemented yet")
|
||||
except IndexError:
|
||||
logging.error(f"No templates to generate object")
|
||||
logging.error(f"No templates to generate object from {unit_group.name}")
|
||||
|
||||
def generate_navy(self) -> None:
|
||||
skip_player_navy = self.generator_settings.no_player_navy
|
||||
if self.control_point.captured and skip_player_navy:
|
||||
return
|
||||
|
||||
skip_enemy_navy = self.generator_settings.no_enemy_navy
|
||||
if not self.control_point.captured and skip_enemy_navy:
|
||||
return
|
||||
|
||||
templates = list(
|
||||
self.faction.templates.for_category(TemplateCategory.Naval, "ship")
|
||||
)
|
||||
|
||||
for position in self.control_point.preset_locations.ships:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
unit_group = self.faction.random_group_for_role_and_task(
|
||||
GroupRole.Naval, GroupTask.Navy
|
||||
)
|
||||
if not unit_group:
|
||||
logging.error(f"{self.faction_name} has no UnitGroup for Navy")
|
||||
return
|
||||
self.generate_ground_object_from_group(unit_group, position)
|
||||
|
||||
|
||||
class NoOpGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
@@ -213,12 +217,14 @@ class CarrierGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
)
|
||||
return False
|
||||
|
||||
templates = list(
|
||||
self.faction.templates.for_category(TemplateCategory.Naval, "carrier")
|
||||
unit_group = self.faction.random_group_for_role_and_task(
|
||||
GroupRole.Naval, GroupTask.AircraftCarrier
|
||||
)
|
||||
|
||||
self.generate_random_from_templates(
|
||||
templates,
|
||||
if not unit_group:
|
||||
logging.error(f"{self.faction_name} has no UnitGroup for AircraftCarrier")
|
||||
return False
|
||||
self.generate_ground_object_from_group(
|
||||
unit_group,
|
||||
PointWithHeading.from_point(
|
||||
self.control_point.position, self.control_point.heading
|
||||
),
|
||||
@@ -240,12 +246,14 @@ class LhaGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
)
|
||||
return False
|
||||
|
||||
templates = list(
|
||||
self.faction.templates.for_category(TemplateCategory.Naval, "lha")
|
||||
unit_group = self.faction.random_group_for_role_and_task(
|
||||
GroupRole.Naval, GroupTask.HelicopterCarrier
|
||||
)
|
||||
|
||||
self.generate_random_from_templates(
|
||||
templates,
|
||||
if not unit_group:
|
||||
logging.error(f"{self.faction_name} has no UnitGroup for HelicopterCarrier")
|
||||
return False
|
||||
self.generate_ground_object_from_group(
|
||||
unit_group,
|
||||
PointWithHeading.from_point(
|
||||
self.control_point.position, self.control_point.heading
|
||||
),
|
||||
@@ -280,110 +288,83 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
self.generate_offshore_strike_targets()
|
||||
self.generate_factories()
|
||||
self.generate_ammunition_depots()
|
||||
|
||||
if self.faction.missiles:
|
||||
self.generate_missile_sites()
|
||||
|
||||
if self.faction.coastal_defenses:
|
||||
self.generate_coastal_sites()
|
||||
self.generate_missile_sites()
|
||||
self.generate_coastal_sites()
|
||||
|
||||
def generate_armor_groups(self) -> None:
|
||||
templates = list(self.faction.templates.for_category(TemplateCategory.Armor))
|
||||
if not templates:
|
||||
logging.error(f"{self.faction_name} has no access to Armor templates")
|
||||
return
|
||||
|
||||
for position in self.control_point.preset_locations.armor_groups:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
unit_group = self.faction.random_group_for_role_and_tasks(
|
||||
GroupRole.GroundForce, ROLE_TASKINGS[GroupRole.GroundForce]
|
||||
)
|
||||
if not unit_group:
|
||||
logging.error(f"{self.faction_name} has no templates for Armor Groups")
|
||||
return
|
||||
self.generate_ground_object_from_group(unit_group, position)
|
||||
|
||||
def generate_aa(self) -> None:
|
||||
presets = self.control_point.preset_locations
|
||||
for position in presets.long_range_sams:
|
||||
self.generate_aa_at(
|
||||
position,
|
||||
[
|
||||
AirDefenseRange.Long,
|
||||
AirDefenseRange.Medium,
|
||||
AirDefenseRange.Short,
|
||||
AirDefenseRange.AAA,
|
||||
],
|
||||
)
|
||||
for position in presets.medium_range_sams:
|
||||
self.generate_aa_at(
|
||||
position,
|
||||
[
|
||||
AirDefenseRange.Medium,
|
||||
AirDefenseRange.Short,
|
||||
AirDefenseRange.AAA,
|
||||
],
|
||||
)
|
||||
for position in presets.short_range_sams:
|
||||
self.generate_aa_at(
|
||||
position,
|
||||
[AirDefenseRange.Short, AirDefenseRange.AAA],
|
||||
)
|
||||
aa_tasking = [GroupTask.AAA]
|
||||
for position in presets.aaa:
|
||||
self.generate_aa_at(
|
||||
position,
|
||||
[AirDefenseRange.AAA],
|
||||
)
|
||||
self.generate_aa_at(position, aa_tasking)
|
||||
aa_tasking.insert(0, GroupTask.SHORAD)
|
||||
for position in presets.short_range_sams:
|
||||
self.generate_aa_at(position, aa_tasking)
|
||||
aa_tasking.insert(0, GroupTask.MERAD)
|
||||
for position in presets.medium_range_sams:
|
||||
self.generate_aa_at(position, aa_tasking)
|
||||
aa_tasking.insert(0, GroupTask.LORAD)
|
||||
for position in presets.long_range_sams:
|
||||
self.generate_aa_at(position, aa_tasking)
|
||||
|
||||
def generate_ewrs(self) -> None:
|
||||
templates = list(
|
||||
self.faction.templates.for_category(TemplateCategory.AirDefence, "EWR")
|
||||
)
|
||||
if not templates:
|
||||
logging.error(f"{self.faction_name} has no access to EWR templates")
|
||||
return
|
||||
|
||||
for position in self.control_point.preset_locations.ewrs:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
unit_group = self.faction.random_group_for_role_and_task(
|
||||
GroupRole.AntiAir, GroupTask.EWR
|
||||
)
|
||||
if not unit_group:
|
||||
logging.error(f"{self.faction_name} has no UnitGroup for EWR")
|
||||
return
|
||||
self.generate_ground_object_from_group(unit_group, position)
|
||||
|
||||
def generate_building_at(
|
||||
self,
|
||||
template_category: TemplateCategory,
|
||||
building_category: str,
|
||||
group_task: GroupTask,
|
||||
position: PointWithHeading,
|
||||
) -> None:
|
||||
templates = list(
|
||||
self.faction.templates.for_category(template_category, building_category)
|
||||
unit_group = self.faction.random_group_for_role_and_task(
|
||||
GroupRole.Building, group_task
|
||||
)
|
||||
if templates:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
else:
|
||||
if not unit_group:
|
||||
logging.error(
|
||||
f"{self.faction_name} has no access to Building type {building_category}"
|
||||
f"{self.faction_name} has no access to Building ({group_task.value})"
|
||||
)
|
||||
return
|
||||
self.generate_ground_object_from_group(unit_group, position)
|
||||
|
||||
def generate_ammunition_depots(self) -> None:
|
||||
for position in self.control_point.preset_locations.ammunition_depots:
|
||||
self.generate_building_at(TemplateCategory.Building, "ammo", position)
|
||||
self.generate_building_at(GroupTask.Ammo, position)
|
||||
|
||||
def generate_factories(self) -> None:
|
||||
for position in self.control_point.preset_locations.factories:
|
||||
self.generate_building_at(TemplateCategory.Building, "factory", position)
|
||||
self.generate_building_at(GroupTask.Factory, position)
|
||||
|
||||
def generate_aa_at(
|
||||
self, position: PointWithHeading, ranges: list[AirDefenseRange]
|
||||
self, position: PointWithHeading, tasks: list[GroupTask]
|
||||
) -> None:
|
||||
|
||||
templates = []
|
||||
for aa_range in ranges:
|
||||
for template in self.faction.templates.for_category(
|
||||
TemplateCategory.AirDefence, aa_range.name
|
||||
):
|
||||
templates.append(template)
|
||||
if len(templates) > 0:
|
||||
for task in tasks:
|
||||
unit_group = self.faction.random_group_for_role_and_task(
|
||||
GroupRole.AntiAir, task
|
||||
)
|
||||
if unit_group:
|
||||
# Only take next (smaller) aa_range when no template available for the
|
||||
# most requested range. Otherwise break the loop and continue
|
||||
break
|
||||
self.generate_ground_object_from_group(unit_group, position)
|
||||
return
|
||||
|
||||
if templates:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
else:
|
||||
logging.error(
|
||||
f"{self.faction_name} has no access to SAM Templates ({', '.join([range.name for range in ranges])})"
|
||||
)
|
||||
logging.error(
|
||||
f"{self.faction_name} has no access to SAM Templates ({', '.join([task.value for task in tasks])})"
|
||||
)
|
||||
|
||||
def generate_scenery_sites(self) -> None:
|
||||
presets = self.control_point.preset_locations
|
||||
@@ -423,38 +404,32 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
self.control_point.connected_objectives.append(g)
|
||||
|
||||
def generate_missile_sites(self) -> None:
|
||||
templates = list(self.faction.templates.for_category(TemplateCategory.Missile))
|
||||
if not templates:
|
||||
logging.error(f"{self.faction_name} has no access to Missile templates")
|
||||
return
|
||||
for position in self.control_point.preset_locations.missile_sites:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
unit_group = self.faction.random_group_for_role_and_task(
|
||||
GroupRole.Defenses, GroupTask.Missile
|
||||
)
|
||||
if not unit_group:
|
||||
logging.error(f"{self.faction_name} has no UnitGroup for Missile")
|
||||
return
|
||||
self.generate_ground_object_from_group(unit_group, position)
|
||||
|
||||
def generate_coastal_sites(self) -> None:
|
||||
templates = list(self.faction.templates.for_category(TemplateCategory.Coastal))
|
||||
if not templates:
|
||||
logging.error(f"{self.faction_name} has no access to Coastal templates")
|
||||
return
|
||||
for position in self.control_point.preset_locations.coastal_defenses:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
unit_group = self.faction.random_group_for_role_and_task(
|
||||
GroupRole.Defenses, GroupTask.Coastal
|
||||
)
|
||||
if not unit_group:
|
||||
logging.error(f"{self.faction_name} has no UnitGroup for Coastal")
|
||||
return
|
||||
self.generate_ground_object_from_group(unit_group, position)
|
||||
|
||||
def generate_strike_targets(self) -> None:
|
||||
building_set = list(set(self.faction.building_set) - {"oil"})
|
||||
if not building_set:
|
||||
logging.error(f"{self.faction_name} has no buildings defined")
|
||||
return
|
||||
for position in self.control_point.preset_locations.strike_locations:
|
||||
category = random.choice(building_set)
|
||||
self.generate_building_at(TemplateCategory.Building, category, position)
|
||||
self.generate_building_at(GroupTask.StrikeTarget, position)
|
||||
|
||||
def generate_offshore_strike_targets(self) -> None:
|
||||
if "oil" not in self.faction.building_set:
|
||||
logging.error(
|
||||
f"{self.faction_name} does not support offshore strike targets"
|
||||
)
|
||||
return
|
||||
for position in self.control_point.preset_locations.offshore_strike_locations:
|
||||
self.generate_building_at(TemplateCategory.Building, "oil", position)
|
||||
self.generate_building_at(GroupTask.Oil, position)
|
||||
|
||||
|
||||
class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
|
||||
@@ -466,8 +441,7 @@ class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
|
||||
|
||||
def generate_fob(self) -> None:
|
||||
self.generate_building_at(
|
||||
TemplateCategory.Building,
|
||||
"fob",
|
||||
GroupTask.FOB,
|
||||
PointWithHeading.from_point(
|
||||
self.control_point.position, self.control_point.heading
|
||||
),
|
||||
|
||||
@@ -8,6 +8,7 @@ from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Iterator, List, TYPE_CHECKING, Union, Optional, Any
|
||||
|
||||
from dcs.unittype import VehicleType, ShipType
|
||||
from dcs.vehicles import vehicle_map
|
||||
from dcs.ships import ship_map
|
||||
|
||||
@@ -17,6 +18,7 @@ from dcs.triggers import TriggerZone
|
||||
from game.dcs.helpers import unit_type_from_name
|
||||
from ..data.radar_db import LAUNCHER_TRACKER_PAIRS, TELARS, TRACK_RADARS
|
||||
from ..dcs.groundunittype import GroundUnitType
|
||||
from ..dcs.shipunittype import ShipUnitType
|
||||
from ..dcs.unittype import UnitType
|
||||
from ..point_with_heading import PointWithHeading
|
||||
from ..utils import Distance, Heading, meters
|
||||
@@ -90,11 +92,13 @@ class GroundUnit:
|
||||
_unit_type: Optional[UnitType[Any]] = None
|
||||
|
||||
@staticmethod
|
||||
def from_template(id: int, t: UnitTemplate, go: TheaterGroundObject) -> GroundUnit:
|
||||
def from_template(
|
||||
id: int, unit_type: str, t: UnitTemplate, go: TheaterGroundObject
|
||||
) -> GroundUnit:
|
||||
return GroundUnit(
|
||||
id,
|
||||
t.name,
|
||||
t.type,
|
||||
unit_type,
|
||||
PointWithHeading.from_point(t.position, Heading.from_degrees(t.heading)),
|
||||
go,
|
||||
)
|
||||
@@ -103,20 +107,16 @@ class GroundUnit:
|
||||
def unit_type(self) -> Optional[UnitType[Any]]:
|
||||
if not self._unit_type:
|
||||
try:
|
||||
if self.type in vehicle_map:
|
||||
vehicle_type = db.vehicle_type_from_name(self.type)
|
||||
self._unit_type = next(GroundUnitType.for_dcs_type(vehicle_type))
|
||||
elif self.type in ship_map:
|
||||
ship_type = db.ship_type_from_name(self.type)
|
||||
# TODO Allow handling of Ships. This requires extension of UnitType
|
||||
return None
|
||||
elif (static_type := db.static_type_from_name(self.type)) is not None:
|
||||
# TODO Allow handling of Statics
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
unit_type: Optional[UnitType[Any]] = None
|
||||
dcs_type = db.unit_type_from_name(self.type)
|
||||
if dcs_type and issubclass(dcs_type, VehicleType):
|
||||
unit_type = next(GroundUnitType.for_dcs_type(dcs_type))
|
||||
elif dcs_type and issubclass(dcs_type, ShipType):
|
||||
unit_type = next(ShipUnitType.for_dcs_type(dcs_type))
|
||||
self._unit_type = unit_type
|
||||
except StopIteration:
|
||||
return None
|
||||
logging.error(f"No UnitType for {self.type}")
|
||||
pass
|
||||
return self._unit_type
|
||||
|
||||
def kill(self) -> None:
|
||||
@@ -153,36 +153,15 @@ class GroundGroup:
|
||||
id: int,
|
||||
g: GroupTemplate,
|
||||
go: TheaterGroundObject,
|
||||
randomization: bool = True,
|
||||
) -> GroundGroup:
|
||||
|
||||
units = []
|
||||
if g.randomizer:
|
||||
g.randomizer.randomize()
|
||||
|
||||
for u_id, unit in enumerate(g.units):
|
||||
tgo_unit = GroundUnit.from_template(u_id, unit, go)
|
||||
if randomization and g.randomizer:
|
||||
if g.randomizer.unit_type:
|
||||
tgo_unit.type = g.randomizer.unit_type
|
||||
try:
|
||||
# Check if unit can be assigned
|
||||
g.randomizer.use_unit()
|
||||
except IndexError:
|
||||
# Do not generate the unit as no more units are available
|
||||
continue
|
||||
units.append(tgo_unit)
|
||||
|
||||
tgo_group = GroundGroup(
|
||||
id,
|
||||
g.name,
|
||||
PointWithHeading.from_point(go.position, go.heading),
|
||||
units,
|
||||
g.generate_units(go),
|
||||
go,
|
||||
)
|
||||
|
||||
tgo_group.static_group = g.static
|
||||
|
||||
return tgo_group
|
||||
|
||||
@property
|
||||
|
||||
Reference in New Issue
Block a user