diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 32f8c300..b93e5400 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -118,6 +118,8 @@ class MizCampaignLoader: AirDefence.SAM_SA_3_S_125_LN_5P73.id, } + REQUIRED_EWR_UNIT_TYPE = AirDefence.EWR_1L13.id + BASE_DEFENSE_RADIUS = nautical_miles(2) def __init__(self, miz: Path, theater: ConflictTheater) -> None: @@ -247,6 +249,12 @@ class MizCampaignLoader: if group.units[0].type in self.REQUIRED_MEDIUM_RANGE_SAM_UNIT_TYPES: yield group + @property + def required_ewrs(self) -> Iterator[VehicleGroup]: + for group in self.red.vehicle_group: + if group.units[0].type in self.REQUIRED_EWR_UNIT_TYPE: + yield group + @property def helipads(self) -> Iterator[StaticGroup]: for group in self.blue.static_group: @@ -356,9 +364,14 @@ class MizCampaignLoader: for group in self.ewrs: closest, distance = self.objective_info(group) - closest.preset_locations.ewrs.append( - PointWithHeading.from_point(group.position, group.units[0].heading) - ) + if distance < self.BASE_DEFENSE_RADIUS: + closest.preset_locations.ewrs.append( + PointWithHeading.from_point(group.position, group.units[0].heading) + ) + else: + closest.preset_locations.ewrs.append( + PointWithHeading.from_point(group.position, group.units[0].heading) + ) for group in self.offshore_strike_targets: closest, distance = self.objective_info(group) @@ -396,6 +409,12 @@ class MizCampaignLoader: PointWithHeading.from_point(group.position, group.units[0].heading) ) + for group in self.required_ewrs: + closest, distance = self.objective_info(group) + closest.preset_locations.required_ewrs.append( + PointWithHeading.from_point(group.position, group.units[0].heading) + ) + for group in self.helipads: closest, distance = self.objective_info(group) closest.helipads.append( diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 8f5e95a2..f2a95200 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -107,6 +107,9 @@ class PresetLocations: #: Locations of medium range SAMs which should always be spawned. required_medium_range_sams: List[PointWithHeading] = field(default_factory=list) + #: Locations of EWRs which should always be spawned. + required_ewrs: List[PointWithHeading] = field(default_factory=list) + @staticmethod def _random_from(points: List[PointWithHeading]) -> Optional[PointWithHeading]: """Finds, removes, and returns a random position from the given list.""" diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 85fb806d..5316b9c8 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -37,10 +37,8 @@ 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 ( - generate_anti_air_group, - generate_ewr_group, -) +from gen.sam.sam_group_generator import generate_anti_air_group +from gen.sam.ewr_group_generator import generate_ewr_group from . import ( ConflictTheater, ControlPoint, @@ -464,7 +462,11 @@ class BaseDefenseGenerator: group_id = self.game.next_group_id() g = EwrGroundObject( - namegen.random_objective_name(), group_id, position, self.control_point + namegen.random_objective_name(), + group_id, + position, + self.control_point, + True, ) group = generate_ewr_group(self.game, g, self.faction) @@ -609,6 +611,7 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): def generate_ground_points(self) -> None: """Generate ground objects and AA sites for the control point.""" skip_sams = self.generate_required_aa() + skip_ewrs = self.generate_required_ewr() if self.control_point.is_global: return @@ -625,6 +628,12 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): skip_sams -= 1 else: self.generate_aa_site() + # 1 in 4 additional objectives are EWR. + elif random.randint(0, 3) == 0: + if skip_ewrs > 0: + skip_ewrs -= 1 + else: + self.generate_ewr_site() else: self.generate_ground_point() @@ -656,6 +665,17 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): presets.required_medium_range_sams ) + def generate_required_ewr(self) -> int: + """Generates the EWR sites that are required by the campaign. + + Returns: + The number of EWR sites that were generated. + """ + presets = self.control_point.preset_locations + for position in presets.required_ewrs: + self.generate_ewr_at(position) + return len(presets.required_ewrs) + def generate_ground_point(self) -> None: try: category = random.choice(self.faction.building_set) @@ -733,6 +753,33 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): g.groups = groups self.control_point.connected_objectives.append(g) + def generate_ewr_site(self) -> None: + position = self.location_finder.location_for(LocationType.Ewr) + if position is None: + return + self.generate_ewr_at(position) + + def generate_ewr_at(self, position: Point) -> None: + group_id = self.game.next_group_id() + + g = EwrGroundObject( + namegen.random_objective_name(), + group_id, + position, + self.control_point, + for_airbase=False, + ) + 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_missile_sites(self) -> None: for i in range(self.faction.missiles_group_count): self.generate_missile_site() diff --git a/game/theater/theatergroundobject.py b/game/theater/theatergroundobject.py index 7c6581da..c476a90b 100644 --- a/game/theater/theatergroundobject.py +++ b/game/theater/theatergroundobject.py @@ -442,7 +442,12 @@ class VehicleGroupGroundObject(BaseDefenseGroundObject): class EwrGroundObject(BaseDefenseGroundObject): def __init__( - self, name: str, group_id: int, position: Point, control_point: ControlPoint + self, + name: str, + group_id: int, + position: Point, + control_point: ControlPoint, + for_airbase: bool, ) -> None: super().__init__( name=name, @@ -452,7 +457,7 @@ class EwrGroundObject(BaseDefenseGroundObject): heading=0, control_point=control_point, dcs_identifier="EWR", - airbase_group=True, + airbase_group=for_airbase, sea_object=False, ) diff --git a/gen/sam/ewr_group_generator.py b/gen/sam/ewr_group_generator.py new file mode 100644 index 00000000..e576f5a8 --- /dev/null +++ b/gen/sam/ewr_group_generator.py @@ -0,0 +1,63 @@ +import random +from typing import List, Optional, Type + +from dcs.unitgroup import VehicleGroup + +from game import Game +from game.factions.faction import Faction +from game.theater.theatergroundobject import EwrGroundObject +from gen.sam.ewrs import ( + BigBirdGenerator, + BoxSpringGenerator, + DogEarGenerator, + FlatFaceGenerator, + HawkEwrGenerator, + PatriotEwrGenerator, + RolandEwrGenerator, + SnowDriftGenerator, + StraightFlushGenerator, + TallRackGenerator, +) +from gen.sam.group_generator import GroupGenerator + +EWR_MAP = { + "BoxSpringGenerator": BoxSpringGenerator, + "TallRackGenerator": TallRackGenerator, + "DogEarGenerator": DogEarGenerator, + "RolandEwrGenerator": RolandEwrGenerator, + "FlatFaceGenerator": FlatFaceGenerator, + "PatriotEwrGenerator": PatriotEwrGenerator, + "BigBirdGenerator": BigBirdGenerator, + "SnowDriftGenerator": SnowDriftGenerator, + "StraightFlushGenerator": StraightFlushGenerator, + "HawkEwrGenerator": HawkEwrGenerator, +} + + +def get_faction_possible_ewrs_generator( + faction: Faction, +) -> List[Type[GroupGenerator]]: + """ + Return the list of possible EWR generators for the given faction + :param faction: Faction name to search units for + """ + return [EWR_MAP[s] for s in faction.ewrs] + + +def generate_ewr_group( + game: Game, ground_object: EwrGroundObject, faction: Faction +) -> Optional[VehicleGroup]: + """Generates an early warning radar group. + + :param game: The Game. + :param ground_object: The ground object which will own the EWR group. + :param faction: Owner faction. + :return: The generated group, or None if one could not be generated. + """ + generators = get_faction_possible_ewrs_generator(faction) + if len(generators) > 0: + generator_class = random.choice(generators) + generator = generator_class(game, ground_object) + generator.generate() + return generator.get_generated_group() + return None diff --git a/gen/sam/sam_group_generator.py b/gen/sam/sam_group_generator.py index 9d8545fb..df59d18b 100644 --- a/gen/sam/sam_group_generator.py +++ b/gen/sam/sam_group_generator.py @@ -23,18 +23,7 @@ from gen.sam.cold_war_flak import ( ColdWarFlakGenerator, EarlyColdWarFlakGenerator, ) -from gen.sam.ewrs import ( - BigBirdGenerator, - BoxSpringGenerator, - DogEarGenerator, - FlatFaceGenerator, - HawkEwrGenerator, - PatriotEwrGenerator, - RolandEwrGenerator, - SnowDriftGenerator, - StraightFlushGenerator, - TallRackGenerator, -) + from gen.sam.freya_ewr import FreyaGenerator from gen.sam.group_generator import GroupGenerator from gen.sam.sam_avenger import AvengerGenerator @@ -152,19 +141,6 @@ SAM_PRICES = { AirDefence.HQ_7_Self_Propelled_LN: 35, } -EWR_MAP = { - "BoxSpringGenerator": BoxSpringGenerator, - "TallRackGenerator": TallRackGenerator, - "DogEarGenerator": DogEarGenerator, - "RolandEwrGenerator": RolandEwrGenerator, - "FlatFaceGenerator": FlatFaceGenerator, - "PatriotEwrGenerator": PatriotEwrGenerator, - "BigBirdGenerator": BigBirdGenerator, - "SnowDriftGenerator": SnowDriftGenerator, - "StraightFlushGenerator": StraightFlushGenerator, - "HawkEwrGenerator": HawkEwrGenerator, -} - def get_faction_possible_sams_generator( faction: Faction, @@ -176,14 +152,6 @@ def get_faction_possible_sams_generator( return [SAM_MAP[s] for s in faction.air_defenses] -def get_faction_possible_ewrs_generator(faction: Faction) -> List[Type[GroupGenerator]]: - """ - Return the list of possible SAM generator for the given faction - :param faction: Faction name to search units for - """ - return [EWR_MAP[s] for s in faction.ewrs] - - def _generate_anti_air_from( generators: Sequence[Type[AirDefenseGroupGenerator]], game: Game, @@ -236,22 +204,3 @@ def generate_anti_air_group( if groups: return groups return [] - - -def generate_ewr_group( - game: Game, ground_object: TheaterGroundObject, faction: Faction -) -> Optional[VehicleGroup]: - """Generates an early warning radar group. - - :param game: The Game. - :param ground_object: The ground object which will own the EWR group. - :param faction: Owner faction. - :return: The generated group, or None if one could not be generated. - """ - generators = get_faction_possible_ewrs_generator(faction) - if len(generators) > 0: - generator_class = random.choice(generators) - generator = generator_class(game, ground_object) - generator.generate() - return generator.get_generated_group() - return None diff --git a/qt_ui/uiconstants.py b/qt_ui/uiconstants.py index b1016692..e34515e0 100644 --- a/qt_ui/uiconstants.py +++ b/qt_ui/uiconstants.py @@ -147,6 +147,8 @@ def load_icons(): "./resources/ui/ground_assets/" + category + "_blue.png" ) ICONS["destroyed"] = QPixmap("./resources/ui/ground_assets/destroyed.png") + ICONS["EWR"] = QPixmap("./resources/ui/ground_assets/ewr.png") + ICONS["EWR_blue"] = QPixmap("./resources/ui/ground_assets/ewr_blue.png") ICONS["ship"] = QPixmap("./resources/ui/ground_assets/ship.png") ICONS["ship_blue"] = QPixmap("./resources/ui/ground_assets/ship_blue.png") ICONS["missile"] = QPixmap("./resources/ui/ground_assets/missile.png") diff --git a/resources/campaigns/invasion_of_iran.miz b/resources/campaigns/invasion_of_iran.miz index de62a998..ea4a51b4 100644 Binary files a/resources/campaigns/invasion_of_iran.miz and b/resources/campaigns/invasion_of_iran.miz differ diff --git a/resources/ui/ground_assets/ewr.png b/resources/ui/ground_assets/ewr.png new file mode 100644 index 00000000..1f902ac2 Binary files /dev/null and b/resources/ui/ground_assets/ewr.png differ diff --git a/resources/ui/ground_assets/ewr_blue.png b/resources/ui/ground_assets/ewr_blue.png new file mode 100644 index 00000000..e4b01cc2 Binary files /dev/null and b/resources/ui/ground_assets/ewr_blue.png differ