From 7c7ae1194d1263692c44b7b293a48e5c58121b5a Mon Sep 17 00:00:00 2001 From: xylolit-mu Date: Sat, 22 Aug 2026 11:22:41 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=AF=84=E5=85=BB?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tasks/KekkaiUtilize/script_task.py | 251 +++++++++++++++++++---------- 1 file changed, 164 insertions(+), 87 deletions(-) diff --git a/tasks/KekkaiUtilize/script_task.py b/tasks/KekkaiUtilize/script_task.py index aa675defd..f21fc3400 100644 --- a/tasks/KekkaiUtilize/script_task.py +++ b/tasks/KekkaiUtilize/script_task.py @@ -9,7 +9,7 @@ from module.base.timer import Timer from module.atom.image_grid import ImageGrid from module.logger import logger -from module.exception import TaskEnd, GamePageUnknownError +from module.exception import TaskEnd, GamePageUnknownError, GameStuckError from tasks.GameUi.game_ui import GameUi from tasks.KekkaiUtilize.page import page_guild_realm, page_guild_realm_utilize, page_guild_realm_growth, \ @@ -29,16 +29,38 @@ class ScriptTask(GameUi, ReplaceShikigami, KekkaiUtilizeAssets): last_best_index = 99 utilize_add_count = 0 + utilize_failed_count = 0 + utilize_terminal_failure = False + utilize_found_eligible_card = False ap_max_num = 0 jade_max_num = 0 + # 同类型、同星级结界卡的最高奖励。达到最高值后,本轮不再打开同档卡片。 + CARD_TIER_INFO = { + CardClass.TAIKO4: ('太鼓', 4, 59), + CardClass.TAIKO5: ('太鼓', 5, 67), + CardClass.TAIKO6: ('太鼓', 6, 76), + CardClass.FISH4: ('斗鱼', 4, 118), + CardClass.FISH5: ('斗鱼', 5, 134), + CardClass.FISH6: ('斗鱼', 6, 152), + } + def run(self): con = self.config.kekkai_utilize.utilize_config + self.utilize_add_count = 0 + self.utilize_failed_count = 0 + self.utilize_terminal_failure = False + self.utilize_found_eligible_card = False + self.ap_max_num = 0 + self.jade_max_num = 0 # 进入寮结界 self.goto_page(page_guild_realm) # 育成界面去蹭卡 if con.utilize_enable: - self.check_utilize_add() + if not self.check_utilize_add(): + # 正常返回而非抛出 TaskEnd,使调度器把本轮记为失败; + # 下次运行时间已由失败处理设置为 10 分钟后。 + return # 查看育成满级 self.check_max_lv(con.shikigami_class, con.auto_fill) @@ -67,10 +89,19 @@ def check_utilize_add(self): while 1: self.utilize_add_count += 1 if self.utilize_add_count >= 5: - logger.warning('没有合适可以蹭的卡, 5分钟后再次执行蹭卡') - self.push_notify(content=f"没有合适可以蹭的卡, 5分钟后再次执行蹭卡") + if not self.utilize_found_eligible_card: + target_name = { + UtilizeRule.TAIKO: '太鼓', + UtilizeRule.FISH: '斗鱼', + UtilizeRule.DEFAULT: '太鼓或斗鱼', + }.get(con.utilize_rule, '目标结界卡') + message = f'未检测到四星及以上{target_name}, 5分钟后再次执行蹭卡' + logger.warning(message) + self.push_notify(content=message) + else: + logger.warning('已检测到四星及以上结界卡,但未能完成选择,5分钟后重试') self.set_next_run(task='KekkaiUtilize', target=datetime.now() + timedelta(minutes=5)) - return + return True # 无论收不收到菜,都会进入看看至少看一眼时间还剩多少 time.sleep(0.5) @@ -86,11 +117,13 @@ def check_utilize_add(self): # remaining_time = remaining_time - timedelta(seconds=30) next_time = datetime.now() + remaining_time self.set_next_run(task='KekkaiUtilize', target=next_time) - return + return True if not self.goto_page(page_guild_realm_utilize): logger.info('Utilize failed, exit') # 开始执行寄养 self.run_utilize(con.select_friend_list, con.shikigami_class, con.shikigami_order) + if self.utilize_terminal_failure: + return False self.goto_page(page_guild_realm_growth) def check_max_lv(self, shikigami_class: ShikigamiClass = ShikigamiClass.N, auto_fill: bool = False): @@ -304,11 +337,15 @@ def switch_friend_list(self, friend: SelectFriendList = SelectFriendList.SAME_SE def order_targets(self) -> ImageGrid: rule = self.config.kekkai_utilize.utilize_config.utilize_rule if rule == UtilizeRule.DEFAULT: - return ImageGrid([self.I_U_FISH_6, self.I_U_TAIKO_6, self.I_U_FISH_5, self.I_U_TAIKO_5]) + return ImageGrid([ + self.I_U_FISH_6, self.I_U_TAIKO_6, + self.I_U_FISH_5, self.I_U_TAIKO_5, + self.I_U_FISH_4, self.I_U_TAIKO_4, + ]) elif rule == UtilizeRule.FISH: - return ImageGrid([self.I_U_FISH_6, self.I_U_FISH_5]) + return ImageGrid([self.I_U_FISH_6, self.I_U_FISH_5, self.I_U_FISH_4]) elif rule == UtilizeRule.TAIKO: - return ImageGrid([self.I_U_TAIKO_6, self.I_U_TAIKO_5]) + return ImageGrid([self.I_U_TAIKO_6, self.I_U_TAIKO_5, self.I_U_TAIKO_4]) else: logger.error('Unknown utilize rule') raise ValueError('Unknown utilize rule') @@ -333,6 +370,41 @@ def order_cards(self) -> list[CardClass]: raise ValueError('Unknown utilize rule') return result + def _reset_utilize_friend_list(self, friend: SelectFriendList) -> None: + """刷新好友列表并回到所选分组的顶部。""" + if friend == SelectFriendList.SAME_SERVER: + self.switch_friend_list(SelectFriendList.SAME_SERVER) + self.swipe(self.S_U_END, interval=3) + self.switch_friend_list(SelectFriendList.DIFFERENT_SERVER) + self.switch_friend_list(SelectFriendList.SAME_SERVER) + else: # 跨区必须切换两次,否则结界卡不会刷新到顶部 + self.switch_friend_list(SelectFriendList.DIFFERENT_SERVER) + self.swipe(self.S_U_END, interval=3) + self.switch_friend_list(SelectFriendList.SAME_SERVER) + self.switch_friend_list(SelectFriendList.DIFFERENT_SERVER) + + def _record_utilize_failure(self, reason: str) -> bool: + """记录一次已选中目标后的实际蹭卡失败;三次后按失败延迟任务。""" + self.utilize_failed_count += 1 + logger.warning( + f'蹭卡失败: {reason} ({self.utilize_failed_count}/3)' + ) + if self.utilize_failed_count < 3: + return False + + message = '连续3次蹭卡失败,目标结界视为已经被蹭,任务失败,10分钟后重试' + logger.error(message) + self.push_notify(content=message) + self.set_next_run( + task='KekkaiUtilize', + finish=True, + success=False, + server=False, + target=datetime.now() + timedelta(minutes=10), + ) + self.utilize_terminal_failure = True + return False + def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, shikigami_class: ShikigamiClass = ShikigamiClass.N, shikigami_order: int = 7): @@ -346,19 +418,10 @@ def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, """ logger.hr('Start utilize') # 不管什么时候进来都要切换刷新列表(同区与跨区保持一致先切换滑动再切换) - if friend == SelectFriendList.SAME_SERVER: - self.switch_friend_list(SelectFriendList.SAME_SERVER) - self.swipe(self.S_U_END, interval=3) - self.switch_friend_list(SelectFriendList.DIFFERENT_SERVER) - self.switch_friend_list(SelectFriendList.SAME_SERVER) - else: # 跨区必须切换两次, 否则结界卡不刷新到头部 - self.switch_friend_list(SelectFriendList.DIFFERENT_SERVER) - self.swipe(self.S_U_END, interval=3) - self.switch_friend_list(SelectFriendList.SAME_SERVER) - self.switch_friend_list(SelectFriendList.DIFFERENT_SERVER) + self._reset_utilize_friend_list(friend) # --------------- 结界卡选择 --------------- - if not self._select_optimal_resource_card(): + if not self._select_optimal_resource_card(friend): return False # 找到卡,重置次数 @@ -370,12 +433,14 @@ def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, logger.warning('Cannot find enter realm button') # 可能是滑动的时候出错 logger.warning('The best reason is that the swipe is wrong') - return None + return self._record_utilize_failure('未识别到进入结界按钮') try: self.goto_page(page_friend_utilize) - except GamePageUnknownError: + except (GamePageUnknownError, GameStuckError) as error: logger.warning('Appear friend realm failed') - return None + return self._record_utilize_failure( + f'进入好友结界失败: {type(error).__name__}' + ) # 判断好友的有两个位置还是一个坑位 stop_image = None self.screenshot() @@ -389,69 +454,62 @@ def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, # 没有坑位可能是其他人的手速太快了抢占了 self.save_image(content='没有坑位了', wait_time=0, push_flag=False, image_type='png') logger.warning('没有坑位可能是其他人的手速太快了抢占了') - return True - # 切换式神的类型 - self.switch_shikigami_class(shikigami_class) - # 上式神 - self.set_shikigami(shikigami_order, stop_image) + return self._record_utilize_failure('目标结界已经没有可用坑位') + try: + # 切换式神的类型 + self.switch_shikigami_class(shikigami_class) + # 上式神 + self.set_shikigami(shikigami_order, stop_image) + except (GamePageUnknownError, GameStuckError) as error: + return self._record_utilize_failure( + f'式神寄养失败: {type(error).__name__}' + ) + self.utilize_failed_count = 0 return True - def _select_optimal_resource_card(self): - """整合后的智能选卡主逻辑(无嵌套函数版)""" - # 类常量声明(需在类中定义) - RESOURCE_PRESETS = { - '斗鱼': [151, 143, 134, 126, 101, 84], - '太鼓': [76, 76, 67, 67, 59, 50] - } - MAX_INDEX = 99 - - def get_resource_index(resource_name, current_value, preset_values): - """获取资源匹配的档位索引""" - for idx, val in enumerate(preset_values): - if current_value >= val: - logger.info(f'📊 {resource_name}区间匹配: {current_value} ≥ {val} (档位{idx})') - return idx - logger.warning(f'⚠️ {resource_name}值[{current_value}]低于所有预设') - return MAX_INDEX - - while True: - self.screenshot() - - # 第一阶段:初始记录获取 - if self.ap_max_num == 0 and self.jade_max_num == 0: - logger.hr('第一阶段:初始记录获取', 2) - if self._current_select_best(): - logger.info(f'✅ 完美结界卡确认成功,重置状态') - self.ap_max_num, self.jade_max_num = 0, 0 - return True - logger.info(f'📝 记录最佳值 | 斗鱼:{self.ap_max_num} 太鼓:{self.jade_max_num}') - return False + def _select_optimal_resource_card(self, friend: SelectFriendList): + """浏览完整个列表,选出最佳资源卡并重新定位到该卡。""" + self.ap_max_num, self.jade_max_num = 0, 0 + logger.hr('第一阶段:浏览列表并记录最佳奖励', 2) + self._current_select_best() + logger.info(f'📝 记录最佳值 | 斗鱼:{self.ap_max_num} 太鼓:{self.jade_max_num}') + try: logger.hr('第二阶段:资源优先级判断', 2) - # 获取双资源档位 - ap_index = get_resource_index('斗鱼', self.ap_max_num, RESOURCE_PRESETS['斗鱼']) - jade_index = get_resource_index('太鼓', self.jade_max_num, RESOURCE_PRESETS['太鼓']) - - # 双资源超限处理 - if ap_index == MAX_INDEX and jade_index == MAX_INDEX: - logger.warning('🔄 斗鱼和太鼓均低于预设,重置初始记录') - self.ap_max_num, self.jade_max_num = 0, 0 + rule = self.config.kekkai_utilize.utilize_config.utilize_rule + if rule == UtilizeRule.TAIKO and self.jade_max_num > 0: + res_type, target = '太鼓', self.jade_max_num + elif rule == UtilizeRule.FISH and self.ap_max_num > 0: + res_type, target = '斗鱼', self.ap_max_num + elif rule == UtilizeRule.DEFAULT and ( + self.ap_max_num > 0 or self.jade_max_num > 0 + ): + ap_as_jade = self.ap_max_num / 1.8 + logger.info( + f'⚖️ 默认换算 | 斗鱼:{self.ap_max_num}体力 ÷ 1.8 ' + f'= {ap_as_jade:.2f} | 太鼓:{self.jade_max_num}勾玉' + ) + if ap_as_jade >= self.jade_max_num: + res_type, target = '斗鱼', self.ap_max_num + else: + res_type, target = '太鼓', self.jade_max_num + else: + logger.warning('🔄 未找到可用的四星及以上太鼓或斗鱼') return False - # 决策优先级 - res_type, target = ('斗鱼', self.ap_max_num) if ap_index <= jade_index else ('太鼓', self.jade_max_num) logger.info(f'⚖️ 选择{res_type}卡 | 目标: {target}') - # 第三阶段:执行选卡操作 - logger.hr('第三阶段:执行选卡操作', 2) + # 浏览已经结束。回到列表顶部重新定位最佳卡,定位完成后由调用方 + # 直接点击右下角“进入结界”。 + logger.hr('第三阶段:重新定位最佳卡', 2) + self._reset_utilize_friend_list(friend) if self._current_select_best(res_type, target, selected_card=True): - logger.info(f'✅ {res_type}卡确认成功,重置状态') - self.ap_max_num, self.jade_max_num = 0, 0 + logger.info(f'✅ {res_type}卡定位成功,准备直接进入结界') return True - else: - logger.warning(f'❌ {res_type}卡确认失败,重置状态') - self.ap_max_num, self.jade_max_num = 0, 0 - return False + logger.warning(f'❌ {res_type}卡重新定位失败') + return False + finally: + self.ap_max_num, self.jade_max_num = 0, 0 def _current_select_best(self, best_card_type=None, best_card_num=0, selected_card=False): """结界卡选择核心逻辑(集成版) @@ -466,8 +524,8 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca """ # ============== 配置常量 ==============# RESOURCE_CONFIG = { - '斗鱼': {'max': 151, 'record_attr': 'ap_max_num'}, - '太鼓': {'max': 76, 'record_attr': 'jade_max_num'} + '斗鱼': {'record_attr': 'ap_max_num'}, + '太鼓': {'record_attr': 'jade_max_num'} } MAX_SWIPES = 20 # 最大滑动次数 CONSEC_MISS = 3 # 允许连续无卡次数 @@ -477,6 +535,7 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca logger.info(f'启动{"探索模式" if not selected_card else f"确认模式 | 目标: {best_card_type} @ {best_card_num}"}') timer = Timer(TIMEOUT).start() miss_count = 0 # 连续无卡计数器 + maxed_card_classes: set[CardClass] = set() # ============== 主滑动循环 ==============# for swipe_count in range(MAX_SWIPES + 1): @@ -508,7 +567,24 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca logger.info((f'第{swipe_count}次滑动' if swipe_count > 0 else '初始界面') + f' | 检测到结界卡:{cards_list}') # 遍历所有结界卡(已按位置排序) - for _, _, area in cards: + for target, _, area in cards: + card_class = target_to_card_class(target) + tier_info = self.CARD_TIER_INFO.get(card_class) + if tier_info: + self.utilize_found_eligible_card = True + + if not selected_card and card_class in maxed_card_classes: + card_type, star, tier_max = tier_info + logger.info( + f'⏭️ {star}星{card_type}已确认最高奖励{tier_max},' + '本轮不再点击同档卡片' + ) + continue + + # 确认阶段可依据模板先过滤另一资源类型,减少无效点击。 + if selected_card and tier_info and tier_info[0] != best_card_type: + continue + # 设置点击区域并获取结界卡详情 self.C_SELECT_CARD.roi_front = area self.click(self.C_SELECT_CARD) @@ -522,8 +598,16 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca logger.info(f'⏭️ 跳过无效卡: {card_type}@{card_value}') continue + if tier_info: + tier_type, star, tier_max = tier_info + if tier_type == card_type and card_value >= tier_max: + maxed_card_classes.add(card_class) + logger.info( + f'✅ {star}星{card_type}达到当前档位最高奖励: ' + f'{card_value}(标准{tier_max})' + ) + # ====== 模式分支处理 ======# - current_max = RESOURCE_CONFIG[card_type]['max'] record_attr = RESOURCE_CONFIG[card_type]['record_attr'] current_record = getattr(self, record_attr, 0) logger.info(f'🔍 识别卡片: {card_type} | 当前值: {card_value}, 最优值: {current_record}') @@ -539,13 +623,6 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca logger.info(f'🎉 确认蹭卡: {card_type} | 当前值: {card_value} ≥ 目标值: {best_card_num}') self.save_image(push_flag=False, wait_time=0, content=f'🎉 确认蹭卡({card_type}: {card_value})') return True - else: # 探索记录模式 - # 发现完美卡直接返回 - if card_value >= current_max: - message = f'🎉 完美蹭卡 | {card_type}: {card_value}' - logger.info(message) - self.save_image(push_flag=False, wait_time=0, content=message) - return True if self.appear(self.I_U_EMPTY_CARD): logger.info('Empty card already appeared, exit explore') From 174136a384f156d562a42acbcb685839f991d3c7 Mon Sep 17 00:00:00 2001 From: xylolit-mu Date: Sat, 22 Aug 2026 12:32:33 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=AF=84=E5=85=BB?= =?UTF-8?q?=E9=80=BB=E8=BE=91.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/i18n/zh-CN.json | 2 + tasks/KekkaiUtilize/script_task.py | 108 +++++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 13 deletions(-) diff --git a/assets/i18n/zh-CN.json b/assets/i18n/zh-CN.json index 57bcba6ba..342eae5bb 100644 --- a/assets/i18n/zh-CN.json +++ b/assets/i18n/zh-CN.json @@ -192,6 +192,8 @@ "utilize_harvest": "是否领取寄养奖励", "utilize_enable": "是否蹭卡", "utilize_enable_help": "关闭则不会蹭卡, 但是其他操作会执行(例如收取寮奖励等)", + "select_friend_list": "蹭卡好友优先范围", + "select_friend_list_help": "仅决定优先扫描同区或跨区;优先范围全是当前策略低价值卡时会自动检查另一区。", "utilize_harvest_help": "关闭则不领取, 可以屯奖励", "check_true_orochi_enable": "是否在结束后检测真蛇", "check_true_orochi_enable_help": "开启后会在任务结束后检测是否出现真蛇, 若出现则自动拉起真蛇", diff --git a/tasks/KekkaiUtilize/script_task.py b/tasks/KekkaiUtilize/script_task.py index f21fc3400..3dc25bfa8 100644 --- a/tasks/KekkaiUtilize/script_task.py +++ b/tasks/KekkaiUtilize/script_task.py @@ -32,6 +32,8 @@ class ScriptTask(GameUi, ReplaceShikigami, KekkaiUtilizeAssets): utilize_failed_count = 0 utilize_terminal_failure = False utilize_found_eligible_card = False + utilize_current_group_has_eligible_card = False + utilize_current_group_scan_completed = False ap_max_num = 0 jade_max_num = 0 @@ -51,6 +53,8 @@ def run(self): self.utilize_failed_count = 0 self.utilize_terminal_failure = False self.utilize_found_eligible_card = False + self.utilize_current_group_has_eligible_card = False + self.utilize_current_group_scan_completed = False self.ap_max_num = 0 self.jade_max_num = 0 # 进入寮结界 @@ -398,13 +402,35 @@ def _record_utilize_failure(self, reason: str) -> bool: self.set_next_run( task='KekkaiUtilize', finish=True, - success=False, server=False, target=datetime.now() + timedelta(minutes=10), ) self.utilize_terminal_failure = True return False + def _finish_low_value_utilize(self) -> bool: + """两个好友分组都没有当前策略的四星以上卡,失败并延迟任务。""" + rule = self.config.kekkai_utilize.utilize_config.utilize_rule + target_name = { + UtilizeRule.TAIKO: '太鼓', + UtilizeRule.FISH: '斗鱼', + UtilizeRule.DEFAULT: '太鼓或斗鱼', + }.get(rule, '目标结界卡') + message = ( + f'同区和跨区全是当前策略低价值卡,未检测到四星及以上{target_name},' + '任务失败,20分钟后重试' + ) + logger.error(message) + self.push_notify(content=message) + self.set_next_run( + task='KekkaiUtilize', + finish=True, + server=False, + target=datetime.now() + timedelta(minutes=20), + ) + self.utilize_terminal_failure = True + return False + def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, shikigami_class: ShikigamiClass = ShikigamiClass.N, shikigami_order: int = 7): @@ -417,12 +443,39 @@ def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, :return: """ logger.hr('Start utilize') - # 不管什么时候进来都要切换刷新列表(同区与跨区保持一致先切换滑动再切换) - self._reset_utilize_friend_list(friend) + fallback_friend = ( + SelectFriendList.DIFFERENT_SERVER + if friend == SelectFriendList.SAME_SERVER + else SelectFriendList.SAME_SERVER + ) + selected_friend = None + + # 配置项仅决定优先分组。优先分组没有当前策略可用的四星以上卡时, + # 才扫描另一个分组;一旦选中目标便立即停止,不再扫描另一区。 + for index, target_friend in enumerate((friend, fallback_friend), start=1): + priority_text = '优先' if index == 1 else '备选' + logger.hr(f'{priority_text}好友分组: {target_friend.value}', 2) + self._reset_utilize_friend_list(target_friend) + select_result = self._select_optimal_resource_card(target_friend) + if select_result is True: + selected_friend = target_friend + logger.info( + f'已在{priority_text}分组[{target_friend.value}]选中蹭卡目标,' + '不再检查其他分组' + ) + break + if select_result is False: + logger.warning( + f'分组[{target_friend.value}]存在可用卡,但目标重新定位失败,' + '本轮不切换分组' + ) + return False + logger.info( + f'分组[{target_friend.value}]没有当前策略可用的四星以上结界卡' + ) - # --------------- 结界卡选择 --------------- - if not self._select_optimal_resource_card(friend): - return False + if selected_friend is None: + return self._finish_low_value_utilize() # 找到卡,重置次数 self.utilize_add_count = 0 @@ -467,9 +520,17 @@ def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, self.utilize_failed_count = 0 return True - def _select_optimal_resource_card(self, friend: SelectFriendList): - """浏览完整个列表,选出最佳资源卡并重新定位到该卡。""" + def _select_optimal_resource_card( + self, friend: SelectFriendList + ) -> bool | None: + """浏览并定位当前分组的最佳卡。 + + :return: True=已定位;None=当前策略没有四星以上卡; + False=检测到可用卡但识别或重新定位失败。 + """ self.ap_max_num, self.jade_max_num = 0, 0 + self.utilize_current_group_has_eligible_card = False + self.utilize_current_group_scan_completed = False logger.hr('第一阶段:浏览列表并记录最佳奖励', 2) self._current_select_best() logger.info(f'📝 记录最佳值 | 斗鱼:{self.ap_max_num} 太鼓:{self.jade_max_num}') @@ -494,8 +555,14 @@ def _select_optimal_resource_card(self, friend: SelectFriendList): else: res_type, target = '太鼓', self.jade_max_num else: - logger.warning('🔄 未找到可用的四星及以上太鼓或斗鱼') - return False + if self.utilize_current_group_has_eligible_card: + logger.warning('🔄 检测到四星以上目标,但奖励数值识别失败') + return False + if not self.utilize_current_group_scan_completed: + logger.warning('当前好友分组未能完整扫描,不能判定为全是低价值卡') + return False + logger.info('当前分组全是当前策略低价值结界卡') + return None logger.info(f'⚖️ 选择{res_type}卡 | 目标: {target}') @@ -552,9 +619,14 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca if not cards: miss_count += 1 logger.info(f'第{swipe_count}次滑动 | 未检测到结界卡' if swipe_count > 0 else '初始界面 | 未检测到结界卡') - # 连续无卡超过阈值则终止/已经出现空卡也不再滑动 + # 游戏会优先排列有结界卡的好友,连续未命中或出现空卡时 + # 即可视为当前分组已扫描完成。 if miss_count > CONSEC_MISS or self.appear(self.I_U_EMPTY_CARD): - logger.warning(f'⚠️ 连续{miss_count}次 | 未检测到结界卡, 终止流程') + logger.warning( + f'⚠️ 连续{miss_count}次未检测到目标结界卡,终止当前分组扫描' + ) + if not selected_card: + self.utilize_current_group_scan_completed = True return None # 执行滑动操作 self.perform_swipe_action() @@ -572,6 +644,8 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca tier_info = self.CARD_TIER_INFO.get(card_class) if tier_info: self.utilize_found_eligible_card = True + if not selected_card: + self.utilize_current_group_has_eligible_card = True if not selected_card and card_class in maxed_card_classes: card_type, star, tier_max = tier_info @@ -626,12 +700,20 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca if self.appear(self.I_U_EMPTY_CARD): logger.info('Empty card already appeared, exit explore') + if not selected_card: + self.utilize_current_group_scan_completed = True return None # ------ 步骤3: 滑动到下一屏 ------# self.perform_swipe_action() # ============== 终止处理 ==============# - logger.warning(f'⚠️ 已达到最大滑动次数{MAX_SWIPES}, 终止流程') + if not selected_card: + self.utilize_current_group_scan_completed = True + logger.info( + f'已按最大滑动次数{MAX_SWIPES}完成当前分组的有界全量扫描' + ) + else: + logger.warning(f'⚠️ 已达到最大滑动次数{MAX_SWIPES}, 目标定位失败') return None def perform_swipe_action(self): From 7041b75b4cd06ff04d97127c7ce760f878ef7387 Mon Sep 17 00:00:00 2001 From: xylolit-mu Date: Tue, 25 Aug 2026 15:15:35 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=AF=84=E5=85=BB?= =?UTF-8?q?=E9=80=BB=E8=BE=91.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tasks/KekkaiUtilize/script_task.py | 164 +++++++++++++---------------- 1 file changed, 76 insertions(+), 88 deletions(-) diff --git a/tasks/KekkaiUtilize/script_task.py b/tasks/KekkaiUtilize/script_task.py index 3dc25bfa8..9a7a2dfb4 100644 --- a/tasks/KekkaiUtilize/script_task.py +++ b/tasks/KekkaiUtilize/script_task.py @@ -456,7 +456,7 @@ def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, priority_text = '优先' if index == 1 else '备选' logger.hr(f'{priority_text}好友分组: {target_friend.value}', 2) self._reset_utilize_friend_list(target_friend) - select_result = self._select_optimal_resource_card(target_friend) + select_result = self._select_optimal_resource_card() if select_result is True: selected_friend = target_friend logger.info( @@ -466,7 +466,7 @@ def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, break if select_result is False: logger.warning( - f'分组[{target_friend.value}]存在可用卡,但目标重新定位失败,' + f'分组[{target_friend.value}]存在可用卡,但第一阶段选卡失败,' '本轮不切换分组' ) return False @@ -520,75 +520,52 @@ def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, self.utilize_failed_count = 0 return True - def _select_optimal_resource_card( - self, friend: SelectFriendList - ) -> bool | None: - """浏览并定位当前分组的最佳卡。 + def _select_optimal_resource_card(self) -> bool | None: + """浏览当前分组,并直接保留第一阶段选中的最佳卡。 - :return: True=已定位;None=当前策略没有四星以上卡; - False=检测到可用卡但识别或重新定位失败。 + 浏览完成后不再重新计算目标或复位列表定位;当前界面保留的 + 最优卡直接供调用方点击“进入结界”。 + + :return: True=第一阶段已留下可用目标; + None=当前策略没有四星以上卡; + False=检测到可用卡但奖励识别或列表扫描失败。 """ self.ap_max_num, self.jade_max_num = 0, 0 self.utilize_current_group_has_eligible_card = False self.utilize_current_group_scan_completed = False - logger.hr('第一阶段:浏览列表并记录最佳奖励', 2) - self._current_select_best() - logger.info(f'📝 记录最佳值 | 斗鱼:{self.ap_max_num} 太鼓:{self.jade_max_num}') - try: - logger.hr('第二阶段:资源优先级判断', 2) + logger.hr('浏览列表并保留最优结界卡', 2) + self._current_select_best() + logger.info( + f'📝 第一阶段浏览完成 | ' + f'斗鱼:{self.ap_max_num} 太鼓:{self.jade_max_num}' + ) + rule = self.config.kekkai_utilize.utilize_config.utilize_rule - if rule == UtilizeRule.TAIKO and self.jade_max_num > 0: - res_type, target = '太鼓', self.jade_max_num - elif rule == UtilizeRule.FISH and self.ap_max_num > 0: - res_type, target = '斗鱼', self.ap_max_num - elif rule == UtilizeRule.DEFAULT and ( + has_selected_card = ( + rule == UtilizeRule.TAIKO and self.jade_max_num > 0 + or rule == UtilizeRule.FISH and self.ap_max_num > 0 + or rule == UtilizeRule.DEFAULT and ( self.ap_max_num > 0 or self.jade_max_num > 0 - ): - ap_as_jade = self.ap_max_num / 1.8 - logger.info( - f'⚖️ 默认换算 | 斗鱼:{self.ap_max_num}体力 ÷ 1.8 ' - f'= {ap_as_jade:.2f} | 太鼓:{self.jade_max_num}勾玉' ) - if ap_as_jade >= self.jade_max_num: - res_type, target = '斗鱼', self.ap_max_num - else: - res_type, target = '太鼓', self.jade_max_num - else: - if self.utilize_current_group_has_eligible_card: - logger.warning('🔄 检测到四星以上目标,但奖励数值识别失败') - return False - if not self.utilize_current_group_scan_completed: - logger.warning('当前好友分组未能完整扫描,不能判定为全是低价值卡') - return False - logger.info('当前分组全是当前策略低价值结界卡') - return None - - logger.info(f'⚖️ 选择{res_type}卡 | 目标: {target}') - - # 浏览已经结束。回到列表顶部重新定位最佳卡,定位完成后由调用方 - # 直接点击右下角“进入结界”。 - logger.hr('第三阶段:重新定位最佳卡', 2) - self._reset_utilize_friend_list(friend) - if self._current_select_best(res_type, target, selected_card=True): - logger.info(f'✅ {res_type}卡定位成功,准备直接进入结界') + ) + if has_selected_card: + logger.info('✅ 第一阶段已保留最优选项,准备直接进入结界') return True - logger.warning(f'❌ {res_type}卡重新定位失败') - return False + + if self.utilize_current_group_has_eligible_card: + logger.warning('🔄 检测到四星以上目标,但奖励数值识别失败') + return False + if not self.utilize_current_group_scan_completed: + logger.warning('当前好友分组未能完整扫描,不能判定为全是低价值卡') + return False + logger.info('当前分组全是当前策略低价值结界卡') + return None finally: self.ap_max_num, self.jade_max_num = 0, 0 - def _current_select_best(self, best_card_type=None, best_card_num=0, selected_card=False): - """结界卡选择核心逻辑(集成版) - 功能:滑动屏幕寻找最优资源卡,支持两种模式: - - 探索模式:记录当前遇到的最佳结界卡数值 - - 确认模式:根据给定条件选择指定类型结界卡 - - :param best_card_type: 目标卡类型('太鼓'/'斗鱼') - :param best_card_num: 要求的最低数值 - :param selected_card: 是否处于确认选择模式 - :return: 找到符合条件返回True,否则None - """ + def _current_select_best(self): + """浏览好友列表、记录奖励,并保留当前选中的最佳结界卡。""" # ============== 配置常量 ==============# RESOURCE_CONFIG = { '斗鱼': {'record_attr': 'ap_max_num'}, @@ -599,10 +576,14 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca TIMEOUT = 120 # 操作超时(秒) # ============== 初始化阶段 ==============# - logger.info(f'启动{"探索模式" if not selected_card else f"确认模式 | 目标: {best_card_type} @ {best_card_num}"}') + logger.info('启动结界卡浏览选择') timer = Timer(TIMEOUT).start() miss_count = 0 # 连续无卡计数器 maxed_card_classes: set[CardClass] = set() + # 每种资源已经实际打开并确认过奖励的最高星级。更低星级的 + # 理论上限不会超过更高星级,因此后续无需再打开确认;同星级 + # 仍由 maxed_card_classes 判断是否已经达到该档最高奖励。 + confirmed_highest_stars: dict[str, int] = {} # ============== 主滑动循环 ==============# for swipe_count in range(MAX_SWIPES + 1): @@ -625,8 +606,7 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca logger.warning( f'⚠️ 连续{miss_count}次未检测到目标结界卡,终止当前分组扫描' ) - if not selected_card: - self.utilize_current_group_scan_completed = True + self.utilize_current_group_scan_completed = True return None # 执行滑动操作 self.perform_swipe_action() @@ -644,10 +624,9 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca tier_info = self.CARD_TIER_INFO.get(card_class) if tier_info: self.utilize_found_eligible_card = True - if not selected_card: - self.utilize_current_group_has_eligible_card = True + self.utilize_current_group_has_eligible_card = True - if not selected_card and card_class in maxed_card_classes: + if card_class in maxed_card_classes: card_type, star, tier_max = tier_info logger.info( f'⏭️ {star}星{card_type}已确认最高奖励{tier_max},' @@ -655,9 +634,18 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca ) continue - # 确认阶段可依据模板先过滤另一资源类型,减少无效点击。 - if selected_card and tier_info and tier_info[0] != best_card_type: - continue + if tier_info: + card_type, star, _ = tier_info + confirmed_star = confirmed_highest_stars.get( + card_type, + 0, + ) + if star < confirmed_star: + logger.info( + f'⏭️ 已确认{confirmed_star}星{card_type}奖励,' + f'不再向下确认{star}星{card_type}' + ) + continue # 设置点击区域并获取结界卡详情 self.C_SELECT_CARD.roi_front = area @@ -674,12 +662,23 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca if tier_info: tier_type, star, tier_max = tier_info - if tier_type == card_type and card_value >= tier_max: - maxed_card_classes.add(card_class) - logger.info( - f'✅ {star}星{card_type}达到当前档位最高奖励: ' - f'{card_value}(标准{tier_max})' + if tier_type == card_type: + previous_star = confirmed_highest_stars.get( + card_type, + 0, ) + if star > previous_star: + confirmed_highest_stars[card_type] = star + logger.info( + f'✅ 已确认{star}星{card_type}奖励,' + f'后续跳过{star}星以下同类型卡片' + ) + if card_value >= tier_max: + maxed_card_classes.add(card_class) + logger.info( + f'✅ {star}星{card_type}达到当前档位最高奖励: ' + f'{card_value}(标准{tier_max})' + ) # ====== 模式分支处理 ======# record_attr = RESOURCE_CONFIG[card_type]['record_attr'] @@ -691,29 +690,18 @@ def _current_select_best(self, best_card_type=None, best_card_num=0, selected_ca logger.info(f'📈 更新记录: {card_type} | {current_record} → {card_value}') setattr(self, record_attr, card_value) - if selected_card: # 确认选择模式 - # 检查是否符合选择条件 - if (card_type == best_card_type) and (card_value >= best_card_num): - logger.info(f'🎉 确认蹭卡: {card_type} | 当前值: {card_value} ≥ 目标值: {best_card_num}') - self.save_image(push_flag=False, wait_time=0, content=f'🎉 确认蹭卡({card_type}: {card_value})') - return True - if self.appear(self.I_U_EMPTY_CARD): logger.info('Empty card already appeared, exit explore') - if not selected_card: - self.utilize_current_group_scan_completed = True + self.utilize_current_group_scan_completed = True return None # ------ 步骤3: 滑动到下一屏 ------# self.perform_swipe_action() # ============== 终止处理 ==============# - if not selected_card: - self.utilize_current_group_scan_completed = True - logger.info( - f'已按最大滑动次数{MAX_SWIPES}完成当前分组的有界全量扫描' - ) - else: - logger.warning(f'⚠️ 已达到最大滑动次数{MAX_SWIPES}, 目标定位失败') + self.utilize_current_group_scan_completed = True + logger.info( + f'已按最大滑动次数{MAX_SWIPES}完成当前分组的有界全量扫描' + ) return None def perform_swipe_action(self): From 792fa357003ab90a7d0072d98e66de5f251ea83e Mon Sep 17 00:00:00 2001 From: xylolit-mu Date: Thu, 27 Aug 2026 09:26:21 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix(KekkaiUtilize):=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=80=A0=E6=83=B0=E9=98=B2=E6=A3=80=E6=B5=8B=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/i18n/zh-CN.json | 4 + module/config/i18n/zh_CN.xml | 4 + tasks/KekkaiUtilize/config.py | 7 ++ tasks/KekkaiUtilize/script_task.py | 124 ++++++++++++++++++++++++++++- 4 files changed, 137 insertions(+), 2 deletions(-) diff --git a/assets/i18n/zh-CN.json b/assets/i18n/zh-CN.json index 342eae5bb..ab13aff6e 100644 --- a/assets/i18n/zh-CN.json +++ b/assets/i18n/zh-CN.json @@ -192,6 +192,10 @@ "utilize_harvest": "是否领取寄养奖励", "utilize_enable": "是否蹭卡", "utilize_enable_help": "关闭则不会蹭卡, 但是其他操作会执行(例如收取寮奖励等)", + "lazy_mode": "怠惰模式", + "lazy_mode_help": "开启后每次任务按权重随机进入怠惰模式;命中时不比较奖励数值,优先选择当前可见的五星及以上目标卡,否则选择首个符合策略的四星卡。", + "lazy_mode_weight": "怠惰模式权重", + "lazy_mode_weight_help": "取值范围0-1,默认1;0表示永不触发,1表示每次触发。", "select_friend_list": "蹭卡好友优先范围", "select_friend_list_help": "仅决定优先扫描同区或跨区;优先范围全是当前策略低价值卡时会自动检查另一区。", "utilize_harvest_help": "关闭则不领取, 可以屯奖励", diff --git a/module/config/i18n/zh_CN.xml b/module/config/i18n/zh_CN.xml index 4a8bd3f79..7c47cb0ff 100644 --- a/module/config/i18n/zh_CN.xml +++ b/module/config/i18n/zh_CN.xml @@ -503,6 +503,10 @@ mode_3策略是刷到五个契灵后开始结契,选择高级、中级盘来 Guild Assets Enable顺路收取寮资金 Box Ap Enable顺路收取体力盒子 Box Exp Enable顺路收取经验盒子 + Lazy Mode怠惰模式 + lazy_mode_help开启后每次任务按权重随机进入怠惰模式;命中时不比较奖励数值,优先选择当前可见的五星及以上目标卡,否则选择首个符合策略的四星卡。 + Lazy Mode Weight怠惰模式权重 + lazy_mode_weight_help取值范围0-1,默认1;0表示永不触发,1表示每次触发。 utilize_rule_help挑选结界卡的规则,使用默认default即可,具体规则看文档[任务列表],不可以选auto,已经弃置了。 auto_switch_sort_help会自动寻找最优的坑位 shikigami_class_help选择的式神类别(寄养默认选择N卡,且不建议选别的) diff --git a/tasks/KekkaiUtilize/config.py b/tasks/KekkaiUtilize/config.py index 6529717ec..915a5e60e 100644 --- a/tasks/KekkaiUtilize/config.py +++ b/tasks/KekkaiUtilize/config.py @@ -35,6 +35,13 @@ class UtilizeConfig(BaseModel): harvest_guild_max_times: int = Field(default=2, description='harvest_guild_max_times_help') utilize_harvest: bool = Field(default=True, description='utilize_harvest_help') utilize_enable: bool = Field(default=True, description='utilize_enable_help') + lazy_mode: bool = Field(default=False, description='lazy_mode_help') + lazy_mode_weight: float = Field( + default=1.0, + ge=0, + le=1, + description='lazy_mode_weight_help', + ) box_ap_enable: bool = Field(default=True) box_exp_enable: bool = Field(default=True) box_exp_waste: bool = Field(default=True, description='box_exp_waste_help') diff --git a/tasks/KekkaiUtilize/script_task.py b/tasks/KekkaiUtilize/script_task.py index 9a7a2dfb4..f615f829e 100644 --- a/tasks/KekkaiUtilize/script_task.py +++ b/tasks/KekkaiUtilize/script_task.py @@ -34,6 +34,7 @@ class ScriptTask(GameUi, ReplaceShikigami, KekkaiUtilizeAssets): utilize_found_eligible_card = False utilize_current_group_has_eligible_card = False utilize_current_group_scan_completed = False + utilize_lazy_mode_active = False ap_max_num = 0 jade_max_num = 0 @@ -44,7 +45,7 @@ class ScriptTask(GameUi, ReplaceShikigami, KekkaiUtilizeAssets): CardClass.TAIKO6: ('太鼓', 6, 76), CardClass.FISH4: ('斗鱼', 4, 118), CardClass.FISH5: ('斗鱼', 5, 134), - CardClass.FISH6: ('斗鱼', 6, 152), + CardClass.FISH6: ('斗鱼', 6, 151), } def run(self): @@ -55,8 +56,20 @@ def run(self): self.utilize_found_eligible_card = False self.utilize_current_group_has_eligible_card = False self.utilize_current_group_scan_completed = False + self.utilize_lazy_mode_active = False self.ap_max_num = 0 self.jade_max_num = 0 + if con.utilize_enable and con.lazy_mode: + lazy_roll = random.random() + self.utilize_lazy_mode_active = ( + lazy_roll < con.lazy_mode_weight + ) + logger.info( + '怠惰模式随机判定: ' + f'roll={lazy_roll:.4f}, ' + f'weight={con.lazy_mode_weight:.4f}, ' + f'active={self.utilize_lazy_mode_active}' + ) # 进入寮结界 self.goto_page(page_guild_realm) # 育成界面去蹭卡 @@ -354,6 +367,15 @@ def order_targets(self) -> ImageGrid: logger.error('Unknown utilize rule') raise ValueError('Unknown utilize rule') + @cached_property + def lazy_scan_targets(self) -> ImageGrid: + """怠惰模式用于浏览位置的全部四星以上资源卡。""" + return ImageGrid([ + self.I_U_FISH_6, self.I_U_TAIKO_6, + self.I_U_FISH_5, self.I_U_TAIKO_5, + self.I_U_FISH_4, self.I_U_TAIKO_4, + ]) + @cached_property def order_cards(self) -> list[CardClass]: rule = self.config.kekkai_utilize.utilize_config.utilize_rule @@ -456,7 +478,10 @@ def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, priority_text = '优先' if index == 1 else '备选' logger.hr(f'{priority_text}好友分组: {target_friend.value}', 2) self._reset_utilize_friend_list(target_friend) - select_result = self._select_optimal_resource_card() + if self.utilize_lazy_mode_active: + select_result = self._select_lazy_resource_card() + else: + select_result = self._select_optimal_resource_card() if select_result is True: selected_friend = target_friend logger.info( @@ -520,6 +545,101 @@ def run_utilize(self, friend: SelectFriendList = SelectFriendList.SAME_SERVER, self.utilize_failed_count = 0 return True + def _lazy_card_matches_rule(self, card_class: CardClass) -> bool: + """判断一张四星以上资源卡是否符合当前怠惰策略。""" + tier_info = self.CARD_TIER_INFO.get(card_class) + if not tier_info: + return False + card_type, _, _ = tier_info + rule = self.config.kekkai_utilize.utilize_config.utilize_rule + if rule == UtilizeRule.TAIKO: + return card_type == '太鼓' + if rule == UtilizeRule.FISH: + return card_type == '斗鱼' + if rule == UtilizeRule.DEFAULT: + return card_type in ('太鼓', '斗鱼') + logger.error('Unknown utilize rule') + raise ValueError('Unknown utilize rule') + + def _select_lazy_resource_card(self) -> bool | None: + """怠惰模式:选当前可见的首张高星卡,否则首张四星卡。""" + max_swipes = 20 + consecutive_miss_limit = 3 + timeout = Timer(120).start() + miss_count = 0 + self.utilize_current_group_has_eligible_card = False + self.utilize_current_group_scan_completed = False + + logger.hr('怠惰模式快速选择结界卡', 2) + for swipe_count in range(max_swipes + 1): + if timeout.reached(): + logger.warning('怠惰模式扫描超时,不能确认当前分组无可用卡') + return False + + self.screenshot() + cards = self.lazy_scan_targets.find_everyone( + self.device.image, + frame_id=self.device.image_frame_id, + ) + eligible_cards = [] + if cards: + eligible_cards = [ + card for card in cards + if self._lazy_card_matches_rule( + target_to_card_class(card[0]) + ) + ] + + if eligible_cards: + self.utilize_found_eligible_card = True + self.utilize_current_group_has_eligible_card = True + high_star_cards = [ + card for card in eligible_cards + if self.CARD_TIER_INFO[ + target_to_card_class(card[0]) + ][1] >= 5 + ] + target, _, area = ( + high_star_cards[0] + if high_star_cards + else eligible_cards[0] + ) + card_class = target_to_card_class(target) + card_type, star, _ = self.CARD_TIER_INFO[card_class] + self.C_SELECT_CARD.roi_front = area + self.click(self.C_SELECT_CARD) + time.sleep(2) + logger.info( + f'怠惰模式已选择首个符合策略的{star}星{card_type}: ' + f'swipe={swipe_count}, area={area}' + ) + return True + + # 当前屏即使只有另一策略的四星以上资源卡,也说明仍位于 + # 有效卡区域,需要继续向下寻找,不能计入连续空屏。 + miss_count = 0 if cards else miss_count + 1 + logger.info( + f'怠惰模式第{swipe_count}屏未发现当前策略四星以上结界卡' + ) + if self.appear(self.I_U_EMPTY_CARD): + logger.info('怠惰模式已到达好友列表空卡区域') + self.utilize_current_group_scan_completed = True + return None + if miss_count > consecutive_miss_limit: + logger.info( + f'怠惰模式连续{miss_count}屏没有四星以上资源卡,' + '结束当前分组扫描' + ) + self.utilize_current_group_scan_completed = True + return None + self.perform_swipe_action() + + self.utilize_current_group_scan_completed = True + logger.info( + f'怠惰模式已按最大滑动次数{max_swipes}完成当前分组扫描' + ) + return None + def _select_optimal_resource_card(self) -> bool | None: """浏览当前分组,并直接保留第一阶段选中的最佳卡。