Support groups for SAM templates.

It's only possible to control emissions for the group as a whole, so
Skynet needs PDs to be in separate groups from the main part of the SAM
for PD to operate correctly.

https://github.com/Khopa/dcs_liberation/issues/429
https://github.com/Khopa/dcs_liberation/issues/470
This commit is contained in:
Dan Albert 2020-12-24 15:50:01 -08:00
parent 10debbc286
commit 85619b156d
11 changed files with 98 additions and 59 deletions

View File

@ -7,6 +7,7 @@ Saves from 2.3 are not compatible with 2.4.
* **[Flight Planner]** Air-to-air and SEAD escorts will no longer be automatically planned for packages that are not in range of threats. * **[Flight Planner]** Air-to-air and SEAD escorts will no longer be automatically planned for packages that are not in range of threats.
* **[Flight Planner]** Non-custom flight plans will now navigate around threat areas en route to the target area when practical. * **[Flight Planner]** Non-custom flight plans will now navigate around threat areas en route to the target area when practical.
* **[Campaign AI]** Auto-purchase now prefers airfields that are not within range of the enemy. * **[Campaign AI]** Auto-purchase now prefers airfields that are not within range of the enemy.
* **[Mission Generator]** Multiple groups are created for complex SAM sites (SAMs with additional point defense or SHORADS), improving Skynet behavior.
# 2.3.3 # 2.3.3

View File

