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: sys_id = old_sys_id # 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 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 [ ship_id for ship_id in fleet.shipIDs if ship_id not in destroyed_object_ids ]): 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 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 report_system_threats(self): if fo.currentTurn() >= 100: return universe = fo.getUniverse() sys_id_list = sorted( universe.systemIDs ) # will normally look at this, the list of all known systems current_turn = fo.currentTurn() # assess fleet and planet threats threat_table = Table([ Text('System'), Text('Vis.'), Float('Total'), Float('by Monsters'), Float('by Fleets'), Float('by Planets'), Float('1 jump away'), Float('2 jumps'), Float('3 jumps') ], table_name="System Threat Turn %d" % current_turn) defense_table = Table([ Text('System Defenses'), Float('Total'), Float('by Planets'), Float('by Fleets'), Float('Fleets 1 jump away'), Float('2 jumps'), Float('3 jumps') ], table_name="System Defenses Turn %d" % current_turn) for sys_id in sys_id_list: sys_status = self.systemStatus.get(sys_id, {}) system = universe.getSystem(sys_id) threat_table.add_row([ system, "Yes" if sys_status.get('currently_visible', False) else "No", sys_status.get('totalThreat', 0), sys_status.get('monsterThreat', 0), sys_status.get('fleetThreat', 0), sys_status.get('planetThreat', 0), sys_status.get('neighborThreat', 0.0), sys_status.get('jump2_threat', 0.0), sys_status.get('jump3_threat', 0.0), ]) defense_table.add_row([ system, sys_status.get('all_local_defenses', 0.0), sys_status.get('mydefenses', {}).get('overall', 0.0), sys_status.get('myFleetRating', 0.0), sys_status.get('my_neighbor_rating', 0.0), sys_status.get('my_jump2_rating', 0.0), sys_status.get('my_jump3_rating', 0.0), ]) threat_table.print_table() defense_table.print_table()