Beispiel #1
0
def can_travel_to_system(fleet_id: int,
                         start: TargetSystem,
                         target: TargetSystem,
                         ensure_return: bool = False) -> List[TargetSystem]:
    """
    Return list systems to be visited.
    """
    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(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]
Beispiel #2
0
 def get_location_target(self):
     """system AITarget where fleet is or will be"""
     # TODO add parameter turn
     fleet = fo.getUniverse().getFleet(self.fleet.id)
     system_id = fleet.systemID
     if system_id >= 0:
         return TargetSystem(system_id)
     else:  # in starlane, so return next system
         return TargetSystem(fleet.nextSystemID)
Beispiel #3
0
def get_safe_path_leg_to_dest(fleet_id, start_id, dest_id):
    start_targ = TargetSystem(start_id)
    dest_targ = TargetSystem(dest_id)
    # TODO actually get a safe path
    this_path = can_travel_to_system(fleet_id,
                                     start_targ,
                                     dest_targ,
                                     ensure_return=False)
    path_ids = [targ.id
                for targ in this_path if targ.id != start_id] + [start_id]
    universe = fo.getUniverse()
    debug("Fleet %d requested safe path leg from %s to %s, found path %s" %
          (fleet_id, universe.getSystem(start_id), universe.getSystem(dest_id),
           PlanetUtilsAI.sys_name_ids(path_ids)))
    return path_ids[0]
Beispiel #4
0
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 = {"troopCapacity": ptroops}
        target_stats = {
            "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))
        if target.get_object().currentMeterValue(
                fo.meterType.maxShield) > 0.0 and not secure_system(
                    sys_id, True):
            continue
        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)
        # cannot wait for next turn, else the secure mission may be abandoned
        if these_fleets and sys_id not in AIstate.invasionTargetedSystemIDs:
            AIstate.invasionTargetedSystemIDs.append(sys_id)
Beispiel #5
0
def get_nearest_supplied_system(start_system_id: SystemId):
    """Return systemAITarget of nearest supplied system from starting system startSystemID."""
    empire = fo.getEmpire()
    fleet_supplyable_system_ids = empire.fleetSupplyableSystemIDs
    universe = fo.getUniverse()

    if start_system_id in fleet_supplyable_system_ids:
        return TargetSystem(start_system_id)
    else:
        min_jumps = 9999  # infinity
        supply_system_id = INVALID_ID
        for system_id in fleet_supplyable_system_ids:
            if start_system_id != INVALID_ID and system_id != INVALID_ID:
                least_jumps_len = universe.jumpDistance(
                    start_system_id, system_id)
                if least_jumps_len < min_jumps:
                    min_jumps = least_jumps_len
                    supply_system_id = system_id
        return TargetSystem(supply_system_id)
Beispiel #6
0
    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 if not self.type == MissionType.PROTECT_REGION
                                                            else system_to_visit)
            self.orders.append(fleet_order)
Beispiel #7
0
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)
Beispiel #8
0
def get_repair_fleet_order(
        fleet: TargetFleet) -> Optional["fleet_orders.OrderRepair"]:
    """
    Return fleet_orders.OrderRepair for fleet to proceed to system with drydock.
    """
    # TODO Cover new mechanics where happiness increases repair rate - don't always use nearest system!
    # find nearest drydock system
    drydock_sys_id = get_best_drydock_system_id(get_fleet_position(fleet.id),
                                                fleet.id)
    if drydock_sys_id is None:
        return None

    debug("Ordering fleet %s to %s for repair" %
          (fleet, fo.getUniverse().getSystem(drydock_sys_id)))
    return fleet_orders.OrderRepair(fleet, TargetSystem(drydock_sys_id))