@ -1,7 +1,6 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import math
import pickle import pickle
import random import random
from dataclasses import dataclass from dataclasses import dataclass
@ -15,7 +14,6 @@ from dcs.vehicles import AirDefence
from game import Game, db from game import Game, db
from game.factions.faction import Faction from game.factions.faction import Faction
from game.theater import Carrier, Lha, LocationType from game.theater import Carrier, Lha, LocationType
from game.theater.conflicttheater import IMPORTANCE_HIGH, IMPORTANCE_LOW
from game.theater.theatergroundobject import ( from game.theater.theatergroundobject import (
BuildingGroundObject, BuildingGroundObject,
CarrierGroundObject, CarrierGroundObject,
@ -479,11 +477,11 @@ class BaseDefenseGenerator:
g = SamGroundObject(namegen.random_objective_name(), group_id, g = SamGroundObject(namegen.random_objective_name(), group_id,
position, self.control_point, for_airbase=True) position, self.control_point, for_airbase=True)
group = generate_anti_air_group(self.game, g, self.faction) groups = generate_anti_air_group(self.game, g, self.faction)
if group is None: if not groups:
logging.error(f"Could not generate SAM at {self.control_point}") logging.error(f"Could not generate SAM at {self.control_point}")
return return
g.groups.append(group) g.groups = groups
self.control_point.base_defenses.append(g) self.control_point.base_defenses.append(g)
def generate_shorad(self) -> None: def generate_shorad(self) -> None:
@ -497,13 +495,13 @@ class BaseDefenseGenerator:
g = SamGroundObject(namegen.random_objective_name(), group_id, g = SamGroundObject(namegen.random_objective_name(), group_id,
position, self.control_point, for_airbase=True) position, self.control_point, for_airbase=True)
group = generate_anti_air_group(self.game, g, self.faction, groups = generate_anti_air_group(self.game, g, self.faction,
ranges=[{AirDefenseRange.Short}]) ranges=[{AirDefenseRange.Short}])
if group is None: if not groups:
logging.error( logging.error(
f"Could not generate SHORAD group at {self.control_point}") f"Could not generate SHORAD group at {self.control_point}")
return return
g.groups.append(group) g.groups = groups
self.control_point.base_defenses.append(g) self.control_point.base_defenses.append(g)
@ -642,12 +640,12 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
g = SamGroundObject(namegen.random_objective_name(), group_id, g = SamGroundObject(namegen.random_objective_name(), group_id,
position, self.control_point, for_airbase=False) position, self.control_point, for_airbase=False)
group = generate_anti_air_group(self.game, g, self.faction, ranges) groups = generate_anti_air_group(self.game, g, self.faction, ranges)
if group is None: if not groups:
logging.error("Could not generate air defense group for %s at %s", logging.error("Could not generate air defense group for %s at %s",
g.name, self.control_point) g.name, self.control_point)
return return
g.groups = [group] g.groups = groups
self.control_point.connected_objectives.append(g) self.control_point.connected_objectives.append(g)
def generate_missile_sites(self) -> None: def generate_missile_sites(self) -> None:

View File

@ -91,7 +91,6 @@ class TheaterGroundObject(MissionTarget):
self.airbase_group = airbase_group self.airbase_group = airbase_group
self.sea_object = sea_object self.sea_object = sea_object
self.is_dead = False self.is_dead = False
# TODO: There is never more than one group.
self.groups: List[Group] = [] self.groups: List[Group] = []
@property @property

View File

@ -1,5 +1,9 @@
import logging
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from enum import Enum from enum import Enum
from typing import Iterator, List
from dcs.unitgroup import VehicleGroup
from game import Game from game import Game
from gen.sam.group_generator import GroupGenerator from gen.sam.group_generator import GroupGenerator
@ -21,6 +25,25 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC):
ground_object.skynet_capable = True ground_object.skynet_capable = True
super().__init__(game, ground_object) super().__init__(game, ground_object)
self.auxiliary_groups: List[VehicleGroup] = []
def add_auxiliary_group(self, name_suffix: str) -> VehicleGroup:
group = VehicleGroup(self.game.next_group_id(),
"|".join([self.go.group_name, name_suffix]))
self.auxiliary_groups.append(group)
return group
def get_generated_group(self) -> VehicleGroup:
raise RuntimeError(
"Deprecated call to AirDefenseGroupGenerator.get_generated_group "
"misses auxiliary groups. Use AirDefenseGroupGenerator.groups "
"instead.")
@property
def groups(self) -> Iterator[VehicleGroup]:
yield self.vg
yield from self.auxiliary_groups
@classmethod @classmethod
@abstractmethod @abstractmethod
def range(cls) -> AirDefenseRange: def range(cls) -> AirDefenseRange:

View File

@ -1,11 +1,13 @@
from __future__ import annotations from __future__ import annotations
import math import math
import random import random
from typing import TYPE_CHECKING, Type from typing import TYPE_CHECKING, Type
from dcs import unitgroup from dcs import unitgroup
from dcs.mapping import Point
from dcs.point import PointAction from dcs.point import PointAction
from dcs.unit import Vehicle, Ship from dcs.unit import Ship, Vehicle
from dcs.unittype import VehicleType from dcs.unittype import VehicleType
from game.factions.faction import Faction from game.factions.faction import Faction
@ -40,12 +42,17 @@ class GroupGenerator:
def add_unit(self, unit_type: Type[VehicleType], name: str, pos_x: float, def add_unit(self, unit_type: Type[VehicleType], name: str, pos_x: float,
pos_y: float, heading: int) -> Vehicle: pos_y: float, heading: int) -> Vehicle:
return self.add_unit_to_group(self.vg, unit_type, name,
Point(pos_x, pos_y), heading)
def add_unit_to_group(self, group: unitgroup.VehicleGroup,
unit_type: Type[VehicleType], name: str,
position: Point, heading: int) -> Vehicle:
unit = Vehicle(self.game.next_unit_id(), unit = Vehicle(self.game.next_unit_id(),
f"{self.go.group_name}|{name}", unit_type.id) f"{group.name}|{name}", unit_type.id)
unit.position.x = pos_x unit.position = position
unit.position.y = pos_y
unit.heading = heading unit.heading = heading
self.vg.add_unit(unit) group.add_unit(unit)
return unit return unit
def get_circular_position(self, num_units, launcher_distance, coverage=90): def get_circular_position(self, num_units, launcher_distance, coverage=90):

View File

@ -171,19 +171,19 @@ def get_faction_possible_ewrs_generator(faction: Faction) -> List[Type[GroupGene
def _generate_anti_air_from( def _generate_anti_air_from(
generators: Sequence[Type[AirDefenseGroupGenerator]], game: Game, generators: Sequence[Type[AirDefenseGroupGenerator]], game: Game,
ground_object: SamGroundObject) -> Optional[VehicleGroup]: ground_object: SamGroundObject) -> List[VehicleGroup]:
if not generators: if not generators:
return None return []
sam_generator_class = random.choice(generators) sam_generator_class = random.choice(generators)
generator = sam_generator_class(game, ground_object) generator = sam_generator_class(game, ground_object)
generator.generate() generator.generate()
return generator.get_generated_group() return list(generator.groups)
def generate_anti_air_group( def generate_anti_air_group(
game: Game, ground_object: SamGroundObject, faction: Faction, game: Game, ground_object: SamGroundObject, faction: Faction,
ranges: Optional[Iterable[Set[AirDefenseRange]]] = None ranges: Optional[Iterable[Set[AirDefenseRange]]] = None
) -> Optional[VehicleGroup]: ) -> List[VehicleGroup]:
""" """
This generate a SAM group This generate a SAM group
:param game: The Game. :param game: The Game.
@ -212,11 +212,11 @@ def generate_anti_air_group(
for range_options in ranges: for range_options in ranges:
generators_for_range = [g for g in generators if generators_for_range = [g for g in generators if
g.range() in range_options] g.range() in range_options]
group = _generate_anti_air_from(generators_for_range, game, groups = _generate_anti_air_from(generators_for_range, game,
ground_object) ground_object)
if group is not None: if groups:
return group return groups
return None return []
def generate_ewr_group(game: Game, ground_object: TheaterGroundObject, def generate_ewr_group(game: Game, ground_object: TheaterGroundObject,

View File

@ -1,5 +1,6 @@
import random import random
from dcs.mapping import Point
from dcs.vehicles import AirDefence from dcs.vehicles import AirDefence
from gen.sam.airdefensegroupgenerator import ( from gen.sam.airdefensegroupgenerator import (
@ -22,7 +23,9 @@ class HawkGenerator(AirDefenseGroupGenerator):
self.add_unit(AirDefence.SAM_Hawk_TR_AN_MPQ_46, "TR", self.position.x + 40, self.position.y, self.heading) self.add_unit(AirDefence.SAM_Hawk_TR_AN_MPQ_46, "TR", self.position.x + 40, self.position.y, self.heading)
# Triple A for close range defense # Triple A for close range defense
self.add_unit(AirDefence.AAA_Vulcan_M163, "AAA", self.position.x + 20, self.position.y+30, self.heading) aa_group = self.add_auxiliary_group("AA")
self.add_unit_to_group(aa_group, AirDefence.AAA_Vulcan_M163, "AAA",
self.position + Point(20, 30), self.heading)
num_launchers = random.randint(3, 6) num_launchers = random.randint(3, 6)
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=180) positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=180)

View File

@ -1,5 +1,6 @@
import random import random
from dcs.mapping import Point
from dcs.vehicles import AirDefence from dcs.vehicles import AirDefence
from gen.sam.airdefensegroupgenerator import ( from gen.sam.airdefensegroupgenerator import (
@ -21,8 +22,13 @@ class HQ7Generator(AirDefenseGroupGenerator):
self.add_unit(AirDefence.HQ_7_Self_Propelled_LN, "LN", self.position.x + 20, self.position.y, self.heading) self.add_unit(AirDefence.HQ_7_Self_Propelled_LN, "LN", self.position.x + 20, self.position.y, self.heading)
# Triple A for close range defense # Triple A for close range defense
self.add_unit(AirDefence.AAA_ZU_23_on_Ural_375, "AAA1", self.position.x + 20, self.position.y+30, self.heading) aa_group = self.add_auxiliary_group("AA")
self.add_unit(AirDefence.AAA_ZU_23_on_Ural_375, "AAA2", self.position.x - 20, self.position.y-30, self.heading) self.add_unit_to_group(aa_group, AirDefence.AAA_ZU_23_on_Ural_375,
"AAA1", self.position + Point(20, 30),
self.heading)
self.add_unit_to_group(aa_group, AirDefence.AAA_ZU_23_on_Ural_375,
"AAA2", self.position.x - Point(20, 30),
self.heading)
num_launchers = random.randint(0, 3) num_launchers = random.randint(0, 3)
if num_launchers > 0: if num_launchers > 0:

View File

@ -1,5 +1,6 @@
import random import random
from dcs.mapping import Point
from dcs.vehicles import AirDefence from dcs.vehicles import AirDefence
from gen.sam.airdefensegroupgenerator import ( from gen.sam.airdefensegroupgenerator import (
@ -30,10 +31,12 @@ class PatriotGenerator(AirDefenseGroupGenerator):
self.add_unit(AirDefence.SAM_Patriot_LN_M901, "LN#" + str(i), position[0], position[1], position[2]) self.add_unit(AirDefence.SAM_Patriot_LN_M901, "LN#" + str(i), position[0], position[1], position[2])
# Short range protection for high value site # Short range protection for high value site
aa_group = self.add_auxiliary_group("AA")
num_launchers = random.randint(3, 4) num_launchers = random.randint(3, 4)
positions = self.get_circular_position(num_launchers, launcher_distance=200, coverage=360) positions = self.get_circular_position(num_launchers, launcher_distance=200, coverage=360)
for i, position in enumerate(positions): for i, (x, y, heading) in enumerate(positions):
self.add_unit(AirDefence.AAA_Vulcan_M163, "SPAAA#" + str(i), position[0], position[1], position[2]) self.add_unit_to_group(aa_group, AirDefence.AAA_Vulcan_M163,
f"SPAAA#{i}", Point(x, y), heading)
@classmethod @classmethod
def range(cls) -> AirDefenseRange: def range(cls) -> AirDefenseRange:

View File

@ -1,5 +1,6 @@
import random import random
from dcs.mapping import Point
from dcs.vehicles import AirDefence from dcs.vehicles import AirDefence
from gen.sam.airdefensegroupgenerator import ( from gen.sam.airdefensegroupgenerator import (
@ -49,47 +50,46 @@ class SA10Generator(AirDefenseGroupGenerator):
def generate_defensive_groups(self) -> None: def generate_defensive_groups(self) -> None:
# AAA for defending against close targets. # AAA for defending against close targets.
aa_group = self.add_auxiliary_group("AA")
num_launchers = random.randint(6, 8) num_launchers = random.randint(6, 8)
positions = self.get_circular_position( positions = self.get_circular_position(
num_launchers, launcher_distance=210, coverage=360) num_launchers, launcher_distance=210, coverage=360)
for i, position in enumerate(positions): for i, (x, y, heading) in enumerate(positions):
self.add_unit(AirDefence.SPAAA_ZSU_23_4_Shilka, "AA#" + str(i), self.add_unit_to_group(aa_group, AirDefence.SPAAA_ZSU_23_4_Shilka,
position[0], position[1], position[2]) f"AA#{i}", Point(x, y), heading)
class Tier2SA10Generator(SA10Generator): class Tier2SA10Generator(SA10Generator):
def generate_defensive_groups(self) -> None: def generate_defensive_groups(self) -> None:
# Create AAA the way the main group does.
super().generate_defensive_groups()
# SA-15 for both shorter range targets and point defense. # SA-15 for both shorter range targets and point defense.
pd_group = self.add_auxiliary_group("PD")
num_launchers = random.randint(2, 4) num_launchers = random.randint(2, 4)
positions = self.get_circular_position( positions = self.get_circular_position(
num_launchers, launcher_distance=140, coverage=360) num_launchers, launcher_distance=140, coverage=360)
for i, position in enumerate(positions): for i, (x, y, heading) in enumerate(positions):
self.add_unit(AirDefence.SAM_SA_15_Tor_9A331, "PD#" + str(i), self.add_unit_to_group(pd_group, AirDefence.SAM_SA_15_Tor_9A331,
position[0], position[1], position[2]) f"PD#{i}", Point(x, y), heading)
# AAA for defending against close targets.
num_launchers = random.randint(6, 8)
positions = self.get_circular_position(
num_launchers, launcher_distance=210, coverage=360)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SPAAA_ZSU_23_4_Shilka, "AA#" + str(i),
position[0], position[1], position[2])
class Tier3SA10Generator(SA10Generator): class Tier3SA10Generator(SA10Generator):
def generate_defensive_groups(self) -> None: def generate_defensive_groups(self) -> None:
# SA-15 for both shorter range targets and point defense.
num_launchers = random.randint(2, 4)
positions = self.get_circular_position(
num_launchers, launcher_distance=140, coverage=360)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_SA_15_Tor_9A331, "PD#" + str(i),
position[0], position[1], position[2])
# AAA for defending against close targets. # AAA for defending against close targets.
aa_group = self.add_auxiliary_group("AA")
num_launchers = random.randint(6, 8) num_launchers = random.randint(6, 8)
positions = self.get_circular_position( positions = self.get_circular_position(
num_launchers, launcher_distance=210, coverage=360) num_launchers, launcher_distance=210, coverage=360)
for i, position in enumerate(positions): for i, (x, y, heading) in enumerate(positions):
self.add_unit(AirDefence.SAM_SA_19_Tunguska_2S6, "AA#" + str(i), self.add_unit_to_group(aa_group, AirDefence.SAM_SA_19_Tunguska_2S6,
position[0], position[1], position[2]) f"AA#{i}", Point(x, y), heading)
# SA-15 for both shorter range targets and point defense.
pd_group = self.add_auxiliary_group("PD")
num_launchers = random.randint(2, 4)
positions = self.get_circular_position(
num_launchers, launcher_distance=140, coverage=360)
for i, (x, y, heading) in enumerate(positions):
self.add_unit_to_group(pd_group, AirDefence.SAM_SA_15_Tor_9A331,
f"PD#{i}", Point(x, y), heading)

View File

@ -355,8 +355,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
# Generate SAM # Generate SAM
generator = sam_generator(self.game, self.ground_object) generator = sam_generator(self.game, self.ground_object)
generator.generate() generator.generate()
generated_group = generator.get_generated_group() self.ground_object.groups = generator.groups
self.ground_object.groups = [generated_group]
GameUpdateSignal.get_instance().updateBudget(self.game) GameUpdateSignal.get_instance().updateBudget(self.game)