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 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 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 _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 _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 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 __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 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_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 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 _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 assess_protection_focus(pinfo): """Return True if planet should use Protection Focus.""" this_planet = pinfo.planet aistate = get_aistate() sys_status = aistate.systemStatus.get(this_planet.systemID, {}) threat_from_supply = ( 0.25 * aistate.empire_standard_enemy_rating * min(2, len(sys_status.get('enemies_nearly_supplied', [])))) debug("%s has regional+supply threat of %.1f", this_planet, 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 == PROTECTION: debug( "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[PROTECTION]) 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 == PROTECTION: debug( "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 != PROTECTION 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 == PROTECTION else 0.5 cur_shield = this_planet.initialMeterValue(fo.meterType.shield) max_shield = this_planet.initialMeterValue(fo.meterType.maxShield) cur_troops = this_planet.initialMeterValue(fo.meterType.troops) max_troops = this_planet.initialMeterValue(fo.meterType.maxTroops) cur_defense = this_planet.initialMeterValue(fo.meterType.defense) max_defense = this_planet.initialMeterValue(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 != PROTECTION 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 != PROTECTION 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 == PROTECTION 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: use_protection = True reason = "D" 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 != PROTECTION 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 == PROTECTION: debug( "Advising %sProtection Focus (reason %s) for planet %s, with local_prod_diff of %.1f, comb. local" " defenses %.1f, local fleet rating %.1f and regional threat %.1f, threat sources: %s", ["dropping ", ""][use_protection], reason, this_planet, local_production_diff, combined_local_defenses, local_fleet_rating, regional_threat, sys_status['regional_fleet_threats']) return use_protection
def _calculate_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 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 assess_protection_focus(pid, info): """Return True if planet should use Protection Focus.""" this_planet = info.planet sys_status = foAI.foAIstate.systemStatus.get(this_planet.systemID, {}) threat_from_supply = (0.25 * foAI.foAIstate.empire_standard_enemy_rating * min(2, len(sys_status.get('enemies_nearly_supplied', [])))) print "Planet %s has regional+supply threat of %.1f" % ('P_%d<%s>' % (pid, this_planet.name), threat_from_supply) regional_threat = sys_status.get('regional_threat', 0) + threat_from_supply if not regional_threat: # no need for protection if info.current_focus == PRODUCTION: print "Advising dropping Protection Focus at %s due to no regional threat" % this_planet return False cur_prod_val = weighted_sum_output(info.current_output) target_prod_val = max(map(weighted_sum_output, [info.possible_output[INDUSTRY], info.possible_output[RESEARCH]])) prot_prod_val = weighted_sum_output(info.possible_output[PRODUCTION]) local_production_diff = 0.8 * cur_prod_val + 0.2 * target_prod_val - prot_prod_val fleet_threat = sys_status.get('fleetThreat', 0) # TODO: relax the below rejection once the overall determination of PFocus is better tuned if not fleet_threat and local_production_diff > 8: if info.current_focus == PRODUCTION: print "Advising dropping Protection Focus at %s due to excessive productivity loss" % this_planet return False local_p_defenses = sys_status.get('mydefenses', {}).get('overall', 0) # TODO have adjusted_p_defenses take other in-system planets into account adjusted_p_defenses = local_p_defenses * (1.0 if info.current_focus != PRODUCTION else 0.5) local_fleet_rating = sys_status.get('myFleetRating', 0) combined_local_defenses = sys_status.get('all_local_defenses', 0) my_neighbor_rating = sys_status.get('my_neighbor_rating', 0) neighbor_threat = sys_status.get('neighborThreat', 0) safety_factor = 1.2 if info.current_focus == PRODUCTION else 0.5 cur_shield = this_planet.currentMeterValue(fo.meterType.shield) max_shield = this_planet.currentMeterValue(fo.meterType.maxShield) cur_troops = this_planet.currentMeterValue(fo.meterType.troops) max_troops = this_planet.currentMeterValue(fo.meterType.maxTroops) cur_defense = this_planet.currentMeterValue(fo.meterType.defense) max_defense = this_planet.currentMeterValue(fo.meterType.maxDefense) def_meter_pairs = [(cur_troops, max_troops), (cur_shield, max_shield), (cur_defense, max_defense)] use_protection = True reason = "" if (fleet_threat and # i.e., an enemy is sitting on us (info.current_focus != PRODUCTION or # too late to start protection TODO: but maybe regen worth it # protection focus only useful here if it maintains an elevated level all([AIDependencies.PROT_FOCUS_MULTIPLIER * a <= b for a, b in def_meter_pairs]))): use_protection = False reason = "A" elif ((info.current_focus != PRODUCTION and cur_shield < max_shield - 2 and not tech_is_complete(AIDependencies.PLANET_BARRIER_I_TECH)) and (cur_defense < max_defense - 2 and not tech_is_complete(AIDependencies.DEFENSE_REGEN_1_TECH)) and (cur_troops < max_troops - 2)): use_protection = False reason = "B1" elif ((info.current_focus == PRODUCTION and cur_shield * AIDependencies.PROT_FOCUS_MULTIPLIER < max_shield - 2 and not tech_is_complete(AIDependencies.PLANET_BARRIER_I_TECH)) and (cur_defense * AIDependencies.PROT_FOCUS_MULTIPLIER < max_defense - 2 and not tech_is_complete(AIDependencies.DEFENSE_REGEN_1_TECH)) and (cur_troops * AIDependencies.PROT_FOCUS_MULTIPLIER < max_troops - 2)): use_protection = False reason = "B2" elif max(max_shield, max_troops, max_defense) < 3: # joke defenses, don't bother with protection focus use_protection = False reason = "C" elif regional_threat and local_production_diff <= 2.0: reason = "D" pass # i.e., use_protection = True elif safety_factor * regional_threat <= local_fleet_rating: use_protection = False reason = "E" elif (safety_factor * regional_threat <= combined_local_defenses and (info.current_focus != PRODUCTION or (0.5 * safety_factor * regional_threat <= local_fleet_rating and fleet_threat == 0 and neighbor_threat < combined_local_defenses and local_production_diff > 5))): use_protection = False reason = "F" elif (regional_threat <= 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 info.current_focus == PRODUCTION: print ("Advising %sProtection Focus (reason %s) for planet %s, with local_prod_diff of %.1f, comb. local" " defenses %.1f, local fleet rating %.1f and regional threat %.1f, threat sources: %s") % ( ["dropping ", ""][use_protection], reason, this_planet, local_production_diff, combined_local_defenses, local_fleet_rating, regional_threat, sys_status['regional_fleet_threats']) return use_protection
def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): """Get armed military fleets.""" global _military_allocations, totMilRating, num_milships 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") num_milships = sum(foAI.foAIstate.fleetStatus.get(fid, {}).get('nships', 0) for fid in all_military_fleet_ids) if "Main" in thisround: totMilRating = sum(CombatRatingsAI.get_fleet_rating(fid) for fid in all_military_fleet_ids) enemy_rating = foAI.foAIstate.empire_standard_enemy_rating 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 "Main" in thisround: print "==================================================" print "%s Round Available Military Rating: %d" % (thisround, avail_mil_rating) print "---------------------------------" remaining_mil_rating = avail_mil_rating allocations = [] allocation_groups = {} 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(sys_id, 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 if False: if fo.currentTurn() < 20: threat_bias = 0 elif fo.currentTurn() < 40: threat_bias = 10 elif fo.currentTurn() < 60: threat_bias = 80 elif fo.currentTurn() < 80: threat_bias = 200 else: threat_bias = 400 else: threat_bias = 0 safety_factor = get_safety_factor() 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'] > totMilRating: continue top_target_systems.append(sys_id) # doing this rather than set, to preserve order if _verbose_mil_reporting: print "----------------------------" # allocation format: ( sysID, newAllocation, takeAny, maxMultiplier ) # ================================ # --------Capital Threat ---------- if capital_sys_id is not None: capital_sys_status = systems_status[capital_sys_id] capital_threat = safety_factor*(2 * threat_bias + combine_ratings_list([capital_sys_status[thrt_key] for thrt_key in ['totalThreat', 'neighborThreat']])) capital_threat += max(0, enemy_sup_factor[sys_id]*enemy_rating - capital_sys_status.get('my_neighbor_rating', 0)) local_support = combine_ratings(already_assigned_rating[capital_sys_id], capital_sys_status['my_neighbor_rating']) base_needed_rating = rating_needed(capital_sys_status['regional_threat'], local_support) needed_rating = max(base_needed_rating, rating_needed(1.4 * capital_threat, already_assigned_rating[capital_sys_id])) max_alloc = max(rating_needed(1.5 * capital_sys_status['regional_threat'], already_assigned_rating[capital_sys_id]), rating_needed(2 * capital_threat, already_assigned_rating[capital_sys_id])) new_alloc = 0 if try_reset: if needed_rating > 0.5*avail_mil_rating: try_again(all_military_fleet_ids) return if needed_rating > 0: new_alloc = min(remaining_mil_rating, needed_rating) allocations.append((capital_sys_id, new_alloc, True, max_alloc)) allocation_groups.setdefault('capitol', []).append((capital_sys_id, new_alloc, True, max_alloc)) if _verbose_mil_reporting: report_format = ("\tAt Capital system %s, local threat %.1f, regional threat %.1f, local support %.1f, " "base_needed_rating %.1f, needed_rating %.1f, new allocation %.1f") print report_format % (universe.getSystem(capital_sys_id), capital_threat, capital_sys_status['regional_threat'], local_support, base_needed_rating, needed_rating, new_alloc) remaining_mil_rating -= new_alloc if "Main" in thisround or new_alloc > 0: if _verbose_mil_reporting: print "Empire Capital System: (%d) %s -- threat : %d, military allocation: existing: %d ; new: %d" % (capital_sys_id, universe.getSystem(capital_sys_id).name, capital_threat, already_assigned_rating[capital_sys_id], new_alloc) print "-----------------" # ================================ # --------Empire Occupied Systems ---------- 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}) if "Main" in thisround: if _verbose_mil_reporting: print "Empire-Occupied Systems: %s" % (["| %d %s |" % (eo_sys_id, universe.getSystem(eo_sys_id).name) for eo_sys_id in empire_occupied_system_ids]) print "-----------------" new_alloc = 0 if empire_occupied_system_ids: oc_sys_tot_threat_v1 = [(o_s_id, threat_bias + safety_factor*combine_ratings_list( [systems_status.get(o_s_id, {}).get(thrt_key, 0) for thrt_key in ['totalThreat', 'neighborThreat']])) for o_s_id in empire_occupied_system_ids] tot_oc_sys_threat = sum([thrt for _, thrt in oc_sys_tot_threat_v1]) tot_cur_alloc = sum([already_assigned_rating[sid] for sid, _ in oc_sys_tot_threat_v1]) # intentionally after tallies, but perhaps should be before oc_sys_tot_threat = [] threat_details = {} for sys_id, sys_threat in oc_sys_tot_threat_v1: j2_threat = systems_status.get(sys_id, {}).get('jump2_threat', 0) local_defenses = combine_ratings_list([systems_status.get(sys_id, {}).get('my_neighbor_rating', 0), already_assigned_rating[sys_id]]) threat_details[sys_id] = (sys_threat, enemy_sup_factor[sys_id] * 0.5 * enemy_rating, j2_threat, local_defenses) oc_sys_tot_threat.append((sys_id, sys_threat + max(0, enemy_sup_factor[sys_id] * 0.5 * enemy_rating + j2_threat - local_defenses ))) oc_sys_alloc = 0 for sid, thrt in oc_sys_tot_threat: cur_alloc = already_assigned_rating[sid] needed_rating = rating_needed(1.3 * thrt, cur_alloc) max_alloc = rating_needed(2*thrt, cur_alloc) if (needed_rating > 0.8 * remaining_mil_rating) and try_reset: try_again(all_military_fleet_ids) return this_alloc = 0 if needed_rating > 0 and remaining_mil_rating > 0: this_alloc = max(0, min(needed_rating, 0.5 * avail_mil_rating, remaining_mil_rating)) new_alloc += this_alloc allocations.append((sid, this_alloc, True, max_alloc)) allocation_groups.setdefault('occupied', []).append((sid, this_alloc, True, max_alloc)) remaining_mil_rating -= this_alloc oc_sys_alloc += this_alloc if "Main" in thisround or this_alloc > 0: if _verbose_mil_reporting: print "Provincial Occupied system %4d ( %10s ) has local threat %8d ; existing military allocation %d and new allocation %8d" % (sid, universe.getSystem(sid).name, thrt, cur_alloc, this_alloc) print "\t base threat was %.1f, supply_threat %.1f, jump2_threat %.1f, and local defenses %.1f" % ( threat_details[sid]) if "Main" in thisround or new_alloc > 0: if _verbose_mil_reporting: print "Provincial Empire-Occupied Sytems under total threat: %d -- total mil allocation: existing %d ; new: %d" % (tot_oc_sys_threat, tot_cur_alloc, oc_sys_alloc) print "-----------------" # ================================ # --------Top Targeted Systems ---------- # TODO: do native invasions highest priority other_targeted_system_ids = top_target_systems if "Main" in thisround: if _verbose_mil_reporting: print "Top Colony and Invasion Targeted Systems : %s" % (["| %d %s |" % (sys_id, universe.getSystem(sys_id).name) for sys_id in other_targeted_system_ids]) print "-----------------" new_alloc = 0 if other_targeted_system_ids: ot_sys_alloc = 0 ot_sys_threat = [(o_s_id, threat_bias + safety_factor * combine_ratings_list([ systems_status.get(o_s_id, {}).get('totalThreat', 0), 0.75 * systems_status.get(o_s_id, {}).get('neighborThreat', 0), 0.5 * systems_status.get(o_s_id, {}).get('jump2_threat', 0)])) for o_s_id in other_targeted_system_ids] totot_sys_threat = sum([thrt for sid, thrt in ot_sys_threat]) tot_cur_alloc = sum([already_assigned_rating[sid] for sid, thrt in ot_sys_threat]) # intentionally after tallies, but perhaps should be before ot_sys_threat = [(sys_id, sys_threat + enemy_sup_factor[sys_id] * 0.5 * enemy_rating) for sys_id, sys_threat in ot_sys_threat] for sid, thrt in ot_sys_threat: cur_alloc = already_assigned_rating[sid] needed_rating = rating_needed(1.4*thrt, cur_alloc) this_alloc = 0 # only record more allocation for this invasion if we already started or have enough rating available take_any = already_assigned_rating[sid] > 0 if needed_rating > 0 and (remaining_mil_rating > needed_rating or take_any): this_alloc = max(0, min(needed_rating, remaining_mil_rating)) max_alloc = rating_needed(3 * thrt, cur_alloc) new_alloc += this_alloc allocations.append((sid, this_alloc, take_any, max_alloc)) allocation_groups.setdefault('topTargets', []).append((sid, this_alloc, take_any, max_alloc)) remaining_mil_rating -= this_alloc ot_sys_alloc += this_alloc if "Main" in thisround or this_alloc > 0: if _verbose_mil_reporting: print "Targeted system %4d ( %10s ) has local threat %8d ; existing military allocation %d and new allocation %8d" % (sid, universe.getSystem(sid).name, thrt, cur_alloc, this_alloc) if "Main" in thisround or new_alloc > 0: if _verbose_mil_reporting: print "-----------------" print "Top Colony and Invasion Targeted Systems under total threat: %d -- total mil allocation-- existing: %d ; new: %d" % (totot_sys_threat, tot_cur_alloc, ot_sys_alloc) print "-----------------" # ================================ # --------Targeted Systems ---------- # TODO: do native invasions highest priority other_targeted_system_ids = [sys_id for sys_id in set(PlanetUtilsAI.get_systems(AIstate.opponentPlanetIDs)) if sys_id not in top_target_systems] if "Main" in thisround: if _verbose_mil_reporting: print "Other Invasion Targeted Systems : %s" % (["| %d %s |" % (sys_id, universe.getSystem(sys_id).name) for sys_id in other_targeted_system_ids]) print "-----------------" # for these, calc local threat only, no neighbor threat, but use a multiplier for fleet safety new_alloc = 0 if other_targeted_system_ids: ot_sys_alloc = 0 ot_sys_threat = [(o_s_id, threat_bias + safety_factor*combine_ratings_list([ systems_status.get(o_s_id, {}).get('totalThreat', 0), 0.75*systems_status.get(o_s_id, {}).get('neighborThreat', 0), 0.5*systems_status.get(o_s_id, {}).get('jump2_threat', 0)])) for o_s_id in other_targeted_system_ids] totot_sys_threat = sum([thrt for sid, thrt in ot_sys_threat]) tot_cur_alloc = sum([already_assigned_rating[sid] for sid, thrt in ot_sys_threat]) # intentionally after tallies, but perhaps should be before ot_sys_threat = [(sys_id, sys_threat + enemy_sup_factor[sys_id] * 0.5 * enemy_rating) for sys_id, sys_threat in ot_sys_threat] for sid, thrt in ot_sys_threat: cur_alloc = already_assigned_rating[sid] needed_rating = rating_needed(1.4 * thrt, cur_alloc) this_alloc = 0 # only record more allocation for this invasion if we already started or have enough rating available take_any = already_assigned_rating[sid] > 0 if needed_rating > 0 and (remaining_mil_rating > needed_rating or take_any): this_alloc = max(0, min(needed_rating, remaining_mil_rating)) new_alloc += this_alloc max_alloc = rating_needed(2 * thrt, cur_alloc) allocations.append((sid, this_alloc, take_any, max_alloc)) allocation_groups.setdefault('otherTargets', []).append((sid, this_alloc, take_any, max_alloc)) remaining_mil_rating -= this_alloc ot_sys_alloc += this_alloc if "Main" in thisround or this_alloc > 0: if _verbose_mil_reporting: print "Targeted system %4d ( %10s ) has local threat %8d ; existing military allocation %d and new allocation %8d" % (sid, universe.getSystem(sid).name, thrt, cur_alloc, this_alloc) if "Main" in thisround or new_alloc > 0: if _verbose_mil_reporting: print "-----------------" print "Invasion Targeted Systems under total threat: %d -- total mil allocation-- existing: %d ; new: %d" % (totot_sys_threat, tot_cur_alloc, ot_sys_alloc) print "-----------------" other_targeted_system_ids = [sys_id for sys_id in list(set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs)) if sys_id not in top_target_systems] if "Main" in thisround: if _verbose_mil_reporting: print "Other Targeted Systems : %s" % (["| %d %s |" % (sys_id, universe.getSystem(sys_id).name) for sys_id in other_targeted_system_ids]) print "-----------------" if other_targeted_system_ids: ot_sys_alloc = 0 ot_sys_threat = [(o_s_id, threat_bias + safety_factor*combine_ratings_list([ systems_status.get(o_s_id, {}).get('totalThreat', 0), 0.75*systems_status.get(o_s_id, {}).get('neighborThreat', 0), 0.5*systems_status.get(o_s_id, {}).get('jump2_threat', 0)])) for o_s_id in other_targeted_system_ids] totot_sys_threat = sum([thrt for sid, thrt in ot_sys_threat]) # intentionally after tallies, but perhaps should be before ot_sys_threat = [(sys_id, sys_threat + enemy_sup_factor[sys_id] * 0.5 * enemy_rating) for sys_id, sys_threat in ot_sys_threat] tot_cur_alloc = sum([already_assigned_rating[sid] for sid, thrt in ot_sys_threat]) new_alloc = 0 for sid, thrt in ot_sys_threat: cur_alloc = already_assigned_rating[sid] needed_rating = rating_needed(1.4 * thrt, cur_alloc) this_alloc = 0 # only record more allocation for this invasion if we already started or have enough rating available take_any = already_assigned_rating[sid] > 0 if needed_rating > 0 and (remaining_mil_rating > needed_rating or take_any): this_alloc = max(0, min(needed_rating, remaining_mil_rating)) new_alloc += this_alloc max_alloc = rating_needed(3*thrt, cur_alloc) allocations.append((sid, this_alloc, take_any, max_alloc)) allocation_groups.setdefault('otherTargets', []).append((sid, this_alloc, take_any, max_alloc)) remaining_mil_rating -= this_alloc ot_sys_alloc += this_alloc if "Main" in thisround or this_alloc > 0: if _verbose_mil_reporting: print "Targeted system %4d ( %10s ) has local threat %8d ; existing military allocation %d and new allocation %8d" % (sid, universe.getSystem(sid).name, thrt, cur_alloc, this_alloc) if "Main" in thisround or new_alloc > 0: if _verbose_mil_reporting: print "-----------------" print "Other Targeted Systems under total threat: %d -- total mil allocation-- existing: %d ; new: %d" % (totot_sys_threat, tot_cur_alloc, ot_sys_alloc) print "-----------------" # TODO The entire code block below is effectively not used as AIstate.opponentSystemIDs is never filled... other_targeted_system_ids = [] # targetable_ids = ColonisationAI.annexableSystemIDs.union(empire.fleetSupplyableSystemIDs) targetable_ids = set(ColonisationAI.systems_by_supply_tier.get(0, []) + ColonisationAI.systems_by_supply_tier.get(1, [])) for sys_id in AIstate.opponentSystemIDs: if sys_id in targetable_ids: other_targeted_system_ids.append(sys_id) else: for nID in universe.getImmediateNeighbors(sys_id, empire_id): if nID in targetable_ids: other_targeted_system_ids.append(sys_id) break if "Main" in thisround: if _verbose_mil_reporting: print "Blockade Targeted Systems : %s" % (["| %d %s |" % (sys_id, universe.getSystem(sys_id).name) for sys_id in other_targeted_system_ids]) print "-----------------" if other_targeted_system_ids: ot_sys_alloc = 0 ot_sys_threat = [(o_s_id, threat_bias + safety_factor*combine_ratings_list([ systems_status.get(o_s_id, {}).get('totalThreat', 0), 0.75*systems_status.get(o_s_id, {}).get('neighborThreat', 0), 0.5*systems_status.get(o_s_id, {}).get('jump2_threat', 0)])) for o_s_id in other_targeted_system_ids] totot_sys_threat = sum([thrt for sid, thrt in ot_sys_threat]) tot_cur_alloc = sum([already_assigned_rating[sid] for sid, thrt in ot_sys_threat]) # intentionally after tallies, but perhaps should be before # this supply threat calc intentionally uses a lower multiplier 0.25 ot_sys_threat = [(sys_id, sys_threat + enemy_sup_factor[sys_id] * 0.25 * enemy_rating) for sys_id, sys_threat in ot_sys_threat] new_alloc = 0 for sid, thrt in ot_sys_threat: cur_alloc = already_assigned_rating[sid] needed_rating = rating_needed(1.4*thrt, cur_alloc) this_alloc = 0 # only record more allocation for this invasion if we already started or have enough rating available take_any = already_assigned_rating[sid] > 0 if needed_rating > 0 and (remaining_mil_rating > needed_rating or take_any): this_alloc = max(0, min(needed_rating, remaining_mil_rating)) new_alloc += this_alloc max_alloc = 1.5 * this_alloc allocations.append((sid, this_alloc, take_any, max_alloc)) allocation_groups.setdefault('otherTargets', []).append((sid, this_alloc, take_any, max_alloc)) remaining_mil_rating -= this_alloc ot_sys_alloc += this_alloc if "Main" in thisround or this_alloc > 0: if _verbose_mil_reporting: print "Blockade Targeted system %4d ( %10s ) has local threat %8d ; existing military allocation %d and new allocation %8d" % (sid, universe.getSystem(sid).name, thrt, cur_alloc, this_alloc) if "Main" in thisround or new_alloc > 0: if _verbose_mil_reporting: print "-----------------" print "Blockade Targeted Systems under total threat: %d -- total mil allocation-- existing: %d ; new: %d" % (totot_sys_threat, tot_cur_alloc, ot_sys_alloc) print "-----------------" current_mil_systems = [sid for sid, alloc, take_any, mm in allocations] interior_targets1 = targetable_ids.difference(current_mil_systems) interior_targets = [sid for sid in interior_targets1 if ( threat_bias + systems_status.get(sid, {}).get('totalThreat', 0) > 0.8 * already_assigned_rating[sid])] if "Main" in thisround: if _verbose_mil_reporting: print print "Other Empire-Proximal Systems : %s" % (["| %d %s |" % (sys_id, universe.getSystem(sys_id).name) for sys_id in interior_targets1]) print "-----------------" # for these, calc local threat only, no neighbor threat, but use a multiplier for fleet safety new_alloc = 0 if interior_targets: ot_sys_alloc = 0 ot_sys_threat = [(o_s_id, threat_bias + safety_factor*(systems_status.get(o_s_id, {}).get('totalThreat', 0))) for o_s_id in interior_targets] totot_sys_threat = sum([thrt for sid, thrt in ot_sys_threat]) tot_cur_alloc = sum([already_assigned_rating[sid] for sid, thrt in ot_sys_threat]) # do not add enemy supply threat here for sid, thrt in ot_sys_threat: cur_alloc = already_assigned_rating[sid] needed_rating = rating_needed(1.5 * thrt, cur_alloc) this_alloc = 0 # only record more allocation for this invasion if we already started or have enough rating available take_any = already_assigned_rating[sid] > 0 if needed_rating > 0 and (remaining_mil_rating > needed_rating or take_any): this_alloc = max(0, min(needed_rating, remaining_mil_rating)) new_alloc += this_alloc max_alloc = 3 * this_alloc allocations.append((sid, this_alloc, take_any, max_alloc)) allocation_groups.setdefault('otherTargets', []).append((sid, this_alloc, take_any, max_alloc)) remaining_mil_rating -= this_alloc ot_sys_alloc += this_alloc if "Main" in thisround or this_alloc > 0: if _verbose_mil_reporting: print "Other interior system %4d ( %10s ) has local threat %8d ; existing military allocation %d and new allocation %8d" % (sid, universe.getSystem(sid).name, thrt, cur_alloc, this_alloc) if "Main" in thisround or new_alloc > 0: if _verbose_mil_reporting: print "-----------------" print "Other Interior Systems under total threat: %d -- total mil allocation-- existing: %d ; new: %d" % (totot_sys_threat, tot_cur_alloc, ot_sys_alloc) print "-----------------" elif "Main" in thisround: if _verbose_mil_reporting: print "-----------------" print "No Other Interior Systems with fleet threat " print "-----------------" monster_dens = [] # explo_target_ids, _ = ExplorationAI.get_current_exploration_info(verbose=False) explo_target_ids = [] if "Main" in thisround: if _verbose_mil_reporting: print print "Exploration-targeted Systems: %s" % (["| %d %s |" % (sys_id, universe.getSystem(sys_id).name) for sys_id in explo_target_ids]) print "-----------------" # for these, calc fleet threat only, no neighbor threat, but use a multiplier for fleet safety new_alloc = 0 if explo_target_ids: ot_sys_alloc = 0 ot_sys_threat = [(o_s_id, safety_factor*combine_ratings(systems_status.get(o_s_id, {}).get('fleetThreat', 0), systems_status.get(o_s_id, {}).get('monsterThreat', 0) + systems_status.get(o_s_id, {}).get('planetThreat', 0))) for o_s_id in explo_target_ids] totot_sys_threat = sum([thrt for sid, thrt in ot_sys_threat]) tot_cur_alloc = sum([0.8*already_assigned_rating[sid] for sid, thrt in ot_sys_threat]) # intentionally after tallies, but perhaps should be before # this supply threat calc intentionally uses a lower multiplier 0.25 ot_sys_threat = [(sys_id, sys_threat + enemy_sup_factor[sys_id] * 0.25 * enemy_rating) for sys_id, sys_threat in ot_sys_threat] for sid, thrt in ot_sys_threat: cur_alloc = already_assigned_rating[sid] needed_rating = rating_needed(1.4 * thrt, cur_alloc) this_alloc = 0 # only record more allocation for this invasion if we already started or have enough rating available take_any = False if needed_rating > 0 and (remaining_mil_rating > needed_rating or take_any): this_alloc = max(0, min(needed_rating, remaining_mil_rating)) new_alloc += this_alloc max_alloc = 2 * this_alloc allocations.append((sid, this_alloc, take_any, max_alloc)) allocation_groups.setdefault('exploreTargets', []).append((sid, this_alloc, take_any, max_alloc)) remaining_mil_rating -= this_alloc ot_sys_alloc += this_alloc if "Main" in thisround or this_alloc > 0: if _verbose_mil_reporting: print "Exploration-targeted %4d ( %10s ) has local threat %8d ; existing military allocation %d and new allocation %8d" % (sid, universe.getSystem(sid).name, thrt, cur_alloc, this_alloc) if "Main" in thisround or new_alloc > 0: if _verbose_mil_reporting: print "-----------------" print "Exploration-targeted s under total threat: %d -- total mil allocation-- existing: %d ; new: %d" % (totot_sys_threat, tot_cur_alloc, ot_sys_alloc) print "-----------------" 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, multiplier in 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 (threat_bias + systems_status.get(sid, {}).get('fleetThreat', 0) + systems_status.get(sid, {}).get('planetThreat', 0) > 0.8*already_assigned_rating[sid])] if "Main" in thisround: if _verbose_mil_reporting: print print "Empire-Accessible Systems not yet allocated military: %s" % (["| %d %s |" % (sys_id, universe.getSystem(sys_id) and universe.getSystem(sys_id).name) for sys_id in border_targets1]) print "-----------------" # for these, calc fleet threat only, no neighbor threat, but use a multiplier for fleet safety new_alloc = 0 if border_targets: ot_sys_alloc = 0 ot_sys_threat = [(o_s_id, threat_bias + safety_factor*combine_ratings(systems_status.get(o_s_id, {}).get('fleetThreat', 0), systems_status.get(o_s_id, {}).get('monsterThreat', 0) + systems_status.get(o_s_id, {}).get('planetThreat', 0))) for o_s_id in border_targets] totot_sys_threat = sum([thrt for sid, thrt in ot_sys_threat]) tot_cur_alloc = sum([0.8*already_assigned_rating[sid] for sid, thrt in ot_sys_threat]) # do not add enemy supply threat here for sid, thrt in ot_sys_threat: cur_alloc = already_assigned_rating[sid] needed_rating = rating_needed(1.2 * thrt, cur_alloc) this_alloc = 0 # only record more allocation for this invasion if we already started or have enough rating available take_any = False if needed_rating > 0 and (remaining_mil_rating > needed_rating or take_any): this_alloc = max(0, min(needed_rating, remaining_mil_rating)) new_alloc += this_alloc max_alloc = safety_factor*2*max(systems_status.get(sid, {}).get('totalThreat', 0), systems_status.get(sid, {}).get('neighborThreat', 0)) allocations.append((sid, this_alloc, take_any, max_alloc)) allocation_groups.setdefault('accessibleTargets', []).append((sid, this_alloc, take_any, max_alloc)) remaining_mil_rating -= this_alloc ot_sys_alloc += this_alloc if "Main" in thisround or this_alloc > 0: if _verbose_mil_reporting: print "Other Empire-Accessible system %4d ( %10s ) has local biased threat %8d ; existing military allocation %d and new allocation %8d" % (sid, universe.getSystem(sid).name, thrt, cur_alloc, this_alloc) if "Main" in thisround or new_alloc > 0: if _verbose_mil_reporting: print "-----------------" print "Other Empire-Accessible Systems under total biased threat: %d -- total mil allocation-- existing: %d ; new: %d" % (totot_sys_threat, tot_cur_alloc, ot_sys_alloc) print "-----------------" elif "Main" in thisround: if _verbose_mil_reporting: print "-----------------" print "No Other Empire-Accessible Systems with biased local threat " print "-----------------" # monster den treatment probably unnecessary now if "Main" in thisround: if _verbose_mil_reporting: print print "Big-Monster Dens: %s" % (["| %d %s |" % (sys_id, universe.getSystem(sys_id).name) for sys_id in monster_dens]) print "-----------------" # for these, calc fleet threat only, no neighbor threat, but use a multiplier for fleet safety new_alloc = 0 if monster_dens: ot_sys_alloc = 0 ot_sys_threat = [(o_s_id, safety_factor * combine_ratings_list([systems_status.get(o_s_id, {}).get('fleetThreat', 0), systems_status.get(o_s_id, {}).get('monsterThreat', 0), systems_status.get(o_s_id, {}).get('planetThreat', 0)])) for o_s_id in monster_dens] for sid, thrt in ot_sys_threat: cur_alloc = 0.8 * already_assigned_rating[sid] this_alloc = 0 if (thrt > cur_alloc) and remaining_mil_rating > 2 * thrt: this_alloc = int(0.99999 + (thrt - cur_alloc) * 1.5) new_alloc += this_alloc allocations.append((sid, this_alloc, False, 5)) remaining_mil_rating -= this_alloc ot_sys_alloc += this_alloc if "Main" in thisround or this_alloc > 0: if _verbose_mil_reporting: print "Monster Den %4d ( %10s ) has local threat %8d ; existing military allocation %d and new allocation %8d" % (sid, universe.getSystem(sid).name, thrt, cur_alloc, this_alloc) if "Main" in thisround or new_alloc > 0: if _verbose_mil_reporting: print "-----------------" 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, mm 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 already_assigned_rating if 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 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 fleet_list.append(fleet_id) fleet_pool_set.remove(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 [el.key() for el in universe.getSystemNeighborsMap(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 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) # TODO: Rate against specific enemies here 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 _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 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 main_fleet_mission = foAI.foAIstate.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 = 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 = foAI.foAIstate.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 = foAI.foAIstate.systemStatus.get(self.target.id, {}).get('myFleetRating', 0) my_other_fleet_rating_vs_planets = foAI.foAIstate.systemStatus.get(self.target.id, {}).get( 'myFleetRatingVsPlanets', 0) is_military = foAI.foAIstate.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: print ("\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: 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
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: 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 = 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.allotted_invasion_targets()] 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(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 = foAI.foAIstate.visInteriorSystemIDs | foAI.foAIstate.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 -= 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 return new_allocations
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 _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))