Beispiel #9
0
def get_repair_fleet_order(fleet, current_system_id):
    """Return fleet_orders.OrderRepair for fleet to proceed to system with drydock.

    :param fleet: fleet that need to be repaired
    :type fleet: target.TargetFleet
    # TODO check if we can remove this id, because fleet already have it.
    :param current_system_id: current location of the fleet, next system if currently on starlane.
    :type current_system_id: int
    :return: order to repair
    :rtype fleet_orders.OrderRepair
    """
    # TODO Cover new mechanics where happiness increases repair rate - don't always use nearest system!
    # find nearest drydock system
    drydock_sys_id = get_best_drydock_system_id(current_system_id, fleet.id)
    if drydock_sys_id is None:
        return None

    debug("Ordering fleet %s to %s for repair" %
          (fleet, fo.getUniverse().getSystem(drydock_sys_id)))
    return fleet_orders.OrderRepair(fleet, TargetSystem(drydock_sys_id))
Beispiel #10
0
    def _get_target_for_protection_mission(self):
        """Get a target for a PROTECT_REGION mission.

        1) If primary target (system target of this mission) is under attack, move to primary target.
        2) If neighbors of primary target have local enemy forces weaker than this fleet, may move to attack
        3) If no neighboring fleets or strongest enemy force is too strong, move to defend primary target
        """
        # TODO: Also check fleet rating vs planets in decision making below not only vs fleets
        universe = fo.getUniverse()
        primary_objective = self.target.id
        debug("Trying to find target for protection mission. Target: %s", self.target)
        immediate_threat = MilitaryAI.get_system_local_threat(primary_objective)
        if immediate_threat:
            debug("Immediate threat! Moving to primary mission target")
            return primary_objective
        else:
            debug("No immediate threats.")
            # Try to eliminate neighbouring fleets
            neighbors = universe.getImmediateNeighbors(primary_objective, fo.empireID())
            threat_list = sorted(map(
                lambda x: (MilitaryAI.get_system_local_threat(x), x),
                neighbors
            ), reverse=True)

            if not threat_list:
                debug("No neighbors (?!). Moving to primary mission target")
                return primary_objective

            debug("%s", threat_list)
            top_threat, candidate_system = threat_list[0]
            if not top_threat:
                # TODO: Move into second ring but needs more careful evaluation
                # For now, consider staying at the current location if enemy
                # owns a planet here which we can bombard.
                current_system_id = self.fleet.get_current_system_id()
                if current_system_id in neighbors:
                    system = universe.getSystem(current_system_id)
                    if assertion_fails(system is not None):
                        return primary_objective
                    empire_id = fo.empireID()
                    for planet_id in system.planetIDs:
                        planet = universe.getPlanet(planet_id)
                        if (planet and
                                not planet.ownedBy(empire_id) and
                                not planet.unowned):
                            debug("Currently no neighboring threats. "
                                  "Staying for bombardment of planet %s", planet)
                            self.clear_fleet_orders()
                            self.set_target(MissionType.MILITARY, TargetSystem(current_system_id))
                            self.generate_fleet_orders()
                            self.issue_fleet_orders()
                            return INVALID_ID

                # TODO consider attacking neighboring, non-military fleets
                # - needs more careful evaluation against neighboring threats
                # empire_id = fo.empireID()
                # for sys_id in neighbors:
                #     system = universe.getSystem(sys_id)
                #     if assertion_fails(system is not None):
                #         continue
                #     local_fleets = system.fleetIDs
                #     for fleet_id in local_fleets:
                #         fleet = universe.getFleet(fleet_id)
                #         if not fleet or fleet.ownedBy(empire_id):
                #             continue
                #         return sys_id

                debug("No neighboring threats. Moving to primary mission target")
                return primary_objective

            # TODO rate against threat in target system
            # TODO only engage if can reach in 1 turn or leaves sufficient defense behind
            fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id)
            debug("This fleet rating: %d. Enemy Rating: %d", fleet_rating, top_threat)
            safety_factor = get_aistate().character.military_safety_factor()
            if fleet_rating < safety_factor*top_threat:
                debug("Neighboring threat is too powerful. Moving to primary mission target")
                return primary_objective  # do not engage!

            debug("Engaging neighboring threat: %d", candidate_system)
            return candidate_system
