def get_safe_path_leg_to_dest(fleet_id, start_id, dest_id): start_targ = universe_object.System(start_id) dest_targ = universe_object.System(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] start_info = PlanetUtilsAI.sys_name_ids([start_id]) dest_info = PlanetUtilsAI.sys_name_ids([dest_id]) path_info = [PlanetUtilsAI.sys_name_ids([sys_id]) for sys_id in path_ids] print "Fleet %d requested safe path leg from %s to %s, found path %s" % (fleet_id, ppstring(start_info), ppstring(dest_info), ppstring(path_info)) return path_ids[0]
def get_safe_path_leg_to_dest(fleet_id, start_id, dest_id): start_targ = universe_object.System(start_id) dest_targ = universe_object.System(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() print "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 can_travel_to_system(fleet_id, from_system_target, to_system_target, ensure_return=False): """ Return list systems to be visited. :param fleet_id: :param fleet_id: int :param from_system_target: :type from_system_target: universe_object.System :param to_system_target: :type to_system_target: universe_object.System :param ensure_return: :type ensure_return: bool :return: :rtype: list """ empire = fo.getEmpire() empire_id = empire.empireID fleet_supplyable_system_ids = set(empire.fleetSupplyableSystemIDs) # get current fuel and max fuel universe = fo.getUniverse() fleet = universe.getFleet(fleet_id) fuel = int(fleet.fuel) if fuel < 1.0 or from_system_target.id == to_system_target.id: return [] if foAI.foAIstate.aggression <= fo.aggression.typical or True: # TODO: sort out if shortestPath leaves off some intermediate destinations path_func = universe.leastJumpsPath else: path_func = universe.shortestPath start_sys_id = from_system_target.id target_sys_id = to_system_target.id if start_sys_id != -1 and target_sys_id != -1: short_path = list(path_func(start_sys_id, target_sys_id, empire_id)) else: short_path = [] legs = zip(short_path[:-1], short_path[1:]) # suppliedStops = [ sid for sid in short_path if sid in fleet_supplyable_system_ids ] # unsupplied_stops = [sid for sid in short_path if sid not in suppliedStops ] unsupplied_stops = [sys_b for sys_a, sys_b in legs if ((sys_a not in fleet_supplyable_system_ids) and (sys_b not in fleet_supplyable_system_ids))] # print "getting path from %s to %s "%(ppstring(PlanetUtilsAI.sys_name_ids([ start_sys_id ])), ppstring(PlanetUtilsAI.sys_name_ids([ target_sys_id ])) ), # print " ::: found initial path %s having suppliedStops %s and unsupplied_stops %s ; tot fuel available is %.1f"%( ppstring(PlanetUtilsAI.sys_name_ids( short_path[:])), suppliedStops, unsupplied_stops, fuel) if False: if target_sys_id in fleet_supplyable_system_ids: print "target has FleetSupply" elif target_sys_id in ColonisationAI.annexable_ring1: print "target in Ring 1" elif target_sys_id in ColonisationAI.annexable_ring2: print "target in Ring 2, has enough aggression is ", foAI.foAIstate.aggression >= fo.aggression.typical elif target_sys_id in ColonisationAI.annexable_ring3: print "target in Ring 2, has enough aggression is ", foAI.foAIstate.aggression >= fo.aggression.aggressive if (not unsupplied_stops or not ensure_return or target_sys_id in fleet_supplyable_system_ids and len(unsupplied_stops) <= fuel or target_sys_id in ColonisationAI.annexable_ring1 and len(unsupplied_stops) < fuel or foAI.foAIstate.aggression >= fo.aggression.typical and target_sys_id in ColonisationAI.annexable_ring2 and len(unsupplied_stops) < fuel - 1 or foAI.foAIstate.aggression >= fo.aggression.aggressive and target_sys_id in ColonisationAI.annexable_ring3 and len(unsupplied_stops) < fuel - 2): return [universe_object.System(sid) for sid in short_path] else: # print " getting path from 'can_travel_to_system_and_return_to_resupply' ", return can_travel_to_system_and_return_to_resupply(fleet_id, from_system_target, to_system_target)
def get_nearest_supplied_system(start_system_id): """ 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 universe_object.System(start_system_id) else: min_jumps = 9999 # infinity supply_system_id = -1 for system_id in fleet_supplyable_system_ids: if start_system_id != -1 and system_id != -1: 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 universe_object.System(supply_system_id)
def can_travel_to_system(fleet_id, start, target, ensure_return=False): """ Return list systems to be visited. :param fleet_id: :type fleet_id: int :param start: :type start: universe_object.System :param target: :type target: universe_object.System :param ensure_return: :type ensure_return: bool :return: :rtype: list """ if start == target: return [universe_object.System(start.id)] debug("Requesting path for fleet %s from %s to %s" % (fo.getUniverse().getFleet(fleet_id), start, target)) target_distance_from_supply = -min(state.get_system_supply(target.id), 0) # low-aggression AIs may not travel far from supply if not foAI.foAIstate.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 [universe_object.System(sys_id) for sys_id in path_info.path]
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': universe_object.System(sys_id), 'move_path_func': can_travel_to_system } 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): print "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 = universe_object.Planet(planet_id) print "assigning invasion fleets %s to target %s" % (these_fleets, target) for fleetID in these_fleets: fleet_mission = foAI.foAIstate.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_target, current_sys_id): """ Return fleet_orders.OrderRepair for fleet to proceed system with drydock. :param fleet_target: fleet that need to be repaired :type fleet_target: universe_object.Fleet # TODO check if we can remove this id, because fleet already have it. :param current_sys_id: current system id :type current_sys_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 supplied system drydock_sys_id = get_best_drydock_system_id(current_sys_id, fleet_target.id) if drydock_sys_id is None: return None print "ordering fleet %d to %s for repair" % (fleet_target.id, ppstring(PlanetUtilsAI.sys_name_ids([drydock_sys_id]))) # create resupply AIFleetOrder return fleet_orders.OrderRepair(fleet_target, universe_object.System(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: universe_object.Fleet # 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 print "Ordering fleet %s to %s for repair" % ( fleet, fo.getUniverse().getSystem(drydock_sys_id)) return fleet_orders.OrderRepair(fleet, universe_object.System(drydock_sys_id))
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) if doing_main: foAI.foAIstate.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 = universe_object.System(sys_id) fleet_mission = foAI.foAIstate.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 print "==================================================" print "Assigning military fleets" print "---------------------------------" 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 = {} these_fleets = FleetUtilsAI.get_fleets_for_mission({'rating': alloc, 'ratingVsPlanets': rvp}, {'rating': minalloc, 'ratingVsPlanets': rvp}, found_stats, starting_system=sys_id, fleet_pool_set=avail_mil_fleet_ids, fleet_list=found_fleets) 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: print "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: print "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 = universe_object.System(sys_id) for fleet_id in these_fleets: fo.issueAggressionOrder(fleet_id, True) fleet_mission = foAI.foAIstate.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: foAI.foAIstate.misc.setdefault('ReassignedFleetMissions', []).append(fleet_mission) if doing_main: print "---------------------------------" 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: print "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 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) print "Exploration system considering following system-distance pairs:\n %s" % ("\n ".join("%3d: %5.1f" % info for info 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() print "Explorable system IDs: %s" % explore_list print "Already targeted: %s" % already_covered needs_vis = foAI.foAIstate.misc.setdefault('needs_vis', []) check_list = foAI.foAIstate.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 [(foAI.foAIstate.needsEmergencyExploration, "foAI.foAIstate.needsEmergencyExploration"), (needs_vis, "needs_vis"), (explore_list, "explore_list")]: if INVALID_ID in sys_list: print_error("INVALID_ID found in " + name) needs_coverage = [sys_id for sys_id in check_list if sys_id not in already_covered and sys_id != INVALID_ID] # emergency coverage can be due to invasion detection trouble, etc. print "Needs coverage: %s" % needs_coverage print "Available scouts & AIstate locs: %s" % [(x, foAI.foAIstate.fleetStatus.get(x, {}).get('sysID', INVALID_ID)) for x in available_scouts] print "Available scouts & universe locs: %s" % [(x, universe.getFleet(x).systemID) for x in available_scouts] if not needs_coverage or not available_scouts: return available_scouts = set(available_scouts) sent_list = [] while available_scouts and needs_coverage: this_sys_id = needs_coverage.pop(0) sys_status = foAI.foAIstate.systemStatus.setdefault(this_sys_id, {}) if this_sys_id not in explore_list: # doesn't necessarily need direct visit if universe.getVisibility(this_sys_id, fo.empireID()) >= fo.visibility.partial: # already got visibility; remove from visit lists and skip if this_sys_id in needs_vis: del needs_vis[needs_vis.index(this_sys_id)] if this_sys_id in foAI.foAIstate.needsEmergencyExploration: del foAI.foAIstate.needsEmergencyExploration[ foAI.foAIstate.needsEmergencyExploration.index(this_sys_id)] print "system id %d already currently visible; skipping exploration" % this_sys_id continue # TODO: if blocked byu monster, try to find nearby system from which to see this system if not foAI.foAIstate.character.may_explore_system(sys_status.setdefault('monsterThreat', 0)) or (fo.currentTurn() < 20 and foAI.foAIstate.systemStatus[this_sys_id]['monsterThreat'] > 200): print "Skipping exploration of system %d due to Big Monster, threat %d" % (this_sys_id, foAI.foAIstate.systemStatus[this_sys_id]['monsterThreat']) continue this_fleet_list = FleetUtilsAI.get_fleets_for_mission(target_stats={}, min_stats={}, cur_stats={}, starting_system=this_sys_id, fleet_pool_set=available_scouts, fleet_list=[]) if not this_fleet_list: print "Seem to have run out of scouts while trying to cover sys_id %d" % this_sys_id break # must have ran out of scouts fleet_id = this_fleet_list[0] fleet_mission = foAI.foAIstate.get_fleet_mission(fleet_id) target = universe_object.System(this_sys_id) if len(MoveUtilsAI.can_travel_to_system_and_return_to_resupply(fleet_id, fleet_mission.get_location_target(), target)) > 0: fleet_mission.set_target(MissionType.EXPLORATION, target) sent_list.append(this_sys_id) else: # system too far out, skip it, but can add scout back to available pool print "sys_id %d too far out for fleet ( ID %d ) to reach" % (this_sys_id, fleet_id) available_scouts.update(this_fleet_list) print "Sent scouting fleets to sysIDs : %s" % sent_list return # pylint: disable=pointless-string-statement """
def __find_path_with_fuel_to_system_with_possible_return(from_system_target, to_system_target, result_system_targets, fleet_supplyable_system_ids, max_fuel, fuel, supply_system_target): """ Return systems required to visit with fuel to nearest supplied system. :param from_system_target: :type from_system_target: universe_object.System :param to_system_target: :type to_system_target: universe_object.System :param result_system_targets: :type result_system_targets: list :param fleet_supplyable_system_ids: :type fleet_supplyable_system_ids: list :param max_fuel: :type max_fuel: int :param fuel: :type fuel: int :param supply_system_target: :type supply_system_target: universe_object.System :return: :rtype list: """ empire_id = fo.empireID() result = True # try to find if there is possible path to wanted system from system new_targets = result_system_targets[:] if from_system_target and to_system_target and supply_system_target: universe = fo.getUniverse() if from_system_target.id != -1 and to_system_target.id != -1: least_jumps_path = universe.leastJumpsPath(from_system_target.id, to_system_target.id, empire_id) else: least_jumps_path = [] result = False from_system_id = from_system_target.id for system_id in least_jumps_path: if from_system_id != system_id: if from_system_id in fleet_supplyable_system_ids: # from supplied system fleet can travel without fuel consumption and also in this system refuels fuel = max_fuel else: fuel -= 1 # leastJumpPath can differ from shortestPath # TODO: use Graph Theory to optimize if True or (system_id != to_system_target.id and system_id in fleet_supplyable_system_ids): # TODO: restructure new_targets.append(universe_object.System(system_id)) if fuel < 0: result = False from_system_id = system_id else: result = False # if there is path to wanted system, then also if there is path back to supplyable system if result: # jump from A to B means least_jumps_path=[A,B], but min_jumps=1 min_jumps = len(universe.leastJumpsPath(to_system_target.id, supply_system_target.id, empire_id)) - 1 if min_jumps > fuel: # print "fleetID:" + str(fleetID) + " fuel:" + str(fuel) + " required: " + str(min_jumps) result = False # else: # resultSystemAITargets.append(toSystemAITarget) if not result: return [] return new_targets
def _info_string(path_info): sequence_string = ", ".join( [str(universe_object.System(sys_id)) for sys_id in path_info.path]) return "dist %.1f, path %s" % (path_info.distance, sequence_string)
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) print "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() print "Explorable system IDs: %s" % explore_list print "Already targeted: %s" % already_covered needs_vis = foAI.foAIstate.misc.setdefault('needs_vis', []) check_list = foAI.foAIstate.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 [(foAI.foAIstate.needsEmergencyExploration, "foAI.foAIstate.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. print "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 ] print "Needs coverage: %s" % needs_coverage print "Available scouts & AIstate locs: %s" % [ (x, foAI.foAIstate.fleetStatus.get(x, {}).get('sysID', INVALID_ID)) for x in available_scouts ] print "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 foAI.foAIstate.needsEmergencyExploration: del foAI.foAIstate.needsEmergencyExploration[ foAI.foAIstate.needsEmergencyExploration.index(sys_id)] print "system id %d already currently visible; skipping exploration" % sys_id needs_coverage.remove(sys_id) continue # skip systems threatened by monsters sys_status = foAI.foAIstate.systemStatus.setdefault(sys_id, {}) if (not foAI.foAIstate.character.may_explore_system( sys_status.setdefault('monsterThreat', 0)) or (fo.currentTurn() < 20 and foAI.foAIstate.systemStatus[sys_id]['monsterThreat'] > 0)): print "Skipping exploration of system %d due to Big Monster, threat %d" % ( sys_id, foAI.foAIstate.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 = foAI.foAIstate.get_fleet_mission(fleet_id) start = fleet_mission.get_location_target() for sys_id in needs_coverage: target = universe_object.System(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 = foAI.foAIstate.get_fleet_mission(fleet_id) target = universe_object.System(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_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 #explorable_system_ids = foAI.foAIstate.get_explorable_systems(AIExplorableSystemType.EXPLORABLE_SYSTEM_UNEXPLORED) explorable_system_ids = list(borderUnexploredSystemIDs) if not explorable_system_ids or (capital_sys_id == -1): return exp_systems_by_dist = sorted( map(lambda x: (universe.linearDistance(capital_sys_id, x), x), explorable_system_ids)) print "Exploration system considering following system-distance pairs:\n %s" % ( "[ " + ", ".join( ["%3d : %5.1f" % (sys, dist) for dist, sys 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() print "explorable sys IDs: %s" % explore_list print "already targeted: %s" % already_covered if 'needsEmergencyExploration' not in dir(foAI.foAIstate): foAI.foAIstate.needsEmergencyExploration = [] needs_vis = foAI.foAIstate.misc.setdefault('needs_vis', []) check_list = foAI.foAIstate.needsEmergencyExploration + needs_vis + explore_list needs_coverage = [ sys_id for sys_id in check_list if sys_id not in already_covered ] # emergency coverage cane be due to invasion detection trouble, etc. print "needs coverage: %s" % needs_coverage print "available scouts & AIstate locs: %s" % (map( lambda x: (x, foAI.foAIstate.fleetStatus.get(x, {}).get('sysID', -1)), available_scouts)) print "available scouts & universe locs: %s" % (map( lambda x: (x, universe.getFleet(x).systemID), available_scouts)) if not needs_coverage or not available_scouts: return available_scouts = set(available_scouts) sent_list = [] while (len(available_scouts) > 0) and (len(needs_coverage) > 0): this_sys_id = needs_coverage.pop(0) sys_status = foAI.foAIstate.systemStatus.setdefault(this_sys_id, {}) if this_sys_id not in explore_list: # doesn't necessarily need direct visit if universe.getVisibility(this_sys_id, fo.empireID()) >= fo.visibility.partial: # already got visibility; remove from visit lists and skip if this_sys_id in needs_vis: del needs_vis[needs_vis.index(this_sys_id)] if this_sys_id in foAI.foAIstate.needsEmergencyExploration: del foAI.foAIstate.needsEmergencyExploration[ foAI.foAIstate.needsEmergencyExploration.index( this_sys_id)] print "sys id %d already currently visible; skipping exploration" % this_sys_id continue # TODO: if blocked byu monster, try to find nearby sys from which to see this sys if (sys_status.setdefault('monsterThreat', 0) > 2000 * foAI.foAIstate.aggression ) or (fo.currentTurn() < 20 and foAI.foAIstate.systemStatus[this_sys_id]['monsterThreat'] > 200): print "Skipping exploration of system %d due to Big Monster, threat %d" % ( this_sys_id, foAI.foAIstate.systemStatus[this_sys_id]['monsterThreat']) continue found_fleets = [] this_fleet_list = FleetUtilsAI.get_fleets_for_mission( nships=1, target_stats={}, min_stats={}, cur_stats={}, species="", systems_to_check=[this_sys_id], systems_checked=[], fleet_pool_set=available_scouts, fleet_list=found_fleets, verbose=False) if not this_fleet_list: print "seem to have run out of scouts while trying to cover sys_id %d" % this_sys_id break # must have ran out of scouts fleet_id = this_fleet_list[0] fleet_mission = foAI.foAIstate.get_fleet_mission(fleet_id) target = universe_object.System(this_sys_id) if len( MoveUtilsAI.can_travel_to_system_and_return_to_resupply( fleet_id, fleet_mission.get_location_target(), target)) > 0: fleet_mission.add_target( AIFleetMissionType.FLEET_MISSION_EXPLORATION, target) sent_list.append(this_sys_id) else: # system too far out, skip it, but can add scout back to available pool print "sys_id %d too far out for fleet ( ID %d ) to reach" % ( this_sys_id, fleet_id) available_scouts.update(this_fleet_list) print "sent scouting fleets to sysIDs : %s" % sent_list return # pylint: disable=pointless-string-statement """
def get_fleets_for_mission(target_stats, min_stats, cur_stats, starting_system, fleet_pool_set, fleet_list, species=""): """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 :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 while systems_enqueued and fleet_pool_set: this_system_id = systems_enqueued.pop(0) this_system_obj = universe_object.System(this_system_id) systems_visited.append(this_system_id) accessible_fleets = foAI.foAIstate.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 len(list(fleet.shipIDs)) > 1: new_fleets = split_fleet(fleet_id) fleet_pool_set.update(new_fleets) fleets_here.extend(new_fleets) # check species for colonization missions if species: for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) if (ship and foAI.foAIstate.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 # providing move_path_func in this fashion rather than directly importing here, in order # to avoid a circular import problem if 'target_system' in target_stats and 'move_path_func' in target_stats: if not target_stats['move_path_func'](fleet_id, this_system_obj, target_stats['target_system']): continue # check if we need additional rating vs planets this_rating_vs_planets = 0 if 'ratingVsPlanets' in target_stats: this_rating_vs_planets = foAI.foAIstate.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 = foAI.foAIstate.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 foAI.foAIstate.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 get_best_drydock_system_id(start_system_id, fleet_id): """ 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 :type start_system_id: int :param fleet_id: fleet that needs path to drydock :type: int :return: most suitable system id where the fleet should be repaired. :rtype: int """ if start_system_id == INVALID_ID: warn("get_best_drydock_system_id passed bad system id.") return None if fleet_id == INVALID_ID: warn("get_best_drydock_system_id passed bad fleet id.") return None universe = fo.getUniverse() start_system = universe_object.System(start_system_id) drydock_system_ids = set() for sys_id, pids in state.get_empire_drydocks().iteritems(): if sys_id == INVALID_ID: warn("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]) fleet_rating = foAI.foAIstate.get_rating(fleet_id) for _, dock_sys_id in sys_distances: dock_system = universe_object.System(dock_sys_id) path = can_travel_to_system(fleet_id, start_system, dock_system) path_rating = sum([ foAI.foAIstate.systemStatus[path_sys.id]['totalThreat'] for path_sys in path ]) SAFETY_MARGIN = 10 if SAFETY_MARGIN * path_rating <= fleet_rating: print( "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 print( "No safe drydock recommendation from %s for fleet %s with fleet rating %.1f." % (start_system, universe.getFleet(fleet_id), fleet_rating)) return None