def __init__( self, current_strength=None, # 当前理智 adb_host=None, # 当前绑定到的设备 out_put=True, # 是否有命令行输出 call_by_gui=False): # 是否为从 GUI 程序调用 ensure_adb_alive() self.adb = ADBConnector(adb_serial=adb_host) self.__is_game_active = False self.__call_by_gui = call_by_gui self.is_called_by_gui = call_by_gui self.viewport = self.adb.screenshot().size self.operation_time = [] self.delay_impl = sleep if DEBUG_LEVEL >= 1: self.__print_info() self.refill_with_item = config.get('behavior/refill_ap_with_item', False) self.refill_with_originium = config.get( 'behavior/refill_ap_with_originium', False) self.use_refill = self.refill_with_item or self.refill_with_originium self.loots = {} self.use_penguin_report = config.get('reporting/enabled', False) if self.use_penguin_report: self.penguin_reporter = penguin_stats.reporter.PenguinStatsReporter( ) self.refill_count = 0 self.max_refill_count = None logger.debug("成功初始化模块")
def connect_device(self, connector=None, *, adb_serial=None): if connector is not None: self.adb = connector elif adb_serial is not None: self.adb = ADBConnector(adb_serial) else: self.adb = None return self.viewport = self.adb.screen_size if self.adb.screenshot_rotate %180: self.viewport = (self.viewport[1], self.viewport[0]) if self.viewport[1] < 720 or Fraction(self.viewport[0], self.viewport[1]) < Fraction(16, 9): title = '设备当前分辨率(%dx%d)不符合要求' % (self.viewport[0], self.viewport[1]) body = '需要宽高比等于或大于 16∶9,且渲染高度不小于 720。' details = None if Fraction(self.viewport[1], self.viewport[0]) >= Fraction(16, 9): body = '屏幕截图可能需要旋转,请尝试在 device-config 中指定旋转角度。' img = self.adb.screenshot() imgfile = os.path.join(config.SCREEN_SHOOT_SAVE_PATH, 'orientation-diagnose-%s.png' % time.strftime("%Y%m%d-%H%M%S")) img.save(imgfile) import json details = '参考 %s 以更正 device-config.json[%s]["screenshot_rotate"]' % (imgfile, json.dumps(self.adb.config_key)) for msg in [title, body, details]: if msg is not None: logger.warn(msg) frontend.alert(title, body, 'warn', details)
def _connect_adb(args): from connector.ADBConnector import ADBConnector, ensure_adb_alive ensure_adb_alive() global device if len(args) == 0: try: device = ADBConnector.auto_connect() except IndexError: print("检测到多台设备") devices = ADBConnector.available_devices() for i, (serial, status) in enumerate(devices): print("%2d. %s\t[%s]" % (i, serial, status)) num = 0 while True: try: num = int(input("请输入序号选择设备: ")) if not 0 <= num < len(devices): raise ValueError() break except ValueError: print("输入不合法,请重新输入") device_name = devices[num][0] device = ADBConnector(device_name) else: serial = args[0] device = ADBConnector(serial)
def connect_device(self): # # select from multiple devices # ensure_adb_alive() # try: # self.adb = ADBConnector.auto_connect() # except IndexError: # print("detected multiple devices") # devices = ADBConnector.available_devices() # for i, (serial, status) in enumerate(devices): # print("%2d. %s\t[%s]" % (i, serial, status)) # while True: # try: # num = int(input("please input number of the device you select: ")) # if not 0 <= num < len(devices): # raise ValueError() # break # except ValueError: # print("invalid input, please try again") # device_name = devices[num][0] # self.adb = ADBConnector(device_name) ensure_adb_alive() try: self.adb = ADBConnector.auto_connect() except (IndexError, RuntimeError, EOFError): return False self.adb.ensure_alive() self.viewport = self.adb.screen_size self._print_info() return True
def __init__( self, current_strength=None, # 当前理智 adb_host=None, # 当前绑定到的设备 out_put=True, # 是否有命令行输出 call_by_gui=False): # 是否为从 GUI 程序调用 ensure_adb_alive() self.adb = ADBConnector(adb_serial=adb_host) self.__is_game_active = False self.__call_by_gui = call_by_gui self.is_called_by_gui = call_by_gui self.viewport = self.adb.screen_size self.operation_time = [] self.delay_impl = sleep if DEBUG_LEVEL >= 1: self.__print_info() self.refill_with_item = config.get('behavior/refill_ap_with_item', False) self.refill_with_originium = config.get( 'behavior/refill_ap_with_originium', False) self.use_refill = self.refill_with_item or self.refill_with_originium self.loots = {} self.use_penguin_report = config.get('reporting/enabled', False) if self.use_penguin_report: self.penguin_reporter = penguin_stats.reporter.PenguinStatsReporter( ) self.refill_count = 0 self.max_refill_count = None if Fraction(self.viewport[0], self.viewport[1]) < Fraction(16, 9): logger.warn('当前分辨率(%dx%d)不符合要求', self.viewport[0], self.viewport[1]) if Fraction(self.viewport[1], self.viewport[0]) >= Fraction(16, 9): logger.info('屏幕截图可能需要旋转,请尝试在 device-config 中指定旋转角度') img = self.adb.screenshot() imgfile = os.path.join( config.SCREEN_SHOOT_SAVE_PATH, 'orientation-diagnose-%s.png' % time.strftime("%Y%m%d-%H%M%S")) img.save(imgfile) import json logger.info( '参考 %s 以更正 device-config.json[%s]["screenshot_rotate"]', imgfile, json.dumps(self.adb.config_key)) logger.debug("成功初始化模块")
def web_connect(self, dev:str): print(dev.split(':', 1)) connector_type, cookie = dev.split(':', 1) if connector_type != 'adb': raise KeyError("unknown connector type " + connector_type) new_connector = ADBConnector(cookie) connector_str = str(new_connector) self.helper.connect_device(new_connector) self.notify("web:current-device", connector_str)
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 __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模式以开启快速图像采集!")
def run(self): print("starting worker thread") loghandler = WebHandler(self.output) loghandler.setLevel(logging.INFO) logging.root.addHandler(loghandler) version = config.version if config.get_instance_id() != 0: version += f" (instance {config.get_instance_id()})" self.notify("web:version", version) ensure_adb_alive() devices = ADBConnector.available_devices() devices = ["adb:" + x[0] for x in devices] self.notify("web:availiable-devices", devices) self.helper = Arknights.helper.ArknightsHelper(frontend=self) while True: self.notify("worker:idle") command: dict = self.input.get(block=True) if command.get('type', None) == "call": self.interrupt_event.clear() self.notify('worker:busy') tag = command.get('tag', None) action = command.get('action', None) return_value = None exc = None try: func = self.allowed_calls[action] args = command.get('args', []) return_value = func(*args) except: exc = sys.exc_info() if exc is None: result = dict(type='call-result', status='resolved', tag=tag, return_value=return_value) else: result = dict(type='call-result', status='exception', tag=tag, exception=format_exception(*exc)) if tag is not None: self.output.put_nowait(result)
def _connect_adb(args): from connector.ADBConnector import ADBConnector, ensure_adb_alive ensure_adb_alive() global device if len(args) == 0: try: device = ADBConnector.auto_connect() except IndexError: print("检测到多台设备") devices = ADBConnector.available_devices() for i, (serial, status) in enumerate(devices): print("%2d. %s\t[%s]" % (i, serial, status)) num = 0 while True: try: num = int(input("请输入序号选择设备: ")) if not 0 <= num < len(devices): raise ValueError() break except ValueError: print("输入不合法,请重新输入") device_name = devices[num][0] device = ADBConnector(device_name) else: serial = args[0] try: device = ADBConnector(serial) except RuntimeError as e: if e.args and isinstance(e.args[0], bytes) and b'not found' in e.args[0]: if ':' in serial and serial.split(':')[-1].isdigit(): print('adb connect', serial) ADBConnector.paranoid_connect(serial) device = ADBConnector(serial) return raise
class ArknightsHelper(object): def __init__( self, current_strength=None, # 当前理智 adb_host=None, # 当前绑定到的设备 out_put=True, # 是否有命令行输出 call_by_gui=False): # 是否为从 GUI 程序调用 ensure_adb_alive() self.adb = ADBConnector(adb_serial=adb_host) self.__is_game_active = False self.__call_by_gui = call_by_gui self.is_called_by_gui = call_by_gui self.viewport = self.adb.screenshot().size self.operation_time = [] self.delay_impl = sleep if DEBUG_LEVEL >= 1: self.__print_info() self.refill_with_item = config.get('behavior/refill_ap_with_item', False) self.refill_with_originium = config.get( 'behavior/refill_ap_with_originium', False) self.use_refill = self.refill_with_item or self.refill_with_originium self.loots = {} self.use_penguin_report = config.get('reporting/enabled', False) if self.use_penguin_report: self.penguin_reporter = penguin_stats.reporter.PenguinStatsReporter( ) self.refill_count = 0 self.max_refill_count = None logger.debug("成功初始化模块") def __print_info(self): logger.info('当前系统信息:') logger.info('ADB 服务器:\t%s:%d', *config.ADB_SERVER) logger.info('分辨率:\t%dx%d', *self.viewport) # logger.info('OCR 引擎:\t%s', ocr.engine.info) logger.info('截图路径:\t%s', config.SCREEN_SHOOT_SAVE_PATH) if config.enable_baidu_api: logger.info( '%s', """百度API配置信息: APP_ID\t{app_id} API_KEY\t{api_key} SECRET_KEY\t{secret_key} """.format(app_id=config.APP_ID, api_key=config.API_KEY, secret_key=config.SECRET_KEY)) def __del(self): self.adb.run_device_cmd("am force-stop {}".format( config.ArkNights_PACKAGE_NAME)) def destroy(self): self.__del() def check_game_active(self): # 启动游戏 需要手动调用 logger.debug("helper.check_game_active") current = self.adb.run_device_cmd( 'dumpsys window windows | grep mCurrentFocus').decode( errors='ignore') logger.debug("正在尝试启动游戏") logger.debug(current) if config.ArkNights_PACKAGE_NAME in current: self.__is_game_active = True logger.debug("游戏已启动") else: self.adb.run_device_cmd("am start -n {}/{}".format( config.ArkNights_PACKAGE_NAME, config.ArkNights_ACTIVITY_NAME)) logger.debug("成功启动游戏") self.__is_game_active = True def __wait( self, n=10, # 等待时间中值 MANLIKE_FLAG=True): # 是否在此基础上设偏移量 if MANLIKE_FLAG: m = uniform(0, 0.3) n = uniform(n - m * 0.5 * n, n + m * n) self.delay_impl(n) def mouse_click( self, # 点击一个按钮 XY): # 待点击的按钮的左上和右下坐标 assert (self.viewport == (1280, 720)) logger.debug("helper.mouse_click") xx = randint(XY[0][0], XY[1][0]) yy = randint(XY[0][1], XY[1][1]) logger.info("接收到点击坐标并传递xx:{}和yy:{}".format(xx, yy)) self.adb.touch_tap((xx, yy)) self.__wait(TINY_WAIT, MANLIKE_FLAG=True) def tap_rect(self, rc): hwidth = (rc[2] - rc[0]) / 2 hheight = (rc[3] - rc[1]) / 2 midx = rc[0] + hwidth midy = rc[1] + hheight xdiff = max(-1, min(1, gauss(0, 0.2))) ydiff = max(-1, min(1, gauss(0, 0.2))) tapx = int(midx + xdiff * hwidth) tapy = int(midy + ydiff * hheight) self.adb.touch_tap((tapx, tapy)) self.__wait(TINY_WAIT, MANLIKE_FLAG=True) def tap_quadrilateral(self, pts): pts = np.asarray(pts) acdiff = max(0, min(2, gauss(1, 0.2))) bddiff = max(0, min(2, gauss(1, 0.2))) halfac = (pts[2] - pts[0]) / 2 m = pts[0] + halfac * acdiff pt2 = pts[1] if bddiff > 1 else pts[3] halfvec = (pt2 - m) / 2 finalpt = m + halfvec * bddiff self.adb.touch_tap(tuple(int(x) for x in finalpt)) self.__wait(TINY_WAIT, MANLIKE_FLAG=True) def wait_for_still_image(self, threshold=16, crop=None, timeout=60, raise_for_timeout=True): if crop is None: shooter = lambda: self.adb.screenshot() else: shooter = lambda: self.adb.screenshot().crop(crop) screenshot = shooter() t0 = time.monotonic() ts = t0 + timeout n = 0 minerr = 65025 while time.monotonic() < ts: self.__wait(1) screenshot2 = shooter() mse = imgreco.imgops.compare_mse(screenshot, screenshot2) if mse <= threshold: return screenshot2 screenshot = screenshot2 if mse < minerr: minerr = mse n += 1 if n == 9: logger.info("等待画面静止") if raise_for_timeout: raise RuntimeError("%d 秒内画面未静止,最小误差=%d,阈值=%d" % (timeout, minerr, threshold)) return None def module_login(self): logger.debug("helper.module_login") logger.info("发送坐标LOGIN_QUICK_LOGIN: {}".format( CLICK_LOCATION['LOGIN_QUICK_LOGIN'])) self.mouse_click(CLICK_LOCATION['LOGIN_QUICK_LOGIN']) self.__wait(BIG_WAIT) logger.info("发送坐标LOGIN_START_WAKEUP: {}".format( CLICK_LOCATION['LOGIN_START_WAKEUP'])) self.mouse_click(CLICK_LOCATION['LOGIN_START_WAKEUP']) self.__wait(BIG_WAIT) def module_battle_slim( self, c_id=None, # 待战斗的关卡编号 set_count=1000, # 战斗次数 check_ai=True, # 是否检查代理指挥 **kwargs): # 扩展参数: ''' :param sub 是否为子程序 (是否为module_battle所调用) :param auto_close 是否自动关闭, 默认为 false :param self_fix 是否尝试自动修复, 默认为 false :param MAX_TIME 最大检查轮数, 默认在 config 中设置, 每隔一段时间进行一轮检查判断作战是否结束 建议自定义该数值以便在出现一定失误, 超出最大判断次数后有一定的自我修复能力 :return: True 完成指定次数的作战 False 理智不足, 退出作战 ''' logger.debug("helper.module_battle_slim") sub = kwargs["sub"] \ if "sub" in kwargs else False auto_close = kwargs["auto_close"] \ if "auto_close" in kwargs else False if not sub: logger.info("战斗-选择{}...启动".format(c_id)) if set_count == 0: return True self.operation_time = [] count = 0 try: for count in range(set_count): # logger.info("开始第 %d 次战斗", count + 1) self.operation_once_statemachine(c_id, ) logger.info("第 %d 次作战完成", count + 1) if count != set_count - 1: # 2019.10.06 更新逻辑后,提前点击后等待时间包括企鹅物流 if config.reporter: self.__wait(SMALL_WAIT, MANLIKE_FLAG=True) else: self.__wait(BIG_WAIT, MANLIKE_FLAG=True) except StopIteration: logger.error('未能进行第 %d 次作战', count + 1) remain = set_count - count - 1 if remain > 0: logger.error('已忽略余下的 %d 次战斗', remain) if not sub: if auto_close: logger.info("简略模块{}结束,系统准备退出".format(c_id)) self.__wait(120, False) self.__del() else: logger.info("简略模块{}结束".format(c_id)) return True else: logger.info("当前任务{}结束,准备进行下一项任务".format(c_id)) return True def can_perform_refill(self): if not self.use_refill: return False if self.max_refill_count is not None: return self.refill_count < self.max_refill_count else: return True @dataclass class operation_once_state: state: Callable = None stop: bool = False operation_start: float = 0 first_wait: bool = True mistaken_delegation: bool = False prepare_reco: dict = None def operation_once_statemachine(self, c_id): smobj = ArknightsHelper.operation_once_state() def on_prepare(smobj): count_times = 0 while True: screenshot = self.adb.screenshot() recoresult = imgreco.before_operation.recognize(screenshot) if recoresult is not None: logger.debug('当前画面关卡:%s', recoresult['operation']) if c_id is not None: # 如果传入了关卡 ID,检查识别结果 if recoresult['operation'] != c_id: logger.error('不在关卡界面') raise StopIteration() break else: count_times += 1 self.__wait(1, False) if count_times <= 7: logger.warning('不在关卡界面') self.__wait(TINY_WAIT, False) continue else: logger.error('{}次检测后都不再关卡界面,退出进程'.format(count_times)) raise StopIteration() self.CURRENT_STRENGTH = int(recoresult['AP'].split('/')[0]) ap_text = '理智' if recoresult['consume_ap'] else '门票' logger.info('当前%s %d, 关卡消耗 %d', ap_text, self.CURRENT_STRENGTH, recoresult['consume']) if self.CURRENT_STRENGTH < int(recoresult['consume']): logger.error(ap_text + '不足 无法继续') if recoresult['consume_ap'] and self.can_perform_refill(): logger.info('尝试回复理智') self.tap_rect( imgreco.before_operation.get_start_operation_rect( self.viewport)) self.__wait(SMALL_WAIT) screenshot = self.adb.screenshot() refill_type = imgreco.before_operation.check_ap_refill_type( screenshot) confirm_refill = False if refill_type == 'item' and self.refill_with_item: logger.info('使用道具回复理智') confirm_refill = True if refill_type == 'originium' and self.refill_with_originium: logger.info('碎石回复理智') confirm_refill = True # FIXME: 道具回复量不足时也会尝试使用 if confirm_refill: self.tap_rect( imgreco.before_operation. get_ap_refill_confirm_rect(self.viewport)) self.refill_count += 1 self.__wait(MEDIUM_WAIT) return # to on_prepare state logger.error('未能回复理智') self.tap_rect( imgreco.before_operation.get_ap_refill_cancel_rect( self.viewport)) raise StopIteration() if not recoresult['delegated']: logger.info('设置代理指挥') self.tap_rect( imgreco.before_operation.get_delegate_rect(self.viewport)) return # to on_prepare state logger.info("理智充足 开始行动") self.tap_rect( imgreco.before_operation.get_start_operation_rect( self.viewport)) smobj.prepare_reco = recoresult smobj.state = on_troop def on_troop(smobj): count_times = 0 while True: self.__wait(TINY_WAIT, False) screenshot = self.adb.screenshot() recoresult = imgreco.before_operation.check_confirm_troop_rect( screenshot) if recoresult: logger.info('确认编队') break else: count_times += 1 if count_times <= 7: logger.warning('等待确认编队') continue else: logger.error('{} 次检测后不再确认编队界面'.format(count_times)) raise StopIteration() self.tap_rect( imgreco.before_operation.get_confirm_troop_rect(self.viewport)) smobj.operation_start = monotonic() smobj.state = on_operation def on_operation(smobj): if smobj.first_wait: if len(self.operation_time) == 0: wait_time = BATTLE_NONE_DETECT_TIME else: wait_time = sum(self.operation_time) / len( self.operation_time) - 7 logger.info('等待 %d s' % wait_time) self.__wait(wait_time, MANLIKE_FLAG=False) smobj.first_wait = False t = monotonic() - smobj.operation_start logger.info('已进行 %.1f s,判断是否结束', t) screenshot = self.adb.screenshot() if imgreco.end_operation.check_level_up_popup(screenshot): logger.info("等级提升") self.operation_time.append(t) smobj.state = on_level_up_popup return if smobj.prepare_reco['consume_ap']: detector = imgreco.end_operation.check_end_operation else: detector = imgreco.end_operation.check_end_operation_alt if detector(screenshot): logger.info('战斗结束') self.operation_time.append(t) crop = imgreco.end_operation.get_still_check_rect( self.viewport) if self.wait_for_still_image(crop=crop, timeout=15, raise_for_timeout=True): smobj.state = on_end_operation return dlgtype, ocrresult = imgreco.common.recognize_dialog(screenshot) if dlgtype is not None: if dlgtype == 'yesno' and '代理指挥' in ocrresult: logger.warning('代理指挥出现失误') smobj.mistaken_delegation = True if config.get('behavior/mistaken_delegation/settle', False): logger.info('以 2 星结算关卡') self.tap_rect( imgreco.common.get_dialog_right_button_rect( screenshot)) self.__wait(2) return else: logger.info('放弃关卡') self.tap_rect( imgreco.common.get_dialog_left_button_rect( screenshot)) # 关闭失败提示 self.wait_for_still_image() self.tap_rect( imgreco.common.get_reward_popup_dismiss_rect( screenshot)) self.__wait(1) return elif dlgtype == 'yesno' and '将会恢复' in ocrresult: logger.info('发现放弃行动提示,关闭') self.tap_rect( imgreco.common.get_dialog_left_button_rect(screenshot)) else: logger.error('未处理的对话框:[%s] %s', dlgtype, ocrresult) raise RuntimeError('unhandled dialog') logger.info('战斗未结束') self.__wait(BATTLE_FINISH_DETECT) def on_level_up_popup(smobj): self.__wait(SMALL_WAIT, MANLIKE_FLAG=True) logger.info('关闭升级提示') self.tap_rect( imgreco.end_operation.get_dismiss_level_up_popup_rect( self.viewport)) self.wait_for_still_image() smobj.state = on_end_operation def on_end_operation(smobj): screenshot = self.adb.screenshot() logger.info('离开结算画面') self.tap_rect( imgreco.end_operation.get_dismiss_end_operation_rect( self.viewport)) reportresult = penguin_stats.reporter.ReportResult.NotReported try: # 掉落识别 drops = imgreco.end_operation.recognize(screenshot) logger.debug('%s', repr(drops)) logger.info('掉落识别结果:%s', format_recoresult(drops)) log_total = len(self.loots) for _, group in drops['items']: for name, qty in group: if name is not None and qty is not None: self.loots[name] = self.loots.get(name, 0) + qty if log_total: self.log_total_loots() if self.use_penguin_report: reportresult = self.penguin_reporter.report(drops) if isinstance(reportresult, penguin_stats.reporter.ReportResult.Ok): logger.debug('report hash = %s', reportresult.report_hash) except Exception as e: logger.error('', exc_info=True) if self.use_penguin_report and reportresult is penguin_stats.reporter.ReportResult.NotReported: filename = os.path.join(config.SCREEN_SHOOT_SAVE_PATH, '未上报掉落-%d.png' % time.time()) with open(filename, 'wb') as f: screenshot.save(f, format='PNG') logger.error('未上报掉落截图已保存到 %s', filename) smobj.stop = True smobj.state = on_prepare smobj.stop = False smobj.operation_start = 0 while not smobj.stop: oldstate = smobj.state smobj.state(smobj) if smobj.state != oldstate: logger.debug('state changed to %s', smobj.state.__name__) if smobj.mistaken_delegation and config.get( 'behavior/mistaken_delegation/skip', True): raise StopIteration() def back_to_main(self): # 回到主页 logger.info("正在返回主页") while True: screenshot = self.adb.screenshot() if imgreco.main.check_main(screenshot): break # 检查是否有返回按钮 if imgreco.common.check_nav_button(screenshot): logger.info('发现返回按钮,点击返回') self.tap_rect( imgreco.common.get_nav_button_back_rect(self.viewport)) self.__wait(SMALL_WAIT) # 点击返回按钮之后重新检查 continue if imgreco.common.check_get_item_popup(screenshot): logger.info('当前为获得物资画面,关闭') self.tap_rect( imgreco.common.get_reward_popup_dismiss_rect( self.viewport)) self.__wait(SMALL_WAIT) continue # 检查是否在设置画面 if imgreco.common.check_setting_scene(screenshot): logger.info("当前为设置/邮件画面,返回") self.tap_rect( imgreco.common.get_setting_back_rect(self.viewport)) self.__wait(SMALL_WAIT) continue # 检测是否有关闭按钮 rect, confidence = imgreco.common.find_close_button(screenshot) if confidence > 0.9: logger.info("发现关闭按钮") self.tap_rect(rect) self.__wait(SMALL_WAIT) continue dlgtype, ocr = imgreco.common.recognize_dialog(screenshot) if dlgtype == 'yesno': if '基建' in ocr or '停止招募' in ocr: self.tap_rect( imgreco.common.get_dialog_right_button_rect( screenshot)) self.__wait(3) continue elif '招募干员' in ocr or '加急' in ocr: self.tap_rect( imgreco.common.get_dialog_left_button_rect(screenshot)) self.__wait(3) continue else: raise RuntimeError('未适配的对话框') elif dlgtype == 'ok': self.tap_rect( imgreco.common.get_dialog_ok_button_rect(screenshot)) self.__wait(1) continue raise RuntimeError('未知画面') logger.info("已回到主页") def module_battle( self, # 完整的战斗模块 c_id, # 选择的关卡 set_count=1000): # 作战次数 logger.debug("helper.module_battle") self.goto_stage(c_id) self.module_battle_slim(c_id, set_count=set_count, check_ai=True, sub=True) return True def main_handler(self, task_list, clear_tasks=False, auto_close=True): logger.info("装载模块...") logger.info("战斗模块...启动") flag = False if len(task_list) == 0: logger.fatal("任务清单为空!") for c_id, count in task_list: if not stage_path.is_stage_supported(c_id): raise ValueError(c_id) logger.info("开始 %s", c_id) flag = self.module_battle(c_id, count) if flag: if self.__call_by_gui or auto_close is False: logger.info("所有模块执行完毕") else: if clear_tasks: self.clear_daily_task() logger.info("所有模块执行完毕... 60s后退出") self.__wait(60) self.__del() else: if self.__call_by_gui or auto_close is False: logger.error("发生未知错误... 进程已结束") else: logger.error("发生未知错误... 60s后退出") self.__wait(60) self.__del() def clear_daily_task(self): logger.debug("helper.clear_daily_task") logger.info("领取每日任务") self.back_to_main() screenshot = self.adb.screenshot() logger.info('进入任务界面') self.tap_quadrilateral(imgreco.main.get_task_corners(screenshot)) self.__wait(SMALL_WAIT) screenshot = self.adb.screenshot() hasbeginner = imgreco.task.check_beginners_task(screenshot) if hasbeginner: logger.info('发现见习任务,切换到每日任务') self.tap_rect( imgreco.task.get_daily_task_rect(screenshot, hasbeginner)) self.__wait(TINY_WAIT) screenshot = self.adb.screenshot() while imgreco.task.check_collectable_reward(screenshot): logger.info('完成任务') self.tap_rect( imgreco.task.get_collect_reward_button_rect(self.viewport)) self.__wait(SMALL_WAIT) while True: screenshot = self.adb.screenshot() if imgreco.common.check_get_item_popup(screenshot): logger.info('领取奖励') self.tap_rect( imgreco.common.get_reward_popup_dismiss_rect( self.viewport)) self.__wait(SMALL_WAIT) else: break screenshot = self.adb.screenshot() logger.info("奖励已领取完毕") def recruit(self): from . import recruit_calc logger.info('识别招募标签') tags = imgreco.recruit.get_recruit_tags(self.adb.screenshot()) logger.info('可选标签:%s', ' '.join(tags)) result = recruit_calc.calculate(tags) logger.debug('计算结果:%s', repr(result)) return result def find_and_tap(self, partition, target): lastpos = None while True: screenshot = self.adb.screenshot() recoresult = imgreco.map.recognize_map(screenshot, partition) if recoresult is None: # TODO: retry logger.error('未能定位关卡地图') raise RuntimeError('recognition failed') if target in recoresult: pos = recoresult[target] logger.info('目标 %s 坐标: %s', target, pos) if lastpos is not None and tuple(pos) == tuple(lastpos): logger.error('拖动后坐标未改变') raise RuntimeError('拖动后坐标未改变') if 0 < pos[0] < self.viewport[0]: logger.info('目标在可视区域内,点击') self.adb.touch_tap(pos, offsets=(5, 5)) self.__wait(3) break else: lastpos = pos originX = self.viewport[0] // 2 + randint(-100, 100) originY = self.viewport[1] // 2 + randint(-100, 100) if pos[0] < 0: # target in left of viewport logger.info('目标在可视区域左侧,向右拖动') # swipe right diff = -pos[0] if abs(diff) < 100: diff = 120 diff = min(diff, self.viewport[0] - originX) elif pos[0] > self.viewport[ 0]: # target in right of viewport logger.info('目标在可视区域右侧,向左拖动') # swipe left diff = self.viewport[0] - pos[0] if abs(diff) < 100: diff = -120 diff = max(diff, -originX) self.adb.touch_swipe2((originX, originY), (diff * 0.7 * uniform(0.8, 1.2), 0), max(250, diff / 2)) self.__wait(5) continue else: raise KeyError((target, partition)) def find_and_tap_daily(self, partition, target, *, recursion=0): screenshot = self.adb.screenshot() recoresult = imgreco.map.recognize_daily_menu(screenshot, partition) if target in recoresult: pos, conf = recoresult[target] logger.info('目标 %s 坐标=%s 差异=%f', target, pos, conf) offset = self.viewport[1] * 0.12 ## 24vh * 24vh range self.tap_rect((*(pos - offset), *(pos + offset))) else: if recursion == 0: logger.info('目标可能在可视区域右侧,向左拖动') originX = self.viewport[0] // 2 + randint(-100, 100) originY = self.viewport[1] // 2 + randint(-100, 100) self.adb.touch_swipe2((originX, originY), (-self.viewport[0] * 0.2, 0), 400) self.__wait(2) self.find_and_tap_daily(partition, target, recursion=recursion + 1) else: logger.error('未找到目标,是否未开放关卡?') def goto_stage(self, stage): if not stage_path.is_stage_supported(stage): logger.error('不支持的关卡:%s', stage) raise ValueError(stage) path = stage_path.get_stage_path(stage) self.back_to_main() logger.info('进入作战') self.tap_quadrilateral( imgreco.main.get_ballte_corners(self.adb.screenshot())) self.__wait(3) if path[0] == 'main': self.find_and_tap('episodes', path[1]) self.find_and_tap(path[1], path[2]) elif path[0] == 'material' or path[0] == 'soc': logger.info('选择类别') self.tap_rect( imgreco.map.get_daily_menu_entry(self.viewport, path[0])) self.find_and_tap_daily(path[0], path[1]) self.find_and_tap(path[1], path[2]) else: raise NotImplementedError() def get_credit(self): logger.debug("helper.get_credit") logger.info("领取信赖") self.back_to_main() screenshot = self.adb.screenshot() logger.info('进入好友列表') self.tap_quadrilateral(imgreco.main.get_friend_corners(screenshot)) self.__wait(SMALL_WAIT) self.tap_quadrilateral(imgreco.main.get_friend_list(screenshot)) self.__wait(SMALL_WAIT) logger.info('访问好友基建') self.tap_quadrilateral(imgreco.main.get_friend_build(screenshot)) self.__wait(MEDIUM_WAIT) building_count = 0 while building_count <= 11: screenshot = self.adb.screenshot() self.tap_quadrilateral( imgreco.main.get_next_friend_build(screenshot)) self.__wait(MEDIUM_WAIT) building_count = building_count + 1 logger.info('访问第 %s 位好友', building_count) logger.info('信赖领取完毕') def get_building(self): logger.debug("helper.get_building") logger.info("清空基建") self.back_to_main() screenshot = self.adb.screenshot() logger.info('进入我的基建') self.tap_quadrilateral(imgreco.main.get_back_my_build(screenshot)) self.__wait(MEDIUM_WAIT + 3) self.tap_quadrilateral(imgreco.main.get_my_build_task(screenshot)) self.__wait(SMALL_WAIT) logger.info('收取制造产物') self.tap_quadrilateral( imgreco.main.get_my_build_task_clear(screenshot)) self.__wait(SMALL_WAIT) logger.info('清理贸易订单') self.tap_quadrilateral(imgreco.main.get_my_sell_task_1(screenshot)) self.__wait(SMALL_WAIT + 1) self.tap_quadrilateral(imgreco.main.get_my_sell_tasklist(screenshot)) self.__wait(SMALL_WAIT - 1) sell_count = 0 while sell_count <= 6: screenshot = self.adb.screenshot() self.tap_quadrilateral( imgreco.main.get_my_sell_task_main(screenshot)) self.__wait(TINY_WAIT) sell_count = sell_count + 1 self.tap_quadrilateral(imgreco.main.get_my_sell_task_2(screenshot)) self.__wait(SMALL_WAIT - 1) sell_count = 0 while sell_count <= 6: screenshot = self.adb.screenshot() self.tap_quadrilateral( imgreco.main.get_my_sell_task_main(screenshot)) self.__wait(TINY_WAIT) sell_count = sell_count + 1 self.back_to_main() logger.info("基建领取完毕") def log_total_loots(self): logger.info('目前已获得:%s', ', '.join('%sx%d' % tup for tup in self.loots.items()))
def ensure_connector(self): if self.helper.adb is None: new_connector = ADBConnector.auto_connect() self.helper.connect_device(new_connector) self.notify("web:current-device", str(new_connector))
class ArknightsHelper(object): def __init__(self, adb_host=None, device_connector=None, frontend=None): # 当前绑定到的设备 self.adb = None if adb_host is not None or device_connector is not None: self.connect_device(device_connector, adb_serial=adb_host) if frontend is None: frontend = DummyFrontend() if self.adb is None: self.connect_device(auto_connect()) self.frontend = frontend self.frontend.attach(self) self.operation_time = [] if DEBUG_LEVEL >= 1: self.__print_info() self.refill_with_item = config.get('behavior/refill_ap_with_item', False) self.refill_with_originium = config.get('behavior/refill_ap_with_originium', False) self.use_refill = self.refill_with_item or self.refill_with_originium self.loots = {} self.use_penguin_report = config.get('reporting/enabled', False) if self.use_penguin_report: self.penguin_reporter = penguin_stats.reporter.PenguinStatsReporter() self.refill_count = 0 self.max_refill_count = None logger.debug("成功初始化模块") def ensure_device_connection(self): if self.adb is None: raise RuntimeError('not connected to device') def connect_device(self, connector=None, *, adb_serial=None): if connector is not None: self.adb = connector elif adb_serial is not None: self.adb = ADBConnector(adb_serial) else: self.adb = None return self.viewport = self.adb.screen_size if self.adb.screenshot_rotate %180: self.viewport = (self.viewport[1], self.viewport[0]) if self.viewport[1] < 720 or Fraction(self.viewport[0], self.viewport[1]) < Fraction(16, 9): title = '设备当前分辨率(%dx%d)不符合要求' % (self.viewport[0], self.viewport[1]) body = '需要宽高比等于或大于 16∶9,且渲染高度不小于 720。' details = None if Fraction(self.viewport[1], self.viewport[0]) >= Fraction(16, 9): body = '屏幕截图可能需要旋转,请尝试在 device-config 中指定旋转角度。' img = self.adb.screenshot() imgfile = os.path.join(config.SCREEN_SHOOT_SAVE_PATH, 'orientation-diagnose-%s.png' % time.strftime("%Y%m%d-%H%M%S")) img.save(imgfile) import json details = '参考 %s 以更正 device-config.json[%s]["screenshot_rotate"]' % (imgfile, json.dumps(self.adb.config_key)) for msg in [title, body, details]: if msg is not None: logger.warn(msg) frontend.alert(title, body, 'warn', details) def __print_info(self): logger.info('当前系统信息:') logger.info('分辨率:\t%dx%d', *self.viewport) # logger.info('OCR 引擎:\t%s', ocr.engine.info) logger.info('截图路径:\t%s', config.SCREEN_SHOOT_SAVE_PATH) if config.enable_baidu_api: logger.info('%s', """百度API配置信息: APP_ID\t{app_id} API_KEY\t{api_key} SECRET_KEY\t{secret_key} """.format( app_id=config.APP_ID, api_key=config.API_KEY, secret_key=config.SECRET_KEY ) ) def __del(self): self.adb.run_device_cmd("am force-stop {}".format(config.ArkNights_PACKAGE_NAME)) def destroy(self): self.__del() def check_game_active(self): # 启动游戏 需要手动调用 logger.debug("helper.check_game_active") current = self.adb.run_device_cmd('dumpsys window windows | grep mCurrentFocus').decode(errors='ignore') logger.debug("正在尝试启动游戏") logger.debug(current) if config.ArkNights_PACKAGE_NAME in current: logger.debug("游戏已启动") else: self.adb.run_device_cmd( "am start -n {}/{}".format(config.ArkNights_PACKAGE_NAME, config.ArkNights_ACTIVITY_NAME)) logger.debug("成功启动游戏") def __wait(self, n=10, # 等待时间中值 MANLIKE_FLAG=True, allow_skip=False): # 是否在此基础上设偏移量 if MANLIKE_FLAG: m = uniform(0, 0.3) n = uniform(n - m * 0.5 * n, n + m * n) self.frontend.delay(n, allow_skip) def mouse_click(self, # 点击一个按钮 XY): # 待点击的按钮的左上和右下坐标 assert (self.viewport == (1280, 720)) logger.debug("helper.mouse_click") xx = randint(XY[0][0], XY[1][0]) yy = randint(XY[0][1], XY[1][1]) logger.info("接收到点击坐标并传递xx:{}和yy:{}".format(xx, yy)) self.adb.touch_tap((xx, yy)) self.__wait(TINY_WAIT, MANLIKE_FLAG=True) def tap_rect(self, rc): hwidth = (rc[2] - rc[0]) / 2 hheight = (rc[3] - rc[1]) / 2 midx = rc[0] + hwidth midy = rc[1] + hheight xdiff = max(-1, min(1, gauss(0, 0.2))) ydiff = max(-1, min(1, gauss(0, 0.2))) tapx = int(midx + xdiff * hwidth) tapy = int(midy + ydiff * hheight) self.adb.touch_tap((tapx, tapy)) self.__wait(TINY_WAIT, MANLIKE_FLAG=True) def tap_quadrilateral(self, pts): pts = np.asarray(pts) acdiff = max(0, min(2, gauss(1, 0.2))) bddiff = max(0, min(2, gauss(1, 0.2))) halfac = (pts[2] - pts[0]) / 2 m = pts[0] + halfac * acdiff pt2 = pts[1] if bddiff > 1 else pts[3] halfvec = (pt2 - m) / 2 finalpt = m + halfvec * bddiff self.adb.touch_tap(tuple(int(x) for x in finalpt)) self.__wait(TINY_WAIT, MANLIKE_FLAG=True) def wait_for_still_image(self, threshold=16, crop=None, timeout=60, raise_for_timeout=True, check_delay=1): if crop is None: shooter = lambda: self.adb.screenshot(False) else: shooter = lambda: self.adb.screenshot(False).crop(crop) screenshot = shooter() t0 = time.monotonic() ts = t0 + timeout n = 0 minerr = 65025 message_shown = False while (t1 := time.monotonic()) < ts: if check_delay > 0: self.__wait(check_delay, False, True) screenshot2 = shooter() mse = imgreco.imgops.compare_mse(screenshot, screenshot2) if mse <= threshold: return screenshot2 screenshot = screenshot2 if mse < minerr: minerr = mse if not message_shown and t1-t0 > 10: logger.info("等待画面静止") if raise_for_timeout: raise RuntimeError("%d 秒内画面未静止,最小误差=%d,阈值=%d" % (timeout, minerr, threshold)) return None
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