1from logging import debug, error, warning 2 3from EnumsAI import ShipRoleType, MissionType 4import EspionageAI 5import FleetUtilsAI 6import freeOrionAIInterface as fo # pylint: disable=import-error 7from aistate_interface import get_aistate 8import MilitaryAI 9import MoveUtilsAI 10import CombatRatingsAI 11from target import TargetFleet, TargetSystem, TargetPlanet 12 13 14def trooper_move_reqs_met(main_fleet_mission, order, verbose): 15 """ 16 Indicates whether or not move requirements specific to invasion troopers are met for the provided mission and order. 17 :type main_fleet_mission: AIFleetMission.AIFleetMission 18 :type order: OrderMove 19 :param verbose: whether to print verbose decision details 20 :type verbose: bool 21 :rtype: bool 22 """ 23 # Don't advance outside of our fleet-supply zone unless the target either has no shields at all or there 24 # is already a military fleet assigned to secure the target, and don't take final jump unless the planet is 25 # (to the AI's knowledge) down to zero shields. Additional checks will also be done by the later 26 # generic movement code 27 invasion_target = main_fleet_mission.target 28 invasion_planet = invasion_target.get_object() 29 invasion_system = invasion_target.get_system() 30 supplied_systems = fo.getEmpire().fleetSupplyableSystemIDs 31 # if about to leave supply lines 32 if order.target.id not in supplied_systems or fo.getUniverse().jumpDistance(order.fleet.id, invasion_system.id) < 5: 33 if invasion_planet.currentMeterValue(fo.meterType.maxShield): 34 military_support_fleets = MilitaryAI.get_military_fleets_with_target_system(invasion_system.id) 35 if not military_support_fleets: 36 if verbose: 37 debug("trooper_move_reqs_met() holding Invasion fleet %d before leaving supply " 38 "because target (%s) has nonzero max shields and there is not yet a military fleet " 39 "assigned to secure the target system." % (order.fleet.id, invasion_planet)) 40 return False 41 42 # if there is a threat in the enemy system, do give military ships at least 1 turn to clear it 43 delay_to_move_troops = 1 if MilitaryAI.get_system_local_threat(order.target.id) else 0 44 45 def eta(fleet_id): 46 return FleetUtilsAI.calculate_estimated_time_of_arrival(fleet_id, invasion_system.id) 47 48 eta_this_fleet = eta(order.fleet.id) 49 if all(((eta_this_fleet - delay_to_move_troops) <= eta(fid) and eta(fid)) 50 for fid in military_support_fleets): 51 if verbose: 52 debug("trooper_move_reqs_met() holding Invasion fleet %d before leaving supply " 53 "because target (%s) has nonzero max shields and no assigned military fleet would arrive" 54 "at least %d turn earlier than the invasion fleet" % (order.fleet.id, invasion_planet, 55 delay_to_move_troops)) 56 return False 57 58 if verbose: 59 debug("trooper_move_reqs_met() allowing Invasion fleet %d to leave supply " 60 "because target (%s) has zero max shields or there is a military fleet assigned to secure " 61 "the target system which will arrive at least 1 turn before the invasion fleet.", 62 order.fleet.id, invasion_planet) 63 return True 64 65 66class AIFleetOrder: 67 """Stores information about orders which can be executed.""" 68 TARGET_TYPE = None 69 ORDER_NAME = '' 70 fleet = None # type: target.TargetFleet 71 target = None # type: target.Target 72 73 def __init__(self, fleet, target): 74 """ 75 :param fleet: fleet to execute order 76 :type fleet: target.TargetFleet 77 :param target: fleet target, depends of order type 78 :type target: target.Target 79 """ 80 if not isinstance(fleet, TargetFleet): 81 error("Order required fleet got %s" % type(fleet)) 82 83 if not isinstance(target, self.TARGET_TYPE): 84 error("Target is not allowed, got %s expect %s" % (type(target), self.TARGET_TYPE)) 85 86 self.fleet = fleet 87 self.target = target 88 self.executed = False 89 self.order_issued = False 90 91 def __setstate__(self, state): 92 # construct the universe objects from stored ids 93 state["fleet"] = TargetFleet(state["fleet"]) 94 target_type = state.pop("target_type") 95 if state["target"] is not None: 96 assert self.TARGET_TYPE.object_name == target_type 97 state["target"] = self.TARGET_TYPE(state["target"]) 98 self.__dict__ = state 99 100 def __getstate__(self): 101 retval = dict(self.__dict__) 102 # do not store the universe object but only the fleet id 103 retval['fleet'] = self.fleet.id 104 if self.target is not None: 105 retval["target"] = self.target.id 106 retval["target_type"] = self.target.object_name 107 else: 108 retval["target_type"] = None 109 return retval 110 111 def is_valid(self): 112 """Check if FleetOrder could be somehow in future issued = is valid.""" 113 if self.executed and self.order_issued: 114 debug("\t\t order not valid because already executed and completed") 115 return False 116 if self.fleet and self.target: 117 return True 118 else: 119 debug("\t\t order not valid: fleet validity: %s and target validity %s" % ( 120 bool(self.fleet), bool(self.target))) 121 return False 122 123 def can_issue_order(self, verbose=False): 124 """If FleetOrder can be issued now.""" 125 # for some orders, may need to re-issue if invasion/outposting/colonization was interrupted 126 if self.executed and not isinstance(self, (OrderOutpost, OrderColonize, OrderInvade)): 127 return False 128 if not self.is_valid(): 129 return False 130 131 if verbose: 132 sys1 = self.fleet.get_system() 133 main_fleet_mission = get_aistate().get_fleet_mission(self.fleet.id) 134 debug(" Can issue %s - Mission Type %s (%s), current loc sys %d - %s" % ( 135 self, main_fleet_mission.type, 136 main_fleet_mission.type, self.fleet.id, sys1)) 137 return True 138 139 def issue_order(self): 140 if not self.can_issue_order(): # appears to be redundant with check in IAFleetMission? 141 debug(" can't issue %s" % self) 142 return False 143 # by default we now set the order as issue and executed. For any subclass where order issuence and execution 144 # is not necessarily sure, these values can be reset after any appropriate checks in the respective 145 # subclass issue_order() 146 self.order_issued = True 147 self.executed = True 148 return True 149 150 def __str__(self): 151 execute_status = 'in progress' 152 if self.executed: 153 execute_status = 'executed' 154 elif self.order_issued: 155 execute_status = 'order issued' 156 return "[%s] of %s to %s %s" % (self.ORDER_NAME, self.fleet.get_object(), 157 self.target.get_object(), execute_status) 158 159 def __eq__(self, other): 160 return type(self) == type(other) and self.fleet == other.fleet and self.target == other.target 161 162 def __hash__(self): 163 return hash(self.fleet) 164 165 166class OrderMove(AIFleetOrder): 167 ORDER_NAME = 'move' 168 TARGET_TYPE = TargetSystem 169 170 def can_issue_order(self, verbose=False): 171 if not super(OrderMove, self).can_issue_order(verbose=verbose): 172 return False 173 # TODO: figure out better way to have invasions (& possibly colonizations) 174 # require visibility on target without needing visibility of all intermediate systems 175 # if False and main_mission_type not in [MissionType.ATTACK, # TODO: consider this later 176 # MissionType.MILITARY, 177 # MissionType.SECURE, 178 # MissionType.HIT_AND_RUN, 179 # MissionType.EXPLORATION]: 180 # if not universe.getVisibility(target_id, get_aistate().empireID) >= fo.visibility.partial: 181 # #if not target_id in interior systems 182 # get_aistate().needsEmergencyExploration.append(fleet.systemID) 183 # return False 184 system_id = self.fleet.get_system().id 185 if system_id == self.target.get_system().id: 186 return True # TODO: already there, but could consider retreating 187 188 aistate = get_aistate() 189 main_fleet_mission = aistate.get_fleet_mission(self.fleet.id) 190 191 # TODO: Rate against specific enemies here 192 fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id) 193 fleet_rating_vs_planets = CombatRatingsAI.get_fleet_rating_against_planets(self.fleet.id) 194 target_sys_status = aistate.systemStatus.get(self.target.id, {}) 195 f_threat = target_sys_status.get('fleetThreat', 0) 196 m_threat = target_sys_status.get('monsterThreat', 0) 197 p_threat = target_sys_status.get('planetThreat', 0) 198 threat = f_threat + m_threat + p_threat 199 safety_factor = aistate.character.military_safety_factor() 200 universe = fo.getUniverse() 201 if main_fleet_mission.type == MissionType.INVASION and not trooper_move_reqs_met(main_fleet_mission, 202 self, verbose): 203 return False 204 if fleet_rating >= safety_factor * threat and fleet_rating_vs_planets >= p_threat: 205 return True 206 elif not p_threat and self.target.id in fo.getEmpire().supplyUnobstructedSystems: 207 return True 208 else: 209 sys1 = universe.getSystem(system_id) 210 sys1_name = sys1 and sys1.name or "unknown" 211 target_system = self.target.get_system() 212 target_system_name = (target_system and target_system.get_object().name) or "unknown" 213 # TODO: adjust calc for any departing fleets 214 my_other_fleet_rating = aistate.systemStatus.get(self.target.id, {}).get('myFleetRating', 0) 215 my_other_fleet_rating_vs_planets = aistate.systemStatus.get(self.target.id, {}).get( 216 'myFleetRatingVsPlanets', 0) 217 is_military = aistate.get_fleet_role(self.fleet.id) == MissionType.MILITARY 218 219 total_rating = CombatRatingsAI.combine_ratings(my_other_fleet_rating, fleet_rating) 220 total_rating_vs_planets = CombatRatingsAI.combine_ratings(my_other_fleet_rating_vs_planets, 221 fleet_rating_vs_planets) 222 if (my_other_fleet_rating > 3 * safety_factor * threat or 223 (is_military and total_rating_vs_planets > 2.5*p_threat and total_rating > safety_factor * threat)): 224 debug(("\tAdvancing fleet %d (rating %d) at system %d (%s) into system %d (%s) with threat %d" 225 " because of sufficient empire fleet strength already at destination" % 226 (self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, threat))) 227 return True 228 elif (threat == p_threat and 229 not self.fleet.get_object().aggressive and 230 not my_other_fleet_rating and 231 not target_sys_status.get('localEnemyFleetIDs', [-1])): 232 if verbose: 233 debug("\tAdvancing fleet %d (rating %d) at system %d (%s) " 234 "into system %d (%s) with planet threat %d because non aggressive" 235 " and no other fleets present to trigger combat" % ( 236 self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, 237 threat)) 238 return True 239 else: 240 if verbose: 241 _info = (self.fleet.id, fleet_rating, system_id, sys1_name, 242 self.target.id, target_system_name, threat) 243 debug("\tHolding fleet %d (rating %d) at system %d (%s) " 244 "before travelling to system %d (%s) with threat %d" % _info) 245 needs_vis = aistate.misc.setdefault('needs_vis', []) 246 if self.target.id not in needs_vis: 247 needs_vis.append(self.target.id) 248 return False 249 250 def issue_order(self): 251 if not super(OrderMove, self).issue_order(): 252 return False 253 fleet_id = self.fleet.id 254 system_id = self.target.get_system().id 255 fleet = self.fleet.get_object() 256 if system_id not in [fleet.systemID, fleet.nextSystemID]: 257 dest_id = system_id 258 fo.issueFleetMoveOrder(fleet_id, dest_id) 259 debug("Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) 260 aistate = get_aistate() 261 if system_id == fleet.systemID: 262 if aistate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: 263 if system_id in aistate.needsEmergencyExploration: 264 aistate.needsEmergencyExploration.remove(system_id) 265 return True 266 267 268class OrderPause(AIFleetOrder): 269 """Ensure Fleet at least temporarily halts movement at the target system.""" 270 ORDER_NAME = 'pause' 271 TARGET_TYPE = TargetSystem 272 273 def is_valid(self): 274 if not super(OrderPause, self).is_valid(): 275 return False 276 return bool(self.target.get_system().get_object()) 277 278 def issue_order(self): 279 if not super(OrderPause, self).issue_order(): 280 return False 281 # not executed until actually arrives at target system 282 self.executed = self.fleet.get_current_system_id() == self.target.get_system().id 283 284 285class OrderResupply(AIFleetOrder): 286 ORDER_NAME = 'resupply' 287 TARGET_TYPE = TargetSystem 288 289 def is_valid(self): 290 if not super(OrderResupply, self).is_valid(): 291 return False 292 return self.target.id in fo.getEmpire().fleetSupplyableSystemIDs 293 294 def issue_order(self): 295 if not super(OrderResupply, self).issue_order(): 296 return False 297 fleet_id = self.fleet.id 298 system_id = self.target.get_system().id 299 fleet = self.fleet.get_object() 300 aistate = get_aistate() 301 if system_id == fleet.systemID: 302 if aistate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: 303 if system_id in aistate.needsEmergencyExploration: 304 aistate.needsEmergencyExploration.remove(system_id) 305 return True 306 if system_id != fleet.nextSystemID: 307 self.executed = False 308 start_id = FleetUtilsAI.get_fleet_system(fleet) 309 dest_id = MoveUtilsAI.get_safe_path_leg_to_dest(fleet_id, start_id, system_id) 310 universe = fo.getUniverse() 311 debug("fleet %d with order type(%s) sent to safe leg dest %s and ultimate dest %s" % ( 312 fleet_id, self.ORDER_NAME, universe.getSystem(dest_id), universe.getSystem(system_id))) 313 fo.issueFleetMoveOrder(fleet_id, dest_id) 314 debug("Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) 315 return True 316 317 318class OrderOutpost(AIFleetOrder): 319 ORDER_NAME = 'outpost' 320 TARGET_TYPE = TargetPlanet 321 322 def is_valid(self): 323 if not super(OrderOutpost, self).is_valid(): 324 return False 325 planet = self.target.get_object() 326 if not planet.unowned: 327 # terminate early 328 self.executed = True 329 self.order_issued = True 330 return False 331 else: 332 return self.fleet.get_object().hasOutpostShips 333 334 def can_issue_order(self, verbose=False): 335 # TODO: check for separate fleet holding outpost ships 336 if not super(OrderOutpost, self).can_issue_order(verbose=verbose): 337 return False 338 universe = fo.getUniverse() 339 ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.CIVILIAN_OUTPOST) 340 if ship_id is None: 341 ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.BASE_OUTPOST) 342 ship = universe.getShip(ship_id) 343 return ship is not None and self.fleet.get_object().systemID == self.target.get_system().id and ship.canColonize 344 345 def issue_order(self): 346 if not super(OrderOutpost, self).issue_order(): 347 return False 348 # we can't know yet if the order will actually execute; instead, rely on the fact that if the order does get 349 # executed, then next turn it will be invalid 350 self.executed = False 351 planet = self.target.get_object() 352 if not planet.unowned: 353 return False 354 fleet_id = self.fleet.id 355 ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.CIVILIAN_OUTPOST) 356 if ship_id is None: 357 ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.BASE_OUTPOST) 358 if fo.issueColonizeOrder(ship_id, self.target.id): 359 debug("Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) 360 return True 361 else: 362 self.order_issued = False 363 warning("Order issuance failed: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) 364 return False 365 366 367class OrderColonize(AIFleetOrder): 368 ORDER_NAME = 'colonize' 369 TARGET_TYPE = TargetPlanet 370 371 def issue_order(self): 372 if not super(OrderColonize, self).issue_order(): 373 return False 374 # we can't know yet if the order will actually execute; instead, rely on the fact that if the order does get 375 # executed, then next turn it will be invalid 376 self.executed = False 377 378 fleet_id = self.fleet.id 379 ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.CIVILIAN_COLONISATION) 380 if ship_id is None: 381 ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.BASE_COLONISATION) 382 383 if fo.issueColonizeOrder(ship_id, self.target.id): 384 debug("Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) 385 return True 386 else: 387 self.order_issued = False 388 warning("Order issuance failed: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) 389 return False 390 391 def is_valid(self): 392 if not super(OrderColonize, self).is_valid(): 393 return False 394 planet = self.target.get_object() 395 if (planet.unowned or planet.ownedBy(fo.empireID())) and not planet.currentMeterValue(fo.meterType.population): 396 return self.fleet.get_object().hasColonyShips 397 # Otherwise, terminate early 398 self.executed = True 399 self.order_issued = True 400 return False 401 402 def can_issue_order(self, verbose=False): 403 if not super(OrderColonize, self).is_valid(): 404 return False 405 # TODO: check for separate fleet holding colony ships 406 ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.CIVILIAN_COLONISATION) 407 if ship_id is None: 408 ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.BASE_COLONISATION) 409 universe = fo.getUniverse() 410 ship = universe.getShip(ship_id) 411 if ship and not ship.canColonize: 412 warning("colonization fleet %d has no colony ship" % self.fleet.id) 413 return ship is not None and self.fleet.get_object().systemID == self.target.get_system().id and ship.canColonize 414 415 416class OrderDefend(AIFleetOrder): 417 """ 418 Used for orbital defense, have no real orders. 419 """ 420 ORDER_NAME = 'defend' 421 TARGET_TYPE = TargetSystem 422 423 424class OrderInvade(AIFleetOrder): 425 ORDER_NAME = 'invade' 426 TARGET_TYPE = TargetPlanet 427 428 def is_valid(self): 429 if not super(OrderInvade, self).is_valid(): 430 return False 431 planet = self.target.get_object() 432 planet_population = planet.currentMeterValue(fo.meterType.population) 433 if planet.unowned and not planet_population: 434 debug("\t\t invasion order not valid due to target planet status-- owned: %s and population %.1f" % ( 435 not planet.unowned, planet_population)) 436 # terminate early 437 self.executed = True 438 self.order_issued = True 439 return False 440 else: 441 return self.fleet.get_object().hasTroopShips 442 443 def can_issue_order(self, verbose=False): 444 if not super(OrderInvade, self).is_valid(): 445 return False 446 # TODO: check for separate fleet holding invasion ships 447 ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.MILITARY_INVASION, False) 448 if ship_id is None: 449 ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.BASE_INVASION) 450 universe = fo.getUniverse() 451 ship = universe.getShip(ship_id) 452 planet = self.target.get_object() 453 return all(( 454 ship is not None, 455 self.fleet.get_object().systemID == planet.systemID, 456 ship.canInvade, 457 not planet.initialMeterValue(fo.meterType.shield) 458 )) 459 460 def issue_order(self): 461 if not super(OrderInvade, self).can_issue_order(): 462 return False 463 464 universe = fo.getUniverse() 465 planet_id = self.target.id 466 planet = self.target.get_object() 467 fleet = self.fleet.get_object() 468 469 invasion_roles = (ShipRoleType.BASE_INVASION, 470 ShipRoleType.MILITARY_INVASION) 471 472 debug("Issuing order: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) 473 # will track if at least one invasion troops successfully deployed 474 result = True 475 aistate = get_aistate() 476 overkill_margin = 2 # TODO: get from character module; allows a handful of extra troops to be immediately 477 # defending planet 478 # invasion orders processed before regen takes place, so use initialMeterValue() here 479 troops_wanted = planet.initialMeterValue(fo.meterType.troops) + overkill_margin 480 troops_already_assigned = 0 # TODO: get from other fleets in same system 481 troops_assigned = 0 482 # Todo: evaluate all local troop ships (including other fleets) before using any, make sure base invasion troops 483 # are used first, and that not too many altogether are used (choosing an optimal collection to use). 484 for invasion_role in invasion_roles: # first checks base troops, then regular 485 if not result: 486 break 487 for ship_id in fleet.shipIDs: 488 if troops_already_assigned + troops_assigned >= troops_wanted: 489 break 490 ship = universe.getShip(ship_id) 491 if aistate.get_ship_role(ship.design.id) != invasion_role: 492 continue 493 494 debug("Ordering troop ship %d to invade %s" % (ship_id, planet)) 495 result = fo.issueInvadeOrder(ship_id, planet_id) and result 496 if not result: 497 shields = planet.currentMeterValue(fo.meterType.shield) 498 planet_stealth = planet.currentMeterValue(fo.meterType.stealth) 499 pop = planet.currentMeterValue(fo.meterType.population) 500 warning("Invasion order failed!") 501 debug(" -- planet has %.1f stealth, shields %.1f, %.1f population and " 502 "is owned by empire %d" % (planet_stealth, shields, pop, planet.owner)) 503 if 'needsEmergencyExploration' not in dir(aistate): 504 aistate.needsEmergencyExploration = [] 505 if fleet.systemID not in aistate.needsEmergencyExploration: 506 aistate.needsEmergencyExploration.append(fleet.systemID) 507 debug("Due to trouble invading, added system %d to Emergency Exploration List" % fleet.systemID) 508 self.executed = False 509 # debug(universe.getPlanet(planet_id).dump()) # TODO: fix fo.UniverseObject.dump() 510 break 511 troops_assigned += ship.troopCapacity 512 # TODO: split off unused troop ships into new fleet and give new orders this cycle 513 if result: 514 debug("Successfully ordered %d troopers to invade %s" % (troops_assigned, planet)) 515 return True 516 else: 517 return False 518 519 520class OrderMilitary(AIFleetOrder): 521 ORDER_NAME = 'military' 522 TARGET_TYPE = TargetSystem 523 524 def is_valid(self): 525 if not super(OrderMilitary, self).is_valid(): 526 return False 527 fleet = self.fleet.get_object() 528 # TODO: consider bombardment-only fleets/orders 529 return fleet is not None and (fleet.hasArmedShips or fleet.hasFighterShips) 530 531 def can_issue_order(self, verbose=False): 532 # TODO: consider bombardment 533 # TODO: consider simmply looking at fleet characteristics, as is done for is_valid() 534 ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.MILITARY) 535 universe = fo.getUniverse() 536 ship = universe.getShip(ship_id) 537 return (ship is not None and 538 self.fleet.get_object().systemID == self.target.id and 539 (ship.isArmed or ship.hasFighters)) 540 541 def issue_order(self): 542 if not super(OrderMilitary, self).issue_order(): 543 return False 544 target_sys_id = self.target.id 545 fleet = self.target.get_object() 546 system_status = get_aistate().systemStatus.get(target_sys_id, {}) 547 total_threat = sum(system_status.get(threat, 0) for threat in ('fleetThreat', 'planetThreat', 'monsterThreat')) 548 combat_trigger = system_status.get('fleetThreat', 0) or system_status.get('monsterThreat', 0) 549 if not combat_trigger and system_status.get('planetThreat', 0): 550 universe = fo.getUniverse() 551 system = universe.getSystem(target_sys_id) 552 for planet_id in system.planetIDs: 553 planet = universe.getPlanet(planet_id) 554 if planet.ownedBy(fo.empireID()): # TODO: also exclude at-peace planets 555 continue 556 if planet.unowned and not EspionageAI.colony_detectable_by_empire(planet_id, empire=fo.empireID()): 557 continue 558 if sum([planet.currentMeterValue(meter_type) for meter_type in 559 [fo.meterType.defense, fo.meterType.shield, fo.meterType.construction]]): 560 combat_trigger = True 561 break 562 if not all(( 563 fleet, 564 fleet.systemID == target_sys_id, 565 system_status.get('currently_visible', False), 566 not (total_threat and combat_trigger) 567 )): 568 self.executed = False 569 return True 570 571 572class OrderRepair(AIFleetOrder): 573 ORDER_NAME = 'repair' 574 TARGET_TYPE = TargetSystem 575 576 def is_valid(self): 577 if not super(OrderRepair, self).is_valid(): 578 return False 579 return self.target.id in fo.getEmpire().fleetSupplyableSystemIDs # TODO: check for drydock still there/owned 580 581 def issue_order(self): 582 if not super(OrderRepair, self).issue_order(): 583 return False 584 fleet_id = self.fleet.id 585 system_id = self.target.get_system().id 586 fleet = self.fleet.get_object() # type: fo.fleet 587 if system_id == fleet.systemID: 588 aistate = get_aistate() 589 if aistate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: 590 if system_id in aistate.needsEmergencyExploration: 591 aistate.needsEmergencyExploration.remove(system_id) 592 elif system_id != fleet.nextSystemID: 593 fo.issueAggressionOrder(fleet_id, False) 594 start_id = FleetUtilsAI.get_fleet_system(fleet) 595 dest_id = MoveUtilsAI.get_safe_path_leg_to_dest(fleet_id, start_id, system_id) 596 universe = fo.getUniverse() 597 debug("fleet %d with order type(%s) sent to safe leg dest %s and ultimate dest %s" % ( 598 fleet_id, self.ORDER_NAME, universe.getSystem(dest_id), universe.getSystem(system_id))) 599 fo.issueFleetMoveOrder(fleet_id, dest_id) 600 debug("Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) 601 ships_cur_health, ships_max_health = FleetUtilsAI.get_current_and_max_structure(fleet_id) 602 self.executed = (ships_cur_health == ships_max_health) 603 return True 604