def can_travel_to_system(fleet_id: int, start: TargetSystem, target: TargetSystem, ensure_return: bool = False) -> List[TargetSystem]: """ Return list systems to be visited. """ if start == target: return [TargetSystem(start.id)] debug("Requesting path for fleet %s from %s to %s" % (fo.getUniverse().getFleet(fleet_id), start, target)) target_distance_from_supply = -min(get_system_supply(target.id), 0) # low-aggression AIs may not travel far from supply if not get_aistate().character.may_travel_beyond_supply( target_distance_from_supply): debug("May not move %d out of supply" % target_distance_from_supply) return [] min_fuel_at_target = target_distance_from_supply if ensure_return else 0 path_info = pathfinding.find_path_with_resupply( start.id, target.id, fleet_id, minimum_fuel_at_target=min_fuel_at_target) if path_info is None: debug("Found no valid path.") return [] debug("Found valid path: %s" % str(path_info)) return [TargetSystem(sys_id) for sys_id in path_info.path]
def get_location_target(self): """system AITarget where fleet is or will be""" # TODO add parameter turn fleet = fo.getUniverse().getFleet(self.fleet.id) system_id = fleet.systemID if system_id >= 0: return TargetSystem(system_id) else: # in starlane, so return next system return TargetSystem(fleet.nextSystemID)
def get_safe_path_leg_to_dest(fleet_id, start_id, dest_id): start_targ = TargetSystem(start_id) dest_targ = TargetSystem(dest_id) # TODO actually get a safe path this_path = can_travel_to_system(fleet_id, start_targ, dest_targ, ensure_return=False) path_ids = [targ.id for targ in this_path if targ.id != start_id] + [start_id] universe = fo.getUniverse() debug("Fleet %d requested safe path leg from %s to %s, found path %s" % (fleet_id, universe.getSystem(start_id), universe.getSystem(dest_id), PlanetUtilsAI.sys_name_ids(path_ids))) return path_ids[0]
def send_invasion_fleets(fleet_ids, evaluated_planets, mission_type): """sends a list of invasion fleets to a list of planet_value_pairs""" if not fleet_ids: return universe = fo.getUniverse() invasion_fleet_pool = set(fleet_ids) for planet_id, pscore, ptroops in evaluated_planets: if pscore < MIN_INVASION_SCORE: continue planet = universe.getPlanet(planet_id) if not planet: continue sys_id = planet.systemID found_fleets = [] found_stats = {} min_stats = {"troopCapacity": ptroops} target_stats = { "troopCapacity": ptroops + _TROOPS_SAFETY_MARGIN, "target_system": TargetSystem(sys_id), } these_fleets = FleetUtilsAI.get_fleets_for_mission( target_stats, min_stats, found_stats, starting_system=sys_id, fleet_pool_set=invasion_fleet_pool, fleet_list=found_fleets, ) if not these_fleets: if not FleetUtilsAI.stats_meet_reqs(found_stats, min_stats): debug( "Insufficient invasion troop allocation for system %d ( %s ) -- requested %s , found %s" % (sys_id, universe.getSystem(sys_id).name, min_stats, found_stats)) invasion_fleet_pool.update(found_fleets) continue else: these_fleets = found_fleets target = TargetPlanet(planet_id) debug("assigning invasion fleets %s to target %s" % (these_fleets, target)) if target.get_object().currentMeterValue( fo.meterType.maxShield) > 0.0 and not secure_system( sys_id, True): continue aistate = get_aistate() for fleetID in these_fleets: fleet_mission = aistate.get_fleet_mission(fleetID) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() fleet_mission.set_target(mission_type, target) # cannot wait for next turn, else the secure mission may be abandoned if these_fleets and sys_id not in AIstate.invasionTargetedSystemIDs: AIstate.invasionTargetedSystemIDs.append(sys_id)
def get_nearest_supplied_system(start_system_id: SystemId): """Return systemAITarget of nearest supplied system from starting system startSystemID.""" empire = fo.getEmpire() fleet_supplyable_system_ids = empire.fleetSupplyableSystemIDs universe = fo.getUniverse() if start_system_id in fleet_supplyable_system_ids: return TargetSystem(start_system_id) else: min_jumps = 9999 # infinity supply_system_id = INVALID_ID for system_id in fleet_supplyable_system_ids: if start_system_id != INVALID_ID and system_id != INVALID_ID: least_jumps_len = universe.jumpDistance( start_system_id, system_id) if least_jumps_len < min_jumps: min_jumps = least_jumps_len supply_system_id = system_id return TargetSystem(supply_system_id)
def generate_fleet_orders(self): """generates AIFleetOrders from fleets targets to accomplish""" universe = fo.getUniverse() fleet_id = self.fleet.id fleet = universe.getFleet(fleet_id) if (not fleet) or fleet.empty or (fleet_id in universe.destroyedObjectIDs(fo.empireID())): # fleet was probably merged into another or was destroyed get_aistate().delete_fleet_info(fleet_id) return # TODO: priority self.clear_fleet_orders() system_id = fleet.systemID start_sys_id = [fleet.nextSystemID, system_id][system_id >= 0] # if fleet doesn't have any mission, # then repair if needed or resupply if is current location not in supplyable system empire = fo.getEmpire() fleet_supplyable_system_ids = empire.fleetSupplyableSystemIDs # if (not self.hasAnyAIMissionTypes()): if not self.target and (system_id not in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs)): if self._need_repair(): repair_fleet_order = MoveUtilsAI.get_repair_fleet_order(self.fleet, start_sys_id) if repair_fleet_order and repair_fleet_order.is_valid(): self.orders.append(repair_fleet_order) cur_fighter_capacity, max_fighter_capacity = FleetUtilsAI.get_fighter_capacity_of_fleet(fleet_id) if (fleet.fuel < fleet.maxFuel or cur_fighter_capacity < max_fighter_capacity and self.get_location_target().id not in fleet_supplyable_system_ids): resupply_fleet_order = MoveUtilsAI.get_resupply_fleet_order(self.fleet, self.get_location_target()) if resupply_fleet_order.is_valid(): self.orders.append(resupply_fleet_order) return # no targets if self.target: # for some targets fleet has to visit systems and therefore fleet visit them system_to_visit = (self.target.get_system() if not self.type == MissionType.PROTECT_REGION else TargetSystem(self._get_target_for_protection_mission())) if not system_to_visit: return orders_to_visit_systems = MoveUtilsAI.create_move_orders_to_system(self.fleet, system_to_visit) # TODO: if fleet doesn't have enough fuel to get to final target, consider resetting Mission for fleet_order in orders_to_visit_systems: self.orders.append(fleet_order) # also generate appropriate final orders fleet_order = self._get_fleet_order_from_target(self.type, self.target if not self.type == MissionType.PROTECT_REGION else system_to_visit) self.orders.append(fleet_order)
def send_invasion_fleets(fleet_ids, evaluated_planets, mission_type): """sends a list of invasion fleets to a list of planet_value_pairs""" if not fleet_ids: return universe = fo.getUniverse() invasion_fleet_pool = set(fleet_ids) for planet_id, pscore, ptroops in evaluated_planets: if pscore < MIN_INVASION_SCORE: continue planet = universe.getPlanet(planet_id) if not planet: continue sys_id = planet.systemID found_fleets = [] found_stats = {} min_stats = {"rating": 0, "troopCapacity": ptroops} target_stats = { "rating": 10, "troopCapacity": ptroops + _TROOPS_SAFETY_MARGIN, "target_system": TargetSystem(sys_id), } these_fleets = FleetUtilsAI.get_fleets_for_mission( target_stats, min_stats, found_stats, starting_system=sys_id, fleet_pool_set=invasion_fleet_pool, fleet_list=found_fleets, ) if not these_fleets: if not FleetUtilsAI.stats_meet_reqs(found_stats, min_stats): debug( "Insufficient invasion troop allocation for system %d ( %s ) -- requested %s , found %s" % (sys_id, universe.getSystem(sys_id).name, min_stats, found_stats)) invasion_fleet_pool.update(found_fleets) continue else: these_fleets = found_fleets target = TargetPlanet(planet_id) debug("assigning invasion fleets %s to target %s" % (these_fleets, target)) aistate = get_aistate() for fleetID in these_fleets: fleet_mission = aistate.get_fleet_mission(fleetID) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() fleet_mission.set_target(mission_type, target)
def get_repair_fleet_order( fleet: TargetFleet) -> Optional["fleet_orders.OrderRepair"]: """ Return fleet_orders.OrderRepair for fleet to proceed to system with drydock. """ # TODO Cover new mechanics where happiness increases repair rate - don't always use nearest system! # find nearest drydock system drydock_sys_id = get_best_drydock_system_id(get_fleet_position(fleet.id), fleet.id) if drydock_sys_id is None: return None debug("Ordering fleet %s to %s for repair" % (fleet, fo.getUniverse().getSystem(drydock_sys_id))) return fleet_orders.OrderRepair(fleet, TargetSystem(drydock_sys_id))
def get_repair_fleet_order(fleet, current_system_id): """Return fleet_orders.OrderRepair for fleet to proceed to system with drydock. :param fleet: fleet that need to be repaired :type fleet: target.TargetFleet # TODO check if we can remove this id, because fleet already have it. :param current_system_id: current location of the fleet, next system if currently on starlane. :type current_system_id: int :return: order to repair :rtype fleet_orders.OrderRepair """ # TODO Cover new mechanics where happiness increases repair rate - don't always use nearest system! # find nearest drydock system drydock_sys_id = get_best_drydock_system_id(current_system_id, fleet.id) if drydock_sys_id is None: return None debug("Ordering fleet %s to %s for repair" % (fleet, fo.getUniverse().getSystem(drydock_sys_id))) return fleet_orders.OrderRepair(fleet, TargetSystem(drydock_sys_id))
def _get_target_for_protection_mission(self): """Get a target for a PROTECT_REGION mission. 1) If primary target (system target of this mission) is under attack, move to primary target. 2) If neighbors of primary target have local enemy forces weaker than this fleet, may move to attack 3) If no neighboring fleets or strongest enemy force is too strong, move to defend primary target """ # TODO: Also check fleet rating vs planets in decision making below not only vs fleets universe = fo.getUniverse() primary_objective = self.target.id debug("Trying to find target for protection mission. Target: %s", self.target) immediate_threat = MilitaryAI.get_system_local_threat(primary_objective) if immediate_threat: debug("Immediate threat! Moving to primary mission target") return primary_objective else: debug("No immediate threats.") # Try to eliminate neighbouring fleets neighbors = universe.getImmediateNeighbors(primary_objective, fo.empireID()) threat_list = sorted(map( lambda x: (MilitaryAI.get_system_local_threat(x), x), neighbors ), reverse=True) if not threat_list: debug("No neighbors (?!). Moving to primary mission target") return primary_objective debug("%s", threat_list) top_threat, candidate_system = threat_list[0] if not top_threat: # TODO: Move into second ring but needs more careful evaluation # For now, consider staying at the current location if enemy # owns a planet here which we can bombard. current_system_id = self.fleet.get_current_system_id() if current_system_id in neighbors: system = universe.getSystem(current_system_id) if assertion_fails(system is not None): return primary_objective empire_id = fo.empireID() for planet_id in system.planetIDs: planet = universe.getPlanet(planet_id) if (planet and not planet.ownedBy(empire_id) and not planet.unowned): debug("Currently no neighboring threats. " "Staying for bombardment of planet %s", planet) self.clear_fleet_orders() self.set_target(MissionType.MILITARY, TargetSystem(current_system_id)) self.generate_fleet_orders() self.issue_fleet_orders() return INVALID_ID # TODO consider attacking neighboring, non-military fleets # - needs more careful evaluation against neighboring threats # empire_id = fo.empireID() # for sys_id in neighbors: # system = universe.getSystem(sys_id) # if assertion_fails(system is not None): # continue # local_fleets = system.fleetIDs # for fleet_id in local_fleets: # fleet = universe.getFleet(fleet_id) # if not fleet or fleet.ownedBy(empire_id): # continue # return sys_id debug("No neighboring threats. Moving to primary mission target") return primary_objective # TODO rate against threat in target system # TODO only engage if can reach in 1 turn or leaves sufficient defense behind fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id) debug("This fleet rating: %d. Enemy Rating: %d", fleet_rating, top_threat) safety_factor = get_aistate().character.military_safety_factor() if fleet_rating < safety_factor*top_threat: debug("Neighboring threat is too powerful. Moving to primary mission target") return primary_objective # do not engage! debug("Engaging neighboring threat: %d", candidate_system) return candidate_system
def assign_scouts_to_explore_systems(): # TODO: use Graph Theory to explore closest systems universe = fo.getUniverse() capital_sys_id = PlanetUtilsAI.get_capital_sys_id() # order fleets to explore if not border_unexplored_system_ids or (capital_sys_id == INVALID_ID): return exp_systems_by_dist = sorted( (universe.linearDistance(capital_sys_id, x), x) for x in border_unexplored_system_ids) debug( "Exploration system considering following system-distance pairs:\n %s" % ("\n ".join("%3d: %5.1f" % (sys_id, dist) for (dist, sys_id) in exp_systems_by_dist))) explore_list = [sys_id for dist, sys_id in exp_systems_by_dist] already_covered, available_scouts = get_current_exploration_info() debug("Explorable system IDs: %s" % explore_list) debug("Already targeted: %s" % already_covered) aistate = get_aistate() needs_vis = aistate.misc.setdefault('needs_vis', []) check_list = aistate.needsEmergencyExploration + needs_vis + explore_list if INVALID_ID in check_list: # shouldn't normally happen, unless due to bug elsewhere for sys_list, name in [(aistate.needsEmergencyExploration, "aistate.needsEmergencyExploration"), (needs_vis, "needs_vis"), (explore_list, "explore_list")]: if INVALID_ID in sys_list: error("INVALID_ID found in " + name, exc_info=True) # emergency coverage can be due to invasion detection trouble, etc. debug("Check list: %s" % check_list) needs_coverage = [ sys_id for sys_id in check_list if sys_id not in already_covered and sys_id != INVALID_ID ] debug("Needs coverage: %s" % needs_coverage) debug("Available scouts & AIstate locs: %s" % [(x, aistate.fleetStatus.get(x, {}).get('sysID', INVALID_ID)) for x in available_scouts]) debug("Available scouts & universe locs: %s" % [(x, universe.getFleet(x).systemID) for x in available_scouts]) if not needs_coverage or not available_scouts: return # clean up targets which can not or don't need to be scouted for sys_id in list(needs_coverage): if sys_id not in explore_list: # doesn't necessarily need direct visit if universe.getVisibility(sys_id, fo.empireID()) >= fo.visibility.partial: # already got visibility; remove from visit lists and skip if sys_id in needs_vis: del needs_vis[needs_vis.index(sys_id)] if sys_id in aistate.needsEmergencyExploration: del aistate.needsEmergencyExploration[ aistate.needsEmergencyExploration.index(sys_id)] debug( "system id %d already currently visible; skipping exploration" % sys_id) needs_coverage.remove(sys_id) continue # skip systems threatened by monsters sys_status = aistate.systemStatus.setdefault(sys_id, {}) if (not aistate.character.may_explore_system( sys_status.setdefault('monsterThreat', 0)) or (fo.currentTurn() < 20 and aistate.systemStatus[sys_id]['monsterThreat'] > 0)): debug( "Skipping exploration of system %d due to Big Monster, threat %d" % (sys_id, aistate.systemStatus[sys_id]['monsterThreat'])) needs_coverage.remove(sys_id) continue # find the jump distance for all possible scout-system pairings options = [] available_scouts = set(available_scouts) for fleet_id in available_scouts: fleet_mission = aistate.get_fleet_mission(fleet_id) start = fleet_mission.get_location_target() for sys_id in needs_coverage: target = TargetSystem(sys_id) path = MoveUtilsAI.can_travel_to_system(fleet_id, start, target, ensure_return=True) if not path: continue num_jumps = len( path) - 1 # -1 as path contains the original system options.append((num_jumps, fleet_id, sys_id)) # Apply a simple, greedy heuristic to match scouts to nearby systems: # Always choose the shortest possible path from the remaining scout-system pairing. # This is clearly not optimal in the general case but it works well enough for now. # TODO: Consider using a more sophisticated assignment algorithm options.sort() while options: debug("Remaining options: %s" % options) _, fleet_id, sys_id = options[0] fleet_mission = aistate.get_fleet_mission(fleet_id) target = TargetSystem(sys_id) info("Sending fleet %d to explore %s" % (fleet_id, target)) fleet_mission.set_target(MissionType.EXPLORATION, target) options = [ option for option in options if option[1] != fleet_id and option[2] != sys_id ] available_scouts.remove(fleet_id) needs_coverage.remove(sys_id) debug("Exploration assignment finished.") debug("Unassigned scouts: %s" % available_scouts) debug("Unassigned exploration targets: %s" % needs_coverage)
def assign_military_fleets_to_systems(use_fleet_id_list=None, allocations=None, round=1): # assign military fleets to military theater systems global _military_allocations universe = fo.getUniverse() if allocations is None: allocations = [] doing_main = (use_fleet_id_list is None) aistate = get_aistate() if doing_main: aistate.misc['ReassignedFleetMissions'] = [] base_defense_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.ORBITAL_DEFENSE) unassigned_base_defense_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types( base_defense_ids) for fleet_id in unassigned_base_defense_ids: fleet = universe.getFleet(fleet_id) if not fleet: continue sys_id = fleet.systemID target = TargetSystem(sys_id) fleet_mission = aistate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() mission_type = MissionType.ORBITAL_DEFENSE fleet_mission.set_target(mission_type, target) all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.MILITARY) if not all_military_fleet_ids: _military_allocations = [] return avail_mil_fleet_ids = list( FleetUtilsAI.extract_fleet_ids_without_mission_types( all_military_fleet_ids)) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair( avail_mil_fleet_ids) these_allocations = _military_allocations debug("==================================================") debug("Assigning military fleets") debug("---------------------------------") else: avail_mil_fleet_ids = list(use_fleet_id_list) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair( avail_mil_fleet_ids) these_allocations = allocations # send_for_repair(mil_needing_repair_ids) #currently, let get taken care of by AIFleetMission.generate_fleet_orders() # get systems to defend avail_mil_fleet_ids = set(avail_mil_fleet_ids) for sys_id, alloc, minalloc, rvp, takeAny in these_allocations: if not doing_main and not avail_mil_fleet_ids: break found_fleets = [] found_stats = {} ensure_return = sys_id not in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs) these_fleets = FleetUtilsAI.get_fleets_for_mission( target_stats={ 'rating': alloc, 'ratingVsPlanets': rvp, 'target_system': TargetSystem(sys_id) }, min_stats={ 'rating': minalloc, 'ratingVsPlanets': rvp, 'target_system': TargetSystem(sys_id) }, cur_stats=found_stats, starting_system=sys_id, fleet_pool_set=avail_mil_fleet_ids, fleet_list=found_fleets, ensure_return=ensure_return) if not these_fleets: if not found_fleets or not (FleetUtilsAI.stats_meet_reqs( found_stats, {'rating': minalloc}) or takeAny): if doing_main: if _verbose_mil_reporting: debug( "NO available/suitable military allocation for system %d ( %s ) -- requested allocation %8d, found available rating %8d in fleets %s" % (sys_id, universe.getSystem(sys_id).name, minalloc, found_stats.get('rating', 0), found_fleets)) avail_mil_fleet_ids.update(found_fleets) continue else: these_fleets = found_fleets elif doing_main and _verbose_mil_reporting: debug( "FULL+ military allocation for system %d ( %s ) -- requested allocation %8d, got %8d with fleets %s" % (sys_id, universe.getSystem(sys_id).name, alloc, found_stats.get('rating', 0), these_fleets)) target = TargetSystem(sys_id) for fleet_id in these_fleets: fo.issueAggressionOrder(fleet_id, True) fleet_mission = aistate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() if sys_id in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs): mission_type = MissionType.SECURE else: mission_type = MissionType.MILITARY fleet_mission.set_target(mission_type, target) fleet_mission.generate_fleet_orders() if not doing_main: aistate.misc.setdefault('ReassignedFleetMissions', []).append(fleet_mission) if doing_main: debug("---------------------------------") last_round = 3 last_round_name = "LastRound" if round <= last_round: # check if any fleets remain unassigned all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.MILITARY) avail_mil_fleet_ids = list( FleetUtilsAI.extract_fleet_ids_without_mission_types( all_military_fleet_ids)) allocations = [] round += 1 thisround = "Extras Remaining Round %d" % round if round < last_round else last_round_name if avail_mil_fleet_ids: debug("Still have available military fleets: %s" % avail_mil_fleet_ids) allocations = get_military_fleets( mil_fleets_ids=avail_mil_fleet_ids, try_reset=False, thisround=thisround) if allocations: assign_military_fleets_to_systems( use_fleet_id_list=avail_mil_fleet_ids, allocations=allocations, round=round)
def get_location_target(self) -> TargetSystem: # TODO add parameter turn return TargetSystem(get_fleet_position(self.fleet.id))
def get_best_drydock_system_id(start_system_id: int, fleet_id: int) -> Optional[int]: """ Get system_id of best drydock capable of repair, where best is nearest drydock that has a current and target happiness greater than the HAPPINESS_THRESHOLD with a path that is not blockaded or that the fleet can fight through to with acceptable losses. :param start_system_id: current location of fleet - used to find closest target :param fleet_id: fleet that needs path to drydock :return: most suitable system id where the fleet should be repaired. """ if start_system_id == INVALID_ID: warning("get_best_drydock_system_id passed bad system id.") return None if fleet_id == INVALID_ID: warning("get_best_drydock_system_id passed bad fleet id.") return None universe = fo.getUniverse() start_system = TargetSystem(start_system_id) drydock_system_ids = set() for sys_id, pids in get_empire_drydocks().items(): if sys_id == INVALID_ID: warning("get_best_drydock_system_id passed bad drydock sys_id.") continue for pid in pids: planet = universe.getPlanet(pid) if (planet and planet.currentMeterValue( fo.meterType.happiness) >= DRYDOCK_HAPPINESS_THRESHOLD and planet.currentMeterValue(fo.meterType.targetHappiness) >= DRYDOCK_HAPPINESS_THRESHOLD): drydock_system_ids.add(sys_id) break sys_distances = sorted([(universe.jumpDistance(start_system_id, sys_id), sys_id) for sys_id in drydock_system_ids]) aistate = get_aistate() fleet_rating = aistate.get_rating(fleet_id) for _, dock_sys_id in sys_distances: dock_system = TargetSystem(dock_sys_id) path = can_travel_to_system(fleet_id, start_system, dock_system) path_rating = sum([ aistate.systemStatus[path_sys.id]["totalThreat"] for path_sys in path ]) SAFETY_MARGIN = 10 if SAFETY_MARGIN * path_rating <= fleet_rating: debug( "Drydock recommendation %s from %s for fleet %s with fleet rating %.1f and path rating %.1f." % (dock_system, start_system, universe.getFleet(fleet_id), fleet_rating, path_rating)) return dock_system.id debug( "No safe drydock recommendation from %s for fleet %s with fleet rating %.1f." % (start_system, universe.getFleet(fleet_id), fleet_rating)) return None
def get_fleets_for_mission(target_stats, min_stats, cur_stats, starting_system, fleet_pool_set, fleet_list, species="", ensure_return=False): """Get fleets for a mission. Implements breadth-first search through systems starting at the **starting_sytem**. In each system, local fleets are checked if they are in the allowed **fleet_pool_set** and suitable for the mission. If so, they are added to the **fleet_list** and **cur_stats** is updated with the currently selected fleet summary. The search continues until the requirements defined in **target_stats** are met or there are no more systems/fleets. In that case, if the **min_stats** are covered, the **fleet_list** is returned anyway. Otherwise, an empty list is returned by the function, in which case the caller can make an evaluation of an emergency use of the found fleets in fleet_list; if not to be used they should be added back to the main pool. :param target_stats: stats the fleet should ideally meet :type target_stats: dict :param min_stats: minimum stats the final fleet must meet to be accepted :type min_stats: dict :param cur_stats: (**mutated**) stat summary of selected fleets :type cur_stats: dict :param starting_system: system_id where breadth-first-search is centered :type starting_system: int :param fleet_pool_set: (**mutated**) fleets allowed to be selected. Split fleed_ids are added, used ones removed. :type: fleet_pool_set: set[int] :param fleet_list: (**mutated**) fleets that are selected for the mission. Gets filled during the call. :type fleet_list: list[int] :param species: species for colonization mission :type species: str :param bool ensure_return: If true, fleet must have sufficient fuel to return into supply after mission :return: List of selected fleet_ids or empty list if couldn't meet minimum requirements. :rtype: list[int] """ universe = fo.getUniverse() colonization_roles = (ShipRoleType.CIVILIAN_COLONISATION, ShipRoleType.BASE_COLONISATION) systems_enqueued = [starting_system] systems_visited = [] # loop over systems in a breadth-first-search trying to find nearby suitable ships in fleet_pool_set aistate = get_aistate() while systems_enqueued and fleet_pool_set: this_system_id = systems_enqueued.pop(0) this_system_obj = TargetSystem(this_system_id) systems_visited.append(this_system_id) accessible_fleets = aistate.systemStatus.get(this_system_id, {}).get( 'myFleetsAccessible', []) fleets_here = [ fid for fid in accessible_fleets if fid in fleet_pool_set ] # loop over all fleets in the system, split them if possible and select suitable ships while fleets_here: fleet_id = fleets_here.pop(0) fleet = universe.getFleet(fleet_id) if not fleet: # TODO should be checked before passed to the function fleet_pool_set.remove(fleet_id) continue # try splitting fleet if fleet.numShips > 1: debug("Splitting candidate fleet to get ships for mission.") new_fleets = split_fleet(fleet_id) fleet_pool_set.update(new_fleets) fleets_here.extend(new_fleets) if ('target_system' in target_stats and not MoveUtilsAI.can_travel_to_system( fleet_id, this_system_obj, target_stats['target_system'], ensure_return=ensure_return)): continue # check species for colonization missions if species: for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) if (ship and aistate.get_ship_role( ship.design.id) in colonization_roles and species == ship.speciesName): break else: # no suitable species found continue # check troop capacity for invasion missions troop_capacity = 0 if 'troopCapacity' in target_stats: troop_capacity = count_troops_in_fleet(fleet_id) if troop_capacity <= 0: continue # check if we need additional rating vs planets this_rating_vs_planets = 0 if 'ratingVsPlanets' in target_stats: this_rating_vs_planets = aistate.get_rating( fleet_id, against_planets=True) if this_rating_vs_planets <= 0 and cur_stats.get( 'rating', 0) >= target_stats.get('rating', 0): # we already have enough general rating, so do not add any more warships useless against planets continue # all checks passed, add ship to selected fleets and update the stats try: fleet_pool_set.remove(fleet_id) except KeyError: error( "After having split a fleet, the original fleet apparently no longer exists.", exc_info=True) continue fleet_list.append(fleet_id) this_rating = aistate.get_rating(fleet_id) cur_stats['rating'] = CombatRatingsAI.combine_ratings( cur_stats.get('rating', 0), this_rating) if 'ratingVsPlanets' in target_stats: cur_stats['ratingVsPlanets'] = CombatRatingsAI.combine_ratings( cur_stats.get('ratingVsPlanets', 0), this_rating_vs_planets) if 'troopCapacity' in target_stats: cur_stats['troopCapacity'] = cur_stats.get('troopCapacity', 0) + troop_capacity # if we already meet the requirements, we can stop looking for more ships if (sum(len(universe.getFleet(fid).shipIDs) for fid in fleet_list) >= 1) \ and stats_meet_reqs(cur_stats, target_stats): return fleet_list # finished system without meeting requirements. Add neighboring systems to search queue. for neighbor_id in universe.getImmediateNeighbors( this_system_id, fo.empireID()): if all((neighbor_id not in systems_visited, neighbor_id not in systems_enqueued, neighbor_id in aistate.exploredSystemIDs)): systems_enqueued.append(neighbor_id) # we ran out of systems or fleets to check but did not meet requirements yet. if stats_meet_reqs(cur_stats, min_stats) and any( universe.getFleet(fid).shipIDs for fid in fleet_list): return fleet_list else: return []
def assign_military_fleets_to_systems(use_fleet_id_list=None, allocations=None, round=1): # assign military fleets to military theater systems global _military_allocations universe = fo.getUniverse() if allocations is None: allocations = [] doing_main = (use_fleet_id_list is None) aistate = get_aistate() if doing_main: aistate.misc['ReassignedFleetMissions'] = [] base_defense_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.ORBITAL_DEFENSE) unassigned_base_defense_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types(base_defense_ids) for fleet_id in unassigned_base_defense_ids: fleet = universe.getFleet(fleet_id) if not fleet: continue sys_id = fleet.systemID target = TargetSystem(sys_id) fleet_mission = aistate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() mission_type = MissionType.ORBITAL_DEFENSE fleet_mission.set_target(mission_type, target) all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) if not all_military_fleet_ids: _military_allocations = [] return avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids) these_allocations = _military_allocations debug("==================================================") debug("Assigning military fleets") debug("---------------------------------") else: avail_mil_fleet_ids = list(use_fleet_id_list) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids) these_allocations = allocations # send_for_repair(mil_needing_repair_ids) #currently, let get taken care of by AIFleetMission.generate_fleet_orders() # get systems to defend avail_mil_fleet_ids = set(avail_mil_fleet_ids) for sys_id, alloc, minalloc, rvp, takeAny in these_allocations: if not doing_main and not avail_mil_fleet_ids: break debug("Allocating for: %s", TargetSystem(sys_id)) found_fleets = [] found_stats = {} ensure_return = sys_id not in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs) these_fleets = FleetUtilsAI.get_fleets_for_mission( target_stats={'rating': alloc, 'ratingVsPlanets': rvp, 'target_system': TargetSystem(sys_id)}, min_stats={'rating': minalloc, 'ratingVsPlanets': rvp, 'target_system': TargetSystem(sys_id)}, cur_stats=found_stats, starting_system=sys_id, fleet_pool_set=avail_mil_fleet_ids, fleet_list=found_fleets, ensure_return=ensure_return) if not these_fleets: debug("Could not allocate any fleets.") if not found_fleets or not (FleetUtilsAI.stats_meet_reqs(found_stats, {'rating': minalloc}) or takeAny): if doing_main: if _verbose_mil_reporting: debug("NO available/suitable military allocation for system %d ( %s ) " "-- requested allocation %8d, found available rating %8d in fleets %s" % (sys_id, universe.getSystem(sys_id).name, minalloc, found_stats.get('rating', 0), found_fleets)) avail_mil_fleet_ids.update(found_fleets) continue else: these_fleets = found_fleets else: debug("Assigning fleets %s to target %s", these_fleets, TargetSystem(sys_id)) if doing_main and _verbose_mil_reporting: debug("FULL+ military allocation for system %d ( %s )" " -- requested allocation %8d, got %8d with fleets %s" % (sys_id, universe.getSystem(sys_id).name, alloc, found_stats.get('rating', 0), these_fleets)) target = TargetSystem(sys_id) for fleet_id in these_fleets: fo.issueAggressionOrder(fleet_id, True) fleet_mission = aistate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() if sys_id in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs): mission_type = MissionType.SECURE elif state.get_empire_planets_by_system(sys_id): mission_type = MissionType.PROTECT_REGION else: mission_type = MissionType.MILITARY fleet_mission.set_target(mission_type, target) fleet_mission.generate_fleet_orders() if not doing_main: aistate.misc.setdefault('ReassignedFleetMissions', []).append(fleet_mission) if doing_main: debug("---------------------------------") last_round = 3 last_round_name = "LastRound" if round <= last_round: # check if any fleets remain unassigned all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) allocations = [] round += 1 thisround = "Extras Remaining Round %d" % round if round < last_round else last_round_name if avail_mil_fleet_ids: debug("Round %s - still have available military fleets: %s", thisround, avail_mil_fleet_ids) allocations = get_military_fleets(mil_fleets_ids=avail_mil_fleet_ids, try_reset=False, thisround=thisround) if allocations: assign_military_fleets_to_systems(use_fleet_id_list=avail_mil_fleet_ids, allocations=allocations, round=round) else: # assign remaining fleets to nearest systems to protect. all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) def system_score(_fid, _sys_id): """Helper function to rank systems by priority""" jump_distance = universe.jumpDistance(_fid, _sys_id) if get_system_local_threat(_sys_id): weight = 10 elif get_system_neighbor_threat(_sys_id): weight = 3 elif get_system_jump2_threat(_sys_id): weight = 1 else: weight = 1 / max(.5, float(state.get_distance_to_enemy_supply(_sys_id)))**1.25 return float(weight) / (jump_distance+1) for fid in avail_mil_fleet_ids: fleet = universe.getFleet(fid) FleetUtilsAI.get_fleet_system(fleet) systems = state.get_empire_planets_by_system().keys() if not systems: continue sys_id = max(systems, key=lambda x: system_score(fid, x)) debug("Assigning leftover %s to system %d " "- nothing better to do.", fleet, sys_id) fleet_mission = aistate.get_fleet_mission(fid) fleet_mission.clear_fleet_orders() target_system = TargetSystem(sys_id) fleet_mission.set_target(MissionType.PROTECT_REGION, target_system) fleet_mission.generate_fleet_orders()