From 72b4ac8ac805761145eb6c576e1f004444f50a9b Mon Sep 17 00:00:00 2001 From: longer-sausage Date: Sun, 5 Jul 2026 19:00:13 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E8=B0=83=E5=BA=A6=E9=AB=98=E4=BC=98=E5=85=88=E7=BA=A7=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- module/os/map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/os/map.py b/module/os/map.py index 7f4f50bc5..1018a1e4b 100644 --- a/module/os/map.py +++ b/module/os/map.py @@ -998,7 +998,7 @@ def false_func(*args, **kwargs): self.interrupt_auto_search() elif ( strategic - and not getattr(self, 'is_running_smart_scheduling_task', lambda: False)() + and not getattr(self.config, '_disable_task_switch', False) and self.config.task_switched() ): if self.config.task.command == "OpsiMeowfficerFarming": From 097950c3508503f28951801432da79cbc35b2e12 Mon Sep 17 00:00:00 2001 From: longer-sausage Date: Sun, 5 Jul 2026 23:27:11 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E8=B0=83=E5=BA=A6=E8=A1=A5=E9=BB=84=E5=B8=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- module/os/tasks/scheduling.py | 164 ++++++++++++++++++++++++++++++---- 1 file changed, 145 insertions(+), 19 deletions(-) diff --git a/module/os/tasks/scheduling.py b/module/os/tasks/scheduling.py index b6a9a7fba..39a1a33a7 100644 --- a/module/os/tasks/scheduling.py +++ b/module/os/tasks/scheduling.py @@ -16,6 +16,8 @@ 配置项: - Scheduler.Enable: 任务启用开关(启用此任务即启用智能调度功能) - OperationCoinsPreserve: 智能调度时侵蚀1保留的黄币阀值(优先级高于原配置) + - UseSmartSchedulingOperationCoinsPreserve: 开启时使用黄币目标调度,关闭时使用体力调度 + - OperationCoinsReturnThreshold: 黄币目标调度回到侵蚀1前需要高于保留值的缓冲数量 - ActionPointPreserve: 智能调度时保留的行动力阀值(同时作用于所有任务) - ActionPointNotifyLevels: 行动力阈值列表,用于推送通知 此模块包含: @@ -65,6 +67,7 @@ class OpsiMeowfficerFarming(CoinTaskMixin, OSMap): CONFIG_PATH_USE_SMART_CL1_PRESERVE = 'OpsiScheduling.OpsiScheduling.UseSmartSchedulingOperationCoinsPreserve' CONFIG_PATH_SMART_CL1_PRESERVE = 'OpsiScheduling.OpsiScheduling.OperationCoinsPreserve' CONFIG_PATH_SMART_AP_PRESERVE = 'OpsiScheduling.OpsiScheduling.ActionPointPreserve' + CONFIG_PATH_SMART_COIN_RETURN_THRESHOLD = 'OpsiScheduling.OpsiScheduling.OperationCoinsReturnThreshold' # 各任务的配置路径常量(集中管理,避免硬编码) CONFIG_PATH_MEOW_AP_PRESERVE = 'OpsiMeowfficerFarming.OpsiMeowfficerFarming.ActionPointPreserve' CONFIG_PATH_CL1_MIN_AP_RESERVE = 'OpsiHazard1Leveling.OpsiHazard1Leveling.MinimumActionPointReserve' @@ -332,9 +335,7 @@ def _get_smart_scheduling_operation_coins_preserve(self): int: 保留的黄币数量 """ # 检查是否启用智能调度黄币保留配置 - use_smart_preserve = self._config_enabled( - keys=self.CONFIG_PATH_USE_SMART_CL1_PRESERVE - ) + use_smart_preserve = self._is_coin_target_scheduling_enabled() if not use_smart_preserve: # 开关未开启,回退到侵蚀1原配置 @@ -344,7 +345,7 @@ def _get_smart_scheduling_operation_coins_preserve(self): # 保证返回 int 以免后续比较报错 if cl1_preserve_original is None: cl1_preserve_original = 0 - logger.info(f'【智能调度】黄币保留使用原配置: {cl1_preserve_original} (智能调度开关未启用)') + logger.info(f'【智能调度】黄币保留使用原配置: {cl1_preserve_original} (黄币目标调度未启用)') return cl1_preserve_original else: # 开关开启,使用智能调度自己的配置,允许为 0 @@ -372,6 +373,12 @@ def _get_smart_scheduling_action_point_preserve(self): ) return preserve or 0 + def _is_coin_target_scheduling_enabled(self): + """判断是否启用黄币目标调度。关闭时使用体力调度。""" + return self._config_enabled( + keys=self.CONFIG_PATH_USE_SMART_CL1_PRESERVE + ) + def _get_coin_task_action_point_preserve(self): """获取智能调度用于启动黄币补充任务的行动力阈值。""" smart_ap_preserve = self._get_smart_scheduling_action_point_preserve() @@ -381,6 +388,76 @@ def _get_coin_task_action_point_preserve(self): keys=self.CONFIG_PATH_MEOW_AP_PRESERVE ) or 1000 + def _get_smart_scheduling_operation_coins_return_threshold(self): + """ + 获取智能调度补黄币阶段的回补增量。 + + 进入补黄币阶段后,黄币需要达到“侵蚀 1 保留值 + 此阈值”,才允许回到侵蚀 1。 + """ + threshold = self.config.cross_get( + keys=self.CONFIG_PATH_SMART_COIN_RETURN_THRESHOLD, + default=0, + ) + try: + threshold = int(threshold or 0) + except (TypeError, ValueError): + logger.warning(f'智能调度黄币回补阈值无效: {threshold},使用 0') + threshold = 0 + return max(threshold, 0) + + def _get_coin_replenish_target(self, yellow_coins, cl1_preserve): + """ + 获取本轮补黄币目标值。 + + 目标值与模拟器保持一致:侵蚀 1 保留值 + 回补阈值。 + """ + start_coins = getattr( + self, + '_smart_scheduling_coin_replenish_start', + getattr(self.config, '_smart_scheduling_coin_replenish_start', None), + ) + if start_coins is None or yellow_coins < start_coins: + start_coins = yellow_coins + self._smart_scheduling_coin_replenish_start = start_coins + self.config._smart_scheduling_coin_replenish_start = start_coins + + return_threshold = self._get_smart_scheduling_operation_coins_return_threshold() + target = cl1_preserve + return_threshold + return target, start_coins, return_threshold + + def _clear_coin_replenish_target(self): + """清理本轮补黄币状态。""" + if hasattr(self, '_smart_scheduling_coin_replenish_start'): + delattr(self, '_smart_scheduling_coin_replenish_start') + if hasattr(self.config, '_smart_scheduling_coin_replenish_start'): + delattr(self.config, '_smart_scheduling_coin_replenish_start') + + def _is_coin_replenish_active(self): + """判断当前是否处于补黄币阶段。""" + return bool( + hasattr(self, '_smart_scheduling_coin_replenish_start') + or hasattr(self.config, '_smart_scheduling_coin_replenish_start') + ) + + def _set_ap_replenish_active(self): + """标记体力调度补黄币阶段已开始。""" + self._smart_scheduling_ap_replenish_active = True + self.config._smart_scheduling_ap_replenish_active = True + + def _clear_ap_replenish_active(self): + """清理体力调度补黄币状态。""" + if hasattr(self, '_smart_scheduling_ap_replenish_active'): + delattr(self, '_smart_scheduling_ap_replenish_active') + if hasattr(self.config, '_smart_scheduling_ap_replenish_active'): + delattr(self.config, '_smart_scheduling_ap_replenish_active') + + def _is_ap_replenish_active(self): + """判断当前是否处于体力调度补黄币阶段。""" + return bool( + hasattr(self, '_smart_scheduling_ap_replenish_active') + or hasattr(self.config, '_smart_scheduling_ap_replenish_active') + ) + def _get_effective_cl1_ap_preserve(self): """ 获取智能调度下侵蚀 1 使用的行动力保留值。 @@ -583,7 +660,7 @@ def _run_scheduled_meowfficer_farming(self, ap_preserve): ap_preserve=ap_preserve, ) - def _handle_smart_scheduling_no_task(self, yellow_coins, total_ap, current_ap, preserve, meow_ap_preserve): + def _handle_smart_scheduling_no_task(self, yellow_coins, total_ap, current_ap, coin_target, meow_ap_preserve): """ 处理黄币和行动力不足导致没有可运行任务的情况。 @@ -595,10 +672,14 @@ def _handle_smart_scheduling_no_task(self, yellow_coins, total_ap, current_ap, p f'防止行动力溢出上下文:黄币不足且总行动力未达补黄币保留,' f'执行短猫清理当前行动力 (当前={current_ap}, 总行动力={total_ap})' ) + if yellow_coins < coin_target: + coin_status = f'黄币 {yellow_coins} 低于补黄币目标 {coin_target}' + else: + coin_status = f'黄币 {yellow_coins} 已达到补黄币阈值 {coin_target}' self.notify_push( title='[AzurPilot] 防止行动力溢出 - 执行短猫', content=( - f'黄币 {yellow_coins} 低于保留值 {preserve}\n' + f'{coin_status}\n' f'总行动力 {total_ap} 低于补黄币保留 {meow_ap_preserve}\n' f'由 OpsiScheduling 直接执行短猫清理当前行动力 {current_ap}' ) @@ -606,7 +687,7 @@ def _handle_smart_scheduling_no_task(self, yellow_coins, total_ap, current_ap, p self._run_scheduled_meowfficer_farming(0) return - self._notify_coins_ap_insufficient(yellow_coins, total_ap, preserve, meow_ap_preserve) + self._notify_coins_ap_insufficient(yellow_coins, total_ap, coin_target, meow_ap_preserve) self._delay_smart_scheduling_for_ap_limit(total_ap, meow_ap_preserve) def _run_scheduled_hazard1_leveling(self, ap_preserve): @@ -669,7 +750,7 @@ def _delay_smart_scheduling_for_ap_limit(self, total_ap, min_ap_reserve): """ 因行动力不足推迟智能调度。 """ - logger.warning(f'行动力低于最低保留 ({total_ap} < {min_ap_reserve})') + logger.warning(f'行动力达到最低保留 ({total_ap} <= {min_ap_reserve})') self._notify_ap_insufficient(total_ap, min_ap_reserve) logger.info('行动力不足,智能调度延迟到下次服务器刷新') self.config.task_delay(server_update=True, task=self.TASK_NAME_SCHEDULING) @@ -682,6 +763,9 @@ def run_smart_scheduling_once(self): cl1_preserve = self._get_smart_scheduling_operation_coins_preserve() cl1_ap_preserve = self._get_effective_cl1_ap_preserve() meow_ap_preserve = self._get_coin_task_action_point_preserve() + coin_target_scheduling = self._is_coin_target_scheduling_enabled() + coin_replenish_active = self._is_coin_replenish_active() + ap_replenish_active = self._is_ap_replenish_active() logger.info(f'【智能调度检查】黄币: {yellow_coins}, 保留值: {cl1_preserve}') if self.is_running_prevent_action_point_overflow_task(): @@ -696,10 +780,49 @@ def run_smart_scheduling_once(self): ) try: - if yellow_coins < cl1_preserve: - logger.info(f'黄币不足 ({yellow_coins} < {cl1_preserve}),需要执行黄币补充任务') - if total_ap < meow_ap_preserve: - logger.warning(f'行动力不足以执行短猫 ({total_ap} < {meow_ap_preserve})') + if coin_target_scheduling and (yellow_coins < cl1_preserve or coin_replenish_active): + coin_target, start_coins, return_threshold = self._get_coin_replenish_target( + yellow_coins, + cl1_preserve, + ) + logger.info( + f'【智能调度检查】补黄币目标: 当前={yellow_coins}, 起始={start_coins}, ' + f'回补阈值={return_threshold}, 目标={coin_target}' + ) + if yellow_coins >= coin_target: + logger.info(f'黄币已补足 ({yellow_coins} >= {coin_target}),恢复侵蚀1练级') + self._clear_coin_replenish_target() + else: + logger.info(f'黄币未补足 ({yellow_coins} < {coin_target}),需要执行黄币补充任务') + if total_ap <= meow_ap_preserve: + logger.warning(f'行动力不足以执行黄币补充任务 ({total_ap} <= {meow_ap_preserve})') + self._handle_smart_scheduling_no_task( + yellow_coins, + total_ap, + current_ap, + coin_target, + meow_ap_preserve, + ) + return + + self._dispatch_coin_task( + yellow_coins, + total_ap, + coin_target, + meow_ap_preserve, + ) + return + + if not coin_target_scheduling and (yellow_coins < cl1_preserve or ap_replenish_active): + if not ap_replenish_active: + self._set_ap_replenish_active() + logger.info( + f'【智能调度检查】体力调度补黄币中: 黄币={yellow_coins}, ' + f'黄币阈值={cl1_preserve}, 总行动力={total_ap}, 行动力阈值={meow_ap_preserve}' + ) + if total_ap <= meow_ap_preserve: + logger.info(f'行动力已达到体力调度阈值 ({total_ap} <= {meow_ap_preserve}),停止补黄币') + self._clear_ap_replenish_active() self._handle_smart_scheduling_no_task( yellow_coins, total_ap, @@ -717,7 +840,7 @@ def run_smart_scheduling_once(self): ) return - if total_ap < cl1_ap_preserve: + if total_ap <= cl1_ap_preserve: self._delay_smart_scheduling_for_ap_limit(total_ap, cl1_ap_preserve) logger.info(f'黄币充足 ({yellow_coins} >= {cl1_preserve}),执行侵蚀1练级') @@ -748,7 +871,7 @@ def run_smart_scheduling(self): self.run_smart_scheduling_once() self.config.check_task_switch() - def _notify_coins_ap_insufficient(self, yellow_coins, total_ap, cl1_preserve, meow_ap_preserve): + def _notify_coins_ap_insufficient(self, yellow_coins, total_ap, coin_target, meow_ap_preserve): """ 发送黄币与行动力双重不足的通知 """ @@ -760,7 +883,10 @@ def _notify_coins_ap_insufficient(self, yellow_coins, total_ap, cl1_preserve, me self.notify_push( title="[AzurPilot] 智能调度 - 黄币与行动力双重不足", - content=f"黄币 {yellow_coins} 低于保留值 {cl1_preserve}\n总行动力 {total_ap} 不足 (需要 {meow_ap_preserve})\n推迟任务" + content=( + f"黄币: {yellow_coins},补黄币阈值: {coin_target}\n" + f"总行动力 {total_ap} 不足 (需要 {meow_ap_preserve})\n推迟任务" + ) ) def _notify_ap_insufficient(self, total_ap, min_reserve): @@ -778,7 +904,7 @@ def _notify_ap_insufficient(self, total_ap, min_reserve): content=f"总行动力 {total_ap} 低于最低保留 {min_reserve},推迟任务" ) - def _dispatch_coin_task(self, yellow_coins, total_ap, preserve_value, meow_ap_preserve): + def _dispatch_coin_task(self, yellow_coins, total_ap, coin_target, meow_ap_preserve): """ 调度黄币补充任务。 @@ -796,7 +922,7 @@ def _dispatch_coin_task(self, yellow_coins, total_ap, preserve_value, meow_ap_pr self._notify_coin_task_proxy( yellow_coins, total_ap, - preserve_value, + coin_target, meow_ap_preserve, self.TASK_NAMES.get(task_name, task_name), ) @@ -806,7 +932,7 @@ def _dispatch_coin_task(self, yellow_coins, total_ap, preserve_value, meow_ap_pr logger.warning('智能调度启用的黄币补充任务均无可执行内容,结束本轮智能调度') self.config.task_stop() - def _notify_coin_task_proxy(self, yellow_coins, total_ap, cl1_preserve, meow_ap_preserve, task_names): + def _notify_coin_task_proxy(self, yellow_coins, total_ap, coin_target, meow_ap_preserve, task_names): """ 发送代理执行黄币补充任务的通知。 """ @@ -815,7 +941,7 @@ def _notify_coin_task_proxy(self, yellow_coins, total_ap, cl1_preserve, meow_ap_ self.notify_push( title="[AzurPilot] 智能调度 - 代理执行黄币补充任务", - content=(f"黄币 {yellow_coins} 低于保留值 {cl1_preserve}\n" + content=(f"黄币: {yellow_coins},补黄币阈值: {coin_target}\n" f"总行动力: {total_ap} (需要 {meow_ap_preserve})\n" f"代理执行{task_names}获取黄币") ) From 114eb6c5902e2586fe5a57dba17110f402f2362b Mon Sep 17 00:00:00 2001 From: longer-sausage Date: Mon, 6 Jul 2026 17:18:33 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E5=A4=A7=E4=B8=96=E7=95=8C?= =?UTF-8?q?=E8=87=AA=E5=BE=8B=E5=AF=BB=E6=95=8C=E6=97=B6=E9=97=B4=E9=99=90?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/template.json | 3 +- module/base/timer.py | 2 +- module/config/argument/args.json | 8 ++ module/config/argument/argument.yaml | 3 + module/config/config_generated.py | 1 + module/config/i18n/en-US.json | 4 + module/config/i18n/ja-JP.json | 4 + module/config/i18n/zh-CN.json | 4 + module/config/i18n/zh-MIAO.json | 4 + module/config/i18n/zh-TW.json | 4 + module/os/map.py | 124 +++++++------------------- module/os/tasks/meowfficer_farming.py | 11 ++- 12 files changed, 74 insertions(+), 98 deletions(-) diff --git a/config/template.json b/config/template.json index 005fcaa18..00fd6ed29 100644 --- a/config/template.json +++ b/config/template.json @@ -2314,7 +2314,8 @@ "NotifyOpsiMail": true, "LauncherPush": true, "IndependentPush": false, - "OpsiOnePushConfig": "provider: null" + "OpsiOnePushConfig": "provider: null", + "AutoSearchTimeLimit": 5 }, "Storage": { "Storage": {} diff --git a/module/base/timer.py b/module/base/timer.py index c051fd4d4..930d07d16 100644 --- a/module/base/timer.py +++ b/module/base/timer.py @@ -109,7 +109,7 @@ def from_seconds(cls, limit, speed=0.5): count = int(limit / speed) return cls(limit, count=count) - def start(self): + def start(self) -> Timer: """启动计时器。 如果计时器未启动,reached() 始终返回 True,从而实现首次快速尝试: diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 244ec8b23..57ca613e6 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -12085,6 +12085,14 @@ "type": "textarea", "value": "provider: null", "mode": "yaml" + }, + "AutoSearchTimeLimit": { + "type": "input", + "value": 5, + "validate": [ + 1, + 240 + ] } }, "Storage": { diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 303bb8f93..1fd609956 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -969,6 +969,9 @@ OpsiGeneral: type: textarea mode: yaml value: "provider: null" + AutoSearchTimeLimit: + value: 5 + validate: [1, 240] OpsiAshBeacon: AttackMode: value: current diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 1aa5d01f0..e313f852e 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -549,6 +549,7 @@ class GeneratedConfig: OpsiGeneral_LauncherPush = True OpsiGeneral_IndependentPush = False OpsiGeneral_OpsiOnePushConfig = 'provider: null' + OpsiGeneral_AutoSearchTimeLimit = 5 # 配置组 `OpsiAshBeacon` OpsiAshBeacon_AttackMode = 'current' # current, current_dossier, current_dossier_only diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index ad880efdc..1ae1ee48b 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -2930,6 +2930,10 @@ "OpsiOnePushConfig": { "name": "Operation Siren notifications push settings", "help": "The settings will take effect only when the \"Siren Operation Information Independent Push\" function is enabled. Use Onepush to push a message about Corrosion 1 and Short Cat. See the document for configuration method: https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/Onepush-configuration-%5BCN%5D" + }, + "AutoSearchTimeLimit": { + "name": "Auto Search Time Limit", + "help": "Unit: minutes. During auto search, the script will assume the game is stuck if no event is detected for this period." } }, "OpsiAshBeacon": { diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index b05557268..a360c812a 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -2930,6 +2930,10 @@ "OpsiOnePushConfig": { "name": "セイレーン作戦プッシュ通知設定", "help": "「塞壬行動信息獨立推送」功能開啟時設置才生效。Onepushを使用して、侵食1と短毛猫に関する情報を1つプッシュします。設定方法はドキュメントを参照してください:https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/Onepush-configuration-%5BCN%5D" + }, + "AutoSearchTimeLimit": { + "name": "自動探索時間制限", + "help": "単位:分。自動探索中にイベントが一定時間発生しない場合、フリーズと判定されます。" } }, "OpsiAshBeacon": { diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index b27f278f3..3c9d7b750 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -2930,6 +2930,10 @@ "OpsiOnePushConfig": { "name": "大世界推送设置", "help": "“大世界信息独立推送”功能开启时设置才生效。使用 Onepush 推送一条关于侵蚀1和短猫的信息。配置方法见文档:https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/Onepush-configuration-%5BCN%5D" + }, + "AutoSearchTimeLimit": { + "name": "自律寻敌时间限制", + "help": "单位:分钟。自律寻敌时连续多长时间没遇到事件会判定为游戏卡死" } }, "OpsiAshBeacon": { diff --git a/module/config/i18n/zh-MIAO.json b/module/config/i18n/zh-MIAO.json index 13fa037b4..b60e90135 100644 --- a/module/config/i18n/zh-MIAO.json +++ b/module/config/i18n/zh-MIAO.json @@ -2930,6 +2930,10 @@ "OpsiOnePushConfig": { "name": "大世界推送设置", "help": "“大世界信息独立推送”功能开启时设置才生效喵。使用 Onepush 推送一条关于侵蚀1和短猫的信息喵。配置方法见文档喵:https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/Onepush-configuration-%5BCN%5D" + }, + "AutoSearchTimeLimit": { + "name": "自律寻敌时间限制喵~", + "help": "单位:分钟。自律寻敌时连续多长时间没遇到事件会判定为游戏卡死喵~" } }, "OpsiAshBeacon": { diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 9f97fe9bf..33ae58cf1 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -2930,6 +2930,10 @@ "OpsiOnePushConfig": { "name": "大世界推送設定", "help": "”大世界資訊獨立推送”功能開啟時設定才生效。使用 Onepush 推送一條關於侵蝕1和短貓的資訊。配置方法見文檔:https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/Onepush-configuration-%5BCN%5D" + }, + "AutoSearchTimeLimit": { + "name": "自律尋敵時間限制", + "help": "單位:分鐘。自律尋敵時連續多長時間沒遇到事件會判定為遊戲卡死" } }, "OpsiAshBeacon": { diff --git a/module/os/map.py b/module/os/map.py index 1018a1e4b..34040bb31 100644 --- a/module/os/map.py +++ b/module/os/map.py @@ -12,6 +12,7 @@ from module.exception import ( CampaignEnd, GameTooManyClickError, + GameStuckError, MapDetectionError, MapWalkError, RequestHumanTakeover, @@ -954,6 +955,7 @@ def false_func(*args, **kwargs): finished_combat = 0 died_timer = Timer(1.5, count=3) self.hp_reset() + auto_search_time_limit_timer = Timer(self.config.OpsiGeneral_AutoSearchTimeLimit * 60, count=1).start() for _ in self.loop(): # 结束条件 if not unlock_checked and unlock_check_timer.reached(): @@ -986,10 +988,12 @@ def false_func(*args, **kwargs): if self.handle_os_auto_search_map_option(drop=drop, enable=success): unlock_checked = True + auto_search_time_limit_timer.reset() continue if self.handle_retirement(): # 退役会中断自动搜索,需要重试 self.ash_popup_canceled = True + auto_search_time_limit_timer.reset() continue if self.combat_appear(): self.on_auto_search_battle_count_add() @@ -1018,102 +1022,15 @@ def false_func(*args, **kwargs): ): success = False logger.warning("Fleet died, stop auto search") + auto_search_time_limit_timer.reset() continue + auto_search_time_limit_timer.reset() if self.handle_map_event(): # 自动搜索无法处理塞壬搜索装置。 + auto_search_time_limit_timer.reset() continue - - return finished_combat - - def os_auto_search_daemon_until_combat( - self, drop=None, strategic=False, interrupt=None, skip_first_screenshot=True - ): - """ - 自动寻敌,遇到第一次战斗就返回。 - - Args: - drop (DropRecord): 掉落记录对象。 - strategic (bool): 是否运行战略搜索。 - interrupt (callable): 中断回调函数。 - skip_first_screenshot: 是否跳过第一次截图。 - - Returns: - int: 完成的战斗次数。 - - Raises: - CampaignEnd: 自动搜索结束时抛出。 - RequestHumanTakeover: 没有自动搜索选项时抛出。 - - Pages: - in: AUTO_SEARCH_OS_MAP_OPTION_OFF - out: AUTO_SEARCH_OS_MAP_OPTION_OFF 且 info_bar_count() >= 2(地图上无可清理对象时)。 - AUTO_SEARCH_REWARD(获得自动搜索奖励时)。 - """ - logger.hr("OS auto search until combat", level=2) - self.on_auto_search_battle_count_reset() - unlock_checked = False - unlock_check_timer = Timer(5, count=10).start() - self.ash_popup_canceled = False - - def false_func(*args, **kwargs): - return False - - success = True - interrupt_confirm = False - if callable(interrupt): - is_interrupt, not_interrupt = interrupt, false_func - elif isinstance(interrupt, list) and len(interrupt) == 2: - is_interrupt = interrupt[0] if callable(interrupt[0]) else false_func - not_interrupt = interrupt[1] if callable(interrupt[1]) else false_func - else: - is_interrupt, not_interrupt = false_func, false_func - finished_combat = 0 - died_timer = Timer(1.5, count=3) - self.hp_reset() - for _ in self.loop(): - # 结束条件 - if not unlock_checked and unlock_check_timer.reached(): - logger.critical("当前海域未解锁自律,请先完成剧情任务。") - raise RequestHumanTakeover - if self.is_in_map(): - self.device.stuck_record_clear() - if not success: - if died_timer.reached(): - logger.warning("Fleet died confirm") - break - else: - if not interrupt_confirm and is_interrupt(): - interrupt_confirm = True - if interrupt_confirm and not_interrupt(): - interrupt_confirm = False - died_timer.reset() - else: - died_timer.reset() - - if not unlock_checked: - if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120)): - unlock_checked = True - elif self.appear( - AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120) - ): - unlock_checked = True - elif self.appear(AUTO_SEARCH_OS_MAP_OPTION_ON, offset=(5, 120)): - unlock_checked = True - - if self.handle_os_auto_search_map_option(drop=drop, enable=success): - unlock_checked = True - continue - if self.handle_retirement(): - # 退役会中断自动搜索,需要重试 - self.ash_popup_canceled = True - continue - if self.combat_appear(): - self.on_auto_search_battle_count_add() - self.interrupt_auto_search(goto_main=False, end_task=False) - return finished_combat - if self.handle_map_event(): - # 自动搜索无法处理塞壬搜索装置。 - continue + if auto_search_time_limit_timer.reached(): + raise GameStuckError('自律寻敌卡死') return finished_combat @@ -1503,8 +1420,13 @@ def run_strategic_search(self): self.hp_reset() self.hp_get() return True - except TaskEnd: - # 任务切换,让异常继续向上传播 + except ( + TaskEnd, + GameStuckError, + GameTooManyClickError, + RequestHumanTakeover, + ): + # 任务切换和恢复型异常必须交给上层调度器处理。 raise except Exception as e: logger.warning(f"Strategic search interrupted: {e}") @@ -2141,6 +2063,13 @@ def _execute_fixed_patrol_scan( for _ in range(2): try: self.map_rescan(rescan_mode="full") + except ( + TaskEnd, + GameStuckError, + GameTooManyClickError, + RequestHumanTakeover, + ): + raise except Exception as e: logger.debug(f"最终全图重扫出现异常,继续重试: {e}", exc_info=True) time.sleep(0.6) @@ -2150,6 +2079,13 @@ def _execute_fixed_patrol_scan( logger.info("执行一次自律寻敌以清理可能的装置") try: self.run_auto_search(question=True, rescan=None, after_auto_search=True) + except ( + TaskEnd, + GameStuckError, + GameTooManyClickError, + RequestHumanTakeover, + ): + raise except Exception as e: logger.warning(f"自律寻敌过程出现异常: {e}") diff --git a/module/os/tasks/meowfficer_farming.py b/module/os/tasks/meowfficer_farming.py index 72d0a1d6f..5df127555 100644 --- a/module/os/tasks/meowfficer_farming.py +++ b/module/os/tasks/meowfficer_farming.py @@ -1,6 +1,11 @@ from module.config.config import TaskEnd from module.config.utils import get_os_reset_remain -from module.exception import RequestHumanTakeover, ScriptError +from module.exception import ( + GameStuckError, + GameTooManyClickError, + RequestHumanTakeover, + ScriptError, +) from module.logger import logger from module.map.map_grids import SelectedGrids from module.os.map import OSMap @@ -181,7 +186,7 @@ def _meow_handle_stay_in_zone(self, zone): try: try: search_completed = self.run_strategic_search() - except TaskEnd: + except (TaskEnd, GameStuckError, GameTooManyClickError, RequestHumanTakeover): raise except Exception as e: logger.warning(f'战略搜索异常: {e}') @@ -194,6 +199,8 @@ def _meow_handle_stay_in_zone(self, zone): try: self.handle_after_auto_search() + except (TaskEnd, GameStuckError, GameTooManyClickError, RequestHumanTakeover): + raise except Exception: logger.exception('handle_after_auto_search 发生异常') finally: