def __init__(self, status_only: bool = False): self._empire = fo.getEmpire() self._universe = fo.getUniverse() self._aistate = get_aistate() # resourceAvailable includes this turns production, but that is wrong for influence self._ip = self._empire.resourceAvailable( fo.resourceType.influence) - self._get_infl_prod() self._adopted = set(self._empire.adoptedPolicies) # When we continue a game in which we just adopted a policy, game state shows the policy as adopted, # but IP still unspent. Correct it here, then calculate anew whether we want to adopt it. if not status_only: for entry in self._empire.turnsPoliciesAdopted: if entry.data() == fo.currentTurn(): debug(f"reverting saved adopt {entry.key()}") fo.issueDeadoptPolicyOrder(entry.key()) self._adopted.remove(entry.key()) self._originally_adopted = copy(self._adopted) empire_owned_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire() self._populated_planet_ids = PlanetUtilsAI.get_populated_planet_ids( empire_owned_planet_ids) self._num_populated = len(self._populated_planet_ids) self._num_outposts = len(empire_owned_planet_ids) - self._num_populated self._max_turn_bureaucracy = self._calculate_max_turn_bureaucracy() self._centralization_cost = fo.getPolicy(centralization).adoptionCost() self._bureaucracy_cost = fo.getPolicy(bureaucracy).adoptionCost() self._wanted_ip = self._wanted_for_bureaucracy() self._adoptable = self._get_adoptable() self._available = set(self._empire.availablePolicies) self._rating_functions = { propaganda: lambda: 20 + self._rate_opinion(propaganda), algo_research: self._rate_algo_research, diversity: self._rate_diversity, artisans: self._rate_artisans, population: self._rate_population, # military policies are mostly chosen by opinion, plus some rule-of-thumb values allied_repair: lambda: self._rate_opinion( charge), # no effect, unless we have allies... charge: lambda: 5 + self._rate_opinion(charge), # A small bonus in battle scanning: lambda: 20 + self._rate_opinion( scanning), # May help us detect ships and planets simplicity: lambda: 20 + self._rate_opinion( simplicity), # Makes simple ships cheaper engineering: self._rate_engineering_corps, exploration: lambda: 10 + self._rate_opinion( exploration), # may give a little research, speeds up scouts flanking: lambda: 5 + self._rate_opinion( flanking), # A small bonus in battle recruitment: lambda: 15 + self._rate_opinion( recruitment), # cheaper troop ships }
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() aistate = get_aistate() visible_system_ids = list(aistate.visInteriorSystemIDs) + list( aistate.visBorderSystemIDs) if home_system_id != INVALID_ID: accessible_system_ids = [ sys_id for sys_id in visible_system_ids if systems_connected(sys_id, home_system_id) ] else: debug( "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)) debug("Current Invasion Targeted SystemIDs: %s" % PlanetUtilsAI.sys_name_ids(AIstate.invasionTargetedSystemIDs)) debug("Current Invasion Targeted PlanetIDs: %s" % PlanetUtilsAI.planet_string(invasion_targeted_planet_ids)) debug(invasion_fleet_ids and "Invasion Fleet IDs: %s" % invasion_fleet_ids or "Available Invasion Fleets: 0") debug("Invasion Fleets Without Missions: %s" % num_invasion_fleets) invasion_timer.start("planning troop base production") reserved_troop_base_targets = [] if aistate.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 # get_aistate().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). # Pass 1: identify qualifying base troop invasion targets for pid in invadable_planet_ids: # TODO: reorganize if pid in aistate.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 get_colonized_planets_in_system(sys_id): if available_pp.get( pid2, 0 ) < 2: # TODO: improve troop base PP sufficiency determination break planet2 = universe.getPlanet(pid2) if not planet2 or not can_build_ship_for_species( planet2.speciesName): continue best_base_trooper_here = 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 = get_species_tag_grade( planet2.speciesName, Tags.ATTACKTROOPS) 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: aistate.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 # get_aistate().qualifyingTroopBaseTargets for pid in list(aistate.qualifyingTroopBaseTargets.keys()): planet = universe.getPlanet(pid) if planet and planet.owner == empire_id: del aistate.qualifyingTroopBaseTargets[pid] continue if pid in invasion_targeted_planet_ids: # TODO: consider overriding standard invasion mission continue if aistate.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) sys_id = planet.systemID this_sys_status = aistate.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 aistate.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 = aistate.qualifyingTroopBaseTargets[pid][0] best_base_trooper_here = 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 warning( "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 = get_species_tag_grade( loc_planet.speciesName, Tags.ATTACKTROOPS) troops_per_ship = CombatRatingsAI.weight_attack_troops( troops_per_ship, species_troop_grade) if not troops_per_ship: warning( "The best orbital invasion design at %s seems not to have any troop capacity." % loc_planet) continue _, col_design, build_choices = get_best_ship_info( PriorityType.PRODUCTION_ORBITAL_INVASION, loc) if not col_design: continue if loc not in build_choices: warning( "Best troop design %s can not be produced at planet with id: %s" % (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): debug( "ruling out base invasion troopers for %s due to high number (%d) required." % (planet, n_bases)) del aistate.qualifyingTroopBaseTargets[pid] continue debug( "Invasion base planning, need %d troops at %d per ship, will build %d ships." % ((planet_troops + 1), troops_per_ship, n_bases)) retval = fo.issueEnqueueShipProductionOrder(col_design.id, loc) debug("Enqueueing %d Troop Bases at %s for %s" % (n_bases, PlanetUtilsAI.planet_string(loc), PlanetUtilsAI.planet_string(pid))) if retval != 0: all_invasion_targeted_system_ids.add(planet.systemID) reserved_troop_base_targets.append(pid) aistate.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"), Number("Score"), Text("Species"), Number("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) invasion_table.print_table(info) sorted_planets = [x for x in sorted_planets if x[1] > 0] # 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 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] this_score, p_troops = evaluate_invasion_planet(pid, empire, secure_ai_fleet_missions, False) best_ship, col_design, build_choices = ProductionAI.getBestShipInfo(EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_ORBITAL_INVASION, loc) if not best_ship: continue n_bases = math.ceil((p_troops+1) / 2) # TODO: reconsider this +1 safety factor retval = fo.issueEnqueueShipProductionOrder(best_ship, 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_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] if best_base_trooper_here == None: # shouldn't be possible at this point, but just to be safe continue # TODO: have TroopShipDesigner give the expected number of troops including species effects troops_per_ship = best_base_trooper_here.troopCapacity if not troops_per_ship: continue this_score, p_troops = evaluate_invasion_planet(pid, empire, secure_ai_fleet_missions, False) best_ship, col_design, build_choices = ProductionAI.getBestShipInfo(EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_ORBITAL_INVASION, loc) if not best_ship: continue 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(best_ship, 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_invasion_fleets(): invasion_timer.start("gathering initial info") universe = fo.getUniverse() empire = fo.getEmpire() empire_id = empire.empireID all_invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.INVASION) AIstate.invasionFleetIDs = FleetUtilsAI.extract_fleet_ids_without_mission_types(all_invasion_fleet_ids) home_system_id = PlanetUtilsAI.get_capital_sys_id() 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 "Warning: Empire has no identifiable homeworld; will treat all visible planets as accessible." accessible_system_ids = visible_system_ids # TODO: check if any troop ships owned, use their system as home system 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") # 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 state.get_empire_inhabited_planets_by_system().get(sys_id, []): 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([MissionType.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.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 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 = 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: 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.get_best_ship_info(PriorityType.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)) evaluated_planets = assign_invasion_values(evaluated_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] 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.end()
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() aistate = get_aistate() visible_system_ids = list(aistate.visInteriorSystemIDs) + list(aistate.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: debug("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)) debug("Current Invasion Targeted SystemIDs: %s" % PlanetUtilsAI.sys_name_ids(AIstate.invasionTargetedSystemIDs)) debug("Current Invasion Targeted PlanetIDs: %s" % PlanetUtilsAI.planet_string(invasion_targeted_planet_ids)) debug(invasion_fleet_ids and "Invasion Fleet IDs: %s" % invasion_fleet_ids or "Available Invasion Fleets: 0") debug("Invasion Fleets Without Missions: %s" % num_invasion_fleets) invasion_timer.start("planning troop base production") reserved_troop_base_targets = [] if aistate.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 # get_aistate().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 = aistate.get_fleet_missions_with_any_mission_types([MissionType.SECURE, MissionType.MILITARY]) # Pass 1: identify qualifying base troop invasion targets for pid in invadable_planet_ids: # TODO: reorganize if pid in aistate.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: aistate.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 # get_aistate().qualifyingTroopBaseTargets for pid in aistate.qualifyingTroopBaseTargets.keys(): planet = universe.getPlanet(pid) if planet and planet.owner == empire_id: del aistate.qualifyingTroopBaseTargets[pid] continue if pid in invasion_targeted_planet_ids: # TODO: consider overriding standard invasion mission continue if aistate.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, True) sys_id = planet.systemID this_sys_status = aistate.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 aistate.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 = aistate.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)): debug("ruling out base invasion troopers for %s due to high number (%d) required." % (planet, n_bases)) del aistate.qualifyingTroopBaseTargets[pid] continue debug("Invasion base planning, need %d troops at %d per ship, will build %d ships." % ( (planet_troops + 1), troops_per_ship, n_bases)) retval = fo.issueEnqueueShipProductionOrder(col_design.id, loc) debug("Enqueueing %d Troop Bases at %s for %s" % (n_bases, PlanetUtilsAI.planet_string(loc), PlanetUtilsAI.planet_string(pid))) if retval != 0: all_invasion_targeted_system_ids.add(planet.systemID) reserved_troop_base_targets.append(pid) aistate.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 ]) info(invasion_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 get_invasion_fleets(): """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 supplyable 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_supplyable_system_ids = empire.fleetSupplyableSystemIDs fleet_supplyable_planet_ids = PlanetUtilsAI.get_planets_in__systems_ids(fleet_supplyable_system_ids) prime_invadable_system_ids = set(ColonisationAI.annexableSystemIDs) 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(exploredPlanetIDs) all_owned_planet_ids = PlanetUtilsAI.get_all_owned_planet_ids(acessible_planet_ids)#need these for unpopulated outposts # print "All Owned and Populated PlanetIDs: " + str(all_owned_planet_ids) 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) # print "Empire Owned PlanetIDs: " + str(empire_owned_planet_ids) 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, empire_id) invasion_targeted_planet_ids.extend( get_invasion_targeted_planet_ids(universe.planetIDs, EnumsAI.AIFleetMissionType.FLEET_MISSION_ORBITAL_INVASION, empire_id)) 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: " + str(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: " + str(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 if len (invadable_planet_ids) > 0: #print "Evaluating Troop Bases (SpaceInvaders) for %s"%(invadable_planet_ids) pass for pid in invadable_planet_ids: #TODO: reorganize planet = universe.getPlanet(pid) if not planet: continue sys_id = planet.systemID sys_partial_vis_turn = dict_from_map(universe.getVisibilityTurnsMap(planet.systemID, empire_id)).get(fo.visibility.partial, -9999) planet_partial_vis_turn = dict_from_map(universe.getVisibilityTurnsMap(pid, empire_id)).get(fo.visibility.partial, -9999) if planet_partial_vis_turn < sys_partial_vis_turn: #print "rejecting %s due to stealth"%planet.name continue for pid2 in ColonisationAI.empireSpeciesSystems.get(sys_id, {}).get('pids', []): if available_pp.get(pid2, 0) < 2: #TODO: improve troop base PP sufficiency determination #print "rejecting %s due to insufficient avail PP"%planet.name break planet2 = universe.getPlanet(pid2) if not planet2: continue if (pid not in foAI.foAIstate.qualifyingTroopBaseTargets) and (planet2.speciesName in ColonisationAI.empireShipBuilders): #print "Adding %s to Troop Bases (SpaceInvaders) potential target list, from %s"%(planet.name, planet2.name) 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] secureAIFleetMissions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([EnumsAI.AIFleetMissionType.FLEET_MISSION_SECURE]) #print "considering possible troop bases at %s" % (foAI.foAIstate.qualifyingTroopBaseTargets.keys()) 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] this_score, p_troops = evaluate_invasion_planet(pid, EnumsAI.AIFleetMissionType.FLEET_MISSION_INVASION, fleet_supplyable_planet_ids, empire, secureAIFleetMissions, False) bestShip, colDesign, buildChoices = ProductionAI.getBestShipInfo(EnumsAI.AIPriorityType.PRIORITY_PRODUCTION_ORBITAL_INVASION, loc) if not bestShip: #print "Error: no troop base can be built at ", PlanetUtilsAI.planet_name_ids([loc]) continue #print "selecting ", PlanetUtilsAI.planet_name_ids([loc]), " to build Orbital troop bases" n_bases = math.ceil((p_troops+1) / 2)#TODO: reconsider this +1 safety factor retval = fo.issueEnqueueShipProductionOrder(bestShip, 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)) res=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 evaluatedPlanetIDs = list(set(invadable_planet_ids) - set(invasion_targeted_planet_ids) - set(reserved_troop_base_targets) ) print "Evaluating potential invasions, PlanetIDs: " + str(evaluatedPlanetIDs) evaluatedPlanets = assign_invasion_values(evaluatedPlanetIDs, EnumsAI.AIFleetMissionType.FLEET_MISSION_INVASION, fleet_supplyable_planet_ids, empire) sortedPlanets = [(pid, pscore, ptroops) for (pid, (pscore, ptroops)) in evaluatedPlanets.items() ] sortedPlanets.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) sortedPlanets = [(pid, pscore%10000, ptroops) for (pid, pscore, ptroops) in sortedPlanets ] print if sortedPlanets: print "Invadable planets\nIDs, ID | Score | Name | Race | Troops" for pid, pscore, ptroops in sortedPlanets: 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" sortedPlanets = [(pid, pscore, ptroops) for (pid, pscore, ptroops) in sortedPlanets if pscore > 0] # export opponent planets for other AI modules AIstate.opponentPlanetIDs = [pid for pid, pscore, trp in sortedPlanets] AIstate.invasionTargets = sortedPlanets # 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(evaluatedPlanetIDs))) invasion_timer.end()
def get_invasion_fleets(): invasion_timer.start("gathering initial info") universe = fo.getUniverse() empire = fo.getEmpire() empire_id = empire.empireID all_invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.INVASION) AIstate.invasionFleetIDs = FleetUtilsAI.extract_fleet_ids_without_mission_types( all_invasion_fleet_ids) home_system_id = PlanetUtilsAI.get_capital_sys_id() 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 "Warning: Empire has no identifiable homeworld; will treat all visible planets as accessible." accessible_system_ids = visible_system_ids # TODO: check if any troop ships owned, use their system as home system 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") # 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 state.get_empire_inhabited_planets_by_system().get( sys_id, []): 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( [MissionType.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.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 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.get_best_ship_info( PriorityType.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)) evaluated_planets = assign_invasion_values(evaluated_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] 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.end()