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]** 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.
* **[Mission Generator]** Multiple groups are created for complex SAM sites (SAMs with additional point defense or SHORADS), improving Skynet behavior.
# 2.3.3

View File

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

View File

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

View File

@ -1,5 +1,9 @@
import logging
from abc import ABC, abstractmethod
from enum import Enum
from typing import Iterator, List
from dcs.unitgroup import VehicleGroup
from game import Game
from gen.sam.group_generator import GroupGenerator
@ -21,6 +25,25 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC):
ground_object.skynet_capable = True
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
@abstractmethod
def range(cls) -> AirDefenseRange:

View File

@ -1,11 +1,13 @@
from __future__ import annotations
import math
import random
from typing import TYPE_CHECKING, Type
from dcs import unitgroup
from dcs.mapping import Point
from dcs.point import PointAction
from dcs.unit import Vehicle, Ship
from dcs.unit import Ship, Vehicle
from dcs.unittype import VehicleType
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,
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(),
f"{self.go.group_name}|{name}", unit_type.id)
unit.position.x = pos_x
unit.position.y = pos_y
f"{group.name}|{name}", unit_type.id)
unit.position = position
unit.heading = heading
self.vg.add_unit(unit)
group.add_unit(unit)
return unit
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(
generators: Sequence[Type[AirDefenseGroupGenerator]], game: Game,
ground_object: SamGroundObject) -> Optional[VehicleGroup]:
ground_object: SamGroundObject) -> List[VehicleGroup]:
if not generators:
return None
return []
sam_generator_class = random.choice(generators)
generator = sam_generator_class(game, ground_object)
generator.generate()
return generator.get_generated_group()
return list(generator.groups)
def generate_anti_air_group(
game: Game, ground_object: SamGroundObject, faction: Faction,
ranges: Optional[Iterable[Set[AirDefenseRange]]] = None
) -> Optional[VehicleGroup]:
) -> List[VehicleGroup]:
"""
This generate a SAM group
:param game: The Game.
@ -212,11 +212,11 @@ def generate_anti_air_group(
for range_options in ranges:
generators_for_range = [g for g in generators if
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)
if group is not None:
return group
return None
if groups:
return groups
return []
def generate_ewr_group(game: Game, ground_object: TheaterGroundObject,

View File

@ -1,5 +1,6 @@
import random
from dcs.mapping import Point
from dcs.vehicles import AirDefence
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)
# 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)
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=180)

View File

@ -1,5 +1,6 @@
import random
from dcs.mapping import Point
from dcs.vehicles import AirDefence
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)
# 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)
self.add_unit(AirDefence.AAA_ZU_23_on_Ural_375, "AAA2", 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_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)
if num_launchers > 0:

View File

@ -1,5 +1,6 @@
import random
from dcs.mapping import Point
from dcs.vehicles import AirDefence
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])
# Short range protection for high value site
aa_group = self.add_auxiliary_group("AA")
num_launchers = random.randint(3, 4)
positions = self.get_circular_position(num_launchers, launcher_distance=200, coverage=360)
for i, position in enumerate(positions):
self.add_unit(AirDefence.AAA_Vulcan_M163, "SPAAA#" + str(i), position[0], position[1], position[2])
for i, (x, y, heading) in enumerate(positions):
self.add_unit_to_group(aa_group, AirDefence.AAA_Vulcan_M163,
f"SPAAA#{i}", Point(x, y), heading)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@ -1,5 +1,6 @@
import random
from dcs.mapping import Point
from dcs.vehicles import AirDefence
from gen.sam.airdefensegroupgenerator import (
@ -49,47 +50,46 @@ class SA10Generator(AirDefenseGroupGenerator):
def generate_defensive_groups(self) -> None:
# AAA for defending against close targets.
aa_group = self.add_auxiliary_group("AA")
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])
for i, (x, y, heading) in enumerate(positions):
self.add_unit_to_group(aa_group, AirDefence.SPAAA_ZSU_23_4_Shilka,
f"AA#{i}", Point(x, y), heading)
class Tier2SA10Generator(SA10Generator):
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.
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, 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.
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])
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)
class Tier3SA10Generator(SA10Generator):
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.
aa_group = self.add_auxiliary_group("AA")
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.SAM_SA_19_Tunguska_2S6, "AA#" + str(i),
position[0], position[1], position[2])
for i, (x, y, heading) in enumerate(positions):
self.add_unit_to_group(aa_group, AirDefence.SAM_SA_19_Tunguska_2S6,
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
generator = sam_generator(self.game, self.ground_object)
generator.generate()
generated_group = generator.get_generated_group()
self.ground_object.groups = [generated_group]
self.ground_object.groups = generator.groups
GameUpdateSignal.get_instance().updateBudget(self.game)