diff --git a/deploy/uv.py b/deploy/uv.py index 945e64cef..3d3cff3bc 100644 --- a/deploy/uv.py +++ b/deploy/uv.py @@ -16,7 +16,6 @@ BOOTSTRAPPED_ENV = "AZURPILOT_UV_BOOTSTRAPPED" BOOTSTRAP_UV_ENV = "AZURPILOT_BOOTSTRAP_UV" NO_BOOTSTRAP_ENV = "AZURPILOT_NO_UV_BOOTSTRAP" -PYTHON_VERSION = "3.14.3" DEPENDENCY_SYNC_TIMEOUT = 30 * 60 @@ -183,10 +182,10 @@ def _uv_python_env(root: Path): def _managed_python_executable(root: Path) -> Optional[Path]: install_dir = venv_python_install_dir(root) - for python_home in sorted(install_dir.glob(f"cpython-{PYTHON_VERSION}-*")): + for python_home in sorted(install_dir.glob("cpython-*-*"), reverse=True): candidates = [ python_home / "python.exe", - python_home / "bin" / "python3.14", + python_home / "bin" / "python3", python_home / "bin" / "python", ] for candidate in candidates: @@ -204,7 +203,7 @@ def _venv_python_works(root: Path) -> bool: [ str(python), "-c", - "import sys; raise SystemExit(0 if sys.version_info[:2] == (3, 14) else 1)", + "import sys; raise SystemExit(0)", ], cwd=str(root), stdout=subprocess.DEVNULL, @@ -335,10 +334,10 @@ def _ensure_self_contained_python( outputs: Optional[list[str]] = None, deadline: float | None = None, ): + env = _uv_python_env(root) if _venv_python_works(root) and _managed_python_executable(root): return - env = _uv_python_env(root) managed_python = _managed_python_executable(root) if managed_python is None: command = [ @@ -349,7 +348,6 @@ def _ensure_self_contained_python( venv_python_install_dir(root), "--no-bin", "--managed-python", - PYTHON_VERSION, ] _run_and_collect( command, @@ -365,7 +363,6 @@ def _ensure_self_contained_python( "python", "find", "--managed-python", - PYTHON_VERSION, ] output = _run_output( command, diff --git a/module/coalition/coalition_scuttle.py b/module/coalition/coalition_scuttle.py index 340dc0c69..55eb95d36 100644 --- a/module/coalition/coalition_scuttle.py +++ b/module/coalition/coalition_scuttle.py @@ -7,7 +7,6 @@ OPTS_INFO_D, EXP_INFO_D, EXP_INFO_A, EXP_INFO_B, EXP_INFO_S ) -from module.handler.assets import GET_MISSION from module.coalition.assets import * from module.coalition.combat import CoalitionCombat from module.coalition.coalition import Coalition @@ -21,6 +20,7 @@ class CoalitionScuttleCombat(CoalitionCombat): triggered_normal_end = False _is_shipwreck = False # 当前战斗是否为沉船D评价 + _is_s_rank = False # 当前战斗是否为S评价 def auto_search_combat_execute(self, emotion_reduce=True, fleet_index=1, expected_end=None): """ @@ -91,10 +91,8 @@ def auto_search_combat_execute(self, emotion_reduce=True, fleet_index=1, expecte break # D评价结算界面:S/A/B评价的动画过渡帧可能短暂误匹配D评价模板, # 但只有真正的沉船才会出现OPTS_INFO_D弹窗。 - # 此处仅break退出循环,不设置沉船标记(未经过OPTS_INFO_D确认)。 - # 真正的沉船由上方OPTS_INFO_D路径触发并设置_is_shipwreck。 + # 此处不设置沉船标记(未经过OPTS_INFO_D确认),让后续S/A/B条件覆盖。 if self.appear(BATTLE_STATUS_D) or self.appear(EXP_INFO_D): - self._withdraw = True break if confirm_timer.reached(): self._withdraw = True @@ -111,7 +109,8 @@ def auto_search_combat_execute(self, emotion_reduce=True, fleet_index=1, expecte # S评价或自动搜索运行中 if self.appear(BATTLE_STATUS_S) or self.appear(EXP_INFO_S) \ - or self.appear(GET_MISSION) or self.is_auto_search_running(): + or self.is_auto_search_running(): + self._is_s_rank = True self.device.screenshot_interval_set() break @@ -136,6 +135,7 @@ def coalition_combat(self): while 1: logger.hr(f'{self.FUNCTION_NAME_BASE}{self.battle_count}', level=2) self._is_shipwreck = False + self._is_s_rank = False # 仅第一场战斗扣减2心情(关卡进入代价),后续战斗不再扣减 self.auto_search_combat_execute( emotion_reduce=self.battle_count == 0, @@ -387,10 +387,10 @@ def run(self, event='', mode='', fleet='', total=0): if self.config.StopCondition_RunCount: self.config.StopCondition_RunCount -= 1 - # SP关卡非D评价(沉船):视为已通过,延迟至服务器刷新 - # D评价视为未通过,继续出击 - if mode == 'sp' and self.triggered_normal_end and not self._is_shipwreck: - logger.info('SP以非D评价通过') + # SP关卡仅S评价视为已通过,延迟至服务器刷新 + # A/B/C/D评价均视为未通过,继续出击 + if mode == 'sp' and self._is_s_rank and not self._is_shipwreck: + logger.info('SP以S评价通过') self.config.task_delay(server_update=True) self.config.task_stop() diff --git a/module/combat/auto_search_combat.py b/module/combat/auto_search_combat.py index 19957ee5f..91630218f 100644 --- a/module/combat/auto_search_combat.py +++ b/module/combat/auto_search_combat.py @@ -35,6 +35,9 @@ class AutoSearchCombat(MapOperation, Combat, CampaignStatus): _auto_search_status_confirm (bool): 自动搜索状态是否已确认。 _withdraw (bool): 是否已执行撤退。 _defeat_count (int): 战败次数。 + _shipwreck_emotion_reduced (bool): 沉船心情扣减是否已执行,防止重复扣减。 + _auto_search_emotion_reduce (bool): 当前战斗是否启用心情扣减。 + _auto_search_fleet_index (int): 当前战斗的舰队索引。 auto_search_oil_limit_triggered (bool): 石油限制是否已触发。 auto_search_coin_limit_triggered (bool): 物资限制是否已触发。 """ @@ -42,6 +45,9 @@ class AutoSearchCombat(MapOperation, Combat, CampaignStatus): _auto_search_status_confirm = False _withdraw = False _defeat_count = 0 + _shipwreck_emotion_reduced = False + _auto_search_emotion_reduce = False + _auto_search_fleet_index = 1 auto_search_oil_limit_triggered = False auto_search_coin_limit_triggered = False @@ -314,24 +320,23 @@ def auto_search_combat_execute(self, emotion_reduce, fleet_index, battle=None, e if self.handle_get_ship(): continue if self.appear_then_click(OPTS_INFO_D, offset=(30, 30), interval=2): - if emotion_reduce: + if emotion_reduce and not self._shipwreck_emotion_reduced: self.emotion.reduce(fleet_index, shipwreck=True) + self._shipwreck_emotion_reduced = True self._withdraw = True break # D评价结算界面(BATTLE_STATUS_D / EXP_INFO_D) # S/A/B评价的动画过渡帧可能短暂误匹配D评价模板, # 但只有真正的沉船才会出现OPTS_INFO_D弹窗。 - # 此处仅break退出循环,不扣减shipwreck心情。 - # 真正的沉船扣减由上方OPTS_INFO_D路径触发。 - # 退出后由auto_search_combat_status()中的handle_battle_status()处理结算画面。 + # 此处不设置 _withdraw,让后续S/A/B评价条件覆盖误匹配。 + # 真正的D评价会先被上方OPTS_INFO_D捕获。 if self.appear(BATTLE_STATUS_D) or self.appear(EXP_INFO_D): - self._withdraw = True break if confirm_timer.reached(): + # 结算确认超时:不扣心情、不盲目点击OPTS_INFO_D + # 只设置_withdraw让status处理,status中检测到OPTS_INFO_D才扣心情 + logger.warning('[自动搜索-战斗] 结算确认超时,进入status处理') self._withdraw = True - self.device.click(OPTS_INFO_D) - if emotion_reduce: - self.emotion.reduce(fleet_index, shipwreck=True) confirm_timer.reset() break if self.appear(BATTLE_STATUS_A) or self.appear(BATTLE_STATUS_B) \ @@ -505,6 +510,24 @@ def auto_search_combat_status(self): if self.handle_mission_popup_ack(): continue + # 处理战斗结算界面——SABC评价在自动搜索中可能快速自动过渡, + # 若截图恰好捕获到结算画面则点击推进并记录评价 + # D评价点击BATTLE_STATUS_D后,会出现OPTS_INFO_D沉船弹窗 + if self.handle_battle_status(): + continue + if self.handle_exp_info(): + continue + # 检测D评价(沉船)弹窗——这是沉船的确认性标志(二次确认) + # 只有OPTS_INFO_D出现才确认是真正的D评价并扣心情 + # S/A/B/C转场误匹配BATTLE_STATUS_D不会出现OPTS_INFO_D,不会扣心情 + if self.appear(OPTS_INFO_D, offset=(30, 30)): + logger.info('[自动搜索-结算] 检测到沉船弹窗,进入撤退处理') + if self._auto_search_emotion_reduce and not self._shipwreck_emotion_reduced: + self.emotion.reduce(self._auto_search_fleet_index, shipwreck=True) + self._shipwreck_emotion_reduced = True + self._withdraw = True + continue + # Handle low emotion combat # Combat status if self._auto_search_status_confirm: @@ -529,6 +552,9 @@ def auto_search_combat(self, emotion_reduce=None, fleet_index=1, battle=None): """ emotion_reduce = emotion_reduce if emotion_reduce is not None else self.emotion.is_calculate + self._auto_search_emotion_reduce = emotion_reduce + self._auto_search_fleet_index = fleet_index + self._shipwreck_emotion_reduced = False self.auto_search_combat_execute(emotion_reduce=emotion_reduce, fleet_index=fleet_index, battle=battle) self.auto_search_combat_status()