def handle_diplomatic_status_update(self, status_update): """Handle an update about the diplomatic status between players, which may or may not include this player.""" debug("Received diplomatic status update to %s about empire %s and empire %s" % ( status_update.status, status_update.empire1, status_update.empire2)) if status_update.empire2 == fo.empireID() and status_update.status == fo.diplomaticStatus.war: get_aistate().log_war_declaration(status_update.empire1, status_update.empire2)
def wrapper(): if get_aistate() is None: return func() else: cache = get_aistate().misc.setdefault('caches', {}).setdefault(func.__name__, {}) this_turn = fo.currentTurn() return cache[this_turn] if this_turn in cache else cache.setdefault(this_turn, func())
def generate_fleet_orders(self): """generates AIFleetOrders from fleets targets to accomplish""" universe = fo.getUniverse() fleet_id = self.fleet.id fleet = universe.getFleet(fleet_id) if (not fleet) or fleet.empty or (fleet_id in universe.destroyedObjectIDs(fo.empireID())): # fleet was probably merged into another or was destroyed get_aistate().delete_fleet_info(fleet_id) return # TODO: priority self.clear_fleet_orders() system_id = fleet.systemID start_sys_id = [fleet.nextSystemID, system_id][system_id >= 0] # if fleet doesn't have any mission, # then repair if needed or resupply if is current location not in supplyable system empire = fo.getEmpire() fleet_supplyable_system_ids = empire.fleetSupplyableSystemIDs # if (not self.hasAnyAIMissionTypes()): if not self.target and (system_id not in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs)): if self._need_repair(): repair_fleet_order = MoveUtilsAI.get_repair_fleet_order(self.fleet, start_sys_id) if repair_fleet_order and repair_fleet_order.is_valid(): self.orders.append(repair_fleet_order) cur_fighter_capacity, max_fighter_capacity = FleetUtilsAI.get_fighter_capacity_of_fleet(fleet_id) if (fleet.fuel < fleet.maxFuel or cur_fighter_capacity < max_fighter_capacity and self.get_location_target().id not in fleet_supplyable_system_ids): resupply_fleet_order = MoveUtilsAI.get_resupply_fleet_order(self.fleet, self.get_location_target()) if resupply_fleet_order.is_valid(): self.orders.append(resupply_fleet_order) return # no targets if self.target: # for some targets fleet has to visit systems and therefore fleet visit them system_to_visit = (self.target.get_system() if not self.type == MissionType.PROTECT_REGION else TargetSystem(self._get_target_for_protection_mission())) if not system_to_visit: return orders_to_visit_systems = MoveUtilsAI.create_move_orders_to_system(self.fleet, system_to_visit) # TODO: if fleet doesn't have enough fuel to get to final target, consider resetting Mission for fleet_order in orders_to_visit_systems: self.orders.append(fleet_order) # also generate appropriate final orders fleet_order = self._get_fleet_order_from_target(self.type, self.target) self.orders.append(fleet_order)
def can_travel_to_system(fleet_id, start, target, ensure_return=False): """ Return list systems to be visited. :param fleet_id: :type fleet_id: int :param start: :type start: target.TargetSystem :param target: :type target: target.TargetSystem :param ensure_return: :type ensure_return: bool :return: :rtype: list """ if start == target: return [TargetSystem(start.id)] debug("Requesting path for fleet %s from %s to %s" % (fo.getUniverse().getFleet(fleet_id), start, target)) target_distance_from_supply = -min(state.get_system_supply(target.id), 0) # low-aggression AIs may not travel far from supply if not get_aistate().character.may_travel_beyond_supply(target_distance_from_supply): debug("May not move %d out of supply" % target_distance_from_supply) return [] min_fuel_at_target = target_distance_from_supply if ensure_return else 0 path_info = pathfinding.find_path_with_resupply(start.id, target.id, fleet_id, minimum_fuel_at_target=min_fuel_at_target) if path_info is None: debug("Found no valid path.") return [] debug("Found valid path: %s" % str(path_info)) return [TargetSystem(sys_id) for sys_id in path_info.path]
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) aistate = get_aistate() if not fleet_a or fleet_a.empty or fleet_a_id in universe.destroyedObjectIDs(fo.empireID()): aistate.delete_fleet_info(fleet_a_id) aistate.update_fleet_rating(fleet_b_id)
def get_military_fleets_with_target_system(target_system_id): military_mission_types = [MissionType.MILITARY, MissionType.SECURE] found_fleets = [] for fleet_mission in get_aistate().get_fleet_missions_with_any_mission_types(military_mission_types): if fleet_mission.target and fleet_mission.target.id == target_system_id: found_fleets.append(fleet_mission.fleet.id) return found_fleets
def cur_best_mil_ship_rating(include_designs=False): """Find the best military ship we have available in this turn and return its rating. :param include_designs: toggles if available designs are considered or only existing ships :return: float: rating of the best ship """ current_turn = fo.currentTurn() if current_turn in _best_ship_rating_cache: best_rating = _best_ship_rating_cache[current_turn] if include_designs: best_design_rating = ProductionAI.cur_best_military_design_rating() best_rating = max(best_rating, best_design_rating) return best_rating best_rating = 0.001 universe = fo.getUniverse() aistate = get_aistate() for fleet_id in FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY): fleet = universe.getFleet(fleet_id) for ship_id in fleet.shipIDs: ship_rating = CombatRatingsAI.ShipCombatStats(ship_id).get_rating(enemy_stats=aistate.get_standard_enemy()) best_rating = max(best_rating, ship_rating) _best_ship_rating_cache[current_turn] = best_rating if include_designs: best_design_rating = ProductionAI.cur_best_military_design_rating() best_rating = max(best_rating, best_design_rating) return max(best_rating, 0.001)
def set_planet_resource_foci(): """set resource focus of planets """ Reporter.print_resource_ai_header() resource_timer.start("Priority") # TODO: take into acct splintering of resource groups aistate = get_aistate() production_priority = aistate.get_priority(PriorityType.RESOURCE_PRODUCTION) research_priority = aistate.get_priority(PriorityType.RESOURCE_RESEARCH) priority_ratio = float(research_priority) / (production_priority + 0.0001) focus_manager = PlanetFocusManager() reporter = Reporter(focus_manager) reporter.capture_section_info("Unfocusable") set_planet_growth_specials(focus_manager) set_planet_production_and_research_specials(focus_manager) reporter.capture_section_info("Specials") focus_manager.calculate_planet_infos(focus_manager.raw_planet_info.keys()) set_planet_protection_foci(focus_manager) reporter.capture_section_info("Protection") set_planet_industry_and_research_foci(focus_manager, priority_ratio) reporter.capture_section_info("Typical") reporter.print_table(priority_ratio) resource_timer.stop_print_and_clear() Reporter.print_resource_ai_footer()
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 issue_order(self): if not super(OrderMilitary, self).issue_order(): return False target_sys_id = self.target.id fleet = self.target.get_object() system_status = get_aistate().systemStatus.get(target_sys_id, {}) total_threat = sum(system_status.get(threat, 0) for threat in ('fleetThreat', 'planetThreat', 'monsterThreat')) combat_trigger = system_status.get('fleetThreat', 0) or system_status.get('monsterThreat', 0) if not combat_trigger and system_status.get('planetThreat', 0): universe = fo.getUniverse() system = universe.getSystem(target_sys_id) for planet_id in system.planetIDs: planet = universe.getPlanet(planet_id) if planet.ownedBy(fo.empireID()): # TODO: also exclude at-peace planets continue if planet.unowned and not EspionageAI.colony_detectable_by_empire(planet_id, empire=fo.empireID()): continue if sum([planet.currentMeterValue(meter_type) for meter_type in [fo.meterType.defense, fo.meterType.shield, fo.meterType.construction]]): combat_trigger = True break if not all(( fleet, fleet.systemID == target_sys_id, system_status.get('currently_visible', False), not (total_threat and combat_trigger) )): self.executed = False return True
def _need_repair(self, repair_limit=0.70): """Check if fleet needs to be repaired. If the fleet is already at a system where it can be repaired, stay there until fully repaired. Otherwise, repair if fleet health is below specified *repair_limit*. For military fleets, there is a special evaluation called, cf. *MilitaryAI.avail_mil_needing_repair()* :param repair_limit: percentage of health below which the fleet is sent to repair :type repair_limit: float :return: True if fleet needs repair :rtype: bool """ # TODO: More complex evaluation if fleet needs repair (consider self-repair, distance, threat, mission...) fleet_id = self.fleet.id # if we are already at a system where we can repair, make sure we use it... system = self.fleet.get_system() # TODO starlane obstruction is not considered in the next call nearest_dock = MoveUtilsAI.get_best_drydock_system_id(system.id, fleet_id) if nearest_dock == system.id: repair_limit = 0.99 # if combat fleet, use military repair check if get_aistate().get_fleet_role(fleet_id) in COMBAT_MISSION_TYPES: return fleet_id in MilitaryAI.avail_mil_needing_repair([fleet_id], on_mission=bool(self.orders), repair_limit=repair_limit)[0] # TODO: Allow to split fleet to send only damaged ships to repair ships_cur_health, ships_max_health = FleetUtilsAI.get_current_and_max_structure(fleet_id) return ships_cur_health < repair_limit * ships_max_health
def get_best_drydock_system_id(start_system_id, fleet_id): """ Get system_id of best drydock capable of repair, where best is nearest drydock that has a current and target happiness greater than the HAPPINESS_THRESHOLD with a path that is not blockaded or that the fleet can fight through to with acceptable losses. :param start_system_id: current location of fleet - used to find closest target :type start_system_id: int :param fleet_id: fleet that needs path to drydock :type: int :return: most suitable system id where the fleet should be repaired. :rtype: int """ if start_system_id == INVALID_ID: warn("get_best_drydock_system_id passed bad system id.") return None if fleet_id == INVALID_ID: warn("get_best_drydock_system_id passed bad fleet id.") return None universe = fo.getUniverse() start_system = TargetSystem(start_system_id) drydock_system_ids = set() for sys_id, pids in state.get_empire_drydocks().iteritems(): if sys_id == INVALID_ID: warn("get_best_drydock_system_id passed bad drydock sys_id.") continue for pid in pids: planet = universe.getPlanet(pid) if (planet and planet.currentMeterValue(fo.meterType.happiness) >= DRYDOCK_HAPPINESS_THRESHOLD and planet.currentMeterValue(fo.meterType.targetHappiness) >= DRYDOCK_HAPPINESS_THRESHOLD): drydock_system_ids.add(sys_id) break sys_distances = sorted([(universe.jumpDistance(start_system_id, sys_id), sys_id) for sys_id in drydock_system_ids]) aistate = get_aistate() fleet_rating = aistate.get_rating(fleet_id) for _, dock_sys_id in sys_distances: dock_system = TargetSystem(dock_sys_id) path = can_travel_to_system(fleet_id, start_system, dock_system) path_rating = sum([aistate.systemStatus[path_sys.id]['totalThreat'] for path_sys in path]) SAFETY_MARGIN = 10 if SAFETY_MARGIN * path_rating <= fleet_rating: debug("Drydock recommendation %s from %s for fleet %s with fleet rating %.1f and path rating %.1f." % (dock_system, start_system, universe.getFleet(fleet_id), fleet_rating, path_rating)) return dock_system.id debug("No safe drydock recommendation from %s for fleet %s with fleet rating %.1f." % (start_system, universe.getFleet(fleet_id), fleet_rating)) return None
def try_again(mil_fleet_ids, try_reset=False, thisround=""): """Clear targets and orders for all specified fleets then call get_military_fleets again.""" aistate = get_aistate() for fid in mil_fleet_ids: mission = aistate.get_fleet_mission(fid) mission.clear_fleet_orders() mission.clear_target() get_military_fleets(try_reset=try_reset, thisround=thisround)
def _calculate_threat(self): systems_status = get_aistate().systemStatus.get(self.sys_id, {}) threat = self.safety_factor * CombatRatingsAI.combine_ratings(systems_status.get('fleetThreat', 0), systems_status.get('monsterThreat', 0) + + systems_status.get('planetThreat', 0)) return self.threat_bias + threat
def get_invasion_targeted_planet_ids(planet_ids, mission_type): invasion_feet_missions = get_aistate().get_fleet_missions_with_any_mission_types([mission_type]) targeted_planets = [] for pid in planet_ids: # add planets that are target of a mission for mission in invasion_feet_missions: target = TargetPlanet(pid) if mission.has_target(mission_type, target): targeted_planets.append(pid) return targeted_planets
def get_empire_fleet_ids_by_role(fleet_role): """Returns a list with fleet_ids that have the specified role.""" fleet_ids = get_empire_fleet_ids() fleet_ids_with_role = [] aistate = get_aistate() for fleet_id in fleet_ids: if aistate.get_fleet_role(fleet_id) != fleet_role: continue fleet_ids_with_role.append(fleet_id) return fleet_ids_with_role
def assign_invasion_bases(): """Assign our troop bases to invasion targets.""" universe = fo.getUniverse() all_troopbase_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.ORBITAL_INVASION) available_troopbase_fleet_ids = set(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_troopbase_fleet_ids)) aistate = get_aistate() for fid in list(available_troopbase_fleet_ids): if fid not in available_troopbase_fleet_ids: # entry may have been discarded in previous loop iterations continue fleet = universe.getFleet(fid) if not fleet: continue sys_id = fleet.systemID system = universe.getSystem(sys_id) available_planets = set(system.planetIDs).intersection(set(aistate.qualifyingTroopBaseTargets.keys())) debug("Considering Base Troopers in %s, found planets %s and registered targets %s with status %s" % ( system.name, list(system.planetIDs), available_planets, [(pid, aistate.qualifyingTroopBaseTargets[pid]) for pid in available_planets])) targets = [pid for pid in available_planets if aistate.qualifyingTroopBaseTargets[pid][1] != -1] if not targets: debug("Failure: found no valid target for troop base in system %s" % system) continue status = aistate.systemStatus.get(sys_id, {}) local_base_troops = set(status.get('myfleets', [])).intersection(available_troopbase_fleet_ids) target_id = INVALID_ID best_score = -1 target_troops = 0 for pid, (p_score, p_troops) in assign_invasion_values(targets).items(): if p_score > best_score: best_score = p_score target_id = pid target_troops = p_troops if target_id == INVALID_ID: continue local_base_troops.discard(fid) found_fleets = [] troops_needed = max(0, target_troops - FleetUtilsAI.count_troops_in_fleet(fid)) found_stats = {} min_stats = {'rating': 0, 'troopCapacity': troops_needed} target_stats = {'rating': 10, 'troopCapacity': troops_needed + _TROOPS_SAFETY_MARGIN} FleetUtilsAI.get_fleets_for_mission(target_stats, min_stats, found_stats, starting_system=sys_id, fleet_pool_set=local_base_troops, fleet_list=found_fleets) for fid2 in found_fleets: FleetUtilsAI.merge_fleet_a_into_b(fid2, fid) available_troopbase_fleet_ids.discard(fid2) available_troopbase_fleet_ids.discard(fid) aistate.qualifyingTroopBaseTargets[target_id][1] = -1 # TODO: should probably delete target = TargetPlanet(target_id) fleet_mission = aistate.get_fleet_mission(fid) fleet_mission.set_target(MissionType.ORBITAL_INVASION, target)
def build_savegame_string() -> bytes: """Encode the AIstate and compress the resulting string with zlib. To decode the string, first call zlib.decompress() on it. :return: compressed savegame string :rtype: str """ from aistate_interface import get_aistate savegame_string = encode(get_aistate()) return base64.b64encode(zlib.compress(savegame_string.encode('utf-8')))
def get_invasion_targeted_planet_ids(planet_ids, mission_type): invasion_feet_missions = get_aistate( ).get_fleet_missions_with_any_mission_types([mission_type]) targeted_planets = [] for pid in planet_ids: # add planets that are target of a mission for mission in invasion_feet_missions: target = TargetPlanet(pid) if mission.has_target(mission_type, target): targeted_planets.append(pid) return targeted_planets
def assess_fleet_role(fleet_id): """ Assesses ShipRoles represented in a fleet and returns a corresponding overall fleetRole (of type MissionType). """ universe = fo.getUniverse() ship_roles = {} fleet = universe.getFleet(fleet_id) if not fleet: print "couldn't get fleet with id " + str(fleet_id) return ShipRoleType.INVALID # count ship_roles aistate = get_aistate() for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) if ship.design: role = aistate.get_ship_role(ship.design.id) else: role = ShipRoleType.INVALID if role != ShipRoleType.INVALID: ship_roles[role] = ship_roles.get(role, 0) + 1 # determine most common ship_role favourite_role = ShipRoleType.INVALID for ship_role in ship_roles: if ship_roles[ship_role] == max(ship_roles.values()): favourite_role = ship_role # assign fleet role if ShipRoleType.CIVILIAN_COLONISATION in ship_roles: selected_role = MissionType.COLONISATION elif ShipRoleType.BASE_COLONISATION in ship_roles: selected_role = MissionType.COLONISATION elif ShipRoleType.CIVILIAN_OUTPOST in ship_roles: selected_role = MissionType.OUTPOST elif ShipRoleType.BASE_OUTPOST in ship_roles: selected_role = MissionType.ORBITAL_OUTPOST elif ShipRoleType.BASE_INVASION in ship_roles: selected_role = MissionType.ORBITAL_INVASION elif ShipRoleType.BASE_DEFENSE in ship_roles: selected_role = MissionType.ORBITAL_DEFENSE elif ShipRoleType.MILITARY_INVASION in ship_roles: selected_role = MissionType.INVASION #### elif favourite_role == ShipRoleType.CIVILIAN_EXPLORATION: selected_role = MissionType.EXPLORATION elif favourite_role == ShipRoleType.MILITARY_ATTACK: selected_role = MissionType.MILITARY elif favourite_role == ShipRoleType.MILITARY: selected_role = MissionType.MILITARY else: selected_role = ShipRoleType.INVALID return selected_role
def calculate_priorities(): """Calculates the priorities of the AI player.""" debug("\n{0}Preparing to Calculate Priorities{0}".format(10 * "=")) prioritiees_timer.start('setting Production Priority') aistate = get_aistate() aistate.set_priority(PriorityType.RESOURCE_PRODUCTION, 50) # let this one stay fixed & just adjust Research debug("\n*** Calculating Research Priority ***\n") prioritiees_timer.start('setting Research Priority') aistate.set_priority( PriorityType.RESOURCE_RESEARCH, _calculate_research_priority()) # TODO: do univ _survey before this debug("\n*** Updating Colonization Status ***\n") prioritiees_timer.start('Evaluating Colonization Status') ColonisationAI.get_colony_fleets( ) # sets aistate.colonisablePlanetIDs and many other values used by other modules debug("\n*** Updating Invasion Status ***\n") prioritiees_timer.start('Evaluating Invasion Status') InvasionAI.get_invasion_fleets( ) # sets AIstate.opponentPlanetIDs, and AIstate.invasionTargetedPlanetIDs debug("\n*** Updating Military Status ***\n") prioritiees_timer.start('Evaluating Military Status') MilitaryAI.get_military_fleets() debug("\n** Calculating Production Priorities ***\n") prioritiees_timer.start('reporting Production Priority') _calculate_industry_priority() # purely for reporting purposes prioritiees_timer.start('setting Exploration Priority') aistate.set_priority(PriorityType.RESOURCE_TRADE, 0) aistate.set_priority(PriorityType.RESOURCE_CONSTRUCTION, 0) aistate.set_priority(PriorityType.PRODUCTION_EXPLORATION, _calculate_exploration_priority()) prioritiees_timer.start('setting Colony Priority') aistate.set_priority(PriorityType.PRODUCTION_COLONISATION, _calculate_colonisation_priority()) prioritiees_timer.start('setting Outpost Priority') aistate.set_priority(PriorityType.PRODUCTION_OUTPOST, _calculate_outpost_priority()) prioritiees_timer.start('setting Invasion Priority') aistate.set_priority(PriorityType.PRODUCTION_INVASION, _calculate_invasion_priority()) prioritiees_timer.start('setting Military Priority') aistate.set_priority(PriorityType.PRODUCTION_MILITARY, _calculate_military_priority()) prioritiees_timer.start('setting other priorities') aistate.set_priority(PriorityType.PRODUCTION_BUILDINGS, 25) prioritiees_timer.stop_print_and_clear()
def follow_vis_system_connections(start_system_id, home_system_id): universe = fo.getUniverse() empire_id = fo.empireID() exploration_list = [start_system_id] aistate = get_aistate() while exploration_list: cur_system_id = exploration_list.pop() if cur_system_id in graph_flags: continue graph_flags.add(cur_system_id) system = universe.getSystem(cur_system_id) if cur_system_id in aistate.visBorderSystemIDs: pre_vis = "a border system" elif cur_system_id in aistate.visInteriorSystemIDs: pre_vis = "an interior system" else: pre_vis = "an unknown system" system_header = "*** system %s;" % system if fo.currentTurn() < 50: visibility_turn_list = sorted(universe.getVisibilityTurnsMap( cur_system_id, empire_id).items(), key=lambda x: x[0].numerator) visibility_info = ", ".join("%s: %s" % (vis.name, turn) for vis, turn in visibility_turn_list) debug("%s previously %s. Visibility per turn: %s " % (system_header, pre_vis, visibility_info)) status_info = [] else: status_info = [system_header] has_been_visible = get_partial_visibility_turn(cur_system_id) > 0 is_connected = systems_connected(cur_system_id, home_system_id) status_info.append(" -- is%s partially visible" % ("" if has_been_visible else " not")) status_info.append(" -- is%s visibly connected to homesystem" % ("" if is_connected else " not")) if has_been_visible: sys_status = aistate.systemStatus.setdefault(cur_system_id, {}) aistate.visInteriorSystemIDs.add(cur_system_id) aistate.visBorderSystemIDs.discard(cur_system_id) neighbors = get_neighbors(cur_system_id) sys_status.setdefault("neighbors", set()).update(neighbors) if neighbors: status_info.append(" -- has neighbors %s" % sorted(neighbors)) for sys_id in neighbors: if sys_id not in aistate.exploredSystemIDs: aistate.unexploredSystemIDs.add(sys_id) if (sys_id not in graph_flags) and ( sys_id not in aistate.visInteriorSystemIDs): aistate.visBorderSystemIDs.add(sys_id) exploration_list.append(sys_id) if fo.currentTurn() < 50: debug("\n".join(status_info)) debug("----------------------------------------------------------")
def assess_fleet_role(fleet_id): """ Assesses ShipRoles represented in a fleet and returns a corresponding overall fleetRole (of type MissionType). """ universe = fo.getUniverse() ship_roles = {} fleet = universe.getFleet(fleet_id) if not fleet: debug("couldn't get fleet with id " + str(fleet_id)) return ShipRoleType.INVALID # count ship_roles aistate = get_aistate() for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) if ship.design: role = aistate.get_ship_role(ship.design.id) else: role = ShipRoleType.INVALID if role != ShipRoleType.INVALID: ship_roles[role] = ship_roles.get(role, 0) + 1 # determine most common ship_role favourite_role = ShipRoleType.INVALID for ship_role in ship_roles: if ship_roles[ship_role] == max(ship_roles.values()): favourite_role = ship_role # assign fleet role if ShipRoleType.CIVILIAN_COLONISATION in ship_roles: selected_role = MissionType.COLONISATION elif ShipRoleType.BASE_COLONISATION in ship_roles: selected_role = MissionType.COLONISATION elif ShipRoleType.CIVILIAN_OUTPOST in ship_roles: selected_role = MissionType.OUTPOST elif ShipRoleType.BASE_OUTPOST in ship_roles: selected_role = MissionType.ORBITAL_OUTPOST elif ShipRoleType.BASE_INVASION in ship_roles: selected_role = MissionType.ORBITAL_INVASION elif ShipRoleType.BASE_DEFENSE in ship_roles: selected_role = MissionType.ORBITAL_DEFENSE elif ShipRoleType.MILITARY_INVASION in ship_roles: selected_role = MissionType.INVASION #### elif favourite_role == ShipRoleType.CIVILIAN_EXPLORATION: selected_role = MissionType.EXPLORATION elif favourite_role == ShipRoleType.MILITARY_ATTACK: selected_role = MissionType.MILITARY elif favourite_role == ShipRoleType.MILITARY: selected_role = MissionType.MILITARY else: selected_role = ShipRoleType.INVALID return selected_role
def fleet_has_ship_with_role(fleet_id, ship_role): """Returns True if a ship with shipRole is in the fleet.""" universe = fo.getUniverse() fleet = universe.getFleet(fleet_id) if fleet is None: return False aistate = get_aistate() for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) if aistate.get_ship_role(ship.design.id) == ship_role: return True return False
def _get_queued_base_troopers(sys_id: SystemId, element: fo.productionQueueElement) -> float: """Get base troopers for given system from a build queue element""" aistate = get_aistate() if element.buildType == EmpireProductionTypes.BT_SHIP: planet = fo.getUniverse().getPlanet(element.locationID) if planet.systemID == sys_id and aistate.get_ship_role( element.designID) == ShipRoleType.BASE_INVASION: design = fo.getShipDesign(element.designID) troops_per_ship = design.troopCapacity * get_species_tag_value( planet.speciesName, Tags.ATTACKTROOPS) return element.remaining * element.blocksize * troops_per_ship return 0.0
def assign_invasion_fleets_to_invade(): """Assign fleet targets to invadable planets.""" aistate = get_aistate() assign_invasion_bases() all_invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.INVASION) invasion_fleet_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types(all_invasion_fleet_ids) send_invasion_fleets(invasion_fleet_ids, AIstate.invasionTargets, MissionType.INVASION) all_invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.INVASION) for fid in FleetUtilsAI.extract_fleet_ids_without_mission_types(all_invasion_fleet_ids): this_mission = aistate.get_fleet_mission(fid) this_mission.check_mergers(context="Post-send consolidation of unassigned troops")
def get_empire_fleet_ids(): """Returns all fleetIDs for current empire.""" empire_id = fo.empireID() universe = fo.getUniverse() empire_fleet_ids = [] destroyed_object_ids = universe.destroyedObjectIDs(empire_id) for fleet_id in set(list(universe.fleetIDs) + list(get_aistate().newlySplitFleets)): fleet = universe.getFleet(fleet_id) if fleet is None: continue if fleet.ownedBy(empire_id) and fleet_id not in destroyed_object_ids and not fleet.empty and fleet.shipIDs: empire_fleet_ids.append(fleet_id) return empire_fleet_ids
def __init__(self, status_only: bool = False): self._empire = fo.getEmpire() self._universe = fo.getUniverse() self._aistate = get_aistate() # resourceAvailable includes this turns production, but that is wrong for influence self._ip = self._empire.resourceAvailable( fo.resourceType.influence) - self._get_infl_prod() self._adopted = set(self._empire.adoptedPolicies) # When we continue a game in which we just adopted a policy, game state shows the policy as adopted, # but IP still unspent. Correct it here, then calculate anew whether we want to adopt it. if not status_only: for entry in self._empire.turnsPoliciesAdopted: if entry.data() == fo.currentTurn(): debug(f"reverting saved adopt {entry.key()}") fo.issueDeadoptPolicyOrder(entry.key()) self._adopted.remove(entry.key()) self._originally_adopted = copy(self._adopted) empire_owned_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire() self._populated_planet_ids = PlanetUtilsAI.get_populated_planet_ids( empire_owned_planet_ids) self._num_populated = len(self._populated_planet_ids) self._num_outposts = len(empire_owned_planet_ids) - self._num_populated self._max_turn_bureaucracy = self._calculate_max_turn_bureaucracy() self._centralization_cost = fo.getPolicy(centralization).adoptionCost() self._bureaucracy_cost = fo.getPolicy(bureaucracy).adoptionCost() self._wanted_ip = self._wanted_for_bureaucracy() self._adoptable = self._get_adoptable() self._available = set(self._empire.availablePolicies) self._rating_functions = { propaganda: lambda: 20 + self._rate_opinion(propaganda), algo_research: self._rate_algo_research, diversity: self._rate_diversity, artisans: self._rate_artisans, population: self._rate_population, # military policies are mostly chosen by opinion, plus some rule-of-thumb values allied_repair: lambda: self._rate_opinion( charge), # no effect, unless we have allies... charge: lambda: 5 + self._rate_opinion(charge), # A small bonus in battle scanning: lambda: 20 + self._rate_opinion( scanning), # May help us detect ships and planets simplicity: lambda: 20 + self._rate_opinion( simplicity), # Makes simple ships cheaper engineering: self._rate_engineering_corps, exploration: lambda: 10 + self._rate_opinion( exploration), # may give a little research, speeds up scouts flanking: lambda: 5 + self._rate_opinion( flanking), # A small bonus in battle recruitment: lambda: 15 + self._rate_opinion( recruitment), # cheaper troop ships }
def split_fleet(fleet_id): """Split a fleet into its ships. :param fleet_id: fleet to be split. :type fleet_id: int :return: New fleets. Empty if couldn't split. :rtype: list[int] """ universe = fo.getUniverse() empire_id = fo.empireID() fleet = universe.getFleet(fleet_id) newfleets = [] if fleet is None: return [] if not fleet.ownedBy(empire_id): return [] if len(list( fleet.shipIDs)) <= 1: # fleet with only one ship cannot be split return [] ship_ids = list(fleet.shipIDs) aistate = get_aistate() for ship_id in ship_ids[1:]: new_fleet_id = fo.issueNewFleetOrder("Fleet %4d" % ship_id, ship_id) if new_fleet_id: new_fleet = universe.getFleet(new_fleet_id) if not new_fleet: warn("Newly split fleet %d not available from universe" % new_fleet_id) fo.issueRenameOrder( new_fleet_id, "Fleet %4d" % new_fleet_id) # to ease review of debugging logs fo.issueAggressionOrder(new_fleet_id, True) aistate.update_fleet_rating(new_fleet_id) newfleets.append(new_fleet_id) aistate.newlySplitFleets[new_fleet_id] = True else: if fleet.systemID == INVALID_ID: warn( "Tried to split ship id (%d) from fleet %d when fleet is in starlane" % (ship_id, fleet_id)) else: warn( "Got no fleet ID back after trying to split ship id (%d) from fleet %d" % (ship_id, fleet_id)) aistate.get_fleet_role(fleet_id, force_new=True) aistate.update_fleet_rating(fleet_id) if newfleets: aistate.ensure_have_fleet_missions(newfleets) return newfleets
def avail_mil_needing_repair(mil_fleet_ids, split_ships=False, on_mission=False, repair_limit=0.70): """Returns tuple of lists: (ids_needing_repair, ids_not).""" fleet_buckets = [[], []] universe = fo.getUniverse() cutoff = [repair_limit, 0.25][on_mission] aistate = get_aistate() for fleet_id in mil_fleet_ids: fleet = universe.getFleet(fleet_id) ship_buckets = [[], []] ships_cur_health = [0, 0] ships_max_health = [0, 0] for ship_id in fleet.shipIDs: this_ship = universe.getShip(ship_id) cur_struc = this_ship.initialMeterValue(fo.meterType.structure) max_struc = this_ship.initialMeterValue(fo.meterType.maxStructure) ship_ok = cur_struc >= cutoff * max_struc ship_buckets[ship_ok].append(ship_id) ships_cur_health[ship_ok] += cur_struc ships_max_health[ship_ok] += max_struc this_sys_id = fleet.systemID if fleet.nextSystemID == INVALID_ID else fleet.nextSystemID fleet_ok = (sum(ships_cur_health) >= cutoff * sum(ships_max_health)) local_status = aistate.systemStatus.get(this_sys_id, {}) my_local_rating = combine_ratings(local_status.get('mydefenses', {}).get('overall', 0), local_status.get('myFleetRating', 0)) my_local_rating_vs_planets = local_status.get('myFleetRatingVsPlanets', 0) combat_trigger = bool(local_status.get('fleetThreat', 0) or local_status.get('monsterThreat', 0)) if not combat_trigger and local_status.get('planetThreat', 0): universe = fo.getUniverse() system = universe.getSystem(this_sys_id) for planet_id in system.planetIDs: planet = universe.getPlanet(planet_id) if planet.ownedBy(fo.empireID()): # TODO: also exclude at-peace planets continue if planet.unowned and not EspionageAI.colony_detectable_by_empire(planet_id, empire=fo.empireID()): continue if sum([planet.currentMeterValue(meter_type) for meter_type in [fo.meterType.defense, fo.meterType.shield, fo.meterType.construction]]): combat_trigger = True break needed_here = combat_trigger and local_status.get('totalThreat', 0) > 0 # TODO: assess if remaining other forces are sufficient safely_needed = needed_here and my_local_rating > local_status.get('totalThreat', 0) and my_local_rating_vs_planets > local_status.get('planetThreat', 0) # TODO: improve both assessment prongs if not fleet_ok: if safely_needed: print "Fleet %d at %s needs repair but deemed safely needed to remain for defense" % (fleet_id, universe.getSystem(fleet.systemID)) else: if needed_here: print "Fleet %d at %s needed present for combat, but is damaged and deemed unsafe to remain." % (fleet_id, universe.getSystem(fleet.systemID)) print "\t my_local_rating: %.1f ; threat: %.1f" % (my_local_rating, local_status.get('totalThreat', 0)) print "Selecting fleet %d at %s for repair" % (fleet_id, universe.getSystem(fleet.systemID)) fleet_buckets[fleet_ok or bool(safely_needed)].append(fleet_id) return fleet_buckets
def send_invasion_fleets(fleet_ids, evaluated_planets, mission_type): """sends a list of invasion fleets to a list of planet_value_pairs""" if not fleet_ids: return universe = fo.getUniverse() invasion_fleet_pool = set(fleet_ids) for planet_id, pscore, ptroops in evaluated_planets: if pscore < MIN_INVASION_SCORE: continue planet = universe.getPlanet(planet_id) if not planet: continue sys_id = planet.systemID found_fleets = [] found_stats = {} min_stats = {"rating": 0, "troopCapacity": ptroops} target_stats = { "rating": 10, "troopCapacity": ptroops + _TROOPS_SAFETY_MARGIN, "target_system": TargetSystem(sys_id), } these_fleets = FleetUtilsAI.get_fleets_for_mission( target_stats, min_stats, found_stats, starting_system=sys_id, fleet_pool_set=invasion_fleet_pool, fleet_list=found_fleets, ) if not these_fleets: if not FleetUtilsAI.stats_meet_reqs(found_stats, min_stats): debug( "Insufficient invasion troop allocation for system %d ( %s ) -- requested %s , found %s" % (sys_id, universe.getSystem(sys_id).name, min_stats, found_stats)) invasion_fleet_pool.update(found_fleets) continue else: these_fleets = found_fleets target = TargetPlanet(planet_id) debug("assigning invasion fleets %s to target %s" % (these_fleets, target)) aistate = get_aistate() for fleetID in these_fleets: fleet_mission = aistate.get_fleet_mission(fleetID) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() fleet_mission.set_target(mission_type, target)
def _dump_empire_info(): empire = fo.getEmpire() turn = fo.currentTurn() # TODO: It would be nice to decouple char and AI state. # Character is immutable and ai state is mutable. # Dependency on immutable object is easier to manage research_index = get_aistate().character.get_research_index() aggression_name = get_trait_name_aggression(get_aistate().character) name_parts = ( empire.name, empire.empireID, "pid", fo.playerID(), fo.playerName(), "RIdx", research_index, aggression_name.capitalize(), ) empire_name = "_".join(str(part) for part in name_parts) stats.empire(empire.empireID, empire_name, turn) stats.empire_color(*empire.colour)
def avail_mil_needing_repair(mil_fleet_ids, split_ships=False, on_mission=False, repair_limit=0.70): """Returns tuple of lists: (ids_needing_repair, ids_not).""" fleet_buckets = [[], []] universe = fo.getUniverse() cutoff = [repair_limit, 0.25][on_mission] aistate = get_aistate() for fleet_id in mil_fleet_ids: fleet = universe.getFleet(fleet_id) ship_buckets = [[], []] ships_cur_health = [0, 0] ships_max_health = [0, 0] for ship_id in fleet.shipIDs: this_ship = universe.getShip(ship_id) cur_struc = this_ship.initialMeterValue(fo.meterType.structure) max_struc = this_ship.initialMeterValue(fo.meterType.maxStructure) ship_ok = cur_struc >= cutoff * max_struc ship_buckets[ship_ok].append(ship_id) ships_cur_health[ship_ok] += cur_struc ships_max_health[ship_ok] += max_struc this_sys_id = fleet.systemID if fleet.nextSystemID == INVALID_ID else fleet.nextSystemID fleet_ok = (sum(ships_cur_health) >= cutoff * sum(ships_max_health)) local_status = aistate.systemStatus.get(this_sys_id, {}) my_local_rating = combine_ratings(local_status.get('mydefenses', {}).get('overall', 0), local_status.get('myFleetRating', 0)) my_local_rating_vs_planets = local_status.get('myFleetRatingVsPlanets', 0) combat_trigger = bool(local_status.get('fleetThreat', 0) or local_status.get('monsterThreat', 0)) if not combat_trigger and local_status.get('planetThreat', 0): universe = fo.getUniverse() system = universe.getSystem(this_sys_id) for planet_id in system.planetIDs: planet = universe.getPlanet(planet_id) if planet.ownedBy(fo.empireID()): # TODO: also exclude at-peace planets continue if planet.unowned and not EspionageAI.colony_detectable_by_empire(planet_id, empire=fo.empireID()): continue if sum([planet.currentMeterValue(meter_type) for meter_type in [fo.meterType.defense, fo.meterType.shield, fo.meterType.construction]]): combat_trigger = True break needed_here = combat_trigger and local_status.get('totalThreat', 0) > 0 # TODO: assess if remaining other forces are sufficient safely_needed = needed_here and my_local_rating > local_status.get('totalThreat', 0) and my_local_rating_vs_planets > local_status.get('planetThreat', 0) # TODO: improve both assessment prongs if not fleet_ok: if safely_needed: debug("Fleet %d at %s needs repair but deemed safely needed to remain for defense" % (fleet_id, universe.getSystem(fleet.systemID))) else: if needed_here: debug("Fleet %d at %s needed present for combat, but is damaged and deemed unsafe to remain." % (fleet_id, universe.getSystem(fleet.systemID))) debug("\t my_local_rating: %.1f ; threat: %.1f" % (my_local_rating, local_status.get('totalThreat', 0))) debug("Selecting fleet %d at %s for repair" % (fleet_id, universe.getSystem(fleet.systemID))) fleet_buckets[fleet_ok or bool(safely_needed)].append(fleet_id) return fleet_buckets
def issue_order(self): if not super(OrderInvade, self).can_issue_order(): return False universe = fo.getUniverse() planet_id = self.target.id planet = self.target.get_object() fleet = self.fleet.get_object() invasion_roles = (ShipRoleType.MILITARY_INVASION, ShipRoleType.BASE_INVASION) debug("Issuing order: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) # will track if at least one invasion troops successfully deployed result = False aistate = get_aistate() for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) role = aistate.get_ship_role(ship.design.id) if role not in invasion_roles: continue debug("Ordering troop ship %d to invade %s" % (ship_id, planet)) result = fo.issueInvadeOrder(ship_id, planet_id) or result if not result: shields = planet.currentMeterValue(fo.meterType.shield) planet_stealth = planet.currentMeterValue(fo.meterType.stealth) pop = planet.currentMeterValue(fo.meterType.population) warn("Invasion order failed!") debug( " -- planet has %.1f stealth, shields %.1f, %.1f population and " "is owned by empire %d" % (planet_stealth, shields, pop, planet.owner)) if 'needsEmergencyExploration' not in dir(aistate): aistate.needsEmergencyExploration = [] if fleet.systemID not in aistate.needsEmergencyExploration: aistate.needsEmergencyExploration.append(fleet.systemID) debug( "Due to trouble invading, adding system %d to Emergency Exploration List" % fleet.systemID) self.executed = False # debug(universe.getPlanet(planet_id).dump()) # TODO: fix fo.UniverseObject.dump() break if result: debug("Successfully ordered troop ship(s) to invade %s" % planet) return True else: return False
def check_mergers(self, context=""): """ Merge local fleets with same mission into this fleet. :param context: Context of the function call for logging purposes :type context: str """ debug("Considering to merge %s", self.__str__()) if self.type not in MERGEABLE_MISSION_TYPES: debug("Mission type does not allow merging.") return if not self.target: debug("Mission has no valid target - do not merge.") return universe = fo.getUniverse() empire_id = fo.empireID() fleet_id = self.fleet.id main_fleet = universe.getFleet(fleet_id) main_fleet_system_id = main_fleet.systemID if main_fleet_system_id == INVALID_ID: debug("Can't merge: fleet in middle of starlane.") return # only merge PROTECT_REGION if there is any threat near target if self.type == MissionType.PROTECT_REGION: neighbor_systems = universe.getImmediateNeighbors(self.target.id, empire_id) if not any(MilitaryAI.get_system_local_threat(sys_id) for sys_id in neighbor_systems): debug("Not merging PROTECT_REGION fleet - no threat nearby.") return destroyed_list = set(universe.destroyedObjectIDs(empire_id)) aistate = get_aistate() system_status = aistate.systemStatus[main_fleet_system_id] other_fleets_here = [fid for fid in system_status.get('myFleetsAccessible', []) if fid != fleet_id and fid not in destroyed_list and universe.getFleet(fid).ownedBy(empire_id)] if not other_fleets_here: debug("No other fleets here") return for fid in other_fleets_here: fleet_mission = aistate.get_fleet_mission(fid) if fleet_mission.type != self.type or fleet_mission.target != self.target: debug("Local candidate %s does not have same mission." % fleet_mission) continue FleetUtilsAI.merge_fleet_a_into_b(fid, fleet_id, context="Order %s of mission %s" % (context, self))
def print_resources_priority(): """Calculate top resource priority.""" universe = fo.getUniverse() empire = fo.getEmpire() empire_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire(universe.planetIDs) print "Resource Priorities:" resource_priorities = {} aistate = get_aistate() for priority_type in get_priority_resource_types(): resource_priorities[priority_type] = aistate.get_priority(priority_type) sorted_priorities = resource_priorities.items() sorted_priorities.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) top_priority = -1 for evaluation_priority, evaluation_score in sorted_priorities: if top_priority < 0: top_priority = evaluation_priority print " %s: %.2f" % (evaluation_priority, evaluation_score) # what is the focus of available resource centers? print warnings = {} foci_table = Table([ Text('Planet'), Text('Size'), Text('Type'), Text('Focus'), Text('Species'), Text('Pop') ], table_name="Planetary Foci Overview Turn %d" % fo.currentTurn()) for pid in empire_planet_ids: planet = universe.getPlanet(pid) population = planet.currentMeterValue(fo.meterType.population) max_population = planet.currentMeterValue(fo.meterType.targetPopulation) if max_population < 1 and population > 0: warnings[planet.name] = (population, max_population) foci_table.add_row([ planet, planet.size, planet.type, "_".join(str(planet.focus).split("_")[1:])[:8], planet.speciesName, "%.1f/%.1f" % (population, max_population) ]) info(foci_table) print "Empire Totals:\nPopulation: %5d \nProduction: %5d\nResearch: %5d\n" % ( empire.population(), empire.productionPoints, empire.resourceProduction(fo.resourceType.research)) for name, (cp, mp) in warnings.iteritems(): print "Population Warning! -- %s has unsustainable current pop %d -- target %d" % (name, cp, mp)
def print_starting_intro(): debug("\n\n\n" + "=" * 20) debug(f"Starting turn {fo.currentTurn()}") debug("=" * 20 + "\n") debug( "***************************************************************************" ) debug( "******* Log info for AI progress chart script. Do not modify. **********" ) debug("Generating Orders") _dump_empire_info() _print_empire_capital() stats.ship_count(get_aistate().shipCount)
def _pre_game_start(empire_id): """ Configuration that should be done before AI start operating. """ aistate = get_aistate() aggression_trait = aistate.character.get_trait(Aggression) diplomatic_corp_configs = { fo.aggression.beginner: DiplomaticCorp.BeginnerDiplomaticCorp, fo.aggression.maniacal: DiplomaticCorp.ManiacalDiplomaticCorp, } global diplomatic_corp diplomatic_corp = diplomatic_corp_configs.get( aggression_trait.key, DiplomaticCorp.DiplomaticCorp)() TechsListsAI.test_tech_integrity() configure_debug_chat(empire_id)
def build_savegame_string(use_compression=True): """Encode the AIstate and compress the resulting string with zlib. To decode the string, first call zlib.decompress() on it. :return: compressed savegame string :rtype: str """ from aistate_interface import get_aistate savegame_string = encode(get_aistate()) if use_compression: import base64 import zlib savegame_string = base64.b64encode(zlib.compress(savegame_string)) return savegame_string
def can_issue_order(self, verbose=False): """If FleetOrder can be issued now.""" # for some orders, may need to re-issue if invasion/outposting/colonization was interrupted if self.executed and not isinstance(self, (OrderOutpost, OrderColonize, OrderInvade)): return False if not self.is_valid(): return False if verbose: sys1 = self.fleet.get_system() main_fleet_mission = get_aistate().get_fleet_mission(self.fleet.id) print " Can issue %s - Mission Type %s (%s), current loc sys %d - %s" % ( self, main_fleet_mission.type, main_fleet_mission.type, self.fleet.id, sys1) return True
def follow_vis_system_connections(start_system_id, home_system_id): universe = fo.getUniverse() empire_id = fo.empireID() exploration_list = [start_system_id] aistate = get_aistate() while exploration_list: cur_system_id = exploration_list.pop() if cur_system_id in graph_flags: continue graph_flags.add(cur_system_id) system = universe.getSystem(cur_system_id) if cur_system_id in aistate.visBorderSystemIDs: pre_vis = "a border system" elif cur_system_id in aistate.visInteriorSystemIDs: pre_vis = "an interior system" else: pre_vis = "an unknown system" system_header = "*** system %s;" % system if fo.currentTurn() < 50: visibility_turn_list = sorted(universe.getVisibilityTurnsMap(cur_system_id, empire_id).items(), key=lambda x: x[0].numerator) visibility_info = ', '.join('%s: %s' % (vis.name, turn) for vis, turn in visibility_turn_list) debug("%s previously %s. Visibility per turn: %s " % (system_header, pre_vis, visibility_info)) status_info = [] else: status_info = [system_header] has_been_visible = get_partial_visibility_turn(cur_system_id) > 0 is_connected = universe.systemsConnected(cur_system_id, home_system_id, -1) # self.empire_id) status_info.append(" -- is%s partially visible" % ("" if has_been_visible else " not")) status_info.append(" -- is%s visibly connected to homesystem" % ("" if is_connected else " not")) if has_been_visible: sys_status = aistate.systemStatus.setdefault(cur_system_id, {}) aistate.visInteriorSystemIDs.add(cur_system_id) aistate.visBorderSystemIDs.discard(cur_system_id) neighbors = set(universe.getImmediateNeighbors(cur_system_id, empire_id)) sys_status.setdefault('neighbors', set()).update(neighbors) if neighbors: status_info.append(" -- has neighbors %s" % sorted(neighbors)) for sys_id in neighbors: if sys_id not in aistate.exploredSystemIDs: aistate.unexploredSystemIDs.add(sys_id) if (sys_id not in graph_flags) and (sys_id not in aistate.visInteriorSystemIDs): aistate.visBorderSystemIDs.add(sys_id) exploration_list.append(sys_id) if fo.currentTurn() < 50: debug('\n'.join(status_info)) debug("----------------------------------------------------------")
def handle_diplomatic_message(self, message): """Handle a diplomatic message update from the server, such as if another player declares war, accepts peace, or cancels a proposed peace treaty. :param message: message.recipient and message.sender are respective empire IDs :return: """ debug("Received diplomatic %s message from %s to %s." % ( message.type, fo.getEmpire(message.sender), 'me' if message.recipient == fo.empireID() else fo.getEmpire(message.recipient))) # TODO: remove the following early return once proper support for third party diplomatic history is added if message.recipient != fo.empireID(): return aistate = get_aistate() if message.type == fo.diplomaticMessageType.peaceProposal: aistate.log_peace_request(message.sender, message.recipient) proposal_sender_player = fo.empirePlayerID(message.sender) attitude = aistate.character.attitude_to_empire(message.sender, aistate.diplomatic_logs) possible_acknowledgments = [] aggression = aistate.character.get_trait(Aggression) if aggression.key <= fo.aggression.typical: possible_acknowledgments = UserStringList("AI_PEACE_PROPOSAL_ACKNOWLEDGEMENTS_MILD_LIST") if attitude > 0: possible_replies = UserStringList("AI_PEACE_PROPOSAL_RESPONSES_YES_MILD_LIST") else: possible_replies = UserStringList("AI_PEACE_PROPOSAL_RESPONSES_NO_MILD_LIST") else: possible_acknowledgments = UserStringList("AI_PEACE_PROPOSAL_ACKNOWLEDGEMENTS_HARSH_LIST") if attitude > 0: possible_replies = UserStringList("AI_PEACE_PROPOSAL_RESPONSES_YES_HARSH_LIST") else: possible_replies = UserStringList("AI_PEACE_PROPOSAL_RESPONSES_NO_HARSH_LIST") acknowledgement = random.choice(possible_acknowledgments) reply_text = random.choice(possible_replies) debug("Acknowledging proposal with initial message (from %d choices): '%s'" % ( len(possible_acknowledgments), acknowledgement)) fo.sendChatMessage(proposal_sender_player, acknowledgement) if attitude > 0: diplo_reply = fo.diplomaticMessage(message.recipient, message.sender, fo.diplomaticMessageType.acceptPeaceProposal) debug("Sending diplomatic message to empire %s of type %s" % (message.sender, diplo_reply.type)) fo.sendDiplomaticMessage(diplo_reply) debug("sending chat to player %d of empire %d, message body: '%s'" % ( proposal_sender_player, message.sender, reply_text)) fo.sendChatMessage(proposal_sender_player, reply_text) elif message.type == fo.diplomaticMessageType.warDeclaration: # note: apparently this is currently (normally?) sent not as a warDeclaration, # but as a simple diplomatic_status_update to war aistate.log_war_declaration(message.sender, message.recipient)
def get_rating(self, enemy_stats=None, ignore_fighters=False): """Calculate a rating against specified enemy. If no enemy is specified, will rate against the empire standard enemy :param enemy_stats: Enemy stats to be rated against - if None :type enemy_stats: ShipCombatStats :param ignore_fighters: If True, acts as if fighters are not launched :type ignore_fighters: bool :return: rating against specified enemy :rtype: float """ # adjust base stats according to enemy stats def _rating(): return my_total_attack * my_structure # The fighter rating calculations are heavily based upon the enemy stats. # So, for now, we compare at least against a certain standard enemy. enemy_stats = enemy_stats or get_aistate().get_standard_enemy() my_attacks, my_structure, my_shields = self.get_basic_stats() # e_avg_attack = 1 if enemy_stats: e_attacks, e_structure, e_shields = enemy_stats.get_basic_stats() if e_attacks: # e_num_attacks = sum(n for n in e_attacks.values()) e_total_attack = sum(n * dmg for dmg, n in e_attacks.items()) # e_avg_attack = e_total_attack / e_num_attacks e_net_attack = sum(n * max(dmg - my_shields, .001) for dmg, n in e_attacks.items()) e_net_attack = max(e_net_attack, .1 * e_total_attack) shield_factor = e_total_attack / e_net_attack my_structure *= max(1, shield_factor) my_total_attack = sum(n * max(dmg - e_shields, .001) for dmg, n in my_attacks.items()) else: my_total_attack = sum(n * dmg for dmg, n in my_attacks.items()) my_structure += my_shields if ignore_fighters: return _rating() my_total_attack += self.estimate_fighter_damage() # TODO: Consider enemy fighters return _rating()
def print_resources_priority(): """Calculate top resource priority.""" universe = fo.getUniverse() empire = fo.getEmpire() empire_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire(universe.planetIDs) debug("Resource Priorities:") resource_priorities = {} aistate = get_aistate() for priority_type in get_priority_resource_types(): resource_priorities[priority_type] = aistate.get_priority(priority_type) sorted_priorities = sorted(resource_priorities.items(), key=itemgetter(1), reverse=True) top_priority = -1 for evaluation_priority, evaluation_score in sorted_priorities: if top_priority < 0: top_priority = evaluation_priority debug(" %s: %.2f", evaluation_priority, evaluation_score) # what is the focus of available resource centers? debug('') warnings = {} foci_table = Table([ Text('Planet'), Text('Size'), Text('Type'), Text('Focus'), Text('Species'), Text('Pop') ], table_name="Planetary Foci Overview Turn %d" % fo.currentTurn()) for pid in empire_planet_ids: planet = universe.getPlanet(pid) population = planet.currentMeterValue(fo.meterType.population) max_population = planet.currentMeterValue(fo.meterType.targetPopulation) if max_population < 1 and population > 0: warnings[planet.name] = (population, max_population) foci_table.add_row([ planet, planet.size, planet.type, "_".join(str(planet.focus).split("_")[1:])[:8], planet.speciesName, "%.1f/%.1f" % (population, max_population) ]) info(foci_table) debug("Empire Totals:\nPopulation: %5d \nProduction: %5d\nResearch: %5d\n", empire.population(), empire.productionPoints, empire.resourceProduction(fo.resourceType.research)) for name, (cp, mp) in warnings.items(): warning("Population Warning! -- %s has unsustainable current pop %d -- target %d", name, cp, mp)
def get_ship_id_with_role(fleet_id, ship_role, verbose=True): """Returns a ship with the specified role in the fleet.""" if not fleet_has_ship_with_role(fleet_id, ship_role): if verbose: debug("No ship with role %s found." % ship_role) return None universe = fo.getUniverse() fleet = universe.getFleet(fleet_id) aistate = get_aistate() for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) if aistate.get_ship_role(ship.design.id) == ship_role: return ship_id
def get_ship_id_with_role(fleet_id, ship_role, verbose=True): """Returns a ship with the specified role in the fleet.""" if not fleet_has_ship_with_role(fleet_id, ship_role): if verbose: print "No ship with role %s found." % ship_role return None universe = fo.getUniverse() fleet = universe.getFleet(fleet_id) aistate = get_aistate() for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) if aistate.get_ship_role(ship.design.id) == ship_role: return ship_id
def issue_order(self): if not super(OrderMove, self).issue_order(): return False fleet_id = self.fleet.id system_id = self.target.get_system().id fleet = self.fleet.get_object() if system_id not in [fleet.systemID, fleet.nextSystemID]: dest_id = system_id fo.issueFleetMoveOrder(fleet_id, dest_id) debug("Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) aistate = get_aistate() if system_id == fleet.systemID: if aistate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: if system_id in aistate.needsEmergencyExploration: aistate.needsEmergencyExploration.remove(system_id) return True
def issue_order(self): if not super(OrderMove, self).issue_order(): return False fleet_id = self.fleet.id system_id = self.target.get_system().id fleet = self.fleet.get_object() if system_id not in [fleet.systemID, fleet.nextSystemID]: dest_id = system_id fo.issueFleetMoveOrder(fleet_id, dest_id) print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) aistate = get_aistate() if system_id == fleet.systemID: if aistate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: if system_id in aistate.needsEmergencyExploration: aistate.needsEmergencyExploration.remove(system_id) return True
def can_issue_order(self, verbose=False): """If FleetOrder can be issued now.""" # for some orders, may need to re-issue if invasion/outposting/colonization was interrupted if self.executed and not isinstance(self, (OrderOutpost, OrderColonize, OrderInvade)): return False if not self.is_valid(): return False if verbose: sys1 = self.fleet.get_system() main_fleet_mission = get_aistate().get_fleet_mission(self.fleet.id) debug( " Can issue %s - Mission Type %s (%s), current loc sys %d - %s" % (self, main_fleet_mission.type, main_fleet_mission.type, self.fleet.id, sys1) ) return True
def _calculate_top_production_queue_priority(): """Calculates the top production queue priority.""" production_queue_priorities = {} aistate = get_aistate() for priorityType in get_priority_production_types(): production_queue_priorities[priorityType] = aistate.get_priority( priorityType) sorted_priorities = production_queue_priorities.items() sorted_priorities.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) top_production_queue_priority = -1 for evaluationPair in sorted_priorities: if top_production_queue_priority < 0: top_production_queue_priority = evaluationPair[0] return top_production_queue_priority
def startNewGame(aggression_input=fo.aggression.aggressive): # pylint: disable=invalid-name """Called by client when a new game is started (but not when a game is loaded). Should clear any pre-existing state and set up whatever is needed for AI to generate orders.""" empire = fo.getEmpire() if empire is None: fatal("This client has no empire. Ignoring new game start message.") return if empire.eliminated: info( "This empire has been eliminated. Ignoring new game start message." ) return turn_timer.start("Server Processing") # initialize AIstate debug("Initializing AI state...") create_new_aistate(aggression_input) aistate = get_aistate() aggression_trait = aistate.character.get_trait(Aggression) debug("New game started, AI Aggression level %d (%s)" % (aggression_trait.key, get_trait_name_aggression(aistate.character))) aistate.session_start_cleanup() debug("Initialization of AI state complete!") debug("Trying to rename our homeworld...") planet_id = PlanetUtilsAI.get_capital() universe = fo.getUniverse() if planet_id is not None and planet_id != INVALID_ID: planet = universe.getPlanet(planet_id) new_name = " ".join([ random.choice(possible_capitals(aistate.character)).strip(), planet.name ]) debug(" Renaming to %s..." % new_name) res = fo.issueRenameOrder(planet_id, new_name) debug(" Result: %d; Planet is now named %s" % (res, planet.name)) diplomatic_corp_configs = { fo.aggression.beginner: DiplomaticCorp.BeginnerDiplomaticCorp, fo.aggression.maniacal: DiplomaticCorp.ManiacalDiplomaticCorp } global diplomatic_corp diplomatic_corp = diplomatic_corp_configs.get( aggression_trait.key, DiplomaticCorp.DiplomaticCorp)() TechsListsAI.test_tech_integrity()
def _calculate_top_production_queue_priority(): """Calculates the top production queue priority.""" production_queue_priorities = {} aistate = get_aistate() for priorityType in get_priority_production_types(): production_queue_priorities[priorityType] = aistate.get_priority( priorityType) sorted_priorities = sorted(production_queue_priorities.items(), key=itemgetter(1), reverse=True) top_production_queue_priority = -1 for evaluationPair in sorted_priorities: if top_production_queue_priority < 0: top_production_queue_priority = evaluationPair[0] return top_production_queue_priority
def assign_invasion_values(planet_ids): """Creates a dictionary that takes planet_ids as key and their invasion score as value.""" empire_id = fo.empireID() planet_values = {} neighbor_values = {} neighbor_val_ratio = .95 universe = fo.getUniverse() secure_missions = get_aistate().get_fleet_missions_with_any_mission_types( [MissionType.SECURE, MissionType.MILITARY]) for pid in planet_ids: planet_values[pid] = neighbor_values.setdefault( pid, evaluate_invasion_planet(pid, secure_missions)) debug("planet %d, values %s", pid, planet_values[pid]) planet = universe.getPlanet(pid) species_name = (planet and planet.speciesName) or "" species = fo.getSpecies(species_name) if species and species.canProduceShips: system = universe.getSystem(planet.systemID) if not system: continue planet_industries = {} for pid2 in system.planetIDs: planet2 = universe.getPlanet(pid2) species_name2 = (planet2 and planet2.speciesName) or "" species2 = fo.getSpecies(species_name2) if species2 and species2.canProduceShips: # to prevent divide-by-zero planet_industries[pid2] = planet2.initialMeterValue( fo.meterType.industry) + 0.1 industry_ratio = planet_industries[pid] / max( planet_industries.values()) for pid2 in system.planetIDs: if pid2 == pid: continue planet2 = universe.getPlanet(pid2) # TODO check for allies if (planet2 and (planet2.owner != empire_id) and ( (planet2.owner != -1) or (planet2.initialMeterValue(fo.meterType.population) > 0))): planet_values[pid][0] += ( industry_ratio * neighbor_val_ratio * (neighbor_values.setdefault( pid2, evaluate_invasion_planet(pid2, secure_missions))[0])) return planet_values
def assign_base(self, fleet_id: int) -> bool: """ Assign an outpost base fleet to execute the plan. It is expected that the fleet consists of only that one outpost base. :return: True on success, False on failure """ if self.base_assigned: warning("Assigned a base to a plan that was already assigned a base to.") return False # give orders to perform the mission target = TargetPlanet(self.target) fleet_mission = get_aistate().get_fleet_mission(fleet_id) fleet_mission.set_target(MissionType.ORBITAL_OUTPOST, target) self.fleet_id = fleet_id return True