加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
main.py 19.57 KB
一键复制 编辑 原始数据 按行查看 历史
聽風 提交于 2024-10-21 22:49 . 增加语音播报及冲锋号
import sys
import json
import os
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QGraphicsView, QGraphicsScene, QGraphicsTextItem, QLabel, QInputDialog, QMessageBox, QPushButton, QScrollArea, QVBoxLayout
from PyQt5.QtGui import QMovie, QColor, QBrush, QIcon
from PyQt5.QtCore import Qt, QTimer, QRectF, QTime
from plyer import notification
from datetime import datetime, timedelta, timezone
from modules.editorjson import MVPJsonEditor # 导入模块
from functools import partial
import subprocess
import time
import threading
from playsound import playsound
import pyttsx3
class MVPTimer(QWidget):
def __init__(self):
super().__init__()
# 获取可执行文件所在的目录
if getattr(sys, 'frozen', False):
# 如果是打包后的可执行文件
base_path = os.path.dirname(sys.executable)
else:
# 如果是正常的 Python 脚本
base_path = os.path.dirname(os.path.abspath(__file__))
self.state_file = os.path.join(base_path, 'timer_state.json') # 保存状态的文件
self.config_file = os.path.join(base_path, 'config.json') # 配置文件路径
self.load_config() # 加载配置文件
self.initUI()
self.restore_timers() # 恢复计时器状态
self.editor = None # 初始化编辑器属性
def center_window(self):
# 获取屏幕的中心点
screen = QApplication.primaryScreen().availableGeometry().center()
# 获取窗口的矩形框架
frame_geometry = self.frameGeometry()
# 将窗口的中心点设置为屏幕的中心点
frame_geometry.moveCenter(screen)
# 移动窗口到新的位置
self.move(frame_geometry.topLeft())
def initUI(self):
self.setWindowTitle('MVP Timer')
# 设置窗口图标
self.setWindowIcon(QIcon('app_icon.ico'))
main_layout = QVBoxLayout()
self.setLayout(main_layout)
# 创建一个滚动区域
self.scroll_area = QScrollArea(self)
self.scroll_area.setWidgetResizable(True)
self.scroll_content = QWidget()
self.scroll_layout = QGridLayout(self.scroll_content)
self.scroll_area.setWidget(self.scroll_content)
# 每个 QGraphicsView 的大小
self.view_width = 150
self.view_height = 220
# 设置窗口大小以确保初始显示3行10列
self.setMinimumSize(self.view_width * self.max_columns + 150, self.view_height * self.min_rows + 100)
# 加载数据并创建计时器
self.load_data()
# 将滚动区域添加到主布局
main_layout.addWidget(self.scroll_area)
# 添加编辑MVP按钮
edit_button = QPushButton('编辑MVP')
edit_button.clicked.connect(self.open_editor)
main_layout.addWidget(edit_button)
# 添加刷新界面按钮
refresh_button = QPushButton('刷新界面')
refresh_button.clicked.connect(self.refresh_application)
main_layout.addWidget(refresh_button)
def load_config(self):
# 提供默认值
self.max_columns = 10
self.min_rows = 3
# 尝试加载配置文件
if os.path.exists(self.config_file):
try:
with open(self.config_file, 'r', encoding='utf-8') as file:
config = json.load(file)
self.max_columns = config.get('max_columns', self.max_columns)
self.min_rows = config.get('min_rows', self.min_rows)
except (FileNotFoundError, json.JSONDecodeError) as e:
QMessageBox.critical(self, "错误", f"加载配置文件错误: {e}")
def load_data(self):
# 读取 JSON 文件
try:
with open('mvp.json', 'r', encoding='utf-8') as file:
data = json.load(file)['data']
self.create_timers(data)
except FileNotFoundError:
QMessageBox.critical(self, "错误", "找不到 mvp.json 文件")
except json.JSONDecodeError:
QMessageBox.critical(self, "错误", "mvp.json 文件格式错误")
def create_timers(self, data):
# 根据新数据重新创建计时器和界面元素
self.timers = []
display_count = 0 # 用于跟踪显示的MVP量
for mvp in data:
if not mvp.get('visible', True): # 检查 visible 字段,默认为 True
continue # 如果 visible 为 False,跳过此MVP
mvp['time'] *= 60 # 将分钟转换为秒
mvp['current_time'] = mvp['time'] # 初始化 current_time
# 创建图形视图
scene = QGraphicsScene()
view = QGraphicsView(scene)
view.setFixedSize(self.view_width, self.view_height) # 增加视图大小
# 添加GIF图片
img_label = QLabel()
img_label.setFixedSize(125, 125) # 设置固定大小
img_label.setAlignment(Qt.AlignCenter) # 居中显示
movie = QMovie(f"img/{mvp['img']}")
img_label.setMovie(movie)
movie.start()
scene.addWidget(img_label)
# 添加叠加信息到左上角
overlay_text = f"{mvp['race']}<br>{mvp['attr']}<br>{mvp['size']}"
add_text_with_outline(scene, overlay_text, 0, 0, width=125, align='left', x_offset=-15) # 左对齐并紧贴左边
# 添加 label 信息到上角
if 'label' in mvp:
add_text_with_outline(scene, mvp['label'], 0, 0, width=125, align='right', x_offset=85) # 右对齐并紧贴右边
# 添加名称、位置和时间
mvpid = mvp.get('mvpid', '未知') # 使用 get 方法提供默认值
info_text = f"{mvp['name']}[{mvpid}]<br>{mvp['loc']}<br>{self.format_time(mvp['time'])}"
info_item = QGraphicsTextItem()
info_item.setHtml(f"""
<div style="
color: black;
background-color: white;
border: 1px solid black;
font-size: 12px;
text-align: center;">
{info_text}
</div>
""")
info_item.setTextWidth(125) # 设置宽度以便居中
info_item.setPos(0, 130) # 设置位置在图片下方
scene.addItem(info_item)
# 创建红色透明蒙版
overlay_rect = scene.addRect(QRectF(0, 0, 125, 125), brush=QBrush(QColor(255, 0, 0, 76)))
overlay_rect.setVisible(False) # 初始时隐藏
# 倒计时显示
countdown_item, outline_items = add_text_with_outline(scene, self.format_time(mvp['time']), 0, 98, width=125, align='center', font_size=22, color='red')
countdown_item.setVisible(False) # 确保倒计时文本初始时隐藏
for outline in outline_items:
outline.setVisible(False) # 确保描边初始时隐藏
timer = QTimer(self)
timer.setInterval(1000)
timer.timeout.connect(partial(self.update_timer, info_item, mvp, timer, overlay_rect, countdown_item, outline_items, scene))
# 捕获初始时间
view.mousePressEvent = lambda event, t=timer, mvp=mvp, rect=overlay_rect, cd=countdown_item, outlines=outline_items, lbl=info_item: self.handle_mouse_event(event, t, mvp, rect, cd, outlines, lbl)
row = display_count // self.max_columns # 动态调整行数
col = display_count % self.max_columns # 动态调整列数
self.scroll_layout.addWidget(view, row, col)
self.timers.append((info_item, mvp, timer, overlay_rect, countdown_item, outline_items))
display_count += 1 # 增加显示的MVP计数
# 确保至少显示30个项目
if display_count < self.max_columns * self.min_rows:
for _ in range(self.max_columns * self.min_rows - display_count):
placeholder = QLabel("空")
placeholder.setFixedSize(self.view_width, self.view_height)
row = display_count // self.max_columns
col = display_count % self.max_columns
self.scroll_layout.addWidget(placeholder, row, col)
display_count += 1
def clear_timers(self):
# 清除当前计时器和界面元素
for info_item, mvp, timer, overlay_rect, countdown_item, outline_items in self.timers:
timer.stop()
# 清除界面元素
info_item.scene().removeItem(info_item)
overlay_rect.scene().removeItem(overlay_rect)
countdown_item.scene().removeItem(countdown_item)
for outline in outline_items:
outline.scene().removeItem(outline)
self.timers.clear()
def refresh_application(self):
# 保存当前计时器状态
self.save_timer_state()
# 清除当前界面
self.clear_timers()
# 重新加载数据
self.load_data()
# 恢复计时器状态
self.restore_timers()
def open_editor(self):
if self.editor is None or not self.editor.isVisible():
self.editor = MVPJsonEditor()
self.editor.show()
def handle_mouse_event(self, event, timer, mvp, overlay_rect, countdown_item, outline_items, info_item):
if event.button() == Qt.RightButton:
self.prompt_for_kill_time(timer, mvp, overlay_rect, countdown_item, outline_items)
elif event.button() == Qt.LeftButton:
if not timer.isActive():
# 启动计时器
mvp['current_time'] = mvp['time']
self.start_timer(timer, mvp, overlay_rect, countdown_item, outline_items)
# 在计时器启动时保存状态
self.save_timer_state()
else:
# 停止计时器
timer.stop()
overlay_rect.setVisible(False)
countdown_item.setVisible(False)
for outline in outline_items:
outline.setVisible(False)
self.delete_timer_state(mvp)
# 重置 current_time 以便下次点击时重新开始计时
mvp['current_time'] = mvp['time']
# 更新信息文本
self.update_info_text(info_item, countdown_item, outline_items, mvp)
def prompt_for_kill_time(self, timer, mvp, overlay_rect, countdown_item, outline_items):
text, ok = QInputDialog.getText(self, '输入时间', '请输入BOSS被击杀时间(0000或00:00)')
if ok:
try:
# 处理不带冒号的时间格式
if len(text) == 4 and text.isdigit():
text = text[:2] + ':' + text[2:]
kill_time = QTime.fromString(text, "hh:mm")
if not kill_time.isValid():
raise ValueError("Invalid time format")
current_time = QTime.currentTime()
elapsed_seconds = kill_time.secsTo(current_time)
remaining_seconds = mvp['time'] - elapsed_seconds
if remaining_seconds > 0:
mvp['current_time'] = remaining_seconds
self.start_timer(timer, mvp, overlay_rect, countdown_item, outline_items)
else:
QMessageBox.warning(self, "警告", "输入的时间已过期")
except Exception as e:
QMessageBox.critical(self, "错误", f"时间格式错误: {e}")
def toggle_timer(self, timer, mvp, overlay_rect, countdown_item, outline_items, info_item):
if timer.isActive():
timer.stop()
overlay_rect.setVisible(False)
countdown_item.setVisible(False)
for outline in outline_items:
outline.setVisible(False)
else:
self.start_timer(timer, mvp, overlay_rect, countdown_item, outline_items)
def start_timer(self, timer, mvp, overlay_rect, countdown_item, outline_items):
if not timer.isActive():
timer.start()
overlay_rect.setVisible(True)
countdown_item.setVisible(True)
for outline in outline_items:
outline.setVisible(True)
def update_timer(self, item, mvp, timer, overlay_rect, countdown_item, outline_items, scene):
mvp['current_time'] -= 1
if mvp['current_time'] <= 0:
if timer.isActive():
timer.stop()
overlay_rect.setVisible(False)
countdown_item.setVisible(False)
for outline in outline_items:
outline.setVisible(False)
self.show_notification(mvp['name'], mvp['mvpid'])
self.delete_timer_state(mvp)
# 重置 current_time 以便下次点击时重新开始计时
mvp['current_time'] = mvp['time']
# 更新信息文本
self.update_info_text(item, countdown_item, outline_items, mvp)
def play_alert_with_delay(self, delay_seconds):
alert_file = 'alert.mp3'
if os.path.exists(alert_file):
time.sleep(delay_seconds) # 延迟指定的秒数
playsound(alert_file)
def show_notification(self, name, mvpid):
try:
message = f"{name}[{mvpid}] 的计时已结束!"
subprocess.run(['msg', '*', message], check=True)
self.text_to_speech(f"冲啊!{name}已经刷新!")
# 在单独的线程中播放 alert.mp3,并设置延迟
delay_seconds = 0.1 # 例如,延迟 0.1 秒
alert_thread = threading.Thread(target=self.play_alert_with_delay, args=(delay_seconds,))
alert_thread.start()
except Exception as e:
QMessageBox.critical(self, "错误", f"{name}[{mvpid}] 的计时通知发送失败: {e}")
def text_to_speech(self, text): # 文字转语音方法
engine = pyttsx3.init()
# 设置语音属性
voices = engine.getProperty('voices')
for voice in voices:
if 'zh' in voice.languages: # 查找支持中文的语音
engine.setProperty('voice', voice.id)
break
engine.say(text)
engine.runAndWait()
def format_time(self, seconds):
# 确保秒数是整数
seconds = int(seconds)
hours = seconds // 3600
minutes = (seconds % 3600) // 60
seconds = seconds % 60
return f"{hours:02}:{minutes:02}:{seconds:02}"
def save_timer_state(self):
try:
state = []
for _, mvp, timer, _, _, _ in self.timers:
if timer.isActive():
end_time = datetime.now() + timedelta(seconds=mvp['current_time'])
state.append({
'id': mvp['id'], # 使用唯一的 id
'end_time': end_time.isoformat(), # 保存绝对结束时间
'active': True # 记录计时器是否处于活动状态
})
else:
state.append({
'id': mvp['id'],
'current_time': mvp['current_time'],
'active': False
})
with open(self.state_file, 'w', encoding='utf-8') as file:
json.dump(state, file)
except Exception as e:
QMessageBox.critical(self, "错误", f"倒计时保存错误: {e}")
def restore_timers(self):
if os.path.exists(self.state_file):
with open(self.state_file, 'r', encoding='utf-8') as file:
state_list = json.load(file)
for state in state_list:
if isinstance(state, dict):
if 'end_time' in state:
end_time = datetime.fromisoformat(state['end_time'])
remaining_time = (end_time - datetime.now()).total_seconds()
else:
remaining_time = state.get('current_time', 0)
if remaining_time > 0:
for info_item, mvp, timer, overlay_rect, countdown_item, outline_items in self.timers:
if mvp['id'] == state.get('id'):
mvp['current_time'] = remaining_time
if state.get('active', False):
self.start_timer(timer, mvp, overlay_rect, countdown_item, outline_items)
else:
for info_item, mvp, timer, overlay_rect, countdown_item, outline_items in self.timers:
if mvp['id'] == state.get('id'):
self.delete_timer_state(mvp)
break
def delete_timer_state(self, mvp):
if os.path.exists(self.state_file):
with open(self.state_file, 'r', encoding='utf-8') as file:
state_list = json.load(file)
state_list = [state for state in state_list if state.get('id') != mvp['id']] # 使用唯一的 id
with open(self.state_file, 'w', encoding='utf-8') as file:
json.dump(state_list, file)
def update_info_text(self, info_item, countdown_item, outline_items, mvp):
formatted_time = self.format_time(mvp['current_time'])
# 更新信息文本
info_item.setHtml(f"""
<div style="
color: black;
background-color: white;
border: 1px solid black;
font-size: 12px;
text-align: center;">
{mvp['name']}[{mvp['mvpid']}]<br>{mvp['loc']}<br>{formatted_time}
</div>
""")
# 更新倒计时文本
countdown_item.setHtml(f"""
<div style="
color: red;
font-size: 22px;
text-align: center;">
{formatted_time}
</div>
""")
for outline in outline_items:
outline.setHtml(f"""
<div style="
color: white;
font-size: 22px;
text-align: center;">
{formatted_time}
</div>
""")
def add_text_with_outline(scene, text, x, y, width, align='center', font_size=10, color='black', x_offset=0):
# 计算居中位置
text_width = width
centered_x = (150 - text_width) / 2 + x_offset # 150 是视图的宽度
# 创建描边效果
offsets = [(-1, -1), (1, 1), (1, -1), (-1, 1), (0, 1), (1, 0), (-1, 0), (0, -1)]
outline_items = []
for dx, dy in offsets:
outline_item = QGraphicsTextItem()
outline_item.setHtml(f"""
<div style="
color: white;
font-size: {font_size}px;
text-align: {align};
width: {width}px;">
{text}
</div>
""")
outline_item.setPos(centered_x + dx, y + dy)
scene.addItem(outline_item)
outline_items.append(outline_item)
# 创建主文本
text_item = QGraphicsTextItem()
text_item.setHtml(f"""
<div style="
color: {color};
font-size: {font_size}px;
text-align: {align};
width: {width}px;">
{text}
</div>
""")
text_item.setPos(centered_x, y)
scene.addItem(text_item)
return text_item, outline_items
if __name__ == '__main__':
app = QApplication(sys.argv)
# 设置应用程序图标
app.setWindowIcon(QIcon('app_icon.ico'))
ex = MVPTimer()
ex.show()
ex.center_window() # 在窗口显示后调用居中方法
sys.exit(app.exec())
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化