def getRequiredSystemAITargets(self): "returns all system AITargets required to visit in this object" # TODO: add parameter turn result = [] if AITargetType.TARGET_SYSTEM == self.getAITargetType(): result.append(self) elif AITargetType.TARGET_PLANET == self.getAITargetType(): universe = fo.getUniverse() planet = universe.getPlanet(self.getTargetID()) aiTarget = AITarget(AITargetType.TARGET_SYSTEM, planet.systemID) result.append(aiTarget) elif AITargetType.TARGET_FLEET == self.getAITargetType(): # Fleet systemID is where is fleet going. # If fleet is going nowhere, then it is location of fleet universe = fo.getUniverse() fleet = universe.getFleet(self.getTargetID()) systemID = fleet.nextSystemID if (systemID == -1): systemID = fleet.systemID aiTarget = AITarget(AITargetType.TARGET_SYSTEM, systemID) result.append(aiTarget) return result
def dump_universe(): """Dump the universe but not more than once per turn.""" cur_turn = fo.currentTurn() if (not hasattr(dump_universe, "last_dump") or dump_universe.last_dump < cur_turn): dump_universe.last_dump = cur_turn fo.getUniverse().dump() # goes to debug logger
def avail_mil_needing_repair(mil_fleet_ids, split_ships=False, on_mission=False, repair_limit=0.70): """Returns tuple of lists: (ids_needing_repair, ids_not).""" fleet_buckets = [[], []] universe = fo.getUniverse() cutoff = [repair_limit, 0.25][on_mission] aistate = get_aistate() for fleet_id in mil_fleet_ids: fleet = universe.getFleet(fleet_id) ship_buckets = [[], []] ships_cur_health = [0, 0] ships_max_health = [0, 0] for ship_id in fleet.shipIDs: this_ship = universe.getShip(ship_id) cur_struc = this_ship.initialMeterValue(fo.meterType.structure) max_struc = this_ship.initialMeterValue(fo.meterType.maxStructure) ship_ok = cur_struc >= cutoff * max_struc ship_buckets[ship_ok].append(ship_id) ships_cur_health[ship_ok] += cur_struc ships_max_health[ship_ok] += max_struc this_sys_id = fleet.systemID if fleet.nextSystemID == INVALID_ID else fleet.nextSystemID fleet_ok = (sum(ships_cur_health) >= cutoff * sum(ships_max_health)) local_status = aistate.systemStatus.get(this_sys_id, {}) my_local_rating = combine_ratings(local_status.get('mydefenses', {}).get('overall', 0), local_status.get('myFleetRating', 0)) my_local_rating_vs_planets = local_status.get('myFleetRatingVsPlanets', 0) combat_trigger = bool(local_status.get('fleetThreat', 0) or local_status.get('monsterThreat', 0)) if not combat_trigger and local_status.get('planetThreat', 0): universe = fo.getUniverse() system = universe.getSystem(this_sys_id) for planet_id in system.planetIDs: planet = universe.getPlanet(planet_id) if planet.ownedBy(fo.empireID()): # TODO: also exclude at-peace planets continue if planet.unowned and not EspionageAI.colony_detectable_by_empire(planet_id, empire=fo.empireID()): continue if sum([planet.currentMeterValue(meter_type) for meter_type in [fo.meterType.defense, fo.meterType.shield, fo.meterType.construction]]): combat_trigger = True break needed_here = combat_trigger and local_status.get('totalThreat', 0) > 0 # TODO: assess if remaining other forces are sufficient safely_needed = needed_here and my_local_rating > local_status.get('totalThreat', 0) and my_local_rating_vs_planets > local_status.get('planetThreat', 0) # TODO: improve both assessment prongs if not fleet_ok: if safely_needed: print "Fleet %d at %s needs repair but deemed safely needed to remain for defense" % (fleet_id, universe.getSystem(fleet.systemID)) else: if needed_here: print "Fleet %d at %s needed present for combat, but is damaged and deemed unsafe to remain." % (fleet_id, universe.getSystem(fleet.systemID)) print "\t my_local_rating: %.1f ; threat: %.1f" % (my_local_rating, local_status.get('totalThreat', 0)) print "Selecting fleet %d at %s for repair" % (fleet_id, universe.getSystem(fleet.systemID)) fleet_buckets[fleet_ok or bool(safely_needed)].append(fleet_id) return fleet_buckets
def startNewGame(aggression=fo.aggression.aggressive): # pylint: disable=invalid-name """Called by client when a new game is started (but not when a game is loaded). Should clear any pre-existing state and set up whatever is needed for AI to generate orders.""" empire = fo.getEmpire() if empire.eliminated: print "This empire has been eliminated. Ignoring new game start message." return turn_timer.start("Server Processing") print "New game started, AI Aggression level %d" % aggression # initialize AIstate global foAIstate foAIstate = AIstate.AIstate(aggression=aggression) foAIstate.session_start_cleanup() print "Initialized foAIstate class" planet_id = PlanetUtilsAI.get_capital() universe = fo.getUniverse() if planet_id is not None and planet_id != -1: planet = universe.getPlanet(planet_id) new_name = " ".join([random.choice(_capitals.get(aggression, []) or [" "]).strip(), planet.name]) print "Capitol City Names are: ", _capitals print "This Capitol New name is ", new_name res = fo.issueRenameOrder(planet_id, new_name) print "Capitol Rename attempt result: %d; planet now named %s" % (res, planet.name) diplomatic_corp_configs = {fo.aggression.beginner: DiplomaticCorp.BeginnerDiplomaticCorp, fo.aggression.maniacal: DiplomaticCorp.ManiacalDiplomaticCorp} global diplomatic_corp diplomatic_corp = diplomatic_corp_configs.get(aggression, DiplomaticCorp.DiplomaticCorp)()
def merge_fleet_a_into_b(fleet_a_id, fleet_b_id, leave_rating=0, need_rating=0, context=""): universe = fo.getUniverse() fleet_a = universe.getFleet(fleet_a_id) fleet_b = universe.getFleet(fleet_b_id) if not fleet_a or not fleet_b: return 0 system_id = fleet_a.systemID if fleet_b.systemID != system_id: return 0 remaining_rating = CombatRatingsAI.get_fleet_rating(fleet_a_id) transferred_rating = 0 for ship_id in fleet_a.shipIDs: this_ship = universe.getShip(ship_id) if not this_ship: continue this_rating = CombatRatingsAI.ShipCombatStats(ship_id).get_rating() remaining_rating = CombatRatingsAI.rating_needed(remaining_rating, this_rating) if remaining_rating < leave_rating: # merging this would leave old fleet under minimum rating, try other ships. continue transferred = fo.issueFleetTransferOrder(ship_id, fleet_b_id) if transferred: transferred_rating = CombatRatingsAI.combine_ratings(transferred_rating, this_rating) else: print " *** transfer of ship %4d, formerly of fleet %4d, into fleet %4d failed; %s" % ( ship_id, fleet_a_id, fleet_b_id, (" context is %s" % context) if context else "") if need_rating != 0 and need_rating <= transferred_rating: break fleet_a = universe.getFleet(fleet_a_id) if not fleet_a or fleet_a.empty or fleet_a_id in universe.destroyedObjectIDs(fo.empireID()): foAI.foAIstate.delete_fleet_info(fleet_a_id) foAI.foAIstate.update_fleet_rating(fleet_b_id)
def assign_invasion_values(planet_ids, mission_type, fleet_suppliable_planet_ids, empire): """Creates a dictionary that takes planet_ids as key and their invasion score as value.""" planet_values = {} neighbor_values = {} neighbor_val_ratio = .95 universe = fo.getUniverse() secure_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([EnumsAI.AIFleetMissionType.FLEET_MISSION_SECURE]) for pid in planet_ids: planet_values[pid] = neighbor_values.setdefault(pid, evaluate_invasion_planet(pid, empire, secure_missions)) print "planet %d, values %s" % (pid, planet_values[pid]) planet = universe.getPlanet(pid) species_name = (planet and planet.speciesName) or "" species = fo.getSpecies(species_name) if species and species.canProduceShips: system = universe.getSystem(planet.systemID) if not system: continue planet_industries = {} for pid2 in system.planetIDs: planet2 = universe.getPlanet(pid2) species_name2 = (planet2 and planet2.speciesName) or "" species2 = fo.getSpecies(species_name2) if species2 and species2.canProduceShips: planet_industries[pid2] = planet2.currentMeterValue(fo.meterType.industry) + 0.1 # to prevent divide-by-zero industry_ratio = planet_industries[pid] / max(planet_industries.values()) for pid2 in system.planetIDs: if pid2 == pid: continue planet2 = universe.getPlanet(pid2) if planet2 and (planet2.owner != empire.empireID) and ((planet2.owner != -1) or (planet.currentMeterValue(fo.meterType.population) > 0)): # TODO check for allies planet_values[pid][0] += industry_ratio * neighbor_val_ratio * (neighbor_values.setdefault(pid2, evaluate_invasion_planet(pid2, empire, secure_missions))[0]) return planet_values
def get_current_and_max_structure(fleet): """Return a 2-tuple of the sums of structure and maxStructure meters of all ships in the fleet :param fleet: :type fleet: int | universe_object.Fleet | fo.Fleet :return: tuple of sums of structure and maxStructure meters of all ships in the fleet :rtype: (float, float) """ universe = fo.getUniverse() destroyed_ids = universe.destroyedObjectIDs(fo.empireID()) if isinstance(fleet, int): fleet = universe.getFleet(fleet) elif isinstance(fleet, Fleet): fleet = fleet.get_object() if not fleet: return (0.0, 0.0) ships_cur_health = 0 ships_max_health = 0 for ship_id in fleet.shipIDs: # Check if we have see this ship get destroyed in a different fleet since the last time we saw the subject fleet # this may be redundant with the new fleet assignment check made below, but for its limited scope it may be more # reliable, in that it does not rely on any particular handling of post-destruction stats if ship_id in destroyed_ids: continue this_ship = universe.getShip(ship_id) # check that the ship has not been seen in a new fleet since this current fleet was last observed if not (this_ship and this_ship.fleetID == fleet.id): continue ships_cur_health += this_ship.initialMeterValue(fo.meterType.structure) ships_max_health += this_ship.initialMeterValue(fo.meterType.maxStructure) return ships_cur_health, ships_max_health
def getAllPopulatedSystemIDs(planetIDs): "return list of all populated systemIDs" universe = fo.getUniverse() allPopulatedSystemIDs = [] popPlanets=getPopulatedPlanetIDs(planetIDs) return [universe.getPlanet(planetID).systemID for planetID in popPlanets ]
def calculate_estimated_time_of_arrival(fleet_id, target_system_id): universe = fo.getUniverse() fleet = universe.getFleet(fleet_id) if not fleet or not fleet.speed: return 99999 distance = universe.shortestPathDistance(fleet_id, target_system_id) return math.ceil(float(distance) / fleet.speed)
def cur_best_mil_ship_rating(include_designs=False): """Find the best military ship we have available in this turn and return its rating. :param include_designs: toggles if available designs are considered or only existing ships :return: float: rating of the best ship """ current_turn = fo.currentTurn() if current_turn in best_ship_rating_cache: best_rating = best_ship_rating_cache[current_turn] if include_designs: best_design_rating = ProductionAI.cur_best_military_design_rating() best_rating = max(best_rating, best_design_rating) return best_rating best_rating = 0.001 universe = fo.getUniverse() for fleet_id in FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY): fleet = universe.getFleet(fleet_id) for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) if ship: ship_info = [(ship.id, ship.designID, ship.speciesName)] ship_rating = foAI.foAIstate.rate_psuedo_fleet(ship_info=ship_info)['overall'] best_rating = max(best_rating, ship_rating) best_ship_rating_cache[current_turn] = best_rating if include_designs: best_design_rating = ProductionAI.cur_best_military_design_rating() best_rating = max(best_rating, best_design_rating) return max(best_rating, 0.001)
def identifyFleetsRoles(): "identify fleet roles" # assign roles to fleets universe = fo.getUniverse() for fleetID in universe.fleetIDs: foAIstate.addFleetRole(fleetID, FleetUtilsAI.assessFleetRole(fleetID))
def getCapital(): # if no current capital returns planet with biggest pop universe = fo.getUniverse() empire = fo.getEmpire() if empire == None: print "Danger Danger! FO can't find an empire for me!!!!" return None empireID = empire.empireID capitalID = empire.capitalID homeworld = universe.getPlanet(capitalID) if homeworld: if homeworld.owner==empireID: return capitalID else: print "Nominal Capitol %s does not appear to be owned by empire %d %s"%(homeworld.name, empireID, empire.name) #exploredSystemIDs = empire.exploredSystemIDs #exploredPlanetIDs = PlanetUtilsAI.getPlanetsInSystemsIDs(exploredSystemIDs) empireOwnedPlanetIDs = getOwnedPlanetsByEmpire(universe.planetIDs, empireID) peopledPlanets = getPopulatedPlanetIDs( empireOwnedPlanetIDs) if not peopledPlanets: if empireOwnedPlanetIDs: return empireOwnedPlanetIDs[0] else: return None popMap = [] for planetID in peopledPlanets: popMap.append( ( universe.getPlanet(planetID).currentMeterValue(fo.meterType.population) , planetID) ) popMap.sort() return popMap[-1][-1]
def set_planet_production_and_research_specials(focus_manager): """Set production and research specials. Sets production/research specials for known (COMPUTRONIUM, HONEYCOMB and CONC_CAMP) production/research specials. Remove planets from list of candidates using bake_future_focus.""" # TODO remove reliance on rules knowledge. Just scan for specials with production # and research bonuses and use what you find. Perhaps maintain a list # of know types of specials # TODO use "best" COMPUTRON planet instead of first found, where "best" means least industry loss, # least threatened, no foci change penalty etc. universe = fo.getUniverse() already_have_comp_moon = False for pid, info in focus_manager.raw_planet_info.items(): planet = info.planet if "COMPUTRONIUM_SPECIAL" in planet.specials and RESEARCH in planet.availableFoci and not already_have_comp_moon: if focus_manager.bake_future_focus(pid, RESEARCH): already_have_comp_moon = True print "%s focus of planet %s (%d) (with Computronium Moon) at Research Focus" % (["set", "left"][info.current_focus == RESEARCH], planet.name, pid) continue if "HONEYCOMB_SPECIAL" in planet.specials and INDUSTRY in planet.availableFoci: if focus_manager.bake_future_focus(pid, INDUSTRY): print "%s focus of planet %s (%d) (with Honeycomb) at Industry Focus" % (["set", "left"][info.current_focus == INDUSTRY], planet.name, pid) continue if ((([bld.buildingTypeName for bld in map(universe.getBuilding, planet.buildingIDs) if bld.buildingTypeName in ["BLD_CONC_CAMP", "BLD_CONC_CAMP_REMNANT"]]) or ([ccspec for ccspec in planet.specials if ccspec in ["CONC_CAMP_MASTER_SPECIAL", "CONC_CAMP_SLAVE_SPECIAL"]])) and INDUSTRY in planet.availableFoci): if focus_manager.bake_future_focus(pid, INDUSTRY): print "%s focus of planet %s (%d) (with Concentration Camps/Remnants) at Industry Focus" % (["set", "left"][info.current_focus == INDUSTRY], planet.name, pid) continue else: new_planet = universe.getPlanet(pid) print ("Error: Failed setting %s for Concentration Camp planet %s (%d) with species %s and current focus %s, but new planet copy shows %s" % (info.future_focus, planet.name, pid, planet.speciesName, planet.focus, new_planet.focus))
def clean(self): global invasionTargets "turn start AIstate cleanup" fleetsLostBySystem.clear() fleetsLostByID.clear() invasionTargets=[] ExplorationAI.graphFlags.clear() print "-------------------------------------------------" print "Border Exploration Update" print "-------------------------------------------------" for sysID in list(self.visBorderSystemIDs): ExplorationAI.followVisSystemConnections(sysID, self.origHomeSystemID) newlyExplored = ExplorationAI.updateExploredSystems() nametags=[] universe = fo.getUniverse() for sysID in newlyExplored: newsys = universe.getSystem(sysID) nametags.append( "ID:%4d -- %20s"%(sysID, (newsys and newsys.name) or"name unknown" ) )# an explored system *should* always be able to be gotten if newlyExplored: print "-------------------------------------------------" print "newly explored systems: \n"+"\n".join(nametags) print "-------------------------------------------------" # cleanup fleet roles #self.updateFleetLocs() self.__cleanFleetRoles() self.__cleanAIFleetMissions(FleetUtilsAI.getEmpireFleetIDs()) print "Fleets lost by system: %s"%fleetsLostBySystem self.updateSystemStatus() ExplorationAI.updateScoutFleets() #should do this after clearing dead fleets, currently should be already done here
def avail_mil_needing_repair(mil_fleet_ids, split_ships=False, on_mission=False, repair_limit=0.70): """Returns tuple of lists: (ids_needing_repair, ids_not).""" fleet_buckets = [[], []] universe = fo.getUniverse() cutoff = [repair_limit, 0.25][on_mission] for fleet_id in mil_fleet_ids: fleet = universe.getFleet(fleet_id) ship_buckets = [[], []] ships_cur_health = [0, 0] ships_max_health = [0, 0] for ship_id in fleet.shipIDs: this_ship = universe.getShip(ship_id) cur_struc = this_ship.currentMeterValue(fo.meterType.structure) max_struc = this_ship.currentMeterValue(fo.meterType.maxStructure) ship_ok = cur_struc >= cutoff * max_struc ship_buckets[ship_ok].append(ship_id) ships_cur_health[ship_ok] += cur_struc ships_max_health[ship_ok] += max_struc this_sys_id = (fleet.nextSystemID != -1 and fleet.nextSystemID) or fleet.systemID fleet_ok = (sum(ships_cur_health) >= cutoff * sum(ships_max_health)) local_status = foAI.foAIstate.systemStatus.get(this_sys_id, {}) my_local_rating = combine_ratings(local_status.get('mydefenses', {}).get('overall', 0), local_status.get('myFleetRating', 0)) needed_here = local_status.get('totalThreat', 0) > 0 # TODO: assess if remaining other forces are sufficient safely_needed = needed_here and my_local_rating > local_status.get('totalThreat', 0) # TODO: improve both assessment prongs if not fleet_ok: if safely_needed: print "Fleet %d at %s needs repair but deemed safely needed to remain for defense" % (fleet_id, ppstring(PlanetUtilsAI.sys_name_ids([fleet.systemID]))) else: if needed_here: print "Fleet %d at %s needed present for combat, but is damaged and deemed unsafe to remain." % (fleet_id, ppstring(PlanetUtilsAI.sys_name_ids([fleet.systemID]))) print "\t my_local_rating: %.1f ; threat: %.1f" % (my_local_rating, local_status.get('totalThreat', 0)) print "Selecting fleet %d at %s for repair" % (fleet_id, ppstring(PlanetUtilsAI.sys_name_ids([fleet.systemID]))) fleet_buckets[fleet_ok or safely_needed].append(fleet_id) return fleet_buckets
def _portion_of_fleet_needed_here(self): """Calculate the portion of the fleet needed in target system considering enemy forces.""" # TODO check rating against planets if assertion_fails(self.type in COMBAT_MISSION_TYPES, msg=str(self)): return 0 if assertion_fails(self.target and self.target.id != INVALID_ID, msg=str(self)): return 0 system_id = self.target.id aistate = get_aistate() local_defenses = MilitaryAI.get_my_defense_rating_in_system(system_id) potential_threat = CombatRatingsAI.combine_ratings( MilitaryAI.get_system_local_threat(system_id), MilitaryAI.get_system_neighbor_threat(system_id) ) universe = fo.getUniverse() system = universe.getSystem(system_id) # tally planetary defenses total_defense = total_shields = 0 for planet_id in system.planetIDs: planet = universe.getPlanet(planet_id) total_defense += planet.currentMeterValue(fo.meterType.defense) total_shields += planet.currentMeterValue(fo.meterType.shield) planetary_ratings = total_defense * (total_shields + total_defense) potential_threat += planetary_ratings # TODO: rewrite to return min rating vs planets as well # consider safety factor just once here rather than everywhere below safety_factor = aistate.character.military_safety_factor() potential_threat *= safety_factor fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id) return CombatRatingsAI.rating_needed(potential_threat, local_defenses) / float(fleet_rating)
def evaluateSystem(systemID, missionType, empireProvinceSystemIDs, otherTargetedSystemIDs, empire): "return the military value of a system" universe = fo.getUniverse() system = universe.getSystem(systemID) if (system == None): return 0 # give preference to home system then closest systems empireID = empire.empireID capitalID = PlanetUtilsAI.getCapital() homeworld = universe.getPlanet(capitalID) if homeworld: homeSystemID = homeworld.systemID evalSystemID = system.systemID leastJumpsPath = len(universe.leastJumpsPath(homeSystemID, evalSystemID, empireID)) distanceFactor = 1.001/(leastJumpsPath + 1) else: homeSystemID=-1 distanceFactor=0 if systemID == homeSystemID: return 10 elif systemID in empireProvinceSystemIDs: return 4 + distanceFactor elif systemID in otherTargetedSystemIDs: return 2 + distanceFactor else: return 1 + .25 * distanceFactor
def sendColonyShips(colonyFleetIDs, evaluatedPlanets, missionType): "sends a list of colony ships to a list of planet_value_pairs" fleetPool = colonyFleetIDs[:] potentialTargets = [ (pid, (score, specName) ) for (pid, (score, specName) ) in evaluatedPlanets if score > 30 ] print "colony/outpost ship matching -- fleets %s to planets %s"%( fleetPool, evaluatedPlanets) #for planetID_value_pair in evaluatedPlanets: fleetPool=set(fleetPool) universe=fo.getUniverse() while (len(fleetPool) > 0 ) and ( len(potentialTargets) >0): thisTarget = potentialTargets.pop(0) thisPlanetID=thisTarget[0] thisSysID = universe.getPlanet(thisPlanetID).systemID if (foAI.foAIstate.systemStatus.setdefault(thisSysID, {}).setdefault('monsterThreat', 0) > 2000) or (fo.currentTurn() <20 and foAI.foAIstate.systemStatus[thisSysID]['monsterThreat'] > 200): print "Skipping colonization of system %s due to Big Monster, threat %d"%(PlanetUtilsAI.sysNameIDs([thisSysID]), foAI.foAIstate.systemStatus[thisSysID]['monsterThreat']) continue thisSpec=thisTarget[1][1] foundFleets=[] thisFleetList = FleetUtilsAI.getFleetsForMission(nships=1, targetStats={}, minStats={}, curStats={}, species=thisSpec, systemsToCheck=[thisSysID], systemsChecked=[], fleetPoolSet = fleetPool, fleetList=foundFleets, verbose=False) if thisFleetList==[]: fleetPool.update(foundFleets)#just to be safe continue #must have no compatible colony/outpost ships fleetID = thisFleetList[0] aiTarget = AITarget.AITarget(AITargetType.TARGET_PLANET, thisPlanetID) aiFleetMission = foAI.foAIstate.getAIFleetMission(fleetID) aiFleetMission.addAITarget(missionType, aiTarget)
def __border_exploration_update(self): universe = fo.getUniverse() exploration_center = PlanetUtilsAI.get_capital_sys_id() # a bad state probably from an old savegame, or else empire has lost (or almost has) if exploration_center == INVALID_ID: exploration_center = self.__origin_home_system_id ExplorationAI.graph_flags.clear() if fo.currentTurn() < 50: print "-------------------------------------------------" print "Border Exploration Update (relative to %s)" % universe.getSystem(exploration_center) print "-------------------------------------------------" if self.visBorderSystemIDs == {INVALID_ID}: self.visBorderSystemIDs.clear() self.visBorderSystemIDs.add(exploration_center) for sys_id in list(self.visBorderSystemIDs): # This set is modified during iteration. if fo.currentTurn() < 50: print "Considering border system %s" % universe.getSystem(sys_id) ExplorationAI.follow_vis_system_connections(sys_id, exploration_center) newly_explored = ExplorationAI.update_explored_systems() nametags = [] for sys_id in newly_explored: newsys = universe.getSystem(sys_id) # an explored system *should* always be able to be gotten nametags.append("ID:%4d -- %-20s" % (sys_id, (newsys and newsys.name) or "name unknown")) if newly_explored: print "-------------------------------------------------" print "Newly explored systems:\n%s" % "\n".join(nametags) print "-------------------------------------------------"
def need_to_pause_movement(self, last_move_target_id, new_move_order): """ When a fleet has consecutive move orders, assesses whether something about the interim destination warrants forcing a stop (such as a military fleet choosing to engage with an enemy fleet about to enter the same system, or it may provide a good vantage point to view current status of next system in path). Assessments about whether the new destination is suitable to move to are (currently) separately made by OrderMove.can_issue_order() :param last_move_target_id: :type last_move_target_id: int :param new_move_order: :type new_move_order: OrderMove :rtype: bool """ fleet = self.fleet.get_object() # don't try skipping over more than one System if fleet.nextSystemID != last_move_target_id: return True universe = fo.getUniverse() current_dest_system = universe.getSystem(fleet.nextSystemID) if not current_dest_system: # shouldn't really happen, but just to be safe return True distance_to_next_system = ((fleet.x - current_dest_system.x)**2 + (fleet.y - current_dest_system.y)**2)**0.5 surplus_travel_distance = fleet.speed - distance_to_next_system # if need more than one turn to reach current destination, then don't add another jump yet if surplus_travel_distance < 0: return True # TODO: add assessments for other situations we'd prefer to pause, such as cited above re military fleets, and # for situations where high value fleets like colony fleets might deem it safest to stop and look around # before proceeding return False
def _need_repair(self, repair_limit=0.70): """Check if fleet needs to be repaired. If the fleet is already at a system where it can be repaired, stay there until fully repaired. Otherwise, repair if fleet health is below specified *repair_limit*. For military fleets, there is a special evaluation called, cf. *MilitaryAI.avail_mil_needing_repair()* :param repair_limit: percentage of health below which the fleet is sent to repair :type repair_limit: float :return: True if fleet needs repair :rtype: bool """ # TODO: More complex evaluation if fleet needs repair (consider self-repair, distance, threat, mission...) universe = fo.getUniverse() fleet_id = self.fleet.id # if we are already at a system where we can repair, make sure we use it... system = self.fleet.get_system() # TODO starlane obstruction is not considered in the next call nearest_dock = MoveUtilsAI.get_best_drydock_system_id(system.id, fleet_id) if nearest_dock == system.id: repair_limit = 0.99 # if combat fleet, use military repair check if foAI.foAIstate.get_fleet_role(fleet_id) in COMBAT_MISSION_TYPES: return fleet_id in MilitaryAI.avail_mil_needing_repair([fleet_id], on_mission=bool(self.orders), repair_limit=repair_limit)[0] # TODO: Allow to split fleet to send only damaged ships to repair fleet = universe.getFleet(fleet_id) ships_cur_health = 0 ships_max_health = 0 for ship_id in fleet.shipIDs: this_ship = universe.getShip(ship_id) ships_cur_health += this_ship.currentMeterValue(fo.meterType.structure) ships_max_health += this_ship.currentMeterValue(fo.meterType.maxStructure) return ships_cur_health < repair_limit * ships_max_health
def __split_new_fleets(self): """Split any new fleets. This function is supposed to be called once at the beginning of the turn. Splitting the auto generated fleets at game start or those created by recently built ships allows the AI to assign correct roles to all ships. """ # TODO: check length of fleets for losses or do in AIstate.__cleanRoles universe = fo.getUniverse() known_fleets = self.get_fleet_roles_map() self.newlySplitFleets.clear() fleets_to_split = [fleet_id for fleet_id in FleetUtilsAI.get_empire_fleet_ids() if fleet_id not in known_fleets] if fleets_to_split: print "Trying to split %d new fleets" % len(fleets_to_split) for fleet_id in fleets_to_split: fleet = universe.getFleet(fleet_id) if not fleet: warn("Trying to split fleet %d but seemingly does not exist" % fleet_id) continue fleet_len = len(fleet.shipIDs) if fleet_len == 1: continue new_fleets = FleetUtilsAI.split_fleet(fleet_id) print "Split fleet %d with %d ships into %d new fleets:" % (fleet_id, fleet_len, len(new_fleets))
def canTravelToSystemAndReturnToResupply(fleetID, fromSystemAITarget, toSystemAITarget, empireID, verbose=False): "check if fleet can travel from starting system to wanted system" systemAITargets = [] if not fromSystemAITarget.getTargetID() == toSystemAITarget.getTargetID: # get supplyable systems empire = fo.getEmpire() fleetSupplyableSystemIDs = empire.fleetSupplyableSystemIDs # get current fuel and max fuel universe = fo.getUniverse() fleet = universe.getFleet(fleetID) maxFuel = int(fleet.maxFuel) fuel = int(fleet.fuel) if verbose: print " fleet ID %d has %.1f fuel to get from %s to %s"%(fleetID, fuel, fromSystemAITarget, toSystemAITarget ) # try to find path without going resupply first supplySystemAITarget = getNearestSuppliedSystem(toSystemAITarget.getTargetID(), empireID) systemAITargets = __findPathWithFuelToSystemWithPossibleReturn(fromSystemAITarget, toSystemAITarget, empireID, systemAITargets, fleetSupplyableSystemIDs, maxFuel, fuel, supplySystemAITarget) # resupply in system first is required to find path if not(fromSystemAITarget.getTargetID() in fleetSupplyableSystemIDs) and len(systemAITargets) == 0: # add supply system to visit fromSystemAITarget = getNearestSuppliedSystem(fromSystemAITarget.getTargetID(), empireID) systemAITargets.append(fromSystemAITarget) # find path from supplied system to wanted system systemAITargets = __findPathWithFuelToSystemWithPossibleReturn(fromSystemAITarget, toSystemAITarget, empireID, systemAITargets, fleetSupplyableSystemIDs, maxFuel, maxFuel, supplySystemAITarget) return systemAITargets
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: target.TargetSystem :param target: :type target: target.TargetSystem :param ensure_return: :type ensure_return: bool :return: :rtype: list """ 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(state.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 __update_empire_standard_enemy(self): """Update the empire's standard enemy. The standard enemy is the enemy that is most often seen. """ # TODO: If no current information available, rate against own fighters universe = fo.getUniverse() empire_id = fo.empireID() # assess enemy fleets that may have been momentarily visible (start with dummy entries) dummy_stats = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) cur_e_fighters = Counter() # actual visible enemies old_e_fighters = Counter({dummy_stats: 0}) # destroyed enemies TODO: consider seen but out of sight enemies for fleet_id in universe.fleetIDs: fleet = universe.getFleet(fleet_id) if (not fleet or fleet.empty or fleet.ownedBy(empire_id) or fleet.unowned or not (fleet.hasArmedShips or fleet.hasFighterShips)): continue # track old/dead enemy fighters for rating assessments in case not enough current info ship_stats = CombatRatingsAI.FleetCombatStats(fleet_id).get_ship_stats(hashable=True) dead_fleet = fleet_id in universe.destroyedObjectIDs(empire_id) e_f_dict = old_e_fighters if dead_fleet else cur_e_fighters for stats in ship_stats: # log only ships that are armed if stats[0]: e_f_dict[stats] += 1 e_f_dict = cur_e_fighters or old_e_fighters self.__empire_standard_enemy = sorted([(v, k) for k, v in e_f_dict.items()])[-1][1] self.empire_standard_enemy_rating = self.get_standard_enemy().get_rating()
def bake_future_focus(self, pid, focus, update=True): """Set the focus and moves it from the raw list to the baked list of planets. pid -- pid focus -- future focus to use update -- If update is True then the meters of the raw planets will be updated. If the planet's change of focus will have a global effect (growth, production or research special), then update should be True. Return success or failure """ info = self.raw_planet_info.get(pid) success = bool(info is not None and (info.current_focus == focus or (focus in info.planet.availableFoci and fo.issueChangeFocusOrder(pid, focus)))) if success: if update and info.current_focus != focus: universe = fo.getUniverse() universe.updateMeterEstimates(self.raw_planet_info.keys()) industry_target = info.planet.currentMeterValue(fo.meterType.targetIndustry) research_target = info.planet.currentMeterValue(fo.meterType.targetResearch) info.possible_output[focus] = (industry_target, research_target) info.future_focus = focus self.baked_planet_info[pid] = self.raw_planet_info.pop(pid) return success
def issue_order(self): if not super(OrderMilitary, self).issue_order(): return False target_sys_id = self.target.id fleet = self.target.get_object() system_status = foAI.foAIstate.systemStatus.get(target_sys_id, {}) total_threat = sum(system_status.get(threat, 0) for threat in ('fleetThreat', 'planetThreat', 'monsterThreat')) combat_trigger = system_status.get('fleetThreat', 0) or system_status.get('monsterThreat', 0) if not combat_trigger and system_status.get('planetThreat', 0): universe = fo.getUniverse() system = universe.getSystem(target_sys_id) for planet_id in system.planetIDs: planet = universe.getPlanet(planet_id) if planet.ownedBy(fo.empireID()): # TODO: also exclude at-peace planets continue if planet.unowned and not EspionageAI.colony_detectable_by_empire(planet_id, empire=fo.empireID()): continue if sum([planet.currentMeterValue(meter_type) for meter_type in [fo.meterType.defense, fo.meterType.shield, fo.meterType.construction]]): combat_trigger = True break if not all(( fleet, fleet.systemID == target_sys_id, system_status.get('currently_visible', False), not (total_threat and combat_trigger) )): self.executed = False return True
def send_invasion_fleets(fleet_ids, evaluated_planets, mission_type): """sends a list of invasion fleets to a list of planet_value_pairs""" universe = fo.getUniverse() invasion_fleet_pool = set(fleet_ids) if not invasion_fleet_pool: return for planet_id, pscore, ptroops in evaluated_planets: 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+1} these_fleets = FleetUtilsAI.get_fleets_for_mission(1, target_stats, min_stats, found_stats, "", systems_to_check=[sys_id], systems_checked=[], fleet_pool_set=invasion_fleet_pool, fleet_list=found_fleets, verbose=False) 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 = AITarget.AITarget(EnumsAI.TargetType.TARGET_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_targets((fleet_mission.get_mission_types() + [-1])[0]) fleet_mission.add_target(mission_type, target)
def cur_best_mil_ship_rating(include_designs=False): """Find the best military ship we have available in this turn and return its rating. :param include_designs: toggles if available designs are considered or only existing ships :return: float: rating of the best ship """ current_turn = fo.currentTurn() if current_turn in _best_ship_rating_cache: best_rating = _best_ship_rating_cache[current_turn] if include_designs: best_design_rating = ProductionAI.cur_best_military_design_rating() best_rating = max(best_rating, best_design_rating) return best_rating best_rating = 0.001 universe = fo.getUniverse() for fleet_id in FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY): fleet = universe.getFleet(fleet_id) for ship_id in fleet.shipIDs: ship_rating = CombatRatingsAI.ShipCombatStats(ship_id).get_rating(enemy_stats=foAI.foAIstate.get_standard_enemy()) best_rating = max(best_rating, ship_rating) _best_ship_rating_cache[current_turn] = best_rating if include_designs: best_design_rating = ProductionAI.cur_best_military_design_rating() best_rating = max(best_rating, best_design_rating) return max(best_rating, 0.001)
def __report_system_threats(self): """Print a table with system threats to the logfile.""" current_turn = fo.currentTurn() if current_turn >= 100: return threat_table = Table([ Text('System'), Text('Vis.'), Float('Total'), Float('by Monsters'), Float('by Fleets'), Float('by Planets'), Float('1 jump away'), Float('2 jumps'), Float('3 jumps')], table_name="System Threat Turn %d" % current_turn ) universe = fo.getUniverse() for sys_id in universe.systemIDs: sys_status = self.systemStatus.get(sys_id, {}) system = universe.getSystem(sys_id) threat_table.add_row([ system, "Yes" if sys_status.get('currently_visible', False) else "No", sys_status.get('totalThreat', 0), sys_status.get('monsterThreat', 0), sys_status.get('fleetThreat', 0), sys_status.get('planetThreat', 0), sys_status.get('neighborThreat', 0.0), sys_status.get('jump2_threat', 0.0), sys_status.get('jump3_threat', 0.0), ]) info(threat_table)
def split_ship_from_fleet(fleet_id, ship_id): universe = fo.getUniverse() fleet = universe.getFleet(fleet_id) if assertion_fails(fleet is not None): return if assertion_fails(ship_id in fleet.shipIDs): return if assertion_fails(fleet.numShips > 1, "Can't split last ship from fleet"): return new_fleet_id = fo.issueNewFleetOrder("Fleet %4d" % ship_id, ship_id) if new_fleet_id: aistate = get_aistate() new_fleet = universe.getFleet(new_fleet_id) if not new_fleet: warn("Newly split fleet %d not available from universe" % new_fleet_id) debug("Successfully split ship %d from fleet %d into new fleet %d", ship_id, fleet_id, new_fleet_id) fo.issueRenameOrder(new_fleet_id, "Fleet %4d" % new_fleet_id) # to ease review of debugging logs fo.issueAggressionOrder(new_fleet_id, True) aistate.update_fleet_rating(new_fleet_id) aistate.newlySplitFleets[new_fleet_id] = True # register the new fleets so AI logic is aware of them sys_status = aistate.systemStatus.setdefault(fleet.systemID, {}) sys_status['myfleets'].append(new_fleet_id) sys_status['myFleetsAccessible'].append(new_fleet_id) else: if fleet.systemID == INVALID_ID: warn( "Tried to split ship id (%d) from fleet %d when fleet is in starlane" % (ship_id, fleet_id)) else: warn( "Got no fleet ID back after trying to split ship id (%d) from fleet %d" % (ship_id, fleet_id)) return new_fleet_id
def startNewGame(aggression_input=fo.aggression.aggressive): # pylint: disable=invalid-name """Called by client when a new game is started (but not when a game is loaded). Should clear any pre-existing state and set up whatever is needed for AI to generate orders.""" empire = fo.getEmpire() if empire.eliminated: print "This empire has been eliminated. Ignoring new game start message." return turn_timer.start("Server Processing") # initialize AIstate global foAIstate print "Initializing foAIstate..." foAIstate = AIstate.AIstate(aggression_input) aggression_trait = foAIstate.character.get_trait(Aggression) print "New game started, AI Aggression level %d (%s)" % ( aggression_trait.key, get_trait_name_aggression(foAIstate.character)) foAIstate.session_start_cleanup() print "Initialization of foAIstate complete!" print "Trying to rename our homeworld..." planet_id = PlanetUtilsAI.get_capital() universe = fo.getUniverse() if planet_id is not None and planet_id != INVALID_ID: planet = universe.getPlanet(planet_id) new_name = " ".join([ random.choice(possible_capitals(foAIstate.character)).strip(), planet.name ]) print " Renaming to %s..." % new_name res = fo.issueRenameOrder(planet_id, new_name) print " Result: %d; Planet is now named %s" % (res, planet.name) diplomatic_corp_configs = { fo.aggression.beginner: DiplomaticCorp.BeginnerDiplomaticCorp, fo.aggression.maniacal: DiplomaticCorp.ManiacalDiplomaticCorp } global diplomatic_corp diplomatic_corp = diplomatic_corp_configs.get( aggression_trait.key, DiplomaticCorp.DiplomaticCorp)() TechsListsAI.test_tech_integrity()
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: target.TargetSystem :param target: :type target: target.TargetSystem :param ensure_return: :type ensure_return: bool :return: :rtype: list """ 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(state.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_capital() -> PlanetId: """ Return current empire capital id. If no current capital returns planet with biggest population in first not empty group. First check all planets with coloniser species, after that with ship builders and at last all inhabited planets. """ universe = fo.getUniverse() empire = fo.getEmpire() empire_id = empire.empireID capital_id = empire.capitalID homeworld = universe.getPlanet(capital_id) if homeworld: if homeworld.owner == empire_id: return capital_id else: debug( "Nominal Capitol %s does not appear to be owned by empire %d %s" % (homeworld.name, empire_id, empire.name)) empire_owned_planet_ids = get_owned_planets_by_empire(universe.planetIDs) peopled_planets = get_populated_planet_ids(empire_owned_planet_ids) if not peopled_planets: if empire_owned_planet_ids: return empire_owned_planet_ids[0] else: return INVALID_ID try: for spec_list in [get_colony_builders(), get_ship_builders(), None]: population_id_pairs = [] for planet_id in peopled_planets: planet = universe.getPlanet(planet_id) if spec_list is None or planet.speciesName in spec_list: population_id_pairs.append( (planet.initialMeterValue(fo.meterType.population), planet_id)) if population_id_pairs: return max(population_id_pairs)[-1] except Exception as e: error(e, exc_info=True) return INVALID_ID # shouldn't ever reach here
def update_explored_systems(): universe = fo.getUniverse() empire = fo.getEmpire() obs_lanes = empire.obstructedStarlanes() obs_lanes_list = [el for el in obs_lanes] # should result in list of tuples (sys_id1, sys_id2) if obs_lanes_list: print "Obstructed starlanes are: %s" % ', '.join('%s-%s' % item for item in obs_lanes_list) else: print "No obstructed Starlanes" empire_id = fo.empireID() newly_explored = [] still_unexplored = [] for sys_id in list(foAI.foAIstate.unexploredSystemIDs): # consider making determination according to visibility rather than actual visit, # which I think is what empire.hasExploredSystem covers (Dilvish-fo) if empire.hasExploredSystem(sys_id): foAI.foAIstate.unexploredSystemIDs.discard(sys_id) foAI.foAIstate.exploredSystemIDs.add(sys_id) system = universe.getSystem(sys_id) print "Moved system %s from unexplored list to explored list" % system border_unexplored_system_ids.discard(sys_id) newly_explored.append(sys_id) else: still_unexplored.append(sys_id) neighbor_list = [] dummy = [] for id_list, next_list in [(newly_explored, neighbor_list), (neighbor_list, dummy)]: for sys_id in id_list: neighbors = list(universe.getImmediateNeighbors(sys_id, empire_id)) for neighbor_id in neighbors: # when it matters, unexplored will be smaller than explored if neighbor_id not in foAI.foAIstate.unexploredSystemIDs: next_list.append(neighbor_id) for sys_id in still_unexplored: neighbors = list(universe.getImmediateNeighbors(sys_id, empire_id)) if any(nid in foAI.foAIstate.exploredSystemIDs for nid in neighbors): border_unexplored_system_ids.add(sys_id) return newly_explored
def __init__(self): self.have_gas_giant = False self.have_asteroids = False self.owned_asteroid_coatings = 0 self.have_ruins = False self.have_nest = False self.have_computronium = False self.have_honeycomb = False self.have_worldtree = False self.num_researchers = 0 # population with research focus self.num_industrialists = 0 # population with industry focus empire_id = fo.empireID() universe = fo.getUniverse() for planet_info in _get_planets_info().values(): planet = universe.getPlanet(planet_info.pid) if planet.ownedBy(empire_id): if AIDependencies.ANCIENT_RUINS_SPECIAL in planet.specials: self.have_ruins = True if AIDependencies.WORLDTREE_SPECIAL in planet.specials: self.have_world_tree = True if AIDependencies.ASTEROID_COATING_OWNED_SPECIAL in planet.specials: self.owned_asteroid_coatings += 1 if planet.focus == FocusType.FOCUS_RESEARCH and AIDependencies.COMPUTRONIUM_SPECIAL in planet.specials: self.have_computronium = True if planet.focus == FocusType.FOCUS_INDUSTRY and AIDependencies.HONEYCOMB_SPECIAL in planet.specials: self.have_honeycomb = True population = planet.currentMeterValue(fo.meterType.population) if planet.focus == FocusType.FOCUS_INDUSTRY: self.num_industrialists += population elif planet.focus == FocusType.FOCUS_RESEARCH: self.num_researchers += population
def __report_last_turn_fleet_missions(self): """Print a table reviewing last turn fleet missions to the log file.""" universe = fo.getUniverse() mission_table = Table( [Text('Fleet'), Text('Mission'), Text('Ships'), Float('Rating'), Float('Troops'), Text('Target')], table_name="Turn %d: Fleet Mission Review from Last Turn" % fo.currentTurn()) for fleet_id, mission in self.get_fleet_missions_map().items(): fleet = universe.getFleet(fleet_id) if not fleet: continue if not mission: mission_table.add_row([fleet]) else: mission_table.add_row([ fleet, mission.type or "None", len(fleet.shipIDs), CombatRatingsAI.get_fleet_rating(fleet_id), FleetUtilsAI.count_troops_in_fleet(fleet_id), mission.target or "-" ]) info(mission_table)
def __update_planets(self): """ Update information about planets. """ universe = fo.getUniverse() empire_id = fo.empireID() for pid in universe.planetIDs: planet = universe.getPlanet(pid) self.__planet_info[pid] = PlanetInfo(pid, planet.speciesName, planet.owner, planet.systemID) if planet.ownedBy(empire_id): population = planet.currentMeterValue(fo.meterType.population) if AIDependencies.ANCIENT_RUINS_SPECIAL in planet.specials: self.__have_ruins = True if population > 0 and AIDependencies.COMPUTRONIUM_SPECIAL in planet.specials: self.__have_computronium = True # TODO: Check if species can set research focus if planet.focus == FocusType.FOCUS_INDUSTRY: self.__num_industrialists += population elif planet.focus == FocusType.FOCUS_RESEARCH: self.__num_researchers += population
def send_invasion_fleets(invasionFleetIDs, evaluatedPlanets, missionType): """sends a list of invasion fleets to a list of planet_value_pairs""" universe=fo.getUniverse() invasionPool = invasionFleetIDs[:] #need to make a copy bestShip, bestDesign, buildChoices = ProductionAI.getBestShipInfo( EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_INVASION) if bestDesign: troopsPerBestShip = 2*( list(bestDesign.parts).count("GT_TROOP_POD") ) else: troopsPerBestShip=5 #may actually not have any troopers available, but this num will do for now #sortedTargets=sorted( [ ( pscore-ptroops/2 , pID, pscore, ptroops) for pID, pscore, ptroops in evaluatedPlanets ] , reverse=True) invasionPool=set(invasionPool) for pID, pscore, ptroops in evaluatedPlanets: # if not invasionPool: return planet=universe.getPlanet(pID) if not planet: continue sysID = planet.systemID foundFleets = [] podsNeeded= math.ceil( (ptroops+0.05)/2.0) foundStats={} minStats= {'rating':0, 'troopPods':podsNeeded} targetStats={'rating':10,'troopPods':podsNeeded+1} theseFleets = FleetUtilsAI.get_fleets_for_mission(1, targetStats , minStats, foundStats, "", systems_to_check=[sysID], systems_checked=[], fleet_pool_set=invasionPool, fleet_list=foundFleets, verbose=False) if not theseFleets: if not FleetUtilsAI.stats_meet_reqs(foundStats, minStats): print "Insufficient invasion troop allocation for system %d ( %s ) -- requested %s , found %s"%(sysID, universe.getSystem(sysID).name, minStats, foundStats) invasionPool.update( foundFleets ) continue else: theseFleets = foundFleets aiTarget = AITarget.AITarget(EnumsAI.TargetType.TARGET_PLANET, pID) print "assigning invasion fleets %s to target %s"%(theseFleets, aiTarget) for fleetID in theseFleets: fleet=universe.getFleet(fleetID) aiFleetMission = foAI.foAIstate.get_fleet_mission(fleetID) aiFleetMission.clear_fleet_orders() aiFleetMission.clear_targets( (aiFleetMission.get_mission_types() + [-1])[0] ) aiFleetMission.add_target(missionType, aiTarget)
def set_planet_protection_foci(focus_manager): """Assess and set protection foci""" universe = fo.getUniverse() for pid, pinfo in list(focus_manager.raw_planet_info.items()): planet = pinfo.planet if PROTECTION in planet.availableFoci and assess_protection_focus( pinfo, focus_manager.priority): current_focus = planet.focus if focus_manager.bake_future_focus(pid, PROTECTION): if current_focus != PROTECTION: debug( "Tried setting %s for planet %s (%d) with species %s and current focus %s, " "got result %d and focus %s", pinfo.future_focus, planet.name, pid, planet.speciesName, current_focus, True, planet.focus, ) debug( "%s focus of planet %s (%d) at Protection(Defense) Focus", ["set", "left"][current_focus == PROTECTION], planet.name, pid, ) continue else: new_planet = universe.getPlanet(pid) warning( "Failed setting PROTECTION for planet %s (%d) with species %s and current focus %s, " "but new planet copy shows %s", planet.name, pid, planet.speciesName, planet.focus, new_planet.focus, )
def _calculate_industry_priority(): # currently only used to print status """calculates the demand for industry""" universe = fo.getUniverse() empire = fo.getEmpire() # get current industry production & Target industry_production = empire.resourceProduction(fo.resourceType.industry) owned_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire( universe.planetIDs) planets = map(universe.getPlanet, owned_planet_ids) target_pp = sum( map(lambda x: x.currentMeterValue(fo.meterType.targetIndustry), planets)) # currently, previously set to 50 in calculatePriorities(), this is just for reporting industry_priority = foAI.foAIstate.get_priority( PriorityType.RESOURCE_PRODUCTION) print print "Industry Production (current/target) : ( %.1f / %.1f ) at turn %s" % ( industry_production, target_pp, fo.currentTurn()) print "Priority for Industry: %s" % industry_priority return industry_priority
def _systems_connected(system1: SystemId, system2: SystemId) -> bool: if system1 == INVALID_ID: return False # optimization: We cache a set of systems connected to the capital # system in a single DFS and check if the systems are both in that set. # This allows us to avoid a BFS for each pair of systems. # Only in case neither of the system connects to the capital, we need to # run the BFS. connected_system_set = systems_connected_to_system(get_capital_sys_id()) sys1_connected_to_capital = system1 in connected_system_set sys2_connected_to_capital = system2 in connected_system_set if sys1_connected_to_capital and sys2_connected_to_capital: # both connected to the capital - so must be connected to each other return True if sys1_connected_to_capital != sys2_connected_to_capital: # Only one connected to the capital - can't be connected to each other return False # both are not connected to the home system - they may or may not be connected to each other return fo.getUniverse().systemsConnected(system1, system2, fo.empireID())
def issue_order(self): if not super(OrderMilitary, self).issue_order(): return False target_sys_id = self.target.id fleet = self.target.get_object() system_status = get_aistate().systemStatus.get(target_sys_id, {}) total_threat = sum( system_status.get(threat, 0) for threat in ("fleetThreat", "planetThreat", "monsterThreat")) combat_trigger = system_status.get( "fleetThreat", 0) or system_status.get("monsterThreat", 0) if not combat_trigger and system_status.get("planetThreat", 0): universe = fo.getUniverse() system = universe.getSystem(target_sys_id) for planet_id in system.planetIDs: planet = universe.getPlanet(planet_id) if planet.ownedBy( fo.empireID()): # TODO: also exclude at-peace planets continue if planet.unowned and not EspionageAI.colony_detectable_by_empire( planet_id, empire=fo.empireID()): continue if sum([ planet.currentMeterValue(meter_type) for meter_type in [ fo.meterType.defense, fo.meterType.shield, fo.meterType.construction ] ]): combat_trigger = True break if not all(( fleet, fleet.systemID == target_sys_id, system_status.get("currently_visible", False), not (total_threat and combat_trigger), )): self.executed = False return True
def startNewGame(aggression=fo.aggression.aggressive): # pylint: disable=invalid-name """Called by client when a new game is started (but not when a game is loaded). Should clear any pre-existing state and set up whatever is needed for AI to generate orders.""" turn_timer.start("Server Processing") print "New game started, AI Aggression level %d" % aggression # initialize AIstate global foAIstate foAIstate = AIstate.AIstate(aggression=aggression) foAIstate.session_start_cleanup() print "Initialized foAIstate class" planet_id = PlanetUtilsAI.get_capital() universe = fo.getUniverse() if planet_id is not None and planet_id != -1: planet = universe.getPlanet(planet_id) new_name = random.choice(_capitols.get( aggression, "").split('\n')).strip() + " " + planet.name print "Capitol City Names are: ", _capitols print "This Capitol New name is ", new_name res = fo.issueRenameOrder(planet_id, new_name) print "Capitol Rename attempt result: %d; planet now named %s" % ( res, planet.name)
def is_valid(self) -> bool: """ Check the colonization plan for validity, i.e. if it could be executed in the future. The plan is valid if it is possible to outpost the target planet and if the planet envisioned to build the outpost bases can still do so. """ universe = fo.getUniverse() # make sure target is valid target = universe.getPlanet(self.target) if target is None or (not target.unowned) or target.speciesName: return False # make sure source is valid source = universe.getPlanet(self.source) if not (source and source.ownedBy(fo.empireID()) and source.speciesName and fo.getSpecies(source.speciesName).canColonize): return False # appears to be valid return True
def _need_repair(self, repair_limit=0.70): """Check if fleet needs to be repaired. If the fleet is already at a system where it can be repaired, stay there until fully repaired. Otherwise, repair if fleet health is below specified *repair_limit*. For military fleets, there is a special evaluation called, cf. *MilitaryAI.avail_mil_needing_repair()* :param repair_limit: percentage of health below which the fleet is sent to repair :type repair_limit: float :return: True if fleet needs repair :rtype: bool """ # TODO: More complex evaluation if fleet needs repair (consider self-repair, distance, threat, mission...) universe = fo.getUniverse() fleet_id = self.fleet.id # if we are already at a system where we can repair, make sure we use it... system = self.fleet.get_system() # TODO starlane obstruction is not considered in the next call nearest_dock = MoveUtilsAI.get_best_drydock_system_id( system.id, fleet_id) if nearest_dock == system.id: repair_limit = 0.99 # if combat fleet, use military repair check if foAI.foAIstate.get_fleet_role(fleet_id) in COMBAT_MISSION_TYPES: return fleet_id in MilitaryAI.avail_mil_needing_repair( [fleet_id], on_mission=bool(self.orders), repair_limit=repair_limit)[0] # TODO: Allow to split fleet to send only damaged ships to repair fleet = universe.getFleet(fleet_id) ships_cur_health = 0 ships_max_health = 0 for ship_id in fleet.shipIDs: this_ship = universe.getShip(ship_id) ships_cur_health += this_ship.initialMeterValue( fo.meterType.structure) ships_max_health += this_ship.initialMeterValue( fo.meterType.maxStructure) return ships_cur_health < repair_limit * ships_max_health
def assign_bases_to_colonize(self): universe = fo.getUniverse() all_outpost_base_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.ORBITAL_OUTPOST) avail_outpost_base_fleet_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types( all_outpost_base_fleet_ids) for fid in avail_outpost_base_fleet_ids: fleet = universe.getFleet(fid) if not fleet: continue sys_id = fleet.systemID system = universe.getSystem(sys_id) avail_plans = [ plan for plan in self._colonization_plans.values() if plan.target in system.planetIDs and not plan.base_assigned ] avail_plans.sort(key=lambda x: x.score, reverse=True) for plan in avail_plans: success = plan.assign_base(fid) if success: break
def enqueue_base(self) -> bool: """ Enqueue the base according to the plan. :return: True on success, False on failure """ if self.base_enqueued: warning("Tried to enqueue a base eventhough already done that.") return False # find the best possible base design for the source planet universe = fo.getUniverse() best_ship, _, _ = get_best_ship_info(PriorityType.PRODUCTION_ORBITAL_OUTPOST, self.source) if best_ship is None: warning("Can't find optimized outpost base design at %s" % (universe.getPlanet(self.source))) try: best_ship = next( design for design in fo.getEmpire().availableShipDesigns if "SD_OUTPOST_BASE" == fo.getShipDesign(design).name ) debug("Falling back to base design SD_OUTPOST_BASE") except StopIteration: # fallback design not available return False # enqueue the design at the source planet retval = fo.issueEnqueueShipProductionOrder(best_ship, self.source) debug( "Enqueueing Outpost Base at %s for %s with result %s" % (universe.getPlanet(self.source), universe.getPlanet(self.target), retval) ) if not retval: warning("Failed to enqueue outpost base at %s" % universe.getPlanet(self.source)) return False self.base_enqueued = True return True
def __print_candidate_table(candidates, mission, show_detail=False): universe = fo.getUniverse() if mission == "Colonization": first_column = Text("Score/Species") def get_first_column_value(x): return round(x[0], 2), x[1] elif mission == "Outposts": first_column = Number("Score") get_first_column_value = itemgetter(0) else: warning("__print_candidate_table(%s, %s): Invalid mission type" % (candidates, mission)) return columns = [ first_column, Text("Planet"), Text("System"), Sequence("Specials") ] if show_detail: columns.append(Sequence("Detail")) candidate_table = Table(*columns, table_name="Potential Targets for %s in Turn %d" % (mission, fo.currentTurn())) for planet_id, score_tuple in candidates: if score_tuple[0] > 0.5: planet = universe.getPlanet(planet_id) entries = [ get_first_column_value(score_tuple), planet, universe.getSystem(planet.systemID), planet.specials, ] if show_detail: entries.append(score_tuple[-1]) candidate_table.add_row(*entries) candidate_table.print_table(info)
def _get_stellar_tomography_bonus(system_id: SystemId) -> float: universe = fo.getUniverse() system = universe.getSystem(system_id) if system.starType == fo.starType.blackHole: factor = get_named_real( "LRN_STELLAR_TOMO_BLACK_TARGET_RESEARCH_PERPLANET") elif system.starType == fo.starType.neutron: factor = get_named_real( "LRN_STELLAR_TOMO_NEUTRON_TARGET_RESEARCH_PERPLANET") else: factor = get_named_real( "LRN_STELLAR_TOMO_NORMAL_STAR_TARGET_RESEARCH_PERPLANET") def is_our_researcher(pid): planet = universe.getPlanet(pid) return planet.focus == FocusType.FOCUS_RESEARCH and planet.owner == fo.empireID( ) num_researcher = sum(1 for pid in system.planetIDs if is_our_researcher(pid)) # if we add another planet with research focus, all will profit. (n+1)^2 - n^2 = 2n + 1 return (num_researcher * 2 + 1) * factor
def issue_order(self): if not super(OrderRepair, self).issue_order(): return False fleet_id = self.fleet.id system_id = self.target.get_system().id fleet = self.fleet.get_object() # type: fo.fleet if system_id == fleet.systemID: if foAI.foAIstate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: if system_id in foAI.foAIstate.needsEmergencyExploration: foAI.foAIstate.needsEmergencyExploration.remove(system_id) elif system_id != fleet.nextSystemID: fo.issueAggressionOrder(fleet_id, False) start_id = FleetUtilsAI.get_fleet_system(fleet) dest_id = MoveUtilsAI.get_safe_path_leg_to_dest(fleet_id, start_id, system_id) universe = fo.getUniverse() print "fleet %d with order type(%s) sent to safe leg dest %s and ultimate dest %s" % ( fleet_id, self.ORDER_NAME, universe.getSystem(dest_id), universe.getSystem(system_id)) fo.issueFleetMoveOrder(fleet_id, dest_id) print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) ships_cur_health, ships_max_health = FleetUtilsAI.get_current_and_max_structure(fleet_id) self.executed = (ships_cur_health == ships_max_health) return True
def issue_order(self): if not super(OrderInvade, self).can_issue_order(): return result = False planet_id = self.target.id planet = self.target.get_object() planet_name = planet and planet.name or "invisible" fleet = self.fleet.get_object() detail_str = "" universe = fo.getUniverse() global dumpTurn for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) if foAI.foAIstate.get_ship_role(ship.design.id) in [ShipRoleType.MILITARY_INVASION, ShipRoleType.BASE_INVASION]: result = fo.issueInvadeOrder(ship_id, planet_id) or result # will track if at least one invasion troops successfully deployed print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) shields = planet.currentMeterValue(fo.meterType.shield) owner = planet.owner if not result: planet_stealth = planet.currentMeterValue(fo.meterType.stealth) pop = planet.currentMeterValue(fo.meterType.population) detail_str = " -- planet has %.1f stealth, shields %.1f, %.1f population and is owned by empire %d" % (planet_stealth, shields, pop, owner) print "Ordered troop ship ID %d to invade %s, got result %d" % (ship_id, planet_name, result), detail_str if not result: if 'needsEmergencyExploration' not in dir(foAI.foAIstate): foAI.foAIstate.needsEmergencyExploration = [] if fleet.systemID not in foAI.foAIstate.needsEmergencyExploration: foAI.foAIstate.needsEmergencyExploration.append(fleet.systemID) print "Due to trouble invading, adding system %d to Emergency Exploration List" % fleet.systemID self.executed = False if shields > 0 and owner == -1 and dumpTurn < fo.currentTurn(): dumpTurn = fo.currentTurn() print "Universe Dump to debug invasions:" universe.dump() break if result: print "Successfully ordered troop ship(s) to invade %s, with detail %s" % (planet_name, detail_str)
def startNewGame(aggression=fo.aggression.aggressive): # pylint: disable=invalid-name """Called by client when a new game is started (but not when a game is loaded). Should clear any pre-existing state and set up whatever is needed for AI to generate orders.""" empire = fo.getEmpire() if empire.eliminated: print "This empire has been eliminated. Ignoring new game start message." return turn_timer.start("Server Processing") print "New game started, AI Aggression level %d (%s)" % ( aggression, UserString(_aggression_names[aggression])) # initialize AIstate global foAIstate foAIstate = AIstate.AIstate(aggression=aggression) foAIstate.session_start_cleanup() print "Initialized foAIstate class" planet_id = PlanetUtilsAI.get_capital() universe = fo.getUniverse() if planet_id is not None and planet_id != -1: planet = universe.getPlanet(planet_id) new_name = " ".join([ random.choice(_capitals.get(aggression, []) or [" "]).strip(), planet.name ]) print "Capitol City Names are: ", _capitals print "This Capitol New name is ", new_name res = fo.issueRenameOrder(planet_id, new_name) print "Capitol Rename attempt result: %d; planet now named %s" % ( res, planet.name) diplomatic_corp_configs = { fo.aggression.beginner: DiplomaticCorp.BeginnerDiplomaticCorp, fo.aggression.maniacal: DiplomaticCorp.ManiacalDiplomaticCorp } global diplomatic_corp diplomatic_corp = diplomatic_corp_configs.get( aggression, DiplomaticCorp.DiplomaticCorp)()
def startNewGame(aggression_input=fo.aggression.aggressive): # pylint: disable=invalid-name """Called by client when a new game is started (but not when a game is loaded). Should clear any pre-existing state and set up whatever is needed for AI to generate orders.""" empire = fo.getEmpire() if empire is None: fatal("This client has no empire. Ignoring new game start message.") return if empire.eliminated: info( "This empire has been eliminated. Ignoring new game start message." ) return turn_timer.start("Server Processing") # initialize AIstate debug("Initializing AI state...") create_new_aistate(aggression_input) aistate = get_aistate() aggression_trait = aistate.character.get_trait(Aggression) debug("New game started, AI Aggression level %d (%s)" % (aggression_trait.key, get_trait_name_aggression(aistate.character))) aistate.session_start_cleanup() debug("Initialization of AI state complete!") debug("Trying to rename our homeworld...") planet_id = PlanetUtilsAI.get_capital() universe = fo.getUniverse() if planet_id is not None and planet_id != INVALID_ID: planet = universe.getPlanet(planet_id) new_name = " ".join([ random.choice(possible_capitals(aistate.character)).strip(), planet.name ]) debug(" Renaming to %s..." % new_name) res = fo.issueRenameOrder(planet_id, new_name) debug(" Result: %d; Planet is now named %s" % (res, planet.name)) _pre_game_start(empire.empireID, aistate)
def _check_abort_mission(self, fleet_order): """ checks if current mission (targeting a planet) should be aborted""" planet = fo.getUniverse().getPlanet(fleet_order.target.target_id) if planet: order_type = fleet_order.order_type if order_type == AIFleetOrderType.ORDER_COLONISE: if planet.currentMeterValue(fo.meterType.population) == 0 and (planet.ownedBy(fo.empireID()) or planet.unowned): return False elif order_type == AIFleetOrderType.ORDER_OUTPOST: if planet.unowned: return False elif order_type == AIFleetOrderType.ORDER_INVADE: #TODO add substantive abort check return False else: return False # canceling fleet orders print " %s" % fleet_order print "Fleet %d had a target planet that is no longer valid for this mission; aborting." % self.target_id self.clear_fleet_orders() self.clear_targets(([-1] + self.get_mission_types()[:1])[-1]) FleetUtilsAI.split_fleet(self.target_id) return True
def __init__(self): universe = fo.getUniverse() resource_timer.start("getPlanets") planet_ids = list(PlanetUtilsAI.get_owned_planets_by_empire()) resource_timer.start("Targets") self.all_planet_info = { pid: PlanetFocusInfo(universe.getPlanet(pid)) for pid in planet_ids } self.raw_planet_info = dict(self.all_planet_info) self.baked_planet_info = {} for pid, pinfo in list(self.raw_planet_info.items()): if not pinfo.planet.availableFoci: self.baked_planet_info[pid] = self.raw_planet_info.pop(pid) aistate = get_aistate() self.priority = ( aistate.get_priority(PriorityType.RESOURCE_PRODUCTION), aistate.get_priority(PriorityType.RESOURCE_RESEARCH), aistate.get_priority(PriorityType.RESOURCE_INFLUENCE), )
def set_planet_production_and_research_specials(focus_manager): """Set production and research specials. Sets production/research specials for known (COMPUTRONIUM, HONEYCOMB and CONC_CAMP) production/research specials. Remove planets from list of candidates using bake_future_focus.""" # TODO use "best" COMPUTRON planet instead of first found, where "best" means least industry loss, # least threatened, no foci change penalty etc. universe = fo.getUniverse() already_have_comp_moon = False for pid, pinfo in focus_manager.raw_planet_info.items(): planet = pinfo.planet if (AIDependencies.COMPUTRONIUM_SPECIAL in planet.specials and RESEARCH in planet.availableFoci and not already_have_comp_moon): if focus_manager.bake_future_focus(pid, RESEARCH): already_have_comp_moon = True print "%s focus of planet %s (%d) (with Computronium Moon) at Research Focus" % ( ["set", "left"][pinfo.current_focus == RESEARCH], planet.name, pid) continue if "HONEYCOMB_SPECIAL" in planet.specials and INDUSTRY in planet.availableFoci: if focus_manager.bake_future_focus(pid, INDUSTRY): print "%s focus of planet %s (%d) (with Honeycomb) at Industry Focus" % ( ["set", "left"][pinfo.current_focus == INDUSTRY], planet.name, pid) continue if ((([bld.buildingTypeName for bld in map(universe.getBuilding, planet.buildingIDs) if bld.buildingTypeName in ["BLD_CONC_CAMP", "BLD_CONC_CAMP_REMNANT"]]) or ([ccspec for ccspec in planet.specials if ccspec in ["CONC_CAMP_MASTER_SPECIAL", "CONC_CAMP_SLAVE_SPECIAL"]])) and INDUSTRY in planet.availableFoci): if focus_manager.bake_future_focus(pid, INDUSTRY): print "%s focus of planet %s (%d) (with Concentration Camps/Remnants) at Industry Focus" % ( ["set", "left"][pinfo.current_focus == INDUSTRY], planet.name, pid) continue else: new_planet = universe.getPlanet(pid) warn("Failed setting %s for Concentration Camp planet %s (%d)" " with species %s and current focus %s, but new planet copy shows %s" % ( pinfo.future_focus, planet.name, pid, planet.speciesName, planet.focus, new_planet.focus))
def can_travel_to_system_and_return_to_resupply(fleet_id, from_system_target, to_system_target): """ Filter systems where fleet can travel from starting system. # TODO rename function :param fleet_id: :type 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 :return: :rtype: list """ system_targets = [] if not from_system_target.id == to_system_target.id: # get supplyable systems empire = fo.getEmpire() fleet_supplyable_system_ids = empire.fleetSupplyableSystemIDs # get current fuel and max fuel universe = fo.getUniverse() fleet = universe.getFleet(fleet_id) max_fuel = int(fleet.maxFuel) fuel = int(fleet.fuel) # if verbose: # print " fleet ID %d has %.1f fuel to get from %s to %s"%(fleetID, fuel, fromSystemAITarget, toSystemAITarget ) # try to find path without going resupply first supply_system_target = get_nearest_supplied_system(to_system_target.id) system_targets = __find_path_with_fuel_to_system_with_possible_return(from_system_target, to_system_target, system_targets, fleet_supplyable_system_ids, max_fuel, fuel, supply_system_target) # resupply in system first is required to find path if from_system_target.id not in fleet_supplyable_system_ids and not system_targets: # add supply system to visit from_system_target = get_nearest_supplied_system(from_system_target.id) system_targets.append(from_system_target) # find path from supplied system to wanted system system_targets = __find_path_with_fuel_to_system_with_possible_return(from_system_target, to_system_target, system_targets, fleet_supplyable_system_ids, max_fuel, max_fuel, supply_system_target) return system_targets
def _evaluate_specials(planet: fo.planet, species: fo.species) -> float: universe = fo.getUniverse() system = universe.getSystem(planet.systemID) result = 0.0 for pid in system.planetIDs: if pid == planet.id: value = AIDependencies.STABILITY_PER_LIKED_SPECIAL_ON_PLANET eval_planet = planet else: value = AIDependencies.STABILITY_PER_LIKED_SPECIAL_IN_SYSTEM eval_planet = universe.getPlanet(pid) for special in eval_planet.specials: if special in species.likes: result += value if special in species.dislikes: result -= value if have_worldtree() and not AIDependencies.not_affect_by_special( AIDependencies.WORLDTREE_SPECIAL, species.name): result += AIDependencies.STABILITY_BY_WORLDTREE gaia = AIDependencies.GAIA_SPECIAL if gaia in planet.specials and not AIDependencies.not_affect_by_special( gaia, species): result += 5 # TBD add named real return result
def merge_fleet_a_into_b(fleet_a_id, fleet_b_id, leave_rating=0, need_rating=0, context=""): universe = fo.getUniverse() fleet_a = universe.getFleet(fleet_a_id) fleet_b = universe.getFleet(fleet_b_id) if not fleet_a or not fleet_b: return 0 remaining_rating = CombatRatingsAI.get_fleet_rating(fleet_a_id) transferred_rating = 0 b_has_monster = False for ship_id in fleet_b.shipIDs: this_ship = universe.getShip(ship_id) if not this_ship: continue if this_ship.isMonster: b_has_monster = True break for ship_id in fleet_a.shipIDs: this_ship = universe.getShip(ship_id) if not this_ship or this_ship.isMonster != b_has_monster: # TODO Is there any reason for the monster check? continue this_rating = CombatRatingsAI.ShipCombatStats(ship_id).get_rating() remaining_rating = CombatRatingsAI.rating_needed(remaining_rating, this_rating) if remaining_rating < leave_rating: # merging this would leave old fleet under minimum rating, try other ships. continue transferred = fo.issueFleetTransferOrder(ship_id, fleet_b_id) if transferred: transferred_rating = CombatRatingsAI.combine_ratings(transferred_rating, this_rating) else: print " *** transfer of ship %4d, formerly of fleet %4d, into fleet %4d failed; %s" % ( ship_id, fleet_a_id, fleet_b_id, (" context is %s" % context) if context else "") if need_rating != 0 and need_rating <= transferred_rating: break fleet_a = universe.getFleet(fleet_a_id) if not fleet_a or fleet_a.empty or fleet_a_id in universe.destroyedObjectIDs(fo.empireID()): foAI.foAIstate.delete_fleet_info(fleet_a_id) foAI.foAIstate.update_fleet_rating(fleet_b_id)