Beispiel #11
0
def assign_scouts_to_explore_systems():
    # TODO: use Graph Theory to explore closest systems
    universe = fo.getUniverse()
    capital_sys_id = PlanetUtilsAI.get_capital_sys_id()
    # order fleets to explore
    if not border_unexplored_system_ids or (capital_sys_id == INVALID_ID):
        return
    exp_systems_by_dist = sorted(
        (universe.linearDistance(capital_sys_id, x), x)
        for x in border_unexplored_system_ids)
    debug(
        "Exploration system considering following system-distance pairs:\n  %s"
        % ("\n  ".join("%3d: %5.1f" % (sys_id, dist)
                       for (dist, sys_id) in exp_systems_by_dist)))
    explore_list = [sys_id for dist, sys_id in exp_systems_by_dist]

    already_covered, available_scouts = get_current_exploration_info()

    debug("Explorable system IDs: %s" % explore_list)
    debug("Already targeted: %s" % already_covered)
    aistate = get_aistate()
    needs_vis = aistate.misc.setdefault('needs_vis', [])
    check_list = aistate.needsEmergencyExploration + needs_vis + explore_list
    if INVALID_ID in check_list:  # shouldn't normally happen, unless due to bug elsewhere
        for sys_list, name in [(aistate.needsEmergencyExploration,
                                "aistate.needsEmergencyExploration"),
                               (needs_vis, "needs_vis"),
                               (explore_list, "explore_list")]:
            if INVALID_ID in sys_list:
                error("INVALID_ID found in " + name, exc_info=True)
    # emergency coverage can be due to invasion detection trouble, etc.
    debug("Check list: %s" % check_list)
    needs_coverage = [
        sys_id for sys_id in check_list
        if sys_id not in already_covered and sys_id != INVALID_ID
    ]
    debug("Needs coverage: %s" % needs_coverage)

    debug("Available scouts & AIstate locs: %s" %
          [(x, aistate.fleetStatus.get(x, {}).get('sysID', INVALID_ID))
           for x in available_scouts])
    debug("Available scouts & universe locs: %s" %
          [(x, universe.getFleet(x).systemID) for x in available_scouts])
    if not needs_coverage or not available_scouts:
        return

    # clean up targets which can not or don't need to be scouted
    for sys_id in list(needs_coverage):
        if sys_id not in explore_list:  # doesn't necessarily need direct visit
            if universe.getVisibility(sys_id,
                                      fo.empireID()) >= fo.visibility.partial:
                # already got visibility; remove from visit lists and skip
                if sys_id in needs_vis:
                    del needs_vis[needs_vis.index(sys_id)]
                if sys_id in aistate.needsEmergencyExploration:
                    del aistate.needsEmergencyExploration[
                        aistate.needsEmergencyExploration.index(sys_id)]
                debug(
                    "system id %d already currently visible; skipping exploration"
                    % sys_id)
                needs_coverage.remove(sys_id)
                continue

        # skip systems threatened by monsters
        sys_status = aistate.systemStatus.setdefault(sys_id, {})
        if (not aistate.character.may_explore_system(
                sys_status.setdefault('monsterThreat', 0))
                or (fo.currentTurn() < 20
                    and aistate.systemStatus[sys_id]['monsterThreat'] > 0)):
            debug(
                "Skipping exploration of system %d due to Big Monster, threat %d"
                % (sys_id, aistate.systemStatus[sys_id]['monsterThreat']))
            needs_coverage.remove(sys_id)
            continue

    # find the jump distance for all possible scout-system pairings
    options = []
    available_scouts = set(available_scouts)
    for fleet_id in available_scouts:
        fleet_mission = aistate.get_fleet_mission(fleet_id)
        start = fleet_mission.get_location_target()
        for sys_id in needs_coverage:
            target = TargetSystem(sys_id)
            path = MoveUtilsAI.can_travel_to_system(fleet_id,
                                                    start,
                                                    target,
                                                    ensure_return=True)
            if not path:
                continue
            num_jumps = len(
                path) - 1  # -1 as path contains the original system
            options.append((num_jumps, fleet_id, sys_id))

    # Apply a simple, greedy heuristic to match scouts to nearby systems:
    # Always choose the shortest possible path from the remaining scout-system pairing.
    # This is clearly not optimal in the general case but it works well enough for now.
    # TODO: Consider using a more sophisticated assignment algorithm
    options.sort()
    while options:
        debug("Remaining options: %s" % options)
        _, fleet_id, sys_id = options[0]
        fleet_mission = aistate.get_fleet_mission(fleet_id)
        target = TargetSystem(sys_id)
        info("Sending fleet %d to explore %s" % (fleet_id, target))
        fleet_mission.set_target(MissionType.EXPLORATION, target)
        options = [
            option for option in options
            if option[1] != fleet_id and option[2] != sys_id
        ]
        available_scouts.remove(fleet_id)
        needs_coverage.remove(sys_id)

    debug("Exploration assignment finished.")
    debug("Unassigned scouts: %s" % available_scouts)
    debug("Unassigned exploration targets: %s" % needs_coverage)
