def _portion_of_fleet_needed_here(self): """Calculate the portion of the fleet needed in target system considering enemy forces.""" # TODO check rating against planets if assertion_fails(self.type in COMBAT_MISSION_TYPES, msg=str(self)): return 0 if assertion_fails(self.target and self.target.id != INVALID_ID, msg=str(self)): return 0 system_id = self.target.id aistate = get_aistate() local_defenses = MilitaryAI.get_my_defense_rating_in_system(system_id) potential_threat = CombatRatingsAI.combine_ratings( MilitaryAI.get_system_local_threat(system_id), MilitaryAI.get_system_neighbor_threat(system_id) ) universe = fo.getUniverse() system = universe.getSystem(system_id) # tally planetary defenses total_defense = total_shields = 0 for planet_id in system.planetIDs: planet = universe.getPlanet(planet_id) total_defense += planet.currentMeterValue(fo.meterType.defense) total_shields += planet.currentMeterValue(fo.meterType.shield) planetary_ratings = total_defense * (total_shields + total_defense) potential_threat += planetary_ratings # TODO: rewrite to return min rating vs planets as well # consider safety factor just once here rather than everywhere below safety_factor = aistate.character.military_safety_factor() potential_threat *= safety_factor fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id) return CombatRatingsAI.rating_needed(potential_threat, local_defenses) / float(fleet_rating)
def merge_fleet_a_into_b(fleet_a_id, fleet_b_id, leave_rating=0, need_rating=0, context=""): universe = fo.getUniverse() fleet_a = universe.getFleet(fleet_a_id) fleet_b = universe.getFleet(fleet_b_id) if not fleet_a or not fleet_b: return 0 system_id = fleet_a.systemID if fleet_b.systemID != system_id: return 0 remaining_rating = CombatRatingsAI.get_fleet_rating(fleet_a_id) transferred_rating = 0 for ship_id in fleet_a.shipIDs: this_ship = universe.getShip(ship_id) if not this_ship: continue this_rating = CombatRatingsAI.ShipCombatStats(ship_id).get_rating() remaining_rating = CombatRatingsAI.rating_needed(remaining_rating, this_rating) if remaining_rating < leave_rating: # merging this would leave old fleet under minimum rating, try other ships. continue transferred = fo.issueFleetTransferOrder(ship_id, fleet_b_id) if transferred: transferred_rating = CombatRatingsAI.combine_ratings(transferred_rating, this_rating) else: print " *** transfer of ship %4d, formerly of fleet %4d, into fleet %4d failed; %s" % ( ship_id, fleet_a_id, fleet_b_id, (" context is %s" % context) if context else "") if need_rating != 0 and need_rating <= transferred_rating: break fleet_a = universe.getFleet(fleet_a_id) if not fleet_a or fleet_a.empty or fleet_a_id in universe.destroyedObjectIDs(fo.empireID()): foAI.foAIstate.delete_fleet_info(fleet_a_id) foAI.foAIstate.update_fleet_rating(fleet_b_id)
def _calculate_threat(self): nearby_forces = CombatRatingsAI.combine_ratings( self._potential_support(), self.assigned_rating) return ( self.threat_bias + + self.safety_factor * CombatRatingsAI.combine_ratings(self._local_threat(), self._neighbor_threat()) + + max(0., self._potential_threat() + self._jump2_threat() - nearby_forces))
def get_concentrated_tot_mil_rating(): """ Give an assessment of total miltary rating as if all fleets were merged into a single mega-fleet. :return: a military rating value :rtype: float """ return CombatRatingsAI.combine_ratings_list([CombatRatingsAI.get_fleet_rating(fleet_id) for fleet_id in FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)])
def get_rating(self, fleet_id, force_new=False, enemy_stats=None, against_planets=False): """Returns a dict with various rating info.""" if fleet_id in self.fleetStatus and not force_new and enemy_stats is None: return self.fleetStatus[fleet_id].get('rating', 0) else: fleet = fo.getUniverse().getFleet(fleet_id) if not fleet: return {} # TODO: also ensure any info for that fleet is deleted status = {'rating': CombatRatingsAI.get_fleet_rating(fleet_id, enemy_stats), 'ratingVsPlanets': CombatRatingsAI.get_fleet_rating_against_planets(fleet_id), 'sysID': fleet.systemID, 'nships': len(fleet.shipIDs)} self.fleetStatus[fleet_id] = status return status['rating'] if not against_planets else status['ratingVsPlanets']
def area_ratings(self, system_ids): """Returns (fleet_threat, max_threat, myFleetRating, threat_fleets) compiled over a group of systems.""" myrating = threat = max_threat = 0 threat_fleets = set() for sys_id in system_ids: sys_status = self.systemStatus.get(sys_id, {}) # TODO: have distinct treatment for both enemy_threat and fleetThreat, respectively fthreat = sys_status.get('enemy_threat', 0) max_threat = max(max_threat, fthreat) threat = CombatRatingsAI.combine_ratings(threat, fthreat) myrating = CombatRatingsAI.combine_ratings(myrating, sys_status.get('myFleetRating', 0)) # myrating = FleetUtilsAI.combine_ratings(myrating, sys_status.get('all_local_defenses', 0)) threat_fleets.update(sys_status.get('local_fleet_threats', [])) return threat, max_threat, myrating, threat_fleets
def __update_empire_standard_enemy(self): """Update the empire's standard enemy. The standard enemy is the enemy that is most often seen. """ # TODO: If no current information available, rate against own fighters universe = fo.getUniverse() empire_id = fo.empireID() # assess enemy fleets that may have been momentarily visible (start with dummy entries) dummy_stats = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) cur_e_fighters = Counter() # actual visible enemies old_e_fighters = Counter({dummy_stats: 0}) # destroyed enemies TODO: consider seen but out of sight enemies for fleet_id in universe.fleetIDs: fleet = universe.getFleet(fleet_id) if (not fleet or fleet.empty or fleet.ownedBy(empire_id) or fleet.unowned or not (fleet.hasArmedShips or fleet.hasFighterShips)): continue # track old/dead enemy fighters for rating assessments in case not enough current info ship_stats = CombatRatingsAI.FleetCombatStats(fleet_id).get_ship_stats(hashable=True) dead_fleet = fleet_id in universe.destroyedObjectIDs(empire_id) e_f_dict = old_e_fighters if dead_fleet else cur_e_fighters for stats in ship_stats: # log only ships that are armed if stats[0]: e_f_dict[stats] += 1 e_f_dict = cur_e_fighters or old_e_fighters self.__empire_standard_enemy = sorted([(v, k) for k, v in e_f_dict.items()])[-1][1] self.empire_standard_enemy_rating = self.get_standard_enemy().get_rating()
def can_issue_order(self, verbose=False): if not super(OrderMove, self).can_issue_order(verbose=verbose): return False # TODO: figure out better way to have invasions (& possibly colonizations) require visibility on target without needing visibility of all intermediate systems # if False and main_mission_type not in [MissionType.ATTACK, # TODO: consider this later # MissionType.MILITARY, # MissionType.SECURE, # MissionType.HIT_AND_RUN, # MissionType.EXPLORATION]: # if not universe.getVisibility(target_id, foAI.foAIstate.empireID) >= fo.visibility.partial: # #if not target_id in interior systems # foAI.foAIstate.needsEmergencyExploration.append(fleet.systemID) # return False system_id = self.fleet.get_system().id if system_id == self.target.get_system().id: return True # TODO: already there, but could consider retreating fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id) target_sys_status = foAI.foAIstate.systemStatus.get(self.target.id, {}) f_threat = target_sys_status.get('fleetThreat', 0) m_threat = target_sys_status.get('monsterThreat', 0) p_threat = target_sys_status.get('planetThreat', 0) threat = f_threat + m_threat + p_threat safety_factor = MilitaryAI.get_safety_factor() universe = fo.getUniverse() if fleet_rating >= safety_factor * threat: return True elif not p_threat and self.target.id in fo.getEmpire().supplyUnobstructedSystems: return True else: sys1 = universe.getSystem(system_id) sys1_name = sys1 and sys1.name or "unknown" target_system = self.target.get_system() target_system_name = (target_system and target_system.get_object().name) or "unknown" my_other_fleet_rating = foAI.foAIstate.systemStatus.get(self.target.id, {}).get('myFleetRating', 0) # TODO: adjust calc for any departing fleets is_military = foAI.foAIstate.get_fleet_role(self.fleet.id) == MissionType.MILITARY total_rating = my_other_fleet_rating + fleet_rating if any(( (my_other_fleet_rating > 3 * safety_factor * threat), (is_military and total_rating > safety_factor * threat), (is_military and total_rating > 0.8 * safety_factor * threat and fleet_rating > 0.2 * threat) )): if verbose: print "\tAdvancing fleet %d (rating %d) at system %d (%s) into system %d (%s) with threat %d because of sufficient empire fleet strength already at destination" % ( self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, threat) return True elif threat == p_threat and not self.fleet.get_object().aggressive and not my_other_fleet_rating and not target_sys_status.get('localEnemyFleetIDs', [-1]): if verbose: print ("\tAdvancing fleet %d (rating %d) at system %d (%s) into system %d (%s) with planet threat %d because nonaggressive" + " and no other fleets present to trigger combat") % (self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, threat) return True else: if verbose: print "\tHolding fleet %d (rating %d) at system %d (%s) before travelling to system %d (%s) with threat %d" % (self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, threat) needs_vis = foAI.foAIstate.misc.setdefault('needs_vis', []) if self.target.id not in needs_vis: needs_vis.append(self.target.id) return False return True
def __init__(self, aggression): # Do not allow to create AIstate instances with an invalid version number. if not hasattr(AIstate, 'version'): raise ConversionError("AIstate must have an integer version attribute for savegame compatibility") if not isinstance(AIstate.version, int): raise ConversionError("Version attribute of AIstate must be an integer!") if AIstate.version < 0: raise ConversionError("AIstate savegame compatibility version must be a positive integer!") # need to store the version explicitly as the class variable "version" is only stored in the # self.__class__.__dict__ while we only pickle the object (i.e. self.__dict__ ) self.version = AIstate.version # Debug info # unique id for game self.uid = self.generate_uid(first=True) # unique ids for turns. {turn: uid} self.turn_uids = {} self._aggression = aggression # 'global' (?) variables self.colonisablePlanetIDs = odict() self.colonisableOutpostIDs = odict() # self.__aiMissionsByFleetID = {} self.__shipRoleByDesignID = {} self.__fleetRoleByID = {} self.diplomatic_logs = {} self.__priorityByType = {} # initialize home system knowledge universe = fo.getUniverse() empire = fo.getEmpire() self.empireID = empire.empireID homeworld = universe.getPlanet(empire.capitalID) self.__origin_home_system_id = homeworld.systemID if homeworld else INVALID_ID self.visBorderSystemIDs = {self.__origin_home_system_id} self.visInteriorSystemIDs = set() self.exploredSystemIDs = set() self.unexploredSystemIDs = {self.__origin_home_system_id} self.fleetStatus = {} # keys: 'sysID', 'nships', 'rating' # systemStatus keys: 'name', 'neighbors' (sysIDs), '2jump_ring' (sysIDs), '3jump_ring', '4jump_ring', 'enemy_ship_count' # 'fleetThreat', 'planetThreat', 'monsterThreat' (specifically, immobile nonplanet threat), 'totalThreat', 'localEnemyFleetIDs', # 'neighborThreat', 'max_neighbor_threat', 'jump2_threat' (up to 2 jumps away), 'jump3_threat', 'jump4_threat', 'regional_threat' # 'myDefenses' (planet rating), 'myfleets', 'myFleetsAccessible'(not just next desitination), 'myFleetRating' # 'my_neighbor_rating' (up to 1 jump away), 'my_jump2_rating', 'my_jump3_rating', my_jump4_rating' # 'local_fleet_threats', 'regional_fleet_threats' <== these are only for mobile fleet threats self.systemStatus = {} self.needsEmergencyExploration = [] self.newlySplitFleets = {} self.militaryRating = 0 self.shipCount = 4 self.misc = {} self.qualifyingColonyBaseTargets = {} self.qualifyingOutpostBaseTargets = {} self.qualifyingTroopBaseTargets = {} self.__empire_standard_enemy = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) # TODO: track on a per-empire basis self.empire_standard_enemy_rating = 0 # TODO: track on a per-empire basis self.character = create_character(aggression, self.empireID)
def _calculate_threat(self): return ( self.threat_bias + + self.safety_factor * CombatRatingsAI.combine_ratings_list([ self._local_threat(), .75 * self._neighbor_threat(), .5 * self._jump2_threat()]) + self._potential_threat())
def _calculate_threat(self): systems_status = foAI.foAIstate.systemStatus.get(self.sys_id, {}) threat = self.safety_factor * CombatRatingsAI.combine_ratings(systems_status.get('fleetThreat', 0), systems_status.get('monsterThreat', 0) + + systems_status.get('planetThreat', 0)) return self.threat_bias + threat
def get_tot_mil_rating(): """ Give an assessment of total miltary rating considering all fleets as if distributed to separate systems. :return: a military rating value :rtype: float """ return sum(CombatRatingsAI.get_fleet_rating(fleet_id) for fleet_id in FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY))
def __str__(self): fleet = self.fleet.get_object() fleet_id = self.fleet.id return "%-25s [%-11s] ships: %2d; total rating: %4d; target: %s" % (fleet, "NONE" if self.type is None else self.type, (fleet and len(fleet.shipIDs)) or 0, CombatRatingsAI.get_fleet_rating(fleet_id), self.target or 'no target')
def merge_fleet_a_into_b(fleet_a_id, fleet_b_id, leave_rating=0, need_rating=0, context=""): universe = fo.getUniverse() fleet_a = universe.getFleet(fleet_a_id) fleet_b = universe.getFleet(fleet_b_id) if not fleet_a or not fleet_b: return 0 remaining_rating = CombatRatingsAI.get_fleet_rating(fleet_a_id) transferred_rating = 0 b_has_monster = False for ship_id in fleet_b.shipIDs: this_ship = universe.getShip(ship_id) if not this_ship: continue if this_ship.isMonster: b_has_monster = True break for ship_id in fleet_a.shipIDs: this_ship = universe.getShip(ship_id) if not this_ship or this_ship.isMonster != b_has_monster: # TODO Is there any reason for the monster check? continue this_rating = CombatRatingsAI.ShipCombatStats(ship_id).get_rating() remaining_rating = CombatRatingsAI.rating_needed(remaining_rating, this_rating) if remaining_rating < leave_rating: # merging this would leave old fleet under minimum rating, try other ships. continue transferred = fo.issueFleetTransferOrder(ship_id, fleet_b_id) if transferred: transferred_rating = CombatRatingsAI.combine_ratings(transferred_rating, this_rating) else: print " *** transfer of ship %4d, formerly of fleet %4d, into fleet %4d failed; %s" % ( ship_id, fleet_a_id, fleet_b_id, [" context is %s" % context, ""][context == ""]) if need_rating != 0 and need_rating <= transferred_rating: break fleet_a = universe.getFleet(fleet_a_id) if not fleet_a or fleet_a.empty or fleet_a_id in universe.destroyedObjectIDs(fo.empireID()): foAI.foAIstate.delete_fleet_info(fleet_a_id) foAI.foAIstate.update_fleet_rating(fleet_b_id)
def __init__(self, aggression): # Debug info # unique id for game self.uid = self.generate_uid(first=True) # unique ids for turns. {turn: uid} self.turn_uids = {} # 'global' (?) variables self.colonisablePlanetIDs = odict() self.colonisableOutpostIDs = odict() # self.__aiMissionsByFleetID = {} self.__shipRoleByDesignID = {} self.__fleetRoleByID = {} self.diplomatic_logs = {} self.__priorityByType = {} # initialize home system knowledge universe = fo.getUniverse() empire = fo.getEmpire() self.empireID = empire.empireID homeworld = universe.getPlanet(empire.capitalID) if homeworld: self.__origin_home_system_id = homeworld.systemID else: self.__origin_home_system_id = INVALID_ID self.visBorderSystemIDs = {self.__origin_home_system_id: 1} self.visInteriorSystemIDs = {} self.exploredSystemIDs = {} self.unexploredSystemIDs = {self.__origin_home_system_id: 1} self.fleetStatus = {} # keys: 'sysID', 'nships', 'rating' # systemStatus keys: 'name', 'neighbors' (sysIDs), '2jump_ring' (sysIDs), '3jump_ring', '4jump_ring' # 'fleetThreat', 'planetThreat', 'monsterThreat' (specifically, immobile nonplanet threat), 'totalThreat', 'localEnemyFleetIDs', # 'neighborThreat', 'max_neighbor_threat', 'jump2_threat' (up to 2 jumps away), 'jump3_threat', 'jump4_threat', 'regional_threat' # 'myDefenses' (planet rating), 'myfleets', 'myFleetsAccessible'(not just next desitination), 'myFleetRating' # 'my_neighbor_rating' (up to 1 jump away), 'my_jump2_rating', 'my_jump3_rating', my_jump4_rating' # 'local_fleet_threats', 'regional_fleet_threats' <== these are only for mobile fleet threats self.systemStatus = {} self.needsEmergencyExploration = [] self.newlySplitFleets = {} self.militaryRating = 0 self.shipCount = 4 self.misc = {} self.qualifyingColonyBaseTargets = {} self.qualifyingOutpostBaseTargets = {} self.qualifyingTroopBaseTargets = {} self.__empire_standard_enemy = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) # TODO: track on a per-empire basis self.empire_standard_enemy_rating = 0 # TODO: track on a per-empire basis self.character = create_character(aggression, self.empireID)
def _maximum_allocation(self, threat): """ Calculate the maximum allocation for the system. The default maximum allocation is the missing forces to obtain a rating given by the threat weighted with the subclass' *max_alloc_factor*. Existing military missions are considered. Subclasses may choose to override this method and implement a different logic. :param float threat: :rtype: float """ return CombatRatingsAI.rating_needed( self._max_alloc_factor * threat, self.assigned_rating)
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 __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 assess_protection_focus(pid, pinfo): """Return True if planet should use Protection Focus.""" this_planet = pinfo.planet sys_status = foAI.foAIstate.systemStatus.get(this_planet.systemID, {}) threat_from_supply = (0.25 * foAI.foAIstate.empire_standard_enemy_rating * min(2, len(sys_status.get('enemies_nearly_supplied', [])))) print "Planet %s has regional+supply threat of %.1f" % ('P_%d<%s>' % (pid, this_planet.name), threat_from_supply) regional_threat = sys_status.get('regional_threat', 0) + threat_from_supply if not regional_threat: # no need for protection if pinfo.current_focus == PRODUCTION: print "Advising dropping Protection Focus at %s due to no regional threat" % this_planet return False cur_prod_val = weighted_sum_output(pinfo.current_output) target_prod_val = max(map(weighted_sum_output, [pinfo.possible_output[INDUSTRY], pinfo.possible_output[RESEARCH]])) prot_prod_val = weighted_sum_output(pinfo.possible_output[PRODUCTION]) local_production_diff = 0.8 * cur_prod_val + 0.2 * target_prod_val - prot_prod_val fleet_threat = sys_status.get('fleetThreat', 0) # TODO: relax the below rejection once the overall determination of PFocus is better tuned if not fleet_threat and local_production_diff > 8: if pinfo.current_focus == PRODUCTION: print "Advising dropping Protection Focus at %s due to excessive productivity loss" % this_planet return False local_p_defenses = sys_status.get('mydefenses', {}).get('overall', 0) # TODO have adjusted_p_defenses take other in-system planets into account adjusted_p_defenses = local_p_defenses * (1.0 if pinfo.current_focus != PRODUCTION else 0.5) local_fleet_rating = sys_status.get('myFleetRating', 0) combined_local_defenses = sys_status.get('all_local_defenses', 0) my_neighbor_rating = sys_status.get('my_neighbor_rating', 0) neighbor_threat = sys_status.get('neighborThreat', 0) safety_factor = 1.2 if pinfo.current_focus == PRODUCTION else 0.5 cur_shield = this_planet.currentMeterValue(fo.meterType.shield) max_shield = this_planet.currentMeterValue(fo.meterType.maxShield) cur_troops = this_planet.currentMeterValue(fo.meterType.troops) max_troops = this_planet.currentMeterValue(fo.meterType.maxTroops) cur_defense = this_planet.currentMeterValue(fo.meterType.defense) max_defense = this_planet.currentMeterValue(fo.meterType.maxDefense) def_meter_pairs = [(cur_troops, max_troops), (cur_shield, max_shield), (cur_defense, max_defense)] use_protection = True reason = "" if (fleet_threat and # i.e., an enemy is sitting on us (pinfo.current_focus != PRODUCTION or # too late to start protection TODO: but maybe regen worth it # protection focus only useful here if it maintains an elevated level all([AIDependencies.PROT_FOCUS_MULTIPLIER * a <= b for a, b in def_meter_pairs]))): use_protection = False reason = "A" elif ((pinfo.current_focus != PRODUCTION and cur_shield < max_shield - 2 and not tech_is_complete(AIDependencies.PLANET_BARRIER_I_TECH)) and (cur_defense < max_defense - 2 and not tech_is_complete(AIDependencies.DEFENSE_REGEN_1_TECH)) and (cur_troops < max_troops - 2)): use_protection = False reason = "B1" elif ((pinfo.current_focus == PRODUCTION and cur_shield * AIDependencies.PROT_FOCUS_MULTIPLIER < max_shield - 2 and not tech_is_complete(AIDependencies.PLANET_BARRIER_I_TECH)) and (cur_defense * AIDependencies.PROT_FOCUS_MULTIPLIER < max_defense - 2 and not tech_is_complete(AIDependencies.DEFENSE_REGEN_1_TECH)) and (cur_troops * AIDependencies.PROT_FOCUS_MULTIPLIER < max_troops - 2)): use_protection = False reason = "B2" elif max(max_shield, max_troops, max_defense) < 3: # joke defenses, don't bother with protection focus use_protection = False reason = "C" elif regional_threat and local_production_diff <= 2.0: reason = "D" pass # i.e., use_protection = True elif safety_factor * regional_threat <= local_fleet_rating: use_protection = False reason = "E" elif (safety_factor * regional_threat <= combined_local_defenses and (pinfo.current_focus != PRODUCTION or (0.5 * safety_factor * regional_threat <= local_fleet_rating and fleet_threat == 0 and neighbor_threat < combined_local_defenses and local_production_diff > 5))): use_protection = False reason = "F" elif (regional_threat <= CombatRatingsAI.combine_ratings(local_fleet_rating, adjusted_p_defenses) and safety_factor * regional_threat <= CombatRatingsAI.combine_ratings_list([my_neighbor_rating, local_fleet_rating, adjusted_p_defenses]) and local_production_diff > 5): use_protection = False reason = "G" if use_protection or pinfo.current_focus == PRODUCTION: print ("Advising %sProtection Focus (reason %s) for planet %s, with local_prod_diff of %.1f, comb. local" " defenses %.1f, local fleet rating %.1f and regional threat %.1f, threat sources: %s") % ( ["dropping ", ""][use_protection], reason, this_planet, local_production_diff, combined_local_defenses, local_fleet_rating, regional_threat, sys_status['regional_fleet_threats']) return use_protection
def evaluate_invasion_planet(planet_id, secure_fleet_missions, verbose=True): """Return the invasion value (score, troops) of a planet.""" detail = [] building_values = { "BLD_IMPERIAL_PALACE": 1000, "BLD_CULTURE_ARCHIVES": 1000, "BLD_AUTO_HISTORY_ANALYSER": 100, "BLD_SHIPYARD_BASE": 100, "BLD_SHIPYARD_ORG_ORB_INC": 200, "BLD_SHIPYARD_ORG_XENO_FAC": 200, "BLD_SHIPYARD_ORG_CELL_GRO_CHAMB": 200, "BLD_SHIPYARD_CON_NANOROBO": 300, "BLD_SHIPYARD_CON_GEOINT": 400, "BLD_SHIPYARD_CON_ADV_ENGINE": 1000, "BLD_SHIPYARD_AST": 300, "BLD_SHIPYARD_AST_REF": 1000, "BLD_SHIPYARD_ENRG_SOLAR": 1500, "BLD_INDUSTRY_CENTER": 500, "BLD_GAS_GIANT_GEN": 200, "BLD_SOL_ORB_GEN": 800, "BLD_BLACK_HOLE_POW_GEN": 2000, "BLD_ENCLAVE_VOID": 500, "BLD_NEUTRONIUM_EXTRACTOR": 2000, "BLD_NEUTRONIUM_SYNTH": 2000, "BLD_NEUTRONIUM_FORGE": 1000, "BLD_CONC_CAMP": 100, "BLD_BIOTERROR_PROJECTOR": 1000, "BLD_SHIPYARD_ENRG_COMP": 3000, } # TODO: add more factors, as used for colonization universe = fo.getUniverse() empire_id = fo.empireID() max_jumps = 8 planet = universe.getPlanet(planet_id) if planet is None: # TODO: exclude planets with stealth higher than empireDetection print "invasion AI couldn't access any info for planet id %d" % planet_id return [0, 0] sys_partial_vis_turn = get_partial_visibility_turn(planet.systemID) planet_partial_vis_turn = get_partial_visibility_turn(planet_id) if planet_partial_vis_turn < sys_partial_vis_turn: print "invasion AI couldn't get current info on planet id %d (was stealthed at last sighting)" % planet_id # TODO: track detection strength, order new scouting when it goes up return [ 0, 0 ] # last time we had partial vis of the system, the planet was stealthed to us species_name = planet.speciesName species = fo.getSpecies(species_name) if not species or AIDependencies.TAG_DESTROYED_ON_CONQUEST in species.tags: # this call iterates over this Empire's available species with which it could colonize after an invasion planet_eval = ColonisationAI.assign_colonisation_values( [planet_id], MissionType.INVASION, None, detail) pop_val = max( 0.75 * planet_eval.get(planet_id, [0])[0], ColonisationAI.evaluate_planet(planet_id, MissionType.OUTPOST, None, detail)) else: pop_val = ColonisationAI.evaluate_planet(planet_id, MissionType.INVASION, species_name, detail) bld_tally = 0 for bldType in [ universe.getBuilding(bldg).buildingTypeName for bldg in planet.buildingIDs ]: bval = building_values.get(bldType, 50) bld_tally += bval detail.append("%s: %d" % (bldType, bval)) tech_tally = 0 for unlocked_tech in AIDependencies.SPECIES_TECH_UNLOCKS.get( species_name, []): if not tech_is_complete(unlocked_tech): rp_cost = fo.getTech(unlocked_tech).researchCost(empire_id) tech_tally += rp_cost * 4 detail.append("%s: %d" % (unlocked_tech, rp_cost * 4)) p_sys_id = planet.systemID capitol_id = PlanetUtilsAI.get_capital() least_jumps_path = [] clear_path = True if capitol_id: homeworld = universe.getPlanet(capitol_id) if homeworld: home_system_id = homeworld.systemID eval_system_id = planet.systemID if (home_system_id != INVALID_ID) and (eval_system_id != INVALID_ID): least_jumps_path = list( universe.leastJumpsPath(home_system_id, eval_system_id, empire_id)) max_jumps = len(least_jumps_path) system_status = foAI.foAIstate.systemStatus.get(p_sys_id, {}) system_fleet_treat = system_status.get('fleetThreat', 1000) system_monster_threat = system_status.get('monsterThreat', 0) sys_total_threat = system_fleet_treat + system_monster_threat + system_status.get( 'planetThreat', 0) max_path_threat = system_fleet_treat mil_ship_rating = MilitaryAI.cur_best_mil_ship_rating() for path_sys_id in least_jumps_path: path_leg_status = foAI.foAIstate.systemStatus.get(path_sys_id, {}) path_leg_threat = path_leg_status.get( 'fleetThreat', 1000) + path_leg_status.get('monsterThreat', 0) if path_leg_threat > 0.5 * mil_ship_rating: clear_path = False if path_leg_threat > max_path_threat: max_path_threat = path_leg_threat pop = planet.currentMeterValue(fo.meterType.population) target_pop = planet.currentMeterValue(fo.meterType.targetPopulation) troops = planet.currentMeterValue(fo.meterType.troops) max_troops = planet.currentMeterValue(fo.meterType.maxTroops) # TODO: refactor troop determination into function for use in mid-mission updates and also consider defender techs max_troops += AIDependencies.TROOPS_PER_POP * (target_pop - pop) this_system = universe.getSystem(p_sys_id) secure_targets = [p_sys_id] + list(this_system.planetIDs) system_secured = False for mission in secure_fleet_missions: if system_secured: break secure_fleet_id = mission.fleet.id s_fleet = universe.getFleet(secure_fleet_id) if not s_fleet or s_fleet.systemID != p_sys_id: continue if mission.type == MissionType.SECURE: target_obj = mission.target.get_object() if target_obj is not None and target_obj.id in secure_targets: system_secured = True break system_secured = system_secured and system_status.get('myFleetRating', 0) if verbose: print("Invasion eval of %s\n" " - maxShields: %.1f\n" " - sysFleetThreat: %.1f\n" " - sysMonsterThreat: %.1f") % ( planet, planet.currentMeterValue(fo.meterType.maxShield), system_fleet_treat, system_monster_threat) supply_val = 0 enemy_val = 0 if planet.owner != -1: # value in taking this away from an enemy enemy_val = 20 * ( planet.currentMeterValue(fo.meterType.targetIndustry) + 2 * planet.currentMeterValue(fo.meterType.targetResearch)) if p_sys_id in ColonisationAI.annexable_system_ids: # TODO: extend to rings supply_val = 100 elif p_sys_id in state.get_system_supply(-1): supply_val = 200 elif p_sys_id in state.get_systems_by_supply_tier(-2): supply_val = 300 elif p_sys_id in state.get_systems_by_supply_tier(-3): supply_val = 400 if max_path_threat > 0.5 * mil_ship_rating: if max_path_threat < 3 * mil_ship_rating: supply_val *= 0.5 else: supply_val *= 0.2 # devalue invasions that would require too much military force threat_factor = min( 1, 0.2 * MilitaryAI.get_tot_mil_rating() / (sys_total_threat + 0.001))**2 design_id, _, locs = ProductionAI.get_best_ship_info( PriorityType.PRODUCTION_INVASION) if not locs or not universe.getPlanet(locs[0]): # We are in trouble anyway, so just calculate whatever approximation... build_time = 4 planned_troops = troops if system_secured else min( troops + max_jumps + build_time, max_troops) planned_troops += .01 # we must attack with more troops than there are defenders troop_cost = math.ceil((planned_troops + _TROOPS_SAFETY_MARGIN) / 6.0) * 20 * FleetUtilsAI.get_fleet_upkeep() else: loc = locs[0] species_here = universe.getPlanet(loc).speciesName design = fo.getShipDesign(design_id) cost_per_ship = design.productionCost(empire_id, loc) build_time = design.productionTime(empire_id, loc) troops_per_ship = CombatRatingsAI.weight_attack_troops( design.troopCapacity, CombatRatingsAI.get_species_troops_grade(species_here)) planned_troops = troops if system_secured else min( troops + max_jumps + build_time, max_troops) planned_troops += .01 # we must attack with more troops than there are defenders ships_needed = math.ceil( (planned_troops + _TROOPS_SAFETY_MARGIN) / float(troops_per_ship)) troop_cost = ships_needed * cost_per_ship # fleet upkeep is already included in query from server # apply some bias to expensive operations normalized_cost = float(troop_cost) / max(fo.getEmpire().productionPoints, 1) normalized_cost = max(1, normalized_cost) cost_score = (normalized_cost**2 / 50.0) * troop_cost base_score = pop_val + supply_val + bld_tally + tech_tally + enemy_val - cost_score planet_score = retaliation_risk_factor(planet.owner) * threat_factor * max( 0, base_score) if clear_path: planet_score *= 1.5 if verbose: print(' - planet score: %.2f\n' ' - troop score: %.2f\n' ' - projected troop cost: %.1f\n' ' - threat factor: %s\n' ' - planet detail: %s\n' ' - popval: %.1f\n' ' - supplyval: %.1f\n' ' - bldval: %s\n' ' - enemyval: %s') % (planet_score, planned_troops, troop_cost, threat_factor, detail, pop_val, supply_val, bld_tally, enemy_val) return [planet_score, planned_troops]
def issue_fleet_orders(self): """issues AIFleetOrders which can be issued in system and moves to next one if is possible""" # TODO: priority order_completed = True debug("\nChecking orders for fleet %s (on turn %d), with mission type %s and target %s", self.fleet.get_object(), fo.currentTurn(), self.type or 'No mission', self.target or 'No Target') if MissionType.INVASION == self.type: self._check_retarget_invasion() just_issued_move_order = False last_move_target_id = INVALID_ID # Note: the following abort check somewhat assumes only one major mission type for fleet_order in self.orders: if (isinstance(fleet_order, (OrderColonize, OrderOutpost, OrderInvade)) and self._check_abort_mission(fleet_order)): return aistate = get_aistate() for fleet_order in self.orders: if just_issued_move_order and self.fleet.get_object().systemID != last_move_target_id: # having just issued a move order, we will normally stop issuing orders this turn, except that if there # are consecutive move orders we will consider moving through the first destination rather than stopping # Without the below noinspection directive, PyCharm is concerned about the 2nd part of the test # noinspection PyTypeChecker if (not isinstance(fleet_order, OrderMove) or self.need_to_pause_movement(last_move_target_id, fleet_order)): break debug("Checking order: %s" % fleet_order) self.check_mergers(context=str(fleet_order)) if fleet_order.can_issue_order(verbose=False): # only move if all other orders completed if isinstance(fleet_order, OrderMove) and order_completed: debug("Issuing fleet order %s" % fleet_order) fleet_order.issue_order() just_issued_move_order = True last_move_target_id = fleet_order.target.id elif not isinstance(fleet_order, OrderMove): debug("Issuing fleet order %s" % fleet_order) fleet_order.issue_order() else: debug("NOT issuing (even though can_issue) fleet order %s" % fleet_order) status_words = tuple(["not", ""][_s] for _s in [fleet_order.order_issued, fleet_order.executed]) debug("Order %s issued and %s fully executed." % status_words) if not fleet_order.executed: order_completed = False else: # check that we're not held up by a Big Monster if fleet_order.order_issued: # A previously issued order that wasn't instantly executed must have had cirumstances change so that # the order can't currently be reissued (or perhaps simply a savegame has been reloaded on the same # turn the order was issued). if not fleet_order.executed: order_completed = False # Go on to the next order. continue debug("CAN'T issue fleet order %s because:" % fleet_order) fleet_order.can_issue_order(verbose=True) if isinstance(fleet_order, OrderMove): this_system_id = fleet_order.target.id this_status = aistate.systemStatus.setdefault(this_system_id, {}) threat_threshold = fo.currentTurn() * MilitaryAI.cur_best_mil_ship_rating() / 4.0 if this_status.get('monsterThreat', 0) > threat_threshold: # if this move order is not this mil fleet's final destination, and blocked by Big Monster, # release and hope for more effective reassignment if (self.type not in (MissionType.MILITARY, MissionType.SECURE) or fleet_order != self.orders[-1]): debug("Aborting mission due to being blocked by Big Monster at system %d, threat %d" % ( this_system_id, aistate.systemStatus[this_system_id]['monsterThreat'])) debug("Full set of orders were:") for this_order in self.orders: debug(" - %s" % this_order) self.clear_fleet_orders() self.clear_target() return break # do not order the next order until this one is finished. else: # went through entire order list if order_completed: debug("Final order is completed") orders = self.orders last_order = orders[-1] if orders else None universe = fo.getUniverse() if last_order and isinstance(last_order, OrderColonize): planet = universe.getPlanet(last_order.target.id) sys_partial_vis_turn = get_partial_visibility_turn(planet.systemID) planet_partial_vis_turn = get_partial_visibility_turn(planet.id) if (planet_partial_vis_turn == sys_partial_vis_turn and not planet.initialMeterValue(fo.meterType.population)): warn("Fleet %d has tentatively completed its " "colonize mission but will wait to confirm population." % self.fleet.id) debug(" Order details are %s" % last_order) debug(" Order is valid: %s; issued: %s; executed: %s" % ( last_order.is_valid(), last_order.order_issued, last_order.executed)) if not last_order.is_valid(): source_target = last_order.fleet target_target = last_order.target debug(" source target validity: %s; target target validity: %s " % ( bool(source_target), bool(target_target))) return # colonize order must not have completed yet clear_all = True last_sys_target = INVALID_ID if last_order and isinstance(last_order, OrderMilitary): last_sys_target = last_order.target.id # not doing this until decide a way to release from a SECURE mission # if (MissionType.SECURE == self.type) or secure_targets = set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs) if last_sys_target in secure_targets: # consider a secure mission if last_sys_target in AIstate.colonyTargetedSystemIDs: secure_type = "Colony" elif last_sys_target in AIstate.outpostTargetedSystemIDs: secure_type = "Outpost" elif last_sys_target in AIstate.invasionTargetedSystemIDs: secure_type = "Invasion" else: secure_type = "Unidentified" debug("Fleet %d has completed initial stage of its mission " "to secure system %d (targeted for %s), " "may release a portion of ships" % (self.fleet.id, last_sys_target, secure_type)) clear_all = False # for PROTECT_REGION missions, only release fleet if no more threat if self.type == MissionType.PROTECT_REGION: # use military logic code below to determine if can release # any or even all of the ships. clear_all = False last_sys_target = self.target.id debug("Check if PROTECT_REGION mission with target %d is finished.", last_sys_target) fleet_id = self.fleet.id if clear_all: if orders: debug("Fleet %d has completed its mission; clearing all orders and targets." % self.fleet.id) debug("Full set of orders were:") for this_order in orders: debug("\t\t %s" % this_order) self.clear_fleet_orders() self.clear_target() if aistate.get_fleet_role(fleet_id) in (MissionType.MILITARY, MissionType.SECURE): allocations = MilitaryAI.get_military_fleets(mil_fleets_ids=[fleet_id], try_reset=False, thisround="Fleet %d Reassignment" % fleet_id) if allocations: MilitaryAI.assign_military_fleets_to_systems(use_fleet_id_list=[fleet_id], allocations=allocations) else: # no orders debug("No Current Orders") else: potential_threat = CombatRatingsAI.combine_ratings( MilitaryAI.get_system_local_threat(last_sys_target), MilitaryAI.get_system_neighbor_threat(last_sys_target) ) threat_present = potential_threat > 0 debug("Fleet threat present? %s", threat_present) target_system = universe.getSystem(last_sys_target) if not threat_present and target_system: for pid in target_system.planetIDs: planet = universe.getPlanet(pid) if (planet and planet.owner != fo.empireID() and planet.currentMeterValue(fo.meterType.maxDefense) > 0): debug("Found local planetary threat: %s", planet) threat_present = True break if not threat_present: debug("No current threat in target system; releasing a portion of ships.") # at least first stage of current task is done; # release extra ships for potential other deployments new_fleets = FleetUtilsAI.split_fleet(self.fleet.id) if self.type == MissionType.PROTECT_REGION: self.clear_fleet_orders() self.clear_target() new_fleets.append(self.fleet.id) else: debug("Threat remains in target system; Considering to release some ships.") new_fleets = [] fleet_portion_to_remain = self._portion_of_fleet_needed_here() if fleet_portion_to_remain > 1: debug("Can not release fleet yet due to large threat.") elif fleet_portion_to_remain > 0: debug("Not all ships are needed here - considering releasing a few") fleet_remaining_rating = CombatRatingsAI.get_fleet_rating(fleet_id) fleet_min_rating = fleet_portion_to_remain * fleet_remaining_rating debug("Starting rating: %.1f, Target rating: %.1f", fleet_remaining_rating, fleet_min_rating) allowance = CombatRatingsAI.rating_needed(fleet_remaining_rating, fleet_min_rating) debug("May release ships with total rating of %.1f", allowance) ship_ids = list(self.fleet.get_object().shipIDs) for ship_id in ship_ids: ship_rating = CombatRatingsAI.get_ship_rating(ship_id) debug("Considering to release ship %d with rating %.1f", ship_id, ship_rating) if ship_rating > allowance: debug("Remaining rating insufficient. Not released.") continue debug("Splitting from fleet.") new_fleet_id = FleetUtilsAI.split_ship_from_fleet(fleet_id, ship_id) if assertion_fails(new_fleet_id and new_fleet_id != INVALID_ID): break new_fleets.append(new_fleet_id) fleet_remaining_rating = CombatRatingsAI.rating_difference( fleet_remaining_rating, ship_rating) allowance = CombatRatingsAI.rating_difference( fleet_remaining_rating, fleet_min_rating) debug("Remaining fleet rating: %.1f - Allowance: %.1f", fleet_remaining_rating, allowance) if new_fleets: aistate.get_fleet_role(fleet_id, force_new=True) aistate.update_fleet_rating(fleet_id) aistate.ensure_have_fleet_missions(new_fleets) else: debug("Planetary defenses are deemed sufficient. Release fleet.") new_fleets = FleetUtilsAI.split_fleet(self.fleet.id) new_military_fleets = [] for fleet_id in new_fleets: if aistate.get_fleet_role(fleet_id) in COMBAT_MISSION_TYPES: new_military_fleets.append(fleet_id) allocations = [] if new_military_fleets: allocations = MilitaryAI.get_military_fleets( mil_fleets_ids=new_military_fleets, try_reset=False, thisround="Fleet Reassignment %s" % new_military_fleets ) if allocations: MilitaryAI.assign_military_fleets_to_systems(use_fleet_id_list=new_military_fleets, allocations=allocations)
def _maximum_allocation(self, threat): return max( CombatRatingsAI.rating_needed(1.5*self._regional_threat(), self.assigned_rating), CombatRatingsAI.rating_needed(2*threat, self.assigned_rating))
def update_system_status(self): print 10 * "=", "Updating System Threats", 10 * "=" universe = fo.getUniverse() empire = fo.getEmpire() empire_id = fo.empireID() destroyed_object_ids = universe.destroyedObjectIDs(empire_id) supply_unobstructed_systems = set(empire.supplyUnobstructedSystems) min_hidden_attack = 4 min_hidden_health = 8 system_id_list = universe.systemIDs # will normally look at this, the list of all known systems # for use in debugging verbose = False # assess enemy fleets that may have been momentarily visible cur_e_fighters = {CombatRatingsAI.default_ship_stats().get_stats(hashable=True): [0]} # start with a dummy entry old_e_fighters = {CombatRatingsAI.default_ship_stats().get_stats(hashable=True): [0]} # start with a dummy entry enemy_fleet_ids = [] enemies_by_system = {} my_fleets_by_system = {} fleet_spot_position = {} saw_enemies_at_system = {} my_milship_rating = MilitaryAI.cur_best_mil_ship_rating() current_turn = fo.currentTurn() for fleet_id in universe.fleetIDs: fleet = universe.getFleet(fleet_id) if fleet is None: continue if not fleet.empty: # TODO: check if currently in system and blockaded before accepting destination as location this_system_id = (fleet.nextSystemID != INVALID_ID and fleet.nextSystemID) or fleet.systemID if fleet.ownedBy(empire_id): if fleet_id not in destroyed_object_ids: my_fleets_by_system.setdefault(this_system_id, []).append(fleet_id) fleet_spot_position.setdefault(fleet.systemID, []).append(fleet_id) else: dead_fleet = fleet_id in destroyed_object_ids if not fleet.ownedBy(-1) and (fleet.hasArmedShips or fleet.hasFighterShips): ship_stats = CombatRatingsAI.FleetCombatStats(fleet_id).get_ship_stats(hashable=True) e_f_dict = [cur_e_fighters, old_e_fighters][dead_fleet] # track old/dead enemy fighters for rating assessments in case not enough current info for stats in ship_stats: attacks = stats[0] if attacks: e_f_dict.setdefault(stats, [0])[0] += 1 partial_vis_turn = universe.getVisibilityTurnsMap(fleet_id, empire_id).get(fo.visibility.partial, -9999) if not dead_fleet: # TODO: consider checking death of individual ships. If ships had been moved from this fleet # into another fleet, we might have witnessed their death in that other fleet but if this fleet # had not been seen since before that transfer then the ships might also still be listed here. sys_status = self.systemStatus.setdefault(this_system_id, {}) sys_status['enemy_ship_count'] = sys_status.get('enemy_ship_count', 0) + len(fleet.shipIDs) if partial_vis_turn >= current_turn - 1: # only interested in immediately recent data saw_enemies_at_system[fleet.systemID] = True enemy_fleet_ids.append(fleet_id) enemies_by_system.setdefault(this_system_id, []).append(fleet_id) if not fleet.ownedBy(-1): self.misc.setdefault('enemies_sighted', {}).setdefault(current_turn, []).append(fleet_id) rating = CombatRatingsAI.get_fleet_rating(fleet_id, enemy_stats=CombatRatingsAI.get_empire_standard_fighter()) if rating > 0.25 * my_milship_rating: self.misc.setdefault('dangerous_enemies_sighted', {}).setdefault(current_turn, []).append(fleet_id) e_f_dict = [cur_e_fighters, old_e_fighters][len(cur_e_fighters) == 1] std_fighter = sorted([(v, k) for k, v in e_f_dict.items()])[-1][1] self.__empire_standard_enemy = std_fighter self.empire_standard_enemy_rating = self.get_standard_enemy().get_rating() # TODO: If no current information available, rate against own fighters # assess fleet and planet threats & my local fleets for sys_id in system_id_list: sys_status = self.systemStatus.setdefault(sys_id, {}) system = universe.getSystem(sys_id) if verbose: print "AIState threat evaluation for %s" % system # update fleets sys_status['myfleets'] = my_fleets_by_system.get(sys_id, []) sys_status['myFleetsAccessible'] = fleet_spot_position.get(sys_id, []) local_enemy_fleet_ids = enemies_by_system.get(sys_id, []) sys_status['localEnemyFleetIDs'] = local_enemy_fleet_ids if system: sys_status['name'] = system.name for fid in system.fleetIDs: if fid in destroyed_object_ids: # TODO: double check are these checks/deletes necessary? self.delete_fleet_info(fid) # this is safe even if fleet wasn't mine continue fleet = universe.getFleet(fid) if not fleet or fleet.empty: self.delete_fleet_info(fid) # this is safe even if fleet wasn't mine continue # update threats sys_vis_dict = universe.getVisibilityTurnsMap(sys_id, fo.empireID()) partial_vis_turn = sys_vis_dict.get(fo.visibility.partial, -9999) mob_ratings = [] # for mobile unowned monster fleets lost_fleet_rating = 0 enemy_ratings = [] monster_ratings = [] mobile_fleets = [] for fid in local_enemy_fleet_ids: fleet = universe.getFleet(fid) if not fleet: continue fleet_rating = CombatRatingsAI.get_fleet_rating(fid, enemy_stats=CombatRatingsAI.get_empire_standard_fighter()) if fleet.speed == 0: monster_ratings.append(fleet_rating) if verbose: print "\t immobile enemy fleet %s has rating %.1f" % (fleet, fleet_rating) else: if verbose: print "\t mobile enemy fleet %s has rating %.1f" % (fleet, fleet_rating) mobile_fleets.append(fid) if fleet.unowned: mob_ratings.append(fleet_rating) else: enemy_ratings.append(fleet_rating) enemy_rating = CombatRatingsAI.combine_ratings_list(enemy_ratings) monster_rating = CombatRatingsAI.combine_ratings_list(monster_ratings) mob_rating = CombatRatingsAI.combine_ratings_list(mob_ratings) if fleetsLostBySystem.get(sys_id, []): lost_fleet_rating = CombatRatingsAI.combine_ratings_list(fleetsLostBySystem[sys_id]) if not system or partial_vis_turn == -9999: # under current visibility rules should not be possible to have any losses or other info here, but just in case... if verbose: print "Have never had partial vis for system %d ( %s ) -- basing threat assessment on old info and lost ships" % (sys_id, sys_status.get('name', "name unknown")) sys_status.setdefault('local_fleet_threats', set()) sys_status['planetThreat'] = 0 sys_status['fleetThreat'] = int(max(CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), 0.98 * sys_status.get('fleetThreat', 0), 1.1 * lost_fleet_rating - monster_rating)) sys_status['monsterThreat'] = int(max(monster_rating, 0.98 * sys_status.get('monsterThreat', 0), 1.1 * lost_fleet_rating - enemy_rating - mob_rating)) sys_status['enemy_threat'] = int(max(enemy_rating, 0.98 * sys_status.get('enemy_threat', 0), 1.1 * lost_fleet_rating - monster_rating - mob_rating)) sys_status['mydefenses'] = {'overall': 0, 'attack': 0, 'health': 0} sys_status['totalThreat'] = sys_status['fleetThreat'] sys_status['regional_fleet_threats'] = sys_status['local_fleet_threats'].copy() continue # have either stale or current info pattack = 0 phealth = 0 mypattack, myphealth = 0, 0 for pid in system.planetIDs: prating = self.assess_planet_threat(pid, sighting_age=current_turn - partial_vis_turn) planet = universe.getPlanet(pid) if not planet: continue if planet.owner == self.empireID: # TODO: check for diplomatic status mypattack += prating['attack'] myphealth += prating['health'] else: if [special for special in planet.specials if "_NEST_" in special]: sys_status['nest_threat'] = 100 pattack += prating['attack'] phealth += prating['health'] sys_status['planetThreat'] = pattack * phealth sys_status['mydefenses'] = {'overall': mypattack * myphealth, 'attack': mypattack, 'health': myphealth} if max(sys_status.get('totalThreat', 0), pattack * phealth) >= 0.6 * lost_fleet_rating: # previous threat assessment could account for losses, ignore the losses now lost_fleet_rating = 0 # TODO use sitrep combat info rather than estimating stealthed enemies by fleets lost to them # TODO also only consider past stealthed fleet threat to still be present if the system is still obstructed # TODO: track visibility across turns in order to distinguish the blip of visibility in (losing) combat, # which FO currently treats as being for the previous turn, partially superseding the previous visibility for that turn if not partial_vis_turn == current_turn: # (universe.getVisibility(sys_id, self.empire_id) >= fo.visibility.partial): sys_status.setdefault('local_fleet_threats', set()) sys_status['currently_visible'] = False # print "Stale visibility for system %d ( %s ) -- last seen %d, current Turn %d -- basing threat assessment on old info and lost ships"%(sys_id, sys_status.get('name', "name unknown"), partial_vis_turn, currentTurn) sys_status['fleetThreat'] = int(max(CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), 0.98 * sys_status.get('fleetThreat', 0), 2.0 * lost_fleet_rating - max(sys_status.get('monsterThreat', 0), monster_rating))) sys_status['enemy_threat'] = int(max(enemy_rating, 0.98 * sys_status.get('enemy_threat', 0), 1.1 * lost_fleet_rating - max(sys_status.get('monsterThreat', 0), monster_rating))) sys_status['monsterThreat'] = int(max(monster_rating, 0.98 * sys_status.get('monsterThreat', 0))) # sys_status['totalThreat'] = ((pattack + enemy_attack + monster_attack) ** 0.8) * ((phealth + enemy_health + monster_health)** 0.6) # reevaluate this sys_status['totalThreat'] = max(CombatRatingsAI.combine_ratings_list([enemy_rating, mob_rating, monster_rating, pattack * phealth]), 2 * lost_fleet_rating, 0.98 * sys_status.get('totalThreat', 0)) else: # system considered visible #TODO: reevaluate as visibility rules change sys_status['currently_visible'] = True sys_status['local_fleet_threats'] = set(mobile_fleets) sys_status['fleetThreat'] = int(max(CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), 2 * lost_fleet_rating - monster_rating)) # includes mobile monsters if verbose: print "enemy threat calc parts: enemy rating %.1f, lost fleet rating %.1f, monster_rating %.1f" % (enemy_rating, lost_fleet_rating, monster_rating) sys_status['enemy_threat'] = int(max(enemy_rating, 2 * lost_fleet_rating - monster_rating)) # does NOT include mobile monsters sys_status['monsterThreat'] = monster_rating sys_status['totalThreat'] = CombatRatingsAI.combine_ratings_list([enemy_rating, mob_rating, monster_rating, pattack * phealth]) sys_status['regional_fleet_threats'] = sys_status['local_fleet_threats'].copy() sys_status['fleetThreat'] = max(sys_status['fleetThreat'], sys_status.get('nest_threat', 0)) sys_status['totalThreat'] = max(sys_status['totalThreat'], sys_status.get('nest_threat', 0)) if partial_vis_turn > 0 and sys_id not in supply_unobstructed_systems: # has been seen with Partial Vis, but is currently supply-blocked sys_status['fleetThreat'] = max(sys_status['fleetThreat'], min_hidden_attack * min_hidden_health) sys_status['totalThreat'] = max(sys_status['totalThreat'], ((pattack + min_hidden_attack) ** 0.8) * ((phealth + min_hidden_health) ** 0.6)) if verbose and sys_status['fleetThreat'] > 0: print "%s intermediate status: %s" % (system, sys_status) enemy_supply, enemy_near_supply = self.assess_enemy_supply() # TODO: assess change in enemy supply over time # assess secondary threats (threats of surrounding systems) and update my fleet rating for sys_id in system_id_list: sys_status = self.systemStatus[sys_id] sys_status['enemies_supplied'] = enemy_supply.get(sys_id, []) sys_status['enemies_nearly_supplied'] = enemy_near_supply.get(sys_id, []) my_ratings_list = [] my_ratings_against_planets_list = [] for fid in sys_status['myfleets']: this_rating = self.get_rating(fid, True, self.get_standard_enemy()) my_ratings_list.append(this_rating) my_ratings_against_planets_list.append(self.get_rating(fid, against_planets=True)) if sys_id != INVALID_ID: sys_status['myFleetRating'] = CombatRatingsAI.combine_ratings_list(my_ratings_list) sys_status['myFleetRatingVsPlanets'] = CombatRatingsAI.combine_ratings_list(my_ratings_against_planets_list) sys_status['all_local_defenses'] = CombatRatingsAI.combine_ratings(sys_status['myFleetRating'], sys_status['mydefenses']['overall']) sys_status['neighbors'] = set(dict_from_map(universe.getSystemNeighborsMap(sys_id, self.empireID))) for sys_id in system_id_list: sys_status = self.systemStatus[sys_id] neighbors = sys_status.get('neighbors', set()) this_system = fo.getUniverse().getSystem(sys_id) if verbose: print "Regional Assessment for %s with local fleet threat %.1f" % (this_system, sys_status.get('fleetThreat', 0)) jumps2 = set() jumps3 = set() jumps4 = set() for seta, setb in [(neighbors, jumps2), (jumps2, jumps3), (jumps3, jumps4)]: for sys2id in seta: setb.update(self.systemStatus.get(sys2id, {}).get('neighbors', set())) jump2ring = jumps2 - neighbors - {sys_id} jump3ring = jumps3 - jumps2 - neighbors - {sys_id} jump4ring = jumps4 - jumps3 - jumps2 - neighbors - {sys_id} sys_status['2jump_ring'] = jump2ring sys_status['3jump_ring'] = jump3ring sys_status['4jump_ring'] = jump4ring threat, max_threat, myrating, j1_threats = self.area_ratings(neighbors, ref_sys_name="neighbors %s" % this_system) if verbose else self.area_ratings(neighbors) sys_status['neighborThreat'] = threat sys_status['max_neighbor_threat'] = max_threat sys_status['my_neighbor_rating'] = myrating threat, max_threat, myrating, j2_threats = self.area_ratings(jump2ring, ref_sys_name="jump2 %s" % this_system) if verbose else self.area_ratings(jump2ring) sys_status['jump2_threat'] = threat sys_status['my_jump2_rating'] = myrating threat, max_threat, myrating, j3_threats = self.area_ratings(jump3ring) sys_status['jump3_threat'] = threat sys_status['my_jump3_rating'] = myrating threat_keys = ['fleetThreat', 'neighborThreat', 'jump2_threat'] # for local system includes both enemies and mobs sys_status['regional_threat'] = CombatRatingsAI.combine_ratings_list(map(lambda x: sys_status.get(x, 0), threat_keys)) # TODO: investigate cases where regional_threat has been nonzero but no regional_threat_fleets # (probably due to attenuating history of past threats) sys_status.setdefault('regional_fleet_threats', set()).update(j1_threats, j2_threats)
def get_tot_mil_rating(): return sum(CombatRatingsAI.get_fleet_rating(fleet_id) for fleet_id in FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY))
def can_issue_order(self, verbose=False): if not super(OrderMove, self).can_issue_order(verbose=verbose): return False # TODO: figure out better way to have invasions (& possibly colonizations) # require visibility on target without needing visibility of all intermediate systems # if False and main_mission_type not in [MissionType.ATTACK, # TODO: consider this later # MissionType.MILITARY, # MissionType.SECURE, # MissionType.HIT_AND_RUN, # MissionType.EXPLORATION]: # if not universe.getVisibility(target_id, get_aistate().empireID) >= fo.visibility.partial: # #if not target_id in interior systems # get_aistate().needsEmergencyExploration.append(fleet.systemID) # return False system_id = self.fleet.get_system().id if system_id == self.target.get_system().id: return True # TODO: already there, but could consider retreating aistate = get_aistate() main_fleet_mission = aistate.get_fleet_mission(self.fleet.id) fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id) fleet_rating_vs_planets = CombatRatingsAI.get_fleet_rating_against_planets( self.fleet.id) target_sys_status = aistate.systemStatus.get(self.target.id, {}) f_threat = target_sys_status.get('fleetThreat', 0) m_threat = target_sys_status.get('monsterThreat', 0) p_threat = target_sys_status.get('planetThreat', 0) threat = f_threat + m_threat + p_threat safety_factor = aistate.character.military_safety_factor() universe = fo.getUniverse() if main_fleet_mission.type == MissionType.INVASION and not trooper_move_reqs_met( main_fleet_mission, self, verbose): return False if fleet_rating >= safety_factor * threat and fleet_rating_vs_planets >= p_threat: return True elif not p_threat and self.target.id in fo.getEmpire( ).supplyUnobstructedSystems: return True else: sys1 = universe.getSystem(system_id) sys1_name = sys1 and sys1.name or "unknown" target_system = self.target.get_system() target_system_name = (target_system and target_system.get_object().name) or "unknown" # TODO: adjust calc for any departing fleets my_other_fleet_rating = aistate.systemStatus.get( self.target.id, {}).get('myFleetRating', 0) my_other_fleet_rating_vs_planets = aistate.systemStatus.get( self.target.id, {}).get('myFleetRatingVsPlanets', 0) is_military = aistate.get_fleet_role( self.fleet.id) == MissionType.MILITARY total_rating = CombatRatingsAI.combine_ratings( my_other_fleet_rating, fleet_rating) total_rating_vs_planets = CombatRatingsAI.combine_ratings( my_other_fleet_rating_vs_planets, fleet_rating_vs_planets) if (my_other_fleet_rating > 3 * safety_factor * threat or (is_military and total_rating_vs_planets > 2.5 * p_threat and total_rating > safety_factor * threat)): debug(( "\tAdvancing fleet %d (rating %d) at system %d (%s) into system %d (%s) with threat %d" " because of sufficient empire fleet strength already at destination" % (self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, threat))) return True elif (threat == p_threat and not self.fleet.get_object().aggressive and not my_other_fleet_rating and not target_sys_status.get('localEnemyFleetIDs', [-1])): if verbose: debug( "\tAdvancing fleet %d (rating %d) at system %d (%s) " "into system %d (%s) with planet threat %d because non aggressive" " and no other fleets present to trigger combat" % (self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, threat)) return True else: if verbose: _info = (self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, threat) debug( "\tHolding fleet %d (rating %d) at system %d (%s) " "before travelling to system %d (%s) with threat %d" % _info) needs_vis = aistate.misc.setdefault('needs_vis', []) if self.target.id not in needs_vis: needs_vis.append(self.target.id) return False
def get_fleets_for_mission(target_stats, min_stats, cur_stats, starting_system, fleet_pool_set, fleet_list, species=""): """Get fleets for a mission. Implements breadth-first search through systems starting at the **starting_sytem**. In each system, local fleets are checked if they are in the allowed **fleet_pool_set** and suitable for the mission. If so, they are added to the **fleet_list** and **cur_stats** is updated with the currently selected fleet summary. The search continues until the requirements defined in **target_stats** are met or there are no more systems/fleets. In that case, if the **min_stats** are covered, the **fleet_list** is returned anyway. Otherwise, an empty list is returned by the function, in which case the caller can make an evaluation of an emergency use of the found fleets in fleet_list; if not to be used they should be added back to the main pool. :param target_stats: stats the fleet should ideally meet :type target_stats: dict :param min_stats: minimum stats the final fleet must meet to be accepted :type min_stats: dict :param cur_stats: (**mutated**) stat summary of selected fleets :type cur_stats: dict :param starting_system: system_id where breadth-first-search is centered :type starting_system: int :param fleet_pool_set: (**mutated**) fleets allowed to be selected. Split fleed_ids are added, used ones removed. :type: fleet_pool_set: set[int] :param fleet_list: (**mutated**) fleets that are selected for the mission. Gets filled during the call. :type fleet_list: list[int] :param species: species for colonization mission :type species: str :return: List of selected fleet_ids or empty list if couldn't meet minimum requirements. :rtype: list[int] """ universe = fo.getUniverse() colonization_roles = (ShipRoleType.CIVILIAN_COLONISATION, ShipRoleType.BASE_COLONISATION) systems_enqueued = [starting_system] systems_visited = [] # loop over systems in a breadth-first-search trying to find nearby suitable ships in fleet_pool_set while systems_enqueued and fleet_pool_set: this_system_id = systems_enqueued.pop(0) systems_visited.append(this_system_id) accessible_fleets = foAI.foAIstate.systemStatus.get(this_system_id, {}).get('myFleetsAccessible', []) fleets_here = [fid for fid in accessible_fleets if fid in fleet_pool_set] # loop over all fleets in the system, split them if possible and select suitable ships while fleets_here: fleet_id = fleets_here.pop(0) fleet = universe.getFleet(fleet_id) if not fleet: # TODO should be checked before passed to the function fleet_pool_set.remove(fleet_id) continue # try splitting fleet if len(list(fleet.shipIDs)) > 1: new_fleets = split_fleet(fleet_id) fleet_pool_set.update(new_fleets) fleets_here.extend(new_fleets) # check species for colonization missions if species: for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) if (ship and foAI.foAIstate.get_ship_role(ship.design.id) in colonization_roles and species == ship.speciesName): break else: # no suitable species found continue # check troop capacity for invasion missions troop_capacity = 0 if 'troopCapacity' in target_stats: troop_capacity = count_troops_in_fleet(fleet_id) if troop_capacity <= 0: continue # check if we need additional rating vs planets this_rating_vs_planets = 0 if 'ratingVsPlanets' in target_stats: this_rating_vs_planets = foAI.foAIstate.get_rating(fleet_id, against_planets=True) if this_rating_vs_planets <= 0 and cur_stats.get('rating', 0) >= target_stats.get('rating', 0): # we already have enough general rating, so do not add any more warships useless against planets continue # all checks passed, add ship to selected fleets and update the stats try: fleet_pool_set.remove(fleet_id) except KeyError: error("After having split a fleet, the original fleet apparently no longer exists.", exc_info=True) continue fleet_list.append(fleet_id) this_rating = foAI.foAIstate.get_rating(fleet_id) cur_stats['rating'] = CombatRatingsAI.combine_ratings(cur_stats.get('rating', 0), this_rating) if 'ratingVsPlanets' in target_stats: cur_stats['ratingVsPlanets'] = CombatRatingsAI.combine_ratings(cur_stats.get('ratingVsPlanets', 0), this_rating_vs_planets) if 'troopCapacity' in target_stats: cur_stats['troopCapacity'] = cur_stats.get('troopCapacity', 0) + troop_capacity # if we already meet the requirements, we can stop looking for more ships if (sum(len(universe.getFleet(fid).shipIDs) for fid in fleet_list) >= 1) \ and stats_meet_reqs(cur_stats, target_stats): return fleet_list # finished system without meeting requirements. Add neighboring systems to search queue. for neighbor_id in universe.getImmediateNeighbors(this_system_id, fo.empireID()): if all(( neighbor_id not in systems_visited, neighbor_id not in systems_enqueued, neighbor_id in foAI.foAIstate.exploredSystemIDs )): systems_enqueued.append(neighbor_id) # we ran out of systems or fleets to check but did not meet requirements yet. if stats_meet_reqs(cur_stats, min_stats) and any(universe.getFleet(fid).shipIDs for fid in fleet_list): return fleet_list else: return []
def __clean_fleet_roles(self, just_resumed=False): """Removes fleetRoles if a fleet has been lost, and update fleet Ratings.""" for sys_id in self.systemStatus: self.systemStatus[sys_id]['myFleetRating'] = 0 self.systemStatus[sys_id]['myFleetRatingVsPlanets'] = 0 universe = fo.getUniverse() ok_fleets = FleetUtilsAI.get_empire_fleet_ids() fleet_list = sorted(list(self.__fleetRoleByID)) ship_count = 0 destroyed_object_ids = universe.destroyedObjectIDs(fo.empireID()) fleet_table = Table([ Text('Fleet'), Float('Rating'), Float('Troops'), Text('Location'), Text('Destination') ], table_name="Fleet Summary Turn %d" % fo.currentTurn()) for fleet_id in fleet_list: status = self.fleetStatus.setdefault(fleet_id, {}) rating = CombatRatingsAI.get_fleet_rating( fleet_id, self.get_standard_enemy()) troops = FleetUtilsAI.count_troops_in_fleet(fleet_id) old_sys_id = status.get('sysID', -2) fleet = universe.getFleet(fleet_id) if fleet: sys_id = fleet.systemID if old_sys_id in [-2, -1]: old_sys_id = sys_id status['nships'] = len(fleet.shipIDs) ship_count += status['nships'] else: sys_id = old_sys_id # can still retrieve a fleet object even if fleet was just destroyed, so shouldn't get here # however,this has been observed happening, and is the reason a fleet check was added a few lines below. # Not at all sure how this came about, but was throwing off threat assessments if fleet_id not in ok_fleets: # or fleet.empty: if (fleet and self.__fleetRoleByID.get(fleet_id, -1) != -1 and fleet_id not in destroyed_object_ids and [ ship_id for ship_id in fleet.shipIDs if ship_id not in destroyed_object_ids ]): if not just_resumed: fleetsLostBySystem.setdefault(old_sys_id, []).append( max(rating, MilitaryAI.MinThreat)) if fleet_id in self.__fleetRoleByID: del self.__fleetRoleByID[fleet_id] if fleet_id in self.__aiMissionsByFleetID: del self.__aiMissionsByFleetID[fleet_id] if fleet_id in self.fleetStatus: del self.fleetStatus[fleet_id] continue else: # fleet in ok fleets this_sys = universe.getSystem(sys_id) next_sys = universe.getSystem(fleet.nextSystemID) fleet_table.add_row([ fleet, rating, troops, this_sys or 'starlane', next_sys or '-', ]) status['rating'] = rating if next_sys: status['sysID'] = next_sys.id elif this_sys: status['sysID'] = this_sys.id else: main_mission = self.get_fleet_mission(fleet_id) main_mission_type = (main_mission.getAIMissionTypes() + [-1])[0] if main_mission_type != -1: targets = main_mission.getAITargets(main_mission_type) if targets: m_mt0 = targets[0] if isinstance(m_mt0.target_type, System): status[ 'sysID'] = m_mt0.target.id # hmm, but might still be a fair ways from here fleet_table.print_table() self.shipCount = ship_count # Next string used in charts. Don't modify it! print "Empire Ship Count: ", ship_count print "Empire standard fighter summary: ", CombatRatingsAI.get_empire_standard_fighter( ).get_stats() print "------------------------"
def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): """Get armed military fleets.""" global _military_allocations universe = fo.getUniverse() empire_id = fo.empireID() home_system_id = PlanetUtilsAI.get_capital_sys_id() all_military_fleet_ids = (mil_fleets_ids if mil_fleets_ids is not None else FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)) if try_reset and (fo.currentTurn() + empire_id) % 30 == 0 and thisround == "Main": try_again(all_military_fleet_ids, try_reset=False, thisround=thisround + " Reset") return mil_fleets_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) mil_needing_repair_ids, mil_fleets_ids = avail_mil_needing_repair(mil_fleets_ids, split_ships=True) avail_mil_rating = sum(map(CombatRatingsAI.get_fleet_rating, mil_fleets_ids)) if not mil_fleets_ids: if "Main" in thisround: _military_allocations = [] return [] # for each system, get total rating of fleets assigned to it already_assigned_rating = {} already_assigned_rating_vs_planets = {} systems_status = foAI.foAIstate.systemStatus enemy_sup_factor = {} # enemy supply for sys_id in universe.systemIDs: already_assigned_rating[sys_id] = 0 already_assigned_rating_vs_planets[sys_id] = 0 enemy_sup_factor[sys_id] = min(2, len(systems_status.get(sys_id, {}).get('enemies_nearly_supplied', []))) for fleet_id in [fid for fid in all_military_fleet_ids if fid not in mil_fleets_ids]: ai_fleet_mission = foAI.foAIstate.get_fleet_mission(fleet_id) if not ai_fleet_mission.target: # shouldn't really be possible continue last_sys = ai_fleet_mission.target.get_system().id # will count this fleet as assigned to last system in target list # TODO last_sys or target sys? this_rating = CombatRatingsAI.get_fleet_rating(fleet_id) this_rating_vs_planets = CombatRatingsAI.get_fleet_rating_against_planets(fleet_id) already_assigned_rating[last_sys] = CombatRatingsAI.combine_ratings( already_assigned_rating.get(last_sys, 0), this_rating) already_assigned_rating_vs_planets[last_sys] = CombatRatingsAI.combine_ratings( already_assigned_rating_vs_planets.get(last_sys, 0), this_rating_vs_planets) for sys_id in universe.systemIDs: my_defense_rating = systems_status.get(sys_id, {}).get('mydefenses', {}).get('overall', 0) already_assigned_rating[sys_id] = CombatRatingsAI.combine_ratings(my_defense_rating, already_assigned_rating[sys_id]) if _verbose_mil_reporting and already_assigned_rating[sys_id]: print "\t System %s already assigned rating %.1f" % ( universe.getSystem(sys_id), already_assigned_rating[sys_id]) # get systems to defend capital_id = PlanetUtilsAI.get_capital() if capital_id is not None: capital_planet = universe.getPlanet(capital_id) else: capital_planet = None # TODO: if no owned planets try to capture one! if capital_planet: capital_sys_id = capital_planet.systemID else: # should be rare, but so as to not break code below, pick a randomish mil-centroid system capital_sys_id = None # unless we can find one to use system_dict = {} for fleet_id in all_military_fleet_ids: status = foAI.foAIstate.fleetStatus.get(fleet_id, None) if status is not None: sys_id = status['sysID'] if not list(universe.getSystem(sys_id).planetIDs): continue system_dict[sys_id] = system_dict.get(sys_id, 0) + status.get('rating', 0) ranked_systems = sorted([(val, sys_id) for sys_id, val in system_dict.items()]) if ranked_systems: capital_sys_id = ranked_systems[-1][-1] else: try: capital_sys_id = foAI.foAIstate.fleetStatus.items()[0][1]['sysID'] except: pass num_targets = max(10, PriorityAI.allotted_outpost_targets) top_target_planets = ([pid for pid, pscore, trp in AIstate.invasionTargets[:PriorityAI.allottedInvasionTargets] if pscore > 20] + [pid for pid, (pscore, spec) in foAI.foAIstate.colonisableOutpostIDs.items()[:num_targets] if pscore > 20] + [pid for pid, (pscore, spec) in foAI.foAIstate.colonisablePlanetIDs.items()[:num_targets] if pscore > 20]) top_target_planets.extend(foAI.foAIstate.qualifyingTroopBaseTargets.keys()) base_col_target_systems = PlanetUtilsAI.get_systems(top_target_planets) top_target_systems = [] for sys_id in AIstate.invasionTargetedSystemIDs + base_col_target_systems: if sys_id not in top_target_systems: if foAI.foAIstate.systemStatus[sys_id]['totalThreat'] > get_tot_mil_rating(): continue top_target_systems.append(sys_id) # doing this rather than set, to preserve order try: # capital defense allocation_helper = AllocationHelper(already_assigned_rating, already_assigned_rating_vs_planets, avail_mil_rating, try_reset) if capital_sys_id is not None: CapitalDefenseAllocator(capital_sys_id, allocation_helper).allocate() # defend other planets empire_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire(universe.planetIDs) empire_occupied_system_ids = list(set(PlanetUtilsAI.get_systems(empire_planet_ids)) - {capital_sys_id}) for sys_id in empire_occupied_system_ids: PlanetDefenseAllocator(sys_id, allocation_helper).allocate() # attack / protect high priority targets for sys_id in top_target_systems: TopTargetAllocator(sys_id, allocation_helper).allocate() # enemy planets other_targeted_system_ids = [sys_id for sys_id in set(PlanetUtilsAI.get_systems(AIstate.opponentPlanetIDs)) if sys_id not in top_target_systems] for sys_id in other_targeted_system_ids: TargetAllocator(sys_id, allocation_helper).allocate() # colony / outpost targets other_targeted_system_ids = [sys_id for sys_id in list(set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs)) if sys_id not in top_target_systems] for sys_id in other_targeted_system_ids: OutpostTargetAllocator(sys_id, allocation_helper).allocate() # TODO blockade enemy systems # interior systems targetable_ids = set(ColonisationAI.systems_by_supply_tier.get(0, []) + ColonisationAI.systems_by_supply_tier.get(1, [])) current_mil_systems = [sid for sid, _, _, _, _ in allocation_helper.allocations] interior_targets1 = targetable_ids.difference(current_mil_systems) interior_targets = [sid for sid in interior_targets1 if ( allocation_helper.threat_bias + systems_status.get(sid, {}).get('totalThreat', 0) > 0.8 * allocation_helper.already_assigned_rating[sid])] for sys_id in interior_targets: InteriorTargetsAllocator(sys_id, allocation_helper).allocate() # TODO Exploration targets # border protections visible_system_ids = foAI.foAIstate.visInteriorSystemIDs.keys() + foAI.foAIstate.visBorderSystemIDs.keys() accessible_system_ids = [sys_id for sys_id in visible_system_ids if universe.systemsConnected(sys_id, home_system_id, empire_id)] current_mil_systems = [sid for sid, alloc, rvp, take_any, _ in allocation_helper.allocations if alloc > 0] border_targets1 = [sid for sid in accessible_system_ids if sid not in current_mil_systems] border_targets = [sid for sid in border_targets1 if ( allocation_helper.threat_bias + systems_status.get(sid, {}).get('fleetThreat', 0) + systems_status.get(sid, {}).get( 'planetThreat', 0) > 0.8 * allocation_helper.already_assigned_rating[sid])] for sys_id in border_targets: BorderSecurityAllocator(sys_id, allocation_helper).allocate() except ReleaseMilitaryException: try_again(all_military_fleet_ids) return new_allocations = [] remaining_mil_rating = avail_mil_rating # for top categories assign max_alloc right away as available for cat in ['capitol', 'occupied', 'topTargets']: for sid, alloc, rvp, take_any, max_alloc in allocation_helper.allocation_by_groups.get(cat, []): if remaining_mil_rating <= 0: break this_alloc = min(remaining_mil_rating, max_alloc) new_allocations.append((sid, this_alloc, alloc, rvp, take_any)) remaining_mil_rating -= this_alloc base_allocs = set() # for lower priority categories, first assign base_alloc around to all, then top up as available for cat in ['otherTargets', 'accessibleTargets', 'exploreTargets']: for sid, alloc, rvp, take_any, max_alloc in allocation_helper.allocation_by_groups.get(cat, []): if remaining_mil_rating <= 0: break base_allocs.add(sid) remaining_mil_rating -= alloc for cat in ['otherTargets', 'accessibleTargets', 'exploreTargets']: for sid, alloc, rvp, take_any, max_alloc in allocation_helper.allocation_by_groups.get(cat, []): if sid not in base_allocs: break if remaining_mil_rating <= 0: new_allocations.append((sid, alloc, alloc, rvp, take_any)) else: new_rating = min(remaining_mil_rating + alloc, max_alloc) new_allocations.append((sid, new_rating, alloc, rvp, take_any)) remaining_mil_rating -= (new_rating - alloc) if "Main" in thisround: _military_allocations = new_allocations if _verbose_mil_reporting or "Main" in thisround: print "------------------------------\nFinal %s Round Military Allocations: %s \n-----------------------" % (thisround, dict([(sid, alloc) for sid, alloc, _, _, _ in new_allocations])) print "(Apparently) remaining military rating: %.1f" % remaining_mil_rating # export military systems for other AI modules if "Main" in thisround: AIstate.militarySystemIDs = list(set([sid for sid, _, _, _, _ in new_allocations]).union( [sid for sid in allocation_helper.already_assigned_rating if allocation_helper.already_assigned_rating[sid] > 0])) else: AIstate.militarySystemIDs = list(set([sid for sid, _, _, _, _ in new_allocations]).union(AIstate.militarySystemIDs)) return new_allocations
def _allocation_vs_planets(self): return CombatRatingsAI.rating_needed( self.safety_factor*self._planet_threat_multiplier()*self._planet_threat(), self.assigned_rating_vs_planets)
def _calculate_threat(self): potential_threat = max(self._potential_threat() - self._potential_support(), 0) actual_threat = self.safety_factor * ( 2*self.threat_bias + + CombatRatingsAI.combine_ratings(self._local_threat(), self._neighbor_threat())) return potential_threat + actual_threat
def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): """Get armed military fleets.""" global _military_allocations universe = fo.getUniverse() empire_id = fo.empireID() home_system_id = PlanetUtilsAI.get_capital_sys_id() all_military_fleet_ids = (mil_fleets_ids if mil_fleets_ids is not None else FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)) if try_reset and (fo.currentTurn() + empire_id) % 30 == 0 and thisround == "Main": try_again(all_military_fleet_ids, try_reset=False, thisround=thisround + " Reset") return mil_fleets_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) mil_needing_repair_ids, mil_fleets_ids = avail_mil_needing_repair(mil_fleets_ids, split_ships=True) avail_mil_rating = sum(map(CombatRatingsAI.get_fleet_rating, mil_fleets_ids)) if not mil_fleets_ids: if "Main" in thisround: _military_allocations = [] return [] # for each system, get total rating of fleets assigned to it already_assigned_rating = {} systems_status = foAI.foAIstate.systemStatus enemy_sup_factor = {} # enemy supply for sys_id in universe.systemIDs: already_assigned_rating[sys_id] = 0 enemy_sup_factor[sys_id] = min(2, len(systems_status.get(sys_id, {}).get('enemies_nearly_supplied', []))) for fleet_id in [fid for fid in all_military_fleet_ids if fid not in mil_fleets_ids]: ai_fleet_mission = foAI.foAIstate.get_fleet_mission(fleet_id) if not ai_fleet_mission.target: # shouldn't really be possible continue last_sys = ai_fleet_mission.target.get_system().id # will count this fleet as assigned to last system in target list # TODO last_sys or target sys? this_rating = CombatRatingsAI.get_fleet_rating(fleet_id) already_assigned_rating[last_sys] = CombatRatingsAI.combine_ratings(already_assigned_rating.get(last_sys, 0), this_rating) for sys_id in universe.systemIDs: my_defense_rating = systems_status.get(sys_id, {}).get('mydefenses', {}).get('overall', 0) already_assigned_rating[sys_id] = CombatRatingsAI.combine_ratings(my_defense_rating, already_assigned_rating[sys_id]) if _verbose_mil_reporting and already_assigned_rating[sys_id]: print "\t System %s already assigned rating %.1f" % ( universe.getSystem(sys_id), already_assigned_rating[sys_id]) # get systems to defend capital_id = PlanetUtilsAI.get_capital() if capital_id is not None: capital_planet = universe.getPlanet(capital_id) else: capital_planet = None # TODO: if no owned planets try to capture one! if capital_planet: capital_sys_id = capital_planet.systemID else: # should be rare, but so as to not break code below, pick a randomish mil-centroid system capital_sys_id = None # unless we can find one to use system_dict = {} for fleet_id in all_military_fleet_ids: status = foAI.foAIstate.fleetStatus.get(fleet_id, None) if status is not None: sys_id = status['sysID'] if not list(universe.getSystem(sys_id).planetIDs): continue system_dict[sys_id] = system_dict.get(sys_id, 0) + status.get('rating', 0) ranked_systems = sorted([(val, sys_id) for sys_id, val in system_dict.items()]) if ranked_systems: capital_sys_id = ranked_systems[-1][-1] else: try: capital_sys_id = foAI.foAIstate.fleetStatus.items()[0][1]['sysID'] except: pass num_targets = max(10, PriorityAI.allotted_outpost_targets) top_target_planets = ([pid for pid, pscore, trp in AIstate.invasionTargets[:PriorityAI.allottedInvasionTargets] if pscore > 20] + [pid for pid, (pscore, spec) in foAI.foAIstate.colonisableOutpostIDs.items()[:num_targets] if pscore > 20] + [pid for pid, (pscore, spec) in foAI.foAIstate.colonisablePlanetIDs.items()[:num_targets] if pscore > 20]) top_target_planets.extend(foAI.foAIstate.qualifyingTroopBaseTargets.keys()) base_col_target_systems = PlanetUtilsAI.get_systems(top_target_planets) top_target_systems = [] for sys_id in AIstate.invasionTargetedSystemIDs + base_col_target_systems: if sys_id not in top_target_systems: if foAI.foAIstate.systemStatus[sys_id]['totalThreat'] > get_tot_mil_rating(): continue top_target_systems.append(sys_id) # doing this rather than set, to preserve order try: # capital defense allocation_helper = AllocationHelper(already_assigned_rating, avail_mil_rating, try_reset) if capital_sys_id is not None: CapitalDefenseAllocator(capital_sys_id, allocation_helper).allocate() # defend other planets empire_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire(universe.planetIDs) empire_occupied_system_ids = list(set(PlanetUtilsAI.get_systems(empire_planet_ids)) - {capital_sys_id}) for sys_id in empire_occupied_system_ids: PlanetDefenseAllocator(sys_id, allocation_helper).allocate() # attack / protect high priority targets for sys_id in top_target_systems: TopTargetAllocator(sys_id, allocation_helper).allocate() # enemy planets other_targeted_system_ids = [sys_id for sys_id in set(PlanetUtilsAI.get_systems(AIstate.opponentPlanetIDs)) if sys_id not in top_target_systems] for sys_id in other_targeted_system_ids: TargetAllocator(sys_id, allocation_helper).allocate() # colony / outpost targets other_targeted_system_ids = [sys_id for sys_id in list(set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs)) if sys_id not in top_target_systems] for sys_id in other_targeted_system_ids: OutpostTargetAllocator(sys_id, allocation_helper).allocate() # TODO blockade enemy systems # interior systems targetable_ids = set(ColonisationAI.systems_by_supply_tier.get(0, []) + ColonisationAI.systems_by_supply_tier.get(1, [])) current_mil_systems = [sid for sid, _, _, _ in allocation_helper.allocations] interior_targets1 = targetable_ids.difference(current_mil_systems) interior_targets = [sid for sid in interior_targets1 if ( allocation_helper.threat_bias + systems_status.get(sid, {}).get('totalThreat', 0) > 0.8 * allocation_helper.already_assigned_rating[sid])] for sys_id in interior_targets: InteriorTargetsAllocator(sys_id, allocation_helper).allocate() # TODO Exploration targets # border protections visible_system_ids = foAI.foAIstate.visInteriorSystemIDs.keys() + foAI.foAIstate.visBorderSystemIDs.keys() accessible_system_ids = [sys_id for sys_id in visible_system_ids if universe.systemsConnected(sys_id, home_system_id, empire_id)] current_mil_systems = [sid for sid, alloc, take_any, _ in allocation_helper.allocations if alloc > 0] border_targets1 = [sid for sid in accessible_system_ids if sid not in current_mil_systems] border_targets = [sid for sid in border_targets1 if ( allocation_helper.threat_bias + systems_status.get(sid, {}).get('fleetThreat', 0) + systems_status.get(sid, {}).get( 'planetThreat', 0) > 0.8 * allocation_helper.already_assigned_rating[sid])] for sys_id in border_targets: BorderSecurityAllocator(sys_id, allocation_helper).allocate() except ReleaseMilitaryException: try_again(all_military_fleet_ids) return allocation_groups = allocation_helper.allocation_by_groups allocations = allocation_helper.allocations new_allocations = [] remaining_mil_rating = avail_mil_rating # for top categories assign max_alloc right away as available for cat in ['capitol', 'occupied', 'topTargets']: for sid, alloc, take_any, max_alloc in allocation_groups.get(cat, []): if remaining_mil_rating <= 0: break this_alloc = min(remaining_mil_rating, max_alloc) new_allocations.append((sid, this_alloc, alloc, take_any)) remaining_mil_rating -= this_alloc base_allocs = set() # for lower priority categories, first assign base_alloc around to all, then top up as available for cat in ['otherTargets', 'accessibleTargets', 'exploreTargets']: for sid, alloc, take_any, max_alloc in allocation_groups.get(cat, []): if remaining_mil_rating <= 0: break base_allocs.add(sid) remaining_mil_rating -= alloc for cat in ['otherTargets', 'accessibleTargets', 'exploreTargets']: for sid, alloc, take_any, max_alloc in allocation_groups.get(cat, []): if sid not in base_allocs: break if remaining_mil_rating <= 0: new_allocations.append((sid, alloc, alloc, take_any)) else: new_rating = min(remaining_mil_rating + alloc, max_alloc) new_allocations.append((sid, new_rating, alloc, take_any)) remaining_mil_rating -= (new_rating - alloc) if "Main" in thisround: _military_allocations = new_allocations _min_mil_allocations.clear() _min_mil_allocations.update([(sid, alloc) for sid, alloc, take_any, _ in allocations]) if _verbose_mil_reporting or "Main" in thisround: print "------------------------------\nFinal %s Round Military Allocations: %s \n-----------------------" % (thisround, dict([(sid, alloc) for sid, alloc, minalloc, take_any in new_allocations])) print "(Apparently) remaining military rating: %.1f" % remaining_mil_rating # export military systems for other AI modules if "Main" in thisround: AIstate.militarySystemIDs = list(set([sid for sid, alloc, minalloc, take_any in new_allocations]).union( [sid for sid in allocation_helper.already_assigned_rating if allocation_helper.already_assigned_rating[sid] > 0])) else: AIstate.militarySystemIDs = list(set([sid for sid, alloc, minalloc, take_any in new_allocations]).union(AIstate.militarySystemIDs)) return new_allocations
def assign_military_fleets_to_systems(use_fleet_id_list=None, allocations=None, round=1): # assign military fleets to military theater systems global _military_allocations universe = fo.getUniverse() if allocations is None: allocations = [] doing_main = (use_fleet_id_list is None) if doing_main: foAI.foAIstate.misc['ReassignedFleetMissions'] = [] base_defense_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.ORBITAL_DEFENSE) unassigned_base_defense_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types(base_defense_ids) for fleet_id in unassigned_base_defense_ids: fleet = universe.getFleet(fleet_id) if not fleet: continue sys_id = fleet.systemID target = universe_object.System(sys_id) fleet_mission = foAI.foAIstate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() mission_type = MissionType.ORBITAL_DEFENSE fleet_mission.set_target(mission_type, target) all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) if not all_military_fleet_ids: _military_allocations = [] return avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids) these_allocations = _military_allocations print "==================================================" print "Assigning military fleets" print "---------------------------------" else: avail_mil_fleet_ids = list(use_fleet_id_list) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids) these_allocations = allocations # send_for_repair(mil_needing_repair_ids) #currently, let get taken care of by AIFleetMission.generate_fleet_orders() # get systems to defend avail_mil_fleet_ids = set(avail_mil_fleet_ids) for sys_id, alloc, minalloc, takeAny in these_allocations: if not doing_main and not avail_mil_fleet_ids: break found_fleets = [] found_stats = {} these_fleets = FleetUtilsAI.get_fleets_for_mission({'rating': alloc}, {'rating': minalloc}, found_stats, starting_system=sys_id, fleet_pool_set=avail_mil_fleet_ids, fleet_list=found_fleets) if not these_fleets: if not found_fleets or not (FleetUtilsAI.stats_meet_reqs(found_stats, {'rating': minalloc}) or takeAny): if doing_main: if _verbose_mil_reporting: print "NO available/suitable military allocation for system %d ( %s ) -- requested allocation %8d, found available rating %8d in fleets %s" % (sys_id, universe.getSystem(sys_id).name, minalloc, found_stats.get('rating', 0), found_fleets) avail_mil_fleet_ids.update(found_fleets) continue else: these_fleets = found_fleets rating = CombatRatingsAI.combine_ratings_list(map(CombatRatingsAI.get_fleet_rating, found_fleets)) if doing_main and _verbose_mil_reporting: if rating < _min_mil_allocations.get(sys_id, 0): print "PARTIAL military allocation for system %d ( %s ) -- requested allocation %8d -- got %8d with fleets %s" % (sys_id, universe.getSystem(sys_id).name, minalloc, rating, these_fleets) else: print "FULL MIN military allocation for system %d ( %s ) -- requested allocation %8d -- got %8d with fleets %s" % (sys_id, universe.getSystem(sys_id).name, _min_mil_allocations.get(sys_id, 0), rating, these_fleets) elif doing_main and _verbose_mil_reporting: print "FULL+ military allocation for system %d ( %s ) -- requested allocation %8d, got %8d with fleets %s" % (sys_id, universe.getSystem(sys_id).name, alloc, found_stats.get('rating', 0), these_fleets) target = universe_object.System(sys_id) for fleet_id in these_fleets: fo.issueAggressionOrder(fleet_id, True) fleet_mission = foAI.foAIstate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() if sys_id in list(set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs + AIstate.blockadeTargetedSystemIDs)): mission_type = MissionType.SECURE else: mission_type = MissionType.MILITARY fleet_mission.set_target(mission_type, target) fleet_mission.generate_fleet_orders() if not doing_main: foAI.foAIstate.misc.setdefault('ReassignedFleetMissions', []).append(fleet_mission) if doing_main: print "---------------------------------" last_round = 3 last_round_name = "LastRound" if round <= last_round: # check if any fleets remain unassigned all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) allocations = [] round += 1 thisround = "Extras Remaining Round %d" % round if round < last_round else last_round_name if avail_mil_fleet_ids: print "Still have available military fleets: %s" % avail_mil_fleet_ids allocations = get_military_fleets(mil_fleets_ids=avail_mil_fleet_ids, try_reset=False, thisround=thisround) if allocations: assign_military_fleets_to_systems(use_fleet_id_list=avail_mil_fleet_ids, allocations=allocations, round=round)
def check_mergers(self, context=""): """ If possible and reasonable, merge this fleet with others. :param context: Context of the function call for logging purposes :type context: str """ if self.type not in MERGEABLE_MISSION_TYPES: return universe = fo.getUniverse() empire_id = fo.empireID() fleet_id = self.fleet.id main_fleet = universe.getFleet(fleet_id) system_id = main_fleet.systemID if system_id == INVALID_ID: return # can't merge fleets in middle of starlane aistate = get_aistate() system_status = aistate.systemStatus[system_id] # if a combat mission, and only have final order (so must be at final target), don't try # merging if there is no local threat (it tends to lead to fleet object churn) if self.type in COMBAT_MISSION_TYPES and len( self.orders) == 1 and not system_status.get('totalThreat', 0): return destroyed_list = list(universe.destroyedObjectIDs(empire_id)) other_fleets_here = [ fid for fid in system_status.get('myFleetsAccessible', []) if fid != fleet_id and fid not in destroyed_list and universe.getFleet(fid).ownedBy(empire_id) ] if not other_fleets_here: return target_id = self.target.id if self.target else None main_fleet_role = aistate.get_fleet_role(fleet_id) for fid in other_fleets_here: fleet_role = aistate.get_fleet_role(fid) if fleet_role not in COMPATIBLE_ROLES_MAP[main_fleet_role]: continue fleet = universe.getFleet(fid) if not fleet or fleet.systemID != system_id or len( fleet.shipIDs) == 0: continue if not (fleet.speed > 0 or main_fleet.speed == 0): # TODO(Cjkjvfnby) Check this condition continue fleet_mission = aistate.get_fleet_mission(fid) do_merge = False need_left = 0 if (main_fleet_role == MissionType.ORBITAL_DEFENSE) or ( fleet_role == MissionType.ORBITAL_DEFENSE): if main_fleet_role == fleet_role: do_merge = True elif (main_fleet_role == MissionType.ORBITAL_INVASION) or ( fleet_role == MissionType.ORBITAL_INVASION): if main_fleet_role == fleet_role: do_merge = False # TODO: could allow merger if both orb invaders and both same target elif not fleet_mission and (main_fleet.speed > 0) and (fleet.speed > 0): do_merge = True else: if not self.target and (main_fleet.speed > 0 or fleet.speed == 0): do_merge = True else: target = fleet_mission.target.id if fleet_mission.target else None if target == target_id: info( "Military fleet %d (%d ships) has same target as %s fleet %d (%d ships). Merging former " "into latter." % (fid, fleet.numShips, fleet_role, fleet_id, len(main_fleet.shipIDs))) # TODO: should probably ensure that fleetA has aggression on now do_merge = float(min( main_fleet.speed, fleet.speed)) / max( main_fleet.speed, fleet.speed) >= 0.6 elif main_fleet.speed > 0: neighbors = aistate.systemStatus.get( system_id, {}).get('neighbors', []) if target == system_id and target_id in neighbors and self.type == MissionType.SECURE: # consider 'borrowing' for work in neighbor system # TODO check condition need_left = 1.5 * sum( aistate.systemStatus.get(nid, {}).get( 'fleetThreat', 0) for nid in neighbors if nid != target_id) fleet_rating = CombatRatingsAI.get_fleet_rating( fid) if need_left < fleet_rating: do_merge = True if do_merge: FleetUtilsAI.merge_fleet_a_into_b( fid, fleet_id, need_left, context="Order %s of mission %s" % (context, self)) return
def _calculate_threat(self): return (self.threat_bias + +self.safety_factor * CombatRatingsAI.combine_ratings_list([ self._local_threat(), .75 * self._neighbor_threat(), .5 * self._jump2_threat() ]) + self._potential_threat())
def __update_system_status(self): print 10 * "=", "Updating System Threats", 10 * "=" universe = fo.getUniverse() empire = fo.getEmpire() empire_id = fo.empireID() destroyed_object_ids = universe.destroyedObjectIDs(empire_id) supply_unobstructed_systems = set(empire.supplyUnobstructedSystems) min_hidden_attack = 4 min_hidden_health = 8 observed_empires = self.misc.setdefault("observed_empires", set()) # TODO: Variables that are recalculated each turn from scratch should not be stored in AIstate # clear previous game state for sys_id in self.systemStatus: self.systemStatus[sys_id]['enemy_ship_count'] = 0 self.systemStatus[sys_id]['myFleetRating'] = 0 self.systemStatus[sys_id]['myFleetRatingVsPlanets'] = 0 # for use in debugging verbose = False # assess enemy fleets that may have been momentarily visible enemies_by_system = {} my_fleets_by_system = {} fleet_spot_position = {} current_turn = fo.currentTurn() for fleet_id in universe.fleetIDs: fleet = universe.getFleet(fleet_id) if not fleet or fleet.empty: self.delete_fleet_info(fleet_id) # this is safe even if fleet wasn't mine continue # TODO: check if currently in system and blockaded before accepting destination as location this_system_id = fleet.nextSystemID if fleet.nextSystemID != INVALID_ID else fleet.systemID dead_fleet = fleet_id in destroyed_object_ids if dead_fleet: self.delete_fleet_info(fleet_id) if fleet.ownedBy(empire_id): if not dead_fleet: my_fleets_by_system.setdefault(this_system_id, []).append(fleet_id) fleet_spot_position.setdefault(fleet.systemID, []).append(fleet_id) continue # TODO: consider checking death of individual ships. If ships had been moved from this fleet # into another fleet, we might have witnessed their death in that other fleet but if this fleet # had not been seen since before that transfer then the ships might also still be listed here. if dead_fleet: continue # we are only interested in immediately recent data if get_partial_visibility_turn(fleet_id) < (current_turn - 1): continue sys_status = self.systemStatus.setdefault(this_system_id, {}) sys_status['enemy_ship_count'] = sys_status.get('enemy_ship_count', 0) + len(fleet.shipIDs) enemies_by_system.setdefault(this_system_id, []).append(fleet_id) if not fleet.unowned: self.misc.setdefault('enemies_sighted', {}).setdefault(current_turn, []).append(fleet_id) observed_empires.add(fleet.owner) # assess fleet and planet threats & my local fleets for sys_id in universe.systemIDs: sys_status = self.systemStatus.setdefault(sys_id, {}) system = universe.getSystem(sys_id) if verbose: print "AIState threat evaluation for %s" % system # update fleets sys_status['myfleets'] = my_fleets_by_system.get(sys_id, []) sys_status['myFleetsAccessible'] = fleet_spot_position.get(sys_id, []) local_enemy_fleet_ids = enemies_by_system.get(sys_id, []) sys_status['localEnemyFleetIDs'] = local_enemy_fleet_ids if system: sys_status['name'] = system.name # update threats monster_ratings = [] # immobile enemy_ratings = [] # owned & mobile mob_ratings = [] # mobile & unowned mobile_fleets = [] # mobile and either owned or unowned for fid in local_enemy_fleet_ids: fleet = universe.getFleet(fid) # ensured to exist fleet_rating = CombatRatingsAI.get_fleet_rating( fid, enemy_stats=CombatRatingsAI.get_empire_standard_fighter()) if fleet.speed == 0: monster_ratings.append(fleet_rating) if verbose: print "\t immobile enemy fleet %s has rating %.1f" % (fleet, fleet_rating) continue if verbose: print "\t mobile enemy fleet %s has rating %.1f" % (fleet, fleet_rating) mobile_fleets.append(fid) if fleet.unowned: mob_ratings.append(fleet_rating) else: enemy_ratings.append(fleet_rating) enemy_rating = CombatRatingsAI.combine_ratings_list(enemy_ratings) monster_rating = CombatRatingsAI.combine_ratings_list(monster_ratings) mob_rating = CombatRatingsAI.combine_ratings_list(mob_ratings) lost_fleets = fleetsLostBySystem.get(sys_id, []) lost_fleet_rating = CombatRatingsAI.combine_ratings_list(lost_fleets) # under current visibility rules should not be possible to have any losses or other info here, # but just in case... partial_vis_turn = get_partial_visibility_turn(sys_id) if not system or partial_vis_turn < 0: if verbose: print "Never had partial vis for %s - basing threat assessment on old info and lost ships" % system sys_status.setdefault('local_fleet_threats', set()) sys_status['planetThreat'] = 0 sys_status['fleetThreat'] = max( CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), 0.98 * sys_status.get('fleetThreat', 0), 1.1*lost_fleet_rating - monster_rating) sys_status['monsterThreat'] = max( monster_rating, 0.98 * sys_status.get('monsterThreat', 0), 1.1*lost_fleet_rating - enemy_rating - mob_rating) sys_status['enemy_threat'] = max( enemy_rating, 0.98 * sys_status.get('enemy_threat', 0), 1.1*lost_fleet_rating - monster_rating - mob_rating) sys_status['mydefenses'] = {'overall': 0, 'attack': 0, 'health': 0} sys_status['totalThreat'] = sys_status['fleetThreat'] sys_status['regional_fleet_threats'] = sys_status['local_fleet_threats'].copy() continue # have either stale or current info pattack = phealth = 0 mypattack = myphealth = 0 for pid in system.planetIDs: planet = universe.getPlanet(pid) if not planet: continue prating = self.assess_planet_threat(pid, sighting_age=current_turn - partial_vis_turn) if planet.ownedBy(empire_id): # TODO: check for diplomatic status mypattack += prating['attack'] myphealth += prating['health'] else: pattack += prating['attack'] phealth += prating['health'] if any("_NEST_" in special for special in planet.specials): sys_status['nest_threat'] = 100 sys_status['planetThreat'] = pattack * phealth sys_status['mydefenses'] = {'overall': mypattack * myphealth, 'attack': mypattack, 'health': myphealth} # previous threat assessment could account for losses, ignore the losses now if max(sys_status.get('totalThreat', 0), pattack * phealth) >= 0.6 * lost_fleet_rating: lost_fleet_rating = 0 # TODO use sitrep combat info rather than estimating stealthed enemies by fleets lost to them # TODO also only consider past stealthed fleet threat to still be present if the system is still obstructed # TODO: track visibility across turns in order to distinguish the blip of visibility in (losing) combat, # which FO currently treats as being for the previous turn, # partially superseding the previous visibility for that turn if not partial_vis_turn == current_turn: sys_status.setdefault('local_fleet_threats', set()) sys_status['currently_visible'] = False # print ("Stale visibility for system %d ( %s ) -- last seen %d, " # "current Turn %d -- basing threat assessment on old info and lost ships") % ( # sys_id, sys_status.get('name', "name unknown"), partial_vis_turn, currentTurn) sys_status['fleetThreat'] = max( CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), 0.98 * sys_status.get('fleetThreat', 0), 2.0 * lost_fleet_rating - max(sys_status.get('monsterThreat', 0), monster_rating)) sys_status['enemy_threat'] = max( enemy_rating, 0.98 * sys_status.get('enemy_threat', 0), 1.1*lost_fleet_rating - max(sys_status.get('monsterThreat', 0), monster_rating)) sys_status['monsterThreat'] = max(monster_rating, 0.98 * sys_status.get('monsterThreat', 0)) # sys_status['totalThreat'] = ((pattack + enemy_attack + monster_attack) ** 0.8)\ # * ((phealth + enemy_health + monster_health)** 0.6) # reevaluate this sys_status['totalThreat'] = max( CombatRatingsAI.combine_ratings_list([enemy_rating, mob_rating, monster_rating, pattack * phealth]), 2 * lost_fleet_rating, 0.98 * sys_status.get('totalThreat', 0)) else: # system considered visible sys_status['currently_visible'] = True sys_status['local_fleet_threats'] = set(mobile_fleets) # includes mobile monsters sys_status['fleetThreat'] = max( CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), 2*lost_fleet_rating - monster_rating) if verbose: print "enemy threat calc parts: enemy rating %.1f, lost fleet rating %.1f, monster_rating %.1f" % ( enemy_rating, lost_fleet_rating, monster_rating) # does NOT include mobile monsters sys_status['enemy_threat'] = max(enemy_rating, 2*lost_fleet_rating - monster_rating) sys_status['monsterThreat'] = monster_rating sys_status['totalThreat'] = CombatRatingsAI.combine_ratings_list( [enemy_rating, mob_rating, monster_rating, pattack * phealth]) sys_status['regional_fleet_threats'] = sys_status['local_fleet_threats'].copy() sys_status['fleetThreat'] = max(sys_status['fleetThreat'], sys_status.get('nest_threat', 0)) sys_status['totalThreat'] = max(sys_status['totalThreat'], sys_status.get('nest_threat', 0)) # has been seen with Partial Vis, but is currently supply-blocked if partial_vis_turn > 0 and sys_id not in supply_unobstructed_systems: sys_status['fleetThreat'] = max(sys_status['fleetThreat'], min_hidden_attack * min_hidden_health) sys_status['totalThreat'] = max( sys_status['totalThreat'], ((pattack + min_hidden_attack) ** 0.8) * ((phealth + min_hidden_health) ** 0.6)) if verbose and sys_status['fleetThreat'] > 0: print "%s intermediate status: %s" % (system, sys_status) enemy_supply, enemy_near_supply = self.assess_enemy_supply() # TODO: assess change in enemy supply over time # assess secondary threats (threats of surrounding systems) and update my fleet rating for sys_id in universe.systemIDs: sys_status = self.systemStatus[sys_id] sys_status['enemies_supplied'] = enemy_supply.get(sys_id, []) observed_empires.update(enemy_supply.get(sys_id, [])) sys_status['enemies_nearly_supplied'] = enemy_near_supply.get(sys_id, []) my_ratings_list = [] my_ratings_against_planets_list = [] for fid in sys_status['myfleets']: this_rating = self.get_rating(fid, True, self.get_standard_enemy()) my_ratings_list.append(this_rating) my_ratings_against_planets_list.append(self.get_rating(fid, against_planets=True)) if sys_id != INVALID_ID: sys_status['myFleetRating'] = CombatRatingsAI.combine_ratings_list(my_ratings_list) sys_status['myFleetRatingVsPlanets'] = CombatRatingsAI.combine_ratings_list( my_ratings_against_planets_list) sys_status['all_local_defenses'] = CombatRatingsAI.combine_ratings( sys_status['myFleetRating'], sys_status['mydefenses']['overall']) sys_status['neighbors'] = set(universe.getImmediateNeighbors(sys_id, self.empireID)) for sys_id in universe.systemIDs: sys_status = self.systemStatus[sys_id] neighbors = sys_status.get('neighbors', set()) this_system = universe.getSystem(sys_id) if verbose: print "Regional Assessment for %s with local fleet threat %.1f" % ( this_system, sys_status.get('fleetThreat', 0)) jumps2 = set() jumps3 = set() jumps4 = set() for seta, setb in [(neighbors, jumps2), (jumps2, jumps3), (jumps3, jumps4)]: for sys2id in seta: setb.update(self.systemStatus.get(sys2id, {}).get('neighbors', set())) jump2ring = jumps2 - neighbors - {sys_id} jump3ring = jumps3 - jumps2 - neighbors - {sys_id} jump4ring = jumps4 - jumps3 - jumps2 - neighbors - {sys_id} sys_status['2jump_ring'] = jump2ring sys_status['3jump_ring'] = jump3ring sys_status['4jump_ring'] = jump4ring threat, max_threat, myrating, j1_threats = self.area_ratings(neighbors) sys_status['neighborThreat'] = threat sys_status['max_neighbor_threat'] = max_threat sys_status['my_neighbor_rating'] = myrating threat, max_threat, myrating, j2_threats = self.area_ratings(jump2ring) sys_status['jump2_threat'] = threat sys_status['my_jump2_rating'] = myrating threat, max_threat, myrating, j3_threats = self.area_ratings(jump3ring) sys_status['jump3_threat'] = threat sys_status['my_jump3_rating'] = myrating # for local system includes both enemies and mobs threat_keys = ['fleetThreat', 'neighborThreat', 'jump2_threat'] sys_status['regional_threat'] = CombatRatingsAI.combine_ratings_list( [sys_status.get(x, 0) for x in threat_keys]) # TODO: investigate cases where regional_threat has been nonzero but no regional_threat_fleets # (probably due to attenuating history of past threats) sys_status.setdefault('regional_fleet_threats', set()).update(j1_threats, j2_threats)
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 issue_fleet_orders(self): """issues AIFleetOrders which can be issued in system and moves to next one if is possible""" # TODO: priority order_completed = True debug( "\nChecking orders for fleet %s (on turn %d), with mission type %s and target %s", self.fleet.get_object(), fo.currentTurn(), self.type or 'No mission', self.target or 'No Target') if MissionType.INVASION == self.type: self._check_retarget_invasion() just_issued_move_order = False last_move_target_id = INVALID_ID # Note: the following abort check somewhat assumes only one major mission type for fleet_order in self.orders: if (isinstance(fleet_order, (OrderColonize, OrderOutpost, OrderInvade)) and self._check_abort_mission(fleet_order)): return aistate = get_aistate() for fleet_order in self.orders: if just_issued_move_order and self.fleet.get_object( ).systemID != last_move_target_id: # having just issued a move order, we will normally stop issuing orders this turn, except that if there # are consecutive move orders we will consider moving through the first destination rather than stopping # Without the below noinspection directive, PyCharm is concerned about the 2nd part of the test # noinspection PyTypeChecker if (not isinstance(fleet_order, OrderMove) or self.need_to_pause_movement(last_move_target_id, fleet_order)): break debug("Checking order: %s" % fleet_order) self.check_mergers(context=str(fleet_order)) if fleet_order.can_issue_order(verbose=False): # only move if all other orders completed if isinstance(fleet_order, OrderMove) and order_completed: debug("Issuing fleet order %s" % fleet_order) fleet_order.issue_order() just_issued_move_order = True last_move_target_id = fleet_order.target.id elif not isinstance(fleet_order, OrderMove): debug("Issuing fleet order %s" % fleet_order) fleet_order.issue_order() else: debug( "NOT issuing (even though can_issue) fleet order %s" % fleet_order) status_words = tuple( ["not", ""][_s] for _s in [fleet_order.order_issued, fleet_order.executed]) debug("Order %s issued and %s fully executed." % status_words) if not fleet_order.executed: order_completed = False else: # check that we're not held up by a Big Monster if fleet_order.order_issued: # A previously issued order that wasn't instantly executed must have had cirumstances change so that # the order can't currently be reissued (or perhaps simply a savegame has been reloaded on the same # turn the order was issued). if not fleet_order.executed: order_completed = False # Go on to the next order. continue debug("CAN'T issue fleet order %s because:" % fleet_order) fleet_order.can_issue_order(verbose=True) if isinstance(fleet_order, OrderMove): this_system_id = fleet_order.target.id this_status = aistate.systemStatus.setdefault( this_system_id, {}) threat_threshold = fo.currentTurn( ) * MilitaryAI.cur_best_mil_ship_rating() / 4.0 if this_status.get('monsterThreat', 0) > threat_threshold: # if this move order is not this mil fleet's final destination, and blocked by Big Monster, # release and hope for more effective reassignment if (self.type not in (MissionType.MILITARY, MissionType.SECURE) or fleet_order != self.orders[-1]): debug( "Aborting mission due to being blocked by Big Monster at system %d, threat %d" % (this_system_id, aistate.systemStatus[this_system_id] ['monsterThreat'])) debug("Full set of orders were:") for this_order in self.orders: debug(" - %s" % this_order) self.clear_fleet_orders() self.clear_target() return break # do not order the next order until this one is finished. else: # went through entire order list if order_completed: debug("Final order is completed") orders = self.orders last_order = orders[-1] if orders else None universe = fo.getUniverse() if last_order and isinstance(last_order, OrderColonize): planet = universe.getPlanet(last_order.target.id) sys_partial_vis_turn = get_partial_visibility_turn( planet.systemID) planet_partial_vis_turn = get_partial_visibility_turn( planet.id) if (planet_partial_vis_turn == sys_partial_vis_turn and not planet.initialMeterValue( fo.meterType.population)): warning( "Fleet %s has tentatively completed its " "colonize mission but will wait to confirm population.", self.fleet) debug(" Order details are %s" % last_order) debug( " Order is valid: %s; issued: %s; executed: %s" % (last_order.is_valid(), last_order.order_issued, last_order.executed)) if not last_order.is_valid(): source_target = last_order.fleet target_target = last_order.target debug( " source target validity: %s; target target validity: %s " % (bool(source_target), bool(target_target))) return # colonize order must not have completed yet clear_all = True last_sys_target = INVALID_ID if last_order and isinstance(last_order, OrderMilitary): last_sys_target = last_order.target.id # not doing this until decide a way to release from a SECURE mission # if (MissionType.SECURE == self.type) or secure_targets = set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs) if last_sys_target in secure_targets: # consider a secure mission if last_sys_target in AIstate.colonyTargetedSystemIDs: secure_type = "Colony" elif last_sys_target in AIstate.outpostTargetedSystemIDs: secure_type = "Outpost" elif last_sys_target in AIstate.invasionTargetedSystemIDs: secure_type = "Invasion" else: secure_type = "Unidentified" debug( "Fleet %d has completed initial stage of its mission " "to secure system %d (targeted for %s), " "may release a portion of ships" % (self.fleet.id, last_sys_target, secure_type)) clear_all = False # for PROTECT_REGION missions, only release fleet if no more threat if self.type == MissionType.PROTECT_REGION: # use military logic code below to determine if can release # any or even all of the ships. clear_all = False last_sys_target = self.target.id debug( "Check if PROTECT_REGION mission with target %d is finished.", last_sys_target) fleet_id = self.fleet.id if clear_all: if orders: debug( "Fleet %d has completed its mission; clearing all orders and targets." % self.fleet.id) debug("Full set of orders were:") for this_order in orders: debug("\t\t %s" % this_order) self.clear_fleet_orders() self.clear_target() if aistate.get_fleet_role(fleet_id) in ( MissionType.MILITARY, MissionType.SECURE): allocations = MilitaryAI.get_military_fleets( mil_fleets_ids=[fleet_id], try_reset=False, thisround="Fleet %d Reassignment" % fleet_id) if allocations: MilitaryAI.assign_military_fleets_to_systems( use_fleet_id_list=[fleet_id], allocations=allocations) else: # no orders debug("No Current Orders") else: potential_threat = CombatRatingsAI.combine_ratings( MilitaryAI.get_system_local_threat(last_sys_target), MilitaryAI.get_system_neighbor_threat(last_sys_target)) threat_present = potential_threat > 0 debug("Fleet threat present? %s", threat_present) target_system = universe.getSystem(last_sys_target) if not threat_present and target_system: for pid in target_system.planetIDs: planet = universe.getPlanet(pid) if (planet and planet.owner != fo.empireID() and planet.currentMeterValue( fo.meterType.maxDefense) > 0): debug("Found local planetary threat: %s", planet) threat_present = True break if not threat_present: debug( "No current threat in target system; releasing a portion of ships." ) # at least first stage of current task is done; # release extra ships for potential other deployments new_fleets = FleetUtilsAI.split_fleet(self.fleet.id) if self.type == MissionType.PROTECT_REGION: self.clear_fleet_orders() self.clear_target() new_fleets.append(self.fleet.id) else: debug( "Threat remains in target system; Considering to release some ships." ) new_fleets = [] fleet_portion_to_remain = self._portion_of_fleet_needed_here( ) if fleet_portion_to_remain >= 1: debug( "Can not release fleet yet due to large threat." ) elif fleet_portion_to_remain > 0: debug( "Not all ships are needed here - considering releasing a few" ) # TODO: Rate against specific enemy threat cause fleet_remaining_rating = CombatRatingsAI.get_fleet_rating( fleet_id) fleet_min_rating = fleet_portion_to_remain * fleet_remaining_rating debug("Starting rating: %.1f, Target rating: %.1f", fleet_remaining_rating, fleet_min_rating) allowance = CombatRatingsAI.rating_needed( fleet_remaining_rating, fleet_min_rating) debug( "May release ships with total rating of %.1f", allowance) ship_ids = list(self.fleet.get_object().shipIDs) for ship_id in ship_ids: ship_rating = CombatRatingsAI.get_ship_rating( ship_id) debug( "Considering to release ship %d with rating %.1f", ship_id, ship_rating) if ship_rating > allowance: debug( "Remaining rating insufficient. Not released." ) continue debug("Splitting from fleet.") new_fleet_id = FleetUtilsAI.split_ship_from_fleet( fleet_id, ship_id) if assertion_fails( new_fleet_id and new_fleet_id != INVALID_ID): break new_fleets.append(new_fleet_id) fleet_remaining_rating = CombatRatingsAI.rating_difference( fleet_remaining_rating, ship_rating) allowance = CombatRatingsAI.rating_difference( fleet_remaining_rating, fleet_min_rating) debug( "Remaining fleet rating: %.1f - Allowance: %.1f", fleet_remaining_rating, allowance) if new_fleets: aistate.get_fleet_role(fleet_id, force_new=True) aistate.update_fleet_rating(fleet_id) aistate.ensure_have_fleet_missions(new_fleets) else: debug( "Planetary defenses are deemed sufficient. Release fleet." ) new_fleets = FleetUtilsAI.split_fleet( self.fleet.id) new_military_fleets = [] for fleet_id in new_fleets: if aistate.get_fleet_role( fleet_id) in COMBAT_MISSION_TYPES: new_military_fleets.append(fleet_id) allocations = [] if new_military_fleets: allocations = MilitaryAI.get_military_fleets( mil_fleets_ids=new_military_fleets, try_reset=False, thisround="Fleet Reassignment %s" % new_military_fleets) if allocations: MilitaryAI.assign_military_fleets_to_systems( use_fleet_id_list=new_military_fleets, allocations=allocations)
def evaluate_invasion_planet(planet_id, secure_fleet_missions, verbose=True): """Return the invasion value (score, troops) of a planet.""" universe = fo.getUniverse() empire_id = fo.empireID() detail = [] planet = universe.getPlanet(planet_id) if planet is None: debug("Invasion AI couldn't access any info for planet id %d" % planet_id) return [0, 0] system_id = planet.systemID # by using the following instead of simply relying on stealth meter reading, can (sometimes) plan ahead even if # planet is temporarily shrouded by an ion storm predicted_detectable = EspionageAI.colony_detectable_by_empire( planet_id, empire=fo.empireID(), default_result=False) if not predicted_detectable: if get_partial_visibility_turn(planet_id) < fo.currentTurn(): debug("InvasionAI predicts planet id %d to be stealthed" % planet_id) return [0, 0] else: debug( "InvasionAI predicts planet id %d to be stealthed" % planet_id + ", but somehow have current visibity anyway, will still consider as target" ) # Check if the target planet was extra-stealthed somehow its system was last viewed # this test below may augment the tests above, but can be thrown off by temporary combat-related sighting system_last_seen = get_partial_visibility_turn(planet_id) planet_last_seen = get_partial_visibility_turn(system_id) if planet_last_seen < system_last_seen: # TODO: track detection strength, order new scouting when it goes up debug( "Invasion AI considering planet id %d (stealthed at last view), still proceeding." % planet_id) # get a baseline evaluation of the planet as determined by ColonisationAI species_name = planet.speciesName species = fo.getSpecies(species_name) empire_research_list = tuple(element.tech for element in fo.getEmpire().researchQueue) if not species or AIDependencies.TAG_DESTROYED_ON_CONQUEST in species.tags: # this call iterates over this Empire's available species with which it could colonize after an invasion planet_eval = ColonisationAI.assign_colonisation_values( [planet_id], MissionType.INVASION, None, detail) colony_base_value = max( 0.75 * planet_eval.get(planet_id, [0])[0], ColonisationAI.evaluate_planet(planet_id, MissionType.OUTPOST, None, detail, empire_research_list)) else: colony_base_value = ColonisationAI.evaluate_planet( planet_id, MissionType.INVASION, species_name, detail, empire_research_list) # Add extra score for all buildings on the planet building_values = { "BLD_IMPERIAL_PALACE": 1000, "BLD_CULTURE_ARCHIVES": 1000, "BLD_AUTO_HISTORY_ANALYSER": 100, "BLD_SHIPYARD_BASE": 100, "BLD_SHIPYARD_ORG_ORB_INC": 200, "BLD_SHIPYARD_ORG_XENO_FAC": 200, "BLD_SHIPYARD_ORG_CELL_GRO_CHAMB": 200, "BLD_SHIPYARD_CON_NANOROBO": 300, "BLD_SHIPYARD_CON_GEOINT": 400, "BLD_SHIPYARD_CON_ADV_ENGINE": 1000, "BLD_SHIPYARD_AST": 300, "BLD_SHIPYARD_AST_REF": 1000, "BLD_SHIPYARD_ENRG_SOLAR": 1500, "BLD_INDUSTRY_CENTER": 500, "BLD_GAS_GIANT_GEN": 200, "BLD_SOL_ORB_GEN": 800, "BLD_BLACK_HOLE_POW_GEN": 2000, "BLD_ENCLAVE_VOID": 500, "BLD_NEUTRONIUM_EXTRACTOR": 2000, "BLD_NEUTRONIUM_SYNTH": 2000, "BLD_NEUTRONIUM_FORGE": 1000, "BLD_CONC_CAMP": 100, "BLD_BIOTERROR_PROJECTOR": 1000, "BLD_SHIPYARD_ENRG_COMP": 3000, } bld_tally = 0 for bldType in [ universe.getBuilding(bldg).buildingTypeName for bldg in planet.buildingIDs ]: bval = building_values.get(bldType, 50) bld_tally += bval detail.append("%s: %d" % (bldType, bval)) # Add extra score for unlocked techs when we conquer the species tech_tally = 0 value_per_pp = 4 for unlocked_tech in AIDependencies.SPECIES_TECH_UNLOCKS.get( species_name, []): if not tech_is_complete(unlocked_tech): rp_cost = fo.getTech(unlocked_tech).researchCost(empire_id) tech_value = value_per_pp * rp_cost tech_tally += tech_value detail.append("%s: %d" % (unlocked_tech, tech_value)) max_jumps = 8 capitol_id = PlanetUtilsAI.get_capital() least_jumps_path = [] clear_path = True if capitol_id: homeworld = universe.getPlanet(capitol_id) if homeworld and homeworld.systemID != INVALID_ID and system_id != INVALID_ID: least_jumps_path = list( universe.leastJumpsPath(homeworld.systemID, system_id, empire_id)) max_jumps = len(least_jumps_path) aistate = get_aistate() system_status = aistate.systemStatus.get(system_id, {}) system_fleet_treat = system_status.get('fleetThreat', 1000) system_monster_threat = system_status.get('monsterThreat', 0) sys_total_threat = system_fleet_treat + system_monster_threat + system_status.get( 'planetThreat', 0) max_path_threat = system_fleet_treat mil_ship_rating = MilitaryAI.cur_best_mil_ship_rating() for path_sys_id in least_jumps_path: path_leg_status = aistate.systemStatus.get(path_sys_id, {}) path_leg_threat = path_leg_status.get( 'fleetThreat', 1000) + path_leg_status.get('monsterThreat', 0) if path_leg_threat > 0.5 * mil_ship_rating: clear_path = False if path_leg_threat > max_path_threat: max_path_threat = path_leg_threat pop = planet.currentMeterValue(fo.meterType.population) target_pop = planet.currentMeterValue(fo.meterType.targetPopulation) troops = planet.currentMeterValue(fo.meterType.troops) troop_regen = planet.currentMeterValue( fo.meterType.troops) - planet.initialMeterValue(fo.meterType.troops) max_troops = planet.currentMeterValue(fo.meterType.maxTroops) # TODO: refactor troop determination into function for use in mid-mission updates and also consider defender techs max_troops += AIDependencies.TROOPS_PER_POP * (target_pop - pop) this_system = universe.getSystem(system_id) secure_targets = [system_id] + list(this_system.planetIDs) system_secured = False for mission in secure_fleet_missions: secure_fleet_id = mission.fleet.id s_fleet = universe.getFleet(secure_fleet_id) if not s_fleet or s_fleet.systemID != system_id: continue if mission.type in [MissionType.SECURE, MissionType.MILITARY]: target_obj = mission.target.get_object() if target_obj is not None and target_obj.id in secure_targets: system_secured = True break system_secured = system_secured and system_status.get('myFleetRating', 0) if verbose: debug( "Invasion eval of %s\n" " - maxShields: %.1f\n" " - sysFleetThreat: %.1f\n" " - sysMonsterThreat: %.1f", planet, planet.currentMeterValue(fo.meterType.maxShield), system_fleet_treat, system_monster_threat) enemy_val = 0 if planet.owner != -1: # value in taking this away from an enemy enemy_val = 20 * ( planet.currentMeterValue(fo.meterType.targetIndustry) + 2 * planet.currentMeterValue(fo.meterType.targetResearch)) # devalue invasions that would require too much military force preferred_max_portion = MilitaryAI.get_preferred_max_military_portion_for_single_battle( ) total_max_mil_rating = MilitaryAI.get_concentrated_tot_mil_rating() threat_exponent = 2 # TODO: make this a character trait; higher aggression with a lower exponent threat_factor = min( 1, preferred_max_portion * total_max_mil_rating / (sys_total_threat + 0.001))**threat_exponent design_id, _, locs = ProductionAI.get_best_ship_info( PriorityType.PRODUCTION_INVASION) if not locs or not universe.getPlanet(locs[0]): # We are in trouble anyway, so just calculate whatever approximation... build_time = 4 planned_troops = troops if system_secured else min( troops + troop_regen * (max_jumps + build_time), max_troops) planned_troops += .01 # we must attack with more troops than there are defenders troop_cost = math.ceil((planned_troops + _TROOPS_SAFETY_MARGIN) / 6.0) * 20 * FleetUtilsAI.get_fleet_upkeep() else: loc = locs[0] species_here = universe.getPlanet(loc).speciesName design = fo.getShipDesign(design_id) cost_per_ship = design.productionCost(empire_id, loc) build_time = design.productionTime(empire_id, loc) troops_per_ship = CombatRatingsAI.weight_attack_troops( design.troopCapacity, get_species_tag_grade(species_here, Tags.ATTACKTROOPS)) planned_troops = troops if system_secured else min( troops + troop_regen * (max_jumps + build_time), max_troops) planned_troops += .01 # we must attack with more troops than there are defenders ships_needed = math.ceil( (planned_troops + _TROOPS_SAFETY_MARGIN) / float(troops_per_ship)) troop_cost = ships_needed * cost_per_ship # fleet upkeep is already included in query from server # apply some bias to expensive operations normalized_cost = float(troop_cost) / max(fo.getEmpire().productionPoints, 1) normalized_cost = max(1., normalized_cost) cost_score = (normalized_cost**2 / 50.0) * troop_cost base_score = colony_base_value + bld_tally + tech_tally + enemy_val - cost_score # If the AI does have enough total military to attack this target, and the target is more than minimally valuable, # don't let the threat_factor discount the adjusted value below MIN_INVASION_SCORE +1, so that if there are no # other targets the AI could still pursue this one. Otherwise, scoring pressure from # MilitaryAI.get_preferred_max_military_portion_for_single_battle might prevent the AI from attacking heavily # defended but still defeatable targets even if it has no softer targets available. if total_max_mil_rating > sys_total_threat and base_score > 2 * MIN_INVASION_SCORE: threat_factor = max(threat_factor, (MIN_INVASION_SCORE + 1) / base_score) planet_score = retaliation_risk_factor(planet.owner) * threat_factor * max( 0, base_score) if clear_path: planet_score *= 1.5 if verbose: debug( ' - planet score: %.2f\n' ' - planned troops: %.2f\n' ' - projected troop cost: %.1f\n' ' - threat factor: %s\n' ' - planet detail: %s\n' ' - popval: %.1f\n' ' - bldval: %s\n' ' - enemyval: %s', planet_score, planned_troops, troop_cost, threat_factor, detail, colony_base_value, bld_tally, enemy_val) debug(' - system secured: %s' % system_secured) return [planet_score, planned_troops]
def _minimum_allocation(self, threat): nearby_forces = CombatRatingsAI.combine_ratings( self.assigned_rating, self._potential_support()) return max( CombatRatingsAI.rating_needed(self._regional_threat(), nearby_forces), CombatRatingsAI.rating_needed(1.4*threat, self.assigned_rating))
def _get_target_for_protection_mission(self): """Get a target for a PROTECT_REGION mission. 1) If primary target (system target of this mission) is under attack, move to primary target. 2) If neighbors of primary target have local enemy forces weaker than this fleet, may move to attack 3) If no neighboring fleets or strongest enemy force is too strong, move to defend primary target """ # TODO: Also check fleet rating vs planets in decision making below not only vs fleets universe = fo.getUniverse() primary_objective = self.target.id # TODO: Rate against specific threats fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id) debug( "%s finding target for protection mission (primary target %s). Fleet Rating: %.1f", self.fleet, self.target, fleet_rating) immediate_threat = MilitaryAI.get_system_local_threat( primary_objective) if immediate_threat: debug(" Immediate threat! Moving to primary mission target") return primary_objective else: debug(" No immediate threats.") # Try to eliminate neighbouring fleets neighbors = universe.getImmediateNeighbors(primary_objective, fo.empireID()) threat_list = sorted(map( lambda x: (MilitaryAI.get_system_local_threat(x), x), neighbors), reverse=True) if not threat_list: debug(" No neighbors (?!). Moving to primary mission target") return primary_objective else: debug(" Neighboring threats:") for threat, sys_id in threat_list: debug(" %s - %.1f", TargetSystem(sys_id), threat) top_threat, candidate_system = threat_list[0] if not top_threat: # TODO: Move into second ring but needs more careful evaluation # For now, consider staying at the current location if enemy # owns a planet here which we can bombard. current_system_id = self.fleet.get_current_system_id() if current_system_id in neighbors: system = universe.getSystem(current_system_id) if assertion_fails(system is not None): return primary_objective empire_id = fo.empireID() for planet_id in system.planetIDs: planet = universe.getPlanet(planet_id) if (planet and not planet.ownedBy(empire_id) and not planet.unowned): debug( "Currently no neighboring threats. " "Staying for bombardment of planet %s", planet) return current_system_id # TODO consider attacking neighboring, non-military fleets # - needs more careful evaluation against neighboring threats # empire_id = fo.empireID() # for sys_id in neighbors: # system = universe.getSystem(sys_id) # if assertion_fails(system is not None): # continue # local_fleets = system.fleetIDs # for fleet_id in local_fleets: # fleet = universe.getFleet(fleet_id) # if not fleet or fleet.ownedBy(empire_id): # continue # return sys_id debug( "No neighboring threats. Moving to primary mission target") return primary_objective # TODO rate against threat in target system # TODO only engage if can reach in 1 turn or leaves sufficient defense behind safety_factor = get_aistate().character.military_safety_factor() if fleet_rating < safety_factor * top_threat: debug( " Neighboring threat is too powerful. Moving to primary mission target" ) return primary_objective # do not engage! debug(" Engaging neighboring threat: %s", TargetSystem(candidate_system)) return candidate_system
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 check_mergers(self, context=""): """ If possible and reasonable, merge this fleet with others. :param context: Context of the function call for logging purposes :type context: str """ if self.type not in MERGEABLE_MISSION_TYPES: return universe = fo.getUniverse() empire_id = fo.empireID() fleet_id = self.fleet.id main_fleet = universe.getFleet(fleet_id) system_id = main_fleet.systemID if system_id == INVALID_ID: return # can't merge fleets in middle of starlane system_status = foAI.foAIstate.systemStatus[system_id] destroyed_list = list(universe.destroyedObjectIDs(empire_id)) other_fleets_here = [fid for fid in system_status.get('myFleetsAccessible', []) if fid != fleet_id and fid not in destroyed_list and universe.getFleet(fid).ownedBy(empire_id)] if not other_fleets_here: return target_id = self.target.id if self.target else None main_fleet_role = foAI.foAIstate.get_fleet_role(fleet_id) for fid in other_fleets_here: fleet_role = foAI.foAIstate.get_fleet_role(fid) if fleet_role not in COMPATIBLE_ROLES_MAP[main_fleet_role]: continue fleet = universe.getFleet(fid) if not fleet or fleet.systemID != system_id or len(fleet.shipIDs) == 0: continue if not (fleet.speed > 0 or main_fleet.speed == 0): # TODO(Cjkjvfnby) Check this condition continue fleet_mission = foAI.foAIstate.get_fleet_mission(fid) do_merge = False need_left = 0 if (main_fleet_role == MissionType.ORBITAL_DEFENSE) or (fleet_role == MissionType.ORBITAL_DEFENSE): if main_fleet_role == fleet_role: do_merge = True elif (main_fleet_role == MissionType.ORBITAL_INVASION) or (fleet_role == MissionType.ORBITAL_INVASION): if main_fleet_role == fleet_role: do_merge = False # TODO: could allow merger if both orb invaders and both same target elif not fleet_mission and (main_fleet.speed > 0) and (fleet.speed > 0): do_merge = True else: if not self.target and (main_fleet.speed > 0 or fleet.speed == 0): do_merge = True else: target = fleet_mission.target.id if fleet_mission.target else None if target == target_id: print "Military fleet %d has same target as %s fleet %d. Merging." % (fid, fleet_role, fleet_id) do_merge = True # TODO: should probably ensure that fleetA has aggression on now elif main_fleet.speed > 0: neighbors = foAI.foAIstate.systemStatus.get(system_id, {}).get('neighbors', []) if target == system_id and target_id in neighbors and self.type == MissionType.SECURE: # consider 'borrowing' for work in neighbor system # TODO check condition need_left = 1.5 * sum(foAI.foAIstate.systemStatus.get(nid, {}).get('fleetThreat', 0) for nid in neighbors if nid != target_id) fleet_rating = CombatRatingsAI.get_fleet_rating(fid) if need_left < fleet_rating: do_merge = True if do_merge: FleetUtilsAI.merge_fleet_a_into_b(fid, fleet_id, need_left, context="Order %s of mission %s" % (context, self)) return
def update_system_status(self): print 10 * "=", "Updating System Threats", 10 * "=" universe = fo.getUniverse() empire = fo.getEmpire() empire_id = fo.empireID() destroyed_object_ids = universe.destroyedObjectIDs(empire_id) supply_unobstructed_systems = set(empire.supplyUnobstructedSystems) min_hidden_attack = 4 min_hidden_health = 8 system_id_list = universe.systemIDs # will normally look at this, the list of all known systems # for use in debugging verbose = False # assess enemy fleets that may have been momentarily visible cur_e_fighters = { CombatRatingsAI.default_ship_stats().get_stats(hashable=True): [0] } # start with a dummy entry old_e_fighters = { CombatRatingsAI.default_ship_stats().get_stats(hashable=True): [0] } # start with a dummy entry enemy_fleet_ids = [] enemies_by_system = {} my_fleets_by_system = {} fleet_spot_position = {} saw_enemies_at_system = {} my_milship_rating = MilitaryAI.cur_best_mil_ship_rating() current_turn = fo.currentTurn() for fleet_id in universe.fleetIDs: fleet = universe.getFleet(fleet_id) if fleet is None: continue if not fleet.empty: # TODO: check if currently in system and blockaded before accepting destination as location this_system_id = (fleet.nextSystemID != INVALID_ID and fleet.nextSystemID) or fleet.systemID if fleet.ownedBy(empire_id): if fleet_id not in destroyed_object_ids: my_fleets_by_system.setdefault(this_system_id, []).append(fleet_id) fleet_spot_position.setdefault(fleet.systemID, []).append(fleet_id) else: dead_fleet = fleet_id in destroyed_object_ids if not fleet.ownedBy(-1) and (fleet.hasArmedShips or fleet.hasFighterShips): ship_stats = CombatRatingsAI.FleetCombatStats( fleet_id).get_ship_stats(hashable=True) e_f_dict = [ cur_e_fighters, old_e_fighters ][dead_fleet] # track old/dead enemy fighters for rating assessments in case not enough current info for stats in ship_stats: attacks = stats[0] if attacks: e_f_dict.setdefault(stats, [0])[0] += 1 partial_vis_turn = universe.getVisibilityTurnsMap( fleet_id, empire_id).get(fo.visibility.partial, -9999) if not dead_fleet: # TODO: consider checking death of individual ships. If ships had been moved from this fleet # into another fleet, we might have witnessed their death in that other fleet but if this fleet # had not been seen since before that transfer then the ships might also still be listed here. sys_status = self.systemStatus.setdefault( this_system_id, {}) sys_status['enemy_ship_count'] = sys_status.get( 'enemy_ship_count', 0) + len(fleet.shipIDs) if partial_vis_turn >= current_turn - 1: # only interested in immediately recent data saw_enemies_at_system[fleet.systemID] = True enemy_fleet_ids.append(fleet_id) enemies_by_system.setdefault(this_system_id, []).append(fleet_id) if not fleet.ownedBy(-1): self.misc.setdefault('enemies_sighted', {}).setdefault( current_turn, []).append(fleet_id) rating = CombatRatingsAI.get_fleet_rating( fleet_id, enemy_stats=CombatRatingsAI. get_empire_standard_fighter()) if rating > 0.25 * my_milship_rating: self.misc.setdefault( 'dangerous_enemies_sighted', {}).setdefault(current_turn, []).append(fleet_id) e_f_dict = [cur_e_fighters, old_e_fighters][len(cur_e_fighters) == 1] std_fighter = sorted([(v, k) for k, v in e_f_dict.items()])[-1][1] self.__empire_standard_enemy = std_fighter self.empire_standard_enemy_rating = self.get_standard_enemy( ).get_rating() # TODO: If no current information available, rate against own fighters # assess fleet and planet threats & my local fleets for sys_id in system_id_list: sys_status = self.systemStatus.setdefault(sys_id, {}) system = universe.getSystem(sys_id) if verbose: print "AIState threat evaluation for %s" % system # update fleets sys_status['myfleets'] = my_fleets_by_system.get(sys_id, []) sys_status['myFleetsAccessible'] = fleet_spot_position.get( sys_id, []) local_enemy_fleet_ids = enemies_by_system.get(sys_id, []) sys_status['localEnemyFleetIDs'] = local_enemy_fleet_ids if system: sys_status['name'] = system.name for fid in system.fleetIDs: if fid in destroyed_object_ids: # TODO: double check are these checks/deletes necessary? self.delete_fleet_info( fid) # this is safe even if fleet wasn't mine continue fleet = universe.getFleet(fid) if not fleet or fleet.empty: self.delete_fleet_info( fid) # this is safe even if fleet wasn't mine continue # update threats sys_vis_dict = universe.getVisibilityTurnsMap( sys_id, fo.empireID()) partial_vis_turn = sys_vis_dict.get(fo.visibility.partial, -9999) mob_ratings = [] # for mobile unowned monster fleets lost_fleet_rating = 0 enemy_ratings = [] monster_ratings = [] mobile_fleets = [] for fid in local_enemy_fleet_ids: fleet = universe.getFleet(fid) if not fleet: continue fleet_rating = CombatRatingsAI.get_fleet_rating( fid, enemy_stats=CombatRatingsAI.get_empire_standard_fighter()) if fleet.speed == 0: monster_ratings.append(fleet_rating) if verbose: print "\t immobile enemy fleet %s has rating %.1f" % ( fleet, fleet_rating) else: if verbose: print "\t mobile enemy fleet %s has rating %.1f" % ( fleet, fleet_rating) mobile_fleets.append(fid) if fleet.unowned: mob_ratings.append(fleet_rating) else: enemy_ratings.append(fleet_rating) enemy_rating = CombatRatingsAI.combine_ratings_list(enemy_ratings) monster_rating = CombatRatingsAI.combine_ratings_list( monster_ratings) mob_rating = CombatRatingsAI.combine_ratings_list(mob_ratings) if fleetsLostBySystem.get(sys_id, []): lost_fleet_rating = CombatRatingsAI.combine_ratings_list( fleetsLostBySystem[sys_id]) if not system or partial_vis_turn == -9999: # under current visibility rules should not be possible to have any losses or other info here, but just in case... if verbose: print "Have never had partial vis for system %d ( %s ) -- basing threat assessment on old info and lost ships" % ( sys_id, sys_status.get('name', "name unknown")) sys_status.setdefault('local_fleet_threats', set()) sys_status['planetThreat'] = 0 sys_status['fleetThreat'] = int( max( CombatRatingsAI.combine_ratings( enemy_rating, mob_rating), 0.98 * sys_status.get('fleetThreat', 0), 1.1 * lost_fleet_rating - monster_rating)) sys_status['monsterThreat'] = int( max(monster_rating, 0.98 * sys_status.get('monsterThreat', 0), 1.1 * lost_fleet_rating - enemy_rating - mob_rating)) sys_status['enemy_threat'] = int( max(enemy_rating, 0.98 * sys_status.get('enemy_threat', 0), 1.1 * lost_fleet_rating - monster_rating - mob_rating)) sys_status['mydefenses'] = { 'overall': 0, 'attack': 0, 'health': 0 } sys_status['totalThreat'] = sys_status['fleetThreat'] sys_status['regional_fleet_threats'] = sys_status[ 'local_fleet_threats'].copy() continue # have either stale or current info pattack = 0 phealth = 0 mypattack, myphealth = 0, 0 for pid in system.planetIDs: prating = self.assess_planet_threat(pid, sighting_age=current_turn - partial_vis_turn) planet = universe.getPlanet(pid) if not planet: continue if planet.owner == self.empireID: # TODO: check for diplomatic status mypattack += prating['attack'] myphealth += prating['health'] else: if [ special for special in planet.specials if "_NEST_" in special ]: sys_status['nest_threat'] = 100 pattack += prating['attack'] phealth += prating['health'] sys_status['planetThreat'] = pattack * phealth sys_status['mydefenses'] = { 'overall': mypattack * myphealth, 'attack': mypattack, 'health': myphealth } if max( sys_status.get('totalThreat', 0), pattack * phealth ) >= 0.6 * lost_fleet_rating: # previous threat assessment could account for losses, ignore the losses now lost_fleet_rating = 0 # TODO use sitrep combat info rather than estimating stealthed enemies by fleets lost to them # TODO also only consider past stealthed fleet threat to still be present if the system is still obstructed # TODO: track visibility across turns in order to distinguish the blip of visibility in (losing) combat, # which FO currently treats as being for the previous turn, partially superseding the previous visibility for that turn if not partial_vis_turn == current_turn: # (universe.getVisibility(sys_id, self.empire_id) >= fo.visibility.partial): sys_status.setdefault('local_fleet_threats', set()) sys_status['currently_visible'] = False # print "Stale visibility for system %d ( %s ) -- last seen %d, current Turn %d -- basing threat assessment on old info and lost ships"%(sys_id, sys_status.get('name', "name unknown"), partial_vis_turn, currentTurn) sys_status['fleetThreat'] = int( max( CombatRatingsAI.combine_ratings( enemy_rating, mob_rating), 0.98 * sys_status.get('fleetThreat', 0), 2.0 * lost_fleet_rating - max(sys_status.get('monsterThreat', 0), monster_rating))) sys_status['enemy_threat'] = int( max( enemy_rating, 0.98 * sys_status.get('enemy_threat', 0), 1.1 * lost_fleet_rating - max(sys_status.get('monsterThreat', 0), monster_rating))) sys_status['monsterThreat'] = int( max(monster_rating, 0.98 * sys_status.get('monsterThreat', 0))) # sys_status['totalThreat'] = ((pattack + enemy_attack + monster_attack) ** 0.8) * ((phealth + enemy_health + monster_health)** 0.6) # reevaluate this sys_status['totalThreat'] = max( CombatRatingsAI.combine_ratings_list([ enemy_rating, mob_rating, monster_rating, pattack * phealth ]), 2 * lost_fleet_rating, 0.98 * sys_status.get('totalThreat', 0)) else: # system considered visible #TODO: reevaluate as visibility rules change sys_status['currently_visible'] = True sys_status['local_fleet_threats'] = set(mobile_fleets) sys_status['fleetThreat'] = int( max( CombatRatingsAI.combine_ratings( enemy_rating, mob_rating), 2 * lost_fleet_rating - monster_rating)) # includes mobile monsters if verbose: print "enemy threat calc parts: enemy rating %.1f, lost fleet rating %.1f, monster_rating %.1f" % ( enemy_rating, lost_fleet_rating, monster_rating) sys_status['enemy_threat'] = int( max(enemy_rating, 2 * lost_fleet_rating - monster_rating)) # does NOT include mobile monsters sys_status['monsterThreat'] = monster_rating sys_status[ 'totalThreat'] = CombatRatingsAI.combine_ratings_list([ enemy_rating, mob_rating, monster_rating, pattack * phealth ]) sys_status['regional_fleet_threats'] = sys_status[ 'local_fleet_threats'].copy() sys_status['fleetThreat'] = max(sys_status['fleetThreat'], sys_status.get('nest_threat', 0)) sys_status['totalThreat'] = max(sys_status['totalThreat'], sys_status.get('nest_threat', 0)) if partial_vis_turn > 0 and sys_id not in supply_unobstructed_systems: # has been seen with Partial Vis, but is currently supply-blocked sys_status['fleetThreat'] = max( sys_status['fleetThreat'], min_hidden_attack * min_hidden_health) sys_status['totalThreat'] = max( sys_status['totalThreat'], ((pattack + min_hidden_attack)**0.8) * ((phealth + min_hidden_health)**0.6)) if verbose and sys_status['fleetThreat'] > 0: print "%s intermediate status: %s" % (system, sys_status) enemy_supply, enemy_near_supply = self.assess_enemy_supply( ) # TODO: assess change in enemy supply over time # assess secondary threats (threats of surrounding systems) and update my fleet rating for sys_id in system_id_list: sys_status = self.systemStatus[sys_id] sys_status['enemies_supplied'] = enemy_supply.get(sys_id, []) sys_status['enemies_nearly_supplied'] = enemy_near_supply.get( sys_id, []) my_ratings_list = [] my_ratings_against_planets_list = [] for fid in sys_status['myfleets']: this_rating = self.get_rating(fid, True, self.get_standard_enemy()) my_ratings_list.append(this_rating) my_ratings_against_planets_list.append( self.get_rating(fid, against_planets=True)) if sys_id != INVALID_ID: sys_status[ 'myFleetRating'] = CombatRatingsAI.combine_ratings_list( my_ratings_list) sys_status[ 'myFleetRatingVsPlanets'] = CombatRatingsAI.combine_ratings_list( my_ratings_against_planets_list) sys_status[ 'all_local_defenses'] = CombatRatingsAI.combine_ratings( sys_status['myFleetRating'], sys_status['mydefenses']['overall']) sys_status['neighbors'] = set( dict_from_map( universe.getSystemNeighborsMap(sys_id, self.empireID))) for sys_id in system_id_list: sys_status = self.systemStatus[sys_id] neighbors = sys_status.get('neighbors', set()) this_system = fo.getUniverse().getSystem(sys_id) if verbose: print "Regional Assessment for %s with local fleet threat %.1f" % ( this_system, sys_status.get('fleetThreat', 0)) jumps2 = set() jumps3 = set() jumps4 = set() for seta, setb in [(neighbors, jumps2), (jumps2, jumps3), (jumps3, jumps4)]: for sys2id in seta: setb.update( self.systemStatus.get(sys2id, {}).get('neighbors', set())) jump2ring = jumps2 - neighbors - {sys_id} jump3ring = jumps3 - jumps2 - neighbors - {sys_id} jump4ring = jumps4 - jumps3 - jumps2 - neighbors - {sys_id} sys_status['2jump_ring'] = jump2ring sys_status['3jump_ring'] = jump3ring sys_status['4jump_ring'] = jump4ring threat, max_threat, myrating, j1_threats = self.area_ratings( neighbors, ref_sys_name="neighbors %s" % this_system) if verbose else self.area_ratings(neighbors) sys_status['neighborThreat'] = threat sys_status['max_neighbor_threat'] = max_threat sys_status['my_neighbor_rating'] = myrating threat, max_threat, myrating, j2_threats = self.area_ratings( jump2ring, ref_sys_name="jump2 %s" % this_system) if verbose else self.area_ratings(jump2ring) sys_status['jump2_threat'] = threat sys_status['my_jump2_rating'] = myrating threat, max_threat, myrating, j3_threats = self.area_ratings( jump3ring) sys_status['jump3_threat'] = threat sys_status['my_jump3_rating'] = myrating threat_keys = ['fleetThreat', 'neighborThreat', 'jump2_threat' ] # for local system includes both enemies and mobs sys_status[ 'regional_threat'] = CombatRatingsAI.combine_ratings_list( map(lambda x: sys_status.get(x, 0), threat_keys)) # TODO: investigate cases where regional_threat has been nonzero but no regional_threat_fleets # (probably due to attenuating history of past threats) sys_status.setdefault('regional_fleet_threats', set()).update(j1_threats, j2_threats)
def get_standard_enemy(self): return CombatRatingsAI.ShipCombatStats( stats=self.__empire_standard_enemy)
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 _get_target_for_protection_mission(self): """Get a target for a PROTECT_REGION mission. 1) If primary target (system target of this mission) is under attack, move to primary target. 2) If neighbors of primary target have local enemy forces weaker than this fleet, may move to attack 3) If no neighboring fleets or strongest enemy force is too strong, move to defend primary target """ # TODO: Also check fleet rating vs planets in decision making below not only vs fleets universe = fo.getUniverse() primary_objective = self.target.id debug("Trying to find target for protection mission. Target: %s", self.target) immediate_threat = MilitaryAI.get_system_local_threat(primary_objective) if immediate_threat: debug("Immediate threat! Moving to primary mission target") return primary_objective else: debug("No immediate threats.") # Try to eliminate neighbouring fleets neighbors = universe.getImmediateNeighbors(primary_objective, fo.empireID()) threat_list = sorted(map( lambda x: (MilitaryAI.get_system_local_threat(x), x), neighbors ), reverse=True) if not threat_list: debug("No neighbors (?!). Moving to primary mission target") return primary_objective debug("%s", threat_list) top_threat, candidate_system = threat_list[0] if not top_threat: # TODO: Move into second ring but needs more careful evaluation # For now, consider staying at the current location if enemy # owns a planet here which we can bombard. current_system_id = self.fleet.get_current_system_id() if current_system_id in neighbors: system = universe.getSystem(current_system_id) if assertion_fails(system is not None): return primary_objective empire_id = fo.empireID() for planet_id in system.planetIDs: planet = universe.getPlanet(planet_id) if (planet and not planet.ownedBy(empire_id) and not planet.unowned): debug("Currently no neighboring threats. " "Staying for bombardment of planet %s", planet) self.clear_fleet_orders() self.set_target(MissionType.MILITARY, TargetSystem(current_system_id)) self.generate_fleet_orders() self.issue_fleet_orders() return INVALID_ID # TODO consider attacking neighboring, non-military fleets # - needs more careful evaluation against neighboring threats # empire_id = fo.empireID() # for sys_id in neighbors: # system = universe.getSystem(sys_id) # if assertion_fails(system is not None): # continue # local_fleets = system.fleetIDs # for fleet_id in local_fleets: # fleet = universe.getFleet(fleet_id) # if not fleet or fleet.ownedBy(empire_id): # continue # return sys_id debug("No neighboring threats. Moving to primary mission target") return primary_objective # TODO rate against threat in target system # TODO only engage if can reach in 1 turn or leaves sufficient defense behind fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id) debug("This fleet rating: %d. Enemy Rating: %d", fleet_rating, top_threat) safety_factor = get_aistate().character.military_safety_factor() if fleet_rating < safety_factor*top_threat: debug("Neighboring threat is too powerful. Moving to primary mission target") return primary_objective # do not engage! debug("Engaging neighboring threat: %d", candidate_system) return candidate_system
def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): """Get armed military fleets.""" global _military_allocations universe = fo.getUniverse() empire_id = fo.empireID() home_system_id = PlanetUtilsAI.get_capital_sys_id() all_military_fleet_ids = (mil_fleets_ids if mil_fleets_ids is not None else FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)) # Todo: This block had been originally added to address situations where fleet missions were not properly # terminating, leaving fleets stuck in stale deployments. Assess if this block is still needed at all; delete # if not, otherwise restructure the following code so that in event a reset is occurring greater priority is given # to providing military support to locations where a necessary Secure mission might have just been released (i.e., # at invasion and colony/outpost targets where the troopships and colony ships are on their way), or else allow # only a partial reset which does not reset Secure missions. enable_periodic_mission_reset = False if enable_periodic_mission_reset and try_reset and (fo.currentTurn() + empire_id) % 30 == 0 and thisround == "Main": debug("Resetting all Military missions as part of an automatic periodic reset to clear stale missions.") try_again(all_military_fleet_ids, try_reset=False, thisround=thisround + " Reset") return mil_fleets_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) mil_needing_repair_ids, mil_fleets_ids = avail_mil_needing_repair(mil_fleets_ids, split_ships=True) avail_mil_rating = combine_ratings_list(CombatRatingsAI.get_fleet_rating(x) for x in mil_fleets_ids) if not mil_fleets_ids: if "Main" in thisround: _military_allocations = [] return [] # for each system, get total rating of fleets assigned to it already_assigned_rating = {} already_assigned_rating_vs_planets = {} aistate = get_aistate() systems_status = aistate.systemStatus enemy_sup_factor = {} # enemy supply for sys_id in universe.systemIDs: already_assigned_rating[sys_id] = 0 already_assigned_rating_vs_planets[sys_id] = 0 enemy_sup_factor[sys_id] = min(2, len(systems_status.get(sys_id, {}).get('enemies_nearly_supplied', []))) for fleet_id in [fid for fid in all_military_fleet_ids if fid not in mil_fleets_ids]: ai_fleet_mission = aistate.get_fleet_mission(fleet_id) if not ai_fleet_mission.target: # shouldn't really be possible continue last_sys = ai_fleet_mission.target.get_system().id # will count this fleet as assigned to last system in target list # TODO last_sys or target sys? this_rating = CombatRatingsAI.get_fleet_rating(fleet_id) this_rating_vs_planets = CombatRatingsAI.get_fleet_rating_against_planets(fleet_id) already_assigned_rating[last_sys] = CombatRatingsAI.combine_ratings( already_assigned_rating.get(last_sys, 0), this_rating) already_assigned_rating_vs_planets[last_sys] = CombatRatingsAI.combine_ratings( already_assigned_rating_vs_planets.get(last_sys, 0), this_rating_vs_planets) for sys_id in universe.systemIDs: my_defense_rating = systems_status.get(sys_id, {}).get('mydefenses', {}).get('overall', 0) already_assigned_rating[sys_id] = CombatRatingsAI.combine_ratings(my_defense_rating, already_assigned_rating[sys_id]) if _verbose_mil_reporting and already_assigned_rating[sys_id]: debug("\t System %s already assigned rating %.1f" % ( universe.getSystem(sys_id), already_assigned_rating[sys_id])) # get systems to defend capital_id = PlanetUtilsAI.get_capital() if capital_id is not None: capital_planet = universe.getPlanet(capital_id) else: capital_planet = None # TODO: if no owned planets try to capture one! if capital_planet: capital_sys_id = capital_planet.systemID else: # should be rare, but so as to not break code below, pick a randomish mil-centroid system capital_sys_id = None # unless we can find one to use system_dict = {} for fleet_id in all_military_fleet_ids: status = aistate.fleetStatus.get(fleet_id, None) if status is not None: system_id = status['sysID'] if not list(universe.getSystem(system_id).planetIDs): continue system_dict[system_id] = system_dict.get(system_id, 0) + status.get('rating', 0) ranked_systems = sorted([(val, sys_id) for sys_id, val in system_dict.items()]) if ranked_systems: capital_sys_id = ranked_systems[-1][-1] else: try: capital_sys_id = next(iter(aistate.fleetStatus.items()))[1]['sysID'] except: # noqa: E722 pass num_targets = max(10, PriorityAI.allotted_outpost_targets) top_target_planets = ([pid for pid, pscore, trp in AIstate.invasionTargets[:PriorityAI.allotted_invasion_targets()] if pscore > InvasionAI.MIN_INVASION_SCORE] + [pid for pid, (pscore, spec) in list(aistate.colonisableOutpostIDs.items())[:num_targets] if pscore > InvasionAI.MIN_INVASION_SCORE] + [pid for pid, (pscore, spec) in list(aistate.colonisablePlanetIDs.items())[:num_targets] if pscore > InvasionAI.MIN_INVASION_SCORE]) top_target_planets.extend(aistate.qualifyingTroopBaseTargets.keys()) base_col_target_systems = PlanetUtilsAI.get_systems(top_target_planets) top_target_systems = [] for sys_id in AIstate.invasionTargetedSystemIDs + base_col_target_systems: if sys_id not in top_target_systems: if aistate.systemStatus[sys_id]['totalThreat'] > get_tot_mil_rating(): continue top_target_systems.append(sys_id) # doing this rather than set, to preserve order try: # capital defense allocation_helper = AllocationHelper(already_assigned_rating, already_assigned_rating_vs_planets, avail_mil_rating, try_reset) if capital_sys_id is not None: CapitalDefenseAllocator(capital_sys_id, allocation_helper).allocate() # defend other planets empire_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire(universe.planetIDs) empire_occupied_system_ids = list(set(PlanetUtilsAI.get_systems(empire_planet_ids)) - {capital_sys_id}) for sys_id in empire_occupied_system_ids: PlanetDefenseAllocator(sys_id, allocation_helper).allocate() # attack / protect high priority targets for sys_id in top_target_systems: TopTargetAllocator(sys_id, allocation_helper).allocate() # enemy planets other_targeted_system_ids = [sys_id for sys_id in set(PlanetUtilsAI.get_systems(AIstate.opponentPlanetIDs)) if sys_id not in top_target_systems] for sys_id in other_targeted_system_ids: TargetAllocator(sys_id, allocation_helper).allocate() # colony / outpost targets other_targeted_system_ids = [sys_id for sys_id in list(set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs)) if sys_id not in top_target_systems] for sys_id in other_targeted_system_ids: OutpostTargetAllocator(sys_id, allocation_helper).allocate() # TODO blockade enemy systems # interior systems targetable_ids = set(state.get_systems_by_supply_tier(0)) current_mil_systems = [sid for sid, _, _, _, _ in allocation_helper.allocations] interior_targets1 = targetable_ids.difference(current_mil_systems) interior_targets = [sid for sid in interior_targets1 if ( allocation_helper.threat_bias + systems_status.get(sid, {}).get('totalThreat', 0) > 0.8 * allocation_helper.already_assigned_rating[sid])] for sys_id in interior_targets: InteriorTargetsAllocator(sys_id, allocation_helper).allocate() # TODO Exploration targets # border protections visible_system_ids = aistate.visInteriorSystemIDs | aistate.visBorderSystemIDs accessible_system_ids = ([sys_id for sys_id in visible_system_ids if universe.systemsConnected(sys_id, home_system_id, empire_id)] if home_system_id != INVALID_ID else []) current_mil_systems = [sid for sid, alloc, rvp, take_any, _ in allocation_helper.allocations if alloc > 0] border_targets1 = [sid for sid in accessible_system_ids if sid not in current_mil_systems] border_targets = [sid for sid in border_targets1 if ( allocation_helper.threat_bias + systems_status.get(sid, {}).get('fleetThreat', 0) + systems_status.get(sid, {}).get( 'planetThreat', 0) > 0.8 * allocation_helper.already_assigned_rating[sid])] for sys_id in border_targets: BorderSecurityAllocator(sys_id, allocation_helper).allocate() except ReleaseMilitaryException: try_again(all_military_fleet_ids) return new_allocations = [] remaining_mil_rating = avail_mil_rating # for top categories assign max_alloc right away as available for cat in ['capitol', 'occupied', 'topTargets']: for sid, alloc, rvp, take_any, max_alloc in allocation_helper.allocation_by_groups.get(cat, []): if remaining_mil_rating <= 0: break this_alloc = min(remaining_mil_rating, max_alloc) new_allocations.append((sid, this_alloc, alloc, rvp, take_any)) remaining_mil_rating = rating_difference(remaining_mil_rating, this_alloc) base_allocs = set() # for lower priority categories, first assign base_alloc around to all, then top up as available for cat in ['otherTargets', 'accessibleTargets', 'exploreTargets']: for sid, alloc, rvp, take_any, max_alloc in allocation_helper.allocation_by_groups.get(cat, []): if remaining_mil_rating <= 0: break alloc = min(remaining_mil_rating, alloc) base_allocs.add(sid) remaining_mil_rating = rating_difference(remaining_mil_rating, alloc) for cat in ['otherTargets', 'accessibleTargets', 'exploreTargets']: for sid, alloc, rvp, take_any, max_alloc in allocation_helper.allocation_by_groups.get(cat, []): if sid not in base_allocs: break if remaining_mil_rating <= 0: new_allocations.append((sid, alloc, alloc, rvp, take_any)) else: local_max_avail = combine_ratings(remaining_mil_rating, alloc) new_rating = min(local_max_avail, max_alloc) new_allocations.append((sid, new_rating, alloc, rvp, take_any)) remaining_mil_rating = rating_difference(local_max_avail, new_rating) if "Main" in thisround: _military_allocations = new_allocations if _verbose_mil_reporting or "Main" in thisround: debug("------------------------------\nFinal %s Round Military Allocations: %s \n-----------------------" % (thisround, dict([(sid, alloc) for sid, alloc, _, _, _ in new_allocations]))) debug("(Apparently) remaining military rating: %.1f" % remaining_mil_rating) return new_allocations