diff --git a/README.md b/README.md index cc1062a69..f2b5f091f 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ arknights-mower shop 招聘许可 赤金 龙门币 # 在商场使用信用点消费,购买物品的优先级从高到低分别是招聘许可、赤金和龙门币,其余物品不购买 arknights-mower base -f12 plan_2 # 自动使用菲亚梅塔恢复B102房间心情最差干员的心情,并保持原位;自动进行名为`plan_2`的的基建排班(排班功能须搭配配置文件使用) -arknights-mower base -c -d33 -f12 +arknights-mower base -c -d33 # 自动收取基建中的信赖/货物/订单;自动放置线索;自动前往 B303 房间(地下 3 层从左往右数第 3 间)使用无人机加速生产或贸易订单; ``` diff --git a/arknights_mower/__init__.py b/arknights_mower/__init__.py index 572ca0747..b7b4cff9b 100644 --- a/arknights_mower/__init__.py +++ b/arknights_mower/__init__.py @@ -15,4 +15,4 @@ __cli__ = not (__pyinstall__ and not sys.argv[1:]) __system__ = platform.system().lower() -__version__ = '2.1.4' +__version__ = '2.1.5' diff --git a/arknights_mower/data/agent.json b/arknights_mower/data/agent.json index ff15fdaf1..b846d085c 100644 --- a/arknights_mower/data/agent.json +++ b/arknights_mower/data/agent.json @@ -111,6 +111,7 @@ "特米米", "天火", "惊蛰", + "星源", "蜜蜡", "莱恩哈特", "薄绿", @@ -152,6 +153,7 @@ "安哲拉", "熔泉", "埃拉托", + "承曦格雷伊", "崖心", "雪雉", "初雪", @@ -209,6 +211,7 @@ "歌蕾蒂娅", "水月", "归溟幽灵鲨", + "多萝西", "闪灵", "夜莺", "凯尔希", diff --git a/arknights_mower/data/level.json b/arknights_mower/data/level.json index 5ab69c8bc..7dafba606 100644 --- a/arknights_mower/data/level.json +++ b/arknights_mower/data/level.json @@ -2056,5 +2056,59 @@ "ap_cost": 18, "code": "WR-10", "name": " 夕" + }, + "DH-1": { + "zone_id": "permanent_sidestory_9_Dossoles_Holiday", + "ap_cost": 9, + "code": "DH-1", + "name": "意外入选" + }, + "DH-2": { + "zone_id": "permanent_sidestory_9_Dossoles_Holiday", + "ap_cost": 9, + "code": "DH-2", + "name": "首轮竞赛" + }, + "DH-3": { + "zone_id": "permanent_sidestory_9_Dossoles_Holiday", + "ap_cost": 9, + "code": "DH-3", + "name": "拔铳相助" + }, + "DH-4": { + "zone_id": "permanent_sidestory_9_Dossoles_Holiday", + "ap_cost": 12, + "code": "DH-4", + "name": "铁人三项" + }, + "DH-5": { + "zone_id": "permanent_sidestory_9_Dossoles_Holiday", + "ap_cost": 12, + "code": "DH-5", + "name": "曲径求胜" + }, + "DH-6": { + "zone_id": "permanent_sidestory_9_Dossoles_Holiday", + "ap_cost": 12, + "code": "DH-6", + "name": "紧追猛赶" + }, + "DH-7": { + "zone_id": "permanent_sidestory_9_Dossoles_Holiday", + "ap_cost": 18, + "code": "DH-7", + "name": "沙滩阻击" + }, + "DH-8": { + "zone_id": "permanent_sidestory_9_Dossoles_Holiday", + "ap_cost": 18, + "code": "DH-8", + "name": "抢滩登陆" + }, + "DH-9": { + "zone_id": "permanent_sidestory_9_Dossoles_Holiday", + "ap_cost": 18, + "code": "DH-9", + "name": "鼠胆龙威" } } \ No newline at end of file diff --git a/arknights_mower/data/zone.json b/arknights_mower/data/zone.json index 13046c6e5..aec37198c 100644 --- a/arknights_mower/data/zone.json +++ b/arknights_mower/data/zone.json @@ -190,5 +190,11 @@ "name": "画中人", "chapterIndex": null, "zoneIndex": 8 + }, + "permanent_sidestory_9_Dossoles_Holiday": { + "type": "SIDESTORY", + "name": "多索雷斯假日", + "chapterIndex": null, + "zoneIndex": 9 } } \ No newline at end of file diff --git a/arknights_mower/fonts/SourceHanSansSC-Bold.otf b/arknights_mower/fonts/SourceHanSansSC-Bold.otf index 77211bb98..615fe502a 100644 Binary files a/arknights_mower/fonts/SourceHanSansSC-Bold.otf and b/arknights_mower/fonts/SourceHanSansSC-Bold.otf differ diff --git a/arknights_mower/ocr/__init__.py b/arknights_mower/ocr/__init__.py index 4279c29a7..197f17b5d 100644 --- a/arknights_mower/ocr/__init__.py +++ b/arknights_mower/ocr/__init__.py @@ -1,40 +1,4 @@ -from ..data import ocr_error -from ..utils import config -from ..utils.log import logger from .model import OcrHandle -from .ocrspace import API, Language +from .rectify import ocr_rectify ocrhandle = OcrHandle() - - -def ocr_rectify(img, pre, valid, text=''): - """ - 调用在线 OCR 校正本地 OCR 得到的错误结果,并返回校正后的识别结果 - 若在线 OCR 依旧无法正确识别则返回 None - - :param img: numpy.array, 图像 - :param pre: (str, tuple), 本地 OCR 得到的错误结果,包括字符串和范围 - :param valid: list[str], 期望得到的识别结果 - :param text: str, 指定 log 信息前缀 - - :return res: str | None, 识别的结果 - """ - logger.warning(f'{text}识别异常:正在调用在线 OCR 处理异常结果……') - - global ocronline - ocronline = API(api_key=config.OCR_APIKEY, - language=Language.Chinese_Simplified) - pre_res = pre[1] - res = ocronline.predict(img, pre[2]) - if res is None or res == pre_res: - logger.warning( - f'{text}识别异常:{pre_res} 为不存在的数据') - elif res not in valid: - logger.warning( - f'{text}识别异常:{pre_res} 和 {res} 均为不存在的数据') - else: - logger.warning( - f'{text}识别异常:{pre_res} 应为 {res}') - ocr_error[pre_res] = res - pre_res = res - return pre_res diff --git a/arknights_mower/ocr/rectify.py b/arknights_mower/ocr/rectify.py new file mode 100644 index 000000000..1aa5df4bd --- /dev/null +++ b/arknights_mower/ocr/rectify.py @@ -0,0 +1,38 @@ +from ..data import ocr_error +from ..utils import config +from ..utils.log import logger +from .ocrspace import API, Language + + +def ocr_rectify(img, pre, valid, text=''): + """ + 调用在线 OCR 校正本地 OCR 得到的错误结果,并返回校正后的识别结果 + 若在线 OCR 依旧无法正确识别则返回 None + + :param img: numpy.array, 图像 + :param pre: (str, tuple), 本地 OCR 得到的错误结果,包括字符串和范围 + :param valid: list[str], 期望得到的识别结果 + :param text: str, 指定 log 信息前缀 + + :return res: str | None, 识别的结果 + """ + logger.warning(f'{text}识别异常:正在调用在线 OCR 处理异常结果……') + + global ocronline + print(config) + ocronline = API(api_key=config.OCR_APIKEY, + language=Language.Chinese_Simplified) + pre_res = pre[1] + res = ocronline.predict(img, pre[2]) + if res is None or res == pre_res: + logger.warning( + f'{text}识别异常:{pre_res} 为不存在的数据') + elif res not in valid: + logger.warning( + f'{text}识别异常:{pre_res} 和 {res} 均为不存在的数据') + else: + logger.warning( + f'{text}识别异常:{pre_res} 应为 {res}') + ocr_error[pre_res] = res + pre_res = res + return pre_res diff --git a/arknights_mower/resources/comfirm_blue.png b/arknights_mower/resources/confirm_blue.png similarity index 100% rename from arknights_mower/resources/comfirm_blue.png rename to arknights_mower/resources/confirm_blue.png diff --git a/arknights_mower/solvers/base_construct.py b/arknights_mower/solvers/base_construct.py index 3ab0d74c6..c315e2400 100644 --- a/arknights_mower/solvers/base_construct.py +++ b/arknights_mower/solvers/base_construct.py @@ -137,7 +137,7 @@ def clue(self) -> None: self.enter_room('meeting') # 点击线索详情 - self.tap((self.recog.w*0.05, self.recog.h*0.95), interval=3) + self.tap((self.recog.w*0.1, self.recog.h*0.9), interval=3) # 如果是线索交流的报告则返回 self.find('clue_summary') and self.back() @@ -252,8 +252,8 @@ def recog_view(self, only_y2: bool = True) -> None: return y2 # x3: 右边黑色 mask 边缘 x3 = self.recog_view_mask_right() - # x4: 四分之三的位置,用来定位单个线索 - x4 = (x1 + 3 * x2) // 4 + # x4: 用来区分单个线索 + x4 = (54 * x1 + 25 * x2) // 79 logger.debug(f'recog_view: y2:{y2}, x3:{x3}, x4:{x4}') @@ -438,8 +438,9 @@ def choose_agent(self, agent: list[str], skip_free: int = 0, order: ArrangeOrder if not first_time: # 滑动到最左边 + self.sleep(interval=0.5, rebuild=False) for _ in range(9): - self.swipe((w//2, h//2), (w//2, 0), interval=0.5) + self.swipe_only((w//2, h//2), (w//2, 0), interval=0.5) self.swipe((w//2, h//2), (w//2, 0), interval=3, rebuild=False) else: # 第一次进入按技能排序 @@ -491,8 +492,9 @@ def choose_agent(self, agent: list[str], skip_free: int = 0, order: ArrangeOrder if len(agent) == 0: break # 否则滑动到最左边 + self.sleep(interval=0.5, rebuild=False) for _ in range(9): - self.swipe((w//2, h//2), (w//2, 0), interval=0.5) + self.swipe_only((w//2, h//2), (w//2, 0), interval=0.5) self.swipe((w//2, h//2), (w//2, 0), interval=3, rebuild=False) # reset the statuses and cancel the rightward-swiping @@ -502,12 +504,17 @@ def choose_agent(self, agent: list[str], skip_free: int = 0, order: ArrangeOrder continue else: - for name in agent_name & agent: - for y in ret: - if y[0] == name: - self.tap((y[1][0]), interval=0, rebuild=False) - break - agent.remove(name) + for y in ret: + name = y[0] + if name in agent_name & agent: + self.tap((y[1][0]), interval=0, rebuild=False) + agent.remove(name) + # for name in agent_name & agent: + # for y in ret: + # if y[0] == name: + # self.tap((y[1][0]), interval=0, rebuild=False) + # break + # agent.remove(name) # 如果已经完成选择则退出 if len(agent) == 0: @@ -522,8 +529,9 @@ def choose_agent(self, agent: list[str], skip_free: int = 0, order: ArrangeOrder if not first_time: # 滑动到最左边 + self.sleep(interval=0.5, rebuild=False) for _ in range(9): - self.swipe((w//2, h//2), (w//2, 0), interval=0.5) + self.swipe_only((w//2, h//2), (w//2, 0), interval=0.5) self.swipe((w//2, h//2), (w//2, 0), interval=3, rebuild=False) else: # 第一次进入按技能排序 @@ -628,7 +636,7 @@ def agent_arrange(self, plan: tp.BasePlan) -> None: continue self.recog.update() self.tap_element( - 'comfirm_blue', detected=True, judge=False, interval=3) + 'confirm_blue', detected=True, judge=False, interval=3) if self.scene() == Scene.INFRA_ARRANGE_CONFIRM: x = self.recog.w // 3 * 2 # double confirm y = self.recog.h - 10 @@ -706,8 +714,9 @@ def choose_agent_in_order(self, agent: list[str], exclude: list[str] = None, exc if first_time and not far_left and agent[idx] != 'Free': # 如果是寻找这位干员目前为止的第一次滑动, 且目前不是最左端,则滑动到最左端 + self.sleep(interval=0.5, rebuild=False) for _ in range(9): - self.swipe((w//2, h//2), (w//2, 0), interval=0.5) + self.swipe_only((w//2, h//2), (w//2, 0), interval=0.5) self.swipe((w//2, h//2), (w//2, 0), interval=3, rebuild=True) far_left = True first_time = False @@ -831,7 +840,9 @@ def fia(self, room: str): self.tap((self.recog.w*BY_STATUS[0], self.recog.h*BY_STATUS[1]), interval=0.1) # 安排空闲干员 _free = self.choose_agent_in_order(_temp_on_shift_agents, exclude_checked_in=True) - self.tap_element('comfirm_blue', detected=True, judge=False, interval=3) + self.tap_element('confirm_blue', detected=True, judge=False, interval=3) + while self.scene() == Scene.CONNECTING: + self.sleep(3) self.back(interval=2) logger.info('进入菲亚梅塔所在宿舍,为%s恢复心情', _recover) @@ -843,13 +854,17 @@ def fia(self, room: str): rest_agents = [_recover, '菲亚梅塔'] self.choose_agent_in_order(rest_agents, exclude_checked_in=True) - self.tap_element('comfirm_blue', detected=True, judge=False, interval=3) + self.tap_element('confirm_blue', detected=True, judge=False, interval=3) + while self.scene() == Scene.CONNECTING: + self.sleep(3) logger.info('恢复完毕,填满宿舍') rest_agents = '菲亚梅塔 Free Free Free Free'.split() self.tap((self.recog.w*0.82, self.recog.h*0.25), interval=2) self.choose_agent_in_order(rest_agents, exclude=[_recover], dormitory=True) - self.tap_element('comfirm_blue', detected=True, judge=False, interval=3) + self.tap_element('confirm_blue', detected=True, judge=False, interval=3) + while self.scene() == Scene.CONNECTING: + self.sleep(3) logger.info('恢复原职') self.back(interval=2) @@ -858,8 +873,9 @@ def fia(self, room: str): self.tap_element('arrange_check_in', interval=2, rebuild=False) self.tap((self.recog.w*0.82, self.recog.h*0.25), interval=2) self.choose_agent_in_order(on_shift_agents) - self.tap_element('comfirm_blue', detected=True, judge=False, interval=3) - + self.tap_element('confirm_blue', detected=True, judge=False, interval=3) + while self.scene() == Scene.CONNECTING: + self.sleep(3) self.back(interval=2) # def clue_statis(self): diff --git a/arknights_mower/solvers/recruit.py b/arknights_mower/solvers/recruit.py index 76a461150..1b2e33094 100644 --- a/arknights_mower/solvers/recruit.py +++ b/arknights_mower/solvers/recruit.py @@ -55,8 +55,6 @@ def transition(self) -> bool: if self.scene() == Scene.INDEX: self.tap_element('index_recruit') elif self.scene() == Scene.RECRUIT_MAIN: - if not self.has_ticket and not self.can_refresh: - return True segments = segment.recruit(self.recog.img) tapped = False for idx, seg in enumerate(segments): @@ -65,6 +63,8 @@ def transition(self) -> bool: if self.tap_element('recruit_finish', scope=seg, detected=True): tapped = True break + if not self.has_ticket and not self.can_refresh: + continue required = self.find('job_requirements', scope=seg) if required is None: self.tap(seg) @@ -137,6 +137,7 @@ def recruit_tags(self) -> bool: # 如果没有招募券则只刷新标签不选人 if not self.has_ticket: + logger.debug('OK') self.back() return diff --git a/arknights_mower/utils/segment.py b/arknights_mower/utils/segment.py index 02ec72d1a..c2bc0f3d9 100644 --- a/arknights_mower/utils/segment.py +++ b/arknights_mower/utils/segment.py @@ -471,14 +471,23 @@ def free_agent(img, draw=False): st = ret[-2][2] # 起点 ed = ret[0][1] # 终点 - # 去除空白的干员框,同时收集 y 坐标 + # 收集 y 坐标并初步筛选 y_set = set() + __ret = [] for poly in ret: __img = img[poly[0, 1]:poly[2, 1], poly[0, 0]:poly[2, 0]] y_set.add(poly[0, 1]) y_set.add(poly[2, 1]) + # 去除空白的干员框 if 80 <= np.min(__img): - ret.remove(poly) + logger.debug(f'drop(empty): {poly.tolist()}') + continue + # 去除被选中的蓝框 + elif np.count_nonzero(__img[:, :, 0] >= 224) == 0 or np.count_nonzero(__img[:, :, 0] == 0) > 0: + logger.debug(f'drop(selected): {poly.tolist()}') + continue + __ret.append(poly) + ret = __ret y1, y2, y4, y5 = sorted(list(y_set)) y0 = height - y5 diff --git a/arknights_mower/utils/solver.py b/arknights_mower/utils/solver.py index 71573a896..d3346ca60 100644 --- a/arknights_mower/utils/solver.py +++ b/arknights_mower/utils/solver.py @@ -119,6 +119,13 @@ def swipe(self, start: tp.Coordinate, movement: tp.Coordinate, duration: int = 1 if interval > 0: self.sleep(interval, rebuild) + def swipe_only(self, start: tp.Coordinate, movement: tp.Coordinate, duration: int = 100, interval: float = 1) -> None: + """ swipe only, no rebuild and recapture """ + end = (start[0] + movement[0], start[1] + movement[1]) + self.device.swipe(start, end, duration=duration) + if interval > 0: + time.sleep(interval) + # def swipe_seq(self, points: list[tp.Coordinate], duration: int = 100, interval: float = 1, rebuild: bool = True) -> None: # """ swipe with point sequence """ # self.device.swipe(points, duration=duration)