diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 8ad73838..566118f9 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -105,6 +105,20 @@ class MizCampaignLoader: OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id + # Multiple options for the required SAMs so campaign designers can more + # easily see the coverage of their IADS. Designers focused on campaigns that + # will primarily use SA-2s can place SA-2 launchers to ensure that they will + # have adequate coverage, and designers focused on campaigns that will + # primarily use SA-10s can do the same. + REQUIRED_SAM_UNIT_TYPES = { + AirDefence.SAM_Hawk_LN_M192, + AirDefence.SAM_Patriot_LN_M901, + AirDefence.SAM_SA_10_S_300PS_LN_5P85C, + AirDefence.SAM_SA_10_S_300PS_LN_5P85D, + AirDefence.SAM_SA_2_LN_SM_90, + AirDefence.SAM_SA_3_S_125_LN_5P73, + } + BASE_DEFENSE_RADIUS = nm_to_meter(2) def __init__(self, miz: Path, theater: ConflictTheater) -> None: @@ -207,6 +221,12 @@ class MizCampaignLoader: if group.units[0].type == self.OFFSHORE_STRIKE_TARGET_UNIT_TYPE: yield group + @property + def required_sams(self) -> Iterator[VehicleGroup]: + for group in self.red.vehicle_group: + if group.units[0].type == self.REQUIRED_SAM_UNIT_TYPES: + yield group + @cached_property def control_points(self) -> Dict[int, ControlPoint]: control_points = {} @@ -306,6 +326,10 @@ class MizCampaignLoader: closest, distance = self.objective_info(group) closest.preset_locations.ships.append(group.position) + for group in self.required_sams: + closest, distance = self.objective_info(group) + closest.preset_locations.required_sams.append(group.position) + def populate_theater(self) -> None: for control_point in self.control_points.values(): self.theater.add_controlpoint(control_point) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 5c549b21..4e61cfa5 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -85,7 +85,7 @@ class PresetLocations: offshore_strike_locations: List[Point] = field(default_factory=list) #: Locations of SAMs which should always be spawned. - fixed_sams: List[Point] = field(default_factory=list) + required_sams: List[Point] = field(default_factory=list) @staticmethod def _random_from(points: List[Point]) -> Optional[Point]: diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 81aac268..2eee490a 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -544,15 +544,31 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): # Always generate at least one AA point. self.generate_aa_site() + skip_sams = self.generate_required_aa() + # And between 2 and 7 other objectives. amount = random.randrange(2, 7) for i in range(amount): # 1 in 4 additional objectives are AA. if random.randint(0, 3) == 0: - self.generate_aa_site() + if skip_sams > 0: + skip_sams -= 1 + else: + self.generate_aa_site() else: self.generate_ground_point() + def generate_required_aa(self) -> int: + """Generates the AA sites that are required by the campaign. + + Returns: + The number of AA sites that were generated. + """ + sams = self.control_point.preset_locations.required_sams + for position in sams: + self.generate_aa_at(position) + return len(sams) + def generate_ground_point(self) -> None: try: category = random.choice(self.faction.building_set) @@ -591,7 +607,9 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): position = self.location_finder.location_for(LocationType.Sam) if position is None: return + self.generate_aa_at(position) + def generate_aa_at(self, position: Point) -> None: group_id = self.game.next_group_id() g = SamGroundObject(namegen.random_objective_name(), group_id, diff --git a/resources/campaigns/inherent_resolve.miz b/resources/campaigns/inherent_resolve.miz index c5d00d7f..12dbaed8 100644 Binary files a/resources/campaigns/inherent_resolve.miz and b/resources/campaigns/inherent_resolve.miz differ