import math import operator import typing import random from game import db from gen.flights.ai_flight_planner_db import INTERCEPT_CAPABLE, CAP_CAPABLE, CAS_CAPABLE, SEAD_CAPABLE from gen.flights.flight import Flight, FlightType # TODO : Ideally should be based on the aircraft type instead / Availability of fuel STRIKE_MAX_RANGE = 150000 SEAD_MAX_RANGE = 150000 MAX_NUMBER_OF_INTERCEPTION_GROUP = 3 MISSION_DURATION = 120 # in minutes CAP_EVERY_X_MINUTES = 20 CAS_EVERY_X_MINUTES = 30 SEAD_EVERY_X_MINUTES = 40 class FlightPlanner: from_cp = None game = None interceptor_flights = [] cap_flights = [] cas_flights = [] strike_flights = [] sead_flights = [] flights = [] potential_sead_targets = [] potential_strike_targets = [] def __init__(self, from_cp, game): # TODO : have the flight planner depend on a 'stance' setting : [Defensive, Aggresive... etc] and faction doctrine self.from_cp = from_cp self.game = game self.aircraft_inventory = {} # local copy of the airbase inventory def reset(self): """ Reset the planned flights and avaialble units """ self.aircraft_inventory = dict({k: v for k, v in self.from_cp.base.aircraft.items()}) self.interceptor_flights = [] self.cap_flights = [] self.cas_flights = [] self.strike_flights = [] self.sead_flights = [] self.flights = [] self.potential_sead_targets = [] self.potential_strike_targets = [] def plan_flights(self): self.reset() self.compute_sead_targets() self.compute_strike_targets() # The priority is to assign air-superiority fighter or interceptor to interception roles, so they can scramble if there is an attacker self.commision_interceptors() # Then some CAP patrol for the next 2 hours self.commision_barcap() # Then setup cas self.commision_cas() # Then prepare some sead flights if required self.commision_sead() # TODO : commision STRIKE / ANTISHIP def commision_interceptors(self): """ Pick some aircraft to assign them to interception roles """ # At least try to generate one interceptor group number_of_interceptor_groups = min(max(sum([v for k, v in self.aircraft_inventory.items()]) / 4, MAX_NUMBER_OF_INTERCEPTION_GROUP), 1) possible_interceptors = [k for k in self.aircraft_inventory.keys() if k in INTERCEPT_CAPABLE] if len(possible_interceptors) <= 0: possible_interceptors = [k for k,v in self.aircraft_inventory.items() if k in CAP_CAPABLE and v >= 2] if number_of_interceptor_groups > 0: inventory = dict({k: v for k, v in self.aircraft_inventory.items() if k in possible_interceptors}) for i in range(number_of_interceptor_groups): try: unit = random.choice([k for k,v in inventory.items() if v >= 2]) except IndexError: break inventory[unit] = inventory[unit] - 2 flight = Flight(unit, 2, self.from_cp, FlightType.INTERCEPTION) flight.points = [] self.interceptor_flights.append(flight) self.flights.append(flight) # Update inventory for k, v in inventory.items(): self.aircraft_inventory[k] = v def commision_barcap(self): """ Pick some aircraft to assign them to defensive CAP roles (BARCAP) """ possible_aircraft = [k for k, v in self.aircraft_inventory.items() if k in CAP_CAPABLE and v >= 2] inventory = dict({k: v for k, v in self.aircraft_inventory.items() if k in possible_aircraft}) offset = random.randint(0,5) for i in range(int(MISSION_DURATION/CAP_EVERY_X_MINUTES)): try: unit = random.choice([k for k, v in inventory.items() if v >= 2]) except IndexError: break inventory[unit] = inventory[unit] - 2 flight = Flight(unit, 2, self.from_cp, FlightType.BARCAP) # Flight path : fly over each ground object (TODO : improve) flight.points = [] flight.scheduled_in = offset + i*random.randint(CAP_EVERY_X_MINUTES-5, CAP_EVERY_X_MINUTES+5) patrol_alt = random.randint(3600, 7000) patrolled = [] for ground_object in self.from_cp.ground_objects: if ground_object.group_id not in patrolled and not ground_object.airbase_group: flight.points.append([ground_object.position.x, ground_object.position.y, patrol_alt]) patrolled.append(ground_object.group_id) self.cap_flights.append(flight) self.flights.append(flight) # Update inventory for k, v in inventory.items(): self.aircraft_inventory[k] = v def commision_cas(self): """ Pick some aircraft to assign them to CAS """ possible_aircraft = [k for k, v in self.aircraft_inventory.items() if k in CAS_CAPABLE and v >= 2] inventory = dict({k: v for k, v in self.aircraft_inventory.items() if k in possible_aircraft}) cas_location = self._get_cas_locations() if len(cas_location) > 0: offset = random.randint(0,5) for i in range(int(MISSION_DURATION/CAS_EVERY_X_MINUTES)): try: unit = random.choice([k for k, v in inventory.items() if v >= 2]) except IndexError: break inventory[unit] = inventory[unit] - 2 flight = Flight(unit, 2, self.from_cp, FlightType.CAS) # Flight path : fly over each ground object (TODO : improve) flight.points = [] flight.scheduled_in = offset + i*random.randint(CAS_EVERY_X_MINUTES-5, CAS_EVERY_X_MINUTES+5) location = random.choice(cas_location) flight.targets.append(cas_location) flight.points.append([location[0], location[1], 1000]) # TODO : Egress / Ingress points self.cas_flights.append(flight) self.flights.append(flight) # Update inventory for k, v in inventory.items(): self.aircraft_inventory[k] = v def commision_sead(self): """ Pick some aircraft to assign them to SEAD tasks """ possible_aircraft = [k for k, v in self.aircraft_inventory.items() if k in SEAD_CAPABLE and v >= 2] inventory = dict({k: v for k, v in self.aircraft_inventory.items() if k in possible_aircraft}) if len(self.potential_sead_targets) > 0: offset = random.randint(0,5) for i in range(int(MISSION_DURATION/SEAD_EVERY_X_MINUTES)): if len(self.potential_sead_targets) <= 0: break try: unit = random.choice([k for k, v in inventory.items() if v >= 2]) except IndexError: break inventory[unit] = inventory[unit] - 2 flight = Flight(unit, 2, self.from_cp, random.choice([FlightType.SEAD, FlightType.DEAD])) # Flight path : fly over each ground object (TODO : improve) flight.points = [] flight.scheduled_in = offset + i*random.randint(SEAD_EVERY_X_MINUTES-5, SEAD_EVERY_X_MINUTES+5) location = self.potential_sead_targets[0][0] self.potential_sead_targets.pop(0) flight.points.append([location.position.x, location.position.y, 1000]) # TODO : Egress / Ingress points self.cas_flights.append(flight) self.flights.append(flight) # Update inventory for k, v in inventory.items(): self.aircraft_inventory[k] = v def _get_cas_locations(self): cas_locations = [] for cp in self.from_cp.connected_points: if cp.captured != self.from_cp.captured: cas_locations.append(((self.from_cp.position.x + cp.position.x)/2, (self.from_cp.position.y + cp.position.y)/2)) return cas_locations def compute_strike_targets(self): """ @return a list of potential strike targets in range """ # target, distance self.potential_strike_targets = [] for cp in [c for c in self.game.theater.controlpoints if c.captured != self.from_cp.captured]: # Compute distance to current cp distance = math.hypot(cp.position.x - self.from_cp.position.x, cp.position.y - self.from_cp.position.y) if distance > 2*STRIKE_MAX_RANGE: # Then it's unlikely any child ground object is in range return added_group = [] for g in cp.ground_objects: if g.group_id in added_group: continue # Compute distance to current cp distance = math.hypot(cp.position.x - self.from_cp.position.x, cp.position.y - self.from_cp.position.y) if distance < SEAD_MAX_RANGE: self.potential_strike_targets.append((g, distance)) added_group.append(g) self.potential_strike_targets.sort(key=operator.itemgetter(1)) def compute_sead_targets(self): """ @return a list of potential sead targets in range """ # target, distance self.potential_sead_targets = [] for cp in [c for c in self.game.theater.controlpoints if c.captured != self.from_cp.captured]: # Compute distance to current cp distance = math.hypot(cp.position.x - self.from_cp.position.x, cp.position.y - self.from_cp.position.y) # Then it's unlikely any ground object is range if distance > 2*SEAD_MAX_RANGE: return for g in cp.ground_objects: if g.dcs_identifier == "AA": # Check that there is at least one unit with a radar in the ground objects unit groups number_of_units = sum([len([r for r in group.units if hasattr(db.unit_type_from_name(r.type), "detection_range") and db.unit_type_from_name(r.type).detection_range > 1000]) for group in g.groups]) if number_of_units <= 0: continue # Compute distance to current cp distance = math.hypot(cp.position.x - self.from_cp.position.x, cp.position.y - self.from_cp.position.y) if distance < SEAD_MAX_RANGE: self.potential_sead_targets.append((g, distance)) self.potential_sead_targets.sort(key=operator.itemgetter(1)) def __repr__(self): return "-"*40 + "\n" + self.from_cp.name + " planned flights :\n"\ + "-"*40 + "\n" + "\n".join([repr(f) for f in self.flights]) + "\n" + "-"*40