Beispiel #12
0
def assign_military_fleets_to_systems(use_fleet_id_list=None,
                                      allocations=None,
                                      round=1):
    # assign military fleets to military theater systems
    global _military_allocations
    universe = fo.getUniverse()
    if allocations is None:
        allocations = []

    doing_main = (use_fleet_id_list is None)
    aistate = get_aistate()
    if doing_main:
        aistate.misc['ReassignedFleetMissions'] = []
        base_defense_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(
            MissionType.ORBITAL_DEFENSE)
        unassigned_base_defense_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types(
            base_defense_ids)
        for fleet_id in unassigned_base_defense_ids:
            fleet = universe.getFleet(fleet_id)
            if not fleet:
                continue
            sys_id = fleet.systemID
            target = TargetSystem(sys_id)
            fleet_mission = aistate.get_fleet_mission(fleet_id)
            fleet_mission.clear_fleet_orders()
            fleet_mission.clear_target()
            mission_type = MissionType.ORBITAL_DEFENSE
            fleet_mission.set_target(mission_type, target)

        all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(
            MissionType.MILITARY)
        if not all_military_fleet_ids:
            _military_allocations = []
            return
        avail_mil_fleet_ids = list(
            FleetUtilsAI.extract_fleet_ids_without_mission_types(
                all_military_fleet_ids))
        mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(
            avail_mil_fleet_ids)
        these_allocations = _military_allocations
        debug("==================================================")
        debug("Assigning military fleets")
        debug("---------------------------------")
    else:
        avail_mil_fleet_ids = list(use_fleet_id_list)
        mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(
            avail_mil_fleet_ids)
        these_allocations = allocations

    # send_for_repair(mil_needing_repair_ids) #currently, let get taken care of by AIFleetMission.generate_fleet_orders()

    # get systems to defend

    avail_mil_fleet_ids = set(avail_mil_fleet_ids)
    for sys_id, alloc, minalloc, rvp, takeAny in these_allocations:
        if not doing_main and not avail_mil_fleet_ids:
            break
        found_fleets = []
        found_stats = {}
        ensure_return = sys_id not in set(AIstate.colonyTargetedSystemIDs +
                                          AIstate.outpostTargetedSystemIDs +
                                          AIstate.invasionTargetedSystemIDs)
        these_fleets = FleetUtilsAI.get_fleets_for_mission(
            target_stats={
                'rating': alloc,
                'ratingVsPlanets': rvp,
                'target_system': TargetSystem(sys_id)
            },
            min_stats={
                'rating': minalloc,
                'ratingVsPlanets': rvp,
                'target_system': TargetSystem(sys_id)
            },
            cur_stats=found_stats,
            starting_system=sys_id,
            fleet_pool_set=avail_mil_fleet_ids,
            fleet_list=found_fleets,
            ensure_return=ensure_return)
        if not these_fleets:
            if not found_fleets or not (FleetUtilsAI.stats_meet_reqs(
                    found_stats, {'rating': minalloc}) or takeAny):
                if doing_main:
                    if _verbose_mil_reporting:
                        debug(
                            "NO available/suitable military allocation for system %d ( %s ) -- requested allocation %8d, found available rating %8d in fleets %s"
                            %
                            (sys_id, universe.getSystem(sys_id).name, minalloc,
                             found_stats.get('rating', 0), found_fleets))
                avail_mil_fleet_ids.update(found_fleets)
                continue
            else:
                these_fleets = found_fleets
        elif doing_main and _verbose_mil_reporting:
            debug(
                "FULL+ military allocation for system %d ( %s ) -- requested allocation %8d, got %8d with fleets %s"
                % (sys_id, universe.getSystem(sys_id).name, alloc,
                   found_stats.get('rating', 0), these_fleets))
        target = TargetSystem(sys_id)
        for fleet_id in these_fleets:
            fo.issueAggressionOrder(fleet_id, True)
            fleet_mission = aistate.get_fleet_mission(fleet_id)
            fleet_mission.clear_fleet_orders()
            fleet_mission.clear_target()
            if sys_id in set(AIstate.colonyTargetedSystemIDs +
                             AIstate.outpostTargetedSystemIDs +
                             AIstate.invasionTargetedSystemIDs):
                mission_type = MissionType.SECURE
            else:
                mission_type = MissionType.MILITARY
            fleet_mission.set_target(mission_type, target)
            fleet_mission.generate_fleet_orders()
            if not doing_main:
                aistate.misc.setdefault('ReassignedFleetMissions',
                                        []).append(fleet_mission)

    if doing_main:
        debug("---------------------------------")
    last_round = 3
    last_round_name = "LastRound"
    if round <= last_round:
        # check if any fleets remain unassigned
        all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(
            MissionType.MILITARY)
        avail_mil_fleet_ids = list(
            FleetUtilsAI.extract_fleet_ids_without_mission_types(
                all_military_fleet_ids))
        allocations = []
        round += 1
        thisround = "Extras Remaining Round %d" % round if round < last_round else last_round_name
        if avail_mil_fleet_ids:
            debug("Still have available military fleets: %s" %
                  avail_mil_fleet_ids)
            allocations = get_military_fleets(
                mil_fleets_ids=avail_mil_fleet_ids,
                try_reset=False,
                thisround=thisround)
        if allocations:
            assign_military_fleets_to_systems(
                use_fleet_id_list=avail_mil_fleet_ids,
                allocations=allocations,
                round=round)
 def get_location_target(self) -> TargetSystem:
     # TODO add parameter turn
     return TargetSystem(get_fleet_position(self.fleet.id))
