Пример #1
0
 def _filter_item_planner_unreachable(self, stack: ItemStack):
     for k, v in list(stack.items()):
         if self.material_planner.item_name_to_id.get('zh').get(
                 k, None) is None or "芯片" in k:
             logger.warning(
                 f"filter {k} in ItemStack:Item not exist in planner.")
             stack.pop(k)
     logger.debug("after filter:" +
                  stack.formatItems('%item%(%quantity%) '))
     return stack
Пример #2
0
 def getEvolveCost(self, now=1, target=2):
     from ArkType.ItemStack import ItemStack
     if target < now:
         logger.error(f"角色目标精英化阶段需要大于当前精英化阶段")
         return ItemStack()
     ret = ItemStack()
     for targ in range(now, target):
         _UM = targ + 1
         try:
             for item in self["phases"][_UM]["evolveCost"]:
                 ret.addItemFromCharCost(item)
         except Exception as e:
             logger.error(f"CHAR={self['name']},EVOLVE={_UM},ERR={str(e)}")
     return ret
Пример #3
0
def _check_request_with_stage(stagecode, request):
    try:
        if request in ItemStack().registItem(getDropList(stagecode)):
            return True
        return False
    except IndexError:
        logger.error(f"无法获得关卡'{stagecode}'所对应的掉落物,是否关卡名称错误?")
        return False
Пример #4
0
 def getSkillUpgradeCost(self,
                         skillID=0,
                         level_from=1,
                         level_to=7,
                         show=False):
     from ArkType.ItemStack import ItemStack
     skillID -= 1
     if len(self["skills"]) < skillID:
         logger.error(f"该角色无第{skillID}技能 最大技能:{len(self['skills'])}")
         return ItemStack()
     ret = ItemStack()
     for level in range(level_from, level_to):
         try:
             if level <= 6:
                 for item in self["allSkillLvlup"][level - 1]['lvlUpCost']:
                     ret.addItemFromCharCost(item)
             elif 6 < level <= 9:
                 for item in self["skills"][skillID]["levelUpCostCond"][
                         level - 7]["levelUpCost"]:
                     ret.addItemFromCharCost(item)
         except Exception as e:
             logger.error(
                 f"CHAR={self['name']},SKILL={skillID},LVL={level},ERR={str(e)}"
             )
     if show:
         logger.notice(
             f"{self['name']}{skillID}技能 {level_from}->{level_to} "
             f"消耗{ret.formatItems('%item%(%quantity%) ')}")
     return ret
Пример #5
0
 def __init__(self, adb_host=None, gui_enabled=False, with_commander=False):
     os.system("cls")
     ensure_adb_alive()
     self.Thread = Job()
     self.after_finish_Thread = Job()
     self.has_after_finish_Thread = False
     self.__adb_host = adb_host
     self.adb = ADBConnector(adb_serial=adb_host)
     self.root = Path(__file__).parent
     self.gui_enabled = gui_enabled
     self.gameTemp = GameTemplate("./imgReco/img/")
     RootData.set("gameTempGlobal", self.gameTemp)
     self.handler = mumu_handler.getSelectGameNow()
     self.getScreenShot = self.adb.screenshot
     self.reconizerC, self.reconizerE, self.reconizerN, self.reconizerN2 = getReconizer(
     )
     self.time_sleep = time_sleep
     self.clicker = Clicker(self.adb)
     self.mapper = Mapping.Mapping("./ArkMapping/MapData2.dat")
     self.material_planner = MaterialPlanning()
     self.playerInfo = None
     self.in_attack = False
     self.total_itemStack = ItemStack()
     self.current_itemStack = ItemStack()
     self._force_stop = False
     self.mapData = ArkMap(self)
     self.dropLinker = ItemDropLinker()
     if with_commander:
         self.commander = commander()
         self.commandRegister(self.commander)
         self.commander.StartTrack()
     if self.handler[1] != -1:
         if self.handler[0][4] == 1280 and self.handler[0][5] == 809:
             logger.system("检测到Mumu模拟器,已开启快速图像采集")
             self.getScreenShot = getscreenshot
         else:
             logger.notice("检测到Mumu模拟器,请使用1280x720模式以开启快速图像采集!")
