1from logging import warning, debug 2 3import freeOrionAIInterface as fo # pylint: disable=import-error 4from aistate_interface import get_aistate 5import AIstate 6import fleet_orders 7import PlanetUtilsAI 8import pathfinding 9from AIDependencies import INVALID_ID, DRYDOCK_HAPPINESS_THRESHOLD 10from target import TargetSystem 11from turn_state import state 12 13 14def create_move_orders_to_system(fleet, target): 15 """ 16 Create a list of move orders from the fleet's current system to the target system. 17 18 :param fleet: Fleet to be moved 19 :type fleet: target.TargetFleet 20 :param target: target system 21 :type target: target.TargetSystem 22 :return: list of move orders 23 :rtype: list[fleet_orders.OrdersMove] 24 """ 25 # TODO: use Graph Theory to construct move orders 26 # TODO: add priority 27 starting_system = fleet.get_system() # current fleet location or current target system if on starlane 28 if starting_system == target: 29 # nothing to do here 30 return [] 31 # if the mission does not end at the targeted system, make sure we can actually return to supply after moving. 32 ensure_return = target.id not in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs 33 + AIstate.invasionTargetedSystemIDs) 34 system_targets = can_travel_to_system(fleet.id, starting_system, target, ensure_return=ensure_return) 35 result = [fleet_orders.OrderMove(fleet, system) for system in system_targets] 36 if not result and starting_system.id != target.id: 37 warning("fleet %s can't travel to system %s" % (fleet.id, target)) 38 return result 39 40 41def can_travel_to_system(fleet_id, start, target, ensure_return=False): 42 """ 43 Return list systems to be visited. 44 45 :param fleet_id: 46 :type fleet_id: int 47 :param start: 48 :type start: target.TargetSystem 49 :param target: 50 :type target: target.TargetSystem 51 :param ensure_return: 52 :type ensure_return: bool 53 :return: 54 :rtype: list 55 """ 56 if start == target: 57 return [TargetSystem(start.id)] 58 59 debug("Requesting path for fleet %s from %s to %s" % (fo.getUniverse().getFleet(fleet_id), start, target)) 60 target_distance_from_supply = -min(state.get_system_supply(target.id), 0) 61 62 # low-aggression AIs may not travel far from supply 63 if not get_aistate().character.may_travel_beyond_supply(target_distance_from_supply): 64 debug("May not move %d out of supply" % target_distance_from_supply) 65 return [] 66 67 min_fuel_at_target = target_distance_from_supply if ensure_return else 0 68 path_info = pathfinding.find_path_with_resupply(start.id, target.id, fleet_id, 69 minimum_fuel_at_target=min_fuel_at_target) 70 if path_info is None: 71 debug("Found no valid path.") 72 return [] 73 74 debug("Found valid path: %s" % str(path_info)) 75 return [TargetSystem(sys_id) for sys_id in path_info.path] 76 77 78def get_nearest_supplied_system(start_system_id): 79 """ Return systemAITarget of nearest supplied system from starting system startSystemID.""" 80 empire = fo.getEmpire() 81 fleet_supplyable_system_ids = empire.fleetSupplyableSystemIDs 82 universe = fo.getUniverse() 83 84 if start_system_id in fleet_supplyable_system_ids: 85 return TargetSystem(start_system_id) 86 else: 87 min_jumps = 9999 # infinity 88 supply_system_id = INVALID_ID 89 for system_id in fleet_supplyable_system_ids: 90 if start_system_id != INVALID_ID and system_id != INVALID_ID: 91 least_jumps_len = universe.jumpDistance(start_system_id, system_id) 92 if least_jumps_len < min_jumps: 93 min_jumps = least_jumps_len 94 supply_system_id = system_id 95 return TargetSystem(supply_system_id) 96 97 98def get_best_drydock_system_id(start_system_id, fleet_id): 99 """ 100 101 Get system_id of best drydock capable of repair, where best is nearest drydock 102 that has a current and target happiness greater than the HAPPINESS_THRESHOLD 103 with a path that is not blockaded or that the fleet can fight through to with 104 acceptable losses. 105 106 :param start_system_id: current location of fleet - used to find closest target 107 :type start_system_id: int 108 :param fleet_id: fleet that needs path to drydock 109 :type: int 110 :return: most suitable system id where the fleet should be repaired. 111 :rtype: int 112 """ 113 if start_system_id == INVALID_ID: 114 warning("get_best_drydock_system_id passed bad system id.") 115 return None 116 117 if fleet_id == INVALID_ID: 118 warning("get_best_drydock_system_id passed bad fleet id.") 119 return None 120 121 universe = fo.getUniverse() 122 start_system = TargetSystem(start_system_id) 123 drydock_system_ids = set() 124 for sys_id, pids in state.get_empire_drydocks().items(): 125 if sys_id == INVALID_ID: 126 warning("get_best_drydock_system_id passed bad drydock sys_id.") 127 continue 128 for pid in pids: 129 planet = universe.getPlanet(pid) 130 if (planet and 131 planet.currentMeterValue(fo.meterType.happiness) >= DRYDOCK_HAPPINESS_THRESHOLD and 132 planet.currentMeterValue(fo.meterType.targetHappiness) >= DRYDOCK_HAPPINESS_THRESHOLD): 133 drydock_system_ids.add(sys_id) 134 break 135 136 sys_distances = sorted([(universe.jumpDistance(start_system_id, sys_id), sys_id) 137 for sys_id in drydock_system_ids]) 138 139 aistate = get_aistate() 140 fleet_rating = aistate.get_rating(fleet_id) 141 for _, dock_sys_id in sys_distances: 142 dock_system = TargetSystem(dock_sys_id) 143 path = can_travel_to_system(fleet_id, start_system, dock_system) 144 145 path_rating = sum([aistate.systemStatus[path_sys.id]['totalThreat'] 146 for path_sys in path]) 147 148 SAFETY_MARGIN = 10 149 if SAFETY_MARGIN * path_rating <= fleet_rating: 150 debug("Drydock recommendation %s from %s for fleet %s with fleet rating %.1f and path rating %.1f." 151 % (dock_system, start_system, universe.getFleet(fleet_id), fleet_rating, path_rating)) 152 return dock_system.id 153 154 debug("No safe drydock recommendation from %s for fleet %s with fleet rating %.1f." 155 % (start_system, universe.getFleet(fleet_id), fleet_rating)) 156 return None 157 158 159def get_safe_path_leg_to_dest(fleet_id, start_id, dest_id): 160 start_targ = TargetSystem(start_id) 161 dest_targ = TargetSystem(dest_id) 162 # TODO actually get a safe path 163 this_path = can_travel_to_system(fleet_id, start_targ, dest_targ, ensure_return=False) 164 path_ids = [targ.id for targ in this_path if targ.id != start_id] + [start_id] 165 universe = fo.getUniverse() 166 debug("Fleet %d requested safe path leg from %s to %s, found path %s" % ( 167 fleet_id, universe.getSystem(start_id), universe.getSystem(dest_id), PlanetUtilsAI.sys_name_ids(path_ids))) 168 return path_ids[0] 169 170 171def get_resupply_fleet_order(fleet_target, current_system_target): 172 """Return fleet_orders.OrderResupply to nearest supplied system. 173 174 :param fleet_target: fleet that needs to be resupplied 175 :type fleet_target: target.TargetFleet 176 # TODO check if we can remove this id, because fleet already have it. 177 :param current_system_target: current system of fleet 178 :type current_system_target: target.TargetSystem 179 :return: order to resupply 180 :rtype fleet_orders.OrderResupply 181 """ 182 # find nearest supplied system 183 supplied_system_target = get_nearest_supplied_system(current_system_target.id) 184 # create resupply AIFleetOrder 185 return fleet_orders.OrderResupply(fleet_target, supplied_system_target) 186 187 188def get_repair_fleet_order(fleet, current_system_id): 189 """Return fleet_orders.OrderRepair for fleet to proceed to system with drydock. 190 191 :param fleet: fleet that need to be repaired 192 :type fleet: target.TargetFleet 193 # TODO check if we can remove this id, because fleet already have it. 194 :param current_system_id: current location of the fleet, next system if currently on starlane. 195 :type current_system_id: int 196 :return: order to repair 197 :rtype fleet_orders.OrderRepair 198 """ 199 # TODO Cover new mechanics where happiness increases repair rate - don't always use nearest system! 200 # find nearest drydock system 201 drydock_sys_id = get_best_drydock_system_id(current_system_id, fleet.id) 202 if drydock_sys_id is None: 203 return None 204 205 debug("Ordering fleet %s to %s for repair" % (fleet, fo.getUniverse().getSystem(drydock_sys_id))) 206 return fleet_orders.OrderRepair(fleet, TargetSystem(drydock_sys_id)) 207