Beispiel #14
0
def get_best_drydock_system_id(start_system_id: int,
                               fleet_id: int) -> Optional[int]:
    """
    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
    :param fleet_id: fleet that needs path to drydock
    :return: most suitable system id where the fleet should be repaired.
    """
    if start_system_id == INVALID_ID:
        warning("get_best_drydock_system_id passed bad system id.")
        return None

    if fleet_id == INVALID_ID:
        warning("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 get_empire_drydocks().items():
        if sys_id == INVALID_ID:
            warning("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
Beispiel #15
0
def get_fleets_for_mission(target_stats,
                           min_stats,
                           cur_stats,
                           starting_system,
                           fleet_pool_set,
                           fleet_list,
                           species="",
                           ensure_return=False):
    """Get fleets for a mission.

    Implements breadth-first search through systems starting at the **starting_sytem**.
    In each system, local fleets are checked if they are in the allowed **fleet_pool_set** and suitable for the mission.
    If so, they are added to the **fleet_list** and **cur_stats** is updated with the currently selected fleet summary.
    The search continues until the requirements defined in **target_stats** are met or there are no more systems/fleets.
    In that case, if the **min_stats** are covered, the **fleet_list** is returned anyway.
    Otherwise, an empty list is returned by the function, in which case the caller can make an evaluation of
    an emergency use of the found fleets in fleet_list; if not to be used they should be added back to the main pool.

    :param target_stats: stats the fleet should ideally meet
    :type target_stats: dict
    :param min_stats: minimum stats the final fleet must meet to be accepted
    :type min_stats: dict
    :param cur_stats: (**mutated**) stat summary of selected fleets
    :type cur_stats: dict
    :param starting_system: system_id where breadth-first-search is centered
    :type starting_system: int
    :param fleet_pool_set: (**mutated**) fleets allowed to be selected. Split fleed_ids are added, used ones removed.
    :type: fleet_pool_set: set[int]
    :param fleet_list: (**mutated**) fleets that are selected for the mission. Gets filled during the call.
    :type fleet_list: list[int]
    :param species: species for colonization mission
    :type species: str
    :param bool ensure_return: If true, fleet must have sufficient fuel to return into supply after mission
    :return: List of selected fleet_ids or empty list if couldn't meet minimum requirements.
    :rtype: list[int]
    """
    universe = fo.getUniverse()
    colonization_roles = (ShipRoleType.CIVILIAN_COLONISATION,
                          ShipRoleType.BASE_COLONISATION)
    systems_enqueued = [starting_system]
    systems_visited = []
    # loop over systems in a breadth-first-search trying to find nearby suitable ships in fleet_pool_set
    aistate = get_aistate()
    while systems_enqueued and fleet_pool_set:
        this_system_id = systems_enqueued.pop(0)
        this_system_obj = TargetSystem(this_system_id)
        systems_visited.append(this_system_id)
        accessible_fleets = aistate.systemStatus.get(this_system_id, {}).get(
            'myFleetsAccessible', [])
        fleets_here = [
            fid for fid in accessible_fleets if fid in fleet_pool_set
        ]
        # loop over all fleets in the system, split them if possible and select suitable ships
        while fleets_here:
            fleet_id = fleets_here.pop(0)
            fleet = universe.getFleet(fleet_id)
            if not fleet:  # TODO should be checked before passed to the function
                fleet_pool_set.remove(fleet_id)
                continue
            # try splitting fleet
            if fleet.numShips > 1:
                debug("Splitting candidate fleet to get ships for mission.")
                new_fleets = split_fleet(fleet_id)
                fleet_pool_set.update(new_fleets)
                fleets_here.extend(new_fleets)

            if ('target_system' in target_stats
                    and not MoveUtilsAI.can_travel_to_system(
                        fleet_id,
                        this_system_obj,
                        target_stats['target_system'],
                        ensure_return=ensure_return)):
                continue

            # check species for colonization missions
            if species:
                for ship_id in fleet.shipIDs:
                    ship = universe.getShip(ship_id)
                    if (ship and aistate.get_ship_role(
                            ship.design.id) in colonization_roles
                            and species == ship.speciesName):
                        break
                else:  # no suitable species found
                    continue
            # check troop capacity for invasion missions
            troop_capacity = 0
            if 'troopCapacity' in target_stats:
                troop_capacity = count_troops_in_fleet(fleet_id)
                if troop_capacity <= 0:
                    continue

            # check if we need additional rating vs planets
            this_rating_vs_planets = 0
            if 'ratingVsPlanets' in target_stats:
                this_rating_vs_planets = aistate.get_rating(
                    fleet_id, against_planets=True)
                if this_rating_vs_planets <= 0 and cur_stats.get(
                        'rating', 0) >= target_stats.get('rating', 0):
                    # we already have enough general rating, so do not add any more warships useless against planets
                    continue

            # all checks passed, add ship to selected fleets and update the stats
            try:
                fleet_pool_set.remove(fleet_id)
            except KeyError:
                error(
                    "After having split a fleet, the original fleet apparently no longer exists.",
                    exc_info=True)
                continue
            fleet_list.append(fleet_id)

            this_rating = aistate.get_rating(fleet_id)
            cur_stats['rating'] = CombatRatingsAI.combine_ratings(
                cur_stats.get('rating', 0), this_rating)
            if 'ratingVsPlanets' in target_stats:
                cur_stats['ratingVsPlanets'] = CombatRatingsAI.combine_ratings(
                    cur_stats.get('ratingVsPlanets', 0),
                    this_rating_vs_planets)
            if 'troopCapacity' in target_stats:
                cur_stats['troopCapacity'] = cur_stats.get('troopCapacity',
                                                           0) + troop_capacity
            # if we already meet the requirements, we can stop looking for more ships
            if (sum(len(universe.getFleet(fid).shipIDs) for fid in fleet_list) >= 1) \
                    and stats_meet_reqs(cur_stats, target_stats):
                return fleet_list

        # finished system without meeting requirements. Add neighboring systems to search queue.
        for neighbor_id in universe.getImmediateNeighbors(
                this_system_id, fo.empireID()):
            if all((neighbor_id not in systems_visited, neighbor_id
                    not in systems_enqueued, neighbor_id
                    in aistate.exploredSystemIDs)):
                systems_enqueued.append(neighbor_id)
    # we ran out of systems or fleets to check but did not meet requirements yet.
    if stats_meet_reqs(cur_stats, min_stats) and any(
            universe.getFleet(fid).shipIDs for fid in fleet_list):
        return fleet_list
    else:
        return []
Beispiel #16
0
def assign_military_fleets_to_systems(use_fleet_id_list=None, allocations=None, round=1):
    # assign military fleets to military theater systems
    global _military_allocations
    universe = fo.getUniverse()
    if allocations is None:
        allocations = []

    doing_main = (use_fleet_id_list is None)
    aistate = get_aistate()
    if doing_main:
        aistate.misc['ReassignedFleetMissions'] = []
        base_defense_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.ORBITAL_DEFENSE)
        unassigned_base_defense_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types(base_defense_ids)
        for fleet_id in unassigned_base_defense_ids:
            fleet = universe.getFleet(fleet_id)
            if not fleet:
                continue
            sys_id = fleet.systemID
            target = TargetSystem(sys_id)
            fleet_mission = aistate.get_fleet_mission(fleet_id)
            fleet_mission.clear_fleet_orders()
            fleet_mission.clear_target()
            mission_type = MissionType.ORBITAL_DEFENSE
            fleet_mission.set_target(mission_type, target)

        all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)
        if not all_military_fleet_ids:
            _military_allocations = []
            return
        avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids))
        mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids)
        these_allocations = _military_allocations
        debug("==================================================")
        debug("Assigning military fleets")
        debug("---------------------------------")
    else:
        avail_mil_fleet_ids = list(use_fleet_id_list)
        mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids)
        these_allocations = allocations

    # send_for_repair(mil_needing_repair_ids) #currently, let get taken care of by AIFleetMission.generate_fleet_orders()

    # get systems to defend

    avail_mil_fleet_ids = set(avail_mil_fleet_ids)
    for sys_id, alloc, minalloc, rvp, takeAny in these_allocations:
        if not doing_main and not avail_mil_fleet_ids:
            break
        debug("Allocating for: %s", TargetSystem(sys_id))
        found_fleets = []
        found_stats = {}
        ensure_return = sys_id not in set(AIstate.colonyTargetedSystemIDs
                                          + AIstate.outpostTargetedSystemIDs
                                          + AIstate.invasionTargetedSystemIDs)
        these_fleets = FleetUtilsAI.get_fleets_for_mission(
            target_stats={'rating': alloc, 'ratingVsPlanets': rvp, 'target_system': TargetSystem(sys_id)},
            min_stats={'rating': minalloc, 'ratingVsPlanets': rvp, 'target_system': TargetSystem(sys_id)},
            cur_stats=found_stats, starting_system=sys_id, fleet_pool_set=avail_mil_fleet_ids,
            fleet_list=found_fleets, ensure_return=ensure_return)
        if not these_fleets:
            debug("Could not allocate any fleets.")
            if not found_fleets or not (FleetUtilsAI.stats_meet_reqs(found_stats, {'rating': minalloc}) or takeAny):
                if doing_main:
                    if _verbose_mil_reporting:
                        debug("NO available/suitable military allocation for system %d ( %s ) "
                              "-- requested allocation %8d, found available rating %8d in fleets %s"
                              % (sys_id, universe.getSystem(sys_id).name, minalloc,
                                 found_stats.get('rating', 0), found_fleets))
                avail_mil_fleet_ids.update(found_fleets)
                continue
            else:
                these_fleets = found_fleets
        else:
            debug("Assigning fleets %s to target %s", these_fleets, TargetSystem(sys_id))
            if doing_main and _verbose_mil_reporting:
                debug("FULL+ military allocation for system %d ( %s )"
                      " -- requested allocation %8d, got %8d with fleets %s"
                      % (sys_id, universe.getSystem(sys_id).name, alloc, found_stats.get('rating', 0), these_fleets))
        target = TargetSystem(sys_id)
        for fleet_id in these_fleets:
            fo.issueAggressionOrder(fleet_id, True)
            fleet_mission = aistate.get_fleet_mission(fleet_id)
            fleet_mission.clear_fleet_orders()
            fleet_mission.clear_target()
            if sys_id in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs):
                mission_type = MissionType.SECURE
            elif state.get_empire_planets_by_system(sys_id):
                mission_type = MissionType.PROTECT_REGION
            else:
                mission_type = MissionType.MILITARY
            fleet_mission.set_target(mission_type, target)
            fleet_mission.generate_fleet_orders()
            if not doing_main:
                aistate.misc.setdefault('ReassignedFleetMissions', []).append(fleet_mission)

    if doing_main:
        debug("---------------------------------")
    last_round = 3
    last_round_name = "LastRound"
    if round <= last_round:
        # check if any fleets remain unassigned
        all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)
        avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids))
        allocations = []
        round += 1
        thisround = "Extras Remaining Round %d" % round if round < last_round else last_round_name
        if avail_mil_fleet_ids:
            debug("Round %s - still have available military fleets: %s", thisround, avail_mil_fleet_ids)
            allocations = get_military_fleets(mil_fleets_ids=avail_mil_fleet_ids, try_reset=False, thisround=thisround)
        if allocations:
            assign_military_fleets_to_systems(use_fleet_id_list=avail_mil_fleet_ids, allocations=allocations, round=round)
        else:
            # assign remaining fleets to nearest systems to protect.
            all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)
            avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids))

            def system_score(_fid, _sys_id):
                """Helper function to rank systems by priority"""
                jump_distance = universe.jumpDistance(_fid, _sys_id)
                if get_system_local_threat(_sys_id):
                    weight = 10
                elif get_system_neighbor_threat(_sys_id):
                    weight = 3
                elif get_system_jump2_threat(_sys_id):
                    weight = 1
                else:
                    weight = 1 / max(.5, float(state.get_distance_to_enemy_supply(_sys_id)))**1.25
                return float(weight) / (jump_distance+1)

            for fid in avail_mil_fleet_ids:
                fleet = universe.getFleet(fid)
                FleetUtilsAI.get_fleet_system(fleet)
                systems = state.get_empire_planets_by_system().keys()
                if not systems:
                    continue
                sys_id = max(systems, key=lambda x: system_score(fid, x))

                debug("Assigning leftover %s to system %d "
                      "- nothing better to do.", fleet, sys_id)

                fleet_mission = aistate.get_fleet_mission(fid)
                fleet_mission.clear_fleet_orders()
                target_system = TargetSystem(sys_id)
                fleet_mission.set_target(MissionType.PROTECT_REGION, target_system)
                fleet_mission.generate_fleet_orders()