Fall back to lower range SAMs when needed.

Mostly fixes https://github.com/Khopa/dcs_liberation/issues/473. The
last part of the fix is to migrate the `shorads` property of the faction
to just be in `sams` and just use the property to decide its use.
Currently factions like USA 2005 that have long range SAMs and SHORADs
only will still not spawn anything at medium sites because they have no
other SAMs declared.
This commit is contained in:
Dan Albert 2020-11-27 17:23:17 -08:00
parent fcdb22db5b
commit e8aa9839b0
2 changed files with 71 additions and 46 deletions

View File

@ -4,7 +4,7 @@ import logging
import math
import pickle
import random
from typing import Any, Dict, Iterable, Optional
from typing import Any, Dict, Iterable, Optional, Set
from dcs.mapping import Point
from dcs.task import CAP, CAS, PinpointStrike
@ -35,9 +35,11 @@ from gen.fleet.ship_group_generator import (
)
from gen.locations.preset_location_finder import MizDataLocationFinder
from gen.missiles.missiles_group_generator import generate_missile_group
from gen.sam.airdefensegroupgenerator import AirDefenseRange
from gen.sam.sam_group_generator import (
LONG_RANGE_SAMS, MEDIUM_RANGE_SAMS, generate_anti_air_group,
generate_ewr_group, generate_shorad_group,
generate_anti_air_group,
generate_ewr_group,
generate_shorad_group,
)
from . import (
ConflictTheater,
@ -585,9 +587,16 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
"""
presets = self.control_point.preset_locations
for position in presets.required_long_range_sams:
self.generate_aa_at(position, filter_names=LONG_RANGE_SAMS)
self.generate_aa_at(position, ranges=[
{AirDefenseRange.Long},
{AirDefenseRange.Medium},
{AirDefenseRange.Short},
])
for position in presets.required_medium_range_sams:
self.generate_aa_at(position, filter_names=MEDIUM_RANGE_SAMS)
self.generate_aa_at(position, ranges=[
{AirDefenseRange.Medium},
{AirDefenseRange.Short},
])
return (len(presets.required_long_range_sams) +
len(presets.required_medium_range_sams))
@ -629,25 +638,23 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
position = self.location_finder.location_for(LocationType.Sam)
if position is None:
return
self.generate_aa_at(position)
self.generate_aa_at(position, ranges=[
# Prefer to use proper SAMs, but fall back to SHORADs if needed.
{AirDefenseRange.Long, AirDefenseRange.Medium},
{AirDefenseRange.Short},
])
def generate_aa_at(self, position: Point,
filter_names: Optional[Iterable[str]] = None) -> None:
def generate_aa_at(
self, position: Point,
ranges: Iterable[Set[AirDefenseRange]]) -> None:
group_id = self.game.next_group_id()
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,
filter_names)
group = generate_anti_air_group(self.game, g, self.faction, ranges)
if group is None:
location = f"{g.name} at {self.control_point}"
if filter_names is not None:
logging.warning(
"Could not generate SAM group for %s from types: %s",
location, ", ".join(filter_names)
)
else:
logging.error("Could not generate SAM group for %s", location)
logging.error("Could not generate air defense group for %s at %s",
g.name, self.control_point)
return
g.groups = [group]
self.control_point.connected_objectives.append(g)

View File

@ -1,5 +1,5 @@
import random
from typing import Dict, Iterable, List, Optional, Type
from typing import Dict, Iterable, List, Optional, Sequence, Set, Type
from dcs.unitgroup import VehicleGroup
from dcs.vehicles import AirDefence
@ -13,6 +13,10 @@ from gen.sam.aaa_flak import FlakGenerator
from gen.sam.aaa_flak18 import Flak18Generator
from gen.sam.aaa_ww2_ally_flak import AllyWW2FlakGenerator
from gen.sam.aaa_zu23_insurgent import ZU23InsurgentGenerator
from gen.sam.airdefensegroupgenerator import (
AirDefenseGroupGenerator,
AirDefenseRange,
)
from gen.sam.cold_war_flak import (
ColdWarFlakGenerator,
EarlyColdWarFlakGenerator,
@ -30,7 +34,6 @@ from gen.sam.ewrs import (
TallRackGenerator,
)
from gen.sam.freya_ewr import FreyaGenerator
from gen.sam.airdefensegroupgenerator import AirDefenseGroupGenerator
from gen.sam.group_generator import GroupGenerator
from gen.sam.sam_avenger import AvengerGenerator
from gen.sam.sam_chaparral import ChaparralGenerator
@ -98,17 +101,6 @@ SAM_MAP: Dict[str, Type[AirDefenseGroupGenerator]] = {
"AllyWW2FlakGenerator": AllyWW2FlakGenerator
}
#: Used to fill the long-range required SAM locations in the campaign.
LONG_RANGE_SAMS = {
"SA10Generator",
"PatriotGenerator",
"Tier2SA10Generator",
"Tier3SA10Generator",
}
#: Used to fill the medium-range required SAM location in the campaign.
MEDIUM_RANGE_SAMS = SAM_MAP.keys() - LONG_RANGE_SAMS
SAM_PRICES = {
AirDefence.SAM_Hawk_PCP: 35,
@ -159,16 +151,12 @@ EWR_MAP = {
def get_faction_possible_sams_generator(
faction: Faction,
filter_names: Optional[Iterable[str]] = None
) -> List[Type[GroupGenerator]]:
faction: Faction) -> List[Type[AirDefenseGroupGenerator]]:
"""
Return the list of possible SAM generator for the given faction
:param faction: Faction name to search units for
:param filter_names: Optional list of names to filter allowed SAMs by.
"""
return [SAM_MAP[s] for s in faction.sams if
filter_names is None or s in filter_names]
return [SAM_MAP[s] for s in faction.sams]
def get_faction_possible_ewrs_generator(faction: Faction) -> List[Type[GroupGenerator]]:
@ -179,23 +167,53 @@ def get_faction_possible_ewrs_generator(faction: Faction) -> List[Type[GroupGene
return [EWR_MAP[s] for s in faction.ewrs]
def _generate_anti_air_from(
generators: Sequence[Type[AirDefenseGroupGenerator]], game: Game,
ground_object: SamGroundObject) -> Optional[VehicleGroup]:
if not generators:
return None
sam_generator_class = random.choice(generators)
generator = sam_generator_class(game, ground_object)
generator.generate()
return generator.get_generated_group()
def generate_anti_air_group(
game: Game, ground_object: TheaterGroundObject, faction: Faction,
filter_names: Optional[Iterable[str]] = None) -> Optional[VehicleGroup]:
game: Game, ground_object: SamGroundObject, faction: Faction,
ranges: Optional[Iterable[Set[AirDefenseRange]]] = None
) -> Optional[VehicleGroup]:
"""
This generate a SAM group
:param game: The Game.
:param ground_object: The ground object which will own the sam group.
:param faction: Owner faction.
:param filter_names: Optional list of names to filter allowed SAMs by.
:param ranges: Optional list of preferred ranges of the air defense to
create. If None, any generator may be used. Otherwise generators
matching the given ranges will be used in order of preference. For
example, when given `[{Long, Medium}, {Short}]`, long and medium range
air defenses will be tried first with no bias, and short range air
defenses will be used if no long or medium range generators are
available to the faction. If instead `[{Long}, {Medium}, {Short}]` had
been used, long range systems would take precedence over medium range
systems. If instead `[{Long, Medium, Short}]` had been used, all types
would be considered with equal preference.
:return: The generated group, or None if one could not be generated.
"""
generators = get_faction_possible_sams_generator(faction, filter_names)
if len(generators) > 0:
sam_generator_class = random.choice(generators)
generator = sam_generator_class(game, ground_object)
generator.generate()
return generator.get_generated_group()
generators = get_faction_possible_sams_generator(faction)
if ranges is None:
ranges = [{
AirDefenseRange.Long,
AirDefenseRange.Medium,
AirDefenseRange.Short,
}]
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,
ground_object)
if group is not None:
return group
return None