Пример #6
0
class ArkAutoRunner(object):
    def __init__(self, adb_host=None, gui_enabled=False, with_commander=False):
        os.system("cls")
        ensure_adb_alive()
        self.Thread = Job()
        self.after_finish_Thread = Job()
        self.has_after_finish_Thread = False
        self.__adb_host = adb_host
        self.adb = ADBConnector(adb_serial=adb_host)
        self.root = Path(__file__).parent
        self.gui_enabled = gui_enabled
        self.gameTemp = GameTemplate("./imgReco/img/")
        RootData.set("gameTempGlobal", self.gameTemp)
        self.handler = mumu_handler.getSelectGameNow()
        self.getScreenShot = self.adb.screenshot
        self.reconizerC, self.reconizerE, self.reconizerN, self.reconizerN2 = getReconizer(
        )
        self.time_sleep = time_sleep
        self.clicker = Clicker(self.adb)
        self.mapper = Mapping.Mapping("./ArkMapping/MapData2.dat")
        self.material_planner = MaterialPlanning()
        self.playerInfo = None
        self.in_attack = False
        self.total_itemStack = ItemStack()
        self.current_itemStack = ItemStack()
        self._force_stop = False
        self.mapData = ArkMap(self)
        self.dropLinker = ItemDropLinker()
        if with_commander:
            self.commander = commander()
            self.commandRegister(self.commander)
            self.commander.StartTrack()
        if self.handler[1] != -1:
            if self.handler[0][4] == 1280 and self.handler[0][5] == 809:
                logger.system("检测到Mumu模拟器,已开启快速图像采集")
                self.getScreenShot = getscreenshot
            else:
                logger.notice("检测到Mumu模拟器,请使用1280x720模式以开启快速图像采集!")

        # TODO:界面识别

    def run_after_current(self, lambda_funcing=None, cover=False):
        if lambda_funcing.__class__.__name__ != 'function':
            logger.error(
                f"function type must be 'function', got '{lambda_funcing.__class__.__name__}' instead"
            )
            return False
        if self.has_after_finish_Thread and not cover:
            logger.error("当前已设定运行结束后任务,若想覆盖请使用cover=True")
            return False
        self.after_finish_Thread = Job(target=lambda_funcing)
        self.has_after_finish_Thread = True
        return True

    def reGetGame(self):
        mumu_handler.regetgame()
        self.handler = mumu_handler.getSelectGameNow()

    def kill_current_task(self):
        if self.Thread.is_running:
            # self.Thread.stop()
            stop_thread(self.Thread)
            logger.warning("已强制停止当前进程,清空进程信息。请检查当前页面状态。")
            self.Thread = Job()

    def stop_current_attack(self):
        if not self._force_stop:
            self._force_stop = True
            logger.notice("在本次进攻完成后将停止攻击")

    @staticmethod
    def _kill_adb():
        import psutil
        for pid in psutil.pids():
            if psutil.Process(pid).name() == 'adb.exe':
                try:
                    psutil.Process(pid).kill()
                except Exception as e:
                    logger.error(str(e))

    def restart_adb(self, kill=False):
        logger.notice("关闭adb中...", flush=False)
        try:
            del self.adb
        except AttributeError:
            pass
        if kill:
            self._kill_adb()
        logger.notice("关闭adb中...完成。正在重启adb")
        ensure_adb_alive()
        self.adb = ADBConnector(adb_serial=self.__adb_host)
        # mumu_handler.regetgame()
        self.reGetGame()
        if self.handler[1] != -1:
            if self.handler[0][4] == 1280 and self.handler[0][5] == 809:
                logger.system("检测到Mumu模拟器,已开启快速图像采集")
                self.getScreenShot = getscreenshot
            else:
                logger.notice("检测到Mumu模拟器,请使用1280x720模式以开启快速图像采集!")

    def resume_attack(self):
        if self._force_stop:
            self._force_stop = False
            logger.notice("已恢复进攻许可")

    def run_withThr(self, lambda_funcing=None):
        if lambda_funcing.__class__.__name__ != 'function':
            logger.error(
                f"function type must be 'function', got '{lambda_funcing.__class__.__name__}' instead"
            )
            return False
        if self.Thread.is_running:
            logger.error("当前线程正在运行中,请等待执行完毕。")
            return False
        self.Thread = Job(
            target=lambda: ark_job_runnger(lambda_funcing, self, 0))
        logger.notice("线程开始运行")
        self.Thread.start()
        return True

    def _check_prts_type(self):
        prts_state = recognizePrtsStatus(self)
        if prts_state == 'DISABLE':
            self.adb.touch_tap((1140, 594), (10, 10))
            logger.notice("开启代理指挥")
            return True
        if prts_state == "LOCK":
            logger.error("代理指挥已锁定,是否已经使用自己的干员三星通关?")
            return False
        return True

    def logStageDrop(self,
                     drop_list,
                     atk_stage,
                     atk_num,
                     cost_time,
                     log_drop=True,
                     show_drop=True):
        end_items = recognizeEndItemsWithTag(self, drop_list)
        _add_drop_to_list(end_items, self.current_itemStack,
                          self.total_itemStack)
        if show_drop:
            logger.common(formatDropItem(end_items))
        if log_drop:
            self._log_drop_item(
                end_items,
                f'./logs/DROP_{time.strftime("%Y-%m-%d_%H_%M_%S")}_{atk_stage}_{atk_num}.jpg'
            )
            self.dropLinker.writeStageDrop(atk_stage, end_items, cost_time)

    def attack_extermination(self,
                             stage_set=None,
                             max_atk=1,
                             use_ap_supply=False,
                             use_diamond=False,
                             use_thread=True,
                             log_drop=True):
        if use_thread:
            return self.run_withThr(lambda: self.attack_extermination(
                stage_set, max_atk, use_ap_supply, use_diamond, False, log_drop
            ))

        def error(msg, err):
            logger.error(f"进攻剿灭作战'{stage_set}'失败[{msg}]:{err}")
            return False

        # 提示信息
        logger.warning('请保证在本剿灭作战中能够稳定400杀通关,否则在结束阶段将可能无法识别,需要人工手动介入!')
        # getStageSet
        if stage_set is None:
            stage_set = getStageTag().getStage('乌萨斯', '废弃矿区')  # TODO:每次轮换更新
        elif isinstance(stage_set, str):
            try:
                stage_set = getStageTag().getStage(None, stage_set)
            except ValueError as e:
                return error('无法获取关卡数据', e)
        if not isinstance(stage_set, StageInfo):
            return error(
                '无法解析关卡数据',
                f'StageSet Typeof {type(stage_set)} cannot be parsed to StageInfo'
            )
        # detect map
        if not self.mapData.fromMaptoMapMain():
            return error('无法跳转到地图界面', '当前界面无对应跳转方式')
        # check
        if not self.getLocation(self.getScreenShot(), ['map_main']):
            return error('跳转到地图界面失败', '可能存在通知等影响跳转。请检查!')
        # click into extre
        self.clicker.mouse_click(537, 662, t=2)
        # select position
        click_data = EXTER_CLICK_DATA.get(stage_set.getName())
        if click_data is None:
            return error('无效的地图名称', f"无效的地图名称:'{stage_set}'")
        for x, y in click_data:
            self.clicker.mouse_click(x, y, t=1.3)
        # start attack
        map_cost = stage_set.getCost()[0]  # ap_cost
        decide_atk = max_atk
        atk_stage = stage_set.getName()
        atk_num = 0
        self.current_itemStack = ItemStack()
        if not self._check_prts_type():
            return False
        inte_now, inte_max = recognizeBeforeOperationInte(self)
        self.playerInfo = PlayerConfig(int(inte_now), int(inte_max)) if self.playerInfo is None \
            else self.playerInfo.update(inte_now, inte_max)
        while self.playerInfo.startOperation(map_cost, use_diamond or use_ap_supply) \
                and max_atk != 0 and not self._force_stop:
            # 检测当前合成玉数量
            camp_num = recognizeCampaignNum(self)
            if camp_num['success']:
                camp_cur = camp_num['now']
                camp_lim = camp_num['max']
                logger.notice(
                    f"当前合成玉数量为{camp_cur},最大合成玉获取数量为{camp_lim},还可获取{camp_lim - camp_cur}合成玉"
                )
                if camp_lim - camp_cur < MIN_ENABLE_EXTER_ATK_CAMP_NUM:
                    logger.error(
                        f"当前合成玉与最大合成玉获取上限小于最小可进攻设定值'{MIN_ENABLE_EXTER_ATK_CAMP_NUM}',放弃攻击"
                    )
                    break
            else:
                return error('无效合成玉信息', '无法在当前页面获取合成玉信息,请检查当前位置!')
            max_atk -= 1
            atk_num += 1
            logger.common("当前实际理智(%s/%s),开始行动(第%s次/共%s次)。" %
                          (inte_now, inte_max, atk_num, decide_atk))
            self.clicker.mouse_click(1148, 658)
            # if self.playerInfo.intellect + 1 < cost:
            max_atk, atk_num, uType = self._check_inte_using(
                inte_now, map_cost, max_atk, atk_num, use_ap_supply,
                use_diamond)
            if uType is True:
                continue
            elif uType is False:
                break
            else:
                pass
            waitStartOperationAttack(
                self, lambda: self.clicker.mouse_click(1105, 521, at=0.3))
            start_time = time.time()
            time.sleep(12)
            # 循环检测当前剿灭作战信息
            while True:
                try:
                    kill_count = self.reconizerE.recognize2(
                        imgops.crop_blackedge2(
                            Image.fromarray(self.getScreenShot(
                                450, 20, 60, 21))),
                        subset='0123456789/')[0]
                except:
                    kill_count = '-'
                reco_enermy = '-'
                try:
                    reco_enermy = self.reconizerE.recognize2(
                        imgops.crop_blackedge2(
                            imgops.clear_background(
                                Image.fromarray(
                                    cv2.split(
                                        self.getScreenShot(618, 20, 102,
                                                           21))[0]), 40)),
                        subset='0123456789/')[0]
                    # enermy_num, total_enermy_num = reco_enermy.split('/', 1)
                except:
                    logger.warning(f"识别敌人数量出现错误(结果为{reco_enermy})")
                    # enermy_num, total_enermy_num = -1, -1
                try:
                    lo_count = self.reconizerE.recognize2(
                        imgops.crop_blackedge2(
                            Image.fromarray(
                                cv2.split(self.getScreenShot(837, 20, 26,
                                                             20))[2]), 120),
                        subset='0123456789/')[0]
                except:
                    lo_count = '-'
                logger.notice(
                    f"[ExtAssist]当前击杀数量:{kill_count:>3}|敌人数量:{reco_enermy:>7}|剩余防御点数:{lo_count:>2}"
                )
                if waitExterminationEnd(self):
                    break
            cost_time = time.time() - start_time
            logger.notice(f"剿灭攻击结束!本次花费{cost_time:2f}秒")
            time.sleep(4)
            self.clicker.mouse_click(1209, 311, t=7)
            drop_list = {('AP_GAMEPLAY', '理智'), ('DIAMOND_SHD', '合成玉'),
                         ('sprite_exp_card_t2', '初级作战记录'),
                         ("EXP_PLAYER", "声望"), ("GOLD", "龙门币")}
            self.logStageDrop(drop_list, atk_stage, atk_num, cost_time,
                              log_drop)
            self.clicker.mouse_click(112, 150, t=5)
            self.playerInfo.endOperation()
            time.sleep(5)
        if max_atk != 0:
            logger.warning(
                f"已完成{atk_num}次,剩余{max_atk if max_atk > 0 else 'INFINITE'}次未攻击"
            )
        self.playerInfo.stopOperation(-1)
        if self._force_stop:
            logger.warning('已强制停止!')
            self._force_stop = False
        logger.notice(
            f"[关卡数据统计]攻击{atk_stage} {atk_num}次,消耗"
            f"{map_cost * atk_num}理智,获取掉落物:{self.current_itemStack.formatItems('%item%(%quantity%) ')}"
        )
        return True

    def attack_planned(self, stage_set, use_thread=True, update_stage=False):
        if use_thread:
            return self.run_withThr(
                lambda: self.attack_planned(stage_set, False))
        if not isinstance(stage_set, (dict, StageSet)):
            logger.error("需要StageSet类型的stage_set")
            return False
        use_ap = stage_set.get('use_ap_supply', True)
        use_diam = stage_set.get('use_diamond', False)
        total_max_atk = stage_set.get('total_max_atk', -1)
        logger.notice(
            f"[计划攻击][全局攻击次数{'不限制' if total_max_atk == -1 else str(total_max_atk)}]"
            f" 计划任务{str(stage_set.get('list', []))}")
        logger.notice(
            f"[计划攻击]{'不' if not use_ap else ''}使用理智药剂 {'不' if not use_diam else ''}使用源石恢复"
        )
        stages = stage_set.get('stages', [])
        _CUR_STAGE = None
        _TAR_STAGE = None
        for index, stageName in enumerate(stage_set.get('list', [])):
            if stages[index]["stage"] != stageName:
                logger.error(
                    f"关卡信息核对失败,请检查StageSet!(需要{stageName},得到了{stages[index]['stage']})"
                )
                continue
            atkT = stages[index]["max_atk"]
            require = stages[index]['require'] if stages[index][
                'use_require'] else None
            _TAR_STAGE = stageName
            logger.notice(
                f"[计划攻击]开始攻击 关卡{_TAR_STAGE} "
                f"{atkT if total_max_atk == -1 or atkT <= total_max_atk else total_max_atk}次 "
                + f"需求列表为{str(stages[index]['require'])}"
                if stages[index]["use_require"] else f"无需求要求")
            at_stage = 0
            try:
                at_stage = 1
                assert self.mapData.changeStage(_CUR_STAGE, _TAR_STAGE)
                at_stage = 2
                assert self.attack_simple(
                    _TAR_STAGE,
                    atkT if total_max_atk == -1 or atkT <= total_max_atk else
                    total_max_atk,
                    require=require,
                    use_ap_supply=use_ap,
                    use_diamond=use_diam,
                    use_thread=False)
                time_sleep(2)
                _CUR_STAGE = _TAR_STAGE
                continue
            except (Exception, AssertionError):
                logger.error(
                    f"计划执行计划中关卡\'{stageName}\'失败:在{['准备', '切换地图', '自动攻击'][at_stage]}中:"
                )
                traceback.print_exc()
                _CUR_STAGE = None

    def attack_simple_autolocate(self,
                                 stage,
                                 max_atk=-1,
                                 require=None,
                                 use_ap_supply=True,
                                 use_diamond=False,
                                 use_thread=True):
        if use_thread:
            return self.run_withThr(lambda: self.attack_simple_autolocate(
                stage, max_atk, require, use_ap_supply, use_diamond, False))
        step = '初始化'
        try:
            logger.notice(f"[自动定位]准备定位关卡{stage}...")
            step = '切换关卡'
            assert self.mapData.changeStage(None, stage)
            step = '进攻'
            assert self.attack_simple(stage, max_atk, require, use_ap_supply,
                                      use_diamond, False)
        except Exception as e:
            logger.error(f"执行自动化攻击出现错误:关卡{stage} 步骤:{step} 错误信息:{str(e)}")
            traceback.print_exc()
            return False
        return True

    def attack_simple(self,
                      stage="AUTO",
                      max_atk=-1,
                      require=None,
                      use_ap_supply=True,
                      use_diamond=False,
                      use_thread=True,
                      planned_stage=None,
                      log_drop=True):
        if use_thread:
            return self.run_withThr(lambda: self.attack_simple(
                stage, max_atk, require, use_ap_supply, use_diamond, False))
        # 清空遗留的强制停止信息
        # self._force_stop = False
        # 对已指定的关卡进行自动攻击至无体力
        map_cost = getMapCost()
        atk_stage = stage
        RETURN_VALUE = True
        # _LOC = self.getLocation(ranges=ArkMap.RECO)
        # if 'map_atk' not in _LOC:
        if not self.gameTemp.dingwei("on_atk_beg\\start.png",
                                     self.getScreenShot(1102, 624, 150, 66)):
            logger.error("当前未在关卡选择内,无法使用简单模式进行挂机!")
            logger.error("请切换到关卡内(能够直接点击开始行动)并重启程序")
            return False

        if stage == "AUTO":
            # 使用tesseract检测关卡最优
            rec = recognizeOperation(
                Image.fromarray(self.getScreenShot(881, 81, 86, 34)), map_cost)
            # rec = self._check_ocr_until_success(881, 81, 86, 35, map_cost)
            if rec[0]:
                logger.common("检测到当前关卡:%s 消耗%s" % (rec[0], rec[1].replace(
                    "ap_", "理智:").replace("et_", "门票:")))
                atk_stage = rec[0]
            else:
                logger.error("请使用stage='STAGE_CODE'进行手动设置!")
                return False
        else:
            rec = [stage, map_cost.where(stage)]
            if rec[1] is None:
                logger.error("未找到当前关卡%s所对应的体力消耗,程序版本是否过时?" % stage)
                return False
            logger.notice(
                "设定当前关卡:%s 消耗%s" %
                (stage, rec[1].replace("ap_", "理智:").replace("et_", "门票:")))
        ap_text = rec[1].split("_")[0].replace("ap", "理智").replace("et", "门票")
        if ap_text == "门票":
            use_diamond, use_ap_supply = False, False
        cost = int(rec[1].split("_")[1])
        # 检测理智用reconizer
        request_require = False
        if require is not None:
            request_require = True
            if not _check_request_with_stage(atk_stage, require):
                logger.error('检测掉落物与需求不匹配!请检查')
                return False

        now, max_ = self.reconizerC.recognize2(
            Image.fromarray(self.getScreenShot(1120, 20, 130, 40)),
            subset="1234567890/")[0].split("/")
        if int(now) < cost and (not use_ap_supply and not use_diamond):
            logger.error("当前%s(%s)小于关卡消耗%s,已停止" % (ap_text, ap_text, now))
            return False
        self.playerInfo = PlayerConfig(
            int(now),
            int(max_)) if self.playerInfo is None else self.playerInfo.update(
                now, max_)
        delay_time = 25
        decide_atk = max_atk if max_atk != -1 else "AUTO"
        atk_num = 0
        # 检查代理指挥
        if not self._check_prts_type():
            return False
        self.current_itemStack = ItemStack().registItem(
            getDropList(atk_stage), 0)
        while self.playerInfo.startOperation(
                cost, use_diamond
                or use_ap_supply) and max_atk != 0 and not self._force_stop:
            max_atk -= 1
            atk_num += 1
            now, max_ = recognizeBeforeOperationInte(self)
            if request_require:
                logger.debug("Require List:" +
                             require.formatItems("%item%(%quantity%) "))
                logger.debug(
                    "Get:" +
                    self.current_itemStack.formatItems("%item%(%quantity%) "))
                if require <= self.current_itemStack:
                    max_atk = 0
                    break
            logger.common("当前实际%s(%s/%s),预测%s(%s),开始行动(第%s次/共%s次)。" %
                          (ap_text, now, max_, ap_text,
                           self.playerInfo.intellect + 1, atk_num, decide_atk))
            self.clicker.mouse_click(1148, 658)
            max_atk, atk_num, uType = self._check_inte_using(
                now, cost, max_atk, atk_num, use_ap_supply, use_diamond)
            if uType is True:
                continue
            elif uType is False:
                break
            waitStartOperationAttack(
                self, lambda: self.clicker.mouse_click(1105, 521, at=0.3))
            start_time = time.time()
            # 检查结束
            time.sleep(delay_time)
            # TODO:检查结束的时候还应该检查是否失败
            logger.debug("等待结束,开始检测通关信息。")
            while not self.getLocation(getscreenshot(99, 577, 427, 116),
                                       "atk_end",
                                       sub_area=[99, 577, 427, 116]):
                time.sleep(1)
                if self.gameTemp.dingwei("on_atk_end\\lvl_up.png",
                                         self.getScreenShot(657, 328, 50, 50)):
                    time.sleep(4)
                    logger.notice("您已升级!")
                    self.playerInfo.restore()
                    self.adb.touch_tap((931, 278), (15, 15))
            cost_time = time.time() - start_time
            logger.common("行动结束,本次花费%.2f秒" % cost_time)
            delay_time = cost_time - 9
            time.sleep(7)
            self.logStageDrop(atk_stage, atk_stage, atk_num, cost_time,
                              log_drop)
            self.adb.touch_tap((796, 466), (15, 15))
            time.sleep(1)
            self.adb.touch_tap((796, 466), (15, 15))
            time.sleep(3)
            self.playerInfo.endOperation()
        if max_atk != 0:
            logger.warning(
                f"已完成{atk_num}次,剩余{max_atk if max_atk > 0 else 'INFINITE'}次未攻击"
            )
        if planned_stage is not None:
            planned_stage.delStage(atk_stage, atk_num)
        self.playerInfo.stopOperation(-1)
        if self._force_stop:
            logger.warning("已强制停止!")
            self._force_stop = False
        logger.notice(
            f"[关卡数据统计]攻击{stage if stage != 'AUTO' else rec[0] + '(自动识别)'} {atk_num}次,消耗"
            f"{cost * atk_num}{ap_text},获取掉落物:{self.current_itemStack.formatItems('%item%(%quantity%) ')}"
        )
        return RETURN_VALUE

    def attackOnce(self, stage=None):
        if stage is None:
            return self.attack_simple(max_atk=1)
        else:
            return self.attack_simple_autolocate(stage, max_atk=1)

    def planRecruit(self, force=0, use_thread=True):
        if use_thread:
            return self.run_withThr(lambda: self.planRecruit(force, False))
        # 先检查当前所处位置,是否是在选择Tag中或公开招募界面
        if not self.mapData.toMain():
            return logger.error('无法返回主界面!')
        if self.getLocation(ranges=["main"]):
            self.clicker.mouse_click(1003, 510, t=1.2)
        assert self.mapper.locateImage(getscreenshot(), 'recruit')
        try:
            recruit_detail = getRecruitDetail(self)
            for _ in recruit_detail:
                logger.debug(str(_))
            for _ in recruit_detail:
                if _["type"] == 'FINISH':
                    _['can'] = True
                    x, y = _['centre']
                    sel_tag = self.getScreenShot(x - 192, y - 65, 192 * 2,
                                                 65 - 8)  # +-192 ->x,-8~-65->y

                    y += 77
                    self.clicker.mouse_click(x, y, t=0.7)
                    waitGachaSkipBtn(self)
                    time_sleep(2.3)
                    self.clicker.mouse_click(1220, 45)
                    time_sleep(3)
                    ge_im = self.getScreenShot()
                    ge_im[20:20 + sel_tag.shape[0],
                          40:40 + sel_tag.shape[1]] = sel_tag
                    del sel_tag
                    cv2.imwrite(
                        f'./logs/RECRUIT_{time.strftime("%Y-%m-%d_%H_%M_%S")}_{str(_["id"])}.jpg',
                        ge_im, [int(cv2.IMWRITE_JPEG_QUALITY), 80])
                    self.clicker.mouse_click(1236, 147, t=1.3)
                if _["can"]:
                    logger.common("选择第%d个公招,当前force=%d" % (_["id"] + 1, force))
                    x, y = _["centre"]
                    self.clicker.mouse_click(x, y, t=1)
                    plan_, rec_ = self.planRecruitSingle(force=force > 0)
                    if plan_["decision"]["action"] == 'refresh':
                        waitRecruitRefreshSuccess(self)
                        time_sleep(1)
                        plan_, rec_ = self.planRecruitSingle(select=False)
                        if plan_['decision']['action'] == 'choose':
                            self._select_recruit_tag(plan_["decision"])
                        elif force > 0:
                            logger.notice("强制选择:空项目")
                            self._select_recruit_tag({
                                "action": "choose",
                                "tag": [],
                                "time": (9, 0)
                            })
                            plan_['decision']['action'] = "choose"
                        elif plan_['decision']['action'] == 'exit':
                            self.clicker.mouse_click(978, 647, t=1,
                                                     at=1)  # back
                    force -= 1
                    if plan_['decision']['action'] not in ["choose", "exit"]:
                        self.clicker.mouse_click(978, 647, t=1, at=1)
                time_sleep(1.5)
            self.clicker.mouse_click(72, 40, t=1.5, at=1.4)
        except Exception as e:
            logger.error("出现错误,强制返回主页")
            logger.error("Error:%s" % str(e))
            self.clicker.mouse_click(271, 38, t=1)
            self.clicker.mouse_click(93, 173, t=3)

    def planRecruitSingle(self,
                          taglist=None,
                          can_refresh=False,
                          select=True,
                          force=False):
        # 检查当前所处位置
        if taglist is None:
            reco_tags = recognizeRecruit(self)
            if None in reco_tags["tags"]:
                logger.error("识别的标签中存在无法识别项,请手动选择!")
                logger.debug("标签:%s" % str(reco_tags))
                return None, reco_tags
            can_refresh = reco_tags["can_refresh"]
        else:
            reco_tags = {
                "tags": taglist,
                "recoV": None,
                "can_refresh": can_refresh,
                "cost": 0
            }
        logger.common("获取标签:" + str(reco_tags["tags"]) +
                      "用时%.3f秒" % reco_tags["cost"])
        result = planRecruit(reco_tags["tags"], can_refresh)
        if result["decision"]["action"] == "exit":
            logger.common("建议跳过本次公开招募")
        elif result["decision"]["action"] == 'refresh':
            logger.common("建议刷新本次公开招募")
        else:
            decision = result["decision"]
            logger.common("建议进行招募:选择TagIndex:" + str(decision["tag"]) +
                          "时间:%sH%sM" %
                          (decision["time"][0], decision["time"][1]))
        if select:
            result['decision'] = self._select_recruit_tag(
                result["decision"], force)
        return result, reco_tags

    def _select_recruit_tag(self, decision, force=False):
        if decision["action"] == "exit":
            if force:
                decision = {'action': 'choose', 'time': (9, 0), 'tag': []}
            else:
                self.clicker.mouse_click(979, 648)
                return decision
        if decision["action"] == 'refresh':
            self.clicker.mouse_click(970, 408, t=0.7)
            self.clicker.mouse_click(841, 507)
            return decision
        if decision["action"] == "choose":
            # 检查是否成功(防止出现龙门币不足的情况)
            for index in decision["tag"]:
                x, y = [(448, 384), (616, 384), (782, 384), (448, 456),
                        (616, 456)][index]
                self.clicker.mouse_click(x, y)
            for _ in range(decision["time"][0] - 1):
                self.clicker.mouse_click(452, 150)
            for _ in range(decision["time"][1] - 1):
                self.clicker.mouse_click(618, 150)
            _LM, _RE, _AL = recognizeCanRecruit(self)
            if _AL:
                self.clicker.mouse_click(974, 582, t=1)
            else:
                logger.error(
                    f"当前{'' if _LM else '龙门币'}{'和' if not _LM and not _RE else ''}"
                    f"{'' if _LM else '公开招募券'}不足,无法招募,返回。")
                self.clicker.mouse_click(979, 648)
            return decision

    def _clear_task_reward(self):
        while self.gameTemp.dingwei("main\\task\\take.png", getscreenshot(1012, 131, 95, 27), 0.1) and not \
                self.gameTemp.dingwei("main\\task\\finish.png", getscreenshot(95, 157, 74, 33), 0.9):
            self.clicker.mouse_click(1118, 146, rx=(-25, 25))
            self.clicker.mouse_click(1115, 147, rx=(-25, 25), t=0.5)
        self.clicker.mouse_click(66, 171, t=0.6)
        self.clicker.mouse_click(66, 171, t=0.3)
        if self.gameTemp.dingwei("main\\task\\take.png", getscreenshot(1012, 131, 95, 27), 0.4) and not \
                self.gameTemp.dingwei("main\\task\\finish.png", getscreenshot(95, 157, 74, 33), 0.9):
            self._clear_task_reward()
        return True
        # self.clicker.mouse_click(1077, 138, t=0.1)
        # self.clicker.mouse_click(1077, 138, t=0.1)

    def check_to_main_notice(self):
        # 先检查通知事件(检查是否每日新登录)
        # 检查每日签到事件
        # 检查特殊事件(如登录活动etc)
        pass

    def clear_daily_task(self, back_to_main=True, use_thread=True):
        if use_thread:
            return self.run_withThr(
                lambda: self.clear_daily_task(back_to_main, False))
        time_sleep(1.2)
        if not self.mapData.toMain():
            logger.error('无法返回到主界面!')
            return False
        if self.mapper.locateImage(getscreenshot(), 'main', method=3):
            self.clicker.mouse_click(805, 606, t=1.5)
        if self.mapper.locateImage(getscreenshot(), ["task", "task_newbee"],
                                   method=3)[0] == "task_newbee":
            self.clicker.mouse_click(768, 36, t=0.8)
            self._clear_task_reward()
            logger.notice(f"日常任务剩余识别:{recognizeTaskLeft(self)}")
            self.clicker.mouse_click(968, 36, t=0.8)
            self._clear_task_reward()
            logger.notice(f"周常任务剩余识别:{recognizeTaskLeft(self)}")

        else:
            self._clear_task_reward()
            logger.notice(f"日常任务剩余识别:{recognizeTaskLeft(self)}")
            self.clicker.mouse_click(865, 37, t=0.8)
            self._clear_task_reward()
            logger.notice(f"周常任务剩余识别:{recognizeTaskLeft(self)}")
        if back_to_main:
            self.clicker.mouse_click(91, 39, t=0.7)

    def _filter_item_planner_unreachable(self, stack: ItemStack):
        for k, v in list(stack.items()):
            if self.material_planner.item_name_to_id.get('zh').get(
                    k, None) is None or "芯片" in k:
                logger.warning(
                    f"filter {k} in ItemStack:Item not exist in planner.")
                stack.pop(k)
        logger.debug("after filter:" +
                     stack.formatItems('%item%(%quantity%) '))
        return stack

    def planMaterial(self, required_dct, owned_dct=None):
        owned_dct = owned_dct or {}
        ret = self.material_planner.get_plan(
            self._filter_item_planner_unreachable(required_dct),
            owned_dct,
            print_output=False,
            outcome=True,
            gold_demand=True,
            exp_demand=True,
            store=True,
            server='CN')
        return ret

    def updatePlannerData(self):
        try:
            self.material_planner.update(force=True)
            logger.system("已完成Planner的数据更新!")
        except Exception as e:
            logger.error("Planner数据更新出现错误:%s" % e)

    def clear_mail_item(self):
        if not self.getLocation(ranges='main'):
            logger.error("请切换到主界面(main)运行邮件收取!")
            return False
        if (self.getScreenShot()[25][217] == [1, 104, 255]).all():
            self.clicker.mouse_click(194, 43, t=1.5)
            waitCustomImageDetect(self,
                                  "main\\receive_all_mail.png",
                                  [1056, 644, 171, 44],
                                  delay=8)
            time_sleep(1.4)

            logger.notice(
                f"收取邮件[当前邮件"
                f"{self.reconizerN.recognize(imgops.clear_background(getscreenshot(98, 641, 101, 28), 100))}]"
                f"[{self.reconizerN.recognize(imgops.clear_background(getscreenshot(307, 638, 35, 32), 100))}"
                f"封未读邮件]")
            # reconizerN.recognize(imgops.clear_background(getscreenshot(98, 641, 101, 28), 100)) -> 邮件统计
            # reconizerN.recognize(imgops.clear_background(getscreenshot(307,638,35,32),100)) ->未读邮件
            self.clicker.mouse_click(1143, 664, t=2)
            waitCustomImageDetect(self,
                                  "main\\recv_mail_g.png", [586, 138, 113, 39],
                                  threshold=0.93)
            self.clicker.mouse_click(1143, 664, t=2)
        return True

    def clear_credit_item(self, use_thread=True):
        if use_thread:
            return self.run_withThr(lambda: self.clear_credit_item(False))
        # TODO:改用wait->image方式提升效率
        # assert self.mapper.locateImage(getscreenshot(), 'main', method=3), "请在主界面(main)运行本指令!"
        self.mapData.toMain()
        getMapCost()
        self.clicker.mouse_click(365, 575, t=1.5)
        self.clicker.mouse_click(125, 228, t=1)
        waitCustomImageDetect(self,
                              "friends\\visit.png", (946, 168, 106, 40),
                              delay=10)
        self.clicker.mouse_click(1000, 166, t=1.5)
        for _ in range(11):
            waitVisitNextFriend(
                self, lambda: self.clicker.mouse_click(1200, 634, at=1, t=3))
        self.clicker.mouse_click(271, 36, t=1.1)
        self.clicker.mouse_click(1200, 172, t=1.4)
        self.clicker.mouse_click(1205, 104, t=2)
        self.clicker.mouse_click(1021, 41, t=2)
        self.clicker.mouse_click(1021, 41, t=1.2)
        if self.gameTemp.dingwei("shop\\credit_use.png",
                                 self.getScreenShot(581, 151, 115, 29)):
            self.clicker.mouse_click(1205, 104, t=0.5)
        for _ in range(10):
            x = 132 + (253 * (_ % 5))
            y = 274 + (254 * (_ // 5))
            # check out of stack
            if self.gameTemp.dingwei('shop\\credit_oos.png',
                                     getscreenshot(x - 104, y + 10, 34, 24)):
                logger.error(f"第{_ + 1}件信用商品已售罄!")
                continue
            self.clicker.mouse_click(x, y, rx=(-30, 30), ry=(-30, 30), t=0.3)
            logger.common(f"购买第{_ + 1}件信用商品")
            waitCustomImageDetect(
                self, 'shop\\credit_buy.png', [838, 565, 44, 35],
                lambda: self.clicker.mouse_click(935, 579, at=0.3, t=1.3), 1.2)
            if waitGetCreditItemOrNot(self) is False:
                logger.common("信用点不足,无法购买,退出")
                self.clicker.mouse_click(1205, 104, t=0.7)
                break
            self.clicker.mouse_click(908, 39, t=1.3)
        logger.notice("已完成信用商店购买,返回主页")
        self.clicker.mouse_click(271, 38, t=1)
        self.clicker.mouse_click(93, 173, t=3)

    def _exit(self):
        logger.system("ArkAutotools Shutting down...")
        if self.playerInfo is not None:
            self.playerInfo.stop()
        self.mapper.writeMapping()
        # self.mapData._writeSetting()
        logger.log2file("./logs/" + getLogFileName())
        try:
            del self.adb
        except (NameError, AttributeError):
            pass
        logger.system("Bye~")
        sys.exit(0)
        # self.commander.StopTrack()

    def exit(self):
        self._exit()

    def quit(self):
        self._exit()

    @RootData.cache("ArkFont-NSH_Demi")
    def _get_font_NSHDemi(self, size=14):
        from PIL import ImageFont
        return ImageFont.truetype("./font/" + 'NotoSansHans-DemiLight.otf',
                                  size,
                                  encoding='utf-8')

    def _putText(self, image, x, y, strs, fontsize=20, font=None, color=None):
        from PIL import ImageDraw
        cv2img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        pilimg = Image.fromarray(cv2img)
        draw = ImageDraw.Draw(pilimg)  # 图片上打印
        font = font or self._get_font_NSHDemi(fontsize)
        color = color or (255, 255, 255)
        draw.text((x, y), strs, color, font=font)
        # 参数1:打印坐标,参数2:文本,参数3:字体颜色,参数4:字体
        return cv2.cvtColor(np.array(pilimg), cv2.COLOR_RGB2BGR)

    def _log_drop_item(self, drop_list, savepath):

        image = getscreenshot(0, 441, 1280, 720 - 441).astype(np.uint8)
        for _ in drop_list:
            if not _["have"]:
                continue
            x, y = _["_pos"]
            image = self._putText(image, x + 427, y + 117,
                                  f'{_["name"]}:{str(_["quantity"])}')
        cv2.imwrite(savepath, image, [int(cv2.IMWRITE_JPEG_QUALITY), 80])

    def commandRegister(self, cmder):
        cmder.createExitCommand("exit",
                                "quit",
                                function=self._exit,
                                help="退出程序")
        cmder.createCommand('ark_main',
                            function=self.commandlistener,
                            help="Ark指令")

    def _show_commander_help(self):
        logger.help(
            f"ArkAutoRunner Version{VERSION} Running on {sys.platform}")
        logger.help("")
        logger.help(
            "ark_main [-h/help]                             -获取ArkAutoRunner的帮助"
        )
        # logger.help("ark_main [-a/attack] [s/l]                     -以单关攻击(s)/列表攻击(l)开始关卡")
        # logger.help("ark_main [-l/list] <ListofStage:(str:int)>     -在列表攻击模式下选择攻击的关卡")
        # logger.help("   e.g. ark_main -a l -l 1-7:5 CE-5:4")
        # logger.help("ark_main [-t/times] <MaxAttackTimes:int>       -在单关攻击模式下最大进攻次数")
        # logger.help("ark_main [-r/require] <Require>                -附加需求的掉落物品(会受到次数限制)")
        # logger.help("   e.g. ark_main -a s -r 聚酸酯组:1 扭转醇:1")
        # logger.help("ark_main [-p/plan] -r <Require> -o <Owned>     -规划")
        # logger.help("ark_main                          -                                   ")
        logger.help(
            "=*=*=*=*=*=*=*=*=*=*=*=ArkAutoRunner Help=*=*=*=*=*=*=*=*=*=*=*=")
        logger.error("正在施工中...")

    def autoUpdate(self, use_thread=True):
        if use_thread:
            return self.run_withThr(lambda: self.autoUpdate(False))
        from ArkTagData.GitUpdater import updateGameData
        # Update GameData
        updateGameData()
        # reset all cache
        RootData.clearCache()
        # catch image from prts.wiki etc
        from ArkTagData.DataUpdater import updateItemData, updateRecruitTag
        updateRecruitTag()
        updateItemData()
        return True

    def commandlistener(self, *cmd):
        # print(cmd)
        if not cmd:
            self._show_commander_help()
            return 0
        commands = cmd[0]

    def getLocation(self,
                    image=None,
                    ranges=None,
                    group=None,
                    method=3,
                    sub_area=None,
                    addon=False,
                    showdebug=False):
        image = image if image is not None else self.getScreenShot()
        return self.mapper.locateImage(image,
                                       ranges=ranges,
                                       group=group,
                                       method=method,
                                       sub_area=sub_area,
                                       addon=addon,
                                       show_debug=showdebug)

    def sleep_computer_after_current_atk(self):
        self.run_after_current(lambda: os.system("shutdown -h"))
        return True

    def getDropInfoHistory(self,
                           stage: str,
                           timerange: list = None,
                           filter: list = None):
        class mainDropInfo:
            def __init__(self):
                self.param = []
                self.value = []
                self.num = 0

            def add(self, stack):
                self.num += 1
                if stack in self.param:
                    self.value[self.param.index(stack)] += 1
                    return True
                self.param.append(stack)
                self.value.append(1)

            def __bool__(self):
                return bool(self.param)

        ret = self.dropLinker.getStageDrop(stage)
        itemStack = ItemStack()
        mainDropInfo = mainDropInfo()
        for info in ret:
            itemStack = itemStack + info.dropList
            items = ItemStack().addItemFromList_NameQuantity(info.getByType(2))
            mainDropInfo.add(items)
        ttl = len(ret)
        extraInfo = {}
        for type_, id_ in getStageTag().getStage(
                stage).getDropInfo().getDropList():
            name = getItemTag()['items'].get(id_)['name']
            if 2 <= type_ <= 3:
                extraInfo.setdefault(name, ' 主要掉落' if type_ == 2 else ' 特殊掉落')

        def proc_percent(val):
            if val >= 5.0 or val <= 0:
                if int(val) == val:
                    return str(int(val))
                return str(val)
            return f"{round(val * 100, 2)}%"

        def log(s):
            logger.notice(f"[DropInfoHistory] {s}")

        def replacer(mapcost: str):
            try:
                return mapcost.replace('_',
                                       ':').replace('ap',
                                                    '理智').replace('et', '门票')
            except:
                return None

        def fill_chi_char(str_, len_=30):
            chr_len = 0
            for char_ in str_:
                if ord(char_) < 128:
                    chr_len += 1
                else:
                    chr_len += 2
            need = len_ - chr_len
            return str_ + ' ' * (need if need >= 0 else 0)

        sspend = replacer(getMapCost().where(stage))
        sspend_value = int(sspend.split(':')[1])
        log(f"关卡{stage}物品掉落信息 总{ttl}次攻击被统计 单次攻击消耗{sspend}")
        log(f"物品名称        | 数量   | 平均概率  | 期望理智 | 附加信息")
        for item, value in itemStack.items():
            expect = proc_percent(round(value / ttl, 2))
            log(f"{item.ljust(9, chr(12288))}| {fill_chi_char(str(value), 6)}"
                f"| {expect:8}"
                f"| {round(sspend_value / (value / ttl), 2):7} "
                f"|{extraInfo.get(item, '')}")
        log('-' * 51)
        if mainDropInfo:
            log(f"[MainDrop][I] {fill_chi_char('主要掉落物内容', 20)} |  次数  |  比例")
            for ind, stack in enumerate(mainDropInfo.param):
                cur_times = mainDropInfo.value[ind]
                log(f"[MainDrop][{ind:1}] {fill_chi_char(str(stack), 18)} | {cur_times:6d} | "
                    f"{proc_percent(cur_times / mainDropInfo.num)}")

    def _check_inte_using(self, now, cost, max_atk, atk_num, use_ap_supply,
                          use_diamond):
        if int(now) < cost:
            self.time_sleep(0.5)
            if self.gameTemp.dingwei("on_atk_beg\\use_ap_recov.png",
                                     self.getScreenShot(691, 80, 73,
                                                        46)) and use_ap_supply:
                logger.common("使用理智药剂恢复理智")
                self.clicker.mouse_click(1090, 579)
                self.time_sleep(0.5)
                if waitApRecovery(self):
                    now = recognizeBeforeOperationInte(self)[0]
                    logger.common("理智已成功恢复至%s" % now)
                    self.playerInfo.update(now)
                    max_atk += 1
                    atk_num -= 1
                return max_atk, atk_num, True
            elif self.gameTemp.dingwei("on_atk_beg\\use_diam_recov.png",
                                       self.getScreenShot(1005, 84, 67,
                                                          41)) and use_diamond:
                logger.common("使用源石恢复理智")
                self.clicker.mouse_click(1090, 579)
                if waitApRecovery(self):
                    now = recognizeBeforeOperationInte(self)[0]
                    logger.common("理智已成功恢复至%s" % now)
                    self.playerInfo.update(now)
                    max_atk += 1
                    atk_num -= 1
                return max_atk, atk_num, True  # TODO:做一下碎石恢复
            else:
                max_atk += 1
                atk_num -= 1
                RETURN_VALUE = False
                logger.warning("理智不足且无法自动回复。")
                return max_atk, atk_num, False
        return max_atk, atk_num, None
Пример #7
0
    def attack_simple(self,
                      stage="AUTO",
                      max_atk=-1,
                      require=None,
                      use_ap_supply=True,
                      use_diamond=False,
                      use_thread=True,
                      planned_stage=None,
                      log_drop=True):
        if use_thread:
            return self.run_withThr(lambda: self.attack_simple(
                stage, max_atk, require, use_ap_supply, use_diamond, False))
        # 清空遗留的强制停止信息
        # self._force_stop = False
        # 对已指定的关卡进行自动攻击至无体力
        map_cost = getMapCost()
        atk_stage = stage
        RETURN_VALUE = True
        # _LOC = self.getLocation(ranges=ArkMap.RECO)
        # if 'map_atk' not in _LOC:
        if not self.gameTemp.dingwei("on_atk_beg\\start.png",
                                     self.getScreenShot(1102, 624, 150, 66)):
            logger.error("当前未在关卡选择内,无法使用简单模式进行挂机!")
            logger.error("请切换到关卡内(能够直接点击开始行动)并重启程序")
            return False

        if stage == "AUTO":
            # 使用tesseract检测关卡最优
            rec = recognizeOperation(
                Image.fromarray(self.getScreenShot(881, 81, 86, 34)), map_cost)
            # rec = self._check_ocr_until_success(881, 81, 86, 35, map_cost)
            if rec[0]:
                logger.common("检测到当前关卡:%s 消耗%s" % (rec[0], rec[1].replace(
                    "ap_", "理智:").replace("et_", "门票:")))
                atk_stage = rec[0]
            else:
                logger.error("请使用stage='STAGE_CODE'进行手动设置!")
                return False
        else:
            rec = [stage, map_cost.where(stage)]
            if rec[1] is None:
                logger.error("未找到当前关卡%s所对应的体力消耗,程序版本是否过时?" % stage)
                return False
            logger.notice(
                "设定当前关卡:%s 消耗%s" %
                (stage, rec[1].replace("ap_", "理智:").replace("et_", "门票:")))
        ap_text = rec[1].split("_")[0].replace("ap", "理智").replace("et", "门票")
        if ap_text == "门票":
            use_diamond, use_ap_supply = False, False
        cost = int(rec[1].split("_")[1])
        # 检测理智用reconizer
        request_require = False
        if require is not None:
            request_require = True
            if not _check_request_with_stage(atk_stage, require):
                logger.error('检测掉落物与需求不匹配!请检查')
                return False

        now, max_ = self.reconizerC.recognize2(
            Image.fromarray(self.getScreenShot(1120, 20, 130, 40)),
            subset="1234567890/")[0].split("/")
        if int(now) < cost and (not use_ap_supply and not use_diamond):
            logger.error("当前%s(%s)小于关卡消耗%s,已停止" % (ap_text, ap_text, now))
            return False
        self.playerInfo = PlayerConfig(
            int(now),
            int(max_)) if self.playerInfo is None else self.playerInfo.update(
                now, max_)
        delay_time = 25
        decide_atk = max_atk if max_atk != -1 else "AUTO"
        atk_num = 0
        # 检查代理指挥
        if not self._check_prts_type():
            return False
        self.current_itemStack = ItemStack().registItem(
            getDropList(atk_stage), 0)
        while self.playerInfo.startOperation(
                cost, use_diamond
                or use_ap_supply) and max_atk != 0 and not self._force_stop:
            max_atk -= 1
            atk_num += 1
            now, max_ = recognizeBeforeOperationInte(self)
            if request_require:
                logger.debug("Require List:" +
                             require.formatItems("%item%(%quantity%) "))
                logger.debug(
                    "Get:" +
                    self.current_itemStack.formatItems("%item%(%quantity%) "))
                if require <= self.current_itemStack:
                    max_atk = 0
                    break
            logger.common("当前实际%s(%s/%s),预测%s(%s),开始行动(第%s次/共%s次)。" %
                          (ap_text, now, max_, ap_text,
                           self.playerInfo.intellect + 1, atk_num, decide_atk))
            self.clicker.mouse_click(1148, 658)
            max_atk, atk_num, uType = self._check_inte_using(
                now, cost, max_atk, atk_num, use_ap_supply, use_diamond)
            if uType is True:
                continue
            elif uType is False:
                break
            waitStartOperationAttack(
                self, lambda: self.clicker.mouse_click(1105, 521, at=0.3))
            start_time = time.time()
            # 检查结束
            time.sleep(delay_time)
            # TODO:检查结束的时候还应该检查是否失败
            logger.debug("等待结束,开始检测通关信息。")
            while not self.getLocation(getscreenshot(99, 577, 427, 116),
                                       "atk_end",
                                       sub_area=[99, 577, 427, 116]):
                time.sleep(1)
                if self.gameTemp.dingwei("on_atk_end\\lvl_up.png",
                                         self.getScreenShot(657, 328, 50, 50)):
                    time.sleep(4)
                    logger.notice("您已升级!")
                    self.playerInfo.restore()
                    self.adb.touch_tap((931, 278), (15, 15))
            cost_time = time.time() - start_time
            logger.common("行动结束,本次花费%.2f秒" % cost_time)
            delay_time = cost_time - 9
            time.sleep(7)
            self.logStageDrop(atk_stage, atk_stage, atk_num, cost_time,
                              log_drop)
            self.adb.touch_tap((796, 466), (15, 15))
            time.sleep(1)
            self.adb.touch_tap((796, 466), (15, 15))
            time.sleep(3)
            self.playerInfo.endOperation()
        if max_atk != 0:
            logger.warning(
                f"已完成{atk_num}次,剩余{max_atk if max_atk > 0 else 'INFINITE'}次未攻击"
            )
        if planned_stage is not None:
            planned_stage.delStage(atk_stage, atk_num)
        self.playerInfo.stopOperation(-1)
        if self._force_stop:
            logger.warning("已强制停止!")
            self._force_stop = False
        logger.notice(
            f"[关卡数据统计]攻击{stage if stage != 'AUTO' else rec[0] + '(自动识别)'} {atk_num}次,消耗"
            f"{cost * atk_num}{ap_text},获取掉落物:{self.current_itemStack.formatItems('%item%(%quantity%) ')}"
        )
        return RETURN_VALUE
Пример #8
0
    def attack_extermination(self,
                             stage_set=None,
                             max_atk=1,
                             use_ap_supply=False,
                             use_diamond=False,
                             use_thread=True,
                             log_drop=True):
        if use_thread:
            return self.run_withThr(lambda: self.attack_extermination(
                stage_set, max_atk, use_ap_supply, use_diamond, False, log_drop
            ))

        def error(msg, err):
            logger.error(f"进攻剿灭作战'{stage_set}'失败[{msg}]:{err}")
            return False

        # 提示信息
        logger.warning('请保证在本剿灭作战中能够稳定400杀通关,否则在结束阶段将可能无法识别,需要人工手动介入!')
        # getStageSet
        if stage_set is None:
            stage_set = getStageTag().getStage('乌萨斯', '废弃矿区')  # TODO:每次轮换更新
        elif isinstance(stage_set, str):
            try:
                stage_set = getStageTag().getStage(None, stage_set)
            except ValueError as e:
                return error('无法获取关卡数据', e)
        if not isinstance(stage_set, StageInfo):
            return error(
                '无法解析关卡数据',
                f'StageSet Typeof {type(stage_set)} cannot be parsed to StageInfo'
            )
        # detect map
        if not self.mapData.fromMaptoMapMain():
            return error('无法跳转到地图界面', '当前界面无对应跳转方式')
        # check
        if not self.getLocation(self.getScreenShot(), ['map_main']):
            return error('跳转到地图界面失败', '可能存在通知等影响跳转。请检查!')
        # click into extre
        self.clicker.mouse_click(537, 662, t=2)
        # select position
        click_data = EXTER_CLICK_DATA.get(stage_set.getName())
        if click_data is None:
            return error('无效的地图名称', f"无效的地图名称:'{stage_set}'")
        for x, y in click_data:
            self.clicker.mouse_click(x, y, t=1.3)
        # start attack
        map_cost = stage_set.getCost()[0]  # ap_cost
        decide_atk = max_atk
        atk_stage = stage_set.getName()
        atk_num = 0
        self.current_itemStack = ItemStack()
        if not self._check_prts_type():
            return False
        inte_now, inte_max = recognizeBeforeOperationInte(self)
        self.playerInfo = PlayerConfig(int(inte_now), int(inte_max)) if self.playerInfo is None \
            else self.playerInfo.update(inte_now, inte_max)
        while self.playerInfo.startOperation(map_cost, use_diamond or use_ap_supply) \
                and max_atk != 0 and not self._force_stop:
            # 检测当前合成玉数量
            camp_num = recognizeCampaignNum(self)
            if camp_num['success']:
                camp_cur = camp_num['now']
                camp_lim = camp_num['max']
                logger.notice(
                    f"当前合成玉数量为{camp_cur},最大合成玉获取数量为{camp_lim},还可获取{camp_lim - camp_cur}合成玉"
                )
                if camp_lim - camp_cur < MIN_ENABLE_EXTER_ATK_CAMP_NUM:
                    logger.error(
                        f"当前合成玉与最大合成玉获取上限小于最小可进攻设定值'{MIN_ENABLE_EXTER_ATK_CAMP_NUM}',放弃攻击"
                    )
                    break
            else:
                return error('无效合成玉信息', '无法在当前页面获取合成玉信息,请检查当前位置!')
            max_atk -= 1
            atk_num += 1
            logger.common("当前实际理智(%s/%s),开始行动(第%s次/共%s次)。" %
                          (inte_now, inte_max, atk_num, decide_atk))
            self.clicker.mouse_click(1148, 658)
            # if self.playerInfo.intellect + 1 < cost:
            max_atk, atk_num, uType = self._check_inte_using(
                inte_now, map_cost, max_atk, atk_num, use_ap_supply,
                use_diamond)
            if uType is True:
                continue
            elif uType is False:
                break
            else:
                pass
            waitStartOperationAttack(
                self, lambda: self.clicker.mouse_click(1105, 521, at=0.3))
            start_time = time.time()
            time.sleep(12)
            # 循环检测当前剿灭作战信息
            while True:
                try:
                    kill_count = self.reconizerE.recognize2(
                        imgops.crop_blackedge2(
                            Image.fromarray(self.getScreenShot(
                                450, 20, 60, 21))),
                        subset='0123456789/')[0]
                except:
                    kill_count = '-'
                reco_enermy = '-'
                try:
                    reco_enermy = self.reconizerE.recognize2(
                        imgops.crop_blackedge2(
                            imgops.clear_background(
                                Image.fromarray(
                                    cv2.split(
                                        self.getScreenShot(618, 20, 102,
                                                           21))[0]), 40)),
                        subset='0123456789/')[0]
                    # enermy_num, total_enermy_num = reco_enermy.split('/', 1)
                except:
                    logger.warning(f"识别敌人数量出现错误(结果为{reco_enermy})")
                    # enermy_num, total_enermy_num = -1, -1
                try:
                    lo_count = self.reconizerE.recognize2(
                        imgops.crop_blackedge2(
                            Image.fromarray(
                                cv2.split(self.getScreenShot(837, 20, 26,
                                                             20))[2]), 120),
                        subset='0123456789/')[0]
                except:
                    lo_count = '-'
                logger.notice(
                    f"[ExtAssist]当前击杀数量:{kill_count:>3}|敌人数量:{reco_enermy:>7}|剩余防御点数:{lo_count:>2}"
                )
                if waitExterminationEnd(self):
                    break
            cost_time = time.time() - start_time
            logger.notice(f"剿灭攻击结束!本次花费{cost_time:2f}秒")
            time.sleep(4)
            self.clicker.mouse_click(1209, 311, t=7)
            drop_list = {('AP_GAMEPLAY', '理智'), ('DIAMOND_SHD', '合成玉'),
                         ('sprite_exp_card_t2', '初级作战记录'),
                         ("EXP_PLAYER", "声望"), ("GOLD", "龙门币")}
            self.logStageDrop(drop_list, atk_stage, atk_num, cost_time,
                              log_drop)
            self.clicker.mouse_click(112, 150, t=5)
            self.playerInfo.endOperation()
            time.sleep(5)
        if max_atk != 0:
            logger.warning(
                f"已完成{atk_num}次,剩余{max_atk if max_atk > 0 else 'INFINITE'}次未攻击"
            )
        self.playerInfo.stopOperation(-1)
        if self._force_stop:
            logger.warning('已强制停止!')
            self._force_stop = False
        logger.notice(
            f"[关卡数据统计]攻击{atk_stage} {atk_num}次,消耗"
            f"{map_cost * atk_num}理智,获取掉落物:{self.current_itemStack.formatItems('%item%(%quantity%) ')}"
        )
        return True
Пример #9
0
    def getDropInfoHistory(self,
                           stage: str,
                           timerange: list = None,
                           filter: list = None):
        class mainDropInfo:
            def __init__(self):
                self.param = []
                self.value = []
                self.num = 0

            def add(self, stack):
                self.num += 1
                if stack in self.param:
                    self.value[self.param.index(stack)] += 1
                    return True
                self.param.append(stack)
                self.value.append(1)

            def __bool__(self):
                return bool(self.param)

        ret = self.dropLinker.getStageDrop(stage)
        itemStack = ItemStack()
        mainDropInfo = mainDropInfo()
        for info in ret:
            itemStack = itemStack + info.dropList
            items = ItemStack().addItemFromList_NameQuantity(info.getByType(2))
            mainDropInfo.add(items)
        ttl = len(ret)
        extraInfo = {}
        for type_, id_ in getStageTag().getStage(
                stage).getDropInfo().getDropList():
            name = getItemTag()['items'].get(id_)['name']
            if 2 <= type_ <= 3:
                extraInfo.setdefault(name, ' 主要掉落' if type_ == 2 else ' 特殊掉落')

        def proc_percent(val):
            if val >= 5.0 or val <= 0:
                if int(val) == val:
                    return str(int(val))
                return str(val)
            return f"{round(val * 100, 2)}%"

        def log(s):
            logger.notice(f"[DropInfoHistory] {s}")

        def replacer(mapcost: str):
            try:
                return mapcost.replace('_',
                                       ':').replace('ap',
                                                    '理智').replace('et', '门票')
            except:
                return None

        def fill_chi_char(str_, len_=30):
            chr_len = 0
            for char_ in str_:
                if ord(char_) < 128:
                    chr_len += 1
                else:
                    chr_len += 2
            need = len_ - chr_len
            return str_ + ' ' * (need if need >= 0 else 0)

        sspend = replacer(getMapCost().where(stage))
        sspend_value = int(sspend.split(':')[1])
        log(f"关卡{stage}物品掉落信息 总{ttl}次攻击被统计 单次攻击消耗{sspend}")
        log(f"物品名称        | 数量   | 平均概率  | 期望理智 | 附加信息")
        for item, value in itemStack.items():
            expect = proc_percent(round(value / ttl, 2))
            log(f"{item.ljust(9, chr(12288))}| {fill_chi_char(str(value), 6)}"
                f"| {expect:8}"
                f"| {round(sspend_value / (value / ttl), 2):7} "
                f"|{extraInfo.get(item, '')}")
        log('-' * 51)
        if mainDropInfo:
            log(f"[MainDrop][I] {fill_chi_char('主要掉落物内容', 20)} |  次数  |  比例")
            for ind, stack in enumerate(mainDropInfo.param):
                cur_times = mainDropInfo.value[ind]
                log(f"[MainDrop][{ind:1}] {fill_chi_char(str(stack), 18)} | {cur_times:6d} | "
                    f"{proc_percent(cur_times / mainDropInfo.num)}")