def _resolve_formation_prompt(self): Log.log_debug("Resolving formation prompt.") formation = (FormationEnum.COMBINED_FLEET_4 if flt.fleets.combined_fleet else FormationEnum.LINE_AHEAD) if self.current_node.name in cfg.config.combat.node_formations: Log.log_debug("Formation specified in config") formation = cfg.config.combat.node_formations[ self.current_node.name] elif (len(self.combat_nodes_run) + 1 in cfg.config.combat.node_formations): # formation resolution occurs before the combat_nodes_run list is # updated, so account for the 1-offset Log.log_debug("Formation specified combat # in config") formation = cfg.config.combat.node_formations[ len(self.combat_nodes_run) + 1] elif self.current_node.sub_node: Log.log_debug("Node is sub node") formation = (FormationEnum.COMBINED_FLEET_1 if flt.fleets.combined_fleet else FormationEnum.LINE_ABREAST) elif self.current_node.air_node: Log.log_debug("Node is air node") formation = (FormationEnum.COMBINED_FLEET_3 if flt.fleets.combined_fleet else FormationEnum.DIAMOND) Log.log_msg(f"Selecting formation {formation.display_name}.") kca_u.kca.click_existing(f'formation_{formation.value}', f'fleet|formation_{formation.value}.png') kca_u.kca.r['lbas'].hover() return formation
def _conduct_sortie(self, sortie_map): if not self._validate_sortie_map(sortie_map): Log.log_warn(f"Map {sortie_map.value} is not available.") return False if cfg.config.combat.clear_stop and self._sortie_map_is_cleared: Log.log_msg(f"Map {sortie_map.value} has been cleared.") self.enabled = False return False self._select_world(sortie_map) time_to_rest = lbas.lbas.manage_lbas() if time_to_rest: Log.log_warn("LBAS is fatigued.") self.set_next_sortie_time(time_to_rest) return False self._select_map(sortie_map) if self._begin_sortie(): self.nodes_run = [] self.combat_nodes_run = [] self.rescued_ships = [] self.map_cleared = False sts.stats.combat.combat_sorties += 1 self._handle_combat(sortie_map) self._check_map_clear() if self.enabled: self.set_next_sortie_time() return True return False
def should_and_able_to_sortie(self): if not self.enabled or not self.time_to_sortie: return False if cfg.config.combat.port_check: if shp.ships.current_ship_count == shp.ships.max_ship_count: Log.log_msg("Port is full.") self.set_next_sortie_time(15) return False if cfg.config.combat.sortie_map.world == 'E': if shp.ships.current_ship_count >= shp.ships.max_ship_count - 5: Log.log_warn("Port is too full for event map.") self.set_next_sortie_time(15) return False if cfg.config.combat.check_fatigue: for fleet in flt.fleets.combat_fleets: if fleet.highest_fatigue > FatigueStateEnum.NO_FATIGUE: Log.log_warn("Combat fleet is fatigued.") self.set_next_sortie_time(49 - fleet.lowest_morale) return False for fleet in flt.fleets.combat_fleets: if fleet.under_repair: Log.log_warn("Combat fleet is under repair.") self.set_next_sortie_time(rep.repair.soonest_complete_time) return False if fleet.needs_repair: Log.log_warn("Combat fleet needs repair.") if rep.repair.docks_are_available: self.set_next_sortie_time() else: self.set_next_sortie_time(rep.repair.soonest_complete_time) return False if fleet.needs_resupply: Log.log_warn("Combat fleet needs resupply.") return False return True
def assign_lbas(self, map_data): if not self.enabled: return False Log.log_msg("Assigning LBAS groups.") kca_u.kca.r['lbas'].hover() for group in self.assignable_lbas_groups: nodes = cfg.config.combat.nodes_for_lbas_group(group.group_id) Log.log_msg( f"Assigning group {group.group_id} to nodes {nodes[0].value} " f"and {nodes[1].value}.") for node in nodes: node_instance = map_data.nodes[node.value] panel = kca_u.kca.wait('kc', 'combat|lbas_panel_side.png', wait=90) panel_pos = 'r' if panel.x - kca_u.kca.game_x > 600 else 'l' if ((panel_pos == 'l' and node_instance.x < 420) or (panel_pos == 'r' and node_instance.x > 780)): kca_u.kca.hover(panel) node_instance.select() kca_u.kca.sleep(1.3) kca_u.kca.r['lbas'].hover() kca_u.kca.click_existing('upper', 'combat|lbas_assign_confirm.png') kca_u.kca.r['lbas'].hover() kca_u.kca.sleep(1)
def manage_lbas(self): if not self.enabled: return False resupply_groups = self._groups_to_resupply switch_state_groups = self._groups_to_switch_state rest_groups = self._groups_to_rest if resupply_groups or switch_state_groups: Log.log_msg("Managing LBAS groups.") self._open_lbas_panel() for group in LBASGroupEnum: group_id = group.value group_instance = self.groups[group_id] if not group_instance.is_active: continue if group_id != 1: kca_u.kca.click_existing( 'upper_right', f'combat|lbas_group_tab_{group_id}.png') kca_u.kca.sleep(1) if group_id in resupply_groups: self._resupply(group_id) if group_id in switch_state_groups: self._set_to_desired_state( group_instance.state, group_instance.desired_group_state) kca_u.kca.sleep(0.5) kca_u.kca.click_existing('lower_left', 'combat|c_world_1.png') kca_u.kca.sleep(1) if rest_groups: return self._time_to_rest
def _resolve_night_battle_prompt(self): Log.log_debug("Resolve night battle prompt.") night_battle = False if self.current_node.name in cfg.config.combat.node_night_battles: Log.log_debug("NB specified in config") night_battle = cfg.config.combat.node_night_battles[ self.current_node.name] elif ( len(self.combat_nodes_run) in cfg.config.combat.node_night_battles): Log.log_debug("NB specified combat # in config") night_battle = cfg.config.combat.node_night_battles[ len(self.combat_nodes_run)] elif self.current_node.boss_node: Log.log_debug("Node is boss node") night_battle = True if night_battle: Log.log_msg("Entering night battle.") kca_u.kca.click_existing( 'kc', 'global|combat_nb_fight.png', cached=True) else: Log.log_msg("Declining night battle.") kca_u.kca.click_existing( 'kc', 'global|combat_nb_retreat.png', cached=True) kca_u.kca.r['lbas'].hover() return night_battle
def switch_fleet(self, context): preset_id = self._get_next_preset_id(context) Log.log_msg(f"Switching to Fleet Preset {preset_id}.") if preset_id not in self.presets: Log.log_error( f"Fleet Preset {preset_id} is not specified in-game. Please " f"check your config.") exit(1) list_idx = (preset_id if preset_id < 5 else 5) - 1 idx_offset = preset_id - 5 if idx_offset > 0: kca_u.kca.sleep() self._scroll_preset_list(idx_offset) kca_u.kca.r['top'].hover() kca_u.kca.sleep() preset_idx_region = Region(kca_u.kca.game_x + 410, kca_u.kca.game_y + 275 + (list_idx * 76), 70, 45) kca_u.kca.click_existing(preset_idx_region, 'fleetswitcher|fleetswitch_button.png') if kca_u.kca.exists('left', 'fleetswitcher|fleetswitch_fail_check.png'): Log.log_error( f"Could not switch in fleet preset {preset_id}. Please check " f"your config and fleet presets.") exit(1) Log.log_msg(f"Fleet Preset {preset_id} loaded.") if context == 'combat': self._set_next_combat_preset()
def need_to_switch(self): if not cfg.config.ship_switcher.enabled or len(self.rules) == 0: return False if self._slots_to_switch: Log.log_msg("Need to switch ships.") return True return False
def _check_repair_sort(self): if not kca_u.kca.exists('upper_right', 'repair|repairlist_icon.png'): Log.log_msg("Changing repair list sort.") sort_button = Region(kca_u.kca.game_x + 1125, kca_u.kca.game_y + 155, 58, 26) while not kca_u.kca.exists('upper_right', 'repair|repairlist_icon.png'): sort_button.click()
def check_config(self): if cfg.config.config_changed: Log.log_msg("Config change detected. Loading updated config.") if cfg.config.initialize_config(): com.combat.update_from_config() exp.expedition.update_from_config() pvp.pvp.update_from_config() qst.quest.update_from_config() sch.scheduler.update_from_config()
def _resolve_fcf_prompt(self): Log.log_debug("Resolve FCF prompt.") min_combat_nodes_before_fcf = 1 # extrapolate this out to config use_fcf = False if len(self.combat_nodes_run) < min_combat_nodes_before_fcf: return False heavy_damage_counter = 0 damaged_ship = None damaged_ship_idx = None escort_ship = None escort_ship_idx = None for ship_idx, ship in enumerate(flt.fleets.combat_ships): if ship_idx == len(flt.fleets.combat_fleets[0].ship_data): # do not count the damage stage of the escort fleet flagship # as it is un-sinkable and un-retreatable pass if ship.damage is DamageStateEnum.HEAVY: Log.log_debug(f"{ship.name} is heavily damaged.") heavy_damage_counter += 1 damaged_ship = ship damaged_ship_idx = ship_idx if heavy_damage_counter == 1: last_combat_fleet_ships = flt.fleets.combat_fleets[-1].ship_data for ship_idx, ship in enumerate(last_combat_fleet_ships): if (ship.ship_type is ShipTypeEnum.DD and ship.damage <= DamageStateEnum.SCRATCH): Log.log_debug(f"Found {ship.name} as escort ship.") escort_ship = ship escort_ship_idx = ship_idx break if not escort_ship or not escort_ship_idx: raise ValueError("Valid FCF retreat escort ship not found.") Log.log_msg( f"Retreating {damaged_ship.name} with {escort_ship.name} " "using FCF.") kca_u.kca.click_existing('lower', 'combat|fcf_retreat_ship.png', cached=True) flt.fleets.combat_ships[damaged_ship_idx].hp = -1 last_combat_fleet_ships[escort_ship_idx].hp = -1 sts.stats.combat.fcfs_done += 1 use_fcf = True else: Log.log_msg("Not retreating ships using FCF.") kca_u.kca.click_existing('lower', 'combat|fcf_continue_fleet.png', cached=True) kca_u.kca.r['lbas'].hover() return use_fcf
def need_to_resupply(self): fleets = [fleet.fleet_id for fleet in self._get_fleets_to_resupply()] if len(fleets) == 1: Log.log_msg(f"Fleet {fleets[0]} needs resupply.") return True elif len(fleets) > 1: display_text = kca_u.kca.readable_list_join(fleets) Log.log_msg(f"Fleets {display_text} need resupply.") return True return False
def _resupply(self, group_id): Log.log_msg(f"Resupplying LBAS group {group_id}.") while kca_u.kca.exists('upper_right', 'combat|lbas_resupply.png'): kca_u.kca.click_existing('upper_right', 'combat|lbas_resupply.png', cached=True) kca_u.kca.sleep(0.1) kca_u.kca.wait_vanish('lower_right', 'combat|lbas_resupply_in_progress.png') kca_u.kca.sleep()
def at_base(self, value): if type(value) is not bool: raise TypeError("Fleet at-base location is not a boolean.") print_log = True if value != self._enabled else False if value is True and print_log: Log.log_msg(f"Fleet {self.fleet_id} has arrived at base!") for ship in self.ship_data: ship.needs_resupply = True elif value is False and print_log: Log.log_msg(f"Fleet {self.fleet_id} is away on assignment.") self._at_base = value
def set_next_sortie_time(self, value=timedelta(seconds=0), override=False): if isinstance(value, timedelta): proposed_sortie_time = datetime.now() + value elif isinstance(value, datetime): proposed_sortie_time = value elif isinstance(value, int): proposed_sortie_time = datetime.now() + timedelta(minutes=value) if proposed_sortie_time > self.next_sortie_time or override: self.next_sortie_time = proposed_sortie_time Log.log_msg("Next sortie at " f"{KCTime.datetime_to_str(self.next_sortie_time)}")
def fleets_are_ready(self): if len(self.fleets_to_send) == 1: Log.log_msg(f"Fleet {self.fleets_to_send[0].fleet_id} ready for " "expedition.") return True elif len(self.fleets_to_send) > 1: display_text = kca_u.kca.readable_list_join( [fleet.fleet_id for fleet in self.fleets_to_send]) Log.log_msg(f"Fleets {display_text} ready for expedition.") return True return False
def conduct_pvp(self): if len(self.available_pvp) == 0: Log.log_msg("No PvP opponents available.") return True Log.log_msg(f"{len(self.available_pvp)} PvP opponents available.") pvp_index = self.available_pvp.pop(0) self._conduct_pvp(pvp_index) if len(self.available_pvp) == 0: self._reset_next_pvp_time()
def return_time(self, value): if type(value) is datetime: self._return_time = value elif value == 0: self._return_time = None elif type(value) is int: self._return_time = KCTime.convert_epoch(value) Log.log_msg(f"Fleet {self.fleet_id} returns at " f"{KCTime.datetime_to_str(self._return_time)}.") else: raise TypeError("Wrong type for return_time")
def require_fleetswitch(self, context): preset_id = self._get_next_preset_id(context) if preset_id is None: return False if preset_id in self.presets: if self.presets[preset_id] == flt.fleets.fleets[1].ship_ids: Log.log_debug("Preset Fleet is already loaded.") return False Log.log_msg(f"Need to switch to Fleet Preset {preset_id}.") return True
def _cycle_between_nodes(self, sortie_map): Log.log_debug("Between nodes.") while True: if kca_u.kca.exists('kc', 'combat|compass.png'): Log.log_msg("Spinning compass.") kca_u.kca.click_existing('kc', 'combat|compass.png', cached=True) kca_u.kca.r['top'].hover() elif ( kca_u.kca.exists( 'formation_line_ahead', 'fleet|formation_line_ahead.png') or kca_u.kca.exists( 'formation_combined_fleet_1', 'fleet|formation_combined_fleet_1.png')): Log.log_msg(f"Combat at node {self.current_node}.") self._resolve_formation_prompt() api.api.update_from_api(self.COMBAT_APIS, need_all=False) return True elif self.current_node.selection_node: kca_u.kca.sleep() Log.log_msg(f"Node select node.") next_node = cfg.config.combat.node_selects.get( self.current_node.name, None) if not next_node: raise ValueError("Node select not defined.") else: Log.log_msg(f"Selecting node {next_node.value}") self.map_data.nodes[next_node.value].select() api_result = api.api.update_from_api( {KCSAPIEnum.SORTIE_NEXT}, need_all=False, timeout=4) if KCSAPIEnum.SORTIE_NEXT.name in api_result: self._find_next_node( api_result[KCSAPIEnum.SORTIE_NEXT.name][0]) elif kca_u.kca.exists('lower_right_corner', 'global|next_alt.png'): # resource node end return False else: Log.log_debug(self.current_node) Log.log_debug(self.current_node.selection_node) Log.log_debug("Wait for combat API.") api_result = api.api.update_from_api( self.COMBAT_APIS, need_all=False, timeout=1) if KCSAPIEnum.SORTIE_NEXT.name in api_result: self._find_next_node( api_result[KCSAPIEnum.SORTIE_NEXT.name][0]) elif KCSAPIEnum.PORT.name in api_result: Log.log_debug("Sortie ended not immediately after battle.") return False elif len(api_result) > 0: kca_u.kca.r['top'].hover() Log.log_msg(f"Action at Node {self.current_node}") return True kca_u.kca.sleep(1)
def resupply_fleets(self): fleets = self._get_fleets_to_resupply() for fleet in fleets: Log.log_msg(f"Resupplying Fleet {fleet.fleet_id}.") fleet.select() api_result = {} while KCSAPIEnum.RESUPPLY_ACTION.name not in api_result: kca_u.kca.click_existing('upper_left', 'resupply|resupply_all.png') api_result = api.api.update_from_api( {KCSAPIEnum.RESUPPLY_ACTION}, need_all=False, timeout=1) kca_u.kca.sleep() sts.stats.resupply.resupplies_done += 1
def hook_chrome(self, host="localhost", port=9222): """Method that initializes the necessary hooks to Chrome using PyChromeDevTools. The visual hook connects to the tab that actually contains the Kancolle HTML5 canvas, while the api hook connects to the tab that interacts with the Kancolle backend. The former is used for detecting refreshes and when using the Chrome driver interaction mode, while the latter is used for reading all API interactions. Args: host (str, optional): Chrome dev protocol server address. Defaults to "localhost". port (int, optional): Chrome dev protocol server port. Defaults to 9222. Raises: Exception: could not find Kancolle tabs in Chrome. """ Log.log_msg("Hooking into Chrome.") self.visual_hook = PyChromeDevTools.ChromeInterface(host=host, port=port) self.api_hook = PyChromeDevTools.ChromeInterface(host=host, port=port) visual_tab = None visual_tab_id = None api_tab = None api_tab_id = None for n, tab in enumerate(self.visual_hook.tabs): if tab['url'] == VISUAL_URL: visual_tab = n visual_tab_id = tab['id'] if API_URL in tab['url']: api_tab = n api_tab_id = tab['id'] if visual_tab_id is None or api_tab_id is None: Log.log_error( "No Kantai Collection tab found in Chrome. Shutting down " "kcauto.") raise Exception( "No running Kantai Collection tab found in Chrome.") self.visual_hook.connect_targetID(visual_tab_id) Log.log_debug( f"Connected to visual tab ({visual_tab}:{visual_tab_id})") self.visual_hook.Page.enable() self.api_hook.connect_targetID(api_tab_id) self.api_hook.Network.enable() Log.log_debug(f"Connected to API tab ({api_tab}:{api_tab_id})") Log.log_success("Connected to Chrome")
def expect_returned_fleets(self): returned_fleets = [] for fleet in flt.fleets.expedition_fleets: if fleet.has_returned: returned_fleets.append(fleet.fleet_id) if len(returned_fleets) == 1: Log.log_msg(f"Fleet {returned_fleets[0]} has returned!") return True elif len(returned_fleets) > 1: display_text = kca_u.kca.readable_list_join(returned_fleets) Log.log_success(f"Fleets {display_text} have returned!") return True return False
def initialize_config(self): update = False config_json = self._load_cfg_json() initial_load = True new_update_time = os.path.getmtime(self.cfg_path) if self.general: initial_load = False if config_json != self.general._config: Log.log_msg("Changes detected from previous config load.") else: Log.log_debug("No change from previous config load.") self.last_cfg_update_time = new_update_time return False try: new_general = ConfigGeneral(config_json) new_expedition = ConfigExpedition(config_json) new_pvp = ConfigPvP(config_json) new_combat = ConfigCombat(config_json) new_event_reset = ConfigEventReset(config_json) new_ship_switcher = ConfigShipSwitcher(config_json) new_passive_repair = ConfigPassiveRepair(config_json) new_quest = ConfigQuest(config_json) new_scheduler = ConfigScheduler(config_json) update = True except Exception as e: Log.log_error(e) if update: Log.log_success("Config successfully loaded.") if not initial_load: Log.log_success("Hot-reloading config in 3...") sleep(1) Log.log_success("2...") sleep(1) Log.log_success("1...") sleep(1) self.general = new_general self.expedition = new_expedition self.pvp = new_pvp self.combat = new_combat self.event_reset = new_event_reset self.ship_switcher = new_ship_switcher self.passive_repair = new_passive_repair self.quest = new_quest self.scheduler = new_scheduler self.last_cfg_update_time = new_update_time return True
def resupply_fleets(self): fleets = self._get_fleets_to_resupply() for fleet in fleets: Log.log_msg(f"Resupplying Fleet {fleet.fleet_id}.") fleet.select() resupply_button = kca_u.kca.wait('upper_left', 'resupply|resupply_all.png') resupply_button.hover() while not kca_u.kca.exists('lower_right', 'resupply|resupply_all_done.png'): resupply_button.click() kca_u.kca.sleep(0.2) kca_u.kca.r['top'].hover() sts.stats.resupply.resupplies_done += 1 kca_u.kca.sleep()
def _resupply(self, group_id): Log.log_msg(f"Resupplying LBAS group {group_id}.") # kca_u.kca.while_wrapper( # self._lbas_panel_resupply_cond, self._lbas_panel_resupply_func, # timeout=10) api_result = {} while KCSAPIEnum.LBAS_RESUPPLY_ACTION.name not in api_result: kca_u.kca.click_existing('upper_right', 'combat|lbas_resupply.png') api_result = api.api.update_from_api( {KCSAPIEnum.LBAS_RESUPPLY_ACTION}, need_all=False, timeout=1) kca_u.kca.sleep() kca_u.kca.wait_vanish('lower_right', 'combat|lbas_resupply_in_progress.png') kca_u.kca.sleep(1)
def _toggle_quests(self, context): Log.log_debug( f"Checking for quests to activate with {context} context.") # quests should only be activated at this point relevant_quests = self._filter_quest_by_context(context) quest_types = sorted( list(self._get_types_from_quests(relevant_quests)), key=lambda quest_type: self.QUEST_TYPE_WEIGHTS[quest_type]) for quest_type in quest_types: Log.log_debug(f"Navigating to {quest_type} quests tab.") kca_u.kca.click_existing('left', f'quest|filter_tab_{quest_type}.png') api.api.update_from_api({KCSAPIEnum.QUEST_LIST}) kca_u.kca.wait('left', f'quest|filter_tab_{quest_type}_active.png', NEAR_EXACT) page_processed = False while not page_processed: for idx, quest in enumerate(self.visible_quests): if quest == -1: page_processed = True break if quest['api_no'] not in self.quest_library: continue quest_i = self.quest_library[quest['api_no']] if quest['api_state'] == 1 and quest_i in relevant_quests: Log.log_msg(f"Activating quest {quest_i.name}.") self._track_quest(quest_i) self._click_quest_idx(idx) sts.stats.quest.quests_activated += 1 continue if (quest['api_state'] == 2 and quest_i not in relevant_quests): Log.log_msg(f"Deactivating quest {quest_i.name}.") self._click_quest_idx(idx) self._untrack_quest(quest_i) sts.stats.quest.quests_deactivated += 1 continue page_processed = True if self.cur_page < self.tot_page: kca_u.kca.click_existing('lower_right', 'global|page_next.png', pad=PAGE_NAV) api.api.update_from_api({KCSAPIEnum.QUEST_LIST})
def receive_expedition(self): received_expeditions = False while kca_u.kca.exists('expedition_flag', 'expedition|expedition_flag.png'): Log.log_msg("Expedition received.") kca_u.kca.r['shipgirl'].click() api.api.update_from_api({KCSAPIEnum.PORT}) sts.stats.expedition.expeditions_received += 1 kca_u.kca.wait('lower_right_corner', 'global|next.png', 20) while kca_u.kca.exists('lower_right_corner', 'global|next.png'): kca_u.kca.sleep() kca_u.kca.r['shipgirl'].click() kca_u.kca.r['top'].hover() received_expeditions = True kca_u.kca.sleep() return received_expeditions
def reset_event_difficulty(self): map_name = cfg.config.combat.sortie_map.value Log.log_msg(f"Resetting {map_name} difficulty.") self._dismiss_chalkboards() self._switch_difficulty(cfg.config.event_reset.reset_difficulty) kca_u.kca.click_existing('lower_left', 'combat|c_world_1.png') kca_u.kca.click_existing( 'kc', f'combat|_event_world_{map_name}.png') self._dismiss_chalkboards() self._switch_difficulty(cfg.config.event_reset.farm_difficulty) self._increment_next_reset_sortie_count() sts.stats.combat.event_resets += 1 Log.log_msg( f"Next reset after sortie #{self.next_reset_sortie_count}.")
def send_expeditions(self): self._validate_expeditions() for fleet in self.fleets_to_send: expedition = choice( cfg.config.expedition.expeditions_for_fleet(fleet.fleet_id)) Log.log_msg(f"Sending fleet {fleet.fleet_id} to expedition " f"{expedition.expedition}.") self._select_world(expedition) self._select_expedition(expedition) if self._dispatch_expedition(fleet, expedition): kca_u.kca.wait('lower', 'expedition|expedition_recall.png') kca_u.kca.sleep(3) else: kca_u.kca.click_existing('lower', 'expedition|e_world_1.png') kca_u.kca.r['top'].hover() kca_u.kca.sleep()