mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Implement Template System for ground objects
- Factored out the current generators to use a better approach with Templates build from the dcs mission editor. - This information is extended with a template-mapping in a json file which allows to logically group together multiple dcs groups and even statics to one template - The combination of mapping and miz will be serialized to a template.json which is only used for loading. - Factions now load templates during initialization and hold all the templates they can really use. This is based around the faction file. - Implemented a template randomizer which allows to add some randomization to templates - Each Template Group can have 1 randomizer which randomizes unit_type and size based on the mapping definition. Larger groups need to be devided in more fine detailed groups as we can now handle them better due to the change from dcs group types to our own classes. - Rewritten the ArmorGroup, Naval and EWR template handling Rework GroundObjectBuyMenu to support templates
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import itertools
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
@@ -24,6 +25,7 @@ from game.data.doctrine import (
|
||||
from game.data.groundunitclass import GroundUnitClass
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from gen.templates import GroundObjectTemplates, TemplateCategory
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.theater.start_generator import ModSettings
|
||||
@@ -72,7 +74,7 @@ class Faction:
|
||||
air_defenses: List[str] = field(default_factory=list)
|
||||
|
||||
# Possible EWR generators for this faction.
|
||||
ewrs: List[str] = field(default_factory=list)
|
||||
ewrs: List[GroundUnitType] = field(default_factory=list)
|
||||
|
||||
# Possible Missile site generators for this faction
|
||||
missiles: List[str] = field(default_factory=list)
|
||||
@@ -137,12 +139,68 @@ class Faction:
|
||||
#: both will use it.
|
||||
unrestricted_satnav: bool = False
|
||||
|
||||
def has_access_to_unittype(self, unit_class: GroundUnitClass) -> bool:
|
||||
# All possible templates which can be generated by the faction
|
||||
templates: GroundObjectTemplates = field(default=GroundObjectTemplates())
|
||||
|
||||
def __getitem__(self, item: str) -> Any:
|
||||
return getattr(self, item)
|
||||
|
||||
def has_access_to_unit_type(self, unit_type: str) -> bool:
|
||||
# Supports all GroundUnit lists and AirDefenses
|
||||
for unit in self.ground_units:
|
||||
if unit_type == unit.dcs_id:
|
||||
return True
|
||||
return unit_type in self.air_defenses
|
||||
|
||||
def has_access_to_unit_class(self, unit_class: GroundUnitClass) -> bool:
|
||||
for vehicle in itertools.chain(self.frontline_units, self.artillery_units):
|
||||
if vehicle.unit_class is unit_class:
|
||||
return True
|
||||
return False
|
||||
|
||||
def load_templates(self, all_templates: GroundObjectTemplates) -> None:
|
||||
# This loads all faction possible sam templates and the default ones
|
||||
# For legacy reasons this allows to check for template names. This can be
|
||||
# improved in the future to have more control about the possible Templates.
|
||||
# For example it can be possible to define the unit_types and check if all
|
||||
# requirements for the template are fulfilled.
|
||||
for category, template in all_templates.templates:
|
||||
if (
|
||||
(
|
||||
category == TemplateCategory.AirDefence
|
||||
and (
|
||||
# Check if faction has the template name or ALL required
|
||||
# unit_types in the list air_defenses. For legacy reasons this
|
||||
# allows both and also the EWR template
|
||||
template.name in self.air_defenses
|
||||
or all(
|
||||
self.has_access_to_unit_type(required_unit)
|
||||
for required_unit in template.required_units
|
||||
)
|
||||
or template.template_type == "EWR"
|
||||
)
|
||||
)
|
||||
or template.name in self.navy_generators
|
||||
or template.name in self.missiles
|
||||
or template.name in self.coastal_defenses
|
||||
or (
|
||||
template.template_type
|
||||
in self.building_set + ["fob", "ammo", "factory"]
|
||||
)
|
||||
or (template.template_type == "carrier" and self.aircraft_carrier)
|
||||
or (template.template_type == "lha" and self.helicopter_carrier)
|
||||
or category == TemplateCategory.Armor
|
||||
):
|
||||
# Make a deep copy of a template and add it to the template_list.
|
||||
# This is required to have faction independent templates. Otherwise
|
||||
# the reference would be the same and changes would affect all.
|
||||
faction_template = copy.deepcopy(template)
|
||||
# Initialize all randomizers
|
||||
for group_template in faction_template.groups:
|
||||
if group_template.randomizer:
|
||||
group_template.randomizer.init_randomization_for_faction(self)
|
||||
self.templates.add_template(category, faction_template)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
|
||||
faction = Faction(locales=json.get("locales"))
|
||||
@@ -182,8 +240,7 @@ class Faction:
|
||||
faction.logistics_units = [
|
||||
GroundUnitType.named(n) for n in json.get("logistics_units", [])
|
||||
]
|
||||
|
||||
faction.ewrs = json.get("ewrs", [])
|
||||
faction.ewrs = [GroundUnitType.named(n) for n in json.get("ewrs", [])]
|
||||
|
||||
faction.air_defenses = json.get("air_defenses", [])
|
||||
# Compatibility for older factions. All air defenses now belong to a
|
||||
@@ -245,6 +302,8 @@ class Faction:
|
||||
|
||||
faction.unrestricted_satnav = json.get("unrestricted_satnav", False)
|
||||
|
||||
# Templates
|
||||
faction.templates = GroundObjectTemplates()
|
||||
return faction
|
||||
|
||||
@property
|
||||
@@ -320,13 +379,13 @@ class Faction:
|
||||
self.remove_vehicle("KORNET")
|
||||
# high digit sams
|
||||
if not mod_settings.high_digit_sams:
|
||||
self.remove_air_defenses("SA10BGenerator")
|
||||
self.remove_air_defenses("SA12Generator")
|
||||
self.remove_air_defenses("SA20Generator")
|
||||
self.remove_air_defenses("SA20BGenerator")
|
||||
self.remove_air_defenses("SA23Generator")
|
||||
self.remove_air_defenses("SA17Generator")
|
||||
self.remove_air_defenses("KS19Generator")
|
||||
self.remove_air_defenses("SA-10B/S-300PS Battery")
|
||||
self.remove_air_defenses("SA-12/S-300V Battery")
|
||||
self.remove_air_defenses("SA-20/S-300PMU-1 Battery")
|
||||
self.remove_air_defenses("SA-20B/S-300PMU-2 Battery")
|
||||
self.remove_air_defenses("SA-23/S-300VM Battery")
|
||||
self.remove_air_defenses("SA-17 Grizzly Battery")
|
||||
self.remove_air_defenses("KS-19 AAA Site")
|
||||
return self
|
||||
|
||||
def remove_aircraft(self, name: str) -> None:
|
||||
|
||||
@@ -123,7 +123,7 @@ class GroundObjectGenerator:
|
||||
if not u.alive:
|
||||
continue
|
||||
|
||||
unit_type = static_type_from_name(u.type)
|
||||
unit_type = unit_type_from_name(u.type)
|
||||
if not unit_type:
|
||||
raise RuntimeError(
|
||||
f"Unit type {u.type} is not a valid dcs unit type"
|
||||
|
||||
@@ -158,7 +158,7 @@ class ProcurementAi:
|
||||
worst_balanced: Optional[GroundUnitClass] = None
|
||||
worst_fulfillment = math.inf
|
||||
for unit_class in GroundUnitClass:
|
||||
if not self.faction.has_access_to_unittype(unit_class):
|
||||
if not self.faction.has_access_to_unit_class(unit_class):
|
||||
continue
|
||||
|
||||
current_ratio = self.cost_ratio_of_ground_unit(cp, unit_class)
|
||||
|
||||
@@ -1,45 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import pickle
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Iterable, List, Set
|
||||
|
||||
from dcs.mapping import Point
|
||||
from typing import List
|
||||
|
||||
from game import Game
|
||||
from game.factions.faction import Faction
|
||||
from game.scenery_group import SceneryGroup
|
||||
from game.theater import PointWithHeading
|
||||
from game.theater.theatergroundobject import (
|
||||
AirDefenseRange,
|
||||
BuildingGroundObject,
|
||||
CarrierGroundObject,
|
||||
EwrGroundObject,
|
||||
FactoryGroundObject,
|
||||
LhaGroundObject,
|
||||
MissileSiteGroundObject,
|
||||
SamGroundObject,
|
||||
ShipGroundObject,
|
||||
SceneryGroundObject,
|
||||
VehicleGroupGroundObject,
|
||||
CoastalSiteGroundObject,
|
||||
SceneryGroundUnit,
|
||||
GroundGroup,
|
||||
)
|
||||
from game.utils import Heading
|
||||
from game.version import VERSION
|
||||
from gen.coastal.coastal_group_generator import generate_coastal_group
|
||||
from gen.defenses.armor_group_generator import generate_armor_group
|
||||
from gen.fleet.ship_group_generator import (
|
||||
generate_carrier_group,
|
||||
generate_lha_group,
|
||||
generate_ship_group,
|
||||
)
|
||||
from gen.missiles.missiles_group_generator import generate_missile_group
|
||||
from gen.templates import GroundObjectTemplates, TemplateCategory, GroundObjectTemplate
|
||||
from gen.naming import namegen
|
||||
from gen.sam.airdefensegroupgenerator import AirDefenseRange
|
||||
from gen.sam.ewr_group_generator import generate_ewr_group
|
||||
from gen.sam.sam_group_generator import generate_anti_air_group
|
||||
from . import (
|
||||
ConflictTheater,
|
||||
ControlPoint,
|
||||
@@ -51,8 +31,6 @@ from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
|
||||
from ..profiling import logged_duration
|
||||
from ..settings import Settings
|
||||
|
||||
GroundObjectTemplates = Dict[str, Dict[str, Any]]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GeneratorSettings:
|
||||
@@ -97,6 +75,9 @@ class GameGenerator:
|
||||
self.settings = settings
|
||||
self.generator_settings = generator_settings
|
||||
|
||||
with logged_duration(f"Initializing templates"):
|
||||
self.load_templates()
|
||||
|
||||
def generate(self) -> Game:
|
||||
with logged_duration("TGO population"):
|
||||
# Reset name generator
|
||||
@@ -142,6 +123,13 @@ 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)
|
||||
|
||||
|
||||
class ControlPointGroundObjectGenerator:
|
||||
def __init__(
|
||||
@@ -170,6 +158,26 @@ class ControlPointGroundObjectGenerator:
|
||||
|
||||
return True
|
||||
|
||||
def generate_random_from_templates(
|
||||
self, templates: list[GroundObjectTemplate], position: PointWithHeading
|
||||
) -> None:
|
||||
try:
|
||||
template = random.choice(templates)
|
||||
with logged_duration(
|
||||
f"Ground Object generation from template {template.name}"
|
||||
):
|
||||
ground_object = template.generate(
|
||||
namegen.random_objective_name(),
|
||||
position,
|
||||
self.control_point,
|
||||
self.game,
|
||||
)
|
||||
self.control_point.connected_objectives.append(ground_object)
|
||||
except NotImplementedError:
|
||||
logging.error("Template Generator not implemented yet")
|
||||
except IndexError:
|
||||
logging.error(f"No templates to generate object")
|
||||
|
||||
def generate_navy(self) -> None:
|
||||
skip_player_navy = self.generator_settings.no_player_navy
|
||||
if self.control_point.captured and skip_player_navy:
|
||||
@@ -179,21 +187,12 @@ class ControlPointGroundObjectGenerator:
|
||||
if not self.control_point.captured and skip_enemy_navy:
|
||||
return
|
||||
|
||||
for position in self.control_point.preset_locations.ships:
|
||||
self.generate_ship_at(position)
|
||||
|
||||
def generate_ship_at(self, position: PointWithHeading) -> None:
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = ShipGroundObject(
|
||||
namegen.random_objective_name(), group_id, position, self.control_point
|
||||
templates = list(
|
||||
self.faction.templates.for_category(TemplateCategory.Naval, "ship")
|
||||
)
|
||||
|
||||
group = generate_ship_group(self.game, g, self.faction_name)
|
||||
g.groups = []
|
||||
if group is not None:
|
||||
g.groups.append(group)
|
||||
self.control_point.connected_objectives.append(g)
|
||||
for position in self.control_point.preset_locations.ships:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
|
||||
|
||||
class NoOpGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
@@ -214,16 +213,16 @@ class CarrierGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
)
|
||||
return False
|
||||
|
||||
# Create ground object group
|
||||
group_id = self.game.next_group_id()
|
||||
g = CarrierGroundObject(
|
||||
namegen.random_objective_name(), group_id, self.control_point
|
||||
templates = list(
|
||||
self.faction.templates.for_category(TemplateCategory.Naval, "carrier")
|
||||
)
|
||||
|
||||
self.generate_random_from_templates(
|
||||
templates,
|
||||
PointWithHeading.from_point(
|
||||
self.control_point.position, self.control_point.heading
|
||||
),
|
||||
)
|
||||
group = generate_carrier_group(self.faction_name, self.game, g)
|
||||
g.groups = []
|
||||
if group is not None:
|
||||
g.groups.append(group)
|
||||
self.control_point.connected_objectives.append(g)
|
||||
self.control_point.name = random.choice(carrier_names)
|
||||
return True
|
||||
|
||||
@@ -241,16 +240,16 @@ class LhaGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
)
|
||||
return False
|
||||
|
||||
# Create ground object group
|
||||
group_id = self.game.next_group_id()
|
||||
g = LhaGroundObject(
|
||||
namegen.random_objective_name(), group_id, self.control_point
|
||||
templates = list(
|
||||
self.faction.templates.for_category(TemplateCategory.Naval, "lha")
|
||||
)
|
||||
|
||||
self.generate_random_from_templates(
|
||||
templates,
|
||||
PointWithHeading.from_point(
|
||||
self.control_point.position, self.control_point.heading
|
||||
),
|
||||
)
|
||||
group = generate_lha_group(self.faction_name, self.game, g)
|
||||
g.groups = []
|
||||
if group is not None:
|
||||
g.groups.append(group)
|
||||
self.control_point.connected_objectives.append(g)
|
||||
self.control_point.name = random.choice(lha_names)
|
||||
return True
|
||||
|
||||
@@ -261,10 +260,8 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
game: Game,
|
||||
generator_settings: GeneratorSettings,
|
||||
control_point: ControlPoint,
|
||||
templates: GroundObjectTemplates,
|
||||
) -> None:
|
||||
super().__init__(game, generator_settings, control_point)
|
||||
self.templates = templates
|
||||
|
||||
def generate(self) -> bool:
|
||||
if not super().generate():
|
||||
@@ -291,156 +288,102 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
self.generate_coastal_sites()
|
||||
|
||||
def generate_armor_groups(self) -> None:
|
||||
for position in self.control_point.preset_locations.armor_groups:
|
||||
self.generate_armor_at(position)
|
||||
|
||||
def generate_armor_at(self, position: PointWithHeading) -> None:
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = VehicleGroupGroundObject(
|
||||
namegen.random_objective_name(),
|
||||
group_id,
|
||||
position,
|
||||
self.control_point,
|
||||
)
|
||||
|
||||
group = generate_armor_group(self.faction_name, self.game, g)
|
||||
if group is None:
|
||||
logging.error(
|
||||
"Could not generate armor group for %s at %s",
|
||||
g.name,
|
||||
self.control_point,
|
||||
)
|
||||
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
|
||||
g.groups = [group]
|
||||
self.control_point.connected_objectives.append(g)
|
||||
|
||||
for position in self.control_point.preset_locations.armor_groups:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
|
||||
def generate_aa(self) -> None:
|
||||
presets = self.control_point.preset_locations
|
||||
for position in presets.long_range_sams:
|
||||
self.generate_aa_at(
|
||||
position,
|
||||
ranges=[
|
||||
{AirDefenseRange.Long},
|
||||
{AirDefenseRange.Medium},
|
||||
{AirDefenseRange.Short},
|
||||
{AirDefenseRange.AAA},
|
||||
[
|
||||
AirDefenseRange.Long,
|
||||
AirDefenseRange.Medium,
|
||||
AirDefenseRange.Short,
|
||||
AirDefenseRange.AAA,
|
||||
],
|
||||
)
|
||||
for position in presets.medium_range_sams:
|
||||
self.generate_aa_at(
|
||||
position,
|
||||
ranges=[
|
||||
{AirDefenseRange.Medium},
|
||||
{AirDefenseRange.Short},
|
||||
{AirDefenseRange.AAA},
|
||||
[
|
||||
AirDefenseRange.Medium,
|
||||
AirDefenseRange.Short,
|
||||
AirDefenseRange.AAA,
|
||||
],
|
||||
)
|
||||
for position in presets.short_range_sams:
|
||||
self.generate_aa_at(
|
||||
position,
|
||||
ranges=[{AirDefenseRange.Short}, {AirDefenseRange.AAA}],
|
||||
[AirDefenseRange.Short, AirDefenseRange.AAA],
|
||||
)
|
||||
for position in presets.aaa:
|
||||
self.generate_aa_at(
|
||||
position,
|
||||
ranges=[{AirDefenseRange.AAA}],
|
||||
[AirDefenseRange.AAA],
|
||||
)
|
||||
|
||||
def generate_ewrs(self) -> None:
|
||||
presets = self.control_point.preset_locations
|
||||
for position in presets.ewrs:
|
||||
self.generate_ewr_at(position)
|
||||
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
|
||||
|
||||
def generate_strike_target_at(self, category: str, position: Point) -> None:
|
||||
for position in self.control_point.preset_locations.ewrs:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
|
||||
obj_name = namegen.random_objective_name()
|
||||
template = random.choice(list(self.templates[category].values()))
|
||||
|
||||
object_id = 0
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
# TODO: Create only one TGO per objective, each with multiple units.
|
||||
for unit in template:
|
||||
object_id += 1
|
||||
|
||||
template_point = Point(unit["offset"].x, unit["offset"].y)
|
||||
g = BuildingGroundObject(
|
||||
obj_name,
|
||||
category,
|
||||
group_id,
|
||||
object_id,
|
||||
position + template_point,
|
||||
Heading.from_degrees(unit["heading"]),
|
||||
self.control_point,
|
||||
unit["type"],
|
||||
def generate_building_at(
|
||||
self,
|
||||
template_category: TemplateCategory,
|
||||
building_category: str,
|
||||
position: PointWithHeading,
|
||||
) -> None:
|
||||
templates = list(
|
||||
self.faction.templates.for_category(template_category, building_category)
|
||||
)
|
||||
if templates:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
else:
|
||||
logging.error(
|
||||
f"{self.faction_name} has no access to Building type {building_category}"
|
||||
)
|
||||
|
||||
self.control_point.connected_objectives.append(g)
|
||||
|
||||
def generate_ammunition_depots(self) -> None:
|
||||
for position in self.control_point.preset_locations.ammunition_depots:
|
||||
self.generate_strike_target_at(category="ammo", position=position)
|
||||
self.generate_building_at(TemplateCategory.Building, "ammo", position)
|
||||
|
||||
def generate_factories(self) -> None:
|
||||
for position in self.control_point.preset_locations.factories:
|
||||
self.generate_factory_at(position)
|
||||
|
||||
def generate_factory_at(self, point: PointWithHeading) -> None:
|
||||
obj_name = namegen.random_objective_name()
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = FactoryGroundObject(
|
||||
obj_name,
|
||||
group_id,
|
||||
point,
|
||||
point.heading,
|
||||
self.control_point,
|
||||
)
|
||||
|
||||
self.control_point.connected_objectives.append(g)
|
||||
self.generate_building_at(TemplateCategory.Building, "factory", position)
|
||||
|
||||
def generate_aa_at(
|
||||
self, position: Point, ranges: Iterable[Set[AirDefenseRange]]
|
||||
self, position: PointWithHeading, ranges: list[AirDefenseRange]
|
||||
) -> None:
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = SamGroundObject(
|
||||
namegen.random_objective_name(),
|
||||
group_id,
|
||||
position,
|
||||
self.control_point,
|
||||
)
|
||||
groups = generate_anti_air_group(self.game, g, self.faction, ranges)
|
||||
if not groups:
|
||||
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:
|
||||
# Only take next (smaller) aa_range when no template available for the
|
||||
# most requested range. Otherwise break the loop and continue
|
||||
break
|
||||
|
||||
if templates:
|
||||
self.generate_random_from_templates(templates, position)
|
||||
else:
|
||||
logging.error(
|
||||
"Could not generate air defense group for %s at %s",
|
||||
g.name,
|
||||
self.control_point,
|
||||
f"{self.faction_name} has no access to SAM Templates ({', '.join([range.name for range in ranges])})"
|
||||
)
|
||||
return
|
||||
g.groups = groups
|
||||
self.control_point.connected_objectives.append(g)
|
||||
|
||||
def generate_ewr_at(self, position: PointWithHeading) -> None:
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = EwrGroundObject(
|
||||
namegen.random_objective_name(),
|
||||
group_id,
|
||||
position,
|
||||
self.control_point,
|
||||
)
|
||||
group = generate_ewr_group(self.game, g, self.faction)
|
||||
if group is None:
|
||||
logging.error(
|
||||
"Could not generate ewr group for %s at %s",
|
||||
g.name,
|
||||
self.control_point,
|
||||
)
|
||||
return
|
||||
g.groups = [group]
|
||||
self.control_point.connected_objectives.append(g)
|
||||
|
||||
def generate_scenery_sites(self) -> None:
|
||||
presets = self.control_point.preset_locations
|
||||
@@ -448,143 +391,93 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
self.generate_tgo_for_scenery(scenery_group)
|
||||
|
||||
def generate_tgo_for_scenery(self, scenery: SceneryGroup) -> None:
|
||||
|
||||
obj_name = namegen.random_objective_name()
|
||||
category = scenery.category
|
||||
group_id = self.game.next_group_id()
|
||||
object_id = 0
|
||||
|
||||
# Special Handling for scenery Objects based on trigger zones
|
||||
g = BuildingGroundObject(
|
||||
namegen.random_objective_name(),
|
||||
scenery.category,
|
||||
scenery.position,
|
||||
Heading.from_degrees(0),
|
||||
self.control_point,
|
||||
)
|
||||
ground_group = GroundGroup(
|
||||
self.game.next_group_id(),
|
||||
scenery.zone_def.name,
|
||||
PointWithHeading.from_point(scenery.position, Heading.from_degrees(0)),
|
||||
[],
|
||||
g,
|
||||
)
|
||||
ground_group.static_group = True
|
||||
g.groups.append(ground_group)
|
||||
# Each nested trigger zone is a target/building/unit for an objective.
|
||||
for zone in scenery.zones:
|
||||
|
||||
object_id += 1
|
||||
local_position = zone.position
|
||||
local_dcs_identifier = zone.name
|
||||
|
||||
g = SceneryGroundObject(
|
||||
obj_name,
|
||||
category,
|
||||
group_id,
|
||||
object_id,
|
||||
local_position,
|
||||
self.control_point,
|
||||
local_dcs_identifier,
|
||||
zone,
|
||||
scenery_unit = SceneryGroundUnit(
|
||||
zone.id,
|
||||
zone.name,
|
||||
"",
|
||||
PointWithHeading.from_point(zone.position, Heading.from_degrees(0)),
|
||||
g,
|
||||
)
|
||||
scenery_unit.zone = zone
|
||||
ground_group.units.append(scenery_unit)
|
||||
|
||||
self.control_point.connected_objectives.append(g)
|
||||
|
||||
return
|
||||
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_missile_site_at(position)
|
||||
|
||||
def generate_missile_site_at(self, position: PointWithHeading) -> None:
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = MissileSiteGroundObject(
|
||||
namegen.random_objective_name(), group_id, position, self.control_point
|
||||
)
|
||||
group = generate_missile_group(self.game, g, self.faction_name)
|
||||
g.groups = []
|
||||
if group is not None:
|
||||
g.groups.append(group)
|
||||
self.control_point.connected_objectives.append(g)
|
||||
return
|
||||
self.generate_random_from_templates(templates, 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_coastal_site_at(position)
|
||||
|
||||
def generate_coastal_site_at(self, position: PointWithHeading) -> None:
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = CoastalSiteGroundObject(
|
||||
namegen.random_objective_name(),
|
||||
group_id,
|
||||
position,
|
||||
self.control_point,
|
||||
position.heading,
|
||||
)
|
||||
group = generate_coastal_group(self.game, g, self.faction_name)
|
||||
g.groups = []
|
||||
if group is not None:
|
||||
g.groups.append(group)
|
||||
self.control_point.connected_objectives.append(g)
|
||||
return
|
||||
self.generate_random_from_templates(templates, position)
|
||||
|
||||
def generate_strike_targets(self) -> None:
|
||||
building_set = list(set(self.faction.building_set) - {"oil"})
|
||||
if not building_set:
|
||||
logging.error("Faction has no buildings defined")
|
||||
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_strike_target_at(category, position)
|
||||
self.generate_building_at(TemplateCategory.Building, category, position)
|
||||
|
||||
def generate_offshore_strike_targets(self) -> None:
|
||||
if "oil" not in self.faction.building_set:
|
||||
logging.error("Faction does not support offshore strike targets")
|
||||
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_strike_target_at("oil", position)
|
||||
self.generate_building_at(TemplateCategory.Building, "oil", position)
|
||||
|
||||
|
||||
class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
|
||||
def generate(self) -> bool:
|
||||
self.generate_fob()
|
||||
self.generate_armor_groups()
|
||||
self.generate_factories()
|
||||
self.generate_ammunition_depots()
|
||||
self.generate_aa()
|
||||
self.generate_ewrs()
|
||||
self.generate_scenery_sites()
|
||||
self.generate_strike_targets()
|
||||
self.generate_offshore_strike_targets()
|
||||
|
||||
if self.faction.missiles:
|
||||
self.generate_missile_sites()
|
||||
|
||||
if self.faction.coastal_defenses:
|
||||
self.generate_coastal_sites()
|
||||
|
||||
return True
|
||||
if super(FobGroundObjectGenerator, self).generate():
|
||||
self.generate_fob()
|
||||
return True
|
||||
return False
|
||||
|
||||
def generate_fob(self) -> None:
|
||||
category = "fob"
|
||||
obj_name = self.control_point.name
|
||||
template = random.choice(list(self.templates[category].values()))
|
||||
point = self.control_point.position
|
||||
# Pick from preset locations
|
||||
object_id = 0
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
# TODO: Create only one TGO per objective, each with multiple units.
|
||||
for unit in template:
|
||||
object_id += 1
|
||||
|
||||
template_point = Point(unit["offset"].x, unit["offset"].y)
|
||||
g = BuildingGroundObject(
|
||||
obj_name,
|
||||
category,
|
||||
group_id,
|
||||
object_id,
|
||||
point + template_point,
|
||||
Heading.from_degrees(unit["heading"]),
|
||||
self.control_point,
|
||||
unit["type"],
|
||||
is_fob_structure=True,
|
||||
)
|
||||
self.control_point.connected_objectives.append(g)
|
||||
self.generate_building_at(
|
||||
TemplateCategory.Building,
|
||||
"fob",
|
||||
PointWithHeading.from_point(
|
||||
self.control_point.position, self.control_point.heading
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class GroundObjectGenerator:
|
||||
def __init__(self, game: Game, generator_settings: GeneratorSettings) -> None:
|
||||
self.game = game
|
||||
self.generator_settings = generator_settings
|
||||
with open("resources/groundobject_templates.p", "rb") as f:
|
||||
self.templates: GroundObjectTemplates = pickle.load(f)
|
||||
|
||||
def generate(self) -> None:
|
||||
# Copied so we can remove items from the original list without breaking
|
||||
@@ -610,10 +503,10 @@ class GroundObjectGenerator:
|
||||
)
|
||||
elif isinstance(control_point, Fob):
|
||||
generator = FobGroundObjectGenerator(
|
||||
self.game, self.generator_settings, control_point, self.templates
|
||||
self.game, self.generator_settings, control_point
|
||||
)
|
||||
else:
|
||||
generator = AirbaseGroundObjectGenerator(
|
||||
self.game, self.generator_settings, control_point, self.templates
|
||||
self.game, self.generator_settings, control_point
|
||||
)
|
||||
return generator.generate()
|
||||
|
||||
@@ -6,21 +6,23 @@ from abc import ABC
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Iterator, List, TYPE_CHECKING, Union, Optional
|
||||
from typing import Iterator, List, TYPE_CHECKING, Union, Optional, Any
|
||||
|
||||
from dcs.vehicles import vehicle_map
|
||||
from dcs.ships import ship_map
|
||||
|
||||
from dcs.mapping import Point
|
||||
from dcs.triggers import TriggerZone
|
||||
from dcs.unit import Unit
|
||||
from dcs.vehicles import vehicle_map
|
||||
|
||||
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.unittype import UnitType
|
||||
from ..point_with_heading import PointWithHeading
|
||||
from ..utils import Distance, Heading, meters
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from gen.templates import UnitTemplate, GroupTemplate, TemplateRandomizer
|
||||
from gen.templates import UnitTemplate, GroupTemplate
|
||||
from .controlpoint import ControlPoint
|
||||
from ..ato.flighttype import FlightType
|
||||
|
||||
@@ -85,7 +87,7 @@ class GroundUnit:
|
||||
position: PointWithHeading
|
||||
ground_object: TheaterGroundObject
|
||||
alive: bool = True
|
||||
_dcs_type: Optional[GroundUnitType] = None
|
||||
_unit_type: Optional[UnitType[Any]] = None
|
||||
|
||||
@staticmethod
|
||||
def from_template(id: int, t: UnitTemplate, go: TheaterGroundObject) -> GroundUnit:
|
||||
@@ -98,17 +100,24 @@ class GroundUnit:
|
||||
)
|
||||
|
||||
@property
|
||||
def unit_type(self) -> Optional[GroundUnitType]:
|
||||
if not self._dcs_type:
|
||||
def unit_type(self) -> Optional[UnitType[Any]]:
|
||||
if not self._unit_type:
|
||||
try:
|
||||
if self.type in vehicle_map:
|
||||
dcs_unit_type = vehicle_map[self.type]
|
||||
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
|
||||
self._dcs_type = next(GroundUnitType.for_dcs_type(dcs_unit_type))
|
||||
except StopIteration:
|
||||
return None
|
||||
return self._dcs_type
|
||||
return self._unit_type
|
||||
|
||||
def kill(self) -> None:
|
||||
self.alive = False
|
||||
|
||||
Reference in New Issue
Block a user