diff --git a/game/game.py b/game/game.py index 47f8cfc4..3f71939d 100644 --- a/game/game.py +++ b/game/game.py @@ -22,6 +22,7 @@ from game.models.game_stats import GameStats from game.plugins import LuaPluginManager from game.utils import Distance from . import naming, persistency +from .ato import Flight from .ato.flighttype import FlightType from .campaignloader import CampaignAirWingConfig from .coalition import Coalition @@ -155,6 +156,7 @@ class Game: 1: {}, 2: {}, } + self.pretense_air_groups: dict[str, Flight] = {} self.on_load(game_still_initializing=True) diff --git a/game/missiongenerator/tgogenerator.py b/game/missiongenerator/tgogenerator.py index a01f33e1..ed83823e 100644 --- a/game/missiongenerator/tgogenerator.py +++ b/game/missiongenerator/tgogenerator.py @@ -290,11 +290,9 @@ class GroundObjectGenerator: # All alive Ships ship_units.append(unit) if vehicle_units: - vg = self.create_vehicle_group(group.group_name, vehicle_units) - vg.hidden_on_mfd = self.ground_object.hide_on_mfd + self.create_vehicle_group(group.group_name, vehicle_units) if ship_units: - sg = self.create_ship_group(group.group_name, ship_units) - sg.hidden_on_mfd = self.ground_object.hide_on_mfd + self.create_ship_group(group.group_name, ship_units) def create_vehicle_group( self, group_name: str, units: list[TheaterUnit] @@ -827,30 +825,45 @@ class HelipadGenerator: else: self.helipads.append(sg) - # Generate a FARP Ammo and Fuel stack for each pad - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading(helipad.heading.degrees, 35), - heading=pad.heading + 180, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - helipad.heading.degrees, 35 - ).point_from_heading(helipad.heading.degrees + 90, 10), - heading=pad.heading + 90, - ) - self.m.static_group( - country=country, - name=(name + "_ws"), - _type=Fortification.Windsock, - position=helipad.point_from_heading(helipad.heading.degrees + 45, 35), - heading=pad.heading, - ) + if self.game.position_culled(helipad): + cull_farp_statics = True + if self.cp.coalition.player: + for package in self.cp.coalition.ato.packages: + for flight in package.flights: + if flight.squadron.location == self.cp: + cull_farp_statics = False + break + elif flight.divert and flight.divert == self.cp: + cull_farp_statics = False + break + else: + cull_farp_statics = False + + if not cull_farp_statics: + # Generate a FARP Ammo and Fuel stack for each pad + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading(helipad.heading.degrees, 35), + heading=pad.heading + 180, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + helipad.heading.degrees, 35 + ).point_from_heading(helipad.heading.degrees + 90, 10), + heading=pad.heading + 90, + ) + self.m.static_group( + country=country, + name=(name + "_ws"), + _type=Fortification.Windsock, + position=helipad.point_from_heading(helipad.heading.degrees + 45, 35), + heading=pad.heading, + ) def append_helipad( self, @@ -927,61 +940,76 @@ class GroundSpawnRoadbaseGenerator: country.id ) - # Generate ammo truck/farp and fuel truck/stack for each pad - if self.game.settings.ground_start_trucks_roadbase: - self.m.vehicle_group( - country=country, - name=(name + "_fuel"), - _type=tanker_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) - self.m.vehicle_group( - country=country, - name=(name + "_ammo"), - _type=ammo_truck_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) + if self.game.position_culled(ground_spawn[0]): + cull_farp_statics = True + if self.cp.coalition.player: + for package in self.cp.coalition.ato.packages: + for flight in package.flights: + if flight.squadron.location == self.cp: + cull_farp_statics = False + break + elif flight.divert and flight.divert == self.cp: + cull_farp_statics = False + break else: - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ), - heading=pad.heading + 270, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), - heading=pad.heading + 180, - ) - if self.game.settings.ground_start_ground_power_trucks_roadbase: - self.m.vehicle_group( - country=country, - name=(name + "_power"), - _type=power_truck_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 20), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) + cull_farp_statics = False + + if not cull_farp_statics: + # Generate ammo truck/farp and fuel truck/stack for each pad + if self.game.settings.ground_start_trucks_roadbase: + self.m.vehicle_group( + country=country, + name=(name + "_fuel"), + _type=tanker_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) + self.m.vehicle_group( + country=country, + name=(name + "_ammo"), + _type=ammo_truck_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) + else: + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ), + heading=pad.heading + 270, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), + heading=pad.heading + 180, + ) + if self.game.settings.ground_start_ground_power_trucks_roadbase: + self.m.vehicle_group( + country=country, + name=(name + "_power"), + _type=power_truck_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 20), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) def generate(self) -> None: try: @@ -1044,61 +1072,76 @@ class GroundSpawnGenerator: country.id ) - # Generate a FARP Ammo and Fuel stack for each pad - if self.game.settings.ground_start_trucks: - self.m.vehicle_group( - country=country, - name=(name + "_fuel"), - _type=tanker_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 175, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) - self.m.vehicle_group( - country=country, - name=(name + "_ammo"), - _type=ammo_truck_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 185, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) + if self.game.position_culled(vtol_pad[0]): + cull_farp_statics = True + if self.cp.coalition.player: + for package in self.cp.coalition.ato.packages: + for flight in package.flights: + if flight.squadron.location == self.cp: + cull_farp_statics = False + break + elif flight.divert and flight.divert == self.cp: + cull_farp_statics = False + break else: - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 180, 45 - ), - heading=pad.heading, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 180, 35 - ), - heading=pad.heading + 270, - ) - if self.game.settings.ground_start_ground_power_trucks: - self.m.vehicle_group( - country=country, - name=(name + "_power"), - _type=power_truck_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 185, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) + cull_farp_statics = False + + if not cull_farp_statics: + # Generate a FARP Ammo and Fuel stack for each pad + if self.game.settings.ground_start_trucks: + self.m.vehicle_group( + country=country, + name=(name + "_fuel"), + _type=tanker_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 175, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) + self.m.vehicle_group( + country=country, + name=(name + "_ammo"), + _type=ammo_truck_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 185, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) + else: + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 180, 45 + ), + heading=pad.heading, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 180, 35 + ), + heading=pad.heading + 270, + ) + if self.game.settings.ground_start_ground_power_trucks: + self.m.vehicle_group( + country=country, + name=(name + "_power"), + _type=power_truck_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 185, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) def generate(self) -> None: try: diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 1bfe7b8b..6b0a316c 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -49,17 +49,9 @@ if TYPE_CHECKING: PRETENSE_SQUADRON_DEF_RETRIES = 100 -PRETENSE_SEAD_FLIGHTS_PER_CP = 2 -PRETENSE_CAS_FLIGHTS_PER_CP = 2 -PRETENSE_BAI_FLIGHTS_PER_CP = 2 -PRETENSE_STRIKE_FLIGHTS_PER_CP = 2 -PRETENSE_BARCAP_FLIGHTS_PER_CP = 2 -PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 PRETENSE_AI_AWACS_PER_FLIGHT = 1 PRETENSE_AI_TANKERS_PER_FLIGHT = 1 -PRETENSE_AI_CARGO_PLANES_PER_SIDE = 2 PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 1 -PRETENSE_PLAYER_FLIGHTS_PER_TYPE = 2 class PretenseAircraftGenerator: @@ -300,8 +292,12 @@ class PretenseAircraftGenerator: if cp.coalition != squadron.coalition: continue - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) mission_types = squadron.auto_assignable_mission_types @@ -320,46 +316,46 @@ class PretenseAircraftGenerator: FlightType.SEAD in mission_types or FlightType.SEAD_SWEEP in mission_types or FlightType.SEAD_ESCORT in mission_types - ) and num_of_sead < PRETENSE_SEAD_FLIGHTS_PER_CP: + ) and num_of_sead < self.game.settings.pretense_sead_flights_per_cp: flight_type = FlightType.SEAD num_of_sead += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.DEAD in mission_types - and num_of_sead < PRETENSE_SEAD_FLIGHTS_PER_CP + and num_of_sead < self.game.settings.pretense_sead_flights_per_cp ): flight_type = FlightType.DEAD num_of_sead += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.CAS in mission_types - ) and num_of_cas < PRETENSE_CAS_FLIGHTS_PER_CP: + ) and num_of_cas < self.game.settings.pretense_cas_flights_per_cp: flight_type = FlightType.CAS num_of_cas += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.BAI in mission_types - ) and num_of_bai < PRETENSE_BAI_FLIGHTS_PER_CP: + ) and num_of_bai < self.game.settings.pretense_bai_flights_per_cp: flight_type = FlightType.BAI num_of_bai += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.STRIKE in mission_types or FlightType.OCA_RUNWAY in mission_types or FlightType.OCA_AIRCRAFT in mission_types - ) and num_of_strike < PRETENSE_STRIKE_FLIGHTS_PER_CP: + ) and num_of_strike < self.game.settings.pretense_strike_flights_per_cp: flight_type = FlightType.STRIKE num_of_strike += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.BARCAP in mission_types or FlightType.TARCAP in mission_types or FlightType.ESCORT in mission_types or FlightType.INTERCEPTION in mission_types - ) and num_of_cap < PRETENSE_BARCAP_FLIGHTS_PER_CP: + ) and num_of_cap < self.game.settings.pretense_barcap_flights_per_cp: flight_type = FlightType.BARCAP num_of_cap += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif FlightType.AEWC in mission_types: flight_type = FlightType.AEWC aircraft_per_flight = PRETENSE_AI_AWACS_PER_FLIGHT @@ -429,8 +425,12 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) if squadron is not None: - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( @@ -453,7 +453,7 @@ class PretenseAircraftGenerator: if isinstance(cp, Airfield): # Generate SEAD flight flight_type = FlightType.SEAD - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight squadron = self.generate_pretense_squadron( cp, coalition, @@ -470,8 +470,12 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) if squadron is not None: - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( @@ -494,7 +498,7 @@ class PretenseAircraftGenerator: # Generate CAS flight flight_type = FlightType.CAS - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight squadron = self.generate_pretense_squadron( cp, coalition, @@ -511,8 +515,12 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) if squadron is not None: - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( @@ -561,7 +569,7 @@ class PretenseAircraftGenerator: if not cp.can_operate(aircraft_type): continue - for i in range(PRETENSE_PLAYER_FLIGHTS_PER_TYPE): + for i in range(self.game.settings.pretense_player_flights_per_type): squadron = self.generate_pretense_squadron_for( aircraft_type, cp, @@ -607,7 +615,7 @@ class PretenseAircraftGenerator: cp: Control point to generate aircraft for. flight: The current flight being generated. """ - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) for side in range(1, 3): if cp_name_trimmed not in cp.coalition.game.pretense_air[side]: @@ -627,7 +635,7 @@ class PretenseAircraftGenerator: flight: The current flight being generated. """ flight_type = flight.flight_type - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) for side in range(1, 3): if cp_name_trimmed not in flight.coalition.game.pretense_air[side]: @@ -675,7 +683,7 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) num_of_cargo_sq_to_generate = ( - PRETENSE_AI_CARGO_PLANES_PER_SIDE + self.game.settings.pretense_ai_cargo_planes_per_side - self.number_of_pretense_cargo_plane_sq_for(cp.coalition.air_wing) ) for i in range(num_of_cargo_sq_to_generate): @@ -795,7 +803,8 @@ class PretenseAircraftGenerator: if flight.package.target != flight.departure: break for mission_target in cp.ground_objects: - flight.package.target = mission_target + if mission_target.alive_unit_count > 0: + flight.package.target = mission_target break elif ( flight.flight_type == FlightType.OCA_RUNWAY @@ -837,23 +846,30 @@ class PretenseAircraftGenerator: break now = self.game.conditions.start_time - flight.package.set_tot_asap(now) + try: + flight.package.set_tot_asap(now) + except: + raise RuntimeError( + f"Pretense flight group {group.name} {flight.squadron.aircraft} {flight.flight_type} for target {flight.package.target} configuration failed. Please check if your Retribution campaign is compatible with Pretense." + ) logging.info( f"Configuring flight {group.name} {flight.squadron.aircraft} {flight.flight_type}, number of players: {flight.client_count}" ) - PretenseFlightGroupConfigurator( - flight, - group, - self.game, - self.mission, - self.time, - self.radio_registry, - self.tacan_registy, - self.mission_data, - dynamic_runways, - self.use_client, - ).configure() + self.mission_data.flights.append( + PretenseFlightGroupConfigurator( + flight, + group, + self.game, + self.mission, + self.time, + self.radio_registry, + self.tacan_registy, + self.mission_data, + dynamic_runways, + self.use_client, + ).configure() + ) if self.ewrj: self._track_ewrj_flight(flight, group) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index cdb10a23..dad732c8 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -30,7 +30,7 @@ class PretenseNameGenerator(NameGenerator): @classmethod def next_pretense_aircraft_name(cls, cp: ControlPoint, flight: Flight) -> str: cls.aircraft_number += 1 - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) return "{}-{}-{}".format( cp_name_trimmed, str(flight.flight_type).lower(), cls.aircraft_number ) @@ -77,12 +77,17 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): == self.flight.coalition.game.coalition_for(is_player) else 1 ) - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ self.flight.flight_type ].append(name) + try: + self.flight.coalition.game.pretense_air_groups[name] = self.flight + except AttributeError: + self.flight.coalition.game.pretense_air_groups = {} + self.flight.coalition.game.pretense_air_groups[name] = self.flight def generate_flight_at_departure(self) -> FlyingGroup[Any]: cp = self.flight.departure @@ -94,7 +99,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): == self.flight.coalition.game.coalition_for(is_player) else 1 ) - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) try: if self.start_type is StartType.IN_FLIGHT: @@ -139,8 +144,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group - self.insert_into_pretense(name) - return self._generate_over_departure(name, cp) + raise NoParkingSlotError elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter if cp.has_helipads and is_heli: diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 605e257f..2a56ba8c 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -259,7 +259,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" cp = self.game.theater.controlpoints[0] for loop_cp in self.game.theater.controlpoints: @@ -398,7 +398,7 @@ class PretenseLuaGenerator(LuaGenerator): ) lua_string_zones += " products = {\n" for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.SEAD: + if mission_type in (FlightType.SEAD, FlightType.DEAD): mission_name = "attack.sead" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -414,6 +414,9 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: + flight = self.game.pretense_air_groups[air_group] + if flight.is_helo: + mission_name = "attack.helo" lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" @@ -503,7 +506,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_sea_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" if cp_side == PRETENSE_BLUE_SIDE: @@ -608,6 +611,9 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: + flight = self.game.pretense_air_groups[air_group] + if flight.is_helo: + mission_name = "attack.helo" lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" @@ -697,7 +703,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_zone_land(self, cp_name: str) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" lua_string_zones += " [1] = { --red side\n" @@ -771,7 +777,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_zone_sea(self, cp_name: str) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" lua_string_zones += " [1] = { --red side\n" @@ -823,29 +829,29 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_plugin_data(self) -> None: self.inject_plugin_script("base", "mist_4_5_107.lua", "mist_4_5_107") - self.inject_plugin_script( - "pretense", "pretense_compiled.lua", "pretense_compiled" - ) - trigger = TriggerStart(comment="Pretense init") - - lua_string_config = "" + lua_string_config = "Config = Config or {}\n" lua_string_config += ( f"Config.maxDistFromFront = " + str(self.game.settings.pretense_maxdistfromfront_distance * 1000) + "\n" ) - lua_string_config += ( - f"Config.closeOverride = " - + str(self.game.settings.pretense_closeoverride_distance * 1000) - + "\n" - ) if self.game.settings.pretense_do_not_generate_sead_missions: lua_string_config += "Config.disablePlayerSead = true\n" else: lua_string_config += "Config.disablePlayerSead = false\n" + trigger = TriggerStart(comment="Pretense config") + trigger.add_action(DoScript(String(lua_string_config))) + self.mission.triggerrules.triggers.append(trigger) + + self.inject_plugin_script( + "pretense", "pretense_compiled.lua", "pretense_compiled" + ) + + trigger = TriggerStart(comment="Pretense init") + init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") init_header = init_header_file.read() @@ -855,7 +861,7 @@ class PretenseLuaGenerator(LuaGenerator): if isinstance(cp, OffMapSpawn): continue - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) cp_side = 2 if cp.captured else 1 for side in range(1, 3): if cp_name_trimmed not in self.game.pretense_air[cp_side]: @@ -947,12 +953,17 @@ class PretenseLuaGenerator(LuaGenerator): else: # Finally, connect remaining non-connected points closest_cps = self.game.theater.closest_friendly_control_points_to(cp) - lua_string_connman += self.generate_pretense_zone_connection( - connected_points, cp.name, closest_cps[0].name - ) - lua_string_connman += self.generate_pretense_zone_connection( - connected_points, cp.name, closest_cps[1].name - ) + for extra_connection in range( + self.game.settings.pretense_extra_zone_connections + ): + if len(closest_cps) > extra_connection: + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, + cp.name, + closest_cps[extra_connection].name, + ) + else: + break lua_string_supply = "local redSupply = {\n" # Generate supply @@ -963,7 +974,7 @@ class PretenseLuaGenerator(LuaGenerator): cp_side_captured = cp_side == 2 if cp_side_captured != cp.captured: continue - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: if mission_type == FlightType.PRETENSE_CARGO: for air_group in self.game.pretense_air[cp_side][ @@ -976,7 +987,7 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_supply += "local offmapZones = {\n" for cp in self.game.theater.controlpoints: if isinstance(cp, Airfield): - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) lua_string_supply += f" zones.{cp_name_trimmed},\n" lua_string_supply += "}\n" @@ -997,8 +1008,7 @@ class PretenseLuaGenerator(LuaGenerator): init_footer = init_footer_file.read() lua_string = ( - lua_string_config - + init_header + init_header + lua_string_zones + lua_string_connman + init_body_1 diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index e67ece34..a6dc8013 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -12,6 +12,7 @@ from dcs.countries import ( CombinedJointTaskForcesBlue, CombinedJointTaskForcesRed, ) +from dcs.task import AFAC, FAC, SetInvisibleCommand, SetImmortalCommand, OrbitAction from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.convoygenerator import ConvoyGenerator @@ -21,7 +22,7 @@ from game.missiongenerator.forcedoptionsgenerator import ForcedOptionsGenerator from game.missiongenerator.frontlineconflictdescription import ( FrontLineConflictDescription, ) -from game.missiongenerator.missiondata import MissionData +from game.missiongenerator.missiondata import MissionData, JtacInfo from game.missiongenerator.tgogenerator import TgoGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator from game.naming import namegen @@ -34,6 +35,8 @@ from .pretenseluagenerator import PretenseLuaGenerator from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator from ..ato.airtaaskingorder import AirTaskingOrder +from ..callsigns import callsign_for_support_unit +from ..dcs.aircrafttype import AircraftType from ..missiongenerator import MissionGenerator if TYPE_CHECKING: @@ -148,27 +151,61 @@ class PretenseMissionGenerator(MissionGenerator): 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( - 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.mission_data, - ) - ground_conflict_gen.generate() + + # Add JTAC + if self.game.blue.faction.has_jtac: + freq = self.radio_registry.alloc_uhf() + # If the option fc3LaserCode is enabled, force all JTAC + # laser codes to 1113 to allow lasing for Su-25 Frogfoots and A-10A Warthogs. + # Otherwise use 1688 for the first JTAC, 1687 for the second etc. + if self.game.settings.plugins.get("ctld.fc3LaserCode"): + code = self.game.laser_code_registry.fc3_code + else: + code = front_line.laser_code + + utype = self.game.blue.faction.jtac_unit + if utype is None: + utype = AircraftType.named("MQ-9 Reaper") + + country = self.mission.country(self.game.blue.faction.country.name) + position = FrontLineConflictDescription.frontline_position( + front_line, self.game.theater, self.game.settings + ) + jtac = self.mission.flight_group( + country=country, + name=namegen.next_jtac_name(), + aircraft_type=utype.dcs_unit_type, + position=position[0], + airport=None, + altitude=5000, + maintask=AFAC, + ) + jtac.points[0].tasks.append( + FAC( + callsign=len(self.mission_data.jtacs) + 1, + frequency=int(freq.mhz), + modulation=freq.modulation, + ) + ) + jtac.points[0].tasks.append(SetInvisibleCommand(True)) + jtac.points[0].tasks.append(SetImmortalCommand(True)) + jtac.points[0].tasks.append( + OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle) + ) + frontline = f"Frontline {player_cp.name}/{enemy_cp.name}" + # Note: Will need to change if we ever add ground based JTAC. + callsign = callsign_for_support_unit(jtac) + self.mission_data.jtacs.append( + JtacInfo( + group_name=jtac.name, + unit_name=jtac.units[0].name, + callsign=callsign, + region=frontline, + code=str(code), + blue=True, + freq=freq, + ) + ) def generate_air_units(self, tgo_generator: TgoGenerator) -> None: """Generate the air units for the Operation""" diff --git a/game/settings/settings.py b/game/settings/settings.py index 3acc74c8..d7c5348b 100644 --- a/game/settings/settings.py +++ b/game/settings/settings.py @@ -980,21 +980,102 @@ class Settings: default=130, min=10, max=10000, + detail=( + "Zones farther away than this from the front line are switched " + "into low activity state, but will still be there as functional " + "parts of the economy. Use this to adjust performance." + ), ) - pretense_closeoverride_distance: int = bounded_int_option( - "Close override distance (km)", + pretense_extra_zone_connections: int = bounded_int_option( + "Extra friendly zone connections", page=PRETENSE_PAGE, section=GENERAL_SECTION, - default=28, - min=5, - max=10000, + default=2, + min=0, + max=10, + detail=( + "Add connections from each zone to this many closest friendly zones," + "which don't have an existing supply route defined in the campaign." + ), ) pretense_do_not_generate_sead_missions: bool = boolean_option( "Do not generate player SEAD missions", page=PRETENSE_PAGE, - section=PERFORMANCE_SECTION, + section=GENERAL_SECTION, default=False, ) + pretense_num_of_cargo_planes: int = bounded_int_option( + "Number of cargo planes per side", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=100, + ) + pretense_sead_flights_per_cp: int = bounded_int_option( + "Number of AI SEAD flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_cas_flights_per_cp: int = bounded_int_option( + "Number of AI CAS flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_bai_flights_per_cp: int = bounded_int_option( + "Number of AI BAI flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_strike_flights_per_cp: int = bounded_int_option( + "Number of AI Strike flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_barcap_flights_per_cp: int = bounded_int_option( + "Number of AI BARCAP flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_ai_aircraft_per_flight: int = bounded_int_option( + "Number of AI aircraft per flight", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=4, + ) + pretense_player_flights_per_type: int = bounded_int_option( + "Number of player flights per aircraft type at each base", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_ai_cargo_planes_per_side: int = bounded_int_option( + "Number of AI cargo planes per side", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=20, + ) # Cheating. Not using auto settings because the same page also has buttons which do # not alter settings. diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index c73f5444..d7481f31 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -494,7 +494,7 @@ presets = { }), comCenter = Preset:new({ display = 'Command Center', - cost = 2500, + cost = 12500, type = 'upgrade', template = "command-center" }) @@ -522,43 +522,43 @@ presets = { }), sa10 = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='sa10', }), sa5 = Preset:new({ display = 'SAM', - cost=3000, + cost=20000, type='defense', template='sa5', }), sa3 = Preset:new({ display = 'SAM', - cost=3000, + cost=4000, type='defense', template='sa3', }), sa6 = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='sa6', }), sa11 = Preset:new({ display = 'SAM', - cost=3000, + cost=10000, type='defense', template='sa11', }), hawk = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='hawk', }), patriot = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='patriot', }), @@ -596,43 +596,43 @@ presets = { }), sa10 = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='sa10', }), sa5 = Preset:new({ display = 'SAM', - cost=3000, + cost=20000, type='defense', template='sa5', }), sa3 = Preset:new({ display = 'SAM', - cost=3000, + cost=4000, type='defense', template='sa3', }), sa6 = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='sa6', }), sa11 = Preset:new({ display = 'SAM', - cost=3000, + cost=10000, type='defense', template='sa11', }), hawk = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='hawk', }), patriot = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='patriot', }), diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index cf8bb881..5dbece1a 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -32,7 +32,6 @@ Config.buildSpeed = Config.buildSpeed or 10 -- structure and defense build speed Config.supplyBuildSpeed = Config.supplyBuildSpeed or 85 -- supply helicopters and convoys build speed Config.missionBuildSpeedReduction = Config.missionBuildSpeedReduction or 0.12 -- reduction of build speed in case of ai missions Config.maxDistFromFront = Config.maxDistFromFront or 129640 -- max distance in meters from front after which zone is forced into low activity state (export mode) -Config.closeOverride = Config.closeOverride or 27780 -- close override distance in meters from front within which zone is never forced into low activity state Config.disablePlayerSead = Config.disablePlayerSead or false Config.missions = Config.missions or {} @@ -505,8 +504,6 @@ end GroupMonitor = {} do GroupMonitor.blockedDespawnTime = 10*60 --used to despawn aircraft that are stuck taxiing for some reason - GroupMonitor.blockedDespawnTimeGround = 30*60 --used to despawn ground units that are stuck en route for some reason - GroupMonitor.blockedDespawnTimeGroundAssault = 90*60 --used to despawn assault units that are stuck en route for some reason GroupMonitor.landedDespawnTime = 10 GroupMonitor.atDestinationDespawnTime = 2*60 GroupMonitor.recoveryReduction = 0.8 -- reduce recovered resource from landed missions by this amount to account for maintenance @@ -642,13 +639,7 @@ do group.state = 'enroute' group.lastStateTime = timer.getAbsTime() MissionTargetRegistry.addBaiTarget(group) - elseif group.product.missionType == 'assault' and timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTimeGroundAssault then - env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') - gr:destroy() - local todeliver = math.floor(group.product.cost) - z:addResource(todeliver) - return true - elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTimeGround then + elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTime then env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') gr:destroy() local todeliver = math.floor(group.product.cost) @@ -744,7 +735,7 @@ do y = group.target.zone.point.z } - TaskExtensions.moveOffRoadToPointAndAssault(gr, tp, group.target.built) + TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built) group.isstopped = false end end @@ -1231,7 +1222,7 @@ do if v.type == 'defense' and v.side ~= group:getCoalition() then local gr = Group.getByName(v.name) for _,unit in ipairs(gr:getUnits()) do - if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') or unit:hasAttribute('AAA') or unit:hasAttribute('IR Guided SAM') or unit:hasAttribute('SAM LL') then + if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') then table.insert(viable, unit:getName()) end end @@ -1245,7 +1236,7 @@ do { id = 'EngageTargets', params = { - targetTypes = {'SAM SR', 'SAM TR', 'AAA', 'IR Guided SAM', 'SAM LL'} + targetTypes = {'SAM SR', 'SAM TR'} } } } @@ -2145,68 +2136,7 @@ do } group:getController():setTask(mis) end - - function TaskExtensions.moveOffRoadToPointAndAssault(group, point, targets) - if not group or not point then return end - if not group:isExist() or group:getSize()==0 then return end - local startPos = group:getUnit(1):getPoint() - - local srx, sry = land.getClosestPointOnRoads('roads', startPos.x, startPos.z) - local erx, ery = land.getClosestPointOnRoads('roads', point.x, point.y) - - local mis = { - id='Mission', - params = { - route = { - points = { - [1] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = srx, - y = sry, - speed = 1000, - action = AI.Task.VehicleFormation.DIAMOND - }, - [2] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = erx, - y = ery, - speed = 1000, - action = AI.Task.VehicleFormation.DIAMOND - }, - [3] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = point.x, - y = point.y, - speed = 1000, - action = AI.Task.VehicleFormation.DIAMOND - } - } - } - } - } - - for i,v in pairs(targets) do - if v.type == 'defense' then - local group = Group.getByName(v.name) - if group then - for i,v in ipairs(group:getUnits()) do - local unpos = v:getPoint() - local pnt = {x=unpos.x, y = unpos.z} - - table.insert(mis.params.route.points, { - type= AI.Task.WaypointType.TURNING_POINT, - x = pnt.x, - y = pnt.y, - speed = 10, - action = AI.Task.VehicleFormation.DIAMOND - }) - end - end - end - end - group:getController():setTask(mis) - end - + function TaskExtensions.landAtPointFromAir(group, point, alt) if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end @@ -4882,7 +4812,7 @@ do product.lastMission = {zoneName = zone.name} timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) - TaskExtensions.moveOffRoadToPointAndAssault(gr, param.point, param.targets) + TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=zone.built}, timer.getTime()+1) end end @@ -5413,7 +5343,7 @@ do product.lastMission = {zoneName = v.name} timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) - TaskExtensions.moveOffRoadToPointAndAssault(gr, param.point, param.targets) + TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=v.built}, timer.getTime()+1) env.info("ZoneCommand - "..product.name.." targeting "..v.name) @@ -5975,7 +5905,7 @@ end BattlefieldManager = {} do - BattlefieldManager.closeOverride = Config.closeOverride -- default 15nm + BattlefieldManager.closeOverride = 27780 -- 15nm BattlefieldManager.farOverride = Config.maxDistFromFront -- default 100nm BattlefieldManager.boostScale = {[0] = 1.0, [1]=1.0, [2]=1.0} BattlefieldManager.noRedZones = false @@ -10554,7 +10484,6 @@ do end end end - return false end function SEAD:getMissionName() @@ -12208,7 +12137,7 @@ do if toGen > 0 then local validMissions = {} for _,v in pairs(Mission.types) do - if timer.getAbsTime() - timer.getTime0() > 120 and self:canCreateMission(v) then + if self:canCreateMission(v) then table.insert(validMissions,v) end end