Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
 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)
Ejemplo n.º 6
0
    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),
        }
Ejemplo n.º 7
0
 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)
Ejemplo n.º 8
0
 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)
Ejemplo n.º 9
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)
Ejemplo n.º 10
0
    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)
Ejemplo n.º 11
0
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)
Ejemplo n.º 12
0
    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)
        }
Ejemplo n.º 13
0
    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)
        }
Ejemplo n.º 14
0
    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)
Ejemplo n.º 15
0
    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
Ejemplo n.º 16
0
    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)
Ejemplo n.º 17
0
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
Ejemplo n.º 18
0
    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)
        }
Ejemplo n.º 19
0
    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)
Ejemplo n.º 20
0
    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)      
Ejemplo n.º 21
0
    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'
Ejemplo n.º 22
0
    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)
Ejemplo n.º 23
0
 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)
Ejemplo n.º 24
0
    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])
Ejemplo n.º 25
0
    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)
Ejemplo n.º 26
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)
Ejemplo n.º 27
0
    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
Ejemplo n.º 28
0
    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)
        }
Ejemplo n.º 29
0
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
Ejemplo n.º 30
0
    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)
Ejemplo n.º 31
0
    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)
Ejemplo n.º 32
0
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')))
Ejemplo n.º 33
0
    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)
Ejemplo n.º 34
0
    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
Ejemplo n.º 35
0
    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
Ejemplo n.º 36
0
    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)