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: fleet_supplyable_system_ids = fo.getEmpire().fleetSupplyableSystemIDs fuel = int(FleetUtilsAI.get_fuel(fleet_id)) # int to get actual number of jumps max_fuel = int(FleetUtilsAI.get_max_fuel(fleet_id)) # 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 splitNewFleets(): "split any fleets (at creation, can have unplanned mix of ship roles)" print "Review of current Fleet Role/Mission records:" print "--------------------" print "Map of Roles keyed by Fleet ID: %s"%foAIstate.getFleetRolesMap() print "--------------------" print "Map of Missions keyed by ID:" for item in foAIstate.getFleetMissionsMap().items(): print " %4d : %s "%item print "--------------------" # TODO: check length of fleets for losses or do in AIstat.__cleanRoles knownFleets= foAIstate.getFleetRolesMap().keys() foAIstate.newlySplitFleets.clear() splitableFleets=[] for fleetID in FleetUtilsAI.getEmpireFleetIDs(): if fleetID in knownFleets: #not a new fleet continue else: splitableFleets.append(fleetID) if splitableFleets: universe=fo.getUniverse() print ("splitting new fleets") for fleetID in splitableFleets: fleet = universe.getFleet(fleetID) if not fleet: print "Error splittting new fleets; resulting fleet ID %d appears to not exist"%fleetID continue fleetLen = len(list(fleet.shipIDs)) if fleetLen ==1: continue newFleets = FleetUtilsAI.splitFleet(fleetID) # try splitting fleet print "\t from splitting fleet ID %4d with %d ships, got %d new fleets:"%(fleetID, fleetLen, len(newFleets))
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(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 __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 calculateColonisationPriority(): "calculates the demand for colony ships by colonisable planets" global allottedColonyTargets totalPP=fo.getEmpire().productionPoints colonyCost=120*(1+ 0.06*len( list(AIstate.popCtrIDs) )) turnsToBuild=8#TODO: check for susp anim pods, build time 10 allottedPortion = [0.4, 0.5][ random.choice([0, 1]) ] #fo.empireID() % 2 if ( foAI.foAIstate.getPriority(EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_COLONISATION) > 2 * foAI.foAIstate.getPriority(EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_MILITARY)): allottedPortion *= 1.5 #allottedColonyTargets = 1+ int(fo.currentTurn()/50) allottedColonyTargets = 1 + int( totalPP*turnsToBuild*allottedPortion/colonyCost) numColonisablePlanetIDs = len( [ pid for (pid, (score, specName) ) in foAI.foAIstate.colonisablePlanetIDs if score > 60 ][:allottedColonyTargets+2] ) if (numColonisablePlanetIDs == 0): return 1 colonyshipIDs = FleetUtilsAI.getEmpireFleetIDsByRole(EnumsAI.AIFleetMissionType.FLEET_MISSION_COLONISATION) numColonyships = len(FleetUtilsAI.extractFleetIDsWithoutMissionTypes(colonyshipIDs)) colonisationPriority = 121 * (1+numColonisablePlanetIDs - numColonyships) / (numColonisablePlanetIDs+1) # print "" # print "Number of Colony Ships : " + str(numColonyships) # print "Number of Colonisable planets : " + str(numColonisablePlanetIDs) # print "Priority for colony ships : " + str(colonisationPriority) if colonisationPriority < 1: return 1 return colonisationPriority
def generateOrders(): print ("Genearting Orders") universe = fo.getUniverse() empire = fo.getEmpire() planetID = empire.capitalID planet = universe.getPlanet(planetID) print "EmpireID: " + str(empire.empireID) + " Name: " + empire.name + " Turn: " + str(fo.currentTurn()) print "CapitalID: " + str(planetID) + " Name: " + planet.name + " Species: " + planet.speciesName # turn cleanup splitFleet() identifyShipDesigns() identifyFleetsRoles() foAIstate.clean(ExplorationAI.getHomeSystemID(), FleetUtilsAI.getEmpireFleetIDs()) # ...missions # ...demands/priorities print("Calling AI Modules") # call AI modules PriorityAI.calculatePriorities() ExplorationAI.assignScoutsToExploreSystems() ColonisationAI.assignColonyFleetsToColonise() InvasionAI.assignInvasionFleetsToInvade() MilitaryAI.assignMilitaryFleetsToSystems() FleetUtilsAI.generateAIFleetOrdersForAIFleetMissions() FleetUtilsAI.issueAIFleetOrdersForAIFleetMissions() ResearchAI.generateResearchOrders() ProductionAI.generateProductionOrders() ResourcesAI.generateResourcesOrders() foAIstate.afterTurnCleanup() fo.doneTurn()
def getInvasionFleets(): "get invasion fleets" allInvasionFleetIDs = FleetUtilsAI.getEmpireFleetIDsByRole(AIFleetMissionType.FLEET_MISSION_INVASION) AIstate.invasionFleetIDs = FleetUtilsAI.extractFleetIDsWithoutMissionTypes(allInvasionFleetIDs) # get supplyable planets universe = fo.getUniverse() empire = fo.getEmpire() empireID = empire.empireID fleetSupplyableSystemIDs = empire.fleetSupplyableSystemIDs fleetSupplyablePlanetIDs = PlanetUtilsAI.getPlanetsInSystemsIDs(fleetSupplyableSystemIDs) # get competitor planets exploredSystemIDs = empire.exploredSystemIDs exploredPlanetIDs = PlanetUtilsAI.getPlanetsInSystemsIDs(exploredSystemIDs) allOwnedPlanetIDs = PlanetUtilsAI.getAllOwnedPlanetIDs(exploredPlanetIDs) # print "All Owned and Populated PlanetIDs: " + str(allOwnedPlanetIDs) empireOwnedPlanetIDs = PlanetUtilsAI.getOwnedPlanetsByEmpire(universe.planetIDs, empireID) # print "Empire Owned PlanetIDs: " + str(empireOwnedPlanetIDs) competitorPlanetIDs = list(set(allOwnedPlanetIDs) - set(empireOwnedPlanetIDs)) print "Competitor PlanetIDs: " + str(competitorPlanetIDs) print "" print "Invasion Targeted SystemIDs: " + str(AIstate.invasionTargetedSystemIDs) invasionTargetedPlanetIDs = getInvasionTargetedPlanetIDs(universe.planetIDs, AIFleetMissionType.FLEET_MISSION_INVASION, empireID) allInvasionTargetedSystemIDs = PlanetUtilsAI.getSystems(invasionTargetedPlanetIDs) # export invasion targeted systems for other AI modules AIstate.invasionTargetedSystemIDs = allInvasionTargetedSystemIDs print "Invasion Targeted PlanetIDs: " + str(invasionTargetedPlanetIDs) invasionFleetIDs = FleetUtilsAI.getEmpireFleetIDsByRole(AIFleetMissionType.FLEET_MISSION_INVASION) if not invasionFleetIDs: print "Available Invasion Fleets: 0" else: print "Invasion FleetIDs: " + str(FleetUtilsAI.getEmpireFleetIDsByRole(AIFleetMissionType.FLEET_MISSION_INVASION)) numInvasionFleets = len(FleetUtilsAI.extractFleetIDsWithoutMissionTypes(invasionFleetIDs)) print "Invasion Fleets Without Missions: " + str(numInvasionFleets) evaluatedPlanetIDs = list(set(competitorPlanetIDs) - set(invasionTargetedPlanetIDs)) # print "Evaluated PlanetIDs: " + str(evaluatedPlanetIDs) evaluatedPlanets = assignInvasionValues(evaluatedPlanetIDs, AIFleetMissionType.FLEET_MISSION_INVASION, fleetSupplyablePlanetIDs, empire) sortedPlanets = evaluatedPlanets.items() sortedPlanets.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) print "" print "Invadable planetIDs:" for evaluationPair in sortedPlanets: print " ID|Score: " + str(evaluationPair) # export opponent planets for other AI modules AIstate.opponentPlanetIDs = sortedPlanets
def generateOrders(): empire = fo.getEmpire() print "Empire: " + empire.name + " TURN: " + str(fo.currentTurn()) print "Capital: " + str(empire.capitalID) # turn cleanup splitFleet() identifyShipDesigns() identifyFleetsRoles() foAIstate.clean(ExplorationAI.getHomeSystemID(), FleetUtilsAI.getEmpireFleetIDs()) # ...missions # ...demands/priorities # call AI modules PriorityAI.calculatePriorities() ExplorationAI.assignScoutsToExploreSystems() ColonisationAI.assignColonyFleetsToColonise() InvasionAI.assignInvasionFleetsToInvade() FleetUtilsAI.generateAIFleetOrdersForAIFleetMissions() FleetUtilsAI.issueAIFleetOrdersForAIFleetMissions() ResearchAI.generateResearchOrders() ProductionAI.generateProductionOrders() ResourcesAI.generateResourcesOrders() foAIstate.afterTurnCleanup() fo.doneTurn()
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 splitFleet(): "split all fleets" # TODO: only after analyzing situation in map can fleet can be split universe = fo.getUniverse() for fleetID in universe.fleetIDs: FleetUtilsAI.splitFleet(fleetID) # old fleet may have different role after split, later will be again identified foAIstate.removeFleetRole(fleetID)
def assignInvasionFleetsToInvade(): # assign fleet targets to invadable planets invasionFleetIDs = AIstate.invasionFleetIDs sendInvasionFleets(invasionFleetIDs, AIstate.invasionTargets, AIFleetMissionType.FLEET_MISSION_INVASION) allInvasionFleetIDs = FleetUtilsAI.getEmpireFleetIDsByRole(AIFleetMissionType.FLEET_MISSION_INVASION) for fid in FleetUtilsAI.extractFleetIDsWithoutMissionTypes(allInvasionFleetIDs): thisMission = foAI.foAIstate.getAIFleetMission(fid) thisMission.checkMergers(context="Post-send consolidation of unassigned troops")
def can_issue_order(self, verbose=False): # TODO: check for separate fleet holding outpost ships if not super(OrderOutpost, self).can_issue_order(verbose=verbose): return False universe = fo.getUniverse() ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.CIVILIAN_OUTPOST) if ship_id is None: ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.BASE_OUTPOST) ship = universe.getShip(ship_id) return ship is not None and self.fleet.get_object().systemID == self.target.get_system().id and ship.canColonize
def getColonyFleets(): # get colony fleets allColonyFleetIDs = FleetUtilsAI.getEmpireFleetIDsByRole(AIFleetMissionType.FLEET_MISSION_COLONISATION) AIstate.colonyFleetIDs = FleetUtilsAI.extractFleetIDsWithoutMissionTypes(allColonyFleetIDs) # get supplyable systems empire = fo.getEmpire() universe = fo.getUniverse() capitalID = empire.capitalID homeworld = universe.getPlanet(capitalID) speciesName = homeworld.speciesName species = fo.getSpecies(speciesName) fleetSupplyableSystemIDs = empire.fleetSupplyableSystemIDs fleetSupplyablePlanetIDs = PlanetUtilsAI.getPlanetsInSystemsIDs(fleetSupplyableSystemIDs) print " fleetSupplyablePlanetIDs:" + str(fleetSupplyablePlanetIDs) # get planets systemIDs = foAI.foAIstate.getExplorableSystems(AIExplorableSystemType.EXPLORABLE_SYSTEM_EXPLORED) planetIDs = PlanetUtilsAI.getPlanetsInSystemsIDs(systemIDs) removeAlreadyOwnedPlanetIDs(planetIDs, AIFleetMissionType.FLEET_MISSION_COLONISATION) removeAlreadyOwnedPlanetIDs(planetIDs, AIFleetMissionType.FLEET_MISSION_OUTPOST) evaluatedPlanets = assignColonisationValues(planetIDs, AIFleetMissionType.FLEET_MISSION_COLONISATION, fleetSupplyablePlanetIDs, species, empire) removeLowValuePlanets(evaluatedPlanets) sortedPlanets = evaluatedPlanets.items() sortedPlanets.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) print "Colonisable planets:" for evaluationPair in sortedPlanets: print " ID|Score: " + str(evaluationPair) print "" # export planets for other AI modules AIstate.colonisablePlanetIDs = sortedPlanets # get outpost fleets allOutpostFleetIDs = FleetUtilsAI.getEmpireFleetIDsByRole(AIFleetMissionType.FLEET_MISSION_OUTPOST) AIstate.outpostFleetIDs = FleetUtilsAI.extractFleetIDsWithoutMissionTypes(allOutpostFleetIDs) evaluatedOutposts = assignColonisationValues(planetIDs, AIFleetMissionType.FLEET_MISSION_OUTPOST, fleetSupplyablePlanetIDs, species, empire) removeLowValuePlanets(evaluatedOutposts) sortedOutposts = evaluatedOutposts.items() sortedOutposts.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) print "Colonisable outposts:" for evaluationPair in sortedOutposts: print " ID|Score: " + str(evaluationPair) print "" # export outposts for other AI modules AIstate.colonisableOutpostIDs = sortedOutposts
def issueOrder(self): if not self.canIssueOrder(): print "can't issue " + self else: self.__setExecuted() # outpost if AIFleetOrderType.ORDER_OUTPOST == self.getAIFleetOrderType(): shipID = None if AITargetType.TARGET_SHIP == self.getSourceAITarget().getAITargetType(): shipID = self.getSourceAITarget().getTargetID() elif AITargetType.TARGET_FLEET == self.getSourceAITarget().getAITargetType(): fleetID = self.getSourceAITarget().getTargetID() shipID = FleetUtilsAI.getShipIDWithRole(fleetID, AIShipRoleType.SHIP_ROLE_CIVILIAN_OUTPOST) fo.issueColonizeOrder(shipID, self.getTargetAITarget().getTargetID()) # colonise elif AIFleetOrderType.ORDER_COLONISE == self.getAIFleetOrderType(): shipID = None if AITargetType.TARGET_SHIP == self.getSourceAITarget().getAITargetType(): shipID = self.getSourceAITarget().getTargetID() elif AITargetType.TARGET_FLEET == self.getSourceAITarget().getAITargetType(): fleetID = self.getSourceAITarget().getTargetID() shipID = FleetUtilsAI.getShipIDWithRole(fleetID, AIShipRoleType.SHIP_ROLE_CIVILIAN_COLONISATION) fo.issueColonizeOrder(shipID, self.getTargetAITarget().getTargetID()) # invade elif AIFleetOrderType.ORDER_INVADE == self.getAIFleetOrderType(): shipID = None if AITargetType.TARGET_SHIP == self.getSourceAITarget().getAITargetType(): shipID = self.getSourceAITarget().getTargetID() elif AITargetType.TARGET_FLEET == self.getSourceAITarget().getAITargetType(): fleetID = self.getSourceAITarget().getTargetID() shipID = FleetUtilsAI.getShipIDWithRole(fleetID, AIShipRoleType.SHIP_ROLE_MILITARY_INVASION) fo.issueInvadeOrder(shipID, self.getTargetAITarget().getTargetID()) # move or resupply elif (AIFleetOrderType.ORDER_MOVE == self.getAIFleetOrderType()) or (AIFleetOrderType.ORDER_RESUPPLY == self.getAIFleetOrderType()): fleetID = self.getSourceAITarget().getTargetID() systemID = self.getTargetAITarget().getTargetID() fo.issueFleetMoveOrder(fleetID, systemID) # split fleet elif AIFleetOrderType.ORDER_SPLIT_FLEET == self.getAIFleetOrderType(): fleetID = self.getSourceAITarget().getTargetID() shipID = self.getTargetAITarget().getTargetID() fo.issueNewFleetOrder(str(shipID), shipID) self.__setExecutionCompleted() elif (AIFleetOrderType.ORDER_ATACK == self.getAIFleetOrderType()): fleetID = self.getSourceAITarget().getTargetID() systemID = self.getTargetAITarget().getRequiredSystemAITargets()[0].getTargetID() fo.issueFleetMoveOrder(fleetID, systemID)
def assign_invasion_fleets_to_invade(): """Assign fleet targets to invadable planets.""" assign_invasion_bases() invasion_fleet_ids = AIstate.invasionFleetIDs send_invasion_fleets(invasion_fleet_ids, AIstate.invasionTargets, MissionType.INVASION) all_invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.INVASION) for fid in FleetUtilsAI.extract_fleet_ids_without_mission_types(all_invasion_fleet_ids): this_mission = foAI.foAIstate.get_fleet_mission(fid) this_mission.check_mergers(context="Post-send consolidation of unassigned troops")
def can_issue_order(self, verbose=False): if not super(OrderColonize, self).is_valid(): return False # TODO: check for separate fleet holding colony ships ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.CIVILIAN_COLONISATION) if ship_id is None: ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.BASE_COLONISATION) universe = fo.getUniverse() ship = universe.getShip(ship_id) if ship and not ship.canColonize: warn("colonization fleet %d has no colony ship" % self.fleet.id) return ship is not None and self.fleet.get_object().systemID == self.target.get_system().id and ship.canColonize
def can_issue_order(self, verbose=False): if not super(OrderInvade, self).is_valid(): return False # TODO: check for separate fleet holding invasion ships ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, AIShipRoleType.SHIP_ROLE_MILITARY_INVASION, False) if ship_id is None: ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, AIShipRoleType.SHIP_ROLE_BASE_INVASION) universe = fo.getUniverse() ship = universe.getShip(ship_id) planet = self.target.get_object() return ship is not None and self.fleet.get_system().id == planet.systemID \ and ship.canInvade and not planet.currentMeterValue(fo.meterType.shield)
def calculateColonisationPriority(): """calculates the demand for colony ships by colonisable planets""" global allottedColonyTargets, colony_growth_barrier enemies_sighted = foAI.foAIstate.misc.get("enemies_sighted", {}) galaxy_is_sparse = ColonisationAI.galaxy_is_sparse() total_pp = fo.getEmpire().productionPoints num_colonies = len(list(AIstate.popCtrIDs)) # significant growth barrier for low aggression, negligible for high aggression colony_growth_barrier = 2 + ((0.5 + foAI.foAIstate.aggression) ** 2) * fo.currentTurn() / 50.0 colonyCost = AIDependencies.COLONY_POD_COST * (1 + AIDependencies.COLONY_POD_UPKEEP * num_colonies) turnsToBuild = 8 # TODO: check for susp anim pods, build time 10 mil_prio = foAI.foAIstate.get_priority(EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_MILITARY) allottedPortion = [[[0.6, 0.8], [0.3, 0.4]], [[0.8, 0.9], [0.3, 0.4]]][galaxy_is_sparse][any(enemies_sighted)][ fo.empireID() % 2 ] # if ( foAI.foAIstate.get_priority(EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_COLONISATION) # > 2 * foAI.foAIstate.get_priority(EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_MILITARY)): # allottedPortion *= 1.5 if mil_prio < 100: allottedPortion *= 2 elif mil_prio < 200: allottedPortion *= 1.5 elif fo.currentTurn() > 100: allottedPortion *= 0.75 ** (num_colonies / 10.0) # allottedColonyTargets = 1+ int(fo.currentTurn()/50) allottedColonyTargets = 1 + int(total_pp * turnsToBuild * allottedPortion / colonyCost) outpost_prio = foAI.foAIstate.get_priority(EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_OUTPOST) # if have any outposts to build, don't build colony ships TODO: make more complex assessment if outpost_prio > 0 or num_colonies > colony_growth_barrier: return 0.0 if num_colonies > colony_growth_barrier: return 0.0 numColonisablePlanetIDs = len( [pid for (pid, (score, _)) in foAI.foAIstate.colonisablePlanetIDs.items() if score > 60][ : allottedColonyTargets + 2 ] ) if numColonisablePlanetIDs == 0: return 1 colonyshipIDs = FleetUtilsAI.get_empire_fleet_ids_by_role(EnumsAI.AIFleetMissionType.FLEET_MISSION_COLONISATION) numColonyships = len(FleetUtilsAI.extract_fleet_ids_without_mission_types(colonyshipIDs)) colonisationPriority = 60 * (1 + numColonisablePlanetIDs - numColonyships) / (numColonisablePlanetIDs + 1) # print # print "Number of Colony Ships : " + str(numColonyships) # print "Number of Colonisable planets : " + str(numColonisablePlanetIDs) # print "Priority for colony ships : " + str(colonisationPriority) if colonisationPriority < 1: return 0 return colonisationPriority
def calculateOutpostPriority(): """calculates the demand for outpost ships by colonisable planets""" global allotted_outpost_targets base_outpost_cost = AIDependencies.OUTPOST_POD_COST enemies_sighted = foAI.foAIstate.misc.get("enemies_sighted", {}) galaxy_is_sparse = ColonisationAI.galaxy_is_sparse() total_pp = fo.getEmpire().productionPoints num_colonies = len(list(AIstate.popCtrIDs)) # significant growth barrier for low aggression, negligible for high aggression if num_colonies > colony_growth_barrier: return 0.0 mil_prio = foAI.foAIstate.get_priority(EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_MILITARY) NOT_SPARCE, ENEMY_UNSEEN = 0, 0 IS_SPARCE, ENEMY_SEEN = 1, 1 allotted_portion = { (NOT_SPARCE, ENEMY_UNSEEN): (0.6, 0.8), (NOT_SPARCE, ENEMY_SEEN): (0.3, 0.4), (IS_SPARCE, ENEMY_UNSEEN): (0.8, 0.9), (IS_SPARCE, ENEMY_SEEN): (0.3, 0.4), }[(galaxy_is_sparse, any(enemies_sighted))][fo.empireID() % 2] if mil_prio < 100: allotted_portion *= 2 elif mil_prio < 200: allotted_portion *= 1.5 allotted_outpost_targets = 1 + int(total_pp * 3 * allotted_portion / base_outpost_cost) num_outpost_targets = len( [ pid for (pid, (score, specName)) in foAI.foAIstate.colonisableOutpostIDs.items() if score > 1.0 * base_outpost_cost / 3.0 ][:allotted_outpost_targets] ) if num_outpost_targets == 0 or not tech_is_complete(AIDependencies.OUTPOSTING_TECH): return 0 outpostShipIDs = FleetUtilsAI.get_empire_fleet_ids_by_role(EnumsAI.AIFleetMissionType.FLEET_MISSION_OUTPOST) num_outpost_ships = len(FleetUtilsAI.extract_fleet_ids_without_mission_types(outpostShipIDs)) outpost_priority = 50 * (num_outpost_targets - num_outpost_ships) / num_outpost_targets # print # print "Number of Outpost Ships : " + str(num_outpost_ships) # print "Number of Colonisable outposts: " + str(num_outpost_planet_ids) print "Priority for outpost ships : " + str(outpost_priority) if outpost_priority < 1: return 0 return outpost_priority
def _calculate_outpost_priority(): """Calculates the demand for outpost ships by colonisable planets.""" global allotted_outpost_targets base_outpost_cost = AIDependencies.OUTPOST_POD_COST enemies_sighted = foAI.foAIstate.misc.get('enemies_sighted', {}) galaxy_is_sparse = ColonisationAI.galaxy_is_sparse() total_pp = fo.getEmpire().productionPoints colony_growth_barrier = foAI.foAIstate.character.max_number_colonies() if state.get_number_of_colonies() > colony_growth_barrier: return 0.0 mil_prio = foAI.foAIstate.get_priority(PriorityType.PRODUCTION_MILITARY) not_sparse, enemy_unseen = 0, 0 is_sparse, enemy_seen = 1, 1 allotted_portion = { (not_sparse, enemy_unseen): (0.6, 0.8), (not_sparse, enemy_seen): (0.3, 0.4), (is_sparse, enemy_unseen): (0.8, 0.9), (is_sparse, enemy_seen): (0.3, 0.4), }[(galaxy_is_sparse, any(enemies_sighted))] allotted_portion = foAI.foAIstate.character.preferred_outpost_portion(allotted_portion) if mil_prio < 100: allotted_portion *= 2 elif mil_prio < 200: allotted_portion *= 1.5 allotted_outpost_targets = 1 + int(total_pp * 3 * allotted_portion / base_outpost_cost) num_outpost_targets = len([pid for (pid, (score, specName)) in foAI.foAIstate.colonisableOutpostIDs.items() if score > max(1.0 * base_outpost_cost / 3.0, ColonisationAI.MINIMUM_COLONY_SCORE)] [:allotted_outpost_targets]) if num_outpost_targets == 0 or not tech_is_complete(AIDependencies.OUTPOSTING_TECH): return 0 outpost_ship_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.OUTPOST) num_outpost_ships = len(FleetUtilsAI.extract_fleet_ids_without_mission_types(outpost_ship_ids)) outpost_priority = (50.0 * (num_outpost_targets - num_outpost_ships)) / num_outpost_targets # discourage early outposting for SP_SLY, due to supply and stealth considerations they are best off # using colony ships until they have other colonizers (and more established military) if list(ColonisationAI.empire_colonizers) == ["SP_SLY"]: outpost_priority /= 3.0 # print # print "Number of Outpost Ships : " + str(num_outpost_ships) # print "Number of Colonisable outposts: " + str(num_outpost_planet_ids) print "Priority for outpost ships: " + str(outpost_priority) if outpost_priority < 1: return 0 return outpost_priority
def check_mergers(self, context=""): """ Merge local fleets with same mission into this fleet. :param context: Context of the function call for logging purposes :type context: str """ debug("Considering to merge %s", self.__str__()) if self.type not in MERGEABLE_MISSION_TYPES: debug("Mission type does not allow merging.") return if not self.target: debug("Mission has no valid target - do not merge.") return universe = fo.getUniverse() empire_id = fo.empireID() fleet_id = self.fleet.id main_fleet = universe.getFleet(fleet_id) main_fleet_system_id = main_fleet.systemID if main_fleet_system_id == INVALID_ID: debug("Can't merge: fleet in middle of starlane.") return # only merge PROTECT_REGION if there is any threat near target if self.type == MissionType.PROTECT_REGION: neighbor_systems = universe.getImmediateNeighbors(self.target.id, empire_id) if not any(MilitaryAI.get_system_local_threat(sys_id) for sys_id in neighbor_systems): debug("Not merging PROTECT_REGION fleet - no threat nearby.") return destroyed_list = set(universe.destroyedObjectIDs(empire_id)) aistate = get_aistate() system_status = aistate.systemStatus[main_fleet_system_id] other_fleets_here = [fid for fid in system_status.get('myFleetsAccessible', []) if fid != fleet_id and fid not in destroyed_list and universe.getFleet(fid).ownedBy(empire_id)] if not other_fleets_here: debug("No other fleets here") return for fid in other_fleets_here: fleet_mission = aistate.get_fleet_mission(fid) if fleet_mission.type != self.type or fleet_mission.target != self.target: debug("Local candidate %s does not have same mission." % fleet_mission) continue FleetUtilsAI.merge_fleet_a_into_b(fid, fleet_id, context="Order %s of mission %s" % (context, self))
def _calculate_colonisation_priority(): """Calculates the demand for colony ships by colonisable planets.""" global allottedColonyTargets enemies_sighted = foAI.foAIstate.misc.get('enemies_sighted', {}) galaxy_is_sparse = ColonisationAI.galaxy_is_sparse() total_pp = fo.getEmpire().productionPoints num_colonies = len(list(AIstate.popCtrIDs)) colony_growth_barrier = foAI.foAIstate.character.max_number_colonies() colony_cost = AIDependencies.COLONY_POD_COST * (1 + AIDependencies.COLONY_POD_UPKEEP * num_colonies) turns_to_build = 8 # TODO: check for susp anim pods, build time 10 mil_prio = foAI.foAIstate.get_priority(PriorityType.PRODUCTION_MILITARY) allotted_portion = ([[[0.6, 0.8], [0.3, 0.4]], [[0.8, 0.9], [0.4, 0.6]]][galaxy_is_sparse] [any(enemies_sighted)]) allotted_portion = foAI.foAIstate.character.preferred_colonization_portion(allotted_portion) # if ( foAI.foAIstate.get_priority(AIPriorityType.PRIORITY_PRODUCTION_COLONISATION) # > 2 * foAI.foAIstate.get_priority(AIPriorityType.PRIORITY_PRODUCTION_MILITARY)): # allotted_portion *= 1.5 if mil_prio < 100: allotted_portion *= 2 elif mil_prio < 200: allotted_portion *= 1.5 elif fo.currentTurn() > 100: allotted_portion *= 0.75 ** (num_colonies / 10.0) # allottedColonyTargets = 1+ int(fo.currentTurn()/50) allottedColonyTargets = 1 + int(total_pp * turns_to_build * allotted_portion / colony_cost) outpost_prio = foAI.foAIstate.get_priority(PriorityType.PRODUCTION_OUTPOST) # if have any outposts to build, don't build colony ships TODO: make more complex assessment if outpost_prio > 0 or num_colonies > colony_growth_barrier: return 0.0 if num_colonies > colony_growth_barrier: return 0.0 num_colonisable_planet_ids = len([pid for (pid, (score, _)) in foAI.foAIstate.colonisablePlanetIDs.items() if score > 60][:allottedColonyTargets + 2]) if num_colonisable_planet_ids == 0: return 1 colony_ship_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.COLONISATION) num_colony_ships = len(FleetUtilsAI.extract_fleet_ids_without_mission_types(colony_ship_ids)) colonisation_priority = 60 * (1 + num_colonisable_planet_ids - num_colony_ships) / (num_colonisable_planet_ids + 1) # print # print "Number of Colony Ships : " + str(num_colony_ships) # print "Number of Colonisable planets : " + str(num_colonisable_planet_ids) # print "Priority for colony ships : " + str(colonisation_priority) if colonisation_priority < 1: return 0 return colonisation_priority
def issue_order(self): if not super(OrderOutpost, self).issue_order(): return planet = self.target.get_object() if not planet.unowned: self.order_issued = True return fleet_id = self.fleet.id ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.CIVILIAN_OUTPOST) if ship_id is None: ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.BASE_OUTPOST) result = fo.issueColonizeOrder(ship_id, self.target.id) print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) if not result: self.executed = 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...) 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 get_aistate().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 ships_cur_health, ships_max_health = FleetUtilsAI.get_current_and_max_structure(fleet_id) return ships_cur_health < repair_limit * ships_max_health
def identifyFleetsRoles(): "identify fleet roles" # assign roles to fleets universe = fo.getUniverse() for fleetID in universe.fleetIDs: foAIstate.addFleetRole(fleetID, FleetUtilsAI.assessFleetRole(fleetID))
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 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 issue_order(self): if not super(OrderResupply, self).issue_order(): return fleet_id = self.fleet.id system_id = self.target.get_system().id fleet = self.fleet.get_object() if system_id not in [fleet.systemID, fleet.nextSystemID]: # if self.order_type == AIFleetOrderType.ORDER_MOVE: # dest_id = system_id # else: # if self.order_type == AIFleetOrderType.ORDER_REPAIR: # 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) print "fleet %d with order type(%s) sent to safe leg dest %s and ultimate dest %s" % ( fleet_id, self.ORDER_NAME, PlanetUtilsAI.sys_name_ids([dest_id]), PlanetUtilsAI.sys_name_ids([system_id])) fo.issueFleetMoveOrder(fleet_id, dest_id) print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) 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) self.order_issued = True
def issue_order(self): if not super(OrderColonize, self).issue_order(): return fleet_id = self.fleet.id ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.CIVILIAN_COLONISATION) if ship_id is None: ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.BASE_COLONISATION) planet = self.target.get_object() planet_name = planet and planet.name or "apparently invisible" result = fo.issueColonizeOrder(ship_id, self.target.id) print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) print "Ordered colony ship ID %d to colonize %s, got result %d" % (ship_id, planet_name, result) if not result: self.executed = False
def get_fleet_role(self, fleet_id, force_new=False): """Returns fleet role by ID.""" if not force_new and fleet_id in self.__fleetRoleByID: return self.__fleetRoleByID[fleet_id] else: role = FleetUtilsAI.assess_fleet_role(fleet_id) self.__fleetRoleByID[fleet_id] = role make_aggressive = False if role in [MissionType.COLONISATION, MissionType.OUTPOST, MissionType.ORBITAL_INVASION, MissionType.ORBITAL_OUTPOST ]: pass elif role in [MissionType.EXPLORATION, MissionType.INVASION ]: this_rating = self.get_rating(fleet_id) # Done! n_ships = self.fleetStatus.get(fleet_id, {}).get('nships', 1) # entry sould exist due to above line if float(this_rating) / n_ships >= 0.5 * MilitaryAI.cur_best_mil_ship_rating(): make_aggressive = True else: make_aggressive = True fo.issueAggressionOrder(fleet_id, make_aggressive) return role
def get_num_military_ships(): fleet_status = get_aistate().fleetStatus return sum( fleet_status.get(fid, {}).get('nships', 0) for fid in FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY))
def generate_fleet_orders(self): """generates AIFleetOrders from fleets targets to accomplish""" universe = fo.getUniverse() fleet_id = self.fleet.id fleet = universe.getFleet(fleet_id) if (not fleet) or fleet.empty or (fleet_id in universe.destroyedObjectIDs( fo.empireID())): # fleet was probably merged into another or was destroyed get_aistate().delete_fleet_info(fleet_id) return # TODO: priority self.clear_fleet_orders() system_id = fleet.systemID # if fleet doesn't have any mission, # then repair if needed or resupply if is current location not in supplyable system empire = fo.getEmpire() fleet_supplyable_system_ids = empire.fleetSupplyableSystemIDs # if (not self.hasAnyAIMissionTypes()): if not self.target and (system_id not in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs)): if self._need_repair(): repair_fleet_order = MoveUtilsAI.get_repair_fleet_order( self.fleet) if repair_fleet_order and repair_fleet_order.is_valid(): self.orders.append(repair_fleet_order) cur_fighter_capacity, max_fighter_capacity = FleetUtilsAI.get_fighter_capacity_of_fleet( fleet_id) if (fleet.fuel < fleet.maxFuel or cur_fighter_capacity < max_fighter_capacity and get_fleet_position( self.fleet.id) not in fleet_supplyable_system_ids): resupply_fleet_order = MoveUtilsAI.get_resupply_fleet_order( self.fleet) if resupply_fleet_order.is_valid(): self.orders.append(resupply_fleet_order) return # no targets if self.target: # for some targets fleet has to visit systems and therefore fleet visit them system_to_visit = (self.target.get_system() if not self.type == MissionType.PROTECT_REGION else TargetSystem( self._get_target_for_protection_mission())) if not system_to_visit: return orders_to_visit_systems = MoveUtilsAI.create_move_orders_to_system( self.fleet, system_to_visit) # TODO: if fleet doesn't have enough fuel to get to final target, consider resetting Mission for fleet_order in orders_to_visit_systems: self.orders.append(fleet_order) # also generate appropriate final orders fleet_order = self._get_fleet_order_from_target( self.type, self.target if not self.type == MissionType.PROTECT_REGION else system_to_visit) self.orders.append(fleet_order)
def send_colony_ships(colony_fleet_ids, evaluated_planets, mission_type): """sends a list of colony ships to a list of planet_value_pairs""" fleet_pool = colony_fleet_ids[:] try_all = False if mission_type == MissionType.OUTPOST: cost = 20 + outpod_pod_cost() else: try_all = True cost = 20 + colony_pod_cost_turns()[1] if fo.currentTurn() < 50: cost *= 0.4 # will be making fast tech progress so value is underestimated elif fo.currentTurn() < 80: cost *= 0.8 # will be making fast-ish tech progress so value is underestimated potential_targets = [ (pid, (score, specName)) for (pid, (score, specName)) in evaluated_planets if score > (0.8 * cost) and score > MINIMUM_COLONY_SCORE ] debug("Colony/outpost ship matching: fleets %s to planets %s" % (fleet_pool, evaluated_planets)) if try_all: debug("Trying best matches to current colony ships") best_scores = dict(evaluated_planets) potential_targets = [] for pid, ratings in _all_colony_opportunities.items(): for rating in ratings: if rating[0] >= 0.75 * best_scores.get(pid, [9999])[0]: potential_targets.append((pid, rating)) potential_targets.sort(key=itemgetter(1), reverse=True) # added a lot of checking because have been getting mysterious exception, after too many recursions to get info fleet_pool = set(fleet_pool) universe = fo.getUniverse() for fid in fleet_pool: fleet = universe.getFleet(fid) if not fleet or fleet.empty: warning("Bad fleet ( ID %d ) given to colonization routine; will be skipped" % fid) fleet_pool.remove(fid) continue report_str = "Fleet ID (%d): %d ships; species: " % (fid, fleet.numShips) for sid in fleet.shipIDs: ship = universe.getShip(sid) if not ship: report_str += "NoShip, " else: report_str += "%s, " % ship.speciesName debug(report_str) debug("") already_targeted = [] # for planetID_value_pair in evaluatedPlanets: aistate = get_aistate() while fleet_pool and potential_targets: target = potential_targets.pop(0) if target in already_targeted: continue planet_id = target[0] if planet_id in already_targeted: continue planet = universe.getPlanet(planet_id) sys_id = planet.systemID if ( aistate.systemStatus.setdefault(sys_id, {}).setdefault("monsterThreat", 0) > 2000 or fo.currentTurn() < 20 and aistate.systemStatus[sys_id]["monsterThreat"] > 200 ): debug( "Skipping colonization of system %s due to Big Monster, threat %d" % (universe.getSystem(sys_id), aistate.systemStatus[sys_id]["monsterThreat"]) ) already_targeted.append(planet_id) continue # make sure not to run into stationary guards if ExplorationAI.system_could_have_unknown_stationary_guard(sys_id): ExplorationAI.request_emergency_exploration(sys_id) continue this_spec = target[1][1] found_fleets = [] try: this_fleet_list = FleetUtilsAI.get_fleets_for_mission( target_stats={}, min_stats={}, cur_stats={}, starting_system=sys_id, species=this_spec, fleet_pool_set=fleet_pool, fleet_list=found_fleets, ) except Exception as e: error(e, exc_info=True) continue if not this_fleet_list: fleet_pool.update(found_fleets) # just to be safe continue # must have no compatible colony/outpost ships fleet_id = this_fleet_list[0] already_targeted.append(planet_id) ai_target = TargetPlanet(planet_id) aistate.get_fleet_mission(fleet_id).set_target(mission_type, ai_target)
def check_mergers(self, fleet_id=None, context=""): if fleet_id is None: fleet_id = self.fleet.id if self.type not in ( MissionType.MILITARY, MissionType.INVASION, MissionType.ORBITAL_INVASION, MissionType.SECURE, MissionType.ORBITAL_DEFENSE, ): return universe = fo.getUniverse() empire_id = fo.empireID() main_fleet = universe.getFleet(fleet_id) system_id = main_fleet.systemID if system_id == -1: return # can't merge fleets in middle of starlane system_status = foAI.foAIstate.systemStatus[system_id] destroyed_list = list(universe.destroyedObjectIDs(empire_id)) other_fleets_here = [ fid for fid in system_status.get('myFleetsAccessible', []) if fid != fleet_id and fid not in destroyed_list and universe.getFleet(fid).ownedBy(empire_id) ] if not other_fleets_here: return # nothing of record to merge with if not self.target: m_mt0_id = None else: m_mt0_id = self.target.id # TODO consider establishing an AI strategy & tactics planning document for discussing & planning # high level considerations for issues like fleet merger compatible_roles_map = { MissionType.ORBITAL_DEFENSE: [MissionType.ORBITAL_DEFENSE], MissionType.MILITARY: [MissionType.MILITARY], MissionType.ORBITAL_INVASION: [MissionType.ORBITAL_INVASION], MissionType.INVASION: [MissionType.INVASION], } main_fleet_role = foAI.foAIstate.get_fleet_role(fleet_id) for fid in other_fleets_here: fleet_role = foAI.foAIstate.get_fleet_role(fid) if fleet_role not in compatible_roles_map[ main_fleet_role]: # TODO: if fleetRoles such as LongRange start being used, adjust this continue # will only considering subsuming fleets that have a compatible role fleet = universe.getFleet(fid) if not (fleet and (fleet.systemID == system_id)): continue if not (fleet.speed > 0 or main_fleet.speed == 0): # TODO(Cjkjvfnby) Check this condition continue fleet_mission = foAI.foAIstate.get_fleet_mission(fid) do_merge = False need_left = 0 if (main_fleet_role == MissionType.ORBITAL_DEFENSE) or ( fleet_role == MissionType.ORBITAL_DEFENSE): if main_fleet_role == fleet_role: do_merge = True elif (main_fleet_role == MissionType.ORBITAL_INVASION) or ( fleet_role == MissionType.ORBITAL_INVASION): if main_fleet_role == fleet_role: do_merge = False # TODO: could allow merger if both orb invaders and both same target elif not fleet_mission and (main_fleet.speed > 0) and (fleet.speed > 0): do_merge = True else: if not self.target and (main_fleet.speed > 0 or fleet.speed == 0): # print "\t\t\t ** Considering merging fleetA (id: %4d) into fleet (id %d) and former has no targets, will take it. FleetA mission was %s "%(fid, fleetID, fleet_mission) do_merge = True else: target = fleet_mission.target.id if fleet_mission.target else None if target == m_mt0_id: print "Military fleet %d has same target as %s fleet %d and will (at least temporarily) be merged into the latter" % ( fid, fleet_role, fleet_id) do_merge = True # TODO: should probably ensure that fleetA has aggression on now elif main_fleet.speed > 0: neighbors = foAI.foAIstate.systemStatus.get( system_id, {}).get('neighbors', []) if ( target == system_id ) and m_mt0_id in neighbors: # consider 'borrowing' for work in neighbor system # TODO check condition if self.type in (MissionType.MILITARY, MissionType.SECURE): # continue if self.type == MissionType.SECURE: # actually, currently this is probably the only one of all four that should really be possible in this situation need_left = 1.5 * sum([ sysStat.get('fleetThreat', 0) for sysStat in [ foAI.foAIstate.systemStatus.get( neighbor, {}) for neighbor in [ nid for nid in foAI. foAIstate.systemStatus.get( system_id, {}).get( 'neighbors', []) if nid != m_mt0_id ] ] ]) fb_rating = foAI.foAIstate.get_rating(fid) if (need_left < fb_rating.get( 'overall', 0)) and fb_rating.get( 'nships', 0) > 1: do_merge = True if do_merge: FleetUtilsAI.merge_fleet_a_into_b( fid, fleet_id, need_left, context="Order %s of mission %s" % (context, self)) return
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. 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 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 MoveUtilsAI.can_travel_to_system( fleet_id, fleet_mission.get_location_target(), target, ensure_return=True): 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 _calculate_exploration_priority(): """Calculates the demand for scouts by unexplored systems.""" empire = fo.getEmpire() num_unexplored_systems = len(ExplorationAI.border_unexplored_system_ids) num_scouts = sum([foAI.foAIstate.fleetStatus.get(fid, {}).get('nships', 0) for fid in FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.EXPLORATION)]) production_queue = empire.productionQueue queued_scout_ships = 0 for queue_index in range(0, len(production_queue)): element = production_queue[queue_index] if element.buildType == EmpireProductionTypes.BT_SHIP: if foAI.foAIstate.get_ship_role(element.designID) == ShipRoleType.CIVILIAN_EXPLORATION: queued_scout_ships += element.remaining * element.blocksize mil_ships = MilitaryAI.get_num_military_ships() # intent of the following calc is essentially # new_scouts_needed = min(need_cap_A, need_cap_B, base_need) - already_got_or_queued # where need_cap_A is to help prevent scouting needs from swamping military needs, and # need_cap_B is to help regulate investment into scouting while the empire is small. # These caps could perhaps instead be tied more directly to military priority and # total empire production. desired_number_of_scouts = int(min(4 + mil_ships/5, 4 + fo.currentTurn()/50.0, 2 + num_unexplored_systems**0.5)) scouts_needed = max(0, desired_number_of_scouts - (num_scouts + queued_scout_ships)) exploration_priority = int(40 * scouts_needed) print print "Number of Scouts: %s" % num_scouts print "Number of Unexplored systems: %s" % num_unexplored_systems print "Military size: %s" % mil_ships print "Priority for scouts: %s" % exploration_priority return exploration_priority
def check_mergers(self, context=""): """ If possible and reasonable, merge this fleet with others. :param context: Context of the function call for logging purposes :type context: str """ if self.type not in MERGEABLE_MISSION_TYPES: return universe = fo.getUniverse() empire_id = fo.empireID() fleet_id = self.fleet.id main_fleet = universe.getFleet(fleet_id) system_id = main_fleet.systemID if system_id == INVALID_ID: return # can't merge fleets in middle of starlane system_status = foAI.foAIstate.systemStatus[system_id] # if a combat mission, and only have final order (so must be at final target), don't try # merging if there is no local threat (it tends to lead to fleet object churn) if self.type in COMBAT_MISSION_TYPES and len( self.orders) == 1 and not system_status.get('totalThreat', 0): return destroyed_list = list(universe.destroyedObjectIDs(empire_id)) other_fleets_here = [ fid for fid in system_status.get('myFleetsAccessible', []) if fid != fleet_id and fid not in destroyed_list and universe.getFleet(fid).ownedBy(empire_id) ] if not other_fleets_here: return target_id = self.target.id if self.target else None main_fleet_role = foAI.foAIstate.get_fleet_role(fleet_id) for fid in other_fleets_here: fleet_role = foAI.foAIstate.get_fleet_role(fid) if fleet_role not in COMPATIBLE_ROLES_MAP[main_fleet_role]: continue fleet = universe.getFleet(fid) if not fleet or fleet.systemID != system_id or len( fleet.shipIDs) == 0: continue if not (fleet.speed > 0 or main_fleet.speed == 0): # TODO(Cjkjvfnby) Check this condition continue fleet_mission = foAI.foAIstate.get_fleet_mission(fid) do_merge = False need_left = 0 if (main_fleet_role == MissionType.ORBITAL_DEFENSE) or ( fleet_role == MissionType.ORBITAL_DEFENSE): if main_fleet_role == fleet_role: do_merge = True elif (main_fleet_role == MissionType.ORBITAL_INVASION) or ( fleet_role == MissionType.ORBITAL_INVASION): if main_fleet_role == fleet_role: do_merge = False # TODO: could allow merger if both orb invaders and both same target elif not fleet_mission and (main_fleet.speed > 0) and (fleet.speed > 0): do_merge = True else: if not self.target and (main_fleet.speed > 0 or fleet.speed == 0): do_merge = True else: target = fleet_mission.target.id if fleet_mission.target else None if target == target_id: info( "Military fleet %d (%d ships) has same target as %s fleet %d (%d ships). Merging former " "into latter." % (fid, fleet.numShips, fleet_role, fleet_id, len(main_fleet.shipIDs))) # TODO: should probably ensure that fleetA has aggression on now do_merge = float(min( main_fleet.speed, fleet.speed)) / max( main_fleet.speed, fleet.speed) >= 0.6 elif main_fleet.speed > 0: neighbors = foAI.foAIstate.systemStatus.get( system_id, {}).get('neighbors', []) if target == system_id and target_id in neighbors and self.type == MissionType.SECURE: # consider 'borrowing' for work in neighbor system # TODO check condition need_left = 1.5 * sum( foAI.foAIstate.systemStatus.get(nid, {}).get( 'fleetThreat', 0) for nid in neighbors if nid != target_id) fleet_rating = CombatRatingsAI.get_fleet_rating( fid) if need_left < fleet_rating: do_merge = True if do_merge: FleetUtilsAI.merge_fleet_a_into_b( fid, fleet_id, need_left, context="Order %s of mission %s" % (context, self)) return
def assign_invasion_bases(): """Assign our troop bases to invasion targets.""" universe = fo.getUniverse() all_troopbase_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.ORBITAL_INVASION) available_troopbase_fleet_ids = set( FleetUtilsAI.extract_fleet_ids_without_mission_types( all_troopbase_fleet_ids)) for fid in list(available_troopbase_fleet_ids): if fid not in available_troopbase_fleet_ids: # entry may have been discarded in previous loop iterations continue fleet = universe.getFleet(fid) if not fleet: continue sys_id = fleet.systemID system = universe.getSystem(sys_id) available_planets = set(system.planetIDs).intersection( set(foAI.foAIstate.qualifyingTroopBaseTargets.keys())) print "Considering Base Troopers in %s, found planets %s and registered targets %s with status %s" % ( system.name, list(system.planetIDs), available_planets, [ (pid, foAI.foAIstate.qualifyingTroopBaseTargets[pid]) for pid in available_planets ]) targets = [ pid for pid in available_planets if foAI.foAIstate.qualifyingTroopBaseTargets[pid][1] != -1 ] if not targets: print "Failure: found no valid target for troop base in system %s" % system continue status = foAI.foAIstate.systemStatus.get(sys_id, {}) local_base_troops = set(status.get( 'myfleets', [])).intersection(available_troopbase_fleet_ids) target_id = INVALID_ID best_score = -1 target_troops = 0 for pid, (p_score, p_troops) in assign_invasion_values(targets).items(): if p_score > best_score: best_score = p_score target_id = pid target_troops = p_troops if target_id == INVALID_ID: continue local_base_troops.discard(fid) found_fleets = [] troops_needed = max( 0, target_troops - FleetUtilsAI.count_troops_in_fleet(fid)) found_stats = {} min_stats = {'rating': 0, 'troopCapacity': troops_needed} target_stats = { 'rating': 10, 'troopCapacity': troops_needed + _TROOPS_SAFETY_MARGIN } FleetUtilsAI.get_fleets_for_mission(target_stats, min_stats, found_stats, starting_system=sys_id, fleet_pool_set=local_base_troops, fleet_list=found_fleets) for fid2 in found_fleets: FleetUtilsAI.merge_fleet_a_into_b(fid2, fid) available_troopbase_fleet_ids.discard(fid2) available_troopbase_fleet_ids.discard(fid) foAI.foAIstate.qualifyingTroopBaseTargets[target_id][ 1] = -1 # TODO: should probably delete target = universe_object.Planet(target_id) fleet_mission = foAI.foAIstate.get_fleet_mission(fid) fleet_mission.set_target(MissionType.ORBITAL_INVASION, target)
def evaluate_invasion_planet(planet_id, secure_fleet_missions, verbose=True): """Return the invasion value (score, troops) of a planet.""" detail = [] building_values = { "BLD_IMPERIAL_PALACE": 1000, "BLD_CULTURE_ARCHIVES": 1000, "BLD_AUTO_HISTORY_ANALYSER": 100, "BLD_SHIPYARD_BASE": 100, "BLD_SHIPYARD_ORG_ORB_INC": 200, "BLD_SHIPYARD_ORG_XENO_FAC": 200, "BLD_SHIPYARD_ORG_CELL_GRO_CHAMB": 200, "BLD_SHIPYARD_CON_NANOROBO": 300, "BLD_SHIPYARD_CON_GEOINT": 400, "BLD_SHIPYARD_CON_ADV_ENGINE": 1000, "BLD_SHIPYARD_AST": 300, "BLD_SHIPYARD_AST_REF": 1000, "BLD_SHIPYARD_ENRG_SOLAR": 1500, "BLD_INDUSTRY_CENTER": 500, "BLD_GAS_GIANT_GEN": 200, "BLD_SOL_ORB_GEN": 800, "BLD_BLACK_HOLE_POW_GEN": 2000, "BLD_ENCLAVE_VOID": 500, "BLD_NEUTRONIUM_EXTRACTOR": 2000, "BLD_NEUTRONIUM_SYNTH": 2000, "BLD_NEUTRONIUM_FORGE": 1000, "BLD_CONC_CAMP": 100, "BLD_BIOTERROR_PROJECTOR": 1000, "BLD_SHIPYARD_ENRG_COMP": 3000, } # TODO: add more factors, as used for colonization universe = fo.getUniverse() empire_id = fo.empireID() max_jumps = 8 planet = universe.getPlanet(planet_id) if planet is None: # TODO: exclude planets with stealth higher than empireDetection print "invasion AI couldn't access any info for planet id %d" % planet_id return [0, 0] sys_partial_vis_turn = get_partial_visibility_turn(planet.systemID) planet_partial_vis_turn = get_partial_visibility_turn(planet_id) if planet_partial_vis_turn < sys_partial_vis_turn: print "invasion AI couldn't get current info on planet id %d (was stealthed at last sighting)" % planet_id # TODO: track detection strength, order new scouting when it goes up return [ 0, 0 ] # last time we had partial vis of the system, the planet was stealthed to us species_name = planet.speciesName species = fo.getSpecies(species_name) if not species or AIDependencies.TAG_DESTROYED_ON_CONQUEST in species.tags: # this call iterates over this Empire's available species with which it could colonize after an invasion planet_eval = ColonisationAI.assign_colonisation_values( [planet_id], MissionType.INVASION, None, detail) pop_val = max( 0.75 * planet_eval.get(planet_id, [0])[0], ColonisationAI.evaluate_planet(planet_id, MissionType.OUTPOST, None, detail)) else: pop_val = ColonisationAI.evaluate_planet(planet_id, MissionType.INVASION, species_name, detail) bld_tally = 0 for bldType in [ universe.getBuilding(bldg).buildingTypeName for bldg in planet.buildingIDs ]: bval = building_values.get(bldType, 50) bld_tally += bval detail.append("%s: %d" % (bldType, bval)) tech_tally = 0 for unlocked_tech in AIDependencies.SPECIES_TECH_UNLOCKS.get( species_name, []): if not tech_is_complete(unlocked_tech): rp_cost = fo.getTech(unlocked_tech).researchCost(empire_id) tech_tally += rp_cost * 4 detail.append("%s: %d" % (unlocked_tech, rp_cost * 4)) p_sys_id = planet.systemID capitol_id = PlanetUtilsAI.get_capital() least_jumps_path = [] clear_path = True if capitol_id: homeworld = universe.getPlanet(capitol_id) if homeworld: home_system_id = homeworld.systemID eval_system_id = planet.systemID if (home_system_id != INVALID_ID) and (eval_system_id != INVALID_ID): least_jumps_path = list( universe.leastJumpsPath(home_system_id, eval_system_id, empire_id)) max_jumps = len(least_jumps_path) system_status = foAI.foAIstate.systemStatus.get(p_sys_id, {}) system_fleet_treat = system_status.get('fleetThreat', 1000) system_monster_threat = system_status.get('monsterThreat', 0) sys_total_threat = system_fleet_treat + system_monster_threat + system_status.get( 'planetThreat', 0) max_path_threat = system_fleet_treat mil_ship_rating = MilitaryAI.cur_best_mil_ship_rating() for path_sys_id in least_jumps_path: path_leg_status = foAI.foAIstate.systemStatus.get(path_sys_id, {}) path_leg_threat = path_leg_status.get( 'fleetThreat', 1000) + path_leg_status.get('monsterThreat', 0) if path_leg_threat > 0.5 * mil_ship_rating: clear_path = False if path_leg_threat > max_path_threat: max_path_threat = path_leg_threat pop = planet.currentMeterValue(fo.meterType.population) target_pop = planet.currentMeterValue(fo.meterType.targetPopulation) troops = planet.currentMeterValue(fo.meterType.troops) max_troops = planet.currentMeterValue(fo.meterType.maxTroops) # TODO: refactor troop determination into function for use in mid-mission updates and also consider defender techs max_troops += AIDependencies.TROOPS_PER_POP * (target_pop - pop) this_system = universe.getSystem(p_sys_id) secure_targets = [p_sys_id] + list(this_system.planetIDs) system_secured = False for mission in secure_fleet_missions: if system_secured: break secure_fleet_id = mission.fleet.id s_fleet = universe.getFleet(secure_fleet_id) if not s_fleet or s_fleet.systemID != p_sys_id: continue if mission.type == MissionType.SECURE: target_obj = mission.target.get_object() if target_obj is not None and target_obj.id in secure_targets: system_secured = True break system_secured = system_secured and system_status.get('myFleetRating', 0) if verbose: print("Invasion eval of %s\n" " - maxShields: %.1f\n" " - sysFleetThreat: %.1f\n" " - sysMonsterThreat: %.1f") % ( planet, planet.currentMeterValue(fo.meterType.maxShield), system_fleet_treat, system_monster_threat) supply_val = 0 enemy_val = 0 if planet.owner != -1: # value in taking this away from an enemy enemy_val = 20 * ( planet.currentMeterValue(fo.meterType.targetIndustry) + 2 * planet.currentMeterValue(fo.meterType.targetResearch)) if p_sys_id in ColonisationAI.annexable_system_ids: # TODO: extend to rings supply_val = 100 elif p_sys_id in ColonisationAI.annexable_ring1: supply_val = 200 elif p_sys_id in ColonisationAI.annexable_ring2: supply_val = 300 elif p_sys_id in ColonisationAI.annexable_ring3: supply_val = 400 if max_path_threat > 0.5 * mil_ship_rating: if max_path_threat < 3 * mil_ship_rating: supply_val *= 0.5 else: supply_val *= 0.2 # devalue invasions that would require too much military force threat_factor = min( 1, 0.2 * MilitaryAI.get_tot_mil_rating() / (sys_total_threat + 0.001))**2 design_id, _, locs = ProductionAI.get_best_ship_info( PriorityType.PRODUCTION_INVASION) if not locs or not universe.getPlanet(locs[0]): # We are in trouble anyway, so just calculate whatever approximation... build_time = 4 planned_troops = troops if system_secured else min( troops + max_jumps + build_time, max_troops) planned_troops += .01 # we must attack with more troops than there are defenders troop_cost = math.ceil((planned_troops + _TROOPS_SAFETY_MARGIN) / 6.0) * 20 * FleetUtilsAI.get_fleet_upkeep() else: loc = locs[0] species_here = universe.getPlanet(loc).speciesName design = fo.getShipDesign(design_id) cost_per_ship = design.productionCost(empire_id, loc) build_time = design.productionTime(empire_id, loc) troops_per_ship = CombatRatingsAI.weight_attack_troops( design.troopCapacity, CombatRatingsAI.get_species_troops_grade(species_here)) planned_troops = troops if system_secured else min( troops + max_jumps + build_time, max_troops) planned_troops += .01 # we must attack with more troops than there are defenders ships_needed = math.ceil( (planned_troops + _TROOPS_SAFETY_MARGIN) / float(troops_per_ship)) troop_cost = ships_needed * cost_per_ship # fleet upkeep is already included in query from server # apply some bias to expensive operations normalized_cost = float(troop_cost) / max(fo.getEmpire().productionPoints, 1) normalized_cost = max(1, normalized_cost) cost_score = (normalized_cost**2 / 50.0) * troop_cost base_score = pop_val + supply_val + bld_tally + tech_tally + enemy_val - cost_score planet_score = retaliation_risk_factor(planet.owner) * threat_factor * max( 0, base_score) if clear_path: planet_score *= 1.5 if verbose: print(' - planet score: %.2f\n' ' - troop score: %.2f\n' ' - projected troop cost: %.1f\n' ' - threat factor: %s\n' ' - planet detail: %s\n' ' - popval: %.1f\n' ' - supplyval: %.1f\n' ' - bldval: %s\n' ' - enemyval: %s') % (planet_score, planned_troops, troop_cost, threat_factor, detail, pop_val, supply_val, bld_tally, enemy_val) return [planet_score, planned_troops]
def get_invasion_fleets(): invasion_timer.start("gathering initial info") universe = fo.getUniverse() empire = fo.getEmpire() empire_id = fo.empireID() home_system_id = PlanetUtilsAI.get_capital_sys_id() visible_system_ids = list(foAI.foAIstate.visInteriorSystemIDs) + list( foAI.foAIstate.visBorderSystemIDs) if home_system_id != INVALID_ID: accessible_system_ids = [ sys_id for sys_id in visible_system_ids if (sys_id != INVALID_ID) and universe.systemsConnected(sys_id, home_system_id, empire_id) ] else: print "Warning: Empire has no identifiable homeworld; will treat all visible planets as accessible." # TODO: check if any troop ships owned, use their system as home system accessible_system_ids = visible_system_ids acessible_planet_ids = PlanetUtilsAI.get_planets_in__systems_ids( accessible_system_ids) all_owned_planet_ids = PlanetUtilsAI.get_all_owned_planet_ids( acessible_planet_ids) # includes unpopulated outposts all_populated_planets = PlanetUtilsAI.get_populated_planet_ids( acessible_planet_ids) # includes unowned natives empire_owned_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire( universe.planetIDs) invadable_planet_ids = set(all_owned_planet_ids).union( all_populated_planets) - set(empire_owned_planet_ids) invasion_targeted_planet_ids = get_invasion_targeted_planet_ids( universe.planetIDs, MissionType.INVASION) invasion_targeted_planet_ids.extend( get_invasion_targeted_planet_ids(universe.planetIDs, MissionType.ORBITAL_INVASION)) all_invasion_targeted_system_ids = set( PlanetUtilsAI.get_systems(invasion_targeted_planet_ids)) invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.INVASION) num_invasion_fleets = len( FleetUtilsAI.extract_fleet_ids_without_mission_types( invasion_fleet_ids)) print "Current Invasion Targeted SystemIDs: ", PlanetUtilsAI.sys_name_ids( AIstate.invasionTargetedSystemIDs) print "Current Invasion Targeted PlanetIDs: ", PlanetUtilsAI.planet_name_ids( invasion_targeted_planet_ids) print invasion_fleet_ids and "Invasion Fleet IDs: %s" % invasion_fleet_ids or "Available Invasion Fleets: 0" print "Invasion Fleets Without Missions: %s" % num_invasion_fleets invasion_timer.start("planning troop base production") reserved_troop_base_targets = [] if foAI.foAIstate.character.may_invade_with_bases(): available_pp = {} for el in empire.planetsWithAvailablePP: # keys are sets of ints; data is doubles avail_pp = el.data() for pid in el.key(): available_pp[pid] = avail_pp # For planning base trooper invasion targets we have a two-pass system. (1) In the first pass we consider all # the invasion targets and figure out which ones appear to be suitable for using base troopers against (i.e., we # already have a populated planet in the same system that could build base troopers) and we have at least a # minimal amount of PP available, and (2) in the second pass we go through the reserved base trooper target list # and check to make sure that there does not appear to be too much military action still needed before the # target is ready to be invaded, we double check that not too many base troopers would be needed, and if things # look clear then we queue up the base troopers on the Production Queue and keep track of where we are building # them, and how many; we may also disqualify and remove previously qualified targets (in case, for example, # we lost our base trooper source planet since it was first added to list). # # For planning and tracking base troopers under construction, we use a dictionary store in # foAI.foAIstate.qualifyingTroopBaseTargets, keyed by the invasion target planet ID. We only store values # for invasion targets that appear likely to be suitable for base trooper use, and store a 2-item list. # The first item in this list is the ID of the planet where we expect to build the base troopers, and the second # entry initially is set to INVALID_ID (-1). The presence of this entry in qualifyingTroopBaseTargets # flags this target as being reserved as a base-trooper invasion target. # In the second pass, if/when we actually start construction, then we modify the record, replacing that second # value with the ID of the planet where the troopers are actually being built. (Right now that is always the # same as the source planet originally identified, but we could consider reevaluating that, or use that second # value to instead record how many base troopers have been queued, so that on later turns we can assess if the # process got delayed & perhaps more troopers need to be queued). secure_ai_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types( [MissionType.SECURE]) # Pass 1: identify qualifying base troop invasion targets for pid in invadable_planet_ids: # TODO: reorganize if pid in foAI.foAIstate.qualifyingTroopBaseTargets: continue planet = universe.getPlanet(pid) if not planet: continue sys_id = planet.systemID sys_partial_vis_turn = get_partial_visibility_turn(sys_id) planet_partial_vis_turn = get_partial_visibility_turn(pid) if planet_partial_vis_turn < sys_partial_vis_turn: continue best_base_planet = INVALID_ID best_trooper_count = 0 for pid2 in state.get_empire_planets_by_system( sys_id, include_outposts=False): if available_pp.get( pid2, 0 ) < 2: # TODO: improve troop base PP sufficiency determination break planet2 = universe.getPlanet(pid2) if not planet2 or planet2.speciesName not in ColonisationAI.empire_ship_builders: continue best_base_trooper_here = \ ProductionAI.get_best_ship_info(PriorityType.PRODUCTION_ORBITAL_INVASION, pid2)[1] if not best_base_trooper_here: continue troops_per_ship = best_base_trooper_here.troopCapacity if not troops_per_ship: continue species_troop_grade = CombatRatingsAI.get_species_troops_grade( planet2.speciesName) troops_per_ship = CombatRatingsAI.weight_attack_troops( troops_per_ship, species_troop_grade) if troops_per_ship > best_trooper_count: best_base_planet = pid2 best_trooper_count = troops_per_ship if best_base_planet != INVALID_ID: foAI.foAIstate.qualifyingTroopBaseTargets.setdefault( pid, [best_base_planet, INVALID_ID]) # Pass 2: for each target previously identified for base troopers, check that still qualifies and # check how many base troopers would be needed; if reasonable then queue up the troops and record this in # foAI.foAIstate.qualifyingTroopBaseTargets for pid in foAI.foAIstate.qualifyingTroopBaseTargets.keys(): planet = universe.getPlanet(pid) if planet and planet.owner == empire_id: del foAI.foAIstate.qualifyingTroopBaseTargets[pid] continue if pid in invasion_targeted_planet_ids: # TODO: consider overriding standard invasion mission continue if foAI.foAIstate.qualifyingTroopBaseTargets[pid][1] != -1: reserved_troop_base_targets.append(pid) if planet: all_invasion_targeted_system_ids.add(planet.systemID) # TODO: evaluate changes to situation, any more troops needed, etc. continue # already building for here _, planet_troops = evaluate_invasion_planet( pid, secure_ai_fleet_missions, False) sys_id = planet.systemID this_sys_status = foAI.foAIstate.systemStatus.get(sys_id, {}) troop_tally = 0 for _fid in this_sys_status.get('myfleets', []): troop_tally += FleetUtilsAI.count_troops_in_fleet(_fid) if troop_tally > planet_troops: # base troopers appear unneeded del foAI.foAIstate.qualifyingTroopBaseTargets[pid] continue if (planet.currentMeterValue(fo.meterType.shield) > 0 and (this_sys_status.get('myFleetRating', 0) < 0.8 * this_sys_status.get('totalThreat', 0) or this_sys_status.get('myFleetRatingVsPlanets', 0) < this_sys_status.get('planetThreat', 0))): # this system not secured, so ruling out invasion base troops for now # don't immediately delete from qualifyingTroopBaseTargets or it will be opened up for regular troops continue loc = foAI.foAIstate.qualifyingTroopBaseTargets[pid][0] best_base_trooper_here = ProductionAI.get_best_ship_info( PriorityType.PRODUCTION_ORBITAL_INVASION, loc)[1] loc_planet = universe.getPlanet(loc) if best_base_trooper_here is None: # shouldn't be possible at this point, but just to be safe warn( "Could not find a suitable orbital invasion design at %s" % loc_planet) continue # TODO: have TroopShipDesigner give the expected number of troops including species effects directly troops_per_ship = best_base_trooper_here.troopCapacity species_troop_grade = CombatRatingsAI.get_species_troops_grade( loc_planet.speciesName) troops_per_ship = CombatRatingsAI.weight_attack_troops( troops_per_ship, species_troop_grade) if not troops_per_ship: warn( "The best orbital invasion design at %s seems not to have any troop capacity." % loc_planet) continue _, col_design, build_choices = ProductionAI.get_best_ship_info( PriorityType.PRODUCTION_ORBITAL_INVASION, loc) if not col_design: continue if loc not in build_choices: warn( 'Best troop design %s can not be produced at planet with id: %s\d' % (col_design, build_choices)) continue n_bases = math.ceil( (planet_troops + 1) / troops_per_ship) # TODO: reconsider this +1 safety factor # TODO: evaluate cost and time-to-build of best base trooper here versus cost and time-to-build-and-travel # for best regular trooper elsewhere # For now, we assume what building base troopers is best so long as either (1) we would need no more than # MAX_BASE_TROOPERS_POOR_INVADERS base troop ships, or (2) our base troopers have more than 1 trooper per # ship and we would need no more than MAX_BASE_TROOPERS_GOOD_INVADERS base troop ships if (n_bases <= MAX_BASE_TROOPERS_POOR_INVADERS or (troops_per_ship > 1 and n_bases <= MAX_BASE_TROOPERS_GOOD_INVADERS)): print "ruling out base invasion troopers for %s due to high number (%d) required." % ( planet, n_bases) del foAI.foAIstate.qualifyingTroopBaseTargets[pid] continue print "Invasion base planning, need %d troops at %d pership, will build %d ships." % ( (planet_troops + 1), troops_per_ship, n_bases) retval = fo.issueEnqueueShipProductionOrder(col_design.id, loc) print "Enqueueing %d Troop Bases at %s for %s" % ( n_bases, PlanetUtilsAI.planet_name_ids( [loc]), PlanetUtilsAI.planet_name_ids([pid])) if retval != 0: all_invasion_targeted_system_ids.add(planet.systemID) reserved_troop_base_targets.append(pid) foAI.foAIstate.qualifyingTroopBaseTargets[pid][1] = loc fo.issueChangeProductionQuantityOrder( empire.productionQueue.size - 1, 1, int(n_bases)) fo.issueRequeueProductionOrder(empire.productionQueue.size - 1, 0) invasion_timer.start("evaluating target planets") # TODO: check if any invasion_targeted_planet_ids need more troops assigned evaluated_planet_ids = list( set(invadable_planet_ids) - set(invasion_targeted_planet_ids) - set(reserved_troop_base_targets)) evaluated_planets = assign_invasion_values(evaluated_planet_ids) sorted_planets = [(pid, pscore % 10000, ptroops) for pid, (pscore, ptroops) in evaluated_planets.items()] sorted_planets.sort(key=lambda x: x[1], reverse=True) sorted_planets = [(pid, pscore % 10000, ptroops) for pid, pscore, ptroops in sorted_planets] invasion_table = Table( [Text('Planet'), Float('Score'), Text('Species'), Float('Troops')], table_name="Potential Targets for Invasion Turn %d" % fo.currentTurn()) for pid, pscore, ptroops in sorted_planets: planet = universe.getPlanet(pid) invasion_table.add_row([ planet, pscore, planet and planet.speciesName or "unknown", ptroops ]) print invasion_table.print_table() sorted_planets = filter(lambda x: x[1] > 0, sorted_planets) # export opponent planets for other AI modules AIstate.opponentPlanetIDs = [pid for pid, _, _ in sorted_planets] AIstate.invasionTargets = sorted_planets # export invasion targeted systems for other AI modules AIstate.invasionTargetedSystemIDs = list(all_invasion_targeted_system_ids) invasion_timer.stop(section_name="evaluating %d target planets" % (len(evaluated_planet_ids))) invasion_timer.stop_print_and_clear()
def assign_invasion_fleets_to_invade(): """Assign fleet targets to invadable planets.""" universe = fo.getUniverse() empire = fo.getEmpire() fleet_suppliable_system_ids = empire.fleetSupplyableSystemIDs fleet_suppliable_planet_ids = PlanetUtilsAI.get_planets_in__systems_ids( fleet_suppliable_system_ids) all_troopbase_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( EnumsAI.AIFleetMissionType.FLEET_MISSION_ORBITAL_INVASION) available_troopbase_fleet_ids = set( FleetUtilsAI.extract_fleet_ids_without_mission_types( all_troopbase_fleet_ids)) for fid in list(available_troopbase_fleet_ids): if fid not in available_troopbase_fleet_ids: # TODO: I do not see how this check makes sense, maybe remove? continue fleet = universe.getFleet(fid) if not fleet: continue sys_id = fleet.systemID system = universe.getSystem(sys_id) available_planets = set(system.planetIDs).intersection( set(foAI.foAIstate.qualifyingTroopBaseTargets.keys())) print "Considering Base Troopers in %s, found planets %s and registered targets %s with status %s" % ( system.name, list(system.planetIDs), available_planets, [ (pid, foAI.foAIstate.qualifyingTroopBaseTargets[pid]) for pid in available_planets ]) targets = [ pid for pid in available_planets if foAI.foAIstate.qualifyingTroopBaseTargets[pid][1] != -1 ] if not targets: print "Error: found no valid target for troop base in system %s (%d)" % ( system.name, sys_id) continue status = foAI.foAIstate.systemStatus.get(sys_id, {}) local_base_troops = set(status.get( 'myfleets', [])).intersection(available_troopbase_fleet_ids) troop_capacity_tally = 0 for fid2 in local_base_troops: troop_capacity_tally += FleetUtilsAI.count_troops_in_fleet(fid2) target_id = -1 best_score = -1 target_troops = 0 for pid, rating in assign_invasion_values( targets, EnumsAI.AIFleetMissionType.FLEET_MISSION_INVASION, fleet_suppliable_planet_ids, empire).items(): p_score, p_troops = rating if p_score > best_score: best_score = p_score target_id = pid target_troops = p_troops if target_id != -1: local_base_troops.discard(fid) found_fleets = [] troops_needed = max( 0, target_troops - FleetUtilsAI.count_troops_in_fleet(fid)) found_stats = {} min_stats = {'rating': 0, 'troopCapacity': troops_needed} target_stats = {'rating': 10, 'troopCapacity': troops_needed} FleetUtilsAI.get_fleets_for_mission( 1, target_stats, min_stats, found_stats, "", systems_to_check=[sys_id], systems_checked=[], fleet_pool_set=local_base_troops, fleet_list=found_fleets, verbose=False) for fid2 in found_fleets: FleetUtilsAI.merge_fleet_a_into_b(fid2, fid) available_troopbase_fleet_ids.discard(fid2) available_troopbase_fleet_ids.discard(fid) foAI.foAIstate.qualifyingTroopBaseTargets[target_id][ 1] = -1 # TODO: should probably delete target = universe_object.Planet(target_id) fleet_mission = foAI.foAIstate.get_fleet_mission(fid) fleet_mission.add_target( EnumsAI.AIFleetMissionType.FLEET_MISSION_ORBITAL_INVASION, target) invasion_fleet_ids = AIstate.invasionFleetIDs send_invasion_fleets(invasion_fleet_ids, AIstate.invasionTargets, EnumsAI.AIFleetMissionType.FLEET_MISSION_INVASION) all_invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( EnumsAI.AIFleetMissionType.FLEET_MISSION_INVASION) for fid in FleetUtilsAI.extract_fleet_ids_without_mission_types( all_invasion_fleet_ids): this_mission = foAI.foAIstate.get_fleet_mission(fid) this_mission.check_mergers( context="Post-send consolidation of unassigned troops")
def get_tot_mil_rating(): return sum( CombatRatingsAI.get_fleet_rating(fleet_id) for fleet_id in FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY))
def __clean_fleet_roles(self, just_resumed=False): """Removes fleetRoles if a fleet has been lost, and update fleet Ratings.""" for sys_id in self.systemStatus: self.systemStatus[sys_id]['myFleetRating'] = 0 self.systemStatus[sys_id]['myFleetRatingVsPlanets'] = 0 universe = fo.getUniverse() ok_fleets = FleetUtilsAI.get_empire_fleet_ids() fleet_list = sorted(list(self.__fleetRoleByID)) ship_count = 0 destroyed_object_ids = universe.destroyedObjectIDs(fo.empireID()) fleet_table = Table([ Text('Fleet'), Float('Rating'), Float('Troops'), Text('Location'), Text('Destination') ], table_name="Fleet Summary Turn %d" % fo.currentTurn()) for fleet_id in fleet_list: status = self.fleetStatus.setdefault(fleet_id, {}) rating = CombatRatingsAI.get_fleet_rating( fleet_id, self.get_standard_enemy()) troops = FleetUtilsAI.count_troops_in_fleet(fleet_id) old_sys_id = status.get('sysID', -2) fleet = universe.getFleet(fleet_id) if fleet: sys_id = fleet.systemID if old_sys_id in [-2, -1]: old_sys_id = sys_id status['nships'] = len(fleet.shipIDs) ship_count += status['nships'] else: # can still retrieve a fleet object even if fleet was just destroyed, so shouldn't get here # however,this has been observed happening, and is the reason a fleet check was added a few lines below. # Not at all sure how this came about, but was throwing off threat assessments sys_id = old_sys_id if fleet_id not in ok_fleets: # or fleet.empty: if (fleet and self.__fleetRoleByID.get(fleet_id, -1) != -1 and fleet_id not in destroyed_object_ids and any(ship_id not in destroyed_object_ids for ship_id in fleet.shipIDs)): if not just_resumed: fleetsLostBySystem.setdefault(old_sys_id, []).append( max(rating, MilitaryAI.MinThreat)) if fleet_id in self.__fleetRoleByID: del self.__fleetRoleByID[fleet_id] if fleet_id in self.__aiMissionsByFleetID: del self.__aiMissionsByFleetID[fleet_id] if fleet_id in self.fleetStatus: del self.fleetStatus[fleet_id] continue else: # fleet in ok fleets this_sys = universe.getSystem(sys_id) next_sys = universe.getSystem(fleet.nextSystemID) fleet_table.add_row([ fleet, rating, troops, this_sys or 'starlane', next_sys or '-', ]) status['rating'] = rating if next_sys: status['sysID'] = next_sys.id elif this_sys: status['sysID'] = this_sys.id else: main_mission = self.get_fleet_mission(fleet_id) main_mission_type = (main_mission.getAIMissionTypes() + [-1])[0] if main_mission_type != -1: targets = main_mission.getAITargets(main_mission_type) if targets: m_mt0 = targets[0] if isinstance(m_mt0.target_type, System): status[ 'sysID'] = m_mt0.target.id # hmm, but might still be a fair ways from here fleet_table.print_table() self.shipCount = ship_count # Next string used in charts. Don't modify it! print "Empire Ship Count: ", ship_count print "Empire standard fighter summary: ", CombatRatingsAI.get_empire_standard_fighter( ).get_stats() print "------------------------"
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 assign_military_fleets_to_systems(use_fleet_id_list=None, allocations=None, round=1): # assign military fleets to military theater systems global _military_allocations universe = fo.getUniverse() if allocations is None: allocations = [] doing_main = (use_fleet_id_list is None) aistate = get_aistate() if doing_main: aistate.misc['ReassignedFleetMissions'] = [] base_defense_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.ORBITAL_DEFENSE) unassigned_base_defense_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types(base_defense_ids) for fleet_id in unassigned_base_defense_ids: fleet = universe.getFleet(fleet_id) if not fleet: continue sys_id = fleet.systemID target = TargetSystem(sys_id) fleet_mission = aistate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() mission_type = MissionType.ORBITAL_DEFENSE fleet_mission.set_target(mission_type, target) all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) if not all_military_fleet_ids: _military_allocations = [] return avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids) these_allocations = _military_allocations debug("==================================================") debug("Assigning military fleets") debug("---------------------------------") else: avail_mil_fleet_ids = list(use_fleet_id_list) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids) these_allocations = allocations # send_for_repair(mil_needing_repair_ids) #currently, let get taken care of by AIFleetMission.generate_fleet_orders() # get systems to defend avail_mil_fleet_ids = set(avail_mil_fleet_ids) for sys_id, alloc, minalloc, rvp, takeAny in these_allocations: if not doing_main and not avail_mil_fleet_ids: break debug("Allocating for: %s", TargetSystem(sys_id)) found_fleets = [] found_stats = {} ensure_return = sys_id not in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs) these_fleets = FleetUtilsAI.get_fleets_for_mission( target_stats={'rating': alloc, 'ratingVsPlanets': rvp, 'target_system': TargetSystem(sys_id)}, min_stats={'rating': minalloc, 'ratingVsPlanets': rvp, 'target_system': TargetSystem(sys_id)}, cur_stats=found_stats, starting_system=sys_id, fleet_pool_set=avail_mil_fleet_ids, fleet_list=found_fleets, ensure_return=ensure_return) if not these_fleets: debug("Could not allocate any fleets.") if not found_fleets or not (FleetUtilsAI.stats_meet_reqs(found_stats, {'rating': minalloc}) or takeAny): if doing_main: if _verbose_mil_reporting: debug("NO available/suitable military allocation for system %d ( %s ) " "-- requested allocation %8d, found available rating %8d in fleets %s" % (sys_id, universe.getSystem(sys_id).name, minalloc, found_stats.get('rating', 0), found_fleets)) avail_mil_fleet_ids.update(found_fleets) continue else: these_fleets = found_fleets else: debug("Assigning fleets %s to target %s", these_fleets, TargetSystem(sys_id)) if doing_main and _verbose_mil_reporting: debug("FULL+ military allocation for system %d ( %s )" " -- requested allocation %8d, got %8d with fleets %s" % (sys_id, universe.getSystem(sys_id).name, alloc, found_stats.get('rating', 0), these_fleets)) target = TargetSystem(sys_id) for fleet_id in these_fleets: fo.issueAggressionOrder(fleet_id, True) fleet_mission = aistate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() if sys_id in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs): mission_type = MissionType.SECURE elif state.get_empire_planets_by_system(sys_id): mission_type = MissionType.PROTECT_REGION else: mission_type = MissionType.MILITARY fleet_mission.set_target(mission_type, target) fleet_mission.generate_fleet_orders() if not doing_main: aistate.misc.setdefault('ReassignedFleetMissions', []).append(fleet_mission) if doing_main: debug("---------------------------------") last_round = 3 last_round_name = "LastRound" if round <= last_round: # check if any fleets remain unassigned all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) allocations = [] round += 1 thisround = "Extras Remaining Round %d" % round if round < last_round else last_round_name if avail_mil_fleet_ids: debug("Round %s - still have available military fleets: %s", thisround, avail_mil_fleet_ids) allocations = get_military_fleets(mil_fleets_ids=avail_mil_fleet_ids, try_reset=False, thisround=thisround) if allocations: assign_military_fleets_to_systems(use_fleet_id_list=avail_mil_fleet_ids, allocations=allocations, round=round) else: # assign remaining fleets to nearest systems to protect. all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) def system_score(_fid, _sys_id): """Helper function to rank systems by priority""" jump_distance = universe.jumpDistance(_fid, _sys_id) if get_system_local_threat(_sys_id): weight = 10 elif get_system_neighbor_threat(_sys_id): weight = 3 elif get_system_jump2_threat(_sys_id): weight = 1 else: weight = 1 / max(.5, float(state.get_distance_to_enemy_supply(_sys_id)))**1.25 return float(weight) / (jump_distance+1) for fid in avail_mil_fleet_ids: fleet = universe.getFleet(fid) FleetUtilsAI.get_fleet_system(fleet) systems = state.get_empire_planets_by_system().keys() if not systems: continue sys_id = max(systems, key=lambda x: system_score(fid, x)) debug("Assigning leftover %s to system %d " "- nothing better to do.", fleet, sys_id) fleet_mission = aistate.get_fleet_mission(fid) fleet_mission.clear_fleet_orders() target_system = TargetSystem(sys_id) fleet_mission.set_target(MissionType.PROTECT_REGION, target_system) fleet_mission.generate_fleet_orders()
def eta(fleet_id): return FleetUtilsAI.calculate_estimated_time_of_arrival( fleet_id, invasion_system.id)
def _calculate_outpost_priority(): """Calculates the demand for outpost ships by colonisable planets.""" global allotted_outpost_targets base_outpost_cost = AIDependencies.OUTPOST_POD_COST aistate = get_aistate() enemies_sighted = aistate.misc.get('enemies_sighted', {}) galaxy_is_sparse = ColonisationAI.galaxy_is_sparse() total_pp = fo.getEmpire().productionPoints colony_growth_barrier = aistate.character.max_number_colonies() if state.get_number_of_colonies() > colony_growth_barrier: return 0.0 mil_prio = aistate.get_priority(PriorityType.PRODUCTION_MILITARY) not_sparse, enemy_unseen = 0, 0 is_sparse, enemy_seen = 1, 1 allotted_portion = { (not_sparse, enemy_unseen): (0.6, 0.8), (not_sparse, enemy_seen): (0.3, 0.4), (is_sparse, enemy_unseen): (0.8, 0.9), (is_sparse, enemy_seen): (0.3, 0.4), }[(galaxy_is_sparse, any(enemies_sighted))] allotted_portion = aistate.character.preferred_outpost_portion( allotted_portion) if mil_prio < 100: allotted_portion *= 2 elif mil_prio < 200: allotted_portion *= 1.5 allotted_outpost_targets = 1 + int( total_pp * 3 * allotted_portion / base_outpost_cost) num_outpost_targets = len([ pid for (pid, (score, specName)) in aistate.colonisableOutpostIDs.items() if score > max(1.0 * base_outpost_cost / 3.0, ColonisationAI.MINIMUM_COLONY_SCORE) ][:allotted_outpost_targets]) if num_outpost_targets == 0 or not tech_is_complete( AIDependencies.OUTPOSTING_TECH): return 0 outpost_ship_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.OUTPOST) num_outpost_ships = len( FleetUtilsAI.extract_fleet_ids_without_mission_types(outpost_ship_ids)) outpost_priority = ( 50.0 * (num_outpost_targets - num_outpost_ships)) / num_outpost_targets # discourage early outposting for SP_SLY, due to supply and stealth considerations they are best off # using colony ships until they have other colonizers (and more established military) if list(ColonisationAI.empire_colonizers) == ["SP_SLY"]: outpost_priority /= 3.0 # print # print "Number of Outpost Ships : " + str(num_outpost_ships) # print "Number of Colonisable outposts: " + str(num_outpost_planet_ids) debug("Priority for outpost ships: %s" % outpost_priority) if outpost_priority < 1: return 0 return outpost_priority
def __clean_fleet_roles(self, just_resumed=False): """Removes fleetRoles if a fleet has been lost, and update fleet Ratings.""" universe = fo.getUniverse() current_empire_fleets = FleetUtilsAI.get_empire_fleet_ids() self.shipCount = 0 destroyed_object_ids = universe.destroyedObjectIDs(fo.empireID()) fleet_table = Table([ Text('Fleet'), Float('Rating'), Float('Troops'), Text('Location'), Text('Destination')], table_name="Fleet Summary Turn %d" % fo.currentTurn() ) # need to loop over a copy as entries are deleted in loop for fleet_id in list(self.__fleetRoleByID): fleet_status = self.fleetStatus.setdefault(fleet_id, {}) rating = CombatRatingsAI.get_fleet_rating(fleet_id, self.get_standard_enemy()) old_sys_id = fleet_status.get('sysID', -2) # TODO: Introduce helper function instead fleet = universe.getFleet(fleet_id) if fleet: sys_id = fleet.systemID if old_sys_id in [-2, -1]: old_sys_id = sys_id fleet_status['nships'] = len(fleet.shipIDs) # TODO: Introduce helper function instead self.shipCount += fleet_status['nships'] else: # can still retrieve a fleet object even if fleet was just destroyed, so shouldn't get here # however,this has been observed happening, and is the reason a fleet check was added a few lines below. # Not at all sure how this came about, but was throwing off threat assessments sys_id = old_sys_id # check if fleet is destroyed and if so, delete stored information if fleet_id not in current_empire_fleets: # or fleet.empty: # TODO(Morlic): Is this condition really correct? Seems like should actually be in destroyed object ids if (fleet and self.__fleetRoleByID.get(fleet_id, -1) != -1 and fleet_id not in destroyed_object_ids and any(ship_id not in destroyed_object_ids for ship_id in fleet.shipIDs)): if not just_resumed: fleetsLostBySystem.setdefault(old_sys_id, []).append(max(rating, MilitaryAI.MinThreat)) self.delete_fleet_info(fleet_id) continue # if reached here, the fleet does still exist this_sys = universe.getSystem(sys_id) next_sys = universe.getSystem(fleet.nextSystemID) fleet_table.add_row([ fleet, rating, FleetUtilsAI.count_troops_in_fleet(fleet_id), this_sys or 'starlane', next_sys or '-', ]) fleet_status['rating'] = rating if next_sys: fleet_status['sysID'] = next_sys.id elif this_sys: fleet_status['sysID'] = this_sys.id else: error("Fleet %s has no valid system." % fleet) info(fleet_table) # Next string used in charts. Don't modify it! debug("Empire Ship Count: %s" % self.shipCount) debug("Empire standard fighter summary: %s", (CombatRatingsAI.get_empire_standard_fighter().get_stats(), )) debug("------------------------")
def _check_retarget_invasion(self): """checks if an invasion mission should be retargeted""" universe = fo.getUniverse() empire_id = fo.empireID() fleet_id = self.fleet.id fleet = universe.getFleet(fleet_id) if fleet.systemID == INVALID_ID: # next_loc = fleet.nextSystemID return # TODO: still check system = universe.getSystem(fleet.systemID) if not system: return orders = self.orders last_sys_target = INVALID_ID if orders: last_sys_target = orders[-1].target.id if last_sys_target == fleet.systemID: return # TODO: check for best local target open_targets = [] already_targeted = InvasionAI.get_invasion_targeted_planet_ids( system.planetIDs, MissionType.INVASION) for pid in system.planetIDs: if pid in already_targeted or ( pid in foAI.foAIstate.qualifyingTroopBaseTargets): continue planet = universe.getPlanet(pid) if planet.unowned or (planet.owner == empire_id): continue if (planet.initialMeterValue(fo.meterType.shield)) <= 0: open_targets.append(pid) if not open_targets: return troops_in_fleet = FleetUtilsAI.count_troops_in_fleet(fleet_id) target_id = INVALID_ID best_score = -1 target_troops = 0 # for pid, rating in InvasionAI.assign_invasion_values( open_targets).items(): p_score, p_troops = rating if p_score > best_score: if p_troops >= troops_in_fleet: continue best_score = p_score target_id = pid target_troops = p_troops if target_id == INVALID_ID: return print "\t AIFleetMission._check_retarget_invasion: splitting and retargetting fleet %d" % fleet_id new_fleets = FleetUtilsAI.split_fleet(fleet_id) self.clear_target() # TODO: clear from foAIstate self.clear_fleet_orders() troops_needed = max( 0, target_troops - FleetUtilsAI.count_troops_in_fleet(fleet_id)) min_stats = {'rating': 0, 'troopCapacity': troops_needed} target_stats = {'rating': 10, 'troopCapacity': troops_needed} found_fleets = [] # TODO check if next statement does not mutate any global states and can be removed _ = FleetUtilsAI.get_fleets_for_mission( target_stats, min_stats, {}, starting_system=fleet.systemID, # noqa: F841 fleet_pool_set=set(new_fleets), fleet_list=found_fleets) for fid in found_fleets: FleetUtilsAI.merge_fleet_a_into_b(fid, fleet_id) target = Planet(target_id) self.set_target(MissionType.INVASION, target) self.generate_fleet_orders()
def assess_protection_focus(pid, info): """Return True if planet should use Protection Focus.""" this_planet = info.planet sys_status = foAI.foAIstate.systemStatus.get(this_planet.systemID, {}) threat_from_supply = ( 0.25 * foAI.foAIstate.empire_standard_enemy_rating * min(2, len(sys_status.get('enemies_nearly_supplied', [])))) print "Planet %s has regional+supply threat of %.1f" % ( 'P_%d<%s>' % (pid, this_planet.name), threat_from_supply) regional_threat = sys_status.get('regional_threat', 0) + threat_from_supply if not regional_threat: # no need for protection if info.current_focus == PRODUCTION: print "Advising dropping Protection Focus at %s due to no regional threat" % this_planet return False cur_prod_val = weighted_sum_output(info.current_output) target_prod_val = max( map(weighted_sum_output, [info.possible_output[INDUSTRY], info.possible_output[RESEARCH]])) prot_prod_val = weighted_sum_output(info.possible_output[PRODUCTION]) local_production_diff = 0.8 * cur_prod_val + 0.2 * target_prod_val - prot_prod_val fleet_threat = sys_status.get('fleetThreat', 0) # TODO: relax the below rejection once the overall determination of PFocus is better tuned if not fleet_threat and local_production_diff > 8: if info.current_focus == PRODUCTION: print "Advising dropping Protection Focus at %s due to excessive productivity loss" % this_planet return False local_p_defenses = sys_status.get('mydefenses', {}).get('overall', 0) # TODO have adjusted_p_defenses take other in-system planets into account adjusted_p_defenses = local_p_defenses * ( 1.0 if info.current_focus != PRODUCTION else 0.5) local_fleet_rating = sys_status.get('myFleetRating', 0) combined_local_defenses = sys_status.get('all_local_defenses', 0) my_neighbor_rating = sys_status.get('my_neighbor_rating', 0) neighbor_threat = sys_status.get('neighborThreat', 0) safety_factor = 1.2 if info.current_focus == PRODUCTION else 0.5 cur_shield = this_planet.currentMeterValue(fo.meterType.shield) max_shield = this_planet.currentMeterValue(fo.meterType.maxShield) cur_troops = this_planet.currentMeterValue(fo.meterType.troops) max_troops = this_planet.currentMeterValue(fo.meterType.maxTroops) cur_defense = this_planet.currentMeterValue(fo.meterType.defense) max_defense = this_planet.currentMeterValue(fo.meterType.maxDefense) def_meter_pairs = [(cur_troops, max_troops), (cur_shield, max_shield), (cur_defense, max_defense)] use_protection = True reason = "" if (fleet_threat and # i.e., an enemy is sitting on us ( info.current_focus != PRODUCTION or # too late to start protection TODO: but maybe regen worth it # protection focus only useful here if it maintains an elevated level all([ AIDependencies.PROT_FOCUS_MULTIPLIER * a <= b for a, b in def_meter_pairs ]))): use_protection = False reason = "A" elif ((info.current_focus != PRODUCTION and cur_shield < max_shield - 2 and not tech_is_complete(AIDependencies.PLANET_BARRIER_I_TECH)) and (cur_defense < max_defense - 2 and not tech_is_complete(AIDependencies.DEFENSE_REGEN_1_TECH)) and (cur_troops < max_troops - 2)): use_protection = False reason = "B1" elif ( (info.current_focus == PRODUCTION and cur_shield * AIDependencies.PROT_FOCUS_MULTIPLIER < max_shield - 2 and not tech_is_complete(AIDependencies.PLANET_BARRIER_I_TECH)) and (cur_defense * AIDependencies.PROT_FOCUS_MULTIPLIER < max_defense - 2 and not tech_is_complete(AIDependencies.DEFENSE_REGEN_1_TECH)) and (cur_troops * AIDependencies.PROT_FOCUS_MULTIPLIER < max_troops - 2)): use_protection = False reason = "B2" elif max(max_shield, max_troops, max_defense ) < 3: # joke defenses, don't bother with protection focus use_protection = False reason = "C" elif regional_threat and local_production_diff <= 2.0: reason = "D" pass # i.e., use_protection = True elif safety_factor * regional_threat <= local_fleet_rating: use_protection = False reason = "E" elif (safety_factor * regional_threat <= combined_local_defenses and (info.current_focus != PRODUCTION or (0.5 * safety_factor * regional_threat <= local_fleet_rating and fleet_threat == 0 and neighbor_threat < combined_local_defenses and local_production_diff > 5))): use_protection = False reason = "F" elif (regional_threat <= FleetUtilsAI.combine_ratings( local_fleet_rating, adjusted_p_defenses) and safety_factor * regional_threat <= FleetUtilsAI.combine_ratings_list( [my_neighbor_rating, local_fleet_rating, adjusted_p_defenses]) and local_production_diff > 5): use_protection = False reason = "G" if use_protection or info.current_focus == PRODUCTION: print( "Advising %sProtection Focus (reason %s) for planet %s, with local_prod_diff of %.1f, comb. local" " defenses %.1f, local fleet rating %.1f and regional threat %.1f, threat sources: %s" ) % (["dropping ", "" ][use_protection], reason, this_planet, local_production_diff, combined_local_defenses, local_fleet_rating, regional_threat, sys_status['regional_fleet_threats']) return use_protection
def _calculate_colonisation_priority(): """Calculates the demand for colony ships by colonisable planets.""" global allottedColonyTargets aistate = get_aistate() enemies_sighted = aistate.misc.get('enemies_sighted', {}) galaxy_is_sparse = ColonisationAI.galaxy_is_sparse() total_pp = fo.getEmpire().productionPoints num_colonies = state.get_number_of_colonies() colony_growth_barrier = aistate.character.max_number_colonies() if num_colonies > colony_growth_barrier: return 0.0 colony_cost = AIDependencies.COLONY_POD_COST * ( 1 + AIDependencies.COLONY_POD_UPKEEP * num_colonies) turns_to_build = 8 # TODO: check for susp anim pods, build time 10 mil_prio = aistate.get_priority(PriorityType.PRODUCTION_MILITARY) allotted_portion = ([[[0.6, 0.8], [0.3, 0.4]], [[0.8, 0.9], [0.4, 0.6]]][galaxy_is_sparse][any(enemies_sighted)]) allotted_portion = aistate.character.preferred_colonization_portion( allotted_portion) # if ( get_aistate().get_priority(AIPriorityType.PRIORITY_PRODUCTION_COLONISATION) # > 2 * get_aistate().get_priority(AIPriorityType.PRIORITY_PRODUCTION_MILITARY)): # allotted_portion *= 1.5 if mil_prio < 100: allotted_portion *= 2 elif mil_prio < 200: allotted_portion *= 1.5 elif fo.currentTurn() > 100: allotted_portion *= 0.75**(num_colonies / 10.0) # allottedColonyTargets = 1+ int(fo.currentTurn()/50) allottedColonyTargets = 1 + int( total_pp * turns_to_build * allotted_portion / colony_cost) outpost_prio = aistate.get_priority(PriorityType.PRODUCTION_OUTPOST) # if have no SP_SLY, and have any outposts to build, don't build colony ships TODO: make more complex assessment colonizers = list(ColonisationAI.empire_colonizers) if "SP_SLY" not in colonizers and outpost_prio > 0: return 0.0 min_score = ColonisationAI.MINIMUM_COLONY_SCORE minimal_top = min_score + 2 # one more than the conditional floor set by ColonisationAI.revise_threat_factor() minimal_opportunities = [ species_name for (_, (score, species_name)) in aistate.colonisablePlanetIDs.items() if min_score < score <= minimal_top ] decent_opportunities = [ species_name for (_, (score, species_name)) in aistate.colonisablePlanetIDs.items() if score > minimal_top ] minimal_planet_factor = 0.2 # count them for something, but not much num_colonisable_planet_ids = len( decent_opportunities ) + minimal_planet_factor * len(minimal_opportunities) if num_colonisable_planet_ids == 0: return 1 colony_ship_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.COLONISATION) num_colony_ships = len( FleetUtilsAI.extract_fleet_ids_without_mission_types(colony_ship_ids)) colonisation_priority = 60 * (1.0 + num_colonisable_planet_ids - num_colony_ships) / ( num_colonisable_planet_ids + 1) if colonizers == ["SP_SLY"]: colonisation_priority *= 2 elif "SP_SLY" in colonizers: colony_opportunities = minimal_opportunities + decent_opportunities colonisation_priority *= (1.0 + colony_opportunities.count("SP_SLY") ) / len(colony_opportunities) # print # print "Number of Colony Ships : " + str(num_colony_ships) # print "Number of Colonisable planets : " + str(num_colonisable_planet_ids) # print "Priority for colony ships : " + str(colonisation_priority) if colonisation_priority < 1: return 0 return colonisation_priority
def issue_fleet_orders(self): """issues AIFleetOrders which can be issued in system and moves to next one if is possible""" # TODO: priority order_completed = True print print "Checking orders for fleet %s (on turn %d), with mission type %s" % ( self.fleet.get_object(), fo.currentTurn(), self.type or 'No mission') if MissionType.INVASION == self.type: self._check_retarget_invasion() just_issued_move_order = False last_move_target_id = INVALID_ID for fleet_order in self.orders: if just_issued_move_order and self.fleet.get_object( ).systemID != last_move_target_id: # having just issued a move order, we will normally stop issuing orders this turn, except that if there # are consecutive move orders we will consider moving through the first destination rather than stopping if (not isinstance(fleet_order, OrderMove) or self.need_to_pause_movement(last_move_target_id, fleet_order)): break print "Checking order: %s" % fleet_order if isinstance( fleet_order, (OrderColonize, OrderOutpost, OrderInvade)): # TODO: invasion? if self._check_abort_mission(fleet_order): print "Aborting fleet order %s" % fleet_order return self.check_mergers(context=str(fleet_order)) if fleet_order.can_issue_order(verbose=False): if isinstance( fleet_order, OrderMove ) and order_completed: # only move if all other orders completed print "Issuing fleet order %s" % fleet_order fleet_order.issue_order() just_issued_move_order = True last_move_target_id = fleet_order.target.id elif not isinstance(fleet_order, OrderMove): print "Issuing fleet order %s" % fleet_order fleet_order.issue_order() else: print "NOT issuing (even though can_issue) fleet order %s" % fleet_order print "Order issued: %s" % fleet_order.order_issued if not fleet_order.order_issued: order_completed = False else: # check that we're not held up by a Big Monster if fleet_order.order_issued: # It's unclear why we'd really get to this spot, but it has been observed to happen, perhaps due to # game being reloaded after code changes. # Go on to the next order. continue print "CAN'T issue fleet order %s" % fleet_order if isinstance(fleet_order, OrderMove): this_system_id = fleet_order.target.id this_status = foAI.foAIstate.systemStatus.setdefault( this_system_id, {}) threat_threshold = fo.currentTurn( ) * MilitaryAI.cur_best_mil_ship_rating() / 4.0 if this_status.get('monsterThreat', 0) > threat_threshold: # if this move order is not this mil fleet's final destination, and blocked by Big Monster, # release and hope for more effective reassignment if (self.type not in (MissionType.MILITARY, MissionType.SECURE) or fleet_order != self.orders[-1]): print "Aborting mission due to being blocked by Big Monster at system %d, threat %d" % ( this_system_id, foAI.foAIstate. systemStatus[this_system_id]['monsterThreat']) print "Full set of orders were:" for this_order in self.orders: print " - %s" % this_order self.clear_fleet_orders() self.clear_target() return break # do not order the next order until this one is finished. else: # went through entire order list if order_completed: print "Final order is completed" orders = self.orders last_order = orders[-1] if orders else None universe = fo.getUniverse() if last_order and isinstance(last_order, OrderColonize): planet = universe.getPlanet(last_order.target.id) sys_partial_vis_turn = get_partial_visibility_turn( planet.systemID) planet_partial_vis_turn = get_partial_visibility_turn( planet.id) if (planet_partial_vis_turn == sys_partial_vis_turn and not planet.initialMeterValue( fo.meterType.population)): warn( "Fleet %d has tentatively completed its " "colonize mission but will wait to confirm population." % self.fleet.id) print " Order details are %s" % last_order print " Order is valid: %s; issued: %s; executed: %s" % ( last_order.is_valid(), last_order.order_issued, last_order.executed) if not last_order.is_valid(): source_target = last_order.fleet target_target = last_order.target print " source target validity: %s; target target validity: %s " % ( bool(source_target), bool(target_target)) return # colonize order must not have completed yet clear_all = True last_sys_target = INVALID_ID if last_order and isinstance(last_order, OrderMilitary): last_sys_target = last_order.target.id # not doing this until decide a way to release from a SECURE mission # if (MissionType.SECURE == self.type) or secure_targets = set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs) if last_sys_target in secure_targets: # consider a secure mission if last_sys_target in AIstate.colonyTargetedSystemIDs: secure_type = "Colony" elif last_sys_target in AIstate.outpostTargetedSystemIDs: secure_type = "Outpost" elif last_sys_target in AIstate.invasionTargetedSystemIDs: secure_type = "Invasion" else: secure_type = "Unidentified" print( "Fleet %d has completed initial stage of its mission " "to secure system %d (targeted for %s), " "may release a portion of ships" % (self.fleet.id, last_sys_target, secure_type)) clear_all = False fleet_id = self.fleet.id if clear_all: if orders: print "Fleet %d has completed its mission; clearing all orders and targets." % self.fleet.id print "Full set of orders were:" for this_order in orders: print "\t\t %s" % this_order self.clear_fleet_orders() self.clear_target() if foAI.foAIstate.get_fleet_role(fleet_id) in ( MissionType.MILITARY, MissionType.SECURE): allocations = MilitaryAI.get_military_fleets( mil_fleets_ids=[fleet_id], try_reset=False, thisround="Fleet %d Reassignment" % fleet_id) if allocations: MilitaryAI.assign_military_fleets_to_systems( use_fleet_id_list=[fleet_id], allocations=allocations) else: # no orders print "No Current Orders" else: # TODO: evaluate releasing a smaller portion or none of the ships system_status = foAI.foAIstate.systemStatus.setdefault( last_sys_target, {}) new_fleets = [] threat_present = system_status.get( 'totalThreat', 0) + system_status.get( 'neighborThreat', 0) > 0 target_system = universe.getSystem(last_sys_target) if not threat_present and target_system: for pid in target_system.planetIDs: planet = universe.getPlanet(pid) if (planet and planet.owner != fo.empireID() and planet.currentMeterValue( fo.meterType.maxDefense) > 0): threat_present = True break if not threat_present: print "No current threat in target system; releasing a portion of ships." # at least first stage of current task is done; # release extra ships for potential other deployments new_fleets = FleetUtilsAI.split_fleet(self.fleet.id) else: print "Threat remains in target system; NOT releasing any ships." new_military_fleets = [] for fleet_id in new_fleets: if foAI.foAIstate.get_fleet_role( fleet_id) in COMBAT_MISSION_TYPES: new_military_fleets.append(fleet_id) allocations = [] if new_military_fleets: allocations = MilitaryAI.get_military_fleets( mil_fleets_ids=new_military_fleets, try_reset=False, thisround="Fleet Reassignment %s" % new_military_fleets) if allocations: MilitaryAI.assign_military_fleets_to_systems( use_fleet_id_list=new_military_fleets, allocations=allocations)
def calculateExplorationPriority(): """calculates the demand for scouts by unexplored systems""" global scoutsNeeded universe = fo.getUniverse() empire = fo.getEmpire() numUnexploredSystems = len( ExplorationAI.borderUnexploredSystemIDs ) #len(foAI.foAIstate.get_explorable_systems(AIExplorableSystemType.EXPLORABLE_SYSTEM_UNEXPLORED)) numScouts = sum( [ foAI.foAIstate.fleetStatus.get(fid, {}).get('nships', 0) for fid in FleetUtilsAI.get_empire_fleet_ids_by_role( EnumsAI.AIFleetMissionType.FLEET_MISSION_EXPLORATION)] ) # FleetUtilsAI.get_empire_fleet_ids_by_role(AIFleetMissionType.FLEET_MISSION_EXPLORATION) productionQueue = empire.productionQueue queuedScoutShips=0 for queue_index in range(0, len(productionQueue)): element=productionQueue[queue_index] if element.buildType == EnumsAI.AIEmpireProductionTypes.BT_SHIP: if foAI.foAIstate.get_ship_role(element.designID) == EnumsAI.AIShipRoleType.SHIP_ROLE_CIVILIAN_EXPLORATION : queuedScoutShips += element.remaining * element.blocksize milShips = MilitaryAI.num_milships # intent of the following calc is essentially # new_scouts_needed = min(need_cap_A, need_cap_B, base_need) - already_got_or_queued # where need_cap_A is to help prevent scouting needs from swamping military needs, and # need_cap_B is to help regulate investment into scouting while the empire is small. # These caps could perhaps instead be tied more directly to military priority and # total empire production. scoutsNeeded = max(0, min( 4+int(milShips/5), 4+int(fo.currentTurn()/50) , 2+ numUnexploredSystems**0.5 ) - numScouts - queuedScoutShips ) explorationPriority = int(40*scoutsNeeded) print print "Number of Scouts : " + str(numScouts) print "Number of Unexplored systems: " + str(numUnexploredSystems) print "military size: ", milShips print "Priority for scouts : " + str(explorationPriority) return explorationPriority
def _calculate_invasion_priority(): """Calculates the demand for troop ships by opponent planets.""" global allottedInvasionTargets if not foAI.foAIstate.character.may_invade(): return 0 empire = fo.getEmpire() enemies_sighted = foAI.foAIstate.misc.get('enemies_sighted', {}) multiplier = 1 num_colonies = len(list(AIstate.popCtrIDs)) colony_growth_barrier = foAI.foAIstate.character.max_number_colonies() if num_colonies > colony_growth_barrier: return 0.0 if len(foAI.foAIstate.colonisablePlanetIDs) > 0: best_colony_score = max( 2, foAI.foAIstate.colonisablePlanetIDs.items()[0][1][0]) else: best_colony_score = 2 allottedInvasionTargets = 1 + int(fo.currentTurn() / 25) total_val = 0 troops_needed = 0 for pid, pscore, trp in AIstate.invasionTargets[:allottedInvasionTargets]: if pscore > best_colony_score: multiplier += 1 total_val += 2 * pscore else: total_val += pscore troops_needed += trp + 4 # ToDo: This seems like it could be improved by some dynamic calculation of buffer if total_val == 0: return 0 production_queue = empire.productionQueue queued_troop_capacity = 0 for queue_index in range(0, len(production_queue)): element = production_queue[queue_index] if element.buildType == EmpireProductionTypes.BT_SHIP: if foAI.foAIstate.get_ship_role(element.designID) in [ ShipRoleType.MILITARY_INVASION, ShipRoleType.BASE_INVASION ]: design = fo.getShipDesign(element.designID) queued_troop_capacity += element.remaining * element.blocksize * design.troopCapacity _, best_design, _ = ProductionAI.get_best_ship_info( PriorityType.PRODUCTION_INVASION) if best_design: troops_per_best_ship = best_design.troopCapacity else: return 1e-6 # if we can not build troop ships, we don't want to build (non-existing) invasion ships # don't count troop bases here as these cannot be redeployed after misplaning # troopFleetIDs = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.INVASION)\ # + FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.ORBITAL_INVASION) troop_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.INVASION) total_troop_capacity = sum( [FleetUtilsAI.count_troops_in_fleet(fid) for fid in troop_fleet_ids]) troop_ships_needed = \ math.ceil((troops_needed - (total_troop_capacity + queued_troop_capacity)) / troops_per_best_ship) # invasion_priority = max( 10+ 200*max(0, troop_ships_needed ) , int(0.1* total_val) ) invasion_priority = multiplier * (30 + 150 * max(0, troop_ships_needed)) if not ColonisationAI.colony_status.get('colonies_under_attack', []): if not ColonisationAI.colony_status.get('colonies_under_threat', []): invasion_priority *= 2.0 else: invasion_priority *= 1.5 if not enemies_sighted: invasion_priority *= 1.5 if invasion_priority < 0: return 0 return invasion_priority * foAI.foAIstate.character.invasion_priority_scaling( )
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: :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 :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() fuel = int(FleetUtilsAI.get_fuel( fleet_id)) # round down to get actually number of jumps if fuel < 1.0 or from_system_target.id == to_system_target.id: return [] if 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 != INVALID_ID and target_sys_id != INVALID_ID: 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 and foAI.foAIstate.character.may_travel_beyond_supply( 2): print "target in Ring 2, has enough aggression" elif target_sys_id in ColonisationAI.annexable_ring3 and foAI.foAIstate.character.may_travel_beyond_supply( 3): print "target in Ring 2, has enough aggression" 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 target_sys_id in ColonisationAI.annexable_ring2 and foAI.foAIstate.character.may_travel_beyond_supply(2) and len(unsupplied_stops) < fuel - 1 or target_sys_id in ColonisationAI.annexable_ring3 and foAI.foAIstate.character.may_travel_beyond_supply(3) 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_invasion_fleets(): invasion_timer.start("gathering initial info") all_invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( EnumsAI.AIFleetMissionType.FLEET_MISSION_INVASION) AIstate.invasionFleetIDs = FleetUtilsAI.extract_fleet_ids_without_mission_types( all_invasion_fleet_ids) # get suppliable planets universe = fo.getUniverse() empire = fo.getEmpire() empire_id = empire.empireID capital_id = PlanetUtilsAI.get_capital() homeworld = None if capital_id: homeworld = universe.getPlanet(capital_id) if homeworld: home_system_id = homeworld.systemID else: home_system_id = -1 fleet_suppliable_system_ids = empire.fleetSupplyableSystemIDs fleet_suppliable_planet_ids = PlanetUtilsAI.get_planets_in__systems_ids( fleet_suppliable_system_ids) prime_invadable_system_ids = set(ColonisationAI.annexable_system_ids) prime_invadable_planet_ids = PlanetUtilsAI.get_planets_in__systems_ids( prime_invadable_system_ids) visible_system_ids = foAI.foAIstate.visInteriorSystemIDs.keys( ) + foAI.foAIstate.visBorderSystemIDs.keys() if home_system_id != -1: accessible_system_ids = [ sys_id for sys_id in visible_system_ids if (sys_id != -1) and universe.systemsConnected(sys_id, home_system_id, empire_id) ] else: print "Invasion Warning: this empire has no identifiable homeworld, will therefor treat all visible planets as accessible." accessible_system_ids = visible_system_ids # TODO: check if any troop ships still owned, use their system as home system acessible_planet_ids = PlanetUtilsAI.get_planets_in__systems_ids( accessible_system_ids) print "Accessible Systems: ", ", ".join( PlanetUtilsAI.sys_name_ids(accessible_system_ids)) print all_owned_planet_ids = PlanetUtilsAI.get_all_owned_planet_ids( acessible_planet_ids) # need these for unpopulated outposts all_populated_planets = PlanetUtilsAI.get_populated_planet_ids( acessible_planet_ids) # need this for natives print "All Visible and accessible Populated PlanetIDs (including this empire's): ", ", ".join( PlanetUtilsAI.planet_name_ids(all_populated_planets)) print print "Prime Invadable Target Systems: ", ", ".join( PlanetUtilsAI.sys_name_ids(prime_invadable_system_ids)) print empire_owned_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire( universe.planetIDs) invadable_planet_ids = set(prime_invadable_planet_ids).intersection( set(all_owned_planet_ids).union(all_populated_planets) - set(empire_owned_planet_ids)) print "Prime Invadable PlanetIDs: ", ", ".join( PlanetUtilsAI.planet_name_ids(invadable_planet_ids)) print print "Current Invasion Targeted SystemIDs: ", ", ".join( PlanetUtilsAI.sys_name_ids(AIstate.invasionTargetedSystemIDs)) invasion_targeted_planet_ids = get_invasion_targeted_planet_ids( universe.planetIDs, EnumsAI.AIFleetMissionType.FLEET_MISSION_INVASION) invasion_targeted_planet_ids.extend( get_invasion_targeted_planet_ids( universe.planetIDs, EnumsAI.AIFleetMissionType.FLEET_MISSION_ORBITAL_INVASION)) all_invasion_targeted_system_ids = set( PlanetUtilsAI.get_systems(invasion_targeted_planet_ids)) print "Current Invasion Targeted PlanetIDs: ", ", ".join( PlanetUtilsAI.planet_name_ids(invasion_targeted_planet_ids)) invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( EnumsAI.AIFleetMissionType.FLEET_MISSION_INVASION) if not invasion_fleet_ids: print "Available Invasion Fleets: 0" else: print "Invasion FleetIDs: %s" % FleetUtilsAI.get_empire_fleet_ids_by_role( EnumsAI.AIFleetMissionType.FLEET_MISSION_INVASION) num_invasion_fleets = len( FleetUtilsAI.extract_fleet_ids_without_mission_types( invasion_fleet_ids)) print "Invasion Fleets Without Missions: %s" % num_invasion_fleets invasion_timer.start("planning troop base production") # only do base invasions if aggression is typical or above reserved_troop_base_targets = [] if foAI.foAIstate.aggression > fo.aggression.typical: available_pp = {} for el in empire.planetsWithAvailablePP: # keys are sets of ints; data is doubles avail_pp = el.data() for pid in el.key(): available_pp[pid] = avail_pp for pid in invadable_planet_ids: # TODO: reorganize planet = universe.getPlanet(pid) if not planet: continue sys_id = planet.systemID sys_partial_vis_turn = universe.getVisibilityTurnsMap( planet.systemID, empire_id).get(fo.visibility.partial, -9999) planet_partial_vis_turn = universe.getVisibilityTurnsMap( pid, empire_id).get(fo.visibility.partial, -9999) if planet_partial_vis_turn < sys_partial_vis_turn: continue for pid2 in ColonisationAI.empire_species_systems.get( sys_id, {}).get('pids', []): if available_pp.get( pid2, 0 ) < 2: # TODO: improve troop base PP sufficiency determination break planet2 = universe.getPlanet(pid2) if not planet2: continue if pid not in foAI.foAIstate.qualifyingTroopBaseTargets and planet2.speciesName in ColonisationAI.empire_ship_builders: foAI.foAIstate.qualifyingTroopBaseTargets.setdefault( pid, [pid2, -1]) break for pid in list(foAI.foAIstate.qualifyingTroopBaseTargets): planet = universe.getPlanet( pid ) # TODO: also check that still have a colony in this system that can make troops if planet and planet.owner == empire_id: del foAI.foAIstate.qualifyingTroopBaseTargets[pid] secure_ai_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types( [EnumsAI.AIFleetMissionType.FLEET_MISSION_SECURE]) for pid in (set(foAI.foAIstate.qualifyingTroopBaseTargets.keys()) - set(invasion_targeted_planet_ids) ): # TODO: consider overriding standard invasion mission planet = universe.getPlanet(pid) if foAI.foAIstate.qualifyingTroopBaseTargets[pid][1] != -1: reserved_troop_base_targets.append(pid) if planet: all_invasion_targeted_system_ids.add(planet.systemID) continue # already building for here sys_id = planet.systemID this_sys_status = foAI.foAIstate.systemStatus.get(sys_id, {}) if (planet.currentMeterValue(fo.meterType.shield) > 0 and this_sys_status.get('myFleetRating', 0) < 0.8 * this_sys_status.get('totalThreat', 0)): continue loc = foAI.foAIstate.qualifyingTroopBaseTargets[pid][0] best_base_trooper_here = ProductionAI.getBestShipInfo( EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_ORBITAL_INVASION, loc)[1] loc_planet = universe.getPlanet(loc) if best_base_trooper_here is None: # shouldn't be possible at this point, but just to be safe print "Could not find a suitable orbital invasion design at %s" % loc_planet continue # TODO: have TroopShipDesigner give the expected number of troops including species effects directly troops_per_ship = best_base_trooper_here.troopCapacity _, _, species_troop_grade = foAI.foAIstate.get_piloting_grades( loc_planet.speciesName) troops_per_ship = foAI.foAIstate.weight_attack_troops( troops_per_ship, species_troop_grade) if not troops_per_ship: print "The best orbital invasion design at %s seems not to have any troop capacity." % loc_planet continue this_score, p_troops = evaluate_invasion_planet( pid, empire, secure_ai_fleet_missions, False) _, col_design, build_choices = ProductionAI.getBestShipInfo( EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_ORBITAL_INVASION, loc) if not col_design: continue if loc not in build_choices: sys.stderr.write( 'Best troop design %s can not be produces in at planet with id: %s\d' % (col_design, build_choices)) n_bases = math.ceil( (p_troops + 1) / troops_per_ship) # TODO: reconsider this +1 safety factor print "Invasion base planning, need %d troops at %d pership, will build %d ships." % ( (p_troops + 1), troops_per_ship, n_bases) retval = fo.issueEnqueueShipProductionOrder(col_design.id, loc) print "Enqueueing %d Troop Bases at %s for %s" % ( n_bases, PlanetUtilsAI.planet_name_ids( [loc]), PlanetUtilsAI.planet_name_ids([pid])) if retval != 0: all_invasion_targeted_system_ids.add(planet.systemID) reserved_troop_base_targets.append(pid) foAI.foAIstate.qualifyingTroopBaseTargets[pid][1] = loc fo.issueChangeProductionQuantityOrder( empire.productionQueue.size - 1, 1, int(n_bases)) fo.issueRequeueProductionOrder(empire.productionQueue.size - 1, 0) invasion_timer.start("evaluating target planets") # TODO: check if any invasion_targeted_planet_ids need more troops assigned evaluated_planet_ids = list( set(invadable_planet_ids) - set(invasion_targeted_planet_ids) - set(reserved_troop_base_targets)) print "Evaluating potential invasions, PlanetIDs: %s" % evaluated_planet_ids evaluated_planets = assign_invasion_values( evaluated_planet_ids, EnumsAI.AIFleetMissionType.FLEET_MISSION_INVASION, fleet_suppliable_planet_ids, empire) sorted_planets = [(pid, pscore % 10000, ptroops) for pid, (pscore, ptroops) in evaluated_planets.items()] sorted_planets.sort(key=lambda x: x[1], reverse=True) sorted_planets = [(pid, pscore % 10000, ptroops) for pid, pscore, ptroops in sorted_planets] print if sorted_planets: print "Invadable planets:\n%-6s | %-6s | %-16s | %-16s | Troops" % ( 'ID', 'Score', 'Name', 'Race') for pid, pscore, ptroops in sorted_planets: planet = universe.getPlanet(pid) if planet: print "%6d | %6d | %16s | %16s | %d" % ( pid, pscore, planet.name, planet.speciesName, ptroops) else: print "%6d | %6d | Error: invalid planet ID" % (pid, pscore) else: print "No Invadable planets identified" sorted_planets = filter(lambda x: x[1] > 0, sorted_planets) # export opponent planets for other AI modules AIstate.opponentPlanetIDs = [pid for pid, _, _ in sorted_planets] AIstate.invasionTargets = sorted_planets # export invasion targeted systems for other AI modules AIstate.invasionTargetedSystemIDs = list(all_invasion_targeted_system_ids) invasion_timer.stop(section_name="evaluating %d target planets" % (len(evaluated_planet_ids))) invasion_timer.end()
def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): """Get armed military fleets.""" global _military_allocations universe = fo.getUniverse() empire_id = fo.empireID() home_system_id = PlanetUtilsAI.get_capital_sys_id() all_military_fleet_ids = (mil_fleets_ids if mil_fleets_ids is not None else FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)) if try_reset and (fo.currentTurn() + empire_id) % 30 == 0 and thisround == "Main": try_again(all_military_fleet_ids, try_reset=False, thisround=thisround + " Reset") return mil_fleets_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) mil_needing_repair_ids, mil_fleets_ids = avail_mil_needing_repair(mil_fleets_ids, split_ships=True) avail_mil_rating = combine_ratings_list(map(CombatRatingsAI.get_fleet_rating, mil_fleets_ids)) if not mil_fleets_ids: if "Main" in thisround: _military_allocations = [] return [] # for each system, get total rating of fleets assigned to it already_assigned_rating = {} already_assigned_rating_vs_planets = {} aistate = get_aistate() systems_status = aistate.systemStatus enemy_sup_factor = {} # enemy supply for sys_id in universe.systemIDs: already_assigned_rating[sys_id] = 0 already_assigned_rating_vs_planets[sys_id] = 0 enemy_sup_factor[sys_id] = min(2, len(systems_status.get(sys_id, {}).get('enemies_nearly_supplied', []))) for fleet_id in [fid for fid in all_military_fleet_ids if fid not in mil_fleets_ids]: ai_fleet_mission = aistate.get_fleet_mission(fleet_id) if not ai_fleet_mission.target: # shouldn't really be possible continue last_sys = ai_fleet_mission.target.get_system().id # will count this fleet as assigned to last system in target list # TODO last_sys or target sys? this_rating = CombatRatingsAI.get_fleet_rating(fleet_id) this_rating_vs_planets = CombatRatingsAI.get_fleet_rating_against_planets(fleet_id) already_assigned_rating[last_sys] = CombatRatingsAI.combine_ratings( already_assigned_rating.get(last_sys, 0), this_rating) already_assigned_rating_vs_planets[last_sys] = CombatRatingsAI.combine_ratings( already_assigned_rating_vs_planets.get(last_sys, 0), this_rating_vs_planets) for sys_id in universe.systemIDs: my_defense_rating = systems_status.get(sys_id, {}).get('mydefenses', {}).get('overall', 0) already_assigned_rating[sys_id] = CombatRatingsAI.combine_ratings(my_defense_rating, already_assigned_rating[sys_id]) if _verbose_mil_reporting and already_assigned_rating[sys_id]: debug("\t System %s already assigned rating %.1f" % ( universe.getSystem(sys_id), already_assigned_rating[sys_id])) # get systems to defend capital_id = PlanetUtilsAI.get_capital() if capital_id is not None: capital_planet = universe.getPlanet(capital_id) else: capital_planet = None # TODO: if no owned planets try to capture one! if capital_planet: capital_sys_id = capital_planet.systemID else: # should be rare, but so as to not break code below, pick a randomish mil-centroid system capital_sys_id = None # unless we can find one to use system_dict = {} for fleet_id in all_military_fleet_ids: status = aistate.fleetStatus.get(fleet_id, None) if status is not None: system_id = status['sysID'] if not list(universe.getSystem(system_id).planetIDs): continue system_dict[system_id] = system_dict.get(system_id, 0) + status.get('rating', 0) ranked_systems = sorted([(val, sys_id) for sys_id, val in system_dict.items()]) if ranked_systems: capital_sys_id = ranked_systems[-1][-1] else: try: capital_sys_id = aistate.fleetStatus.items()[0][1]['sysID'] except: pass num_targets = max(10, PriorityAI.allotted_outpost_targets) top_target_planets = ([pid for pid, pscore, trp in AIstate.invasionTargets[:PriorityAI.allotted_invasion_targets()] if pscore > InvasionAI.MIN_INVASION_SCORE] + [pid for pid, (pscore, spec) in aistate.colonisableOutpostIDs.items()[:num_targets] if pscore > InvasionAI.MIN_INVASION_SCORE] + [pid for pid, (pscore, spec) in aistate.colonisablePlanetIDs.items()[:num_targets] if pscore > InvasionAI.MIN_INVASION_SCORE]) top_target_planets.extend(aistate.qualifyingTroopBaseTargets.keys()) base_col_target_systems = PlanetUtilsAI.get_systems(top_target_planets) top_target_systems = [] for sys_id in AIstate.invasionTargetedSystemIDs + base_col_target_systems: if sys_id not in top_target_systems: if aistate.systemStatus[sys_id]['totalThreat'] > get_tot_mil_rating(): continue top_target_systems.append(sys_id) # doing this rather than set, to preserve order try: # capital defense allocation_helper = AllocationHelper(already_assigned_rating, already_assigned_rating_vs_planets, avail_mil_rating, try_reset) if capital_sys_id is not None: CapitalDefenseAllocator(capital_sys_id, allocation_helper).allocate() # defend other planets empire_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire(universe.planetIDs) empire_occupied_system_ids = list(set(PlanetUtilsAI.get_systems(empire_planet_ids)) - {capital_sys_id}) for sys_id in empire_occupied_system_ids: PlanetDefenseAllocator(sys_id, allocation_helper).allocate() # attack / protect high priority targets for sys_id in top_target_systems: TopTargetAllocator(sys_id, allocation_helper).allocate() # enemy planets other_targeted_system_ids = [sys_id for sys_id in set(PlanetUtilsAI.get_systems(AIstate.opponentPlanetIDs)) if sys_id not in top_target_systems] for sys_id in other_targeted_system_ids: TargetAllocator(sys_id, allocation_helper).allocate() # colony / outpost targets other_targeted_system_ids = [sys_id for sys_id in list(set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs)) if sys_id not in top_target_systems] for sys_id in other_targeted_system_ids: OutpostTargetAllocator(sys_id, allocation_helper).allocate() # TODO blockade enemy systems # interior systems targetable_ids = set(state.get_systems_by_supply_tier(0)) current_mil_systems = [sid for sid, _, _, _, _ in allocation_helper.allocations] interior_targets1 = targetable_ids.difference(current_mil_systems) interior_targets = [sid for sid in interior_targets1 if ( allocation_helper.threat_bias + systems_status.get(sid, {}).get('totalThreat', 0) > 0.8 * allocation_helper.already_assigned_rating[sid])] for sys_id in interior_targets: InteriorTargetsAllocator(sys_id, allocation_helper).allocate() # TODO Exploration targets # border protections visible_system_ids = aistate.visInteriorSystemIDs | aistate.visBorderSystemIDs accessible_system_ids = ([sys_id for sys_id in visible_system_ids if universe.systemsConnected(sys_id, home_system_id, empire_id)] if home_system_id != INVALID_ID else []) current_mil_systems = [sid for sid, alloc, rvp, take_any, _ in allocation_helper.allocations if alloc > 0] border_targets1 = [sid for sid in accessible_system_ids if sid not in current_mil_systems] border_targets = [sid for sid in border_targets1 if ( allocation_helper.threat_bias + systems_status.get(sid, {}).get('fleetThreat', 0) + systems_status.get(sid, {}).get( 'planetThreat', 0) > 0.8 * allocation_helper.already_assigned_rating[sid])] for sys_id in border_targets: BorderSecurityAllocator(sys_id, allocation_helper).allocate() except ReleaseMilitaryException: try_again(all_military_fleet_ids) return new_allocations = [] remaining_mil_rating = avail_mil_rating # for top categories assign max_alloc right away as available for cat in ['capitol', 'occupied', 'topTargets']: for sid, alloc, rvp, take_any, max_alloc in allocation_helper.allocation_by_groups.get(cat, []): if remaining_mil_rating <= 0: break this_alloc = min(remaining_mil_rating, max_alloc) new_allocations.append((sid, this_alloc, alloc, rvp, take_any)) remaining_mil_rating = rating_difference(remaining_mil_rating, this_alloc) base_allocs = set() # for lower priority categories, first assign base_alloc around to all, then top up as available for cat in ['otherTargets', 'accessibleTargets', 'exploreTargets']: for sid, alloc, rvp, take_any, max_alloc in allocation_helper.allocation_by_groups.get(cat, []): if remaining_mil_rating <= 0: break alloc = min(remaining_mil_rating, alloc) base_allocs.add(sid) remaining_mil_rating = rating_difference(remaining_mil_rating, alloc) for cat in ['otherTargets', 'accessibleTargets', 'exploreTargets']: for sid, alloc, rvp, take_any, max_alloc in allocation_helper.allocation_by_groups.get(cat, []): if sid not in base_allocs: break if remaining_mil_rating <= 0: new_allocations.append((sid, alloc, alloc, rvp, take_any)) else: local_max_avail = combine_ratings(remaining_mil_rating, alloc) new_rating = min(local_max_avail, max_alloc) new_allocations.append((sid, new_rating, alloc, rvp, take_any)) remaining_mil_rating = rating_difference(local_max_avail, new_rating) if "Main" in thisround: _military_allocations = new_allocations if _verbose_mil_reporting or "Main" in thisround: debug("------------------------------\nFinal %s Round Military Allocations: %s \n-----------------------" % (thisround, dict([(sid, alloc) for sid, alloc, _, _, _ in new_allocations]))) debug("(Apparently) remaining military rating: %.1f" % remaining_mil_rating) return new_allocations
def assign_military_fleets_to_systems(use_fleet_id_list=None, allocations=None, round=1): # assign military fleets to military theater systems global _military_allocations universe = fo.getUniverse() if allocations is None: allocations = [] doing_main = (use_fleet_id_list is None) aistate = get_aistate() if doing_main: aistate.misc['ReassignedFleetMissions'] = [] base_defense_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.ORBITAL_DEFENSE) unassigned_base_defense_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types(base_defense_ids) for fleet_id in unassigned_base_defense_ids: fleet = universe.getFleet(fleet_id) if not fleet: continue sys_id = fleet.systemID target = TargetSystem(sys_id) fleet_mission = aistate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() mission_type = MissionType.ORBITAL_DEFENSE fleet_mission.set_target(mission_type, target) all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) if not all_military_fleet_ids: _military_allocations = [] return avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids) these_allocations = _military_allocations debug("==================================================") debug("Assigning military fleets") debug("---------------------------------") else: avail_mil_fleet_ids = list(use_fleet_id_list) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids) these_allocations = allocations # send_for_repair(mil_needing_repair_ids) #currently, let get taken care of by AIFleetMission.generate_fleet_orders() # get systems to defend avail_mil_fleet_ids = set(avail_mil_fleet_ids) for sys_id, alloc, minalloc, rvp, takeAny in these_allocations: if not doing_main and not avail_mil_fleet_ids: break found_fleets = [] found_stats = {} 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: debug("NO available/suitable military allocation for system %d ( %s ) -- requested allocation %8d, found available rating %8d in fleets %s" % (sys_id, universe.getSystem(sys_id).name, minalloc, found_stats.get('rating', 0), found_fleets)) avail_mil_fleet_ids.update(found_fleets) continue else: these_fleets = found_fleets elif doing_main and _verbose_mil_reporting: debug("FULL+ military allocation for system %d ( %s ) -- requested allocation %8d, got %8d with fleets %s" % (sys_id, universe.getSystem(sys_id).name, alloc, found_stats.get('rating', 0), these_fleets)) target = TargetSystem(sys_id) for fleet_id in these_fleets: fo.issueAggressionOrder(fleet_id, True) fleet_mission = aistate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() if sys_id in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs): mission_type = MissionType.SECURE else: mission_type = MissionType.MILITARY fleet_mission.set_target(mission_type, target) fleet_mission.generate_fleet_orders() if not doing_main: aistate.misc.setdefault('ReassignedFleetMissions', []).append(fleet_mission) if doing_main: debug("---------------------------------") last_round = 3 last_round_name = "LastRound" if round <= last_round: # check if any fleets remain unassigned all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) allocations = [] round += 1 thisround = "Extras Remaining Round %d" % round if round < last_round else last_round_name if avail_mil_fleet_ids: debug("Still have available military fleets: %s" % avail_mil_fleet_ids) allocations = get_military_fleets(mil_fleets_ids=avail_mil_fleet_ids, try_reset=False, thisround=thisround) if allocations: assign_military_fleets_to_systems(use_fleet_id_list=avail_mil_fleet_ids, allocations=allocations, round=round)
def evaluate_invasion_planet(planet_id): """Return the invasion value (score, troops) of a planet.""" universe = fo.getUniverse() empire_id = fo.empireID() detail = [] planet = universe.getPlanet(planet_id) if planet is None: debug("Invasion AI couldn't access any info for planet id %d" % planet_id) return [0, 0] system_id = planet.systemID # by using the following instead of simply relying on stealth meter reading, # can (sometimes) plan ahead even if planet is temporarily shrouded by an ion storm predicted_detectable = EspionageAI.colony_detectable_by_empire( planet_id, empire=fo.empireID(), default_result=False) if not predicted_detectable: if get_partial_visibility_turn(planet_id) < fo.currentTurn(): debug("InvasionAI predicts planet id %d to be stealthed" % planet_id) return [0, 0] else: debug( "InvasionAI predicts planet id %d to be stealthed" % planet_id + ", but somehow have current visibility anyway, will still consider as target" ) # Check if the target planet was extra-stealthed somehow its system was last viewed # this test below may augment the tests above, # but can be thrown off by temporary combat-related sighting system_last_seen = get_partial_visibility_turn(planet_id) planet_last_seen = get_partial_visibility_turn(system_id) if planet_last_seen < system_last_seen: # TODO: track detection strength, order new scouting when it goes up debug( "Invasion AI considering planet id %d (stealthed at last view), still proceeding." % planet_id) # get a baseline evaluation of the planet as determined by ColonisationAI species_name = planet.speciesName species = fo.getSpecies(species_name) empire_research_list = tuple(element.tech for element in fo.getEmpire().researchQueue) if not species or AIDependencies.TAG_DESTROYED_ON_CONQUEST in species.tags: # this call iterates over this Empire's available species with which it could colonize after an invasion planet_eval = ColonisationAI.assign_colonisation_values( [planet_id], MissionType.INVASION, None, detail) colony_base_value = max( 0.75 * planet_eval.get(planet_id, [0])[0], calculate_planet_colonization_rating. calculate_planet_colonization_rating( planet_id=planet_id, mission_type=MissionType.OUTPOST, spec_name=None, detail=detail, empire_research_list=empire_research_list, ), ) else: colony_base_value = calculate_planet_colonization_rating.calculate_planet_colonization_rating( planet_id=planet_id, mission_type=MissionType.INVASION, spec_name=species_name, detail=detail, empire_research_list=empire_research_list, ) # Add extra score for all buildings on the planet building_values = { "BLD_IMPERIAL_PALACE": 1000, "BLD_CULTURE_ARCHIVES": 1000, "BLD_AUTO_HISTORY_ANALYSER": 100, "BLD_SHIPYARD_BASE": 100, "BLD_SHIPYARD_ORG_ORB_INC": 200, "BLD_SHIPYARD_ORG_XENO_FAC": 200, "BLD_SHIPYARD_ORG_CELL_GRO_CHAMB": 200, "BLD_SHIPYARD_CON_NANOROBO": 300, "BLD_SHIPYARD_CON_GEOINT": 400, "BLD_SHIPYARD_CON_ADV_ENGINE": 1000, "BLD_SHIPYARD_AST": 300, "BLD_SHIPYARD_AST_REF": 1000, "BLD_SHIPYARD_ENRG_SOLAR": 1500, "BLD_INDUSTRY_CENTER": 500, "BLD_GAS_GIANT_GEN": 200, "BLD_SOL_ORB_GEN": 800, "BLD_BLACK_HOLE_POW_GEN": 2000, "BLD_ENCLAVE_VOID": 500, "BLD_NEUTRONIUM_EXTRACTOR": 2000, "BLD_NEUTRONIUM_SYNTH": 2000, "BLD_NEUTRONIUM_FORGE": 1000, "BLD_CONC_CAMP": 100, "BLD_BIOTERROR_PROJECTOR": 1000, "BLD_SHIPYARD_ENRG_COMP": 3000, } bld_tally = 0 for bldType in [ universe.getBuilding(bldg).buildingTypeName for bldg in planet.buildingIDs ]: bval = building_values.get(bldType, 50) bld_tally += bval detail.append("%s: %d" % (bldType, bval)) # Add extra score for unlocked techs when we conquer the species tech_tally = 0 value_per_pp = 4 for unlocked_tech in AIDependencies.SPECIES_TECH_UNLOCKS.get( species_name, []): if not tech_is_complete(unlocked_tech): rp_cost = fo.getTech(unlocked_tech).researchCost(empire_id) tech_value = value_per_pp * rp_cost tech_tally += tech_value detail.append("%s: %d" % (unlocked_tech, tech_value)) least_jumps_path, max_jumps = _get_path_from_capital(planet) clear_path = True aistate = get_aistate() system_status = aistate.systemStatus.get(system_id, {}) system_fleet_treat = system_status.get("fleetThreat", 1000) system_monster_threat = system_status.get("monsterThreat", 0) sys_total_threat = system_fleet_treat + system_monster_threat + system_status.get( "planetThreat", 0) max_path_threat = system_fleet_treat mil_ship_rating = MilitaryAI.cur_best_mil_ship_rating() for path_sys_id in least_jumps_path: path_leg_status = aistate.systemStatus.get(path_sys_id, {}) path_leg_threat = path_leg_status.get( "fleetThreat", 1000) + path_leg_status.get("monsterThreat", 0) if path_leg_threat > 0.5 * mil_ship_rating: clear_path = False if path_leg_threat > max_path_threat: max_path_threat = path_leg_threat pop = planet.currentMeterValue(fo.meterType.population) target_pop = planet.currentMeterValue(fo.meterType.targetPopulation) troops = planet.currentMeterValue(fo.meterType.troops) troop_regen = planet.currentMeterValue( fo.meterType.troops) - planet.initialMeterValue(fo.meterType.troops) max_troops = planet.currentMeterValue(fo.meterType.maxTroops) # TODO: refactor troop determination into function for use in mid-mission updates and also consider defender techs max_troops += AIDependencies.TROOPS_PER_POP * (target_pop - pop) this_system = universe.getSystem(system_id) secure_targets = [system_id] + list(this_system.planetIDs) system_secured = False secure_fleets = get_aistate().get_fleet_missions_with_any_mission_types( [MissionType.SECURE, MissionType.MILITARY]) for mission in secure_fleets: secure_fleet_id = mission.fleet.id s_fleet = universe.getFleet(secure_fleet_id) if not s_fleet or s_fleet.systemID != system_id: continue if mission.type in [MissionType.SECURE, MissionType.MILITARY]: target_obj = mission.target.get_object() if target_obj is not None and target_obj.id in secure_targets: system_secured = True break system_secured = system_secured and system_status.get("myFleetRating", 0) debug( "Invasion eval of %s\n" " - maxShields: %.1f\n" " - sysFleetThreat: %.1f\n" " - sysMonsterThreat: %.1f", planet, planet.currentMeterValue(fo.meterType.maxShield), system_fleet_treat, system_monster_threat, ) enemy_val = 0 if planet.owner != -1: # value in taking this away from an enemy enemy_val = 20 * ( planet.currentMeterValue(fo.meterType.targetIndustry) + 2 * planet.currentMeterValue(fo.meterType.targetResearch)) # devalue invasions that would require too much military force preferred_max_portion = MilitaryAI.get_preferred_max_military_portion_for_single_battle( ) total_max_mil_rating = MilitaryAI.get_concentrated_tot_mil_rating() threat_exponent = 2 # TODO: make this a character trait; higher aggression with a lower exponent threat_factor = min( 1, preferred_max_portion * total_max_mil_rating / (sys_total_threat + 0.001))**threat_exponent design_id, _, locs = get_best_ship_info(PriorityType.PRODUCTION_INVASION) if not locs or not universe.getPlanet(locs[0]): # We are in trouble anyway, so just calculate whatever approximation... build_time = 4 planned_troops = troops if system_secured else min( troops + troop_regen * (max_jumps + build_time), max_troops) planned_troops += 0.01 # we must attack with more troops than there are defenders troop_cost = math.ceil((planned_troops + _TROOPS_SAFETY_MARGIN) / 6.0) * 20 * FleetUtilsAI.get_fleet_upkeep() else: loc = locs[0] species_here = universe.getPlanet(loc).speciesName design = fo.getShipDesign(design_id) cost_per_ship = design.productionCost(empire_id, loc) build_time = design.productionTime(empire_id, loc) troops_per_ship = CombatRatingsAI.weight_attack_troops( design.troopCapacity, get_species_tag_grade(species_here, Tags.ATTACKTROOPS)) planned_troops = troops if system_secured else min( troops + troop_regen * (max_jumps + build_time), max_troops) planned_troops += 0.01 # we must attack with more troops than there are defenders ships_needed = math.ceil( (planned_troops + _TROOPS_SAFETY_MARGIN) / float(troops_per_ship)) troop_cost = ships_needed * cost_per_ship # fleet upkeep is already included in query from server # apply some bias to expensive operations normalized_cost = float(troop_cost) / max(fo.getEmpire().productionPoints, 1) normalized_cost = max(1.0, normalized_cost) cost_score = (normalized_cost**2 / 50.0) * troop_cost base_score = colony_base_value + bld_tally + tech_tally + enemy_val - cost_score # If the AI does have enough total military to attack this target, and the target is more than minimally valuable, # don't let the threat_factor discount the adjusted value below MIN_INVASION_SCORE +1, so that if there are no # other targets the AI could still pursue this one. Otherwise, scoring pressure from # MilitaryAI.get_preferred_max_military_portion_for_single_battle might prevent the AI from attacking heavily # defended but still defeatable targets even if it has no softer targets available. if total_max_mil_rating > sys_total_threat and base_score > 2 * MIN_INVASION_SCORE: threat_factor = max(threat_factor, (MIN_INVASION_SCORE + 1) / base_score) planet_score = retaliation_risk_factor(planet.owner) * threat_factor * max( 0, base_score) if clear_path: planet_score *= 1.5 debug( " - planet score: %.2f\n" " - planned troops: %.2f\n" " - projected troop cost: %.1f\n" " - threat factor: %s\n" " - planet detail: %s\n" " - popval: %.1f\n" " - bldval: %s\n" " - enemyval: %s", planet_score, planned_troops, troop_cost, threat_factor, detail, colony_base_value, bld_tally, enemy_val, ) debug(" - system secured: %s" % system_secured) return [planet_score, planned_troops]
def issue_fleet_orders(self): """issues AIFleetOrders which can be issued in system and moves to next one if is possible""" # TODO: priority order_completed = True debug( "\nChecking orders for fleet %s (on turn %d), with mission type %s and target %s", self.fleet.get_object(), fo.currentTurn(), self.type or "No mission", self.target or "No Target", ) if MissionType.INVASION == self.type: self._check_retarget_invasion() just_issued_move_order = False last_move_target_id = INVALID_ID for fleet_order in self.orders: if isinstance( fleet_order, (OrderColonize, OrderOutpost, OrderInvade)) and self._check_abort_mission(fleet_order): self.clear_fleet_orders() self.clear_target() return aistate = get_aistate() for fleet_order in self.orders: if just_issued_move_order and self.fleet.get_object( ).systemID != last_move_target_id: # having just issued a move order, we will normally stop issuing orders this turn, except that if there # are consecutive move orders we will consider moving through the first destination rather than stopping # Without the below noinspection directive, PyCharm is concerned about the 2nd part of the test # noinspection PyTypeChecker if not isinstance(fleet_order, OrderMove) or self.need_to_pause_movement( last_move_target_id, fleet_order): break debug("Checking order: %s" % fleet_order) self.check_mergers(context=str(fleet_order)) if fleet_order.can_issue_order(verbose=False): # only move if all other orders completed if isinstance(fleet_order, OrderMove) and order_completed: debug("Issuing fleet order %s" % fleet_order) fleet_order.issue_order() just_issued_move_order = True last_move_target_id = fleet_order.target.id elif not isinstance(fleet_order, OrderMove): debug("Issuing fleet order %s" % fleet_order) fleet_order.issue_order() else: debug( "NOT issuing (even though can_issue) fleet order %s" % fleet_order) status_words = tuple( ["not", ""][_s] for _s in [fleet_order.order_issued, fleet_order.executed]) debug("Order %s issued and %s fully executed." % status_words) if not fleet_order.executed: order_completed = False else: # check that we're not held up by a Big Monster if fleet_order.order_issued: # A previously issued order that wasn't instantly executed must have had cirumstances change so that # the order can't currently be reissued (or perhaps simply a savegame has been reloaded on the same # turn the order was issued). if not fleet_order.executed: order_completed = False # Go on to the next order. continue debug("CAN'T issue fleet order %s because:" % fleet_order) fleet_order.can_issue_order(verbose=True) if isinstance(fleet_order, OrderMove): this_system_id = fleet_order.target.id this_status = aistate.systemStatus.setdefault( this_system_id, {}) threat_threshold = fo.currentTurn( ) * MilitaryAI.cur_best_mil_ship_rating() / 4.0 if this_status.get("monsterThreat", 0) > threat_threshold: # if this move order is not this mil fleet's final destination, and blocked by Big Monster, # release and hope for more effective reassignment if (self.type not in (MissionType.MILITARY, MissionType.SECURE) or fleet_order != self.orders[-1]): debug( "Aborting mission due to being blocked by Big Monster at system %d, threat %d" % (this_system_id, aistate.systemStatus[this_system_id] ["monsterThreat"])) debug("Full set of orders were:") for this_order in self.orders: debug(" - %s" % this_order) self.clear_fleet_orders() self.clear_target() return break # do not order the next order until this one is finished. else: # went through entire order list if order_completed: debug("Final order is completed") orders = self.orders last_order = orders[-1] if orders else None universe = fo.getUniverse() if last_order and isinstance(last_order, OrderColonize): planet = universe.getPlanet(last_order.target.id) sys_partial_vis_turn = get_partial_visibility_turn( planet.systemID) planet_partial_vis_turn = get_partial_visibility_turn( planet.id) if planet_partial_vis_turn == sys_partial_vis_turn and not planet.initialMeterValue( fo.meterType.population): warning( "Fleet %s has tentatively completed its " "colonize mission but will wait to confirm population.", self.fleet, ) debug(" Order details are %s" % last_order) debug( " Order is valid: %s; issued: %s; executed: %s" % (last_order.is_valid(), last_order.order_issued, last_order.executed)) if not last_order.is_valid(): source_target = last_order.fleet target_target = last_order.target debug( " source target validity: %s; target target validity: %s " % (bool(source_target), bool(target_target))) return # colonize order must not have completed yet clear_all = True last_sys_target = INVALID_ID secure_targets = set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs) if last_order and isinstance(last_order, OrderMilitary): last_sys_target = last_order.target.id # not doing this until decide a way to release from a SECURE mission # if (MissionType.SECURE == self.type) or if last_sys_target in secure_targets: # consider a secure mission if last_sys_target in AIstate.colonyTargetedSystemIDs: secure_type = "Colony" elif last_sys_target in AIstate.outpostTargetedSystemIDs: secure_type = "Outpost" elif last_sys_target in AIstate.invasionTargetedSystemIDs: secure_type = "Invasion" else: secure_type = "Unidentified" debug( "Fleet %d has completed initial stage of its mission " "to secure system %d (targeted for %s), " "may release a portion of ships" % (self.fleet.id, last_sys_target, secure_type)) clear_all = False # for PROTECT_REGION missions, only release fleet if no more threat if self.type == MissionType.PROTECT_REGION: # use military logic code below to determine if can release # any or even all of the ships. clear_all = False last_sys_target = self.target.id debug( "Check if PROTECT_REGION mission with target %d is finished.", last_sys_target) # for SECURE missions, only release fleet if no settle or invade mission is ongoing if self.type == MissionType.SECURE and self.target.id in secure_targets: debug(f"Secure mission for {self.target} not yet finished") return fleet_id = self.fleet.id if clear_all: if orders: debug( "Fleet %d has completed its mission; clearing all orders and targets." % self.fleet.id) debug("Full set of orders were:") for this_order in orders: debug("\t\t %s" % this_order) self.clear_fleet_orders() self.clear_target() if aistate.get_fleet_role(fleet_id) in ( MissionType.MILITARY, MissionType.SECURE): allocations = MilitaryAI.get_military_fleets( mil_fleets_ids=[fleet_id], try_reset=False, thisround="Fleet %d Reassignment" % fleet_id) if allocations: MilitaryAI.assign_military_fleets_to_systems( use_fleet_id_list=[fleet_id], allocations=allocations) else: # no orders debug("No Current Orders") else: potential_threat = combine_ratings( MilitaryAI.get_system_local_threat(last_sys_target), MilitaryAI.get_system_neighbor_threat(last_sys_target), ) threat_present = potential_threat > 0 debug("Fleet threat present? %s", threat_present) target_system = universe.getSystem(last_sys_target) if not threat_present and target_system: for pid in target_system.planetIDs: planet = universe.getPlanet(pid) if (planet and planet.owner != fo.empireID() and planet.currentMeterValue( fo.meterType.maxDefense) > 0): debug("Found local planetary threat: %s", planet) threat_present = True break if not threat_present: debug( "No current threat in target system; releasing a portion of ships." ) # at least first stage of current task is done; # release extra ships for potential other deployments new_fleets = FleetUtilsAI.split_fleet(self.fleet.id) if self.type == MissionType.PROTECT_REGION: self.clear_fleet_orders() self.clear_target() new_fleets.append(self.fleet.id) else: debug( "Threat remains in target system; Considering to release some ships." ) new_fleets = [] fleet_portion_to_remain = self._portion_of_fleet_needed_here( ) if fleet_portion_to_remain >= 1: debug( "Can not release fleet yet due to large threat." ) elif fleet_portion_to_remain > 0: debug( "Not all ships are needed here - considering releasing a few" ) # TODO: Rate against specific enemy threat cause fleet_remaining_rating = CombatRatingsAI.get_fleet_rating( fleet_id) fleet_min_rating = fleet_portion_to_remain * fleet_remaining_rating debug("Starting rating: %.1f, Target rating: %.1f", fleet_remaining_rating, fleet_min_rating) allowance = CombatRatingsAI.rating_needed( fleet_remaining_rating, fleet_min_rating) debug( "May release ships with total rating of %.1f", allowance) for ship_id in self.fleet.get_object().shipIDs: if len(self.fleet.get_object().shipIDs) == 1: break ship_rating = CombatRatingsAI.get_ship_rating( ship_id) debug( "Considering to release ship %d with rating %.1f", ship_id, ship_rating) if ship_rating > allowance: debug( "Remaining rating insufficient. Not released." ) continue debug("Splitting from fleet.") new_fleet_id = FleetUtilsAI.split_ship_from_fleet( fleet_id, ship_id) if assertion_fails(new_fleet_id != INVALID_ID): break new_fleets.append(new_fleet_id) fleet_remaining_rating = CombatRatingsAI.rating_difference( fleet_remaining_rating, ship_rating) allowance = CombatRatingsAI.rating_difference( fleet_remaining_rating, fleet_min_rating) debug( "Remaining fleet rating: %.1f - Allowance: %.1f", fleet_remaining_rating, allowance) if new_fleets: aistate.get_fleet_role(fleet_id, force_new=True) aistate.update_fleet_rating(fleet_id) aistate.ensure_have_fleet_missions(new_fleets) else: debug( "Planetary defenses are deemed sufficient. Release fleet." ) new_fleets = FleetUtilsAI.split_fleet( self.fleet.id) new_military_fleets = [] for fleet_id in new_fleets: if aistate.get_fleet_role( fleet_id) in COMBAT_MISSION_TYPES: new_military_fleets.append(fleet_id) allocations = [] if new_military_fleets: allocations = MilitaryAI.get_military_fleets( mil_fleets_ids=new_military_fleets, try_reset=False, thisround="Fleet Reassignment %s" % new_military_fleets, ) if allocations: MilitaryAI.assign_military_fleets_to_systems( use_fleet_id_list=new_military_fleets, allocations=allocations)