def __init__(self, config, stats, regions, fleets): """Initializes the Combat module. Args: config (Config): kcauto-kai Config instance stats (Stats): kcauto-kai Stats instance regions (dict): dict of pre-defined kcauto-kai regions fleets (dict): dict of active CombatFleet instances """ self.config = config self.stats = stats self.regions = regions self.kc_region = regions['game'] self.observeRegion = Region(self.kc_region) self.fleets = fleets self.next_combat_time = datetime.now() self.combined_fleet = self.config.combat['combined_fleet'] self.striking_fleet = self.config.combat['striking_fleet'] self.primary_fleet = fleets[3] if self.striking_fleet else fleets[1] self.fleet_icon = 'fleet_icon_standard.png' if self.combined_fleet: self.fleet_icon = 'fleet_icon_{}.png'.format( self.config.combat['fleet_mode']) self.dmg = {} self.map = MapData(self.config.combat['map'], self.regions, self.config) self.current_position = [0, 0] self.current_node = None self.nodes_run = [] self.lbas = (LBAS(config, regions, self.map) if self.config.combat['lbas_enabled'] else None)
def _choose_and_check_availability_of_ship(self, position, criteria): """Select a ship in the ship list based on the specified position, and see if it available for switching in. Args: position (int): 0-based position in ship list criteria (dict): dictionary of criteria Returns: bool or str: result of _check_ship_availability() call """ fleet_indicator_area = Region(self.kc_region.x + 550, self.kc_region.y + 225 + (43 * position), 35, 35) if fleet_indicator_area.exists( Pattern('fleet_indicator_shiplist.png').similar( Globals.SHIP_LIST_FLEET_ICON_SIMILARITY)): # if the ship is already in a fleet, skip it return False self._choose_ship_by_position(position) availability = self._check_ship_availability(criteria) if availability is True: return True # not an actual navigation, but a click to get rid of a side panel Util.check_and_click(self.regions['lower_right'], 'page_first.png') return availability
def ShowupCheck(): r_mainmenu = Region(Region(429, 149, 596, 231)) with r_mainmenu: find("1466956847138.png").click() Region(Region(533, 615, 232, 76)).find("1466956881720.png").click() wait(1) type(Key.ESC) return
def algorithm(self, operand, *args, **kargs): if self.value: operand = Region(operand).nearby(self.value) else: operand = Region(operand).nearby() #operand = Region(operand.getX()-(self.proximity/2), operand.getY()-(self.proximity/2), operand.getW()+self.proximity, operand.getH()+self.proximity) return operand
def testCreateRegionFromList(self): regions = [Region(50,50,50,50), Region(200,50,50,50), Region(200,50,50,50), Region(200,200,50,50)] region = Region(regions) # Width/Height should have changed to 200/200 self.assertEqual(region.getX(), 50) self.assertEqual(region.getY(), 50) self.assertEqual(region.getW(), 200) self.assertEqual(region.getH(), 200)
def __init__(self, config, stats, regions, fleets): """Initializes the Combat module. Args: config (Config): kcauto-kai Config instance stats (Stats): kcauto-kai Stats instance regions (dict): dict of pre-defined kcauto-kai regions fleets (dict): dict of active CombatFleet instances """ self.enabled = True self.disabled_time = None self.config = config self.stats = stats self.regions = regions self.kc_region = regions['game'] self.fast_kc_region = Region(self.kc_region) self.fast_kc_region.setAutoWaitTimeout(0) self.observeRegion = Region(self.kc_region) self.fleets = fleets self.next_combat_time = datetime.now() self.combined_fleet = self.config.combat['combined_fleet'] self.striking_fleet = self.config.combat['striking_fleet'] self.primary_fleet = fleets[3] if self.striking_fleet else fleets[1] self.fleet_icon = 'fleet_icon_standard.png' if self.combined_fleet: self.fleet_icon = 'fleet_icon_{}.png'.format( self.config.combat['fleet_mode']) self.dmg = {} self.map = MapData(self.config.combat['map'], self.regions, self.config) self.current_position = [0, 0] self.current_node = None self.nodes_run = [] self.lbas = (LBAS(config, regions, self.map) if self.config.combat['lbas_enabled'] else None) # combat-related regions x = self.kc_region.x y = self.kc_region.y self.module_regions = { 'game': self.kc_region, 'check_fatigue': Region(x + 500, y + 135, 22, 290), 'check_damage': Region(x + 461, y + 130, 48, 300), 'check_damage_7th': Region(x + 461, y + 376, 48, 50), 'check_damage_flagship': Region(x + 290, y + 185, 70, 50), 'check_damage_combat': Region(x + 290, y + 140, 70, 320), 'event_next': Region(x + 720, y + 340, 80, 70), }
def testCanLimitRegion(self): region = Region(50,50,400,400).limit( Region(150,100,150,25) ) self.assertEqual(region.getX(), 150) self.assertEqual(region.getY(), 100) self.assertEqual(region.getW(), 150) self.assertEqual(region.getH(), 25) region = Region(100,100,200,200).limit( Region(50,50,400,400) ) self.assertEqual(region.getX(), 100) self.assertEqual(region.getY(), 100) self.assertEqual(region.getW(), 200) self.assertEqual(region.getH(), 200)
def testSetClickOffset(self): region = Region(100,100,100,100) region.setClickOffset(Location(10,10)) # Check that it is set right location = region.getClickOffset() self.assertEqual(location.getX(), 10) self.assertEqual(location.getY(), 10) location = region.getClickLocation() self.assertEqual(location.getX(), 160.0) self.assertEqual(location.getY(), 160.0)
def __init__(self, config, stats, regions, fleets, combat): """Initializes the ShipSwitcher module. Args: config (Config): kcauto Config instance stats (Stats): kcauto Stats instance regions (dict): dict of pre-defined kcauto regions fleets (dict): dict of active CombatFleet instances combat (ComabtModule): active Combat Module instance """ # create a safely-mutable copy of the config self.config = deepcopy(config) self.stats = stats self.regions = regions self.kc_region = regions['game'] self.fleets = fleets self.combat = combat self.ship_count = 1 self.ship_page_count = 1 self.ship_last_page_count = 1 self.current_shiplist_page = 1 self.temp_ship_config_dict = {} self.temp_ship_position_dict = {} self.position_cache = {} self.sparkling_cache = {} x = self.kc_region.x y = self.kc_region.y self.module_regions = { 'panels': [], 'shiplist_class_col': Region(x + 350, y + 150, 200, 285), } for slot in range(0, 6): # create panel regions per slot panel_region = Region( x + 121 + (352 * (slot % 2)), y + 135 + (113 * (slot / 2)), 330, 110) panel_region.setAutoWaitTimeout(0) self.module_regions['panels'].append(panel_region) # set sparkle check points per relevant slot if slot not in self.config.ship_switcher: continue slot_config = self.config.ship_switcher[slot] if 'sparkle' in slot_config['criteria']: self._set_sparkle_cache(slot)
def testCreateRegionFromList(self): regions = [ Region(50, 50, 50, 50), Region(200, 50, 50, 50), Region(200, 50, 50, 50), Region(200, 200, 50, 50) ] region = Region(regions) # Width/Height should have changed to 200/200 self.assertEqual(region.getX(), 50) self.assertEqual(region.getY(), 50) self.assertEqual(region.getW(), 200) self.assertEqual(region.getH(), 200)
def add(self, operand): # If we're trying to add None, just return the original region if not operand: return self regions = [self, operand] # more than one region, get min/max region minX, minY = 9999, 9999 maxX, maxY = -9999, -9999 for region in regions: if region.getX() < minX: minX = int(region.getX()) if region.getY() < minY: minY = int(region.getY()) # If this is a region type if hasattr(region, "getW") and hasattr(region, "getH"): if (region.getX() + region.getW()) > maxX: maxX = region.getX() + region.getW() if (region.getY() + region.getH()) > maxY: maxY = region.getY() + region.getH() else: if region.getX() > maxX: maxX = int(region.getX()) if region.getY() > maxY: maxY = int(region.getY()) return Region(minX, minY, maxX - minX, maxY - minY)
def __init__(self, config, stats, regions, fleets): """Initializes the Expedition module. Args: config (Config): kcauto Config instance stats (Stats): kcauto Stats instance regions (dict): dict of pre-defined kcauto regions fleets (dict): dict of active ExpeditionFleet instances """ self.enabled = True self.disabled_time = None self.config = config self.stats = stats self.regions = regions self.kc_region = regions['game'] self.fleets = fleets self.resupply = None # defined later, after initialization # expedition-related regions x = self.kc_region.x y = self.kc_region.y self.module_regions = { 'game': self.kc_region, 'expedition_ranks': Region(x + 235, y + 235, 26, 372) }
def __init__(self, config, stats, regions, fleets, combat): """Initializes the Repair module. Args: config (Config): kcauto Config instance stats (Stats): kcauto Stats instance regions (dict): dict of pre-defined kcauto regions fleets (dict): dict of active combat Fleet instances combat (ComabtModule): active Combat Module instance """ self.config = config self.stats = stats self.regions = regions self.kc_region = self.regions['game'] self.fleets = fleets self.combat = combat self.ship_count = 1 self.ship_page_count = 1 self.ship_last_page_count = 1 self.current_shiplist_page = 1 self.repair_slots = 0 self.repair_timers = [] x = self.kc_region.x y = self.kc_region.y self.module_regions = { 'repair_shiplist_fleet_markers': Region(x + 555, y + 188, 45, 462) }
def testRegionCanAddRegion(self): region = Region(100, 100, 100, 100) region = region.add(Region(100, 100, 200, 200)) # Width/Height should have changed to 200/200 self.assertEqual(region.getX(), 100) self.assertEqual(region.getY(), 100) self.assertEqual(region.getW(), 200) self.assertEqual(region.getH(), 200)
def apply(self, operand, *args, **kargs): x1 = operand.getX() + self.dx1 y1 = operand.getY() + self.dy1 x2 = operand.getX() + operand.getW() + self.dx2 y2 = operand.getY() + operand.getH() + self.dy2 operand = Region(x1, y1, x2 - x1, y2 - y1) #morph return operand
def testSetOffset(self): region = Region(100, 100, 100, 100) region = region.offset(Location(100, 100)) self.assertEqual(region.getX(), 200) self.assertEqual(region.getY(), 200) self.assertEqual(region.getW(), 100) self.assertEqual(region.getH(), 100)
def MakeFullScreen_try(): # setFindFailedResponse(SKIP) setThrowException(False) # Enlarge left bar try: Region(Region(1033, 376, 58, 222)).find("1466943356273.png").click() except FindFailed: pass # remove up tool bar try: Region(Region(1085, 131, 84, 28)).find("1466943375529.png").click() except FindFailed: pass # setFindFailedResponse(ABORT) setThrowException(True) Debug.user("Make Full Screen") return
def __init__(self, config, regions, map): """Initializes the LBAS module for use in the Combat module. Args: config (Config): kcauto Config instance regions (dict): dict of pre-defined kcauto regions map (MapData): MapData instance from the Combat module """ self.config = config self.regions = regions self.kc_region = regions['game'] self.map = map self.fatigue = {} # lbas-related regions x = self.kc_region.x y = self.kc_region.y self.module_regions = { 'check_lbas_fatigue': Region(x + 850, y + 350, 55, 330), 'lbas_mode_switcher': Region(x + 1135, y + 200, 55, 80) }
def _generate_preset_list_region(self, preset_id): """Method that generates the region to search for the fleet preset recall button. Args: preset_id (int): which nth preset to switch to (1-based) Returns: Region: Sikuli region to search the fleet preset recall button in """ # preset_id is 1-based, offset is 0-based offset = 4 if preset_id > 5 else preset_id - 1 return Region(self.kc_region.x + 275, self.kc_region.y + 185 + (offset * 52), 45, 29)
def testSetOffset(self): region = Region(100,100,100,100) region = region.offset(Location(100,100)) self.assertEqual(region.getX(), 200) self.assertEqual(region.getY(), 200) self.assertEqual(region.getW(), 100) self.assertEqual(region.getH(), 100)
def _check_ship_availability(self, criteria): """Checks the chosen ship's returns its availability for switching. Args: criteria (list): dictionary containing shipswitch criteria Returns: bool or str: True if the ship is available; False if it does not meet the criteria; 'conflict' if it meets the criteria but a ship of the same type is already in the fleet """ # wait until the panel is ready before speeding through checks self.regions['lower_right'].wait( Pattern('shiplist_shipswitch_button.png').similar(0.75), 5) # temp region for speed matching temp_region = Region(self.regions['upper_right']) temp_region.setAutoWaitTimeout(0) # check damage state; repair and heavy checked by default valid_damages = list(self.fleets[1].get_damages_at_threshold( self.config.combat['repair_limit'])) valid_damages.extend(['repair', 'heavy']) for damage in set(valid_damages): if temp_region.exists( Pattern('ship_state_dmg_{}.png'.format(damage)).similar( Globals.DAMAGE_SIMILARITY)): Util.log_warning("Candidate ship is damaged.") return False # check fatigue states if it is a criteria if 'fatigue' in criteria: for fatigue in ('medium', 'high'): if temp_region.exists( Pattern('ship_state_fatigue_{}.png'.format( fatigue)).similar(Globals.FATIGUE_SIMILARITY)): Util.log_warning("Candidate ship is fatigued.") return False # check sparkle if it is a criteria if 'sparkle' in criteria: if temp_region.exists( Pattern('sparkle_indicator_shiplist.png').similar(0.9), 2): Util.log_warning("Candidate ship is sparkled.") return False # passed criteria; check if there is a conflicting ship in fleet if Util.check_and_click(self.regions['lower_right'], 'shiplist_shipswitch_button.png'): Util.log_msg("Candidate ship successfully switched in.") return True else: Util.log_warning("Candidate ship has conflict with fleet.") return 'conflict'
def __init__(self, config, stats, regions, fleets, combat): self.config = config self.stats = stats self.regions = regions self.kc_region = regions['game'] self.fleets = fleets self.combat = combat self.ship_count = 1 self.ship_page_count = 1 self.ship_last_page_count = 1 self.current_shiplist_page = 1 self.temp_ship_config_dict = {} self.temp_ship_position_dict = {} self.position_cache = {} self.sparkling_cache = {} x = self.kc_region.x y = self.kc_region.y self.module_regions = { 'panels': [], 'shiplist_class_col': Region(x + 350, y + 150, 200, 285), } for slot in range(0, 6): # create panel regions per slot panel_region = Region(x + 121 + (352 * (slot % 2)), y + 135 + (113 * (slot / 2)), 330, 110) panel_region.setAutoWaitTimeout(0) self.module_regions['panels'].append(panel_region) # set sparkle check points per relevant slot if slot not in self.config.ship_switcher: continue slot_config = self.config.ship_switcher[slot] if 'sparkle' in slot_config['criteria']: self._set_sparkle_cache(slot)
def testRegionCanAddRegion(self): region = Region(100,100,100,100) region = region.add(Region(100,100,200,200)) # Width/Height should have changed to 200/200 self.assertEqual(region.getX(), 100) self.assertEqual(region.getY(), 100) self.assertEqual(region.getW(), 200) self.assertEqual(region.getH(), 200)
def apply(self, operand, previousMatches=None, entity=None, *args, **kargs): # Include context's parent in previous matched if self.parent and entity.parent: parentRegion = [entity.parent.region] else: parentRegion = [] if len(previousMatches + parentRegion) == 0: raise Exception("Cannot find any previous regions") return Region(previousMatches + parentRegion + [operand])
def testSetClickOffset(self): region = Region(100, 100, 100, 100) region.setClickOffset(Location(10, 10)) # Check that it is set right location = region.getClickOffset() self.assertEqual(location.getX(), 10) self.assertEqual(location.getY(), 10) location = region.getClickLocation() self.assertEqual(location.getX(), 160.0) self.assertEqual(location.getY(), 160.0)
def limit(self, operand): # If we're trying to limit None, return original if not operand: return self x1 = self.getX() if self.getX() > operand.getX() else operand.getX() y1 = self.getY() if self.getY() > operand.getY() else operand.getY() x2 = (self.getX() + self.getW()) if (self.getX() + self.getW()) < ( operand.getX() + operand.getW()) else (operand.getX() + operand.getW()) y2 = (self.getY() + self.getH()) if (self.getY() + self.getH()) < ( operand.getY() + operand.getH()) else (operand.getY() + operand.getH()) # Check region is valid positive if x2 - x1 < 0 or y2 - y1 < 0: raise Exception( "Region %s is outside the bounds of the ParentRegion %s" % (self, operand)) return Region(x1, y1, x2 - x1, y2 - y1)
def _filter_ships_on_level(self, ship_positions): """Given a list of ship positions, such as one generated by the _filter_ships method, generate a new ship positions list based on the levels. Args: ship_positions (list): list of ints of previously matched ship positions Returns: list: list of positions of ships matching the level criteria """ filtered_ship_positions = [] for position in ship_positions: # get ship config based on position ship_config = {} for ship_name in self.temp_ship_position_dict: if position in self.temp_ship_position_dict[ship_name]: ship_config = self.temp_ship_config_dict[ship_name] break if 'level' in ship_config: # create new region based on the position level_area = Region( self.kc_region.x + 540, self.kc_region.y + 128 + (28 * position), 50, 22) ship_level = Util.read_ocr_number_text(level_area) ship_level = sub(r"\D", "", ship_level) ship_level = 1 if not ship_level else int(ship_level) if ship_config['level'][0] == '<': if ship_level <= int(ship_config['level'][1:]): filtered_ship_positions.append(position) if ship_config['level'][0] == '>': if ship_level >= int(ship_config['level'][1:]): filtered_ship_positions.append(position) else: filtered_ship_positions.append(position) filtered_ship_positions.sort() return filtered_ship_positions
def __init__(self, config, stats, regions, fleet): """Initialies the PvP module. Args: config (Config): kcauto Config instance stats (Stats): kcauto Stats instance regions (dict): dict of pre-defined kcauto regions fleets (Fleet): Fleet instance of fleet that will conduct PvP """ self.config = config self.stats = stats self.regions = regions self.kc_region = regions['game'] self.fleet = fleet self.next_pvp_time = None self._set_next_pvp_time() self.opponent = {} x = self.kc_region.x y = self.kc_region.y self.module_regions = { 'enemy_pvp_fleet': Region(x + 710, y, 365, Globals.GAME_HEIGHT) }
def MakeFullScreen(): # Enlarge left bar r = Region(Region(1042, 426, 35, 114)) with Region(r): if exists("1466943356273.png"): click(Location(1061, 458)) # Region(Region(1033,376,58,222)).find("1466943356273.png").click() # remove up tool bar r_toptoolbar = Region(Region(1085, 131, 84, 28)) with Region(r_toptoolbar): if exists("1466943375529.png"): click() # Region(Region(1085,131,84,28)).find("1466943375529.png").click() Debug.user("Make Full Screen") return
def focus_kc(cls, config): """Method for focusing on the window Kantai Collection is running in and defining all the pre-defined regions based off of it. Args: config (Config): kcauto-kai Config instance Returns: Region: Region instance containing the location of the Kantai Collection game screen list: list of pre-defined Regions """ kc = cls.focus_app(config) # match the reference point to find the exact location of the game # within the game container window reference_region = kc.wait(Pattern('kc_reference_point.png').exact()) x = reference_region.x - 99 y = reference_region.y regions = {} # pre-defined regions are defined as (X_start, Y_start, width, height) # generic regions regions['game'] = Region(x, y, 800, 480) regions['left'] = Region(x, y, 400, 480) regions['right'] = Region(x + 400, y, 400, 480) regions['upper'] = Region(x, y, 800, 240) regions['lower'] = Region(x, y + 240, 800, 240) regions['upper_left'] = Region(x, y, 400, 240) regions['upper_right'] = Region(x + 400, y, 400, 240) regions['lower_left'] = Region(x, y + 240, 400, 240) regions['lower_right'] = Region(x + 400, y + 240, 400, 240) regions['lower_right_corner'] = Region(x + 710, y + 390, 90, 90) # function-specific regions regions['expedition_flag'] = Region(x + 490, y, 60, 60) regions['top_menu'] = Region(x + 115, y + 25, 550, 50) regions['home_menu'] = Region(x + 30, y + 85, 335, 325) regions['side_menu'] = Region(x, y + 120, 100, 280) regions['top_submenu'] = Region(x + 100, y + 100, 700, 45) regions['quest_status'] = Region(x + 710, y + 110, 65, 340) regions['check_supply'] = Region(x + 465, y + 155, 65, 285) regions['ship_counter'] = Region(x + 570, y, 105, 30) # repair-related regions regions['repair_panel'] = Region(x + 600, y + 110, 100, 340) regions['repair_shiplist_fleet_markers'] = Region( x + 375, y + 125, 28, 310) # combat-related regions regions['enemy_pvp_fleet'] = Region(x + 400, y, 400, 480) regions['formation_line_ahead'] = Region(x + 390, y + 160, 175, 50) regions['formation_double_line'] = Region(x + 520, y + 160, 175, 50) regions['formation_diamond'] = Region(x + 650, y + 160, 120, 50) regions['formation_echelon'] = Region(x + 390, y + 320, 190, 50) regions['formation_line_abreast'] = Region(x + 520, y + 320, 190, 50) regions['formation_vanguard'] = Region(x + 650, y + 320, 120, 50) regions['formation_combinedfleet_1'] = Region( x + 420, y + 150, 160, 50) regions['formation_combinedfleet_2'] = Region( x + 580, y + 150, 160, 50) regions['formation_combinedfleet_3'] = Region( x + 420, y + 280, 160, 50) regions['formation_combinedfleet_4'] = Region( x + 580, y + 280, 160, 50) return (kc, regions)
def testCanAddLocationToRegion(self): region = Region(100,100,1,1) # Check that initialized properly self.assertEqual(region.getX(), 100) self.assertEqual(region.getY(), 100) self.assertEqual(region.getW(), 1) self.assertEqual(region.getH(), 1) region = region.add(Location(50,50)) # X,Y Should have changed to 50 self.assertEqual(region.getX(), 50) self.assertEqual(region.getY(), 50) # W,H should have changed to 50 self.assertEqual(region.getW(), 51) self.assertEqual(region.getH(), 51) region = region.add(Location(200,50)) # Width/Height should have changed to 200/200 self.assertEqual(region.getX(), 50) self.assertEqual(region.getY(), 50) self.assertEqual(region.getW(), 150) self.assertEqual(region.getH(), 51) region = region.add(Location(200,200)) # Width/Height should have changed to 200/200 self.assertEqual(region.getX(), 50) self.assertEqual(region.getY(), 50) self.assertEqual(region.getW(), 150) self.assertEqual(region.getH(), 150)
class CombatModule(object): def __init__(self, config, stats, regions, fleets): """Initializes the Combat module. Args: config (Config): kcauto-kai Config instance stats (Stats): kcauto-kai Stats instance regions (dict): dict of pre-defined kcauto-kai regions fleets (dict): dict of active CombatFleet instances """ self.enabled = True self.disabled_time = None self.config = config self.stats = stats self.regions = regions self.kc_region = regions['game'] self.fast_kc_region = Region(self.kc_region) self.fast_kc_region.setAutoWaitTimeout(0) self.observeRegion = Region(self.kc_region) self.fleets = fleets self.next_combat_time = datetime.now() self.combined_fleet = self.config.combat['combined_fleet'] self.striking_fleet = self.config.combat['striking_fleet'] self.primary_fleet = fleets[3] if self.striking_fleet else fleets[1] self.fleet_icon = 'fleet_icon_standard.png' if self.combined_fleet: self.fleet_icon = 'fleet_icon_{}.png'.format( self.config.combat['fleet_mode']) self.dmg = {} self.map = MapData(self.config.combat['map'], self.regions, self.config) self.current_position = [0, 0] self.current_node = None self.nodes_run = [] self.lbas = (LBAS(config, regions, self.map) if self.config.combat['lbas_enabled'] else None) # combat-related regions x = self.kc_region.x y = self.kc_region.y self.module_regions = { 'game': self.kc_region, 'check_fatigue': Region(x + 500, y + 135, 22, 290), 'check_damage': Region(x + 461, y + 130, 48, 300), 'check_damage_7th': Region(x + 461, y + 376, 48, 50), 'check_damage_flagship': Region(x + 290, y + 185, 70, 50), 'check_damage_combat': Region(x + 290, y + 140, 70, 320), 'event_next': Region(x + 720, y + 340, 80, 70), } def goto_combat(self): """Method to navigate to the combat menu. """ Nav.goto(self.regions, 'combat') def check_need_to_sortie(self): """Method to check whether the combat fleets need to sortie based on the stored next combat time. Returns: bool: True if the combat fleets need to sortie, False otherwise """ if not self.enabled: return False if self.next_combat_time < datetime.now(): return True return False def set_next_combat_time(self, delta={}): """Method to set the next combat time based on the provided hours, minutes, and seconds delta. Args: delta (dict, optional): dict containing the hours, minutes, and seconds delta """ self.next_combat_time = datetime.now() + timedelta( hours=delta['hours'] if 'hours' in delta else 0, minutes=delta['minutes'] if 'minutes' in delta else 0, seconds=delta['seconds'] if 'seconds' in delta else 0) def combat_logic_wrapper(self): """Method that fires off the necessary child methods that encapsulates the entire action of sortieing combat fleets and resolving combat. Returns: bool: False if the combat fleets could not be sortied """ self.stats.increment_combat_attempted() if not self._select_combat_map(): # LBAS fatigue check failed; cancel sortie return False if self._conduct_pre_sortie_checks(): start_button = 'combat_start.png' if (self.lbas and (self.config.combat['lbas_group_1_nodes'] or self.config.combat['lbas_group_2_nodes'] or self.config.combat['lbas_group_3_nodes'])): start_button = 'combat_start_lbas.png' # attempt to click sortie start button if Util.check_and_click(self.regions['lower_right'], start_button): Util.log_msg("Beginning combat sortie.") else: # generic sortie fail catch Util.log_warning("Could not begin sortie for some reason!") self.set_next_combat_time({'minutes': 5}) return False else: # fleet fatigue/damage check failed; cancel sortie return False # reset FCF retreat counters for combined and striking fleets if self.combined_fleet: self.fleets[1].reset_fcf_retreat_counts() self.fleets[2].reset_fcf_retreat_counts() if self.striking_fleet: self.fleets[3].reset_fcf_retreat_counts() self._run_combat_logic() self.set_next_combat_time() # after combat, resolve the FCF retreat counters for combined and # striking fleets and add them back to their damage counters if self.combined_fleet: self.fleets[1].resolve_fcf_retreat_counts() self.fleets[2].resolve_fcf_retreat_counts() self.fleets[2].damage_counts['repair'] = 0 if self.striking_fleet: self.fleets[3].resolve_fcf_retreat_counts() self.fleets[3].damage_counts['repair'] = 0 else: self.fleets[1].damage_counts['repair'] = 0 return True def _select_combat_map(self): """Method that goes through the menu and chooses the specified map to sortie to. LBAS checks are also resolved at this point. Returns: bool: True if the combat map is successfully chosen and started, False if an LBAS check failed """ Util.rejigger_mouse(self.regions, 'top') if self.map.world == 'event': Util.wait_and_click(self.regions['lower'], '_event_world.png') else: Util.wait_and_click_and_wait( self.regions['lower'], 'c_world_{}.png'.format(self.map.world), self.kc_region, 'c_world_{}-1.png'.format(self.map.world)) Util.rejigger_mouse(self.regions, 'top') if self.lbas: # resupply and delay sortie time if LBAS fails fatigue check lbas_check_fatigue = ('CheckFatigue' in self.config.combat['misc_options']) pass_lbas_check, delay_time = ( self.lbas.resupply_groups(lbas_check_fatigue)) if not pass_lbas_check: self.set_next_combat_time({'minutes': delay_time}) return False if self.map.world == 'event': for page in range(1, int(self.map.subworld[0])): Util.check_and_click(self.kc_region, '_event_next_page_{}.png'.format(page)) Util.rejigger_mouse(self.regions, 'top') Util.kc_sleep(2) Util.wait_and_click( self.kc_region, '_event_world_{}.png'.format(self.map.subworld)) # dismiss Ooyodo chalkboards self.kc_region.wait('event_chalkboard.png', 10) while self.kc_region.exists('event_chalkboard'): Util.kc_sleep(1) Util.click_preset_region(self.regions, 'center') Util.kc_sleep(1) if self.regions['lower_right'].exists('sortie_select.png'): break else: if int(self.map.subworld) > 4: Util.wait_and_click(self.regions['right'], 'c_world_eo_arrow.png') Util.rejigger_mouse(self.regions, 'top') Util.kc_sleep(2) Util.wait_and_click( self.kc_region, 'c_world_{}-{}.png'.format(self.map.world, self.map.subworld)) Util.wait_and_click(self.regions['lower_right'], 'sortie_select.png') Util.rejigger_mouse(self.regions, 'top') return True def _conduct_pre_sortie_checks(self): """Method to conduct pre-sortie fatigue and supply checks on the combat fleets as needed. Returns: bool: True if the fleet passes the pre-sortie checks, False otherwise """ cancel_sortie = False if self.config.combat['fleet_mode'] == 'striking': # switch fleet to 3rd fleet if striking fleet Util.kc_sleep(1) Fleet.switch(self.regions['top_submenu'], 3) needs_resupply, self.dmg, fleet_fatigue = ( self._run_pre_sortie_fleet_check_logic(self.primary_fleet)) if self.combined_fleet: # additional combined fleet checks Fleet.switch(self.regions['top_submenu'], 2) two_needs_resupply, fleet_two_damages, fleet_two_fatigue = ( self._run_pre_sortie_fleet_check_logic(self.fleets[2])) Fleet.switch(self.regions['top_submenu'], 1) self.dmg = self._combine_fleet_damages(self.dmg, fleet_two_damages) for key in fleet_fatigue: fleet_fatigue[key] = (fleet_fatigue[key] or fleet_two_fatigue[key]) if needs_resupply: Util.log_warning("Canceling combat sortie: resupply required.") self.set_next_combat_time() cancel_sortie = True if 'CheckFatigue' in self.config.combat['misc_options']: if fleet_fatigue['high']: Util.log_warning( "Canceling combat sortie: fleet has high fatigue.") self.set_next_combat_time({'minutes': 25}) cancel_sortie = True elif fleet_fatigue['medium']: Util.log_warning( "Canceling combat sortie: fleet has medium fatigue.") self.set_next_combat_time({'minutes': 15}) cancel_sortie = True # just use fleet 1's method damage_counts_at_threshold = ( self.primary_fleet.get_damage_counts_at_threshold( self.config.combat['repair_limit'], self.dmg)) if damage_counts_at_threshold > 0: Util.log_warning( "Canceling combat sortie: {:d} ships above damage threshold.". format(damage_counts_at_threshold)) self.set_next_combat_time() cancel_sortie = True if ('PortCheck' in self.config.combat['misc_options'] or self.map.world == 'event'): port_full_notice = ('warning_port_full_event.png' if self.map.world == 'event' else 'warning_port_full.png') if self.regions['lower'].exists(port_full_notice): Util.log_warning("Canceling combat sortie: port is full.") self.set_next_combat_time({'minutes': 15}) cancel_sortie = True if cancel_sortie: return False return True def _run_pre_sortie_fleet_check_logic(self, fleet): """Method that actually does the checking of supplies and damages of the fleet during the pre-sortie fleet check. Also includes special handling of the 7th ship in striking fleets. Args: fleet (CombatFleet): CombatFleet instance of fleet being checked Returns: bool: indicates whether or not the fleed requires resupply dict: dict of combat damages dict: dict of fleet fatigue """ needs_resupply = False if not fleet.check_supplies(self.regions['check_supply']): fleet.needs_resupply = True needs_resupply = True fleet_damages = (fleet.check_damages_7th(self.module_regions) if self.config.combat['fleet_mode'] == 'striking' else fleet.check_damages( self.module_regions['check_damage'])) fleet.print_damage_counts(repair=True) if 'CheckFatigue' in self.config.combat['misc_options']: fleet_fatigue = fleet.check_fatigue( self.module_regions['check_fatigue']) fleet.print_fatigue_states() return (needs_resupply, fleet_damages, fleet_fatigue) return (needs_resupply, fleet_damages, {}) def _run_combat_logic(self): """Method that contains the logic and fires off necessary child methods for resolving anything combat-related. Includes LBAS node assignment, compass spins, formation selects, night battle selects, FCF retreats for combined fleet, flagship retreats, mid-battle damage checks, and resource node ends. """ self.stats.increment_combat_done() if self.lbas: self.lbas.assign_groups() self.primary_fleet.needs_resupply = True if self.combined_fleet: self.fleets[2].needs_resupply = True # primary combat loop sortieing = True self.nodes_run = [] disable_combat = False post_combat_screens = [] while sortieing: at_node, dialogue_click = self._run_loop_between_nodes() # stop the background observer if no longer on the map screen if self.config.combat['engine'] == 'live': self.observeRegion.stopObserver() if at_node: # arrived at combat node self._increment_nodes_run() # reset ClearStop temp variables if 'ClearStop' in self.config.combat['misc_options']: disable_combat = False post_combat_screens = [] if dialogue_click: # click to get rid of initial boss dialogue in case it # exists Util.kc_sleep(5) Util.click_preset_region(self.regions, 'center') Util.kc_sleep() Util.click_preset_region(self.regions, 'center') Util.rejigger_mouse(self.regions, 'lbas') combat_result = self._run_loop_during_battle() # resolve night battle if combat_result == 'night_battle': if self._select_night_battle(self._resolve_night_battle()): self._run_loop_during_battle() self.regions['lower_right_corner'].wait('next.png', 30) # battle complete; resolve combat results Util.click_preset_region(self.regions, 'center') self.regions['game'].wait('mvp_marker.png', 30) self.dmg = self.primary_fleet.check_damages( self.module_regions['check_damage_combat']) self.primary_fleet.print_damage_counts() if 'ClearStop' in self.config.combat['misc_options']: # check for a medal drop here if ClearStop is enabled self.regions['lower_right_corner'].wait('next.png', 30) if self.regions['right'].exists('medal_marker.png'): disable_combat = True if self.combined_fleet: self.regions['lower_right_corner'].wait('next.png', 30) Util.click_preset_region(self.regions, 'center') Util.kc_sleep(2) self.regions['game'].wait('mvp_marker.png', 30) fleet_two_damages = self.fleets[2].check_damages( self.module_regions['check_damage_combat']) self.fleets[2].print_damage_counts() self.dmg = self._combine_fleet_damages( self.dmg, fleet_two_damages) # ascertain whether or not the escort fleet's flagship is # damaged if necessary if (fleet_two_damages['heavy'] == 1 and not self.fleets[2].flagship_damaged): self.fleets[2].check_damage_flagship( self.module_regions) Util.rejigger_mouse(self.regions, 'lbas') # click through while not next battle or home while not ( self.fast_kc_region.exists('home_menu_sortie.png') or self.fast_kc_region.exists('combat_flagship_dmg.png') or self.fast_kc_region.exists('combat_retreat.png')): if self.regions['lower_right_corner'].exists('next.png'): Util.click_preset_region(self.regions, 'center') Util.rejigger_mouse(self.regions, 'top') if 'ClearStop' in self.config.combat['misc_options']: post_combat_screens.append('next') elif self.regions['lower_right_corner'].exists( 'next_alt.png'): Util.click_preset_region(self.regions, 'center') Util.rejigger_mouse(self.regions, 'top') if 'ClearStop' in self.config.combat['misc_options']: post_combat_screens.append('next_alt') if self.map.world == 'event': # if the 'next' asset exists in this region during an # event map sortie, the map is cleared if self.module_regions['event_next'].exists( 'next.png'): Util.click_preset_region(self.regions, 'center') Util.rejigger_mouse(self.regions, 'top') disable_combat = True if self.combined_fleet or self.striking_fleet: self._resolve_fcf() Util.rejigger_mouse(self.regions, 'top') if self.regions['left'].exists('home_menu_sortie.png'): # arrived at home; sortie complete self._print_sortie_complete_msg(self.nodes_run) sortieing = False break if self.regions['lower_right_corner'].exists( 'combat_flagship_dmg.png'): # flagship retreat; sortie complete Util.log_msg("Flagship damaged. Automatic retreat.") Util.click_preset_region(self.regions, 'game') self.regions['left'].wait('home_menu_sortie.png', 30) self._print_sortie_complete_msg(self.nodes_run) sortieing = False break if self.regions['lower_right_corner'].exists('next_alt.png'): # resource node end; sortie complete while not self.regions['left'].exists('home_menu_sortie.png'): if self.regions['lower_right_corner'].exists('next.png'): Util.click_preset_region(self.regions, 'center') Util.rejigger_mouse(self.regions, 'top') elif self.regions['lower_right_corner'].exists( 'next_alt.png'): Util.click_preset_region(self.regions, 'center') Util.rejigger_mouse(self.regions, 'top') self._print_sortie_complete_msg(self.nodes_run) sortieing = False break if self.kc_region.exists('combat_retreat.png'): continue_sortie = self._resolve_continue_sortie() # resolve retreat/continue if continue_sortie: self._select_continue_sortie(True) else: self._select_continue_sortie(False) self.regions['left'].wait('home_menu_sortie.png', 30) self._print_sortie_complete_msg(self.nodes_run) sortieing = False break # after sortie is complete, check the dismissed post-combat screens to # see if combat should be disabled if ('ClearStop' in self.config.combat['misc_options'] and not disable_combat): # TODO: additional logic needed to resolve end of 1-6 if self.map.world == 'event' and len(post_combat_screens) > 2: # event map and more than 2 post-combat screens dismissed; # assume that it means that event map is cleared disable_combat = True # if the disable combat flag is set, disable the combat module if disable_combat: self.disable_module() def _print_sortie_complete_msg(self, nodes_run): """Method that prints the post-sortie status report indicating number of nodes run and nodes run. Args: nodes_run (list): list of combat node numbers (legacy mode) or Nodes instances (live mode) run in the primary combat logic """ Util.log_success( "Sortie complete. Encountered {} combat nodes (nodes {}).".format( len(nodes_run), ', '.join(str(node) for node in nodes_run))) def _run_loop_between_nodes(self): """Method that continuously checks for the next update between combat nodes. Resolves compass spins, formation selects, node selects, and resource node ends. Returns: bool: True if the method ends on a combat node, False otherwise bool: True if the click to remove boss dialogue should be done, False otherwise (only applicable if first bool is True) """ at_node = False # if in live engine mode, begin the background observer to track and # update the fleet position if self.config.combat['engine'] == 'live': self._start_fleet_observer() while not at_node: if self.fast_kc_region.exists('compass.png'): # spin compass while (self.kc_region.exists('compass.png')): Util.click_preset_region(self.regions, 'center') Util.rejigger_mouse(self.regions, 'lbas') Util.kc_sleep(3) elif (self.regions['formation_line_ahead'].exists( 'formation_line_ahead.png') or self.regions['formation_combinedfleet_1'].exists( 'formation_combinedfleet_1.png')): # check for both single fleet and combined fleet formations # since combined fleets can have single fleet battles self._print_current_node() formations = self._resolve_formation() for formation in formations: if self._select_formation(formation): break Util.rejigger_mouse(self.regions, 'lbas') at_node = True return (True, True) elif self.fast_kc_region.exists('combat_node_select.png'): # node select dialog option exists; resolve fleet location and # select node if self.config.combat['engine'] == 'legacy': # only need to manually update self.current_node if in # legacy engine mode self._update_fleet_position_once() if (self.current_node.name in self.config.combat['node_selects']): next_node = self.config.combat['node_selects'][ self.current_node.name] Util.log_msg("Selecting Node {} from Node {}.".format( next_node, self.current_node)) self.map.nodes[next_node].click_node(self.regions['game']) Util.rejigger_mouse(self.regions, 'lbas') elif (self.regions['lower_right_corner'].exists('next.png') or self.fast_kc_region.exists('combat_nb_fight.png')): # post-combat or night battle select without selecting a # formation self._print_current_node() Util.rejigger_mouse(self.regions, 'lbas') at_node = True return (True, False) elif self.regions['lower_right_corner'].exists( 'combat_flagship_dmg.png'): # flagship retreat return (False, False) elif self.regions['lower_right_corner'].exists('next_alt.png'): # resource node end return (False, False) def _run_loop_during_battle(self): """Method that continuously runs during combat for the night battle prompt or battle end screen. Returns: str: 'night_battle' if combat ends on the night battle prompt, 'results' if otherwise """ while True: if self.kc_region.exists('combat_nb_fight.png'): return 'night_battle' elif self.regions['lower_right_corner'].exists('next.png'): return 'results' else: pass def _start_fleet_observer(self): """Method that starts the observeRegion/observeInBackground methods that tracks the fleet position icon in real-time in the live engine mode. """ self.observeRegion.onAppear( Pattern(self.fleet_icon).similar(Globals.FLEET_ICON_SIMILARITY), self._update_fleet_position) self.observeRegion.observeInBackground(FOREVER) def _stop_fleet_observer(self): """Stops the observer started by the _start_fleet_observer() method. """ self.observeRegion.stopObserver() def _update_fleet_position(self, event): """Method that is run by the fleet observer to continuously update the fleet's status. Args: event (event): sikuli observer event """ fleet_match = event.getMatch() # lastMatch is based off of screen positions, so subtract game region # x and y to get in-game positions self.current_position = [ fleet_match.x + (fleet_match.w / 2) - self.kc_region.x, fleet_match.y + fleet_match.h - self.kc_region.y ] # debug console print for the observer's found position of the fleet """ print( "{}, {} ({})".format( self.current_position[0], self.current_position[1], fleet_match)) """ matched_node = self.map.find_node_by_pos(*self.current_position) self.current_node = (matched_node if matched_node is not None else self.current_node) event.repeat() def _update_fleet_position_once(self): """Method that can be called to find and update the fleet's position on-demand. """ fleet_match = self.kc_region.find( Pattern(self.fleet_icon).similar(Globals.FLEET_ICON_SIMILARITY)) # lastMatch is based off of screen positions, so subtract game region # x and y to get in-game positions self.current_position = [ fleet_match.x + (fleet_match.w / 2) - self.kc_region.x, fleet_match.y + fleet_match.h - self.kc_region.y ] # debug console print for the method's found position of the fleet """ print( "{}, {} ({})".format( self.current_position[0], self.current_position[1], fleet_match)) """ matched_node = self.map.find_node_by_pos(*self.current_position) self.current_node = (matched_node if matched_node is not None else self.current_node) Util.log_msg("Fleet at node {}.".format(self.current_node)) def _increment_nodes_run(self): """Method to properly append to the nodes_run attribute; the combat node number if the engine is in legacy mode, otherwise with the Node instance of the encountered node if in live mode """ if self.config.combat['engine'] == 'legacy': self.nodes_run.append(len(self.nodes_run) + 1) elif self.config.combat['engine'] == 'live': self.nodes_run.append(self.current_node) def _print_current_node(self): """Method to print out which node the fleet is at. Behavior differs depending on the combat engine mode. """ if self.config.combat['engine'] == 'legacy': Util.log_msg("Fleet at Node #{}".format(len(self.nodes_run) + 1)) if self.config.combat['engine'] == 'live': Util.log_msg("Fleet at Node {}".format(self.current_node)) def _resolve_formation(self): """Method to resolve which formation to select depending on the combat engine mode and any custom specified formations. Returns: tuple: tuple of formations to try in order """ # +1 since this happens before entering a node next_node_count = len(self.nodes_run) + 1 custom_formations = self.config.combat['formations'] if self.config.combat['engine'] == 'legacy': # if legacy engine, custom formation can only be applied on a node # count basis; if a custom formation is not defined, default to # combinedfleet_4 or line_ahead if next_node_count in custom_formations: Util.log_msg("Custom formation specified for node #{}.".format( next_node_count)) return (custom_formations[next_node_count], ) else: Util.log_msg( "No custom formation specified for node #{}.".format( next_node_count)) return ('combinedfleet_4' if self.combined_fleet else 'line_ahead', ) elif self.config.combat['engine'] == 'live': # if live engine, custom formation can be applied by node name or # node count; if a custom formation is not defined, defer to the # mapData instance's resolve_formation method if (self.current_node and self.current_node.name in custom_formations): Util.log_msg("Custom formation specified for node {}.".format( self.current_node.name)) return (custom_formations[self.current_node.name], ) elif next_node_count in custom_formations: Util.log_msg("Custom formation specified for node #{}.".format( next_node_count)) return (custom_formations[next_node_count], ) else: Util.log_msg( "Formation specified for node {} via map data.".format( self.current_node.name)) return self.map.resolve_formation(self.current_node) def _resolve_night_battle(self): """Method to resolve whether or not to conduct night battle depending on the combat engine mode and any custom specified night battle modes. Returns: bool: True if night battle should be conducted, False otherwise """ # no +1 since this happens after entering a node next_node_count = len(self.nodes_run) custom_night_battles = self.config.combat['night_battles'] if self.config.combat['engine'] == 'legacy': # if legacy engine, custom night battle modes can only be applied # on a node count basis; if a custom night battle mode is not # defined, default to True if next_node_count in custom_night_battles: Util.log_msg( "Custom night battle specified for node #{}.".format( next_node_count)) return custom_night_battles[next_node_count] else: Util.log_msg("No night battle specified for node #{}.".format( next_node_count)) return False elif self.config.combat['engine'] == 'live': # if live engine, custom night battle modes can be applied by node # name or node count; if a custom night battle mode is not defined, # defer to the mapData instance's resolve_night_battle method if (self.current_node and self.current_node.name in custom_night_battles): Util.log_msg( "Custom night battle specified for node {}.".format( self.current_node.name)) return custom_night_battles[self.current_node.name] elif next_node_count in custom_night_battles: Util.log_msg( "Custom night battle specified for node #{}.".format( next_node_count)) return custom_night_battles[next_node_count] else: Util.log_msg( "Night battle specified for node {} via map data.".format( self.current_node.name)) return self.map.resolve_night_battle(self.current_node) def _resolve_continue_sortie(self): """Method to resolve whether or not to continue the sortie based on number of nodes run, map data (if applicable), and damage counts. Returns: bool: True if sortie should be continued, False otherwise """ # check whether to retreat against combat nodes count if len(self.nodes_run) >= self.config.combat['combat_nodes']: Util.log_msg("Ran the necessary number of nodes. Retreating.") return False # if on live engine mode, check if the current node is a retreat node if self.config.combat['engine'] == 'live': if not self.map.resolve_continue_sortie(self.current_node): Util.log_msg("Node {} is a retreat node. Retreating.".format( self.current_node)) return False # check whether to retreat against fleet damage state threshold_dmg_count = ( self.primary_fleet.get_damage_counts_at_threshold( self.config.combat['retreat_limit'], self.dmg)) if threshold_dmg_count > 0: continue_override = False if self.combined_fleet and threshold_dmg_count == 1: # if there is only one heavily damaged ship and it is # the flagship of the escort fleet, do not retreat if (self.fleets[2].damage_counts['heavy'] == 1 and self.fleets[2].flagship_damaged): continue_override = True Util.log_msg( "The 1 ship damaged beyond threshold is the escort " "fleet's flagship (unsinkable). Continuing sortie.") if not continue_override: Util.log_warning( "{} ship(s) damaged above threshold. Retreating.".format( threshold_dmg_count)) return False return True def _select_formation(self, formation): """Method that selects the specified formation on-screen. Args: formation (str): formation to select Returns: bool: True if the formation was clicked, False if its button could not be found """ Util.log_msg("Engaging the enemy in {} formation.".format( formation.replace('_', ' '))) return Util.check_and_click( self.regions['formation_{}'.format(formation)], 'formation_{}.png'.format(formation)) def _select_night_battle(self, nb): """Method that selects the night battle sortie button or retreats from it. Args: nb (bool): indicates whether or not night battle should be done or not Returns: bool: True if night battle was initiated, False otherwise """ if nb: Util.log_msg("Commencing night battle.") Util.check_and_click(self.kc_region, 'combat_nb_fight.png') self.kc_region.waitVanish('combat_nb_fight.png') Util.kc_sleep() return True else: Util.log_msg("Declining night battle.") Util.check_and_click(self.kc_region, 'combat_nb_retreat.png') self.kc_region.waitVanish('combat_nb_retreat.png') Util.kc_sleep() return False def _select_continue_sortie(self, continue_sortie): """Method that selects the sortie continue or retreat button. Args: continue_sortie (bool): True if the the sortie continue button should be pressed, False otherwise """ if continue_sortie: Util.log_msg("Continuing sortie.") Util.check_and_click(self.kc_region, 'combat_continue.png') self.kc_region.waitVanish('combat_continue.png') Util.kc_sleep() else: Util.log_msg("Retreating from sortie.") Util.check_and_click(self.kc_region, 'combat_retreat.png') self.kc_region.waitVanish('combat_retreat.png') Util.kc_sleep() def _resolve_fcf(self): """Method that resolves the FCF prompt. Does not use FCF if there are more than one ship in a heavily damaged state. Supports both combined fleet FCF and striking force FCF """ if self.regions['lower_left'].exists('fcf_retreat_ship.png'): fcf_retreat = False if self.combined_fleet: # for combined fleets, check the heavy damage counts of both # fleets 1 and 2 fleet_1_heavy_damage = self.fleets[1].damage_counts['heavy'] fleet_2_heavy_damage = self.fleets[2].damage_counts['heavy'] if fleet_1_heavy_damage + fleet_2_heavy_damage == 1: fcf_retreat = True self.fleets[1].increment_fcf_retreat_count() self.fleets[2].increment_fcf_retreat_count() elif self.striking_fleet: # for striking fleets, check the heavy damage counts of the # 3rd fleet if self.fleets[3].damage_counts['heavy'] == 1: fcf_retreat = True self.fleets[3].increment_fcf_retreat_count() if fcf_retreat: if (Util.check_and_click(self.regions['lower'], 'fcf_retreat_ship.png')): # decrement the Combat module's internal dmg count so it # knows to continue sortie to the next node self.dmg['heavy'] -= 1 else: Util.log_warning("Declining to retreat ship with FCF.") Util.check_and_click(self.regions['lower'], 'fcf_continue_fleet.png') def _combine_fleet_damages(self, main, escort): """Method for conveniently combining two damage dicts for combined fleets. Args: main (dict): damage dict of main fleet escort (dict): damage dict of escort fleet Returns: dict: damage dict aggregating all damage counts for both main and escort fleets """ combined = {} # create new to not update by reference for key in main: combined[key] = main[key] + escort[key] return combined def disable_module(self): Util.log_success("De-activating the combat module.") self.enabled = False self.disabled_time = datetime.now() def enable_module(self): Util.log_success("Re-activating the combat module.") self.enabled = True self.disabled_time = None def print_status(self): """Method that prints the next sortie time status of the Combat module. """ if self.enabled: Util.log_success("Next combat sortie at {}".format( self.next_combat_time.strftime('%Y-%m-%d %H:%M:%S'))) else: Util.log_success("Combat module disabled as of {}".format( self.disabled_time.strftime('%Y-%m-%d %H:%M:%S')))
def testCanLimitRegion(self): region = Region(50, 50, 400, 400).limit(Region(150, 100, 150, 25)) self.assertEqual(region.getX(), 150) self.assertEqual(region.getY(), 100) self.assertEqual(region.getW(), 150) self.assertEqual(region.getH(), 25) region = Region(100, 100, 200, 200).limit(Region(50, 50, 400, 400)) self.assertEqual(region.getX(), 100) self.assertEqual(region.getY(), 100) self.assertEqual(region.getW(), 200) self.assertEqual(region.getH(), 200)
def recover(kcauto_kai, e): """Attempts very basic recovery actions on a FindFailed exception. WIP and does not integrate with the config. Args: kcauto_kai (KCAutoKai): KCAutoKai instance e (Exception): Exception Returns: bool: True on successful recovery, otherwise raises an error """ kc_region = kcauto_kai.kc_region regions = kcauto_kai.regions recovery_method = 'kc3' Util.log_warning( "FindFailed error occurred; attempting basic recovery.") App.focus(kcauto_kai.config.program) kc_region.mouseMove(Location(1, 1)) # basic recovery attempt type(Key.ESC) sleep(1) if kc_region.exists(Pattern('kc_reference_point.png').exact()): # reference point exists, so we are in-game Util.log_success("Recovery successful.") kcauto_kai.stats.increment_recoveries() return True elif kc_region.exists('next.png'): # crashed at some results screen; try to click it away until we see # the main game screen while (kc_region.exists('next.png') and not kc_region.exists( Pattern('kc_reference_point.png').exact())): Util.click_screen(regions, 'center') sleep(2) if kc_region.exists(Pattern('kc_reference_point.png').exact()): # reference point exists, so we are back in-game Util.log_success("Recovery successful.") kcauto_kai.stats.increment_recoveries() return True # catbomb recovery if kc_region.exists('catbomb.png') and recovery_method != 'None': if recovery_method == 'browser': Region.type(Key.F5) elif recovery_method == 'kc3': Region.type(Key.F5) sleep(1) Region.type(Key.SPACE) sleep(1) Region.type(Key.TAB) sleep(1) Region.type(Key.SPACE) elif recovery_method == 'kcv': Region.type(Key.F5) elif recovery_method == 'kct': Region.type(Key.ALT) sleep(1) Region.type(Key.DOWN) sleep(1) Region.type(Key.DOWN) sleep(1) Region.type(Key.ENTER) elif recovery_method == 'eo': Region.type(Key.F5) sleep(1) Region.type(Key.TAB) sleep(1) Region.type(Key.SPACE) sleep(3) kc_region.mouseMove(Location(0, 0)) sleep(3) Util.wait_and_click(kc_region, Pattern('game_start.png').similar(0.999), 60) sleep(5) Util.log_success("Recovery successful.") kcauto_kai.stats.increment_recoveries() return True # recovery failed Util.log_error("Irrecoverable crash") print(e) raise
def recover(kcauto_kai, config, e): """Attempts very basic recovery actions on a FindFailed exception. WIP and does not integrate with the config. Args: kcauto_kai (KCAutoKai): KCAutoKai instance config (Config): Config instance e (Exception): Exception Returns: bool: True on successful recovery, otherwise raises an error """ kc_region = (kcauto_kai.kc_region if kcauto_kai.kc_region else Util.focus_kc(config)) kc_region = kcauto_kai.kc_region regions = kcauto_kai.regions Util.log_warning(e) Util.log_warning( "** FindFailed error occurred; attempting basic recovery. **") App.focus(kcauto_kai.config.program) kc_region.mouseMove(Location(1, 1)) # basic recovery attempt Region.type(kc_region, Key.ESC) sleep(1) Region.type(kc_region, Key.SPACE) if kc_region.exists(Pattern('kc_reference_point.png').exact()): # reference point exists, so we are in-game Util.log_success("Recovery successful.") kcauto_kai.stats.increment_recoveries() return True elif kc_region.exists('next.png'): # crashed at some results screen; try to click it away until we see # the main game screen while (kc_region.exists('next.png') and not kc_region.exists( Pattern('kc_reference_point.png').exact())): Util.click_preset_region(regions, 'center') sleep(2) if kc_region.exists(Pattern('kc_reference_point.png').exact()): # reference point exists, so we are back in-game Util.log_success("Recovery successful.") kcauto_kai.stats.increment_recoveries() return True # catbomb recovery if kc_region.exists('catbomb.png', 10): Util.log_warning("** Catbomb detected. **") catbombed = True catbomb_n = 0 while catbombed and catbomb_n < 7: # generic f5-space-tab-space keystrokes to mimick refresh # attempt Region.type(kc_region, Key.F5) sleep(1) Region.type(kc_region, Key.SPACE) sleep(1) Region.type(kc_region, Key.TAB) sleep(1) Region.type(kc_region, Key.SPACE) sleep(3) # clear mouse kc_region.mouseMove(Location(1, 1)) if kc_region.exists('catbomb.png'): sleep_len = pow(2, catbomb_n + 4) Util.log_warning( "Catbomb recovery attempt {} failed; trying again in " "{} seconds!".format(catbomb_n + 1, sleep_len)) sleep(sleep_len) catbomb_n += 1 else: catbombed = False sleep(3) Util.wait_and_click(kc_region, Pattern('game_start.png').similar(0.999), 60) sleep(5) Util.log_success("Catbomb recovery successful.") kcauto_kai.stats.increment_recoveries() return True # recovery failed Util.log_error("** Irrecoverable crash. **") print(e) raise
def testCanAddLocationToRegion(self): region = Region(100, 100, 1, 1) # Check that initialized properly self.assertEqual(region.getX(), 100) self.assertEqual(region.getY(), 100) self.assertEqual(region.getW(), 1) self.assertEqual(region.getH(), 1) region = region.add(Location(50, 50)) # X,Y Should have changed to 50 self.assertEqual(region.getX(), 50) self.assertEqual(region.getY(), 50) # W,H should have changed to 50 self.assertEqual(region.getW(), 51) self.assertEqual(region.getH(), 51) region = region.add(Location(200, 50)) # Width/Height should have changed to 200/200 self.assertEqual(region.getX(), 50) self.assertEqual(region.getY(), 50) self.assertEqual(region.getW(), 150) self.assertEqual(region.getH(), 51) region = region.add(Location(200, 200)) # Width/Height should have changed to 200/200 self.assertEqual(region.getX(), 50) self.assertEqual(region.getY(), 50) self.assertEqual(region.getW(), 150) self.assertEqual(region.getH(), 150)