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)) aistate = get_aistate() 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(aistate.qualifyingTroopBaseTargets.keys())) debug("Considering Base Troopers in %s, found planets %s and registered targets %s with status %s" % ( system.name, list(system.planetIDs), available_planets, [(pid, aistate.qualifyingTroopBaseTargets[pid]) for pid in available_planets])) targets = [pid for pid in available_planets if aistate.qualifyingTroopBaseTargets[pid][1] != -1] if not targets: debug("Failure: found no valid target for troop base in system %s" % system) continue status = aistate.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) aistate.qualifyingTroopBaseTargets[target_id][1] = -1 # TODO: should probably delete target = TargetPlanet(target_id) fleet_mission = aistate.get_fleet_mission(fid) fleet_mission.set_target(MissionType.ORBITAL_INVASION, target)
def __report_last_turn_fleet_missions(self): """Print a table reviewing last turn fleet missions to the log file.""" universe = fo.getUniverse() mission_table = Table( [Text('Fleet'), Text('Mission'), Text('Ships'), Float('Rating'), Float('Troops'), Text('Target')], table_name="Turn %d: Fleet Mission Review from Last Turn" % fo.currentTurn()) for fleet_id, mission in self.get_fleet_missions_map().items(): fleet = universe.getFleet(fleet_id) if not fleet: continue if not mission: mission_table.add_row([fleet]) else: mission_table.add_row([ fleet, mission.type or "None", len(fleet.shipIDs), CombatRatingsAI.get_fleet_rating(fleet_id), FleetUtilsAI.count_troops_in_fleet(fleet_id), mission.target or "-" ]) info(mission_table)
def split_new_fleets(self): """Split any new fleets (at new game creation, can have unplanned mix of ship roles).""" universe = fo.getUniverse() mission_table = Table([Text('Fleet'), Text('Mission'), Text('Ships'), Float('Rating'), Float('Troops'), Text('Target')], table_name="Turn %d: Fleet Mission Review from Last Turn" % fo.currentTurn()) for fleet_id, mission in self.get_fleet_missions_map().items(): fleet = universe.getFleet(fleet_id) if not fleet: continue if not mission: mission_table.add_row([fleet]) else: mission_table.add_row([ fleet, mission.type or "None", len(fleet.shipIDs), CombatRatingsAI.get_fleet_rating(fleet_id), FleetUtilsAI.count_troops_in_fleet(fleet_id), mission.target or "-" ]) mission_table.print_table() # TODO: check length of fleets for losses or do in AIstat.__cleanRoles 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 "Splitting new fleets" for fleet_id in fleets_to_split: fleet = universe.getFleet(fleet_id) if not fleet: print >> sys.stderr, "After splitting fleet: resulting fleet ID %d appears to not exist" % fleet_id continue fleet_len = len(list(fleet.shipIDs)) if fleet_len == 1: continue new_fleets = FleetUtilsAI.split_fleet(fleet_id) # try splitting fleet print "\t from splitting fleet ID %4d with %d ships, got %d new fleets:" % (fleet_id, fleet_len, len(new_fleets))
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! print "Empire Ship Count: ", self.shipCount print "Empire standard fighter summary: ", CombatRatingsAI.get_empire_standard_fighter().get_stats() print "------------------------"
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 __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 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 = AITarget.AITarget(EnumsAI.TargetType.TARGET_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 _check_retarget_invasion(self): """checks if an invasion mission should be retargeted""" universe = fo.getUniverse() empire = fo.getEmpire() empire_id = fo.empireID() fleet_id = self.fleet.id fleet = universe.getFleet(fleet_id) if fleet.systemID == -1: # next_loc = fleet.nextSystemID return # TODO: still check system = universe.getSystem(fleet.systemID) if not system: return orders = self.orders last_sys_target = -1 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, AIFleetMissionType.FLEET_MISSION_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.currentMeterValue(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 = -1 best_score = -1 target_troops = 0 # fleet_supplyable_system_ids = empire.fleetSupplyableSystemIDs fleet_supplyable_planet_ids = PlanetUtilsAI.get_planets_in__systems_ids( fleet_supplyable_system_ids) for pid, rating in InvasionAI.assign_invasion_values( open_targets, AIFleetMissionType.FLEET_MISSION_INVASION, fleet_supplyable_planet_ids, empire).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 == -1: 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() # pods_needed = max(0, math.ceil((target_troops - 2 * (FleetUtilsAI.count_parts_fleetwide(fleet_id, ["GT_TROOP_POD"])) + 0.05) / 2.0)) troops_needed = max( 0, target_troops - FleetUtilsAI.count_troops_in_fleet(fleet_id)) found_stats = {} 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( 1, target_stats, min_stats, found_stats, "", systems_to_check=[fleet.systemID], systems_checked=[], fleet_pool_set=set(new_fleets), fleet_list=found_fleets, verbose=False) for fid in found_fleets: FleetUtilsAI.merge_fleet_a_into_b(fid, fleet_id) target = Planet(target_id) self.add_target(AIFleetMissionType.FLEET_MISSION_INVASION, target) self.generate_fleet_orders()
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 _calculate_invasion_priority(): """Calculates the demand for troop ships by opponent planets.""" global allottedInvasionTargets if foAI.foAIstate.aggression <= fo.aggression.turtle: return 0 empire = fo.getEmpire() enemies_sighted = foAI.foAIstate.misc.get('enemies_sighted', {}) multiplier = 1 num_colonies = len(list(AIstate.popCtrIDs)) 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 if foAI.foAIstate.aggression == fo.aggression.beginner and fo.currentTurn( ) < 150: return 0 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 if foAI.foAIstate.aggression == fo.aggression.beginner: return 0.5 * invasion_priority else: return invasion_priority
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 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 __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 _calculate_invasion_priority(): """Calculates the demand for troop ships by opponent planets.""" aistate = get_aistate() if not aistate.character.may_invade(): return 0 empire = fo.getEmpire() enemies_sighted = aistate.misc.get("enemies_sighted", {}) multiplier = 1 colony_growth_barrier = aistate.character.max_number_colonies() if get_number_of_colonies() > colony_growth_barrier: return 0.0 if len(aistate.colonisablePlanetIDs) > 0: best_colony_score = max( 2, next(iter(aistate.colonisablePlanetIDs.items()))[1][0]) else: best_colony_score = 2 total_val = 0 troops_needed = 0 for pid, pscore, trp in AIstate.invasionTargets[:allotted_invasion_targets( )]: 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 aistate.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, _ = 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 colonies_is_under_attack(): if not colonies_is_under_treat(): 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 * aistate.character.invasion_priority_scaling()
def _check_retarget_invasion(self): """checks if an invasion mission should be retargeted""" universe = fo.getUniverse() empire = fo.getEmpire() empire_id = fo.empireID() fleet_id = self.fleet.id fleet = universe.getFleet(fleet_id) if fleet.systemID == -1: # next_loc = fleet.nextSystemID return # TODO: still check system = universe.getSystem(fleet.systemID) if not system: return orders = self.orders last_sys_target = -1 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.currentMeterValue(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 = -1 best_score = -1 target_troops = 0 # for pid, rating in InvasionAI.assign_invasion_values(open_targets, empire).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 == -1: 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() # pods_needed = max(0, math.ceil((target_troops - 2 * (FleetUtilsAI.count_parts_fleetwide(fleet_id, ["GT_TROOP_POD"])) + 0.05) / 2.0)) troops_needed = max(0, target_troops - FleetUtilsAI.count_troops_in_fleet(fleet_id)) found_stats = {} 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(1, target_stats, min_stats, found_stats, "", systems_to_check=[fleet.systemID], systems_checked=[], fleet_pool_set=set(new_fleets), fleet_list=found_fleets, verbose=False) for fid in found_fleets: FleetUtilsAI.merge_fleet_a_into_b(fid, fleet_id) target = Planet(target_id) self.add_target(MissionType.INVASION, target) self.generate_fleet_orders()
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) aistate = get_aistate() for pid in system.planetIDs: if pid in already_targeted or (pid in aistate.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 debug("\t 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 = TargetPlanet(target_id) self.set_target(MissionType.INVASION, target) self.generate_fleet_orders()
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) aistate = get_aistate() for pid in system.planetIDs: if pid in already_targeted or ( pid in aistate.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 debug("\t 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 = TargetPlanet(target_id) self.set_target(MissionType.INVASION, target) self.generate_fleet_orders()