mirror of
https://github.com/moesnow/March7thAssistant.git
synced 2026-08-03 06:34:00 +08:00
- 新增 hotkey_toggle_autoplot 配置项,默认快捷键 F9 - 新增 hotkey_toggle_autoplot_enable 配置项,默认关闭 - 持久化自动对话选项(跳过/选择/战斗检测/短信) - 工具箱自动对话卡片展开后显示快捷键提示 Closes #1110
331 lines
13 KiB
Python
331 lines
13 KiB
Python
from PySide6.QtCore import Qt
|
||
from PySide6.QtWidgets import QWidget, QLabel, QVBoxLayout, QSpacerItem, QScroller, QScrollerProperties, QFileDialog
|
||
from qfluentwidgets import FluentIcon as FIF
|
||
from qfluentwidgets import SettingCardGroup, PushSettingCard, ScrollArea, InfoBar, InfoBarPosition, MessageBox, PushButton
|
||
from .card.pushsettingcard1 import PushSettingCardCode
|
||
from .card.autoplot_setting_card import AutoPlotSettingCard
|
||
from .common.style_sheet import StyleSheet
|
||
import tasks.tool as tool
|
||
import base64
|
||
import subprocess
|
||
import pyperclip
|
||
from module.config import cfg
|
||
from tasks.base.tasks import start_task
|
||
import os
|
||
import sys
|
||
|
||
|
||
from module.localization import tr
|
||
|
||
|
||
class ToolsInterface(ScrollArea):
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent=parent)
|
||
self.parent = parent
|
||
self.scrollWidget = QWidget()
|
||
self.vBoxLayout = QVBoxLayout(self.scrollWidget)
|
||
|
||
self.toolsLabel = QLabel(tr("工具箱"), self)
|
||
|
||
self.ToolsGroup = SettingCardGroup(tr('工具箱'), self.scrollWidget)
|
||
self.automaticPlotCard = AutoPlotSettingCard(
|
||
FIF.IMAGE_EXPORT,
|
||
tr("自动对话"),
|
||
tr("进入剧情页面后自动开始运行,支持大于等于 1920*1080 的 16:9 分辨率,不支持云·星穹铁道")
|
||
)
|
||
self.gameScreenshotCard = PushSettingCard(
|
||
tr('捕获'),
|
||
FIF.CLIPPING_TOOL,
|
||
tr("游戏截图"),
|
||
tr("检查程序获取的图像是否正确,支持OCR识别文字(可用于自行排查异常)")
|
||
)
|
||
self.unlockfpsCard = PushSettingCard(
|
||
tr('解锁'),
|
||
FIF.SPEED_HIGH,
|
||
tr("解锁帧率"),
|
||
tr("通过修改注册表解锁120帧率,如已解锁,再次点击将恢复60帧率(支持国服和国际服)")
|
||
)
|
||
self.redemptionCodeCard = PushSettingCardCode(
|
||
tr('执行'),
|
||
FIF.BOOK_SHELF,
|
||
tr("兑换码"),
|
||
"redemption_code",
|
||
self
|
||
)
|
||
self.cloudTouchCard = PushSettingCard(
|
||
tr('启动'),
|
||
FIF.CLOUD,
|
||
tr("触屏模式"),
|
||
tr("以云游戏移动端 UI 的方式启动游戏,可搭配 UU远程 平板触控模式,启动后会将命令复制到剪贴板内")
|
||
)
|
||
self.screenTestCard = PushSettingCard(
|
||
tr('测试'),
|
||
FIF.CHECKBOX,
|
||
tr("界面可切换性测试"),
|
||
tr("以最短路径遍历所有可达界面,验证界面切换是否正常")
|
||
)
|
||
|
||
self.__initWidget()
|
||
|
||
def __initWidget(self):
|
||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||
self.setViewportMargins(0, 80, 0, 20)
|
||
self.setWidget(self.scrollWidget)
|
||
self.setWidgetResizable(True)
|
||
self.setObjectName('toolsInterface')
|
||
|
||
self.scrollWidget.setObjectName('scrollWidget')
|
||
self.toolsLabel.setObjectName('toolsLabel')
|
||
StyleSheet.TOOLS_INTERFACE.apply(self)
|
||
|
||
self.__initLayout()
|
||
self.__connectSignalToSlot()
|
||
|
||
QScroller.grabGesture(self.viewport(), QScroller.ScrollerGestureType.LeftMouseButtonGesture)
|
||
scroller = QScroller.scroller(self.viewport())
|
||
scroller_props = scroller.scrollerProperties()
|
||
scroller_props.setScrollMetric(QScrollerProperties.ScrollMetric.OvershootDragDistanceFactor, 0.05)
|
||
scroller_props.setScrollMetric(QScrollerProperties.ScrollMetric.OvershootScrollDistanceFactor, 0.05)
|
||
scroller_props.setScrollMetric(QScrollerProperties.ScrollMetric.DecelerationFactor, 0.5)
|
||
scroller.setScrollerProperties(scroller_props)
|
||
|
||
def __initLayout(self):
|
||
self.toolsLabel.move(36, 30)
|
||
|
||
self.ToolsGroup.addSettingCard(self.automaticPlotCard)
|
||
self.ToolsGroup.addSettingCard(self.gameScreenshotCard)
|
||
self.ToolsGroup.addSettingCard(self.unlockfpsCard)
|
||
self.ToolsGroup.addSettingCard(self.redemptionCodeCard)
|
||
self.ToolsGroup.addSettingCard(self.cloudTouchCard)
|
||
self.ToolsGroup.addSettingCard(self.screenTestCard)
|
||
|
||
if sys.platform != 'win32':
|
||
self.automaticPlotCard.setDisabled(True)
|
||
self.unlockfpsCard.setDisabled(True)
|
||
self.cloudTouchCard.setDisabled(True)
|
||
|
||
self.ToolsGroup.titleLabel.setHidden(True)
|
||
|
||
self.vBoxLayout.setSpacing(28)
|
||
self.vBoxLayout.setContentsMargins(36, 10, 36, 0)
|
||
self.vBoxLayout.addWidget(self.ToolsGroup)
|
||
|
||
for i in range(self.ToolsGroup.vBoxLayout.count()):
|
||
item = self.ToolsGroup.vBoxLayout.itemAt(i)
|
||
if isinstance(item, QSpacerItem):
|
||
self.ToolsGroup.vBoxLayout.removeItem(item)
|
||
break
|
||
|
||
def __onUnlockfpsCardClicked(self):
|
||
from utils.registry.star_rail_setting import get_game_fps, set_game_fps # 延迟导入,避免非 Windows 平台报错
|
||
try:
|
||
fps = get_game_fps()
|
||
if fps == 120:
|
||
set_game_fps(60)
|
||
InfoBar.info(
|
||
title=tr('恢复60成功 (^∀^●)'),
|
||
content="",
|
||
orient=Qt.Orientation.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=1000,
|
||
parent=self
|
||
)
|
||
else:
|
||
set_game_fps(120)
|
||
InfoBar.success(
|
||
title=tr('解锁120成功 (^∀^●)'),
|
||
content="",
|
||
orient=Qt.Orientation.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=1000,
|
||
parent=self
|
||
)
|
||
except:
|
||
InfoBar.warning(
|
||
title=tr('解锁失败'),
|
||
content=tr("请将游戏图像质量修改为自定义后重试"),
|
||
orient=Qt.Orientation.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=5000,
|
||
parent=self
|
||
)
|
||
|
||
def __onCloudTouchCardClicked(self):
|
||
exe_path = os.path.abspath(os.path.join(cfg.genshin_starRail_fps_unlocker_path, "unlocker.exe"))
|
||
config_path = os.path.abspath(os.path.join(cfg.genshin_starRail_fps_unlocker_path, "hoyofps_config.ini"))
|
||
game_path = os.path.abspath(cfg.game_path)
|
||
try:
|
||
if cfg.genshin_starRail_fps_unlocker_allow is False:
|
||
from qfluentwidgets import MessageBox
|
||
# 依次展示多次确认对话框,每次提示更严肃,任意一次取消即返回
|
||
confirm_messages = [
|
||
(tr('此功能依赖第三方程序实现'),
|
||
tr('https://github.com/winTEuser/Genshin_StarRail_fps_unlocker\n使用本功能产生的所有问题与本项目与开发者团队无关,是否继续?')),
|
||
(tr('再次确认:可能存在风险'),
|
||
tr('工作原理是通过 WriteProcessMemory 把代码写进游戏,是否继续?')),
|
||
(tr('最终确认:请谨慎操作'),
|
||
tr('确认继续并允许启用触屏模式?')),
|
||
]
|
||
|
||
for title, message in confirm_messages:
|
||
from qfluentwidgets import MessageBox
|
||
step_confirm = MessageBox(title, message, self.window())
|
||
step_confirm.yesButton.setText(tr('确认'))
|
||
step_confirm.cancelButton.setText(tr('取消'))
|
||
if not step_confirm.exec():
|
||
return
|
||
|
||
cfg.set_value("genshin_starRail_fps_unlocker_allow", True)
|
||
|
||
if not game_path or not os.path.exists(game_path):
|
||
InfoBar.warning(
|
||
title=tr('游戏路径配置错误(╥╯﹏╰╥)'),
|
||
content=tr("请在“设置”-->“程序”中配置正确的游戏路径"),
|
||
orient=Qt.Orientation.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=5000,
|
||
parent=self
|
||
)
|
||
return
|
||
|
||
if not os.path.exists(exe_path):
|
||
start_task("mobileui_update")
|
||
return
|
||
|
||
config_dir = os.path.dirname(config_path)
|
||
if not os.path.exists(config_dir):
|
||
os.makedirs(config_dir, exist_ok=True)
|
||
|
||
import configparser
|
||
cp = configparser.ConfigParser()
|
||
|
||
if os.path.exists(config_path):
|
||
cp.read(config_path, encoding='utf-16')
|
||
if 'Setting' not in cp:
|
||
cp['Setting'] = {}
|
||
old_path = cp['Setting'].get('HKSRPath', '')
|
||
old_fps = cp['Setting'].get('FPS', '')
|
||
if old_path != game_path or old_fps != '60':
|
||
cp['Setting']['HKSRPath'] = game_path
|
||
cp['Setting']['FPS'] = '60'
|
||
with open(config_path, 'w', encoding='utf-16') as f:
|
||
cp.write(f)
|
||
else:
|
||
cp['Setting'] = {'HKSRPath': game_path, 'FPS': '60'}
|
||
with open(config_path, 'w', encoding='utf-16') as f:
|
||
cp.write(f)
|
||
args = ["-HKSR", "-EnableMobileUI"]
|
||
subprocess.Popen([exe_path] + args, cwd=config_dir)
|
||
pyperclip.copy(f'cd "{config_dir}" && "{exe_path}" {" ".join(args)}')
|
||
InfoBar.success(
|
||
title=tr('启动成功(^∀^●)'),
|
||
content=tr("已将命令复制到剪贴板"),
|
||
orient=Qt.Orientation.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=2000,
|
||
parent=self
|
||
)
|
||
except Exception as e:
|
||
InfoBar.warning(
|
||
title=tr('启动失败'),
|
||
content=str(e),
|
||
orient=Qt.Orientation.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=5000,
|
||
parent=self
|
||
)
|
||
|
||
def __onGameScreenshotCardClicked(self):
|
||
"""点击截图卡片后先选择来源:游戏窗口或本地图片。"""
|
||
message_box = MessageBox(tr("截图来源"), tr("请选择截图方式"), self.window())
|
||
message_box.contentLabel.setMinimumWidth(500)
|
||
|
||
message_box.yesButton.setText(tr("捕获游戏窗口"))
|
||
message_box.cancelButton.setText(tr("取消"))
|
||
|
||
choose_file_button = PushButton(tr("手动选择图片"), message_box)
|
||
message_box.buttonLayout.insertWidget(1, choose_file_button, 1, Qt.AlignmentFlag.AlignVCenter)
|
||
|
||
action = {"value": None}
|
||
|
||
def _choose_local_file():
|
||
action["value"] = "file"
|
||
message_box.reject()
|
||
|
||
choose_file_button.clicked.connect(_choose_local_file)
|
||
|
||
accepted = message_box.exec()
|
||
|
||
if action["value"] == "file":
|
||
file_path, _ = QFileDialog.getOpenFileName(
|
||
self,
|
||
tr("选择图片"),
|
||
"",
|
||
"Image Files (*.png *.jpg *.jpeg *.bmp *.webp)"
|
||
)
|
||
if file_path:
|
||
tool.start_screenshot_from_file(file_path)
|
||
return
|
||
|
||
if accepted:
|
||
tool.start("screenshot")
|
||
|
||
def __connectSignalToSlot(self):
|
||
self.gameScreenshotCard.clicked.connect(self.__onGameScreenshotCardClicked)
|
||
self.automaticPlotCard.switchChanged.connect(self.__onAutoPlotSwitchChanged)
|
||
self.automaticPlotCard.optionsChanged.connect(self.__onAutoPlotOptionsChanged)
|
||
self.unlockfpsCard.clicked.connect(self.__onUnlockfpsCardClicked)
|
||
self.cloudTouchCard.clicked.connect(self.__onCloudTouchCardClicked)
|
||
self.screenTestCard.clicked.connect(self.__onScreenTestCardClicked)
|
||
|
||
def __onScreenTestCardClicked(self):
|
||
start_task("screen_test")
|
||
|
||
def __onAutoPlotSwitchChanged(self, isChecked: bool):
|
||
"""Handle auto plot switch state change"""
|
||
if isChecked:
|
||
# Update options first
|
||
options = self.automaticPlotCard.getOptions()
|
||
tool.update_plot_options(options)
|
||
# Start auto plot
|
||
tool.start("plot")
|
||
InfoBar.success(
|
||
title=tr('自动对话已启动'),
|
||
content="",
|
||
orient=Qt.Orientation.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=1000,
|
||
parent=self
|
||
)
|
||
else:
|
||
# Stop auto plot
|
||
tool.stop_plot()
|
||
InfoBar.info(
|
||
title=tr('自动对话已停止'),
|
||
content="",
|
||
orient=Qt.Orientation.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=1000,
|
||
parent=self
|
||
)
|
||
|
||
def __onAutoPlotOptionsChanged(self, options: dict):
|
||
"""Handle auto plot options change"""
|
||
# Update options if auto plot is running
|
||
if self.automaticPlotCard.getSwitchState():
|
||
tool.update_plot_options(options)
|
||
|
||
def toggleAutoPlot(self):
|
||
"""切换自动对话状态(供全局热键调用)"""
|
||
current_state = self.automaticPlotCard.getSwitchState()
|
||
new_state = not current_state
|
||
self.automaticPlotCard.setValue(new_state)
|