diff --git a/.gitignore b/.gitignore index 1f16fe0..36d9063 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,12 @@ /credentials /gui /ui_design/source +/ui_design/images +/ui_design/ui_design .idea/ *.DS_Store *.pyc *.orig __pycache__/ +env/ +venv/ \ No newline at end of file diff --git a/TUTORIAL.md b/TUTORIAL.md index b0184f9..5f8be5f 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -60,7 +60,7 @@ In the prompt Powershell console, input `python main.py`, then you can run the c # Windows 下 Python 运行环境搭建教程 -## 安装Anaconda Python 2.x +## 安装Anaconda Python 3.x 点击 https://www.anaconda.com/download/ , 64位电脑按如下选择: diff --git a/main.py b/main.py index 7ce8fa3..1abcc34 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -import ora.gui as Gui +import ora.gui_ as Gui import multiprocessing as mp def main(): diff --git a/ora/command_line.py b/ora/command_line.py index 700c940..5a3f6c8 100644 --- a/ora/command_line.py +++ b/ora/command_line.py @@ -4,8 +4,8 @@ from . import overwatch as OW from . import game from . import pool -from json import load from sys import argv +import json def log(*args): @@ -15,30 +15,34 @@ def log(*args): class Program(object): def __init__(self): self.game_instance = None - self.argv = argv + self.argv = argv[:4] + self.msg = self._msg() + self.data = self._get_data_to_dict(argv) - def _get_data(self, key): - """ - Extract key from sys.argv, then delete the key - """ - key += '=' - key_index = len(key) - for i, s in enumerate(self.argv): - if s[:key_index] == key: - string = s[key_index:] - del self.argv[i] - return string - return None - - def _user_input(self): - """ - Retrieving info from user input in command line - """ - # Getting fps - if 'help' in self.argv: - print(""" + def _get_data_to_dict(self, argv): + if 'help' in argv: + print(self.msg.get('help')) + exit(0) + if len(self.argv) < 4: + raise ValueError(self.msg.get('need_help')) + + result = {} + argv = argv[4:] + if argv: + for i, s in enumerate(argv): + if '=' not in s: + raise ValueError('<{}> {}'.format(s, self.msg.get('lack'))) + k, v = s.split('=') + result[k] = v + return result + return result + + @staticmethod + def _msg(): + result = {} + result['help'] = """ Example: - main_cli.exe players=player.txt fps=2 start_time=0 end_time=0 + main_cli.exe version=0 players=player.txt fps=2 start_time=0 end_time=0 Mandatory arguments: Absolute path of video(example: F:/video.mp4) @@ -46,67 +50,77 @@ def _user_input(self): A number representing game type. 0: OWL, 1: Non-OWL Optional: + version=0 OWL stage number(0=preseason, 1, 2, 3, 4) players=players.txt A text file saving info of all 12 players with JSON formatting fps=2 FPS of analyzer (2 by default) start_time=0 Starting time in seconds end_time=0 Ending time in seconds (If both are 0, then the whole video is analyzed) - """) - exit(0) - fps = self._get_data('fps') + """ + result['need_help'] = """ + Please input with proper amount of arguments. + If you need any help, please type: main_cli.py help + """ + result['lack'] = "optional lack of token '='" + result['json'] = """ + JSON format: + { + "right": { + "players": [ + "player7", + "player8", + "player9", + "player10", + "player11", + "player12" + ], + "team": "Team B" + }, + "left": { + "players": [ + "player1", + "player2", + "player3", + "player4", + "player5", + "player6", + ], + "team": "Team A" + } + } + """ + return result + + def _user_input(self): + """ + Retrieving info from user input in command line + """ info = { - 'fps': 2 if fps is None else fps, - '_player': self._get_data('player'), - 'start_time': self._get_data('start_time') or 0, - 'end_time': self._get_data('end_time') or 0, + 'fps': self.data.get('fps', 2), + '_players': self.data.get('players'), + 'start_time': self.data.get('start_time', 0), + 'end_time': self.data.get('end_time', 0), + 'game_version': self.data.get('version', 0), + 'video_path': self.argv[1], + 'output_path': self.argv[2], + 'game_type': self.argv[3] } - if len(self.argv) <= 3 or len(self.argv) > 4: - raise ValueError(""" - Please input with proper amount of arguments. - 如需帮助请输入 main_cli.py help - """) - info['video_path'] = self.argv[1] - info['output_path'] = self.argv[2] - info['game_type'] = self.argv[3] return info def info(self): info = self._user_input() - player_path = info.pop('_player') + player_path = info.pop('_players') if player_path is None: info['name_team_left'] = 'Team A' info['name_team_right'] = 'Team B' info['name_players_team_left'] = ['player' + str(i) for i in range(1, 7)] info['name_players_team_right'] = ['player' + str(i) for i in range(7, 13)] else: - with open(player_path, 'r') as f: - data = load(f) - """ - JSON format: - { - "right": { - "players": [ - "player7", - "player8", - "player9", - "player10", - "player11", - "player12" - ], - "team": "Team B" - }, - "left": { - "players": [ - "player1", - "player2", - "player3", - "player4", - "player5", - "player6", - ], - "team": "Team A" - } - } - """ + try: + with open(player_path, 'r') as f: + data = json.load(f) + except json.decoder.JSONDecodeError: + print(self.msg.get('json')) + exit(0) info['name_team_left'] = data['left']['team'] info['name_team_right'] = data['right']['team'] info['name_players_team_left'] = data['left']['players'] @@ -140,6 +154,15 @@ def info(self): if not (info['game_type'] == 0 or info['game_type'] == 1): raise ValueError('Invalid game type!') + + try: + print(info['game_version']) + info['game_version'] = int(info['game_version']) + except ValueError: + log('Invalid OWL stage number!') + + if info['game_type'] == 0 and info['game_version'] not in [0, 1, 2, 3, 4]: + raise ValueError('Invalid OWL stage number!') return info def run(self): @@ -150,8 +173,7 @@ def run(self): self.game_instance.analyze(info['start_time'], info['end_time'], is_test=False) pool.PROCESS_POOL.close() pool.PROCESS_POOL.join() - self.game_instance.output_to_json() - self.game_instance.output_to_excel() + self.game_instance.output() log('ok') program = Program() diff --git a/ora/gui/__init__.py b/ora/gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ora/gui/default.py b/ora/gui/default.py new file mode 100644 index 0000000..5990880 --- /dev/null +++ b/ora/gui/default.py @@ -0,0 +1,6 @@ + +COPY_RIGHT = ''' + COPYRIGHT + + ''' + diff --git a/ora/gui/functions.py b/ora/gui/functions.py new file mode 100644 index 0000000..ec3ac32 --- /dev/null +++ b/ora/gui/functions.py @@ -0,0 +1,51 @@ +from os.path import join + +from PyQt5.Qt import QIcon, QSize +from PyQt5.QtWidgets import QFileDialog + +SRC_PATH = './ora/gui/images' + + +def dynamic_base_class(instance, cls, cls_name): + instance.__class__ = type(cls_name, (cls, instance.__class__), {}) + return instance + + +def set_background_img(widget, file_name, path='/bgs/'): + widget.setStyleSheet("background-image: url(%s)" % (SRC_PATH + path + file_name)) + + +def set_background_color(widget, color): + widget.setStyleSheet("background-color: %s" % color) + + +def set_plain_text(widget, text): + widget.setPlainText(text) + + +def pic_to_icon(file_name, path='icons'): + return QIcon(join(SRC_PATH, path, file_name)) + + +def set_full_icon(widget, file_name, path='icons'): + qicon = pic_to_icon(file_name, path) + widget.setIcon(qicon) + widget.setIconSize(QSize(100, 100)) + + +def remove_listwidget_item(listwidget): + listwidget.takeItem(listwidget.currentRow()) + +def open_file_dialog(parent): + filename, _ = QFileDialog.getOpenFileName(parent, u'select video') + return filename + +def set_qclass_child_widgets_style(widget, qclass, style): + for c in get_qclass_child_widgets(widget, qclass): + c.setStyleSheet(style) + + +def get_qclass_child_widgets(widget, qclass): + for c in widget.children(): + if isinstance(c, qclass): + yield c \ No newline at end of file diff --git a/ora/gui/gui.py b/ora/gui/gui.py new file mode 100644 index 0000000..3da0f21 --- /dev/null +++ b/ora/gui/gui.py @@ -0,0 +1,236 @@ + +from PyQt5 import uic, QtCore, QtWidgets, QtGui +from widget import * +from style import * +from functions import * +from gui_api import * + +QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) +windowui, QtBaseClass = uic.loadUiType('./ora/gui/main.ui') + +SRC_PATH = './ora/gui/images' + + +class UiFunc(object): + @staticmethod + def _add_list_item(listwidget, item_class, *args): + citem = item_class(listwidget, *args) + item = QtWidgets.QListWidgetItem(listwidget) + item.setSizeHint(citem.sizeHint()) + listwidget.addItem(item) + listwidget.setItemWidget(item, citem) + + @staticmethod + def _add_widget_item(parent, layout, widget_class, geometry, *args, **kwargs): + widget = widget_class(parent, *args, **kwargs) + widget.setGeometry(*geometry) + if not layout: + layout = parent.layout + layout.addWidget(widget) + return widget + + @staticmethod + def _add_custome_item(listwidget, item_class, *args): + citem = item_class(listwidget, *args) + # citem.setObjectName('DyItem') + item = QtWidgets.QListWidgetItem(listwidget) + item.setSizeHint(citem.sizeHint()) + listwidget.addItem(item) + listwidget.setItemWidget(item, citem) + + @staticmethod + def _set_hover_icon(widget, normal_file, hover_file): + set_full_icon(widget, normal_file, 'widgets') + widget.setAttribute(Qt.WA_Hover, True) + widget.setStyleSheet('background-image: url(%s)' % (SRC_PATH + '/widgets/' + hover_file)) + + +class BeautiUi(windowui, WindowDragMixin, ControlButtonMixin): + def __init__(self): + super(BeautiUi, self).__init__() + self.setupUi(self) + + self.setWindowFlags(Qt.FramelessWindowHint) + self.set_control_button(self.min_button, self.min_button, self.close_button) + + self.tab_listwidget.setFrameShape(QtWidgets.QFrame.NoFrame) + # self.tab_listwidget.setSpacing(50) + self.video_listwidget.setFrameShape(QtWidgets.QFrame.NoFrame) + + self._set_style() + + set_full_icon(self.publish_box, 'switch_on.png') + + def _set_style(self): + for wi, bg in background_imgs.items(): + set_background_img(getattr(self, wi), bg) + + for wi, ic in button_icons.items(): + set_full_icon(getattr(self, wi), ic) + + for wi, cl in background_colors.items(): + set_background_color(getattr(self, wi), cl) + + for wi, tx in plain_text.items(): + set_plain_text(getattr(self, wi), tx) + + for label in get_qclass_child_widgets(self.stackedWidgetPage1, QtWidgets.QLineEdit): + pass + + +class MainUi(QtWidgets.QMainWindow, BeautiUi, UiFunc): + def __init__(self): + super(MainUi, self).__init__() + self._set_tab_listwidget_items([['1_normal.png', '1_selected.png'], ['2_normal.png', '2_selected.png'], ['3_normal.png', '3_selected.png']]) + self.tab_listwidget.setIconSize(QSize(100, 100)) + self._init_widget() + self._init_connect() + self._init_property() + self._init_default() + + def _init_widget(self): + self._add_custome_item(self.video_listwidget, VideoItem, '', '/path/1.mp4', 'WAITING', 'Shanghai Dragons', 'Dallas Fuel', 'replay.png') + self._add_custome_item(self.video_listwidget, VideoItem, '', '/path/1.mp4', 'RUNNING', 'Shanghai Dragons', 'Dallas Fuel', 'replay.png') + + def _init_connect(self): + self.tab_listwidget.itemClicked.connect(self._tab_listwidget_item_clicked) + self.video_listwidget.customContextMenuRequested.connect(self._video_list_context_menu) + + self.team_left_lineedit.editingFinished.connect(lambda: self._team_name_edit_finished('left')) + self.team_right_lineedit.editingFinished.connect(lambda: self._team_name_edit_finished('right')) + + self.save_button.clicked.connect(self.save_button_clicked) + self.analyze_button.clicked.connect(self.analyze_button_clicked) + self.analyze_button.clicked.connect(self.select_video) # TODO: need connect to add video button + + def _init_property(self): + + # set player's text to property like self.player_left0_text + for widget in get_qclass_child_widgets(self.team_setting_group, QtWidgets.QLineEdit): + name = widget.objectName + if name.startswith('player_'): + setattr(self, name.rstrip('lineedit') + 'text', widget.text()) + + def _init_default(self): + self.tab_listwidget.setCurrentRow(0) + self.main_stackedwidget.setCurrentIndex(0) + + def _set_tab_listwidget_items(self, pics): + for pic in pics: + item = QtWidgets.QListWidgetItem(QtGui.QIcon(SRC_PATH + '/tab_icons/' + pic[0]), '') + item.normal_icon = QtGui.QIcon(SRC_PATH + '/tab_icons/' + pic[0]) + item.selected_icon = QtGui.QIcon(SRC_PATH + '/tab_icons/' + pic[1]) + self.tab_listwidget.addItem(item) + + def _set_tab_listwidget_normal_icon(self): + for i in range(0, self.tab_listwidget.count()): + item = self.tab_listwidget.item(i) + item.setIcon(item.normal_icon) + + def _tab_listwidget_item_clicked(self, item): + self.main_stackedwidget.setCurrentIndex(self.tab_listwidget.currentRow()) + self._set_tab_listwidget_normal_icon() + item.setIcon(item.selected_icon) + + def _video_list_context_menu(self): + menu = QtWidgets.QMenu() + menu.addAction('Delete', lambda: remove_listwidget_item(self.video_listwidget)) + menu.addAction('Add', self._select_and_add_video) + menu.exec_(QtGui.QCursor.pos()) + + def _select_and_add_video(self): + filename = open_file_dialog(self) + return self._add_video(filename) + + def _add_video(self, filename): + print(filename) + + def _team_name_edit_finished(self, team='left'): + current_video_item = self.current_video_item + setattr(current_video_item, 'set_team_%s_text' % team, getattr(self, 'input_team_%s_text' % team)) + + def select_video(self): + filename, _ = QtWidgets.QFileDialog.getOpenFileName(self, u'Select Video', filter="* (*.*)") + self._set_new_video(filename) + + def _set_new_video(self, path): + self._add_custome_item(self.video_listwidget, VideoItem, path, path, 'WAITING', 'Shanghai Dragons', + 'Dallas Fuel', 'replay.png') + + def save_button_clicked(self): + dirname = QtWidgets.QFileDialog.getExistingDirectory(self, u'Select Folder') + self.path_lineedit.setText(dirname) + + def analyze_button_clicked(self): + return analyze_button_clicked(self.is_owl, self.game_info) + + @property + def input_team_left_text(self): + return self.team_left_lineedit.text() + + @property + def input_team_right_text(self): + return self.team_right_lineedit.text() + + @property + def input_teams_left_players(self): + return [getattr(self, 'player_left%s_lineedit' % i) for i in range(0, 7)] + + @property + def input_teams_right_players(self): + return [getattr(self, 'player_right%s_lineedit' % i) for i in range(0, 7)] + + @property + def current_video_item(self): + item = self.video_listwidget.currentItem() + return self.video_listwidget.itemWidget(item) + + @property + def is_published(self): + return self.publish_box.isChecked() + + @property + def is_owl(self): + return True if self.type_owl_radiobutton.isChecked() else False + + @property + def start_time(self): + return self.start_time_lineedit.text() + + @property + def end_time(self): + return self.end_time_lineedit.text() + + @property + def video_path_text(self): + return self.path_lineedit.text() + + @property + def output_path_text(self): + return self.path_lineedit.text() + + @property + def game_info(self): + info = { + "name_team_left": self.input_team_left_text, + "name_team_right": self.input_team_right_text, + "name_players_team_left": self.input_teams_left_players, + "name_players_team_right": self.input_teams_right_players, + "video_path": self.video_path_text, + "output_path": self.output_path_text, + "start_time": self.start_time, + "end_time": self.end_time, + "fps": 0, + "game_type": 0, + "game_version": 1 + } + + return info + +if __name__ == '__main__': + app = QtWidgets.QApplication(sys.argv) + with open('style.qss') as qss: + app.setStyleSheet(qss.read()) + w = MainUi() + w.show() + sys.exit(app.exec_()) \ No newline at end of file diff --git a/ora/gui/gui_api.py b/ora/gui/gui_api.py new file mode 100644 index 0000000..ef46a41 --- /dev/null +++ b/ora/gui/gui_api.py @@ -0,0 +1,56 @@ + +from ora import game +from ora import pool +from ora import overwatch as OW + + +def get_video_info(video_path): + """ + Get video info show in video list. + :param video_path: + :return: teamname1, teamname2, length + + """ + pass + + +def publish_analysis(): + """Run when checked 'Publish your analysis'""" + pass + + +def analyze_button_clicked(is_owl, info): + """ + Run when click 'ANALYZE' button + You should add args as need. + """ + is_owl = OW.GAMETYPE_OWL if is_owl else OW.GAMETYPE_CUSTOM + game_instance = game.Game(is_owl) + game_instance.set_game_info(info) + pool.initPool() + try: + game_instance.analyze(info['start_time'], info['end_time'], is_test=False) + except Exception as err: + print(err) + else: + game_instance.output() + + pool.PROCESS_POOL.close() + pool.PROCESS_POOL.join() + + +def save_button_clicked(*args): + """ + Run when click 'SAVE' button + You should add args as need. + """ + pass + + +def auto_check_update_checked(): + """Run when checked 'Automatically check for update'""" + pass + + + + diff --git a/ora/gui/images/bgs/analyze_bg.png b/ora/gui/images/bgs/analyze_bg.png new file mode 100644 index 0000000..51d1816 Binary files /dev/null and b/ora/gui/images/bgs/analyze_bg.png differ diff --git a/ora/gui/images/bgs/column_title_bg.png b/ora/gui/images/bgs/column_title_bg.png new file mode 100644 index 0000000..6d3c5bd Binary files /dev/null and b/ora/gui/images/bgs/column_title_bg.png differ diff --git a/ora/gui/images/bgs/left_bg.png b/ora/gui/images/bgs/left_bg.png new file mode 100644 index 0000000..85f89dd Binary files /dev/null and b/ora/gui/images/bgs/left_bg.png differ diff --git a/ora/gui/images/bgs/left_bg_shadow.png b/ora/gui/images/bgs/left_bg_shadow.png new file mode 100644 index 0000000..110be63 Binary files /dev/null and b/ora/gui/images/bgs/left_bg_shadow.png differ diff --git a/ora/gui/images/bgs/right_bg.png b/ora/gui/images/bgs/right_bg.png new file mode 100644 index 0000000..f88bd52 Binary files /dev/null and b/ora/gui/images/bgs/right_bg.png differ diff --git a/ora/gui/images/bgs/top_bg.png b/ora/gui/images/bgs/top_bg.png new file mode 100644 index 0000000..e45f0c8 Binary files /dev/null and b/ora/gui/images/bgs/top_bg.png differ diff --git a/ora/gui/images/icons/design_03.png b/ora/gui/images/icons/design_03.png new file mode 100644 index 0000000..7092b52 Binary files /dev/null and b/ora/gui/images/icons/design_03.png differ diff --git a/ora/gui/images/icons/design_05.png b/ora/gui/images/icons/design_05.png new file mode 100644 index 0000000..d04d24a Binary files /dev/null and b/ora/gui/images/icons/design_05.png differ diff --git a/ora/gui/images/icons/design_07.png b/ora/gui/images/icons/design_07.png new file mode 100644 index 0000000..86f9b6b Binary files /dev/null and b/ora/gui/images/icons/design_07.png differ diff --git a/ora/gui/images/icons/design_08.png b/ora/gui/images/icons/design_08.png new file mode 100644 index 0000000..51fa369 Binary files /dev/null and b/ora/gui/images/icons/design_08.png differ diff --git a/ora/gui/images/icons/design_09.png b/ora/gui/images/icons/design_09.png new file mode 100644 index 0000000..84610e7 Binary files /dev/null and b/ora/gui/images/icons/design_09.png differ diff --git a/ora/gui/images/icons/info-icon.png b/ora/gui/images/icons/info-icon.png new file mode 100644 index 0000000..c795198 Binary files /dev/null and b/ora/gui/images/icons/info-icon.png differ diff --git a/ora/gui/images/tab_icons/1_normal.png b/ora/gui/images/tab_icons/1_normal.png new file mode 100644 index 0000000..271e6e6 Binary files /dev/null and b/ora/gui/images/tab_icons/1_normal.png differ diff --git a/ora/gui/images/tab_icons/1_selected.png b/ora/gui/images/tab_icons/1_selected.png new file mode 100644 index 0000000..201fbcb Binary files /dev/null and b/ora/gui/images/tab_icons/1_selected.png differ diff --git a/ora/gui/images/tab_icons/2_normal.png b/ora/gui/images/tab_icons/2_normal.png new file mode 100644 index 0000000..c2157fd Binary files /dev/null and b/ora/gui/images/tab_icons/2_normal.png differ diff --git a/ora/gui/images/tab_icons/2_selected.png b/ora/gui/images/tab_icons/2_selected.png new file mode 100644 index 0000000..a3d89bf Binary files /dev/null and b/ora/gui/images/tab_icons/2_selected.png differ diff --git a/ora/gui/images/tab_icons/3_normal.png b/ora/gui/images/tab_icons/3_normal.png new file mode 100644 index 0000000..87186e8 Binary files /dev/null and b/ora/gui/images/tab_icons/3_normal.png differ diff --git a/ora/gui/images/tab_icons/3_selected.png b/ora/gui/images/tab_icons/3_selected.png new file mode 100644 index 0000000..0d12817 Binary files /dev/null and b/ora/gui/images/tab_icons/3_selected.png differ diff --git a/ora/gui/images/widgets/button_analyze.png b/ora/gui/images/widgets/button_analyze.png new file mode 100644 index 0000000..9cb94f9 Binary files /dev/null and b/ora/gui/images/widgets/button_analyze.png differ diff --git a/ora/gui/images/widgets/button_analyze_onhover.png b/ora/gui/images/widgets/button_analyze_onhover.png new file mode 100644 index 0000000..ce63d33 Binary files /dev/null and b/ora/gui/images/widgets/button_analyze_onhover.png differ diff --git a/ora/gui/images/widgets/button_save.png b/ora/gui/images/widgets/button_save.png new file mode 100644 index 0000000..0116e81 Binary files /dev/null and b/ora/gui/images/widgets/button_save.png differ diff --git a/ora/gui/images/widgets/button_save_onhover.png b/ora/gui/images/widgets/button_save_onhover.png new file mode 100644 index 0000000..ec1a5de Binary files /dev/null and b/ora/gui/images/widgets/button_save_onhover.png differ diff --git a/ora/gui/images/widgets/radiobox_normal.png b/ora/gui/images/widgets/radiobox_normal.png new file mode 100644 index 0000000..787a997 Binary files /dev/null and b/ora/gui/images/widgets/radiobox_normal.png differ diff --git a/ora/gui/images/widgets/radiobox_normal_f.png b/ora/gui/images/widgets/radiobox_normal_f.png new file mode 100644 index 0000000..5bb7ea9 Binary files /dev/null and b/ora/gui/images/widgets/radiobox_normal_f.png differ diff --git a/ora/gui/images/widgets/radiobox_selected.png b/ora/gui/images/widgets/radiobox_selected.png new file mode 100644 index 0000000..26bcf91 Binary files /dev/null and b/ora/gui/images/widgets/radiobox_selected.png differ diff --git a/ora/gui/images/widgets/radiobox_selected_f.png b/ora/gui/images/widgets/radiobox_selected_f.png new file mode 100644 index 0000000..7fea800 Binary files /dev/null and b/ora/gui/images/widgets/radiobox_selected_f.png differ diff --git a/ora/gui/images/widgets/switch_off.png b/ora/gui/images/widgets/switch_off.png new file mode 100644 index 0000000..c24ff6b Binary files /dev/null and b/ora/gui/images/widgets/switch_off.png differ diff --git a/ora/gui/images/widgets/switch_off_f.png b/ora/gui/images/widgets/switch_off_f.png new file mode 100644 index 0000000..2450c4a Binary files /dev/null and b/ora/gui/images/widgets/switch_off_f.png differ diff --git a/ora/gui/images/widgets/switch_on.png b/ora/gui/images/widgets/switch_on.png new file mode 100644 index 0000000..c0f6133 Binary files /dev/null and b/ora/gui/images/widgets/switch_on.png differ diff --git a/ora/gui/images/widgets/switch_on_f.png b/ora/gui/images/widgets/switch_on_f.png new file mode 100644 index 0000000..0cd3d0f Binary files /dev/null and b/ora/gui/images/widgets/switch_on_f.png differ diff --git a/ora/gui/images/widgets/video_container.png b/ora/gui/images/widgets/video_container.png new file mode 100644 index 0000000..4d80d7d Binary files /dev/null and b/ora/gui/images/widgets/video_container.png differ diff --git a/ora/gui/main.ui b/ora/gui/main.ui new file mode 100644 index 0000000..e610754 --- /dev/null +++ b/ora/gui/main.ui @@ -0,0 +1,966 @@ + + + MainWindow + + + + 0 + 0 + 1085 + 790 + + + + MainWindow + + + + + + 120 + 95 + 973 + 701 + + + + + + + main_stackedwidget + + + Qt::LeftToRight + + + QFrame::NoFrame + + + 0 + + + + stackedWidgetPage1 + + + + + 0 + 0 + 700 + 645 + + + + + 8 + + + + Qt::CustomContextMenu + + + video_listwidget + + + QFrame::Plain + + + + 30 + 30 + + + + true + + + true + + + + + + 712 + 296 + 113 + 20 + + + + false + + + + + + 842 + 296 + 113 + 20 + + + + false + + + + + + 842 + 326 + 113 + 20 + + + + false + + + + + + 712 + 326 + 113 + 20 + + + + false + + + + + + 842 + 356 + 113 + 20 + + + + false + + + + + + 712 + 356 + 113 + 20 + + + + false + + + + + + 712 + 62 + 113 + 20 + + + + false + + + false + + + + + + 840 + 62 + 113 + 20 + + + + false + + + + + + 712 + 448 + 113 + 20 + + + + false + + + + + + 712 + 388 + 113 + 20 + + + + false + + + + + + 842 + 448 + 113 + 20 + + + + false + + + + + + 712 + 418 + 113 + 20 + + + + false + + + + + + 842 + 418 + 113 + 20 + + + + false + + + + + + 842 + 388 + 113 + 20 + + + + false + + + + + + 712 + 480 + 113 + 20 + + + + false + + + + + + 842 + 480 + 113 + 20 + + + + false + + + + + + 842 + 40 + 54 + 16 + + + + End at + + + + + + 714 + 98 + 93 + 16 + + + + Type of game + + + + + + 818 + 98 + 43 + 16 + + + + OWL + + + + + + 886 + 98 + 65 + 16 + + + + Custom + + + + + + 714 + 124 + 131 + 16 + + + + Publish your analysis + + + + + + 886 + 122 + 63 + 31 + + + + + + + false + + + + + + 712 + 222 + 113 + 20 + + + + false + + + + + + 842 + 222 + 113 + 20 + + + + false + + + + + + 712 + 200 + 73 + 16 + + + + + + + Team Left + + + + + + 846 + 198 + 67 + 16 + + + + + + + Team Right + + + + + + 714 + 270 + 89 + 16 + + + + + + + Players Left + + + + + + 842 + 268 + 89 + 16 + + + + + + + Players Right + + + + + + 700 + -2 + 271 + 35 + + + + video_title_group + + + + + + + + 2 + -2 + 265 + 35 + + + + + Segoe UI + 20 + 50 + false + + + + VIDEO SETTING + + + + + + + 700 + 30 + 271 + 133 + + + + video_setting_group + + + + + + + + 14 + 12 + 54 + 12 + + + + + + + Start at + + + + + + + 700 + 160 + 273 + 41 + + + + team_title_group + + + + + + + + 2 + 4 + 267 + 37 + + + + + Segoe UI + 20 + + + + TEAM SETTING + + + + + + + 700 + 200 + 271 + 502 + + + + team_setting_group + + + + + + + + + 0 + 645 + 700 + 50 + + + + + + + true + + + + + 610 + 12 + 80 + 28 + + + + analyze_button + + + + + + + + + 564 + 10 + 39 + 31 + + + + save_button + + + false + + + + + + + + + false + + + + + + 10 + 10 + 555 + 31 + + + + Save + + + false + + + save_button + analyze_button + path_lineedit + + team_setting_group + team_title_group + video_setting_group + video_title_group + video_listwidget + player_left0_lineedit + player_right0_lineedit + player_right1_lineedit + player_left1_lineedit + player_right2_lineedit + player_left2_lineedit + start_time_lineedit + end_time_lineedit + player_left5_lineedit + player_left3_lineedit + player_right5_lineedit + player_left4_lineedit + player_right4_lineedit + player_right3_lineedit + player_left6_lineedit + player_right6_lineedit + label_6 + label_7 + type_owl_radiobutton + type_custom_radiobutton + label_8 + publish_box + team_left_lineedit + team_right_lineedit + label_10 + label_11 + label_12 + label_13 + analyze_group + + + + + + 12 + 40 + 217 + 37 + + + + + Adobe Devanagari + 20 + + + + ORA Setting + + + + + + 16 + 128 + 93 + 16 + + + + Analyzer FPS + + + + + + 14 + 152 + 255 + 16 + + + + Automatically check for update + + + + + + + + 0 + 40 + 253 + 411 + + + + Copyright + + + + + + 270 + 52 + 113 + 97 + + + + + + + 396 + 52 + 113 + 97 + + + + + + + 274 + 8 + 103 + 41 + + + + Donation Info + + + + + + + + 120 + 0 + 976 + 95 + + + + top_group + + + + + + + + 0 + 0 + 65 + 80 + + + + + 20 + + + + + + + true + + + + + + 795 + 0 + 65 + 80 + + + + + 20 + + + + false + + + + + + false + + + false + + + true + + + + + + 860 + 0 + 65 + 80 + + + + + 20 + + + + min_button + + + + + + true + + + + + + 925 + 0 + 65 + 80 + + + + + 20 + + + + + + + true + + + + + + + 0 + 0 + 120 + 795 + + + + left_group + + + + + + + + 0 + 0 + 120 + 95 + + + + + Adobe 黑体 Std R + 25 + + + + LOGO + + + Qt::AlignCenter + + + + + + 0 + 95 + 120 + 670 + + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + -1 + + + + top_group + left_group + main_stackedwidget + + + + + diff --git a/ora/gui/mixin.py b/ora/gui/mixin.py new file mode 100644 index 0000000..86e9370 --- /dev/null +++ b/ora/gui/mixin.py @@ -0,0 +1,80 @@ +from functools import partial + +from PyQt5 import QtWidgets +from PyQt5.QtCore import Qt + +from functions import set_background_color + + +class MousePressChangeBackgroundMixin(QtWidgets.QWidget): + def __init__(self, normal_color="#143048", selected_color="#11293f"): + super(MousePressChangeBackgroundMixin, self).__init__() + self.setMouseTracking(True) + self.normal_color = normal_color + self.selected_color = selected_color + + @property + def _color(self): + return '#' + ''.join([str(hex(c)[2:]) for c in self.palette().color(self.backgroundRole()).toRgb().getRgb()[:3]]) + + def is_selected(self): + return True if self._color == self.selected_color else False + + def _set_color(self, color): + self.setStyleSheet("background-color: %s" % color) + for c in self.children(): + if isinstance(c, QtWidgets.QLabel): + c.setStyleSheet("background-color: %s" % color) + + def enterEvent(self, QMouseEvent): + pass + + def mouseMoveEvent(self, QMouseEvent): + pass + + def mousePressEvent(self, QMouseEvent): + color = self.normal_color if self.is_selected() else self.selected_color + self._set_color(color) + + def mouseReleaseEvent(self, QMouseEvent): + color = self._color + self._set_color(color) + + +class WindowDragMixin(object): + def mousePressEvent(self, event): + if event.button() == Qt.LeftButton: + self.m_drag = True + self.m_DragPosition = event.globalPos() - self.pos() + event.accept() + + def mouseMoveEvent(self, QMouseEvent): + if QMouseEvent.buttons() and Qt.LeftButton: + self.move(QMouseEvent.globalPos() - self.m_DragPosition) + QMouseEvent.accept() + + def mouseReleaseEvent(self, QMouseEvent): + self.m_drag = False + + +class ControlButtonMixin(QtWidgets.QWidget): + def __init__(self): + super(ControlButtonMixin, self).__init__() + + def set_control_button(self, min_button, max_button, exit_button, max_icon='', resize_icon='', bg_color='#000000'): + # self.max_button = max_button + # self.max_icon = max_icon + self.resize_icon = resize_icon + + # map(partial(set_background_color, color=bg_color), [min_button, max_button, exit_button]) + min_button.clicked.connect(self.showMinimized) + # max_button.clicked.connect(self._max_button_clicked) + exit_button.clicked.connect(self.close) + + def _max_button_clicked(self): + if self.isMaximized(): + self.showNormal() + self.max_button.setText(self.resize_icon) + else: + self.showMaximized() + self.max_button.setText(self.resize_icon) diff --git a/ora/gui/preview.png b/ora/gui/preview.png new file mode 100644 index 0000000..0c0eeed Binary files /dev/null and b/ora/gui/preview.png differ diff --git a/ora/gui/readme.md b/ora/gui/readme.md new file mode 100644 index 0000000..ad0dcd3 --- /dev/null +++ b/ora/gui/readme.md @@ -0,0 +1,5 @@ +The Gui with PyQt5 is under development…… + +You can run demo with: + +`Python gui.py` \ No newline at end of file diff --git a/ora/gui/replay.png b/ora/gui/replay.png new file mode 100644 index 0000000..b16d2f3 Binary files /dev/null and b/ora/gui/replay.png differ diff --git a/ora/gui/style.py b/ora/gui/style.py new file mode 100644 index 0000000..daea927 --- /dev/null +++ b/ora/gui/style.py @@ -0,0 +1,41 @@ + +from default import COPY_RIGHT + +background_imgs = { + 'left_group': 'left_bg.png', + #'top_group': 'top_bg.png', + #'analyze_group': 'analyze_bg.png', + 'video_title_group': 'column_title_bg.png', + 'team_title_group': 'column_title_bg.png', + 'video_setting_group': 'right_bg.png', + 'team_setting_group': 'right_bg.png', + #'left_shadow_label': 'left_bg_shadow.png', + 'video_listwidget': '' +} + +background_colors = { + 'video_listwidget': '#143048', + 'top_group': '#143048' +} + +button_icons = { + 'back_button': 'design_03', + 'help_button': 'design_05', + 'min_button': 'design_07', + 'close_button': 'design_09' +} + +text_colors = { + 'textUpLeftLabel': '#ffffff', + 'textUpRightLabel': '#3df0d0', + 'textDownLabelTeam1': '#c9d8e5', + 'textDownLabelTeam2': '#c9d8e5', + 'textDownLeftLabel': '#80bce2', + 'textDownRightLabel': '#80bce2' + +} + +# set widget text with setPlainText() +plain_text = { + 'copy_right_text_edit': COPY_RIGHT, +} \ No newline at end of file diff --git a/ora/gui/style.qss b/ora/gui/style.qss new file mode 100644 index 0000000..5d420d7 --- /dev/null +++ b/ora/gui/style.qss @@ -0,0 +1,80 @@ +QMainWindow { + background-color: rgb(34, 78, 116); +} +QStackedWidget { + background-color: rgb(34, 78, 116); +} + +QLineEdit { + background-color: rgb(23, 57, 86); + border-radius: 3px; + color: rgb(201, 216, 229); +} +QCheckBox { + color: rgb(201, 216, 229); +} +QStackedWidget { + background-color: rgb(13, 37, 59); +} +QLabel { + border: None; + color: rgb(201, 216, 229); +} +QGroupBox { + border: None; + +} +QPushButton { + border: None; +} +#publish_box::indicator:checked { + image: url(./ora/gui/images/widgets/switch_on_f.png); +} +#publish_box::indicator:unchecked { + image: url(./ora/gui/images/widgets/switch_off_f.png); +} +QRadioButton::indicator:unchecked{ + image: url(./ora/gui/images/widgets/radiobox_normal_f.png); +} +QRadioButton::indicator:checked{ + image: url(./ora/gui/images/widgets/radiobox_selected_f.png); +} +#analyze_button { + border-style: solid; + border-width: 2px; + border-color: rgb(37, 173, 231); + color: rgb(37, 173, 231); +} +#save_button { + image: url(./ora/gui/images/widgets/button_save.png); +} +#save_button:hover { + image: url(./ora/gui/images/widgets/button_save_onhover.png); +} +#analyze_button { + image: url(./ora/gui/images/widgets/button_analyze.png); +} +#analyze_button:hover { + image: url(./ora/gui/images/widgets/button_analyze_onhover.png); +} +#tab_listwidget::item { + min-height: 100px; +} +#top_group { + margin-bottom: 2px; + background-color: rgb(34, 78, 116); +} +#min_button { + margin-left: 2px; +} +#video_listwidget { + margin-right: 2px; + margin-bottom: 2px; +} +#stackedWidgetPage1 { + background-color: rgb(34, 78, 116); +} +#analyze_group { + background-color: rgb(21, 44, 62); + margin-right: 2px; +} \ No newline at end of file diff --git a/ora/gui/widget.py b/ora/gui/widget.py new file mode 100644 index 0000000..ef05b60 --- /dev/null +++ b/ora/gui/widget.py @@ -0,0 +1,228 @@ +import sys + +from PyQt5 import QtGui, QtCore +from PyQt5.Qt import QSize + +from style import text_colors +from mixin import * + + +class ListWidget(QtWidgets.QListWidget): + def __init__(self, parent=None): + super(ListWidget, self).__init__(parent) + self.setFrameShape(QtWidgets.QFrame.NoFrame) + + +class StackedWidget(QtWidgets.QStackedWidget): + def __init__(self, parent=None): + super(StackedWidget, self).__init__(parent) + + +class VideoItem(MousePressChangeBackgroundMixin): + def __init__(self, parent=None, path='', up_left_str='', up_right_str='', down_left_str='', down_right_str='', icon_path=''): + """ + The video item class. You can set text/icon with args when instantiate or by call obj.set_path_text, obj.set_icon, etc. + :param parent: + :param up_left_str: Path text + :param up_right_str: Status text + :param down_left_str: Team 1 name + :param down_right_str: Team 2 name + :param icon_path: The item icon path + """ + super(VideoItem, self).__init__() + print(path) + + self.textUpLayout = QtWidgets.QHBoxLayout() + self.textUpLeftLabel = FullLabel() + self.textUpRightLabel = QtWidgets.QLabel() + self.textUpLayout.addWidget(self.textUpLeftLabel) + self.textUpLayout.addWidget(self.textUpRightLabel) + + self.textDownLayout = QtWidgets.QHBoxLayout() + self.textDownLabelTeam1 = QtWidgets.QLabel() + self.textDownLeftLabel = QtWidgets.QLabel() + self.textDownLabelTeam2 = QtWidgets.QLabel() + self.textDownRightLabel = QtWidgets.QLabel() + self.textDownLayout.addWidget(self.textDownLabelTeam1) + self.textDownLayout.addWidget(self.textDownLeftLabel) + self.textDownLayout.addWidget(self.textDownLabelTeam2) + self.textDownLayout.addWidget(self.textDownRightLabel) + + self.allTextLayout = QtWidgets.QVBoxLayout() + self.allTextLayout.addLayout(self.textUpLayout) + self.allTextLayout.addLayout(self.textDownLayout) + + self.allQHBoxLayout = QtWidgets.QHBoxLayout() + self.iconQLabel = QtWidgets.QLabel() + self.iconQLabel.setFixedSize(200, 110) + self.allQHBoxLayout.addWidget(self.iconQLabel, 0) + self.allQHBoxLayout.addLayout(self.allTextLayout, 1) + self.allQHBoxLayout.setSpacing(0) + self.allQHBoxLayout.setContentsMargins(0, 0, 0, 0) + self.setLayout(self.allQHBoxLayout) + + self._set_label_text(self.textUpLeftLabel, up_left_str) + self._set_label_text(self.textUpRightLabel, up_right_str) + self._set_label_text(self.textDownLabelTeam1, "TEAM 1") + self._set_label_text(self.textDownLeftLabel, down_left_str) + self._set_label_text(self.textDownLabelTeam2, "TEAM 2") + self._set_label_text(self.textDownRightLabel, down_right_str) + + self.setStyleSheet('border: None') + + if icon_path: + # self.setIcon(icon_path) + self.iconQLabel.setPixmap(QtGui.QPixmap(icon_path)) + self.iconQLabel.setScaledContents(True) + + for w, c in text_colors.items(): + self._set_text_color(getattr(self, w), c) + + @staticmethod + def _set_text_color(widget, color): + widget.setStyleSheet("color: %s" % color) + + @staticmethod + def _set_label_text(label, text): + label.setText(text) + + def set_path_text(self, text): + self._set_label_text(self.textUpLeftLabel, text) + + def set_status_text(self, text): + self._set_label_text(self.textUpRightLabel, text) + + def set_team_left_text(self, text): + self._set_label_text(self.textDownLabelTeam1, text) + + def set_team_right_text(self, text): + self._set_label_text(self.textDownLabelTeam2, text) + + def set_icon(self, img_path): + self.iconQLabel.setPixmap(QtGui.QPixmap(img_path)) + + +class TabItem(QtWidgets.QWidget): + def __init__(self, parent=None, text='', img_path=''): + super(TabItem, self).__init__(parent) + self.textLabel = QtWidgets.QLabel() + self.iconLabel = QtWidgets.QLabel() + self.textLabel.setText(text) + self.iconLabel.setPixmap(QtGui.QPixmap(img_path)) + + self.layout = QtWidgets.QVBoxLayout() + self.layout.addWidget(self.iconLabel) + self.layout.addWidget(self.textLabel) + self.layout.setContentsMargins(0, 0, 0, 0) + self.setLayout(self.layout) + + +class PicTabItem(QtWidgets.QPushButton): + def __init__(self, parent=None, normal_img='', selected_img=''): + super(PicTabItem, self).__init__(parent) + self.normal_img = normal_img + self.selected_img = selected_img + self.setFixedSize(120, 90) + self.label = QtWidgets.QLabel() + self.label.setPixmap(QtGui.QPixmap(normal_img)) + #self.label.setMinimumSize(120, 90) + self.layout = QtWidgets.QGridLayout() + self.layout.addWidget(self.label) + self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.setSizeConstraint(4) + self.setLayout(self.layout) + + self.clicked.connect(self.button_clicked) + + def button_clicked(self): + print(self.normal_img) + + def to_selected_img(self): + self.label.setPixmap(QtGui.QPixmap(self.selected_img)) + + def to_normal_img(self): + self.label.setPixmap(QtGui.QPixmap(self.normal_img)) + + +class PicLabel(QtWidgets.QLabel): + def __init__(self, parent=None): + super(PicLabel, self).__init__(parent) + self.setStyleSheet("{background-color: rgb(14, 31, 51);}") + self.setGeometry(0, 0, 120, 67) + + +class TextLabel(QtWidgets.QLabel): + def __init__(self, parent=None, text=''): + super(TextLabel, self).__init__(parent) + self.setStyleSheet("{background-color: rgb(14, 31, 51);}") + self.setGeometry(0, 0, 120, 67) + self.setText(text) + + +class LineEdit(QtWidgets.QLineEdit): + def __init__(self, parent=None, text=''): + super(LineEdit, self).__init__(parent) + self.setStyleSheet() + + + +class ClickButton(QtWidgets.QPushButton): + def __init__(self, parent=None, text='', icon_path=None): + super(ClickButton, self).__init__(parent) + self.setFlat(True) + self.setText(text) + if icon_path: + self.setIcon(icon_path) + self.setIconSize(QSize(100, 100)) + + +class RadioButton(QtWidgets.QRadioButton): + def __init__(self): + super(RadioButton, self).__init__() + + +class CheckButton(QtWidgets.QCheckBox): + def __init__(self): + super(CheckButton, self).__init__() + + +class FullLabel(QtWidgets.QLabel): + def __init__(self): + super(FullLabel, self).__init__() + + def enterEvent(self, QEvent): + QEvent.accept() + + +def click_button(): + button = ClickButton() + + +class MainWindow(QtWidgets.QMainWindow): + def __init__(self): + super(MainWindow, self).__init__() + self.setWindowFlags(Qt.FramelessWindowHint) + self.setGeometry(50, 50, 1200, 800) + + +class _MyWindow(QtWidgets.QMainWindow): + def __init__(self): + super(_MyWindow, self).__init__() + self.listwidget = QtWidgets.QListWidget(self) + citem = VideoItem(self, '/path/1.mp4', 'WAITING', 'Shanghai Dragons', 'Dallas Fuel', 'replay.png') + item = QtWidgets.QListWidgetItem(self.listwidget) + item.setSizeHint(citem.sizeHint()) + self.listwidget.addItem(item) + self.listwidget.setItemWidget(item, citem) + self.setCentralWidget(self.listwidget) + + +if __name__ == '__main__': + app = QtWidgets.QApplication(sys.argv) + w = MainWindow() + w.show() + sys.exit(app.exec_()) + + + + diff --git a/ora/gui.py b/ora/gui_.py similarity index 100% rename from ora/gui.py rename to ora/gui_.py diff --git a/ora/overwatch.py b/ora/overwatch.py index 6855f34..cac0a2c 100644 --- a/ora/overwatch.py +++ b/ora/overwatch.py @@ -101,6 +101,7 @@ def get_ui_variable(name, gametype, version): WINSTON = "winston" ZARYA = "zarya" ZENYATTA = "zenyatta" +WRECKINGBALL = "wreckingball" MEKA = "meka" RIPTIRE = "riptire" @@ -116,6 +117,13 @@ def get_ui_variable(name, gametype, version): SYMMETRA, TORBJON, TRACER, WIDOWMAKER, WINSTON, ZARYA, ZENYATTA] +SUPPORT_LIST = [ANA, BRIGITTE, LUCIO, MERCY, MOIRA, ZENYATTA] + +DPS_LIST = [BASTION, DOOMFIST, GENJI, HANZO, JUNKRAT, MCCREE, MEI, PHARAH, REAPER, + SOLDIER76, SOMBRA, SYMMETRA, TORBJON, TRACER, WIDOWMAKER] + +TANK_LIST = [DVA, ORISA, REINHARDT, ROADHOG, WINSTON, ZARYA, WRECKINGBALL] + ASSIST_CHARACTER_LIST = [ANA, GENJI, JUNKRAT, MCCREE, MEI, MERCY, ORISA, REINHARDT, ROADHOG, SOMBRA, ZARYA, ZENYATTA] diff --git a/ora/stats/single_match_stats.py b/ora/stats/single_match_stats.py index feedf2f..dab1f5b 100644 --- a/ora/stats/single_match_stats.py +++ b/ora/stats/single_match_stats.py @@ -1,11 +1,349 @@ +# coding: utf-8 import os import sys import zipfile import json +from copy import deepcopy +from statistics import mean +import ora.overwatch as OW sys.path.append('../utils') from utils import stats +def time_to_frame(time): + pass + +class SingleTeamFightStats: + + def __init__(self, start_frame=0, end_frame=0): + + self.start_frame = start_frame + self.end_frame = end_frame + self.start_time = FramesData[start_frame]["time"] + self.end_time = FramesData[end_frame]["time"] + self.turning_point = 0 + self.ults_used_per_team = [0, 0] + self.ults_used_total = 0 + self.elims_list = [] + return + + def update(self): + pass + + def _update_elims_list(self): + total_team_tf_elim = 0 + starting_frame_ind = TeamfightStats.data[self.tf_index].start_frame + if self.tf_index == len(TeamfightStats.data) - 1: + ending_frame_ind = len(EventsData) - 1 + for frame_ind in range(starting_frame_ind, self.frame_index): + frame = EventsData[frame_ind] + for event in frame: + if event["action"] == "Eliminate" \ + and ((event['subject']['player_ind'] < 6 and self.index < 6) \ + or (event['subject']['player_ind'] >= 6 and self.index >= 6)): + total_team_tf_elim += 1 + self.tf_elim_index_list[-1] = total_team_tf_elim + 1 + + def _update_ults_used(self): + pass + + def _update_turning_point(self): + pass + + +class TeamfightStats: + + def __init__(self): + self.data = [] + return + + def _new_teamfight(self): + self.data.append(SingleTeamFightStats()) + return + + def _split_teamfights(self): + pass + + +class PlayerStats: + def __init__(self, index, frame_index = 0, tf_index = 0, prev_data = None): + if prev_data != None: + self = deepcopy(prev_data) + else: + self.num_charas_used = 0 + self.charas_list = [] + + self.elims = 0 + self.deaths = 0 + self.resurrects = 0 + self.resurrected = 0 + self.ult_charge = 0 + + self.tf_elims_list = [0] + self.tf_deaths_list = [0] + self.tf_elim_index_list = [0] + self.tf_death_index_list = [0] + self.avg_tf_elims = 0 + self.avg_tf_deaths = 0 + self.avg_tf_elim_index = 0 + self.avg_tf_death_index = 0 + + self.first_elims = 0 + self.first_elimed = 0 + + self.elims_per_10min = 0 + self.deaths_per_10min = 0 + self.dps_elims = 0 + self.elimed_by_dps = 0 + self.tank_elims = 0 + self.elimed_by_tank = 0 + self.healer_elims = 0 + self.elimed_by_healer = 0 + + self.critical_elims = 0 + self.ratio_critical_elim = 0 + + self.index = index + self.tf_index = tf_index + self.frame_index = frame_index + self.prev_data = prev_data + return + + def update(self): + self._update_charas() + self._update_elims() + self._update_deaths() + self._update_resurrects() + self._update_resurrected() + self._update_ult_charge() + + self._update_critical_elims() + + # 必须得在_update_elims和_update_critical_elims后面 + # 输出为%前的整数 不含% + self._update_ratio_critical_elim() + + # 得在_update_elims后执行 + self._update_elims_per_10min() + + # 得在_update_deaths后执行 + self._update_deaths_per_10min() + + self._update_dps_elims() + self._update_tank_elims() + self._update_support_elims() + self._update_elimed_by_dps() + self._update_elimed_by_tank() + self._update_elimed_by_support() + + self._update_tf() + self._update_avg_tf_elims() + self._update_avg_tf_deaths() + self._update_avg_elim_index() + self._update_avg_death_index() + + def _update_tf(self): + if self.tf_index != self.prev_data.tf_index: + self.tf_elims_list.append(0) + self.tf_deaths_list.append(0) + self.tf_elim_index_list.append(0) + self.tf_death_index_list.append(0) + + flag_first_elim = False + flag_first_death = False + + for event in EventsData[self.frame_index]: + if event["action"] == "Eliminate" and event['subject']['player_ind'] == self.index: + self.tf_elims_list[-1] += 1 + if tf_elim_index_list[-1] == 0: + flag_first_elim = True + if event["action"] == "Eliminate" and event['object']['player_ind'] == self.index: + self.tf_deaths_list[-1] += 1 + if tf_death_index_list[-1] == 0: + flag_first_elim = True + + # Go over previous frames, calculate team-wise elim/death index + if flag_first_elim: + total_team_tf_elim = 0 + starting_frame_ind = TeamfightStats.data[self.tf_index].start_frame + if self.tf_index == len(TeamfightStats.data) - 1: + ending_frame_ind = len(EventsData) - 1 + for frame_ind in range(starting_frame_ind, self.frame_index): + frame = EventsData[frame_ind] + for event in frame: + if event["action"] == "Eliminate" \ + and ((event['subject']['player_ind'] < 6 and self.index < 6) \ + or (event['subject']['player_ind'] >= 6 and self.index >= 6)): + total_team_tf_elim += 1 + self.tf_elim_index_list[-1] = total_team_tf_elim + 1 + + if flag_first_death: + total_team_tf_death = 0 + starting_frame_ind = TeamfightStats.data[self.tf_index].start_frame + if self.tf_index == len(TeamfightStats.data) - 1: + ending_frame_ind = len(EventsData) - 1 + for frame_ind in range(starting_frame_ind, self.frame_index): + frame = EventsData[frame_ind] + for event in frame: + if event["action"] == "Eliminate" \ + and ((event['object']['player_ind'] < 6 and self.index < 6) \ + or (event['oject']['player_ind'] >= 6 and self.index >= 6)): + total_team_tf_death += 1 + self.tf_death_index_list[-1] = total_team_tf_death + 1 + + def _update_avg_tf_elims(self): + self.avg_tf_elims = mean(self.tf_elims_list) + + def _update_avg_tf_deaths(self): + self.avg_tf_deaths = mean(self.tf_deaths_list) + + def _update_avg_elim_index(self): + self.avg_tf_elim_index = mean(self.tf_elim_index_list) + + def _update_avg_death_index(self): + self.avg_tf_death_index = mean(self.tf_death_index_list) + + def _update_charas(self): + curr_chara = FramesData[self.frame_index]["players"][self.index]["chara"] + if num_charas_used > 0: + if curr_chara != charas_list[-1]: + charas_list.append(curr_chara) + if ~(curr_chara in charas_list): + num_charas_used += 1 + else: + num_charas_used += 1 + charas_list.append(curr_chara) + + def _update_elims(self): + for event in EventsData[self.frame_index]: + if event["action"] == "Eliminate" and event['subject']['player_ind'] == self.index: + self.elims += 1 + + def _update_deaths(self): + for event in EventsData[self.frame_index]: + if event["action"] == "Eliminate" and event['object']['player_ind'] == self.index: + self.deaths += 1 + + def _update_resurrects(self): + for event in EventsData[self.frame_index]: + if event["action"] == "Resurrect" and event['subject']['player_ind'] == self.index: + self.resurrects += 1 + + def _update_resurrected(self): + for event in EventsData[self.frame_index]: + if event["action"] == "Resurrect" and event['object']['player_ind'] == self.index: + self.resurrected += 1 + + def _update_ult_charge(): + self.ult_charge = FramesData[self.frame_index]["players"][self.index]["ult_charge"] + + def _update_critical_elims(self): + for event in EventsData[self.frame_index]: + if event["action"] == "Eliminate" and event['subject']['player_ind'] == self.index \ + and elim['critical kill'] == 'Y': + self.critical_elims += 1 + + def _update_ratio_critical_elim(self): + self.ratio_critical_elim = self.critical_elims * 100 / self.elims + + def _update_elims_per_10min(self): + self.elims_per_10min = self.elims * 10 * 60/self.time + + def _update_deaths_per_10min(self): + self.deaths_per_10min = self.deaths * 10 * 60/self.time + + def _update_dps_elims(self): + for event in EventsData[self.frame_index]: + if event["action"] == "Eliminate" and event['subject']['player_ind'] == self.index\ + and event['subject']['chara'] in OW.DPS_LIST: + self.dps_elims += 1 + + def _update_tank_elims(self): + for event in EventsData[self.frame_index]: + if event["action"] == "Eliminate" and event['subject']['player_ind'] == self.index\ + and event['subject']['chara'] in OW.TANK_LIST: + self.tank_elims += 1 + + def _update_support_elims(self): + for event in EventsData[self.frame_index]: + if event["action"] == "Eliminate" and event['subject']['player_ind'] == self.index\ + and event['subject']['chara'] in OW.SUPPORT_LIST: + self.support_elims += 1 + + def _update_elimed_by_dps(self): + for event in EventsData[self.frame_index]: + if event["action"] == "Eliminate" and event['object']['player_ind'] == self.index\ + and event['subject']['chara'] in OW.DPS_LIST: + self.elimed_by_dps += 1 + + def _update_elimed_by_tank(self): + for event in EventsData[self.frame_index]: + if event["action"] == "Eliminate" and event['object']['player_ind'] == self.index\ + and event['subject']['chara'] in OW.TANK_LIST: + self.elimed_by_tank += 1 + + def _update_elimed_by_support(self): + for event in EventsData[self.frame_index]: + if event["action"] == "Eliminate" and event['object']['player_ind'] == self.index\ + and event['subject']['chara'] in OW.SUPPORT_LIST: + self.elimed_by_healer += 1 + +class BasicStats: + + def __init__(self): + self.first_elims = [0, 0] # [team1, team2] + self.normal_elims = [0, 0] + self.immediate_elims = [0, 0] + self.ability_elims = [0, 0] + self.ult_elims = [0, 0] + + self.first_elim_tf_win_ratio = [0, 0] + self.player_with_most_first_elims = [0, 0] + + self.total_ults_used = 0 + self.total_ults_used_per_tf = 0 + self.global_avg_elims_per_ult = 0 + self.avg_elims_per_ult = [0, 0] + + self.player_with_most_elims = [0, 0, 0, 0] # [player_ind_of_team1, #_of_elims_of_team1, player_ind_of_team2, #_of_elims_of_team2] + self.chara_with_most_elims = [0, 0, 0, 0] # [chara_ind_of_team1, #_of_elims_of_team1, chara_ind_of_team2, #_of_elims_of_team2] + + self.player_with_most_deaths = [0, 0, 0, 0] + self.chara_with_most_deaths = [0, 0, 0, 0] + + self.player_with_least_deaths = [0, 0, 0, 0] + self.chara_with_least_deaths = [0, 0, 0, 0] + + return + + + + +class FrameStats: + def __init__(self, frame, last_frame_data=None): + self.frame = frame + if last_frame_data: + self.last_frame_data = deepcopy(last_frame_data) + self.players = self.last_frame_data['players'] + self.time = self.last_frame_data['time'] + return + + self.last_frame_data = None + self.players = [] + for i in range(12): + self.players.append(PlayerStats(i)) + + self.basics = BasicStats() + self.time = 0 + self.tf_index = 0 # index of current teamfight + + def _update_player(self, player): + + + + + + class SingleMatchStats: """Class of a SingleMatchStats object. @@ -36,54 +374,164 @@ def __init__(self, zip_path): Returns: None - """ archive = zipfile.ZipFile(zip_path, 'r') - self.data_metainfo = json.loads(archive.read('metainfo.json')) - self.data_frames = json.loads(archive.read('frames.json')) - self.data_sheet1 = json.loads(archive.read('data_sheet1.json')) - self.data_sheet2 = json.loads(archive.read('data_sheet2.json')) - self.data_sheet3 = json.loads(archive.read('data_sheet3.json')) - self.elims = self.get_eliminations() - self.teamfight_separations = self.get_teamfight_separations() - self.teamfight = self.get_all_teamfight() - - def get_eliminations(self, start_time=0, end_time=0): + self._data_metainfo = json.loads(archive.read('metainfo.json')) + self._data_frames = json.loads(archive.read('frames.json')) + self._data_sheet1 = json.loads(archive.read('data_sheet1.json')) + self._data_sheet2 = json.loads(archive.read('data_sheet2.json')) + self._data_sheet3 = json.loads(archive.read('data_sheet3.json')) + self._elims = self.get_eliminations() + self._resurrects = self.get_resurrects() + self._suicides = self.get_suicides() + self._demechs = self.get_demechs() + self._teamfight_separations = self.get_teamfight_separations() + self._teamfight = self.get_all_teamfight() + + + self.players_arr = self.get_init_player_basic_data() + + def add_new_frame(self): + + # All data contained in one flame includes: + # Players (12 in total), CURRENT teamfight data, CURRENT global data + player = { + "index": 0, + 'num_charas_used': 0, + + "elims": 0, + "deaths": 0, + "resurrects": 0, + "resurrected": 0, + "ult_charge": 0, + + "avg_elims_during_tf": 0, + "avg_deaths_during_tf": 0, + + "avg_elim_ind_during_tf": 0, + "avg_death_ind_during_tf": 0, + + "elims_per_10min": 0, + "deaths_per_10min": 0, + + "dps_elims": 0, + "elimed_by_dps": 0, + + "tank_elims": 0, + "elimed_by_tank": 0, + + "healer_elims": 0, + "elimed_by_healer": 0, + + "first_elims": 0, + "first_elimed": 0, + + "critical_elims": 0, + "ratio_critical_elim": 0, + + "immediate_elimed": 0, + "ult_elims": 0 + } + + basics = { + "first_elims": [0, 0], + "normal_elims": [0, 0], + "immediate_elims": [0, 0], + "ability_elims": [0, 0], + "ult_elims": [0, 0], + + "first_elim_tf_win_ratio": [0, 0], + "player_with_most_first_elims": [0, 0], + + "total_ults_used": 0, + "total_ults_used_per_tf": 0, + "global_avg_elims_per_ult": 0, + "avg_elims_per_ult": [0, 0], + + "player_with_most_elims": [0, 0, 0, 0], + "chara_with_most_elims": [0, 0, 0, 0], + + "player_with_most_deaths": [0, 0, 0, 0], + "chara_with_most_deaths": [0, 0, 0, 0], + + "player_with_least_deaths": [0, 0, 0, 0], + "chara_with_least_deaths": [0, 0, 0, 0] + } + + + + teamfight_data = { + "start_time": 0, + "end_time": 0, + "turning_point": 0, + + "ults_used_per_team": [0, 0], + "ults_used_total": 0, + "elims_list": [ + [{ + "subject_player": "PlayerA", + "subject_chara": "Tracer", + "subject_index": 1, + "global_elim_ind": 0, + "team_elim_ind": 0, + "time": 21.5 + }, + { + "object_player": "PlayerB", + "object_chara": "Tracer", + "object_index": 7, + "global_elimed_ind": 0, + "team_elimed_ind": 0, + "time": 21 + }, + { + "object_player": "PlayerC", + "object_chara": "Zenyatta", + "object_index": 9, + "global_elimed_ind": 1, + "team_elimed_ind": 1, + "time": 22 + } + ], + [{ + "subject_player": "PlayerD", + "subject_chara": "Roadhog", + "subject_index": 8, + "global_elim_ind": 2, + "team_elim_ind": 1, + "time": 28 + }, + { + "object_player": "PlayerE", + "object_chara": "Mccree", + "object_index": 2, + "global_elimed_ind": 2, + "team_elimed_ind": 1, + "time": 28 + }], + + ], + } + + + def get_eliminations(self, start_frame=0, end_frame=0): """Get all eliminatins in a given time range. - If start_time == 0 and end_time == 0, return full elim list. + If start_frame == 0 and end_frame == 0, return full elim list. Author: Appcell Args: - start_time: start of the time range given in seconds - end_time: end of the time range given in seconds + start_frame: start of the time range given in seconds + end_frame: end of the time range given in seconds Returns: A list of all eliminatins in a given time range. """ - res = [] - if end_time == 0 and start_time == 0: - for event in self.data_sheet1: - if event['action'] == 'Eliminate': - time_arr = event['time'].split(':') - curr_time = stats.hms_to_seconds(time_arr[0], - time_arr[1], time_arr[2]) - event['time'] = curr_time - res.append(event) - else: - for event in self.data_sheet1: - time_arr = event['time'].split(':') - curr_time = stats.hms_to_seconds(time_arr[0], - time_arr[1], time_arr[2]) - if curr_time >= start_time and curr_time >= end_time \ - and self.event['action'] == 'Eliminate': - event['time'] = curr_time - res.append(event) - return res + return self._get_actions('Eliminate',start_frame,end_frame) - def get_eliminations_incremented(self, data, start_time, end_time): + def get_eliminations_incremented(self, data, start_frame, end_frame): """Append eliminations in given time range onto existed array Author: @@ -91,19 +539,115 @@ def get_eliminations_incremented(self, data, start_time, end_time): Args: data: list of given elimination data - start_time: start of the time range given in seconds - end_time: end of the time range given in seconds + start_frame: start of the time range given in seconds + end_frame: end of the time range given in seconds Returns: A list of all eliminatins in a given time range appended to original list. """ - return data.append(self.get_eliminations(start_time, end_time)) + return self._get_actions('Eliminate',start_frame,end_frame,data) + def get_resurrects(self, start_frame=0, end_frame=0): + """Get all resurrects in a given time range. + + If start_frame == 0 and end_frame == 0, return full resurrect list. + + Author: + ForYou + + Args: + start_frame: start of the time range given in seconds + end_frame: end of the time range given in seconds + + Returns: + A list of all resurrects in a given time range. + """ + return self._get_actions('Resurrect',start_frame,end_frame) + + def get_resurrects_incremented(self, data, start_frame, end_frame): + """Append resurrects in given time range onto existed array + + Author: + ForYou + + Args: + data: list of given resurrects data + start_frame: start of the time range given in seconds + end_frame: end of the time range given in seconds + + Returns: + A list of all resurrects in a given time range appended to + original list. + """ + return self._get_actions('Resurrect',start_frame,end_frame,data) + + def get_suicides(self, start_frame=0, end_frame=0): + """Get all suicides in a given time range. + + If start_frame == 0 and end_frame == 0, return full suicide list. + + Author: + ForYou + + Args: + start_frame: start of the time range given in seconds + end_frame: end of the time range given in seconds + + Returns: + A list of all suicides in a given time range. + """ + return self._get_actions('Suicide',start_frame,end_frame) + + def get_suicides_incremented(self, data, start_frame, end_frame): + """Append suicides in given time range onto existed array + + Author: + ForYou + + Args: + data: list of given suicides data + start_frame: start of the time range given in seconds + end_frame: end of the time range given in seconds + + Returns: + A list of all suicides in a given time range appended to original list. + """ + return self._get_actions('Suicide',start_frame,end_frame,data) + + def get_demechs(self, start_frame=0, end_frame=0): + """Get all demechs in a given time range. + If start_frame == 0 and end_frame == 0, return full demech list. + + Author: + ForYou + + Args: + start_frame: start of the time range given in seconds + end_frame: end of the time range given in seconds + + Returns: + A list of all demechs in a given time range. + """ + return self._get_actions('Demech',start_frame,end_frame) + + def get_demechs_incremented(self, data, start_frame, end_frame): + """Append demechs in given time range onto existed array + + Author: + ForYou + + Args: + data: list of given demechs data + start_frame: start of the time range given in seconds + end_frame: end of the time range given in seconds + + Returns: + A list of all demechs in a given time range appended to original list. + """ + return self._get_actions('Demech',start_frame,end_frame,data) def get_teamfight_index(self, time): """Get index of team-fight happening at given timestamp. - - A temporary standard for separating team-fights: Author: ForYou @@ -135,7 +679,8 @@ def get_teamfight_separations(self): None Returns: - A list of all timestamps marking separations between each teamfight. + A list of all timestamps marking separations between each + teamfight. """ res = [0] if len(self.elims) > 1: @@ -147,18 +692,18 @@ def get_teamfight_separations(self): return res - def get_ults(self,start_time, end_time): - """输出时间段内的data_sheet3 + def get_ults(self,start_frame, end_frame): + """Output all data in sheet3 in a given timerange. Author: maocili Args: - start_time : 开始时间 - end_time : 结束时间 + start_frame: start time + end_frame: end time Returns: - res : 包含data_sheet3的list + All data in sheet3 from start_frame to end_frame """ res=[] @@ -169,61 +714,56 @@ def get_ults(self,start_time, end_time): curr_time = stats.hms_to_seconds(time_arr[0], time_arr[1], time_arr[2]) data['time'] = curr_time - if start_time <= data['time'] and end_time >= data['time']: + if start_frame <= data['time'] and end_frame >= data['time']: res.append(data) - elif end_time <= data['time']: + elif end_frame <= data['time']: break return res - def get_arr_varitation(self, start_time, end_time): + def get_arr_varitation(self, start_frame, end_frame): """根据self.data_sheet3 输出时间段中的大招能量变化 Author: maocili - Args: - start_time : 开始时间 - end_time : 结束时间 - + start_frame : 开始时间 + end_frame : 结束时间 Returns: res : 包含大招能量变化的dict """ res = { - 'start_time': start_time, - 'end_time': end_time, + 'start_frame': start_frame, + 'end_frame': end_frame, 'ult_charge': [] } - start = self.get_ults(start_time,start_time) - end = self.get_ults(end_time,end_time) + start = self.get_ults(start_frame,start_frame) + end = self.get_ults(end_frame,end_frame) for i in end: for index,player in enumerate(i['players']): end[0]['players'][index]['ults'] = end[0]['players'][index]['ults'] - start[0]['players'][index]['ults'] - end[0]['time'] = [start_time,end_time] + end[0]['time'] = [start_frame,end_frame] return end - def get_ult_vary(self, data, start_time, end_time): + def get_ult_vary(self, data, start_frame, end_frame): """在data的基础上加上新时间段的变化 - Author: maocili - Args: data : 时间段的大招能量变化 - start_time : 开始时间 - end_time : 结束时间 - + start_frame : 开始时间 + end_frame : 结束时间 Returns: res : 两个时间段的大招能量变化 """ - new_data= self.get_arr_varitation(start_time,end_time)[0] + new_data= self.get_arr_varitation(start_frame,end_frame)[0] for index,player in enumerate(new_data['players']): new_data['players'][index]['ults'] = new_data['players'][index]['ults'] + data[0]['players'][index]['ults'] @@ -260,7 +800,7 @@ def get_all_eliminations(self): return len(self.elims) def get_all_deaths(self): - """get the total deaths + """Get # of total deaths Author: ngc7293 @@ -282,7 +822,7 @@ def get_all_deaths(self): return death_num def get_most_elim_player(self): - """get the most eliminations players + """Get the most eliminations players Author: ngc7293 @@ -313,8 +853,8 @@ def get_most_elim_player(self): return res - def get_most_kda_player(self): - """get the most kd-rate players + def get_highest_kd_player(self): + """Get the player with highest kd ratio Author: ngc7293 @@ -323,7 +863,7 @@ def get_most_kda_player(self): None Returns: - str: the player have most kd-rate + str: name of the player with highest kd ratio """ player_dict = {} for events in self.data_sheet1: @@ -348,41 +888,41 @@ def get_most_kda_player(self): player_dict[player]['dead'] = 1 mkp_dict[player] = player_dict[player]['elim']/player_dict[player]['dead'] - return max(mkp_dict,key=mkp_dict.get) + return max(mkp_dict, key=mkp_dict.get) def get_all_teamfight(self): - """get the teamfight in whole game + """Get a list of all teamfights in full match Author: ngc7293 Args: None - + Returns: - list: the list of all teamfights + list: a list of all teamfights """ if self.teamfight_separations == [0]: sep = [0,self.get_total_time()] res = [] - start_time_target = 0 - end_time_target = 1 + start_frame_target = 0 + end_frame_target = 1 one_fight = [] for event in self.elims: if event['action'] == 'Eliminate' and \ - event['time'] >= sep[start_time_target] and \ - event['time'] <= sep[end_time_target]: + event['time'] >= sep[start_frame_target] and \ + event['time'] <= sep[end_frame_target]: one_fight.append(event) - elif event['time'] >= sep[end_time_target]: - start_time_target += 1 - end_time_target += 1 + elif event['time'] >= sep[end_frame_target]: + start_frame_target += 1 + end_frame_target += 1 res.append(one_fight) one_fight = [] res.append(one_fight) return res - def get_avgtime_teamfight(self): - """get the avg teamfight time in whole game + def get_average_teamfight_time(self): + """Get the avg teamfight time computed across the whole game Author: ngc7293 @@ -391,13 +931,13 @@ def get_avgtime_teamfight(self): None Returns: - float: the avgtime of all teamfight + float: avg teamfight time """ fight_time = [fight[-1]['time']-fight[0]['time']+7 for fight in self.teamfight] return sum(fight_time)/len(fight_time) - def get_count_teamfight(self): - """get the teamfights' count + def get_totalnum_teamfight(self): + """Get the teamfights' count Author: ngc7293 @@ -406,6 +946,542 @@ def get_count_teamfight(self): None Returns: - int: the count of teamfight + int: the # of teamfight """ - return len(self.teamfight) \ No newline at end of file + return len(self.teamfight) + + def _get_actions(self, action, start_frame = 0, end_frame = 0, data = None): + """Get action list via "action" key-value pairs in events list. + + Author: + ForYou + + Args: + action: action type + start_frame: start of the time range given in seconds + end_frame: end of the time range given in seconds + + Returns: + List of all actions + """ + res = data if data else [] + if end_frame == 0 and start_frame == 0: + for event in self.data_sheet1: + if event['action'] == action: + time_arr = event['time'].split(':') + curr_time = stats.hms_to_seconds( + time_arr[0], time_arr[1], time_arr[2]) + event['frame_ind'] = curr_time + res.append(event) + else: + for event in self.data_sheet1: + time_arr = event['time'].split(':') + curr_time = stats.hms_to_seconds(time_arr[0], + time_arr[1], time_arr[2]) + if curr_time >= start_frame and curr_time >= end_frame \ + and self.event['action'] == action: + event['time'] = curr_time + res.append(event) + return res + + + + def get_init_player_basic_data(self): + """ Initialize data according to data_sheet2 + + Author: + ForYou + + Args: + None + + Returns: + players_arr:以选手player为单位的数组,共12个,每个player中包括以下内容: + team:队伍名称 + team_status:队伍进攻或防守 + index:编号从左往右 + name:选手id + starting lineup:首先使用的英雄 + final lineup:最后使用的英雄 + totalhero:使用的英雄数量 + + totalEliminate:击杀总数(不含助攻即最后一击) + totalEliminateDetail:击杀来源(数组) + chara:英雄名 + num:击杀次数 + totalassist:助攻总数 + totalassistDetail:助攻来源(数组) + chara:英雄名 + num:助攻次数 + totalResurrect:复活总数 + totaldie:死亡总数 + totaldieDetail:死亡来源(数组) + chara:英雄名 + num:死亡次数 + totalcritical die:被爆头击杀总数 + totalSuicide:自杀总数 + totalResurrected:被复活总数 + totalcritical kill:爆头击杀总数 + totalassist die:被助攻总数(集火目标) + 机甲相关 + totalDemech:击杀机甲总数(不含助攻即最后一击) + totalDemechassist:助攻机甲总数 + totalDemechdie:机甲死亡总数 + totalDemechcritical die:机甲被爆头击杀总数 + totalDemechcritical kill:爆头击杀机甲总数 + totalDemechassist die:机甲被助攻总数(集火目标) + heros:(数组)使用的英雄明细如下 + chara:英雄名 + Eliminate:击杀总数(不含助攻即最后一击) + EliminateDetail:击杀来源(数组) + chara:英雄名 + num:击杀次数 + assist:助攻总数 + assistDetail:助攻来源(数组) + chara:英雄名 + num:助攻次数 + Resurrect:复活总数 + die:死亡总数 + dieDetail:死亡来源(数组) + chara:英雄名 + num:死亡次数 + critical die:被爆头击杀总数 + Suicide:自杀总数 + Resurrected:被复活总数 + critical kill:爆头击杀总数 + assist die:被助攻总数(集火目标) + 机甲相关 + Demech:击杀机甲总数(不含助攻即最后一击) + Demechassist:助攻机甲总数 + Demechdie:机甲死亡总数 + Demechcritical die:机甲被爆头击杀总数 + Demechcritical kill:爆头击杀机甲总数 + Demechassist die:机甲被助攻总数(集火目标) + """ + heros = { + 'chara':'', + 'Eliminate':0, + 'assist':0, + 'Resurrect':0, + 'die':0, + 'Suicide':0, + 'Resurrected':0, + 'critical die':0, + 'critical kill':0, + 'assist die':0, + 'EliminateDetail':[], + 'assistDetail':[], + 'dieDetail':[]} + + total = { + 'totalhero':1, + 'totalEliminate':0, + 'totalassist':0, + 'totalResurrect':0, + 'totaldie':0, + 'totalSuicide':0, + 'totalResurrected':0, + 'totalcritical die':0, + 'totalcritical kill':0, + 'totalassist die':0, + 'totalDemech':0, + 'totalDemechassist':0, + 'totalDemechdie':0, + 'totalDemechcritical die':0, + 'totalDemechcritical kill':0, + 'totalDemechassist die':0, + 'totalEliminateDetail':[], + 'totalassistDetail':[], + 'totaldieDetail':[]} + + players_arr = [] + + for teamindex, teamvalue in enumerate(self.data_sheet2): + for player in teamvalue['players']: + hero = player['starting lineup'] + heros['chara'] = hero + player['heros'] = [] + player['heros'].append(heros) + player['team'] = teamvalue['team'] + player['team_status'] = teamvalue['team_status'] + for key in total: + player[key] = total[key] + players_arr.append(player) + + return players_arr + + + def get_player_basic_eliminates(self, start_frame=0, end_frame=0, data=None): + """获取在原数据基础上返回固定时间段内击杀相关数据粗加工 + + Author: + ForYou + + Args: + start_frame开始时间end_frame结束时间data原数据 + + Returns: + 同get_init_player_basic_data()返回值 + """ + + #提供了data原有数据就在原有数据累加,没有提供则在初始化的选手信息self.players_arr上添加 + if data == 0: + players_arr = self.players_arr + else: + players_arr = data + + #提供了开始和结束时间则取时间内的击杀事件加工,没有提供则默认全部击杀事件 + if end_frame == 0 and start_frame == 0: + eliminations = self.elims + else: + eliminations = self.get_eliminations(start_frame, end_frame) + + heros = { + 'chara':'', + 'Eliminate':0, + 'assist':0, + 'Resurrect':0, + 'die':0, + 'Suicide':0, + 'Resurrected':0, + 'critical die':0, + 'critical kill':0, + 'assist die':0, + 'EliminateDetail':[], + 'assistDetail':[], + 'dieDetail':[]} + + #遍历提供的击杀事件,从其中提取信息 + for elim_ind, elim in enumerate(eliminations): + #遍历选手列表,找到信息对应的选手,循环中主要处理三类信息 1:击杀者 2:助攻 3:被击杀者 + for player_ind, player in enumerate(players_arr): + # 1:击杀者相关信息(1a 总击杀 1b爆头总击杀 1c 总击杀明细 1d 使用英雄击杀相关) + if player['name'] == elim['subject']['player']: + # 1a 总击杀 + players_arr[player_ind]['totalEliminate'] += 1 + # 1b 爆头总击杀 + if elim['critical kill'] == 'Y': + players_arr[player_ind]['totalcritical kill'] += 1 + # 1c 总击杀明细 (敌方哪个英雄 几次) + hero_eliminate = 0 + for obj_hero_index , obj_hero_value in enumerate(player['totalEliminateDetail']): + if obj_hero_value['chara'] == elim['object']['chara']: + hero_eliminate = 1 + players_arr[player_ind]['totalEliminateDetail'][obj_hero_index]['num'] += 1 + break + # 击杀明细列表中 无此英雄则新增 + if hero_eliminate == 0: + newhero_eliminate = {'chara':elim['object']['chara'],'num':1} + players_arr[player_ind]['totalEliminateDetail'].append(newhero_eliminate) + # 1d 使用英雄击杀相关 + heroisexsit = 0 + #遍历已使用英雄 + for used_hero_index, used_hero_value in enumerate(player['heros']): + #和选手总击杀类似,这里处理选手不同英雄的击杀数据 + if used_hero_value['chara'] == elim['subject']['chara']: + heroisexsit = 1 + players_arr[player_ind]['heros'][used_hero_index]['Eliminate'] += 1 + if elim['critical kill'] == 'Y': + players_arr[player_ind]['heros'][used_hero_index]['critical kill'] += 1 + hero_eliminate = 0 + #击杀敌方英雄详情 + for hero_action_detail_index , hero_action_detail_value in enumerate(player['heros']['EliminateDetail']): + if hero_action_detail_value['chara'] == elim['object']['chara']: + hero_eliminate = 1 + players_arr[player_ind]['heros'][used_hero_index]['EliminateDetail'][hero_action_detail_index]['num'] += 1 + break + if hero_eliminate == 0: + newhero_eliminate = {'chara':elim['object']['chara'],'num':1} + players_arr[player_ind]['heros'][used_hero_index]['EliminateDetail'].append(newhero_eliminate) + break + #未使用过此英雄则新增 + if heroisexsit == 0: + newhero = deepcopy(heros) + newhero['chara'] = elim['subject']['chara'] + newhero['Eliminate'] = 1 + newhero_eliminate = {'chara':elim['object']['chara'],'num':1} + newhero['EliminateDetail'].append(newhero_eliminate) + players_arr[player_ind]['totalhero'] += 1 + if elim['critical kill'] == 'Y': + newhero['critical kill'] = 1 + players_arr[player_ind]['heros'].append(newhero) + # 2:助攻相关信息 total_assist_die字段记录这次击杀事件造成几个助攻,累加在选手的assist die和totalassist die上,assist die/die 可以看出敌方队伍集火目标 + total_assist_die = 0 + for assistplayer in elim['assist']: + #遍历助攻列表 处理选手的 2a总助攻 2b总助攻明细 2c不同英雄助攻信息 + if player['name'] == assist[assistplayer]['player']: + total_assist_die += 1 + # 2a 总助攻 + players_arr[player_ind]['totalassist'] += 1 + # 2b 总助攻明细 + heroassist = 0 + for obj_hero_index , obj_hero_value in enumerate(player['totalassistDetail']): + if obj_hero_value['chara'] == elim['object']['chara']: + heroassist = 1 + players_arr[player_ind]['totalassistDetail'][obj_hero_index]['num'] += 1 + break + if heroassist == 0: + newheroassist = {'chara':elim['object']['chara'],'num':1} + players_arr[player_ind]['totalassistDetail'].append(newheroassist) + # 2c 遍历英雄更新助攻信息 + heroisexsit = 0 + for used_hero_index, used_hero_value in enumerate(player['heros']): + if used_hero_value['chara'] == assist[assistplayer]['hero']: + heroisexsit = 1 + players_arr[player_ind]['heros'][used_hero_index]['assist'] += 1 + #助攻击杀敌方英雄信息 + heroassist = 0 + for hero_action_detail_index , hero_action_detail_value in enumerate(player['heros']['assistDetail']): + if hero_action_detail_value['chara'] == elim['object']['chara']: + heroassist = 1 + players_arr[player_ind]['heros'][used_hero_index]['assistDetail'][hero_action_detail_index]['num'] += 1 + break + if heroassist == 0: + newheroassist = {'chara':elim['object']['chara'],'num':1} + players_arr[player_ind]['heros'][used_hero_index]['assistDetail'].append(newheroassist) + break + #使用英雄不存在则新增 + if heroisexsit == 0: + newhero = deepcopy(heros) + newhero['chara'] = assist[assistplayer]['hero'] + newhero['assist'] = 1 + newheroassist = {'chara':elim['object']['chara'],'num':1} + newhero['assistDetail'].append(newheroassist) + players_arr[player_ind]['totalhero'] += 1 + players_arr[player_ind]['heros'].append(newhero) + # 3:被击杀者信息(3a 总死亡 3b总被助攻 3c 总被爆头击杀 3d 被击杀明细 3e 被击杀英雄相关) + if player['name'] == elim['object']['player']: + #3a 总死亡 + players_arr[player_ind]['totaldie'] += 1 + #3b 总被助攻数 + players_arr[player_ind]['totalassist die'] += total_assist_die + #3c 总被爆头击杀数 + if elim['critical kill'] == 'Y': + players_arr[player_ind]['totalcritical die'] += 1 + #3d 被击杀明细 (造成击杀的敌方英雄构成及次数) + herodie = 0 + for obj_hero_index , obj_hero_value in enumerate(player['totaldieDetail']): + if obj_hero_value['chara'] == elim['subject']['chara']: + herodie = 1 + players_arr[player_ind]['totaldieDetail'][obj_hero_index]['num'] += 1 + break + if herodie == 0: + newherodie = {'chara':elim['subject']['chara'],'num':1} + players_arr[player_ind]['totaldieDetail'].append(newherodie) + #3e 被击杀英雄(死亡构成) 数据结构与总数据类似 + heroisexsit = 0 + for used_hero_index, used_hero_value in enumerate(player['heros']): + if used_hero_value['chara'] == elim['object']['chara']: + heroisexsit = 1 + players_arr[player_ind]['heros'][used_hero_index]['die'] += 1 + players_arr[player_ind]['heros'][used_hero_index]['assist die'] += total_assist_die + if elim['critical kill'] == 'Y': + players_arr[player_ind]['heros'][used_hero_index]['critical die'] += 1 + herodie = 0 + for hero_action_detail_index , hero_action_detail_value in enumerate(player['heros']['dieDetail']): + if hero_action_detail_value['chara'] == elim['subject']['chara']: + herodie = 1 + players_arr[player_ind]['heros'][used_hero_index]['dieDetail'][hero_action_detail_index]['num'] += 1 + break + if herodie == 0: + newherodie = {'chara':elim['subject']['chara'],'num':1} + players_arr[player_ind]['heros'][used_hero_index]['dieDetail'].append(newherodie) + break + #被击杀的英雄不存在则新增 + if heroisexsit == 0: + newhero = deepcopy(heros) + newhero['chara'] = elim['object']['chara'] + newhero['die'] = 1 + newherodie = {'chara':elim['subject']['chara'],'num':1} + newhero['dieDetail'].append(newherodie) + newhero['assist die'] = total_assist_die + players_arr[player_ind]['totalhero'] += 1 + if elim['critical kill'] == 'Y': + newhero['critical die'] = 1 + players_arr[player_ind]['heros'].append(newhero) + + def get_player_basic_resurrects(self, start_frame=0, end_frame=0, data=0): + ''' + 获取在原数据基础上返回固定时间段内复活相关数据粗加工 + Author: + ForYou + Args: + start_frame开始时间end_frame结束时间data原数据 + Returns: + 同get_init_player_basic_data()返回值 + ''' + #提供了data原有数据就在原有数据累加,没有提供则在初始化的选手信息self.players_arr上添加 + if data == 0: + players_arr = self.players_arr + else: + players_arr = data + #提供了开始和结束时间则取时间内的复活事件加工,没有提供则默认全部复活事件 + if end_frame == 0 and start_frame == 0: + resurrects = self.resurrects + else: + resurrects = self.get_resurrects(start_frame,end_frame) + heros = {'chara':'','Eliminate':0,'assist':0,'Resurrect':0,'die':0,'Suicide':0,'Resurrected':0,'critical die':0,'critical kill':0,'assist die':0,'EliminateDetail':[],'assistDetail':[],'dieDetail':[]} + #遍历提供的复活事件 + for index, value in enumerate(resurrects): + #遍历选手列表 更新 1 复活数据 2 被复活数据 + for player_ind, playervalue in enumerate(players_arr): + #复活数据 和击杀数据一样做了英雄分组 1是为了保持数据格式的一致性,2是防止出另外具有复活技能的英雄 + if playervalue['name'] == value['subject']['player']: + players_arr[player_ind]['totalResurrect'] += 1 + heroisexsit = 0 + for used_hero_index, used_hero_value in enumerate(playervalue['heros']): + if used_hero_value['chara'] == value['subject']['chara']: + heroisexsit = 1 + players_arr[player_ind]['heros'][used_hero_index]['Resurrect'] += 1 + break + if heroisexsit == 0: + newhero = deepcopy(heros) + newhero['chara'] = value['subject']['chara'] + newhero['Resurrect'] = 1 + players_arr[player_ind]['totalhero'] += 1 + players_arr[player_ind]['heros'].append(newhero) + #被复活数据 + if playervalue['name'] == value['object']['player']: + players_arr[player_ind]['totalResurrected'] += 1 + heroisexsit = 0 + for used_hero_index, used_hero_value in enumerate(playervalue['heros']): + if used_hero_value['chara'] == value['object']['chara']: + heroisexsit = 1 + players_arr[player_ind]['heros'][used_hero_index]['Resurrected'] += 1 + break + if heroisexsit == 0: + newhero = deepcopy(heros) + newhero['chara'] = value['object']['chara'] + newhero['Resurrected'] = 1 + players_arr[player_ind]['totalhero'] += 1 + players_arr[player_ind]['heros'].append(newhero) + + def get_player_basic_suicides(self,start_frame=0,end_frame=0,data=0): + ''' + 获取在原数据基础上返回固定时间段内自杀相关数据粗加工 + Author: + ForYou + Args: + start_frame开始时间end_frame结束时间data原数据 + Returns: + 同get_init_player_basic_data()返回值 + ''' + #提供了data原有数据就在原有数据累加,没有提供则在初始化的选手信息self.players_arr上添加 + if data == 0: + players_arr = self.players_arr + else: + players_arr = data + #提供了开始和结束时间则取时间内的自杀事件加工,没有提供则默认全部自杀事件 + if end_frame == 0 and start_frame == 0: + suicides = self.suicides + else: + suicides = self.get_suicides(start_frame,end_frame) + heros = {'chara':'','Eliminate':0,'assist':0,'Resurrect':0,'die':0,'Suicide':0,'Resurrected':0,'critical die':0,'critical kill':0,'assist die':0,'EliminateDetail':[],'assistDetail':[],'dieDetail':[]} + #遍历提供的自杀事件 + for index, value in enumerate(suicides): + #遍历选手列表 更新自杀数据 + for player_ind, playervalue in enumerate(players_arr): + if playervalue['name'] == value['object']['player']: + players_arr[player_ind]['totalSuicide'] += 1 + heroisexsit = 0 + for used_hero_index, used_hero_value in enumerate(playervalue['heros']): + if used_hero_value['chara'] == value['object']['chara']: + heroisexsit = 1 + players_arr[player_ind]['heros'][used_hero_index]['Suicide'] += 1 + break + if heroisexsit == 0: + newhero = deepcopy(heros) + newhero['chara'] = value['object']['chara'] + newhero['Suicide'] = 1 + players_arr[player_ind]['totalhero'] += 1 + players_arr[player_ind]['heros'].append(newhero) + + def get_player_basic_demechs(self,start_frame=0,end_frame=0,data=0): + ''' + 获取在原数据基础上返回固定时间段内机甲相关数据粗加工 + Author: + ForYou + Args: + start_frame开始时间end_frame结束时间data原数据 + Returns: + 同get_init_player_basic_data()返回值 + ''' + #提供了data原有数据就在原有数据累加,没有提供则在初始化的选手信息self.players_arr上添加 + if data == 0: + players_arr = self.players_arr + else: + players_arr = data + #提供了开始和结束时间则取时间内的击杀机甲事件加工,没有提供则默认全部击杀机甲事件 + if end_frame == 0 and start_frame == 0: + demechs = self.demechs + else: + demechs = self.get_suicides(start_frame,end_frame) + heros = {'chara':'','Eliminate':0,'assist':0,'Resurrect':0,'die':0,'Suicide':0,'Resurrected':0,'critical die':0,'critical kill':0,'assist die':0,'EliminateDetail':[],'assistDetail':[],'dieDetail':[]} + #遍历提供的击杀机甲事件 此函数与击杀事件get_player_basic_eliminates类似,注释会从简 + for index, value in enumerate(demechs): + #遍历选手列表 处理选手的 1 拆机甲 2 助攻拆机甲 3 被拆机甲 + for player_ind, playervalue in enumerate(players_arr): + # 1:拆机甲相关数据 拆机甲总数 爆头拆机甲总数 不同英雄拆机甲数据 + if playervalue['name'] == value['subject']['player']: + players_arr[player_ind]['totalDemech'] += 1 + if value['critical kill'] == 'Y': + players_arr[player_ind]['totalDemechcritical kill'] += 1 + heroisexsit = 0 + for used_hero_index, used_hero_value in enumerate(playervalue['heros']): + if used_hero_value['chara'] == value['subject']['chara']: + heroisexsit = 1 + players_arr[player_ind]['heros'][used_hero_index]['Demech'] += 1 + if value['critical kill'] == 'Y': + players_arr[player_ind]['heros'][used_hero_index]['Demechcritical kill'] += 1 + break + if heroisexsit == 0: + newhero = deepcopy(heros) + newhero['chara'] = value['subject']['chara'] + newhero['Demech'] = 1 + players_arr[player_ind]['totalhero'] += 1 + if value['critical kill'] == 'Y': + newhero['Demechcritical kill'] = 1 + players_arr[player_ind]['heros'].append(newhero) + #2:助攻拆机甲数据 遍历助攻列表 处理选手的 总助攻 不同英雄助攻信息 + total_demech_assistdie = 0 + for assistplayer in value['assist']: + if playervalue['name'] == assist[assistplayer]['player']: + total_demech_assistdie += 1 + players_arr[player_ind]['totalDemechassist'] += 1 + heroisexsit = 0 + for used_hero_index, used_hero_value in enumerate(playervalue['heros']): + if used_hero_value['chara'] == assist[assistplayer]['hero']: + heroisexsit = 1 + players_arr[player_ind]['heros'][used_hero_index]['Demechassist'] += 1 + break + if heroisexsit == 0: + newhero = deepcopy(heros) + newhero['chara'] = assist[assistplayer]['hero'] + newhero['Demechassist'] = 1 + players_arr[player_ind]['totalhero'] += 1 + players_arr[player_ind]['heros'].append(newhero) + #3:被拆机甲信息(总死亡 总被助攻 总被爆头击杀 被击杀英雄此处专指dva) + if playervalue['name'] == value['object']['player']: + players_arr[player_ind]['totalDemechdie'] += 1 + players_arr[player_ind]['totalDemechassist die'] += total_demech_assistdie + if value['critical kill'] == 'Y': + players_arr[player_ind]['totalDemechcritical die'] += 1 + heroisexsit = 0 + for used_hero_index, used_hero_value in enumerate(playervalue['heros']): + #判断存疑 如果机甲的chara是 meta 这里面需要修改为dva 如果不是meta则不需要修改 + if used_hero_value['chara'] == value['object']['chara']: + heroisexsit = 1 + players_arr[player_ind]['heros'][used_hero_index]['Demechdie'] += 1 + players_arr[player_ind]['heros'][used_hero_index]['Demechassist die'] += total_demech_assistdie + if value['critical kill'] == 'Y': + players_arr[player_ind]['heros'][used_hero_index]['Demechcritical die'] += 1 + break + if heroisexsit == 0: + newhero = deepcopy(heros) + newhero['chara'] = value['object']['chara'] + newhero['Demechdie'] = 1 + newhero['Demechassist die'] = total_demech_assistdie + players_arr[player_ind]['totalhero'] += 1 + if value['critical kill'] == 'Y': + newhero['Demechcritical die'] = 1 + players_arr[player_ind]['heros'].append(newhero) diff --git a/requirements.txt b/requirements.txt index 080c3ab..3e41708 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ scikit-image opencv-python openpyxl +requests \ No newline at end of file diff --git a/run_qt_gui.py b/run_qt_gui.py new file mode 100644 index 0000000..2213aff --- /dev/null +++ b/run_qt_gui.py @@ -0,0 +1,17 @@ +import sys +from PyQt5 import QtWidgets +from ora.gui.gui import MainUi + +sys._excepthook = sys.excepthook +def exception_hook(exctype, value, traceback): + sys._excepthook(exctype, value, traceback) + sys.exit(1) +sys.excepthook = exception_hook + +if __name__ == '__main__': + app = QtWidgets.QApplication(sys.argv) + with open('./ora/gui/style.qss') as qss: + app.setStyleSheet(qss.read()) + w = MainUi() + w.show() + sys.exit(app.exec_()) \ No newline at end of file