def _process_pvp_list(self, data): try: pvp_data = data['api_data']['api_list'] pvp.pvp.update_pvp_list(pvp_data) except KeyError: Log.log_debug("No pvp data found in API response.") return None
def _process_expedition_start(self, data): try: complete_time = data['api_data']['api_complatetime'] return complete_time except KeyError: Log.log_debug("No expedition sent data") return None
def _process_battle_deck(self, data): try: deck_data = data['api_data']['api_ship_data'] shp.ships.update_local_ships(deck_data) for fleet in flt.fleets.combat_fleets: fleet.update_ship_data() except KeyError: Log.log_debug("No CF battle data found in API response.")
def _load_quest_data(self): Log.log_debug("Loading Quest data.") quest_data = JsonData.load_json('data|quests|quests.json') for quest_name in quest_data: quest = Quest(quest_name, quest_data[quest_name]) self.quest_library[quest_name] = quest self.quest_library[quest.quest_id] = quest self.quest_id_to_name[quest_data[quest_name]['id']] = quest_name
def _process_get_data(self, data): try: get_data_ship = data['api_data']['api_mst_ship'] shp.ships.update_ship_library(get_data_ship) JsonData.dump_json(get_data_ship, 'data|temp|get_data_ship.json') except KeyError: Log.log_debug("No getData found in API response.") return None
def _process_expedition_list(self, data): try: exp.expedition.available_expeditions = ( data['api_data']['api_list_items']) exp.expedition.populate_available_expeditions_per_world() except KeyError: Log.log_debug("No expedition list data found in API response.") return None
def _click_quest_idx(self, idx): Log.log_debug(f"Clicking quest at position {idx}.") quest_list_region = Region(kca_u.kca.game_x + 230, kca_u.kca.game_y + 173 + (idx * 102), 830, 30) quest_list_region.click() api.api.update_from_api( {KCSAPIEnum.QUEST_LIST, KCSAPIEnum.QUEST_TURN_IN}, need_all=False) kca_u.kca.sleep(1)
def update_local_ships(self, data): # from this api call, api_id = local_api_id, and api_ship_id = api_id Log.log_debug("Updating ship data from API.") self.local_ships = [] self.local_ships_by_local_id = {} for ship in data: ship_instance = ship_instance = self.get_ship_from_api_id( ship['api_ship_id'], ship) self.local_ships.append(ship_instance) self.local_ships_by_local_id[ship['api_id']] = ship_instance
def get_and_save_wgtf_data(cls): """Wrapper method for retrieving and storing WCTF data. """ Log.log_debug("Attempting to get WCTF data.") suffixes = cls._get_suffix_data() name_db = cls._get_ship_name_data(suffixes) Log.log_debug("Successfully downloaded WCTF data.") JsonData.dump_json(name_db, 'data|temp|wctf.json')
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 _switch_ship(self, ship): if kca_u.kca.click_existing( 'lower_right', 'shipswitcher|shiplist_shipswitch_button.png', cached=True): kca_u.kca.r['top'].hover() kca_u.kca.wait('right', 'shipswitcher|shiplist_button.png') return True else: Log.log_debug("Could not switch to selected ship.") return False
def select(self): Log.log_debug(f"Selecting fleet {self.fleet_id}.") kca_u.kca.click_existing('top_submenu', f'fleet|fleet_{self.fleet_id}.png') while not kca_u.kca.exists('top_submenu', f'fleet|fleet_{self.fleet_id}_active.png', FLEET_NUMBER_ICON): kca_u.kca.click_existing('top_submenu', f'fleet|fleet_{self.fleet_id}.png', FLEET_NUMBER_ICON) kca_u.kca.sleep()
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 _process_sortie_maps(self, data): try: available_maps = data['api_data']['api_map_info'] com.combat.update_combat_map_list(available_maps) except KeyError: Log.log_debug("No available combat map data in API response.") try: lbas_data = data['api_data']['api_air_base'] lbas.lbas.update_lbas_groups(lbas_data) except KeyError: Log.log_debug("No available lbas data in API response.")
def _process_sortie_start(self, data): try: select_nodes = ( data['api_data']['api_select_route']['api_select_cells']) com.combat.select_nodes = select_nodes except KeyError: Log.log_debug("No select node data found in API response.") try: next_node = data['api_data']['api_no'] return next_node except KeyError: Log.log_debug("No next node data found in API response.")
def process_battle_result(self, data): Log.log_debug("Processing battle results.") if 'api_first_clear' in data: cleared = data['api_first_clear'] == 1 if cleared: Log.log_debug("Map has been cleared.") self.map_cleared = True if 'api_get_ship' in data: dropped_ship_id = data['api_get_ship']['api_ship_id'] ship = shp.ships.get_ship_from_api_id(dropped_ship_id) self.rescued_ships.append(ship) Log.log_success(f"Rescued {ship.name} (#{ship.sortno}).") sts.stats.combat.ships_rescued += 1
def update_resource_stats(self, data): Log.log_debug("Updating resource data from API.") for rsc in data: if rsc['api_id'] == 1: self.fuel = rsc['api_value'] if rsc['api_id'] == 2: self.ammo = rsc['api_value'] if rsc['api_id'] == 3: self.steel = rsc['api_value'] if rsc['api_id'] == 4: self.bauxite = rsc['api_value'] if rsc['api_id'] == 6: self.bucket = rsc['api_value']
def __init__(self): self.start_time = datetime.now() self.loop_count = 0 self.combat = CombatStats(self.start_time) self.expedition = ExpeditionStats(self.start_time) self.pvp = PvPStats(self.start_time) self.ship_switcher = ShipSwitcherStats(self.start_time) self.resupply = ResupplyStats(self.start_time) self.repair = RepairStats(self.start_time) self.quest = QuestStats(self.start_time) self.recovery = RecoveryStats(self.start_time) self.rsc = ResourceStats(self.start_time) Log.log_debug("Stats module initialized.")
def load_json(cls, path): """Method for deserializing a json file. Args: path (str): kcauto-style path to json file. Returns: object: deserialized object. """ json_path = cls.create_path(path) Log.log_debug(f"Loading data from '{json_path}'.") with open(json_path, encoding='utf-8') as json_file: data = json.load(json_file) return data
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 _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 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 _reset_next_pvp_time(self): """Method to reset the next PvP time. Called when the next PvP time needs to be reset in subsequent times. """ jst_time = KCTime.convert_to_jst(datetime.now()) if 5 <= jst_time.hour < 15: temp_time = jst_time.replace(hour=15, minute=randint(5, 15)) elif 15 <= jst_time.hour < 24: temp_time = jst_time + timedelta(days=1) temp_time = temp_time.replace(hour=5, minute=randint(5, 15)) else: temp_time = jst_time.replace(hour=5, minute=randint(5, 15)) self.next_pvp_time = KCTime.convert_from_jst(temp_time) Log.log_debug( f"Next PvP at {KCTime.datetime_to_str(self.next_pvp_time)}.")
def update_repair_data(self, data): Log.log_debug("Updating Repair data from API.") self.docks_count = 0 self.docks_available_count = 0 self.complete_times = [] self.ships_under_repair = [] for dock_data in data: if dock_data['api_state'] > -1: self.docks_count += 1 if dock_data['api_state'] == 0: self.docks_available_count += 1 if dock_data['api_state'] == 1: self.complete_times.append( KCTime.convert_epoch(dock_data['api_complete_time'])) self.ships_under_repair.append(dock_data['api_ship_id'])
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 dump_json(cls, data, path, pretty=False): """Method for serializing an object into a json file. Args: data (object): object to serialize. path (str): kcauto-style path. pretty (bool, optional): flag for pretty-printing the dumped file. Defaults to False. """ json_path = cls.create_path(path) Log.log_debug(f"Writing data to '{json_path}'.") with open(json_path, 'w', encoding='utf-8') as json_file: if not pretty: json.dump(data, json_file, ensure_ascii=False) else: json.dump(data, json_file, ensure_ascii=False, indent=2)
def manage_quests(self, context=None, fast_check=True): # dismiss Ooyodo kca_u.kca.r['center'].click() kca_u.kca.sleep(1) if context and context != self.last_checked_context: fast_check = False self.last_checked_context = context if not context and self.last_checked_context: context = self.last_checked_context if fast_check: if self._turn_in_quests(context): self._toggle_quests(context) else: self._turn_in_quests(context) self._toggle_quests(context) Log.log_debug( f"Tracked quests: {list(self.next_check_intervals.keys())}")
def predict_battle(self, data): print(data) Log.log_debug("Predicting battle from data.") if 'api_flavor_info' in data: print('boss node') # if 'api_midnight_flag' in data: # print(f"nightbattle: {data['api_midnight_flag']}") new_hps = ( list(data['api_f_nowhps'] + data['api_f_nowhps_combined']) if flt.fleets.combined_fleet else list(data['api_f_nowhps'])) print('calculate hps') new_hps = self._calculate_hps(new_hps, data) print(new_hps) flt.fleets.combat_fleets[0].update_ship_hps(new_hps[0:6]) Log.log_msg(flt.fleets.combat_fleets[0]) if flt.fleets.combined_fleet: flt.fleets.combat_fleets[1].update_ship_hps(new_hps[6:12]) Log.log_msg(flt.fleets.combat_fleets[1])
def _set_to_desired_state(self, start, stop): if start is stop: return Log.log_msg(f"Switching LBAS state from {start.display_name} to " f"{stop.display_name}.") state_order = list(LBASStateEnum) idx = state_order.index(start) relative_order = state_order[idx:] + state_order[:idx] for idx, state in enumerate(relative_order): if state is stop: break Log.log_debug(f"Switching LBAS state from {state.display_name} to " f"{relative_order[idx + 1].display_name}.") cur_name = state.name.lower() next_name = relative_order[idx + 1].name.lower() kca_u.kca.click_existing('upper_right', f'combat|lbas_group_mode_{cur_name}.png') kca_u.kca.r['top'].hover() kca_u.kca.wait('upper_right', f'combat|lbas_group_mode_{next_name}.png') kca_u.kca.sleep(0.5) api.api.update_from_api({KCSAPIEnum.SORTIE_ASSIGN_LBAS})
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