def __update_empire_standard_enemy(self): """Update the empire's standard enemy. The standard enemy is the enemy that is most often seen. """ # TODO: If no current information available, rate against own fighters universe = fo.getUniverse() empire_id = fo.empireID() # assess enemy fleets that may have been momentarily visible (start with dummy entries) dummy_stats = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) cur_e_fighters = Counter() # actual visible enemies old_e_fighters = Counter({dummy_stats: 0}) # destroyed enemies TODO: consider seen but out of sight enemies for fleet_id in universe.fleetIDs: fleet = universe.getFleet(fleet_id) if (not fleet or fleet.empty or fleet.ownedBy(empire_id) or fleet.unowned or not (fleet.hasArmedShips or fleet.hasFighterShips)): continue # track old/dead enemy fighters for rating assessments in case not enough current info ship_stats = CombatRatingsAI.FleetCombatStats(fleet_id).get_ship_stats(hashable=True) dead_fleet = fleet_id in universe.destroyedObjectIDs(empire_id) e_f_dict = old_e_fighters if dead_fleet else cur_e_fighters for stats in ship_stats: # log only ships that are armed if stats[0]: e_f_dict[stats] += 1 e_f_dict = cur_e_fighters or old_e_fighters self.__empire_standard_enemy = sorted([(v, k) for k, v in e_f_dict.items()])[-1][1] self.empire_standard_enemy_rating = self.get_standard_enemy().get_rating()
def __update_empire_standard_enemy(self): """Update the empire's standard enemy. The standard enemy is the enemy that is most often seen. """ # TODO: If no current information available, rate against own fighters universe = fo.getUniverse() empire_id = fo.empireID() # assess enemy fleets that may have been momentarily visible (start with dummy entries) dummy_stats = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) cur_e_fighters = Counter() # actual visible enemies old_e_fighters = Counter({dummy_stats: 0}) # destroyed enemies TODO: consider seen but out of sight enemies for fleet_id in universe.fleetIDs: fleet = universe.getFleet(fleet_id) if (not fleet or fleet.empty or fleet.ownedBy(empire_id) or fleet.unowned or not (fleet.hasArmedShips or fleet.hasFighterShips)): continue # track old/dead enemy fighters for rating assessments in case not enough current info ship_stats = CombatRatingsAI.FleetCombatStats(fleet_id).get_ship_stats(hashable=True) dead_fleet = fleet_id in universe.destroyedObjectIDs(empire_id) e_f_dict = old_e_fighters if dead_fleet else cur_e_fighters for stats in ship_stats: # log only ships that are armed if stats[0]: e_f_dict[stats] += 1 e_f_dict = cur_e_fighters or old_e_fighters self.__empire_standard_enemy = sorted([(v, k) for k, v in e_f_dict.items()])[-1][1] self.empire_standard_enemy_rating = self.get_standard_enemy().get_rating()
def __init__(self, aggression): # Do not allow to create AIstate instances with an invalid version number. if not hasattr(AIstate, 'version'): raise ConversionError("AIstate must have an integer version attribute for savegame compatibility") if not isinstance(AIstate.version, int): raise ConversionError("Version attribute of AIstate must be an integer!") if AIstate.version < 0: raise ConversionError("AIstate savegame compatibility version must be a positive integer!") # need to store the version explicitly as the class variable "version" is only stored in the # self.__class__.__dict__ while we only pickle the object (i.e. self.__dict__ ) self.version = AIstate.version # Debug info # unique id for game self.uid = self.generate_uid(first=True) # unique ids for turns. {turn: uid} self.turn_uids = {} self._aggression = aggression # 'global' (?) variables self.colonisablePlanetIDs = odict() self.colonisableOutpostIDs = odict() # self.__aiMissionsByFleetID = {} self.__shipRoleByDesignID = {} self.__fleetRoleByID = {} self.diplomatic_logs = {} self.__priorityByType = {} # initialize home system knowledge universe = fo.getUniverse() empire = fo.getEmpire() self.empireID = empire.empireID homeworld = universe.getPlanet(empire.capitalID) self.__origin_home_system_id = homeworld.systemID if homeworld else INVALID_ID self.visBorderSystemIDs = {self.__origin_home_system_id} self.visInteriorSystemIDs = set() self.exploredSystemIDs = set() self.unexploredSystemIDs = {self.__origin_home_system_id} self.fleetStatus = {} # keys: 'sysID', 'nships', 'rating' # systemStatus keys: 'name', 'neighbors' (sysIDs), '2jump_ring' (sysIDs), '3jump_ring', '4jump_ring', 'enemy_ship_count' # 'fleetThreat', 'planetThreat', 'monsterThreat' (specifically, immobile nonplanet threat), 'totalThreat', 'localEnemyFleetIDs', # 'neighborThreat', 'max_neighbor_threat', 'jump2_threat' (up to 2 jumps away), 'jump3_threat', 'jump4_threat', 'regional_threat' # 'myDefenses' (planet rating), 'myfleets', 'myFleetsAccessible'(not just next desitination), 'myFleetRating' # 'my_neighbor_rating' (up to 1 jump away), 'my_jump2_rating', 'my_jump3_rating', my_jump4_rating' # 'local_fleet_threats', 'regional_fleet_threats' <== these are only for mobile fleet threats self.systemStatus = {} self.needsEmergencyExploration = [] self.newlySplitFleets = {} self.militaryRating = 0 self.shipCount = 4 self.misc = {} self.qualifyingColonyBaseTargets = {} self.qualifyingOutpostBaseTargets = {} self.qualifyingTroopBaseTargets = {} self.__empire_standard_enemy = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) # TODO: track on a per-empire basis self.empire_standard_enemy_rating = 0 # TODO: track on a per-empire basis self.character = create_character(aggression, self.empireID)
def __init__(self, aggression): # Debug info # unique id for game self.uid = self.generate_uid(first=True) # unique ids for turns. {turn: uid} self.turn_uids = {} # 'global' (?) variables self.colonisablePlanetIDs = odict() self.colonisableOutpostIDs = odict() # self.__aiMissionsByFleetID = {} self.__shipRoleByDesignID = {} self.__fleetRoleByID = {} self.diplomatic_logs = {} self.__priorityByType = {} # initialize home system knowledge universe = fo.getUniverse() empire = fo.getEmpire() self.empireID = empire.empireID homeworld = universe.getPlanet(empire.capitalID) if homeworld: self.__origin_home_system_id = homeworld.systemID else: self.__origin_home_system_id = INVALID_ID self.visBorderSystemIDs = {self.__origin_home_system_id: 1} self.visInteriorSystemIDs = {} self.exploredSystemIDs = {} self.unexploredSystemIDs = {self.__origin_home_system_id: 1} self.fleetStatus = {} # keys: 'sysID', 'nships', 'rating' # systemStatus keys: 'name', 'neighbors' (sysIDs), '2jump_ring' (sysIDs), '3jump_ring', '4jump_ring', 'enemy_ship_count' # 'fleetThreat', 'planetThreat', 'monsterThreat' (specifically, immobile nonplanet threat), 'totalThreat', 'localEnemyFleetIDs', # 'neighborThreat', 'max_neighbor_threat', 'jump2_threat' (up to 2 jumps away), 'jump3_threat', 'jump4_threat', 'regional_threat' # 'myDefenses' (planet rating), 'myfleets', 'myFleetsAccessible'(not just next desitination), 'myFleetRating' # 'my_neighbor_rating' (up to 1 jump away), 'my_jump2_rating', 'my_jump3_rating', my_jump4_rating' # 'local_fleet_threats', 'regional_fleet_threats' <== these are only for mobile fleet threats self.systemStatus = {} self.needsEmergencyExploration = [] self.newlySplitFleets = {} self.militaryRating = 0 self.shipCount = 4 self.misc = {} self.qualifyingColonyBaseTargets = {} self.qualifyingOutpostBaseTargets = {} self.qualifyingTroopBaseTargets = {} self.__empire_standard_enemy = CombatRatingsAI.default_ship_stats( ).get_stats(hashable=True) # TODO: track on a per-empire basis self.empire_standard_enemy_rating = 0 # TODO: track on a per-empire basis self.character = create_character(aggression, self.empireID)
def __init__(self, aggression): # Debug info # unique id for game self.uid = self.generate_uid(first=True) # unique ids for turns. {turn: uid} self.turn_uids = {} # 'global' (?) variables self.colonisablePlanetIDs = odict() self.colonisableOutpostIDs = odict() # self.__aiMissionsByFleetID = {} self.__shipRoleByDesignID = {} self.__fleetRoleByID = {} self.diplomatic_logs = {} self.__priorityByType = {} # initialize home system knowledge universe = fo.getUniverse() empire = fo.getEmpire() self.empireID = empire.empireID homeworld = universe.getPlanet(empire.capitalID) if homeworld: self.__origin_home_system_id = homeworld.systemID else: self.__origin_home_system_id = INVALID_ID self.visBorderSystemIDs = {self.__origin_home_system_id: 1} self.visInteriorSystemIDs = {} self.exploredSystemIDs = {} self.unexploredSystemIDs = {self.__origin_home_system_id: 1} self.fleetStatus = {} # keys: 'sysID', 'nships', 'rating' # systemStatus keys: 'name', 'neighbors' (sysIDs), '2jump_ring' (sysIDs), '3jump_ring', '4jump_ring' # 'fleetThreat', 'planetThreat', 'monsterThreat' (specifically, immobile nonplanet threat), 'totalThreat', 'localEnemyFleetIDs', # 'neighborThreat', 'max_neighbor_threat', 'jump2_threat' (up to 2 jumps away), 'jump3_threat', 'jump4_threat', 'regional_threat' # 'myDefenses' (planet rating), 'myfleets', 'myFleetsAccessible'(not just next desitination), 'myFleetRating' # 'my_neighbor_rating' (up to 1 jump away), 'my_jump2_rating', 'my_jump3_rating', my_jump4_rating' # 'local_fleet_threats', 'regional_fleet_threats' <== these are only for mobile fleet threats self.systemStatus = {} self.needsEmergencyExploration = [] self.newlySplitFleets = {} self.militaryRating = 0 self.shipCount = 4 self.misc = {} self.qualifyingColonyBaseTargets = {} self.qualifyingOutpostBaseTargets = {} self.qualifyingTroopBaseTargets = {} self.__empire_standard_enemy = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) # TODO: track on a per-empire basis self.empire_standard_enemy_rating = 0 # TODO: track on a per-empire basis self.character = create_character(aggression, self.empireID)
def 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 __init__(self, aggression): # Do not allow to create AIstate instances with an invalid version number. if not hasattr(AIstate, 'version'): raise ConversionError("AIstate must have an integer version attribute for savegame compatibility") if not isinstance(AIstate.version, int): raise ConversionError("Version attribute of AIstate must be an integer!") if AIstate.version < 0: raise ConversionError("AIstate savegame compatibility version must be a positive integer!") # need to store the version explicitly as the class variable "version" is only stored in the # self.__class__.__dict__ while we only pickle the object (i.e. self.__dict__ ) self.version = AIstate.version # Debug info # unique id for game self.uid = self.generate_uid(first=True) # unique ids for turns. {turn: uid} self.turn_uids = {} # see AIstate docstring re importance of int cast for aggression self._aggression = int(aggression) # 'global' (?) variables self.colonisablePlanetIDs = odict() self.colonisableOutpostIDs = odict() # self.__aiMissionsByFleetID = {} self.__shipRoleByDesignID = {} self.__fleetRoleByID = {} self.diplomatic_logs = {} self.__priorityByType = {} # initialize home system knowledge universe = fo.getUniverse() empire = fo.getEmpire() self.empireID = empire.empireID homeworld = universe.getPlanet(empire.capitalID) self.__origin_home_system_id = homeworld.systemID if homeworld else INVALID_ID self.visBorderSystemIDs = {self.__origin_home_system_id} self.visInteriorSystemIDs = set() self.exploredSystemIDs = set() self.unexploredSystemIDs = {self.__origin_home_system_id} self.fleetStatus = {} # keys: 'sysID', 'nships', 'rating' # systemStatus keys: # 'name', 'neighbors' (sysIDs), '2jump_ring' (sysIDs), '3jump_ring', '4jump_ring', 'enemy_ship_count', # 'fleetThreat', 'planetThreat', 'monsterThreat' (specifically, immobile nonplanet threat), 'totalThreat', # 'localEnemyFleetIDs', 'neighborThreat', 'max_neighbor_threat', 'jump2_threat' (up to 2 jumps away), # 'jump3_threat', 'jump4_threat', 'regional_threat', 'myDefenses' (planet rating), 'myfleets', # 'myFleetsAccessible'(not just next desitination), 'myFleetRating', 'my_neighbor_rating' (up to 1 jump away), # 'my_jump2_rating', 'my_jump3_rating', my_jump4_rating', 'local_fleet_threats', # 'regional_fleet_threats' <== these are only for mobile fleet threats self.systemStatus = {} self.needsEmergencyExploration = [] self.newlySplitFleets = {} self.militaryRating = 0 self.shipCount = 4 self.misc = {} # Keys: "enemies_sighted" (dict[turn: list[fleetIDs]]), # "observed_empires" (set[enemy empire IDs]), # "ReassignedFleetMissions" (list[FleetMissions]) self.orbital_colonization_manager = ColonisationAI.OrbitalColonizationManager() self.qualifyingTroopBaseTargets = {} # TODO: track on a per-empire basis self.__empire_standard_enemy = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) self.empire_standard_enemy_rating = 0 # TODO: track on a per-empire basis self.character = create_character(aggression, self.empireID) self.last_turn_played = 0
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 # for use in debugging verbose = False # assess enemy fleets that may have been momentarily visible # start with dummy entries cur_e_fighters = { CombatRatingsAI.default_ship_stats().get_stats(hashable=True): [0] } old_e_fighters = { CombatRatingsAI.default_ship_stats().get_stats(hashable=True): [0] } 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: 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 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 # this is a fleet not owned by us if not fleet.unowned and (fleet.hasArmedShips or fleet.hasFighterShips): ship_stats = CombatRatingsAI.FleetCombatStats( fleet_id).get_ship_stats(hashable=True) # track old/dead enemy fighters for rating assessments in case not enough current info e_f_dict = old_e_fighters if dead_fleet else cur_e_fighters for stats in ship_stats: # log only ships that are armed if stats[0]: e_f_dict.setdefault(stats, [0])[0] += 1 # 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) # TODO: If no current information available, rate against own fighters e_f_dict = cur_e_fighters if len( cur_e_fighters) > 1 else old_e_fighters self.__empire_standard_enemy = sorted([ (v, k) for k, v in e_f_dict.items() ])[-1][1] self.empire_standard_enemy_rating = self.get_standard_enemy( ).get_rating() # 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 # TODO: double check are these checks/deletes necessary? for fid in system.fleetIDs: fleet = universe.getFleet(fid) if not fleet or fleet.empty or fid in destroyed_object_ids: self.delete_fleet_info( fid) # this is safe even if fleet wasn't mine # 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, []) 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 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)