def compute_possible_locations( terrain_name: str, cp_name: str) -> PresetControlPointLocations: """ Extract the list of preset locations from miz data :param terrain_name: Terrain/Map name :param cp_name: Control Point / Airbase name :return: """ miz_file = Path("./resources/mizdata/", terrain_name.lower(), cp_name + ".miz") offshore_locations: List[PresetLocation] = [] ashore_locations: List[PresetLocation] = [] powerplants_locations: List[PresetLocation] = [] antiship_locations: List[PresetLocation] = [] if miz_file.exists(): m = Mission() m.load_file(miz_file.absolute()) for vehicle_group in m.country("USA").vehicle_group: if len(vehicle_group.units) > 0: ashore_locations.append( PresetLocation(vehicle_group.position, vehicle_group.units[0].heading, vehicle_group.name)) for ship_group in m.country("USA").ship_group: if len(ship_group.units) > 0 and ship_group.units[ 0].type == ships.Oliver_Hazzard_Perry_class.id: offshore_locations.append( PresetLocation(ship_group.position, ship_group.units[0].heading, ship_group.name)) for static_group in m.country("USA").static_group: if len(static_group.units) > 0: powerplants_locations.append( PresetLocation(static_group.position, static_group.units[0].heading, static_group.name)) if m.country("Iran") is not None: for vehicle_group in m.country("Iran").vehicle_group: if len(vehicle_group.units) > 0 and vehicle_group.units[ 0].type == MissilesSS.SS_N_2_Silkworm.id: antiship_locations.append( PresetLocation(vehicle_group.position, vehicle_group.units[0].heading, vehicle_group.name)) return PresetControlPointLocations(ashore_locations, offshore_locations, antiship_locations, powerplants_locations)
def generateGroup(mission: Mission, flight: FlightData, package: PackageHandler): template = getTemplateGroup(mission, flight) if len(flight.members) == 0: return if len(flight.members) > 4: raise Exception("Too many units in flight, limit is 4") group = createGroup(mission, flight) country = mission.country("USA") group.task = flight.role parkingHandler = ParkingHandler(mission, template, country) for i, member in enumerate(flight.members): aircraft_type = readAircraftType(member) p = mission.aircraft(member.unitName(), aircraft_type, country) parkingHandler.assign_parking(p) p.set_client() package.setupRadio(p, flight.frequency) group.add_unit(p) mission.flight_group_from_airport assignFromTemplate(group, template) country.add_aircraft_group(group) return
def create_frontline_dict(mission: Mission) -> Dict[str, Dict]: frontline_dict = {} for group in mission.country("USA").vehicle_group: groupname = str(group.name).replace(group.name.id, "").replace(":","") control_points = groupname.split("|") frontline_dict[groupname] = { "points": [(i.position.x, i.position.y) for i in group.points], "start_cp": int(control_points[0]) } return frontline_dict
class MissionGenerator: def __init__(self, game: Game, time: datetime) -> None: self.game = game self.time = time self.mission = Mission(game.theater.terrain) self.unit_map = UnitMap() self.air_support = AirSupport() self.laser_code_registry = LaserCodeRegistry() self.radio_registry = RadioRegistry() self.tacan_registry = TacanRegistry() self.generation_started = False with open("resources/default_options.lua", "r", encoding="utf-8") as f: self.mission.options.load_from_dict( dcs.lua.loads(f.read())["options"]) def generate_miz(self, output: Path) -> UnitMap: if self.generation_started: raise RuntimeError( "Mission has already begun generating. To reset, create a new " "MissionSimulation.") self.generation_started = True self.setup_mission_coalitions() self.add_airfields_to_unit_map() self.initialize_registries() EnvironmentGenerator(self.mission, self.game.conditions, self.time).generate() tgo_generator = TgoGenerator( self.mission, self.game, self.radio_registry, self.tacan_registry, self.unit_map, ) tgo_generator.generate() ConvoyGenerator(self.mission, self.game, self.unit_map).generate() CargoShipGenerator(self.mission, self.game, self.unit_map).generate() self.generate_destroyed_units() # Generate ground conflicts first so the JTACs get the first laser code (1688) # rather than the first player flight with a TGP. self.generate_ground_conflicts() air_support, flights = self.generate_air_units(tgo_generator) TriggerGenerator(self.mission, self.game).generate() ForcedOptionsGenerator(self.mission, self.game).generate() VisualsGenerator(self.mission, self.game).generate() LuaGenerator(self.game, self.mission, air_support, flights).generate() DrawingsGenerator(self.mission, self.game).generate() self.setup_combined_arms() self.notify_info_generators(tgo_generator, air_support, flights) # TODO: Shouldn't this be first? namegen.reset_numbers() self.mission.save(output) return self.unit_map def setup_mission_coalitions(self) -> None: self.mission.coalition["blue"] = Coalition( "blue", bullseye=self.game.blue.bullseye.to_pydcs()) self.mission.coalition["red"] = Coalition( "red", bullseye=self.game.red.bullseye.to_pydcs()) self.mission.coalition["neutrals"] = Coalition( "neutrals", bullseye=Bullseye(Point(0, 0, self.mission.terrain)).to_pydcs()) p_country = self.game.blue.country_name e_country = self.game.red.country_name self.mission.coalition["blue"].add_country( country_dict[country_id_from_name(p_country)]()) self.mission.coalition["red"].add_country( country_dict[country_id_from_name(e_country)]()) belligerents = [ country_id_from_name(p_country), country_id_from_name(e_country), ] for country in country_dict.keys(): if country not in belligerents: self.mission.coalition["neutrals"].add_country( country_dict[country]()) def add_airfields_to_unit_map(self) -> None: for control_point in self.game.theater.controlpoints: if isinstance(control_point, Airfield): self.unit_map.add_airfield(control_point) def initialize_registries(self) -> None: unique_map_frequencies: set[RadioFrequency] = set() self.initialize_tacan_registry(unique_map_frequencies) self.initialize_radio_registry(unique_map_frequencies) for frequency in unique_map_frequencies: self.radio_registry.reserve(frequency) def initialize_tacan_registry( self, unique_map_frequencies: set[RadioFrequency]) -> None: """ Dedup beacon/radio frequencies, since some maps have some frequencies used multiple times. """ beacons = load_beacons_for_terrain(self.game.theater.terrain.name) for beacon in beacons: unique_map_frequencies.add(beacon.frequency) if beacon.is_tacan: if beacon.channel is None: logging.warning( f"TACAN beacon has no channel: {beacon.callsign}") else: self.tacan_registry.mark_unavailable(beacon.tacan_channel) def initialize_radio_registry( self, unique_map_frequencies: set[RadioFrequency]) -> None: for data in AirfieldData.for_theater(self.game.theater): if data.atc is not None: unique_map_frequencies.add(data.atc.hf) unique_map_frequencies.add(data.atc.vhf_fm) unique_map_frequencies.add(data.atc.vhf_am) unique_map_frequencies.add(data.atc.uhf) # No need to reserve ILS or TACAN because those are in the # beacon list. def generate_ground_conflicts(self) -> None: """Generate FLOTs and JTACs for each active front line.""" for front_line in self.game.theater.conflicts(): player_cp = front_line.blue_cp enemy_cp = front_line.red_cp conflict = FrontLineConflictDescription.frontline_cas_conflict( self.game.blue.faction.name, self.game.red.faction.name, self.mission.country(self.game.blue.country_name), self.mission.country(self.game.red.country_name), front_line, self.game.theater, ) # Generate frontline ops player_gp = self.game.ground_planners[player_cp.id].units_per_cp[ enemy_cp.id] enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[ player_cp.id] ground_conflict_gen = FlotGenerator( self.mission, conflict, self.game, player_gp, enemy_gp, player_cp.stances[enemy_cp.id], enemy_cp.stances[player_cp.id], self.unit_map, self.radio_registry, self.air_support, self.laser_code_registry, ) ground_conflict_gen.generate() def generate_air_units( self, tgo_generator: TgoGenerator ) -> tuple[AirSupport, list[FlightData]]: """Generate the air units for the Operation""" # Air Support (Tanker & Awacs) air_support_generator = AirSupportGenerator( self.mission, self.describe_air_conflict(), self.game, self.radio_registry, self.tacan_registry, self.air_support, ) air_support_generator.generate() # Generate Aircraft Activity on the map aircraft_generator = AircraftGenerator( self.mission, self.game.settings, self.game, self.time, self.radio_registry, self.tacan_registry, self.laser_code_registry, self.unit_map, air_support=air_support_generator.air_support, helipads=tgo_generator.helipads, ) aircraft_generator.clear_parking_slots() aircraft_generator.generate_flights( self.mission.country(self.game.blue.country_name), self.game.blue.ato, tgo_generator.runways, ) aircraft_generator.generate_flights( self.mission.country(self.game.red.country_name), self.game.red.ato, tgo_generator.runways, ) aircraft_generator.spawn_unused_aircraft( self.mission.country(self.game.blue.country_name), self.mission.country(self.game.red.country_name), ) for flight in aircraft_generator.flights: if not flight.client_units: continue flight.aircraft_type.assign_channels_for_flight( flight, air_support_generator.air_support) return air_support_generator.air_support, aircraft_generator.flights def generate_destroyed_units(self) -> None: """Add destroyed units to the Mission""" if not self.game.settings.perf_destroyed_units: return for d in self.game.get_destroyed_units(): try: type_name = d["type"] if not isinstance(type_name, str): raise TypeError( "Expected the type of the destroyed static to be a string" ) utype = unit_type_from_name(type_name) except KeyError: logging.warning(f"Destroyed unit has no type: {d}") continue pos = Point(cast(float, d["x"]), cast(float, d["z"]), self.mission.terrain) if utype is not None and not self.game.position_culled(pos): self.mission.static_group( country=self.mission.country(self.game.blue.country_name), name="", _type=utype, hidden=True, position=pos, heading=d["orientation"], dead=True, ) def describe_air_conflict(self) -> FrontLineConflictDescription: player_cp, enemy_cp = self.game.theater.closest_opposing_control_points( ) mid_point = player_cp.position.point_from_heading( player_cp.position.heading_between_point(enemy_cp.position), player_cp.position.distance_to_point(enemy_cp.position) / 2, ) return FrontLineConflictDescription( self.game.theater, FrontLine(player_cp, enemy_cp), self.game.blue.faction.name, self.game.red.faction.name, self.mission.country(self.game.blue.country_name), self.mission.country(self.game.red.country_name), mid_point, ) def notify_info_generators( self, tgo_generator: TgoGenerator, air_support: AirSupport, flights: list[FlightData], ) -> None: """Generates subscribed MissionInfoGenerator objects.""" gens: list[MissionInfoGenerator] = [ KneeboardGenerator(self.mission, self.game), BriefingGenerator(self.mission, self.game), ] for gen in gens: for dynamic_runway in tgo_generator.runways.values(): gen.add_dynamic_runway(dynamic_runway) for tanker in air_support.tankers: if tanker.blue: gen.add_tanker(tanker) for aewc in air_support.awacs: if aewc.blue: gen.add_awacs(aewc) for jtac in air_support.jtacs: if jtac.blue: gen.add_jtac(jtac) for flight in flights: gen.add_flight(flight) gen.generate() def setup_combined_arms(self) -> None: self.mission.groundControl.pilot_can_control_vehicles = COMBINED_ARMS_SLOTS > 0 self.mission.groundControl.blue_tactical_commander = COMBINED_ARMS_SLOTS self.mission.groundControl.blue_observer = 1
class MizCampaignLoader: BLUE_COUNTRY = CombinedJointTaskForcesBlue() RED_COUNTRY = CombinedJointTaskForcesRed() OFF_MAP_UNIT_TYPE = F_15C.id CV_UNIT_TYPE = CVN_74_John_C__Stennis.id LHA_UNIT_TYPE = LHA_1_Tarawa.id FRONT_LINE_UNIT_TYPE = Armor.APC_M113.id FOB_UNIT_TYPE = Unarmed.CP_SKP_11_ATC_Mobile_Command_Post.id EWR_UNIT_TYPE = AirDefence.EWR_55G6.id SAM_UNIT_TYPE = AirDefence.SAM_SA_10_S_300PS_SR_64H6E.id GARRISON_UNIT_TYPE = AirDefence.SAM_SA_19_Tunguska_2S6.id OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id MISSILE_SITE_UNIT_TYPE = MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M.id COASTAL_DEFENSE_UNIT_TYPE = MissilesSS.SS_N_2_Silkworm.id # Multiple options for the required SAMs so campaign designers can more # accurately see the coverage of their IADS for the expected type. REQUIRED_LONG_RANGE_SAM_UNIT_TYPES = { AirDefence.SAM_Patriot_LN_M901.id, AirDefence.SAM_SA_10_S_300PS_LN_5P85C.id, AirDefence.SAM_SA_10_S_300PS_LN_5P85D.id, } REQUIRED_MEDIUM_RANGE_SAM_UNIT_TYPES = { AirDefence.SAM_Hawk_LN_M192.id, AirDefence.SAM_SA_2_LN_SM_90.id, AirDefence.SAM_SA_3_S_125_LN_5P73.id, } BASE_DEFENSE_RADIUS = nm_to_meter(2) def __init__(self, miz: Path, theater: ConflictTheater) -> None: self.theater = theater self.mission = Mission() self.mission.load_file(str(miz)) self.control_point_id = itertools.count(1000) # If there are no red carriers there usually aren't red units. Make sure # both countries are initialized so we don't have to deal with None. if self.mission.country(self.BLUE_COUNTRY.name) is None: self.mission.coalition["blue"].add_country(self.BLUE_COUNTRY) if self.mission.country(self.RED_COUNTRY.name) is None: self.mission.coalition["red"].add_country(self.RED_COUNTRY) @staticmethod def control_point_from_airport(airport: Airport) -> ControlPoint: # The wiki says this is a legacy property and to just use regular. size = SIZE_REGULAR # The importance is taken from the periodicity of the airport's # warehouse divided by 10. 30 is the default, and out of range (valid # values are between 1.0 and 1.4). If it is used, pick the default # importance. if airport.periodicity == 30: importance = IMPORTANCE_MEDIUM else: importance = airport.periodicity / 10 cp = Airfield(airport, size, importance) cp.captured = airport.is_blue() # Use the unlimited aircraft option to determine if an airfield should # be owned by the player when the campaign is "inverted". cp.captured_invert = airport.unlimited_aircrafts return cp def country(self, blue: bool) -> Country: country = self.mission.country( self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name) # Should be guaranteed because we initialized them. assert country return country @property def blue(self) -> Country: return self.country(blue=True) @property def red(self) -> Country: return self.country(blue=False) def off_map_spawns(self, blue: bool) -> Iterator[FlyingGroup]: for group in self.country(blue).plane_group: if group.units[0].type == self.OFF_MAP_UNIT_TYPE: yield group def carriers(self, blue: bool) -> Iterator[ShipGroup]: for group in self.country(blue).ship_group: if group.units[0].type == self.CV_UNIT_TYPE: yield group def lhas(self, blue: bool) -> Iterator[ShipGroup]: for group in self.country(blue).ship_group: if group.units[0].type == self.LHA_UNIT_TYPE: yield group def fobs(self, blue: bool) -> Iterator[VehicleGroup]: for group in self.country(blue).vehicle_group: if group.units[0].type == self.FOB_UNIT_TYPE: yield group @property def ships(self) -> Iterator[ShipGroup]: for group in self.blue.ship_group: if group.units[0].type == self.SHIP_UNIT_TYPE: yield group @property def ewrs(self) -> Iterator[VehicleGroup]: for group in self.blue.vehicle_group: if group.units[0].type == self.EWR_UNIT_TYPE: yield group @property def sams(self) -> Iterator[VehicleGroup]: for group in self.blue.vehicle_group: if group.units[0].type == self.SAM_UNIT_TYPE: yield group @property def garrisons(self) -> Iterator[VehicleGroup]: for group in self.blue.vehicle_group: if group.units[0].type == self.GARRISON_UNIT_TYPE: yield group @property def offshore_strike_targets(self) -> Iterator[StaticGroup]: for group in self.blue.static_group: if group.units[0].type == self.OFFSHORE_STRIKE_TARGET_UNIT_TYPE: yield group @property def missile_sites(self) -> Iterator[VehicleGroup]: for group in self.blue.vehicle_group: if group.units[0].type == self.MISSILE_SITE_UNIT_TYPE: yield group @property def coastal_defenses(self) -> Iterator[VehicleGroup]: for group in self.blue.vehicle_group: if group.units[0].type == self.COASTAL_DEFENSE_UNIT_TYPE: yield group @property def required_long_range_sams(self) -> Iterator[VehicleGroup]: for group in self.red.vehicle_group: if group.units[0].type in self.REQUIRED_LONG_RANGE_SAM_UNIT_TYPES: yield group @property def required_medium_range_sams(self) -> Iterator[VehicleGroup]: for group in self.red.vehicle_group: if group.units[ 0].type in self.REQUIRED_MEDIUM_RANGE_SAM_UNIT_TYPES: yield group @cached_property def control_points(self) -> Dict[int, ControlPoint]: control_points = {} for airport in self.mission.terrain.airport_list(): if airport.is_blue() or airport.is_red(): control_point = self.control_point_from_airport(airport) control_points[control_point.id] = control_point for blue in (False, True): for group in self.off_map_spawns(blue): control_point = OffMapSpawn(next(self.control_point_id), str(group.name), group.position) control_point.captured = blue control_point.captured_invert = group.late_activation control_points[control_point.id] = control_point for group in self.carriers(blue): # TODO: Name the carrier. control_point = Carrier("carrier", group.position, next(self.control_point_id)) control_point.captured = blue control_point.captured_invert = group.late_activation control_points[control_point.id] = control_point for group in self.lhas(blue): # TODO: Name the LHA. control_point = Lha("lha", group.position, next(self.control_point_id)) control_point.captured = blue control_point.captured_invert = group.late_activation control_points[control_point.id] = control_point for group in self.fobs(blue): control_point = Fob(str(group.name), group.position, next(self.control_point_id)) control_point.captured = blue control_point.captured_invert = group.late_activation control_points[control_point.id] = control_point return control_points @property def front_line_path_groups(self) -> Iterator[VehicleGroup]: for group in self.country(blue=True).vehicle_group: if group.units[0].type == self.FRONT_LINE_UNIT_TYPE: yield group @cached_property def front_lines(self) -> Dict[str, ComplexFrontLine]: # Dict of front line ID to a front line. front_lines = {} for group in self.front_line_path_groups: # The unit will have its first waypoint at the source CP and the # final waypoint at the destination CP. Intermediate waypoints # define the curve of the front line. waypoints = [p.position for p in group.points] origin = self.theater.closest_control_point(waypoints[0]) if origin is None: raise RuntimeError( f"No control point near the first waypoint of {group.name}" ) destination = self.theater.closest_control_point(waypoints[-1]) if destination is None: raise RuntimeError( f"No control point near the final waypoint of {group.name}" ) # Snap the begin and end points to the control points. waypoints[0] = origin.position waypoints[-1] = destination.position front_line_id = f"{origin.id}|{destination.id}" front_lines[front_line_id] = ComplexFrontLine(origin, waypoints) self.control_points[origin.id].connect( self.control_points[destination.id]) self.control_points[destination.id].connect( self.control_points[origin.id]) return front_lines def objective_info(self, group: Group) -> Tuple[ControlPoint, int]: closest = self.theater.closest_control_point(group.position) distance = closest.position.distance_to_point(group.position) return closest, distance def add_preset_locations(self) -> None: for group in self.garrisons: closest, distance = self.objective_info(group) if distance < self.BASE_DEFENSE_RADIUS: closest.preset_locations.base_garrisons.append(group.position) else: logging.warning( f"Found garrison unit too far from base: {group.name}") for group in self.sams: closest, distance = self.objective_info(group) if distance < self.BASE_DEFENSE_RADIUS: closest.preset_locations.base_air_defense.append( group.position) else: closest.preset_locations.strike_locations.append( group.position) for group in self.ewrs: closest, distance = self.objective_info(group) closest.preset_locations.ewrs.append(group.position) for group in self.offshore_strike_targets: closest, distance = self.objective_info(group) closest.preset_locations.offshore_strike_locations.append( group.position) for group in self.ships: closest, distance = self.objective_info(group) closest.preset_locations.ships.append(group.position) for group in self.missile_sites: closest, distance = self.objective_info(group) closest.preset_locations.missile_sites.append(group.position) for group in self.coastal_defenses: closest, distance = self.objective_info(group) closest.preset_locations.coastal_defenses.append(group.position) for group in self.required_long_range_sams: closest, distance = self.objective_info(group) closest.preset_locations.required_long_range_sams.append( group.position) for group in self.required_medium_range_sams: closest, distance = self.objective_info(group) closest.preset_locations.required_medium_range_sams.append( group.position) def populate_theater(self) -> None: for control_point in self.control_points.values(): self.theater.add_controlpoint(control_point) self.add_preset_locations() self.theater.set_frontline_data(self.front_lines)
class MizCampaignLoader: BLUE_COUNTRY = CombinedJointTaskForcesBlue() RED_COUNTRY = CombinedJointTaskForcesRed() OFF_MAP_UNIT_TYPE = F_15C.id CV_UNIT_TYPE = Stennis.id LHA_UNIT_TYPE = LHA_Tarawa.id FRONT_LINE_UNIT_TYPE = Armor.M_113.id SHIPPING_LANE_UNIT_TYPE = HandyWind.id FOB_UNIT_TYPE = Unarmed.SKP_11.id FARP_HELIPAD = "SINGLE_HELIPAD" OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id MISSILE_SITE_UNIT_TYPE = MissilesSS.Scud_B.id COASTAL_DEFENSE_UNIT_TYPE = MissilesSS.Hy_launcher.id # Multiple options for air defenses so campaign designers can more accurately see # the coverage of their IADS for the expected type. LONG_RANGE_SAM_UNIT_TYPES = { AirDefence.Patriot_ln.id, AirDefence.S_300PS_5P85C_ln.id, AirDefence.S_300PS_5P85D_ln.id, } MEDIUM_RANGE_SAM_UNIT_TYPES = { AirDefence.Hawk_ln.id, AirDefence.S_75M_Volhov.id, AirDefence._5p73_s_125_ln.id, } SHORT_RANGE_SAM_UNIT_TYPES = { AirDefence.M1097_Avenger.id, AirDefence.Rapier_fsa_launcher.id, AirDefence._2S6_Tunguska.id, AirDefence.Strela_1_9P31.id, } AAA_UNIT_TYPES = { AirDefence.Flak18.id, AirDefence.Vulcan.id, AirDefence.ZSU_23_4_Shilka.id, } EWR_UNIT_TYPE = AirDefence._1L13_EWR.id ARMOR_GROUP_UNIT_TYPE = Armor.M_1_Abrams.id FACTORY_UNIT_TYPE = Fortification.Workshop_A.id AMMUNITION_DEPOT_UNIT_TYPE = Warehouse._Ammunition_depot.id STRIKE_TARGET_UNIT_TYPE = Fortification.Tech_combine.id def __init__(self, miz: Path, theater: ConflictTheater) -> None: self.theater = theater self.mission = Mission() with logged_duration("Loading miz"): self.mission.load_file(str(miz)) self.control_point_id = itertools.count(1000) # If there are no red carriers there usually aren't red units. Make sure # both countries are initialized so we don't have to deal with None. if self.mission.country(self.BLUE_COUNTRY.name) is None: self.mission.coalition["blue"].add_country(self.BLUE_COUNTRY) if self.mission.country(self.RED_COUNTRY.name) is None: self.mission.coalition["red"].add_country(self.RED_COUNTRY) @staticmethod def control_point_from_airport(airport: Airport) -> ControlPoint: # The wiki says this is a legacy property and to just use regular. size = SIZE_REGULAR # The importance is taken from the periodicity of the airport's # warehouse divided by 10. 30 is the default, and out of range (valid # values are between 1.0 and 1.4). If it is used, pick the default # importance. if airport.periodicity == 30: importance = IMPORTANCE_MEDIUM else: importance = airport.periodicity / 10 cp = Airfield(airport, size, importance) cp.captured = airport.is_blue() # Use the unlimited aircraft option to determine if an airfield should # be owned by the player when the campaign is "inverted". cp.captured_invert = airport.unlimited_aircrafts return cp def country(self, blue: bool) -> Country: country = self.mission.country( self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name) # Should be guaranteed because we initialized them. assert country return country @property def blue(self) -> Country: return self.country(blue=True) @property def red(self) -> Country: return self.country(blue=False) def off_map_spawns(self, blue: bool) -> Iterator[PlaneGroup]: for group in self.country(blue).plane_group: if group.units[0].type == self.OFF_MAP_UNIT_TYPE: yield group def carriers(self, blue: bool) -> Iterator[ShipGroup]: for group in self.country(blue).ship_group: if group.units[0].type == self.CV_UNIT_TYPE: yield group def lhas(self, blue: bool) -> Iterator[ShipGroup]: for group in self.country(blue).ship_group: if group.units[0].type == self.LHA_UNIT_TYPE: yield group def fobs(self, blue: bool) -> Iterator[VehicleGroup]: for group in self.country(blue).vehicle_group: if group.units[0].type == self.FOB_UNIT_TYPE: yield group @property def ships(self) -> Iterator[ShipGroup]: for group in self.red.ship_group: if group.units[0].type == self.SHIP_UNIT_TYPE: yield group @property def offshore_strike_targets(self) -> Iterator[StaticGroup]: for group in self.red.static_group: if group.units[0].type == self.OFFSHORE_STRIKE_TARGET_UNIT_TYPE: yield group @property def missile_sites(self) -> Iterator[VehicleGroup]: for group in self.red.vehicle_group: if group.units[0].type == self.MISSILE_SITE_UNIT_TYPE: yield group @property def coastal_defenses(self) -> Iterator[VehicleGroup]: for group in self.red.vehicle_group: if group.units[0].type == self.COASTAL_DEFENSE_UNIT_TYPE: yield group @property def long_range_sams(self) -> Iterator[VehicleGroup]: for group in self.red.vehicle_group: if group.units[0].type in self.LONG_RANGE_SAM_UNIT_TYPES: yield group @property def medium_range_sams(self) -> Iterator[VehicleGroup]: for group in self.red.vehicle_group: if group.units[0].type in self.MEDIUM_RANGE_SAM_UNIT_TYPES: yield group @property def short_range_sams(self) -> Iterator[VehicleGroup]: for group in self.red.vehicle_group: if group.units[0].type in self.SHORT_RANGE_SAM_UNIT_TYPES: yield group @property def aaa(self) -> Iterator[VehicleGroup]: for group in itertools.chain(self.blue.vehicle_group, self.red.vehicle_group): if group.units[0].type in self.AAA_UNIT_TYPES: yield group @property def ewrs(self) -> Iterator[VehicleGroup]: for group in self.red.vehicle_group: if group.units[0].type in self.EWR_UNIT_TYPE: yield group @property def armor_groups(self) -> Iterator[VehicleGroup]: for group in itertools.chain(self.blue.vehicle_group, self.red.vehicle_group): if group.units[0].type in self.ARMOR_GROUP_UNIT_TYPE: yield group @property def helipads(self) -> Iterator[StaticGroup]: for group in self.blue.static_group: if group.units[0].type == self.FARP_HELIPAD: yield group @property def factories(self) -> Iterator[StaticGroup]: for group in self.blue.static_group: if group.units[0].type in self.FACTORY_UNIT_TYPE: yield group @property def ammunition_depots(self) -> Iterator[StaticGroup]: for group in itertools.chain(self.blue.static_group, self.red.static_group): if group.units[0].type in self.AMMUNITION_DEPOT_UNIT_TYPE: yield group @property def strike_targets(self) -> Iterator[StaticGroup]: for group in itertools.chain(self.blue.static_group, self.red.static_group): if group.units[0].type in self.STRIKE_TARGET_UNIT_TYPE: yield group @property def scenery(self) -> List[SceneryGroup]: return SceneryGroup.from_trigger_zones(self.mission.triggers._zones) @cached_property def control_points(self) -> Dict[int, ControlPoint]: control_points = {} for airport in self.mission.terrain.airport_list(): if airport.is_blue() or airport.is_red(): control_point = self.control_point_from_airport(airport) control_points[control_point.id] = control_point for blue in (False, True): for group in self.off_map_spawns(blue): control_point = OffMapSpawn(next(self.control_point_id), str(group.name), group.position) control_point.captured = blue control_point.captured_invert = group.late_activation control_points[control_point.id] = control_point for ship in self.carriers(blue): # TODO: Name the carrier. control_point = Carrier("carrier", ship.position, next(self.control_point_id)) control_point.captured = blue control_point.captured_invert = ship.late_activation control_points[control_point.id] = control_point for ship in self.lhas(blue): # TODO: Name the LHA.db control_point = Lha("lha", ship.position, next(self.control_point_id)) control_point.captured = blue control_point.captured_invert = ship.late_activation control_points[control_point.id] = control_point for fob in self.fobs(blue): control_point = Fob(str(fob.name), fob.position, next(self.control_point_id)) control_point.captured = blue control_point.captured_invert = fob.late_activation control_points[control_point.id] = control_point return control_points @property def front_line_path_groups(self) -> Iterator[VehicleGroup]: for group in self.country(blue=True).vehicle_group: if group.units[0].type == self.FRONT_LINE_UNIT_TYPE: yield group @property def shipping_lane_groups(self) -> Iterator[ShipGroup]: for group in self.country(blue=True).ship_group: if group.units[0].type == self.SHIPPING_LANE_UNIT_TYPE: yield group def add_supply_routes(self) -> None: for group in self.front_line_path_groups: # The unit will have its first waypoint at the source CP and the final # waypoint at the destination CP. Each waypoint defines the path of the # cargo ship. waypoints = [p.position for p in group.points] origin = self.theater.closest_control_point(waypoints[0]) if origin is None: raise RuntimeError( f"No control point near the first waypoint of {group.name}" ) destination = self.theater.closest_control_point(waypoints[-1]) if destination is None: raise RuntimeError( f"No control point near the final waypoint of {group.name}" ) self.control_points[origin.id].create_convoy_route( destination, waypoints) self.control_points[destination.id].create_convoy_route( origin, list(reversed(waypoints))) def add_shipping_lanes(self) -> None: for group in self.shipping_lane_groups: # The unit will have its first waypoint at the source CP and the final # waypoint at the destination CP. Each waypoint defines the path of the # cargo ship. waypoints = [p.position for p in group.points] origin = self.theater.closest_control_point(waypoints[0]) if origin is None: raise RuntimeError( f"No control point near the first waypoint of {group.name}" ) destination = self.theater.closest_control_point(waypoints[-1]) if destination is None: raise RuntimeError( f"No control point near the final waypoint of {group.name}" ) self.control_points[origin.id].create_shipping_lane( destination, waypoints) self.control_points[destination.id].create_shipping_lane( origin, list(reversed(waypoints))) def objective_info(self, near: Positioned) -> Tuple[ControlPoint, Distance]: closest = self.theater.closest_control_point(near.position) distance = meters(closest.position.distance_to_point(near.position)) return closest, distance def add_preset_locations(self) -> None: for static in self.offshore_strike_targets: closest, distance = self.objective_info(static) closest.preset_locations.offshore_strike_locations.append( PointWithHeading.from_point(static.position, static.units[0].heading)) for ship in self.ships: closest, distance = self.objective_info(ship) closest.preset_locations.ships.append( PointWithHeading.from_point(ship.position, ship.units[0].heading)) for group in self.missile_sites: closest, distance = self.objective_info(group) closest.preset_locations.missile_sites.append( PointWithHeading.from_point(group.position, group.units[0].heading)) for group in self.coastal_defenses: closest, distance = self.objective_info(group) closest.preset_locations.coastal_defenses.append( PointWithHeading.from_point(group.position, group.units[0].heading)) for group in self.long_range_sams: closest, distance = self.objective_info(group) closest.preset_locations.long_range_sams.append( PointWithHeading.from_point(group.position, group.units[0].heading)) for group in self.medium_range_sams: closest, distance = self.objective_info(group) closest.preset_locations.medium_range_sams.append( PointWithHeading.from_point(group.position, group.units[0].heading)) for group in self.short_range_sams: closest, distance = self.objective_info(group) closest.preset_locations.short_range_sams.append( PointWithHeading.from_point(group.position, group.units[0].heading)) for group in self.aaa: closest, distance = self.objective_info(group) closest.preset_locations.aaa.append( PointWithHeading.from_point(group.position, group.units[0].heading)) 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)) for group in self.armor_groups: closest, distance = self.objective_info(group) closest.preset_locations.armor_groups.append( PointWithHeading.from_point(group.position, group.units[0].heading)) for static in self.helipads: closest, distance = self.objective_info(static) closest.helipads.append( PointWithHeading.from_point(static.position, static.units[0].heading)) for static in self.factories: closest, distance = self.objective_info(static) closest.preset_locations.factories.append( PointWithHeading.from_point(static.position, static.units[0].heading)) for static in self.ammunition_depots: closest, distance = self.objective_info(static) closest.preset_locations.ammunition_depots.append( PointWithHeading.from_point(static.position, static.units[0].heading)) for static in self.strike_targets: closest, distance = self.objective_info(static) closest.preset_locations.strike_locations.append( PointWithHeading.from_point(static.position, static.units[0].heading)) for scenery_group in self.scenery: closest, distance = self.objective_info(scenery_group) closest.preset_locations.scenery.append(scenery_group) def populate_theater(self) -> None: for control_point in self.control_points.values(): self.theater.add_controlpoint(control_point) self.add_preset_locations() self.add_supply_routes() self.add_shipping_lanes()
class Operation: attackers_starting_position = None # type: db.StartingPosition defenders_starting_position = None # type: db.StartingPosition current_mission = None # type: Mission regular_mission = None # type: Mission quick_mission = None # type: Mission conflict = None # type: Conflict airgen = None # type: AircraftConflictGenerator triggersgen = None # type: TriggersGenerator airsupportgen = None # type: AirSupportConflictGenerator visualgen = None # type: VisualGenerator groundobjectgen = None # type: GroundObjectsGenerator briefinggen = None # type: BriefingGenerator forcedoptionsgen = None # type: ForcedOptionsGenerator radio_registry: Optional[RadioRegistry] = None tacan_registry: Optional[TacanRegistry] = None environment_settings = None trigger_radius = TRIGGER_RADIUS_MEDIUM is_quick = None is_awacs_enabled = False ca_slots = 0 def __init__(self, game, attacker_name: str, defender_name: str, from_cp: ControlPoint, departure_cp: ControlPoint, to_cp: ControlPoint): self.game = game self.attacker_name = attacker_name self.attacker_country = db.FACTIONS[attacker_name].country self.defender_name = defender_name self.defender_country = db.FACTIONS[defender_name].country print(self.defender_country, self.attacker_country) self.from_cp = from_cp self.departure_cp = departure_cp self.to_cp = to_cp self.is_quick = False self.plugin_scripts: List[str] = [] def units_of(self, country_name: str) -> List[UnitType]: return [] def is_successfull(self, debriefing: Debriefing) -> bool: return True @property def is_player_attack(self) -> bool: return self.from_cp.captured def initialize(self, mission: Mission, conflict: Conflict): self.current_mission = mission self.conflict = conflict # self.briefinggen = BriefingGenerator(self.current_mission, self.game) Is it safe to remove this, or does it also break save compat? def prepare(self, terrain: Terrain, is_quick: bool): with open("resources/default_options.lua", "r") as f: options_dict = loads(f.read())["options"] self.current_mission = Mission(terrain) print(self.game.player_country) print(country_dict[db.country_id_from_name(self.game.player_country)]) print(country_dict[db.country_id_from_name( self.game.player_country)]()) # Setup coalition : self.current_mission.coalition["blue"] = Coalition("blue") self.current_mission.coalition["red"] = Coalition("red") p_country = self.game.player_country e_country = self.game.enemy_country self.current_mission.coalition["blue"].add_country( country_dict[db.country_id_from_name(p_country)]()) self.current_mission.coalition["red"].add_country( country_dict[db.country_id_from_name(e_country)]()) print([ c for c in self.current_mission.coalition["blue"].countries.keys() ]) print([ c for c in self.current_mission.coalition["red"].countries.keys() ]) if is_quick: self.quick_mission = self.current_mission else: self.regular_mission = self.current_mission self.current_mission.options.load_from_dict(options_dict) self.is_quick = is_quick if is_quick: self.attackers_starting_position = None self.defenders_starting_position = None else: self.attackers_starting_position = self.departure_cp.at # TODO: Is this possible? if self.to_cp is not None: self.defenders_starting_position = self.to_cp.at else: self.defenders_starting_position = None def inject_lua_trigger(self, contents: str, comment: str) -> None: trigger = TriggerStart(comment=comment) trigger.add_action(DoScript(String(contents))) self.current_mission.triggerrules.triggers.append(trigger) def bypass_plugin_script(self, mnemonic: str) -> None: self.plugin_scripts.append(mnemonic) def inject_plugin_script(self, plugin_mnemonic: str, script: str, script_mnemonic: str) -> None: if script_mnemonic in self.plugin_scripts: logging.debug( f"Skipping already loaded {script} for {plugin_mnemonic}") else: self.plugin_scripts.append(script_mnemonic) plugin_path = Path("./resources/plugins", plugin_mnemonic) script_path = Path(plugin_path, script) if not script_path.exists(): logging.error( f"Cannot find {script_path} for plugin {plugin_mnemonic}") return trigger = TriggerStart(comment=f"Load {script_mnemonic}") filename = script_path.resolve() fileref = self.current_mission.map_resource.add_resource_file( filename) trigger.add_action(DoScriptFile(fileref)) self.current_mission.triggerrules.triggers.append(trigger) def notify_info_generators( self, groundobjectgen: GroundObjectsGenerator, airsupportgen: AirSupportConflictGenerator, jtacs: List[JtacInfo], airgen: AircraftConflictGenerator, ): """Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings) """ gens: List[MissionInfoGenerator] = [ KneeboardGenerator(self.current_mission, self.game), BriefingGenerator(self.current_mission, self.game) ] for gen in gens: for dynamic_runway in groundobjectgen.runways.values(): gen.add_dynamic_runway(dynamic_runway) for tanker in airsupportgen.air_support.tankers: gen.add_tanker(tanker) if self.is_awacs_enabled: for awacs in airsupportgen.air_support.awacs: gen.add_awacs(awacs) for jtac in jtacs: gen.add_jtac(jtac) for flight in airgen.flights: gen.add_flight(flight) gen.generate() def generate(self): radio_registry = RadioRegistry() tacan_registry = TacanRegistry() # Dedup beacon/radio frequencies, since some maps have some frequencies # used multiple times. beacons = load_beacons_for_terrain(self.game.theater.terrain.name) unique_map_frequencies: Set[RadioFrequency] = set() for beacon in beacons: unique_map_frequencies.add(beacon.frequency) if beacon.is_tacan: if beacon.channel is None: logging.error( f"TACAN beacon has no channel: {beacon.callsign}") else: tacan_registry.reserve(beacon.tacan_channel) for airfield, data in AIRFIELD_DATA.items(): if data.theater == self.game.theater.terrain.name: unique_map_frequencies.add(data.atc.hf) unique_map_frequencies.add(data.atc.vhf_fm) unique_map_frequencies.add(data.atc.vhf_am) unique_map_frequencies.add(data.atc.uhf) # No need to reserve ILS or TACAN because those are in the # beacon list. for frequency in unique_map_frequencies: radio_registry.reserve(frequency) # Set mission time and weather conditions. EnvironmentGenerator(self.current_mission, self.game.conditions).generate() # Generate ground object first groundobjectgen = GroundObjectsGenerator(self.current_mission, self.conflict, self.game, radio_registry, tacan_registry) groundobjectgen.generate() # Generate destroyed units for d in self.game.get_destroyed_units(): try: utype = db.unit_type_from_name(d["type"]) except KeyError: continue pos = Point(d["x"], d["z"]) if utype is not None and not self.game.position_culled( pos) and self.game.settings.perf_destroyed_units: self.current_mission.static_group( country=self.current_mission.country( self.game.player_country), name="", _type=utype, hidden=True, position=pos, heading=d["orientation"], dead=True, ) # Air Support (Tanker & Awacs) airsupportgen = AirSupportConflictGenerator(self.current_mission, self.conflict, self.game, radio_registry, tacan_registry) airsupportgen.generate(self.is_awacs_enabled) # Generate Activity on the map airgen = AircraftConflictGenerator(self.current_mission, self.conflict, self.game.settings, self.game, radio_registry) airgen.generate_flights( self.current_mission.country(self.game.player_country), self.game.blue_ato, groundobjectgen.runways) airgen.generate_flights( self.current_mission.country(self.game.enemy_country), self.game.red_ato, groundobjectgen.runways) # Generate ground units on frontline everywhere jtacs: List[JtacInfo] = [] for front_line in self.game.theater.conflicts(True): player_cp = front_line.control_point_a enemy_cp = front_line.control_point_b conflict = Conflict.frontline_cas_conflict( self.attacker_name, self.defender_name, self.current_mission.country(self.attacker_country), self.current_mission.country(self.defender_country), player_cp, enemy_cp, self.game.theater) # Generate frontline ops player_gp = self.game.ground_planners[player_cp.id].units_per_cp[ enemy_cp.id] enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[ player_cp.id] groundConflictGen = GroundConflictGenerator( self.current_mission, conflict, self.game, player_gp, enemy_gp, player_cp.stances[enemy_cp.id]) groundConflictGen.generate() jtacs.extend(groundConflictGen.jtacs) # Setup combined arms parameters self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0 if self.game.player_country in [ country.name for country in self.current_mission.coalition["blue"].countries.values() ]: self.current_mission.groundControl.blue_tactical_commander = self.ca_slots else: self.current_mission.groundControl.red_tactical_commander = self.ca_slots # Triggers triggersgen = TriggersGenerator(self.current_mission, self.conflict, self.game) triggersgen.generate() # Options forcedoptionsgen = ForcedOptionsGenerator(self.current_mission, self.conflict, self.game) forcedoptionsgen.generate() # Generate Visuals Smoke Effects visualgen = VisualGenerator(self.current_mission, self.conflict, self.game) if self.game.settings.perf_smoke_gen: visualgen.generate() luaData = {} luaData["AircraftCarriers"] = {} luaData["Tankers"] = {} luaData["AWACs"] = {} luaData["JTACs"] = {} luaData["TargetPoints"] = {} self.assign_channels_to_flights(airgen.flights, airsupportgen.air_support) for tanker in airsupportgen.air_support.tankers: luaData["Tankers"][tanker.callsign] = { "dcsGroupName": tanker.dcsGroupName, "callsign": tanker.callsign, "variant": tanker.variant, "radio": tanker.freq.mhz, "tacan": str(tanker.tacan.number) + tanker.tacan.band.name } if self.is_awacs_enabled: for awacs in airsupportgen.air_support.awacs: luaData["AWACs"][awacs.callsign] = { "dcsGroupName": awacs.dcsGroupName, "callsign": awacs.callsign, "radio": awacs.freq.mhz } for jtac in jtacs: luaData["JTACs"][jtac.callsign] = { "dcsGroupName": jtac.dcsGroupName, "callsign": jtac.callsign, "zone": jtac.region, "dcsUnit": jtac.unit_name, "laserCode": jtac.code } for flight in airgen.flights: if flight.friendly and flight.flight_type in [ FlightType.ANTISHIP, FlightType.DEAD, FlightType.SEAD, FlightType.STRIKE ]: flightType = flight.flight_type.name flightTarget = flight.package.target if flightTarget: flightTargetName = None flightTargetType = None if hasattr(flightTarget, 'obj_name'): flightTargetName = flightTarget.obj_name flightTargetType = flightType + f" TGT ({flightTarget.category})" elif hasattr(flightTarget, 'name'): flightTargetName = flightTarget.name flightTargetType = flightType + " TGT (Airbase)" luaData["TargetPoints"][flightTargetName] = { "name": flightTargetName, "type": flightTargetType, "position": { "x": flightTarget.position.x, "y": flightTarget.position.y } } # set a LUA table with data from Liberation that we want to set # at the moment it contains Liberation's install path, and an overridable definition for the JTACAutoLase function # later, we'll add data about the units and points having been generated, in order to facilitate the configuration of the plugin lua scripts state_location = "[[" + os.path.abspath(".") + "]]" lua = """ -- setting configuration table env.info("DCSLiberation|: setting configuration table") -- all data in this table is overridable. dcsLiberation = {} -- the base location for state.json; if non-existent, it'll be replaced with LIBERATION_EXPORT_DIR, TEMP, or DCS working directory dcsLiberation.installPath=""" + state_location + """ """ # Process the tankers lua += """ -- list the tankers generated by Liberation dcsLiberation.Tankers = { """ for key in luaData["Tankers"]: data = luaData["Tankers"][key] dcsGroupName = data["dcsGroupName"] callsign = data["callsign"] variant = data["variant"] tacan = data["tacan"] radio = data["radio"] lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', variant='{variant}', tacan='{tacan}', radio='{radio}' }}, \n" #lua += f" {{name='{dcsGroupName}', description='{callsign} ({variant})', information='Tacan:{tacan} Radio:{radio}' }}, \n" lua += "}" # Process the AWACSes lua += """ -- list the AWACs generated by Liberation dcsLiberation.AWACs = { """ for key in luaData["AWACs"]: data = luaData["AWACs"][key] dcsGroupName = data["dcsGroupName"] callsign = data["callsign"] radio = data["radio"] lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', radio='{radio}' }}, \n" #lua += f" {{name='{dcsGroupName}', description='{callsign} (AWACS)', information='Radio:{radio}' }}, \n" lua += "}" # Process the JTACs lua += """ -- list the JTACs generated by Liberation dcsLiberation.JTACs = { """ for key in luaData["JTACs"]: data = luaData["JTACs"][key] dcsGroupName = data["dcsGroupName"] callsign = data["callsign"] zone = data["zone"] laserCode = data["laserCode"] dcsUnit = data["dcsUnit"] lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', zone='{zone}', laserCode='{laserCode}', dcsUnit='{dcsUnit}' }}, \n" #lua += f" {{name='{dcsGroupName}', description='JTAC {callsign} ', information='Laser:{laserCode}', jtac={laserCode} }}, \n" lua += "}" # Process the Target Points lua += """ -- list the target points generated by Liberation dcsLiberation.TargetPoints = { """ for key in luaData["TargetPoints"]: data = luaData["TargetPoints"][key] name = data["name"] pointType = data["type"] positionX = data["position"]["x"] positionY = data["position"]["y"] lua += f" {{name='{name}', pointType='{pointType}', positionX='{positionX}', positionY='{positionY}' }}, \n" #lua += f" {{name='{pointType} {name}', point{{x={positionX}, z={positionY} }} }}, \n" lua += "}" lua += """ -- list the airbases generated by Liberation -- dcsLiberation.Airbases = {} -- list the aircraft carriers generated by Liberation -- dcsLiberation.Carriers = {} -- later, we'll add more data to the table """ trigger = TriggerStart(comment="Set DCS Liberation data") trigger.add_action(DoScript(String(lua))) self.current_mission.triggerrules.triggers.append(trigger) # Inject Plugins Lua Scripts and data for plugin in LuaPluginManager.plugins(): if plugin.enabled: plugin.inject_scripts(self) plugin.inject_configuration(self) self.assign_channels_to_flights(airgen.flights, airsupportgen.air_support) self.notify_info_generators(groundobjectgen, airsupportgen, jtacs, airgen) def assign_channels_to_flights(self, flights: List[FlightData], air_support: AirSupport) -> None: """Assigns preset radio channels for client flights.""" for flight in flights: if not flight.client_units: continue self.assign_channels_to_flight(flight, air_support) def assign_channels_to_flight(self, flight: FlightData, air_support: AirSupport) -> None: """Assigns preset radio channels for a client flight.""" airframe = flight.aircraft_type try: aircraft_data = AIRCRAFT_DATA[airframe.id] except KeyError: logging.warning(f"No aircraft data for {airframe.id}") return if aircraft_data.channel_allocator is not None: aircraft_data.channel_allocator.assign_channels_for_flight( flight, air_support)