diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py index 44c1ba02..4022139e 100644 --- a/game/ato/flightplans/pretensecargo.py +++ b/game/ato/flightplans/pretensecargo.py @@ -3,7 +3,7 @@ from __future__ import annotations import random from collections.abc import Iterator from dataclasses import dataclass -from datetime import timedelta +from datetime import datetime from typing import TYPE_CHECKING, Type from game.utils import feet @@ -31,16 +31,20 @@ class PretenseCargoFlightPlan(StandardFlightPlan[FerryLayout]): def tot_waypoint(self) -> FlightWaypoint: return self.layout.arrival - def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: + def tot_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None: # TOT planning isn't really useful for ferries. They're behind the front # lines so no need to wait for escorts or for other missions to complete. return None - def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: + def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None: return None @property - def mission_departure_time(self) -> timedelta: + def mission_begin_on_station_time(self) -> datetime | None: + return None + + @property + def mission_departure_time(self) -> datetime: return self.package.time_over_target @@ -77,14 +81,14 @@ class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): offmap_heading, PRETENSE_CARGO_FLIGHT_DISTANCE ) - altitude_is_agl = self.flight.unit_type.dcs_unit_type.helicopter + altitude_is_agl = self.flight.is_helo altitude = ( - feet(1500) + feet(self.coalition.game.settings.heli_cruise_alt_agl) if altitude_is_agl else self.flight.unit_type.preferred_patrol_altitude ) - builder = WaypointBuilder(self.flight, self.coalition) + builder = WaypointBuilder(self.flight) ferry_layout = FerryLayout( departure=builder.join(offmap_transport_spawn), nav_to=builder.nav_path( @@ -101,8 +105,7 @@ class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): ferry_layout.departure = builder.join(offmap_transport_spawn) ferry_layout.nav_to.append(builder.join(offmap_transport_spawn)) ferry_layout.nav_from.append(builder.join(offmap_transport_spawn)) - print(ferry_layout) return ferry_layout - def build(self) -> PretenseCargoFlightPlan: + def build(self, dump_debug_info: bool = False) -> PretenseCargoFlightPlan: return PretenseCargoFlightPlan(self.flight, self.layout()) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 85a29793..67efe309 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -395,7 +395,7 @@ class PretenseAircraftGenerator: ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) ato.add_package(package) return @@ -442,7 +442,7 @@ class PretenseAircraftGenerator: divert=cp, ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) package.add_flight(flight) @@ -483,7 +483,7 @@ class PretenseAircraftGenerator: divert=cp, ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) package.add_flight(flight) @@ -524,7 +524,7 @@ class PretenseAircraftGenerator: divert=cp, ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) package.add_flight(flight) @@ -582,11 +582,11 @@ class PretenseAircraftGenerator: StartType.COLD, divert=cp, ) - for roster_pilot in flight.roster.pilots: - if roster_pilot is not None: - roster_pilot.player = True + for roster_pilot in flight.roster.members: + if roster_pilot.pilot is not None: + roster_pilot.pilot.player = True print( - f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.name} at {squadron.location.name}. Pilot client count: {flight.client_count}" + f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.display_name} at {squadron.location.name}. Pilot client count: {flight.client_count}" ) package.add_flight(flight) @@ -720,7 +720,9 @@ class PretenseAircraftGenerator: flight.departure, flight ) logging.info( - f"Generating flight in {flight.coalition.faction.name} package {flight.squadron.aircraft} {flight.flight_type} for target: {package.target.name}, departure: {flight.from_cp.name}" + f"Generating flight in {flight.coalition.faction.name} package" + f" {flight.squadron.aircraft} {flight.flight_type} for target: {package.target.name}," + f" departure: {flight.departure.name}" ) if flight.alive: @@ -761,45 +763,81 @@ class PretenseAircraftGenerator: self.ground_spawns, self.mission_data, ).create_flight_group() - if flight.flight_type == FlightType.CAS: + + control_points_to_scan = ( + list(self.game.theater.closest_opposing_control_points()) + + self.game.theater.controlpoints + ) + + if ( + flight.flight_type == FlightType.CAS + or flight.flight_type == FlightType.TARCAP + ): for conflict in self.game.theater.conflicts(): flight.package.target = conflict break + elif flight.flight_type == FlightType.BARCAP: + for cp in control_points_to_scan: + if cp.coalition != flight.coalition or cp == flight.departure: + continue + if flight.package.target != flight.departure: + break + for mission_target in cp.ground_objects: + flight.package.target = mission_target + break elif ( flight.flight_type == FlightType.STRIKE or flight.flight_type == FlightType.BAI ): - for cp in self.game.theater.closest_opposing_control_points(): - if cp.coalition == flight.coalition: + for cp in control_points_to_scan: + if cp.coalition == flight.coalition or cp == flight.departure: continue + if flight.package.target != flight.departure: + break for mission_target in cp.ground_objects: flight.package.target = mission_target + break elif ( flight.flight_type == FlightType.OCA_RUNWAY or flight.flight_type == FlightType.OCA_AIRCRAFT ): - for cp in self.game.theater.controlpoints: - if cp.coalition == flight.coalition or not isinstance(cp, Airfield): + for cp in control_points_to_scan: + if ( + cp.coalition == flight.coalition + or not isinstance(cp, Airfield) + or cp == flight.departure + ): continue flight.package.target = cp - elif flight.flight_type == FlightType.DEAD: - for cp in self.game.theater.controlpoints: - if cp.coalition == flight.coalition: + break + elif ( + flight.flight_type == FlightType.DEAD + or flight.flight_type == FlightType.SEAD + ): + for cp in control_points_to_scan: + if cp.coalition == flight.coalition or cp == flight.departure: continue + if flight.package.target != flight.departure: + break for ground_object in cp.ground_objects: is_ewr = isinstance(ground_object, EwrGroundObject) is_sam = isinstance(ground_object, SamGroundObject) if is_ewr or is_sam: flight.package.target = ground_object + break elif flight.flight_type == FlightType.AIR_ASSAULT: - for cp in self.game.theater.closest_opposing_control_points(): - if cp.coalition == flight.coalition: + for cp in control_points_to_scan: + if cp.coalition == flight.coalition or cp == flight.departure: continue if flight.is_hercules: if cp.coalition == flight.coalition or not isinstance(cp, Airfield): continue flight.package.target = cp + break + + now = self.game.conditions.start_time + flight.package.set_tot_asap(now) logging.info( f"Configuring flight {group.name} {flight.squadron.aircraft} {flight.flight_type}, number of players: {flight.client_count}" @@ -812,7 +850,6 @@ class PretenseAircraftGenerator: self.time, self.radio_registry, self.tacan_registy, - self.laser_code_registry, self.mission_data, dynamic_runways, self.use_client, @@ -832,7 +869,7 @@ class PretenseAircraftGenerator: or flight.client_count and ( not self.need_ecm - or flight.loadout.has_weapon_of_type(WeaponType.JAMMER) + or flight.any_member_has_weapon_of_type(WeaponType.JAMMER) ) ): self.ewrj_package_dict[id(flight.package)].append(group) diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py index 18a75981..10bc45a4 100644 --- a/game/pretense/pretenseflightgroupconfigurator.py +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -3,14 +3,17 @@ from __future__ import annotations from datetime import datetime from typing import Any, Optional, TYPE_CHECKING -from dcs import Mission +from dcs import Mission, Point +from dcs.flyingunit import FlyingUnit from dcs.unitgroup import FlyingGroup from game.ato import Flight, FlightType +from game.ato.flightmember import FlightMember from game.data.weapons import Pylon from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.aircraft.aircraftbehavior import AircraftBehavior from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter +from game.missiongenerator.aircraft.bingoestimator import BingoEstimator from game.missiongenerator.aircraft.flightdata import FlightData from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, @@ -37,7 +40,6 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): time: datetime, radio_registry: RadioRegistry, tacan_registry: TacanRegistry, - laser_code_registry: LaserCodeRegistry, mission_data: MissionData, dynamic_runways: dict[str, RunwayData], use_client: bool, @@ -50,7 +52,6 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): time, radio_registry, tacan_registry, - laser_code_registry, mission_data, dynamic_runways, use_client, @@ -63,7 +64,6 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): self.time = time self.radio_registry = radio_registry self.tacan_registry = tacan_registry - self.laser_code_registry = laser_code_registry self.mission_data = mission_data self.dynamic_runways = dynamic_runways self.use_client = use_client @@ -72,12 +72,12 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group) AircraftPainter(self.flight, self.group).apply_livery() self.setup_props() - self.setup_payload() + self.setup_payloads() self.setup_fuel() flight_channel = self.setup_radios() laser_codes: list[Optional[int]] = [] - for unit, pilot in zip(self.group.units, self.flight.roster.pilots): + for unit, pilot in zip(self.group.units, self.flight.roster.members): self.configure_flight_member(unit, pilot, laser_codes) divert = None @@ -90,12 +90,21 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): self.flight, self.group, self.mission, - self.game.conditions.start_time, self.time, self.game.settings, self.mission_data, ).create_waypoints() + divert_position: Point | None = None + if self.flight.divert is not None: + divert_position = self.flight.divert.position + bingo_estimator = BingoEstimator( + self.flight.unit_type.fuel_consumption, + self.flight.arrival.position, + divert_position, + self.flight.flight_plan.waypoints, + ) + self.group.uncontrolled = False return FlightData( @@ -105,7 +114,7 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): flight_type=self.flight.flight_type, units=self.group.units, size=len(self.group.units), - friendly=self.flight.from_cp.captured, + friendly=self.flight.departure.captured, departure_delay=mission_start_time, departure=self.flight.departure.active_runway( self.game.theater, self.game.conditions, self.dynamic_runways @@ -116,21 +125,26 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): divert=divert, waypoints=waypoints, intra_flight_channel=flight_channel, - bingo_fuel=self.flight.flight_plan.bingo_fuel, - joker_fuel=self.flight.flight_plan.joker_fuel, + bingo_fuel=bingo_estimator.estimate_bingo(), + joker_fuel=bingo_estimator.estimate_joker(), custom_name=self.flight.custom_name, laser_codes=laser_codes, ) - def setup_payload(self) -> None: - for p in self.group.units: - p.pylons.clear() + def setup_payloads(self) -> None: + for unit, member in zip(self.group.units, self.flight.iter_members()): + self.setup_payload(unit, member) + + def setup_payload(self, unit: FlyingUnit, member: FlightMember) -> None: + unit.pylons.clear() + + loadout = member.loadout if self.flight.flight_type == FlightType.SEAD: - self.flight.loadout = self.flight.loadout.default_for_task_and_aircraft( + loadout = member.loadout.default_for_task_and_aircraft( FlightType.SEAD_SWEEP, self.flight.unit_type.dcs_unit_type ) - loadout = self.flight.loadout + if self.game.settings.restrict_weapons_by_date: loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) @@ -138,4 +152,4 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): if weapon is None: continue pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) - pylon.equip(self.group, weapon) + pylon.equip(unit, weapon) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 4ddbb6e8..cdb10a23 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -118,7 +118,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): raise RuntimeError( f"Cannot spawn fixed-wing aircraft at {cp} because of insufficient ground spawn slots." ) - pilot_count = len(self.flight.roster.pilots) + pilot_count = len(self.flight.roster.members) if ( not is_heli and self.flight.roster.player_count != pilot_count diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 79bf3eec..ae08b766 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -148,7 +148,7 @@ class PretenseMissionGenerator(MissionGenerator): player_cp = front_line.blue_cp enemy_cp = front_line.red_cp conflict = FrontLineConflictDescription.frontline_cas_conflict( - front_line, self.game.theater, self.game.settings + front_line, self.game.theater ) # Generate frontline ops player_gp = self.game.ground_planners[player_cp.id].units_per_cp[ @@ -166,7 +166,6 @@ class PretenseMissionGenerator(MissionGenerator): self.unit_map, self.radio_registry, self.mission_data, - self.laser_code_registry, ) ground_conflict_gen.generate()