Spaces:
Runtime error
Runtime error
ango
commited on
Commit
·
970efde
1
Parent(s):
5825182
04.15 commit
Browse files- qt/app.py +21 -14
- qt/components/__init__.py +6 -11
- qt/components/config.py +22 -0
- qt/components/dashboard.py +1 -1
- qt/components/equipments.py +1 -2
- qt/scripts/config.py +86 -0
- qt/scripts/dashboard.py +4 -2
- qt/scripts/top.py +9 -4
- schools/bei_ao_jue/skills.py +13 -0
- utils/analyzer.py +18 -11
- utils/parser.py +13 -12
qt/app.py
CHANGED
@@ -4,7 +4,9 @@ import sys
|
|
4 |
from PySide6.QtGui import QIcon
|
5 |
|
6 |
from qt.components.top import TopWidget
|
|
|
7 |
from qt.scripts.top import top_script
|
|
|
8 |
from qt.components.equipments import EquipmentsWidget
|
9 |
from qt.scripts.equipments import equipments_script
|
10 |
from qt.components.consumables import ConsumablesWidget
|
@@ -18,8 +20,8 @@ from qt.scripts.recipes import recipes_script
|
|
18 |
from qt.components.dashboard import DashboardWidget
|
19 |
from qt.scripts.dashboard import dashboard_script
|
20 |
|
21 |
-
from PySide6.QtWidgets import QApplication, QMainWindow, QStyleFactory
|
22 |
-
|
23 |
|
24 |
|
25 |
class MainWindow(QMainWindow):
|
@@ -38,37 +40,42 @@ class MainWindow(QMainWindow):
|
|
38 |
layout = QVBoxLayout(self.central_widget)
|
39 |
|
40 |
self.top_widget = TopWidget()
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
self.bottom_widget = QWidget()
|
42 |
-
|
43 |
-
|
44 |
-
layout.addWidget(self.bottom_widget)
|
45 |
|
46 |
-
|
|
|
47 |
self.dashboard_widget = DashboardWidget()
|
48 |
-
bottom_layout.addWidget(self.
|
49 |
bottom_layout.addWidget(self.dashboard_widget, 1)
|
50 |
|
51 |
self.equipments_widget = EquipmentsWidget()
|
52 |
-
self.
|
53 |
self.consumable_widget = ConsumablesWidget()
|
54 |
-
self.
|
55 |
self.talents_widget = TalentsWidget()
|
56 |
-
self.
|
57 |
self.recipes_widget = RecipesWidget()
|
58 |
-
self.
|
59 |
|
60 |
parser = top_script(
|
61 |
-
self.top_widget, self.bottom_widget, self.dashboard_widget,
|
62 |
self.talents_widget, self.recipes_widget, self.equipments_widget, self.consumable_widget,
|
63 |
)
|
|
|
64 |
equipments = equipments_script(self.equipments_widget)
|
65 |
consumables = consumables_script(self.consumable_widget)
|
66 |
talents = talents_script(self.talents_widget)
|
67 |
recipes = recipes_script(self.recipes_widget)
|
68 |
dashboard_script(parser, self.dashboard_widget, talents, recipes, equipments, consumables)
|
69 |
|
70 |
-
self.bottom_widget.hide()
|
71 |
-
|
72 |
|
73 |
if __name__ == "__main__":
|
74 |
app = QApplication(sys.argv)
|
|
|
4 |
from PySide6.QtGui import QIcon
|
5 |
|
6 |
from qt.components.top import TopWidget
|
7 |
+
from qt.scripts.config import config_script
|
8 |
from qt.scripts.top import top_script
|
9 |
+
from qt.components.config import ConfigWidget
|
10 |
from qt.components.equipments import EquipmentsWidget
|
11 |
from qt.scripts.equipments import equipments_script
|
12 |
from qt.components.consumables import ConsumablesWidget
|
|
|
20 |
from qt.components.dashboard import DashboardWidget
|
21 |
from qt.scripts.dashboard import dashboard_script
|
22 |
|
23 |
+
from PySide6.QtWidgets import QApplication, QMainWindow, QStyleFactory
|
24 |
+
from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QTabWidget
|
25 |
|
26 |
|
27 |
class MainWindow(QMainWindow):
|
|
|
40 |
layout = QVBoxLayout(self.central_widget)
|
41 |
|
42 |
self.top_widget = TopWidget()
|
43 |
+
layout.addWidget(self.top_widget, 1)
|
44 |
+
|
45 |
+
self.config_widget = ConfigWidget()
|
46 |
+
layout.addWidget(self.config_widget, 1)
|
47 |
+
self.config_widget.hide()
|
48 |
+
|
49 |
self.bottom_widget = QWidget()
|
50 |
+
layout.addWidget(self.bottom_widget, 8)
|
51 |
+
self.bottom_widget.hide()
|
|
|
52 |
|
53 |
+
bottom_layout = QHBoxLayout(self.bottom_widget)
|
54 |
+
self.detail_widget = QTabWidget()
|
55 |
self.dashboard_widget = DashboardWidget()
|
56 |
+
bottom_layout.addWidget(self.detail_widget, 1)
|
57 |
bottom_layout.addWidget(self.dashboard_widget, 1)
|
58 |
|
59 |
self.equipments_widget = EquipmentsWidget()
|
60 |
+
self.detail_widget.addTab(self.equipments_widget, "配装")
|
61 |
self.consumable_widget = ConsumablesWidget()
|
62 |
+
self.detail_widget.addTab(self.consumable_widget, "消耗品")
|
63 |
self.talents_widget = TalentsWidget()
|
64 |
+
self.detail_widget.addTab(self.talents_widget, "奇穴")
|
65 |
self.recipes_widget = RecipesWidget()
|
66 |
+
self.detail_widget.addTab(self.recipes_widget, "秘籍")
|
67 |
|
68 |
parser = top_script(
|
69 |
+
self.top_widget, self.config_widget, self.bottom_widget, self.dashboard_widget,
|
70 |
self.talents_widget, self.recipes_widget, self.equipments_widget, self.consumable_widget,
|
71 |
)
|
72 |
+
config_script(parser, self.config_widget, self.equipments_widget)
|
73 |
equipments = equipments_script(self.equipments_widget)
|
74 |
consumables = consumables_script(self.consumable_widget)
|
75 |
talents = talents_script(self.talents_widget)
|
76 |
recipes = recipes_script(self.recipes_widget)
|
77 |
dashboard_script(parser, self.dashboard_widget, talents, recipes, equipments, consumables)
|
78 |
|
|
|
|
|
79 |
|
80 |
if __name__ == "__main__":
|
81 |
app = QApplication(sys.argv)
|
qt/components/__init__.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1 |
-
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel
|
2 |
-
|
3 |
-
from PySide6.QtWidgets import QComboBox, QRadioButton, QTextBrowser,
|
4 |
-
from PySide6.QtCore import Qt
|
5 |
|
6 |
|
7 |
class LabelWidget(QWidget):
|
@@ -143,20 +142,16 @@ class SpinWithLabel(LabelWidget):
|
|
143 |
|
144 |
|
145 |
class TextWithLabel(LabelWidget):
|
146 |
-
def __init__(self, label
|
147 |
super().__init__(label)
|
148 |
layout = QVBoxLayout(self)
|
149 |
|
150 |
-
|
151 |
-
self.text_browser = QTextEdit()
|
152 |
-
else:
|
153 |
-
self.text_browser = QTextBrowser()
|
154 |
|
155 |
layout.addWidget(self.label)
|
156 |
layout.addWidget(self.text_browser)
|
157 |
|
158 |
-
|
159 |
-
layout.addStretch()
|
160 |
|
161 |
def set_text(self, text):
|
162 |
self.text_browser.setText(text)
|
|
|
1 |
+
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel
|
2 |
+
from PySide6.QtWidgets import QAbstractItemView, QTableWidgetItem, QHeaderView, QListView
|
3 |
+
from PySide6.QtWidgets import QComboBox, QRadioButton, QTextBrowser, QLineEdit, QSpinBox, QListWidget, QTableWidget
|
|
|
4 |
|
5 |
|
6 |
class LabelWidget(QWidget):
|
|
|
142 |
|
143 |
|
144 |
class TextWithLabel(LabelWidget):
|
145 |
+
def __init__(self, label):
|
146 |
super().__init__(label)
|
147 |
layout = QVBoxLayout(self)
|
148 |
|
149 |
+
self.text_browser = QLineEdit()
|
|
|
|
|
|
|
150 |
|
151 |
layout.addWidget(self.label)
|
152 |
layout.addWidget(self.text_browser)
|
153 |
|
154 |
+
layout.addStretch()
|
|
|
155 |
|
156 |
def set_text(self, text):
|
157 |
self.text_browser.setText(text)
|
qt/components/config.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton
|
2 |
+
|
3 |
+
from qt.components import ComboWithLabel, TextWithLabel
|
4 |
+
|
5 |
+
|
6 |
+
class ConfigWidget(QWidget):
|
7 |
+
def __init__(self):
|
8 |
+
super().__init__()
|
9 |
+
layout = QVBoxLayout(self)
|
10 |
+
top_layout = QHBoxLayout()
|
11 |
+
layout.addLayout(top_layout)
|
12 |
+
bottom_layout = QHBoxLayout()
|
13 |
+
layout.addLayout(bottom_layout)
|
14 |
+
|
15 |
+
self.config_select = ComboWithLabel("Select")
|
16 |
+
top_layout.addWidget(self.config_select, 1)
|
17 |
+
self.load_config = QPushButton("Load")
|
18 |
+
bottom_layout.addWidget(self.load_config, 1)
|
19 |
+
self.config_name = TextWithLabel("Name")
|
20 |
+
top_layout.addWidget(self.config_name, 1)
|
21 |
+
self.save_config = QPushButton("Save")
|
22 |
+
bottom_layout.addWidget(self.save_config, 1)
|
qt/components/dashboard.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTabWidget
|
2 |
|
3 |
-
from qt.components import ComboWithLabel, SpinWithLabel,
|
4 |
from base.constant import SHIELD_BASE_MAP
|
5 |
|
6 |
|
|
|
1 |
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTabWidget
|
2 |
|
3 |
+
from qt.components import ComboWithLabel, SpinWithLabel, LabelWithLabel, TableWithLabel
|
4 |
from base.constant import SHIELD_BASE_MAP
|
5 |
|
6 |
|
qt/components/equipments.py
CHANGED
@@ -4,8 +4,7 @@ import os
|
|
4 |
from qt.constant import POSITION_MAP, STONES_POSITIONS, EQUIPMENTS_DIR, ENCHANTS_DIR, STONES_DIR, MAX_STONE_ATTR
|
5 |
from qt.constant import EMBED_POSITIONS, MAX_EMBED_LEVEL, MAX_STONE_LEVEL, SPECIAL_ENCHANT_POSITIONS
|
6 |
from qt.components import ComboWithLabel, RadioWithLabel, TableWithLabel
|
7 |
-
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QTabWidget
|
8 |
-
from PySide6.QtCore import Qt
|
9 |
|
10 |
|
11 |
class EquipmentWidget(QWidget):
|
|
|
4 |
from qt.constant import POSITION_MAP, STONES_POSITIONS, EQUIPMENTS_DIR, ENCHANTS_DIR, STONES_DIR, MAX_STONE_ATTR
|
5 |
from qt.constant import EMBED_POSITIONS, MAX_EMBED_LEVEL, MAX_STONE_LEVEL, SPECIAL_ENCHANT_POSITIONS
|
6 |
from qt.components import ComboWithLabel, RadioWithLabel, TableWithLabel
|
7 |
+
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QTabWidget
|
|
|
8 |
|
9 |
|
10 |
class EquipmentWidget(QWidget):
|
qt/scripts/config.py
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import os
|
3 |
+
|
4 |
+
from qt.components.config import ConfigWidget
|
5 |
+
from qt.components.equipments import EquipmentsWidget
|
6 |
+
from utils.parser import Parser
|
7 |
+
|
8 |
+
|
9 |
+
if not os.path.exists("config"):
|
10 |
+
CONFIG = {}
|
11 |
+
else:
|
12 |
+
CONFIG = json.load(open("config", encoding="utf-8"))
|
13 |
+
|
14 |
+
|
15 |
+
def config_script(parser: Parser, config_widget: ConfigWidget, equipments_widget: EquipmentsWidget):
|
16 |
+
def load_config():
|
17 |
+
config_name = config_widget.config_select.combo_box.currentText()
|
18 |
+
config = CONFIG.get(parser.school.school, {}).get(config_name, {})
|
19 |
+
|
20 |
+
for label, equipment in equipments_widget.items():
|
21 |
+
if 'equipment' not in config[label]:
|
22 |
+
continue
|
23 |
+
else:
|
24 |
+
index = equipment.equipment.combo_box.findText(config[label]['equipment'])
|
25 |
+
equipment.equipment.combo_box.setCurrentIndex(index)
|
26 |
+
|
27 |
+
equipment.strength_level.combo_box.setCurrentIndex(config[label]['strength_level'])
|
28 |
+
if 'enchant' in config[label]:
|
29 |
+
index = equipment.enchant.combo_box.findText(config[label]['enchant'])
|
30 |
+
equipment.enchant.combo_box.setCurrentIndex(index)
|
31 |
+
if 'special_enchant' in config[label]:
|
32 |
+
if equipment.special_enchant.radio_button.isChecked() != config[label]['special_enchant']:
|
33 |
+
equipment.special_enchant.radio_button.click()
|
34 |
+
if 'embed_levels' in config[label]:
|
35 |
+
for i, embed_level in enumerate(equipment.embed_levels):
|
36 |
+
embed_level.combo_box.setCurrentIndex(config[label]['embed_levels'][i])
|
37 |
+
if 'stone_level' in config[label]:
|
38 |
+
equipment.stone_level.combo_box.setCurrentIndex(config[label]['stone_level'])
|
39 |
+
if 'stone_attrs' in config[label]:
|
40 |
+
for i, stone_attr in enumerate(equipment.stone_attrs):
|
41 |
+
index = equipment.stone_attrs[i].combo_box.findText(config[label]['stone_attrs'][i])
|
42 |
+
stone_attr.combo_box.setCurrentIndex(index)
|
43 |
+
|
44 |
+
config_widget.config_name.text_browser.setText(config_name)
|
45 |
+
|
46 |
+
config_widget.load_config.clicked.connect(load_config)
|
47 |
+
|
48 |
+
def save_config():
|
49 |
+
config_name = config_widget.config_name.text_browser.text()
|
50 |
+
if parser.school.school not in CONFIG:
|
51 |
+
CONFIG[parser.school.school] = {}
|
52 |
+
if config_name not in CONFIG[parser.school.school]:
|
53 |
+
CONFIG[parser.school.school][config_name] = {}
|
54 |
+
config = CONFIG[parser.school.school][config_name]
|
55 |
+
|
56 |
+
for label, equipment in equipments_widget.items():
|
57 |
+
config[label] = {}
|
58 |
+
if not (text := equipment.equipment.combo_box.currentText()):
|
59 |
+
continue
|
60 |
+
else:
|
61 |
+
config[label]['equipment'] = text
|
62 |
+
config[label]['strength_level'] = equipment.strength_level.combo_box.currentIndex()
|
63 |
+
if equipment.enchant:
|
64 |
+
config[label]['enchant'] = equipment.enchant.combo_box.currentText()
|
65 |
+
if equipment.special_enchant:
|
66 |
+
config[label]['special_enchant'] = equipment.special_enchant.radio_button.isChecked()
|
67 |
+
if equipment.embed_levels:
|
68 |
+
config[label]['embed_levels'] = [
|
69 |
+
embed_level.combo_box.currentIndex() for embed_level in equipment.embed_levels
|
70 |
+
]
|
71 |
+
if equipment.stone_level:
|
72 |
+
config[label]['stone_level'] = equipment.stone_level.combo_box.currentIndex()
|
73 |
+
if equipment.stone_attrs:
|
74 |
+
config[label]['stone_attrs'] = [
|
75 |
+
stone_attr.combo_box.currentText() for stone_attr in equipment.stone_attrs
|
76 |
+
]
|
77 |
+
json.dump(CONFIG, open("config", "w", encoding="utf-8"), ensure_ascii=False)
|
78 |
+
|
79 |
+
config_choices = list(CONFIG.get(parser.school.school, {}))
|
80 |
+
if current_select := config_widget.config_select.combo_box.currentText():
|
81 |
+
default_index = config_choices.index(current_select)
|
82 |
+
else:
|
83 |
+
default_index = -1
|
84 |
+
config_widget.config_select.set_items(config_choices, default_index=default_index)
|
85 |
+
|
86 |
+
config_widget.save_config.clicked.connect(save_config)
|
qt/scripts/dashboard.py
CHANGED
@@ -33,8 +33,9 @@ def detail_content(detail):
|
|
33 |
damage_content = [
|
34 |
["命中伤害", f"{detail['damage']}"],
|
35 |
["会心伤害", f"{detail['critical_damage']}"],
|
36 |
-
["
|
37 |
-
["
|
|
|
38 |
]
|
39 |
gradient_content = [
|
40 |
[ATTR_TYPE_TRANSLATE[k], f"{round(v / detail['expected_damage'] * 100, 2)}%"]
|
@@ -109,6 +110,7 @@ def dashboard_script(parser: Parser,
|
|
109 |
detail_widget.skill_combo.set_items(skill_choices, default_index=default_index)
|
110 |
|
111 |
def set_status(_):
|
|
|
112 |
detail_widget = dashboard_widget.detail_widget
|
113 |
skill = detail_widget.skill_combo.combo_box.currentText()
|
114 |
status = detail_widget.status_combo.combo_box.currentText()
|
|
|
33 |
damage_content = [
|
34 |
["命中伤害", f"{detail['damage']}"],
|
35 |
["会心伤害", f"{detail['critical_damage']}"],
|
36 |
+
["期望伤害", f"{round(detail['expected_damage'], 2)}"],
|
37 |
+
["会心", f"{round(detail['critical_strike'] * 100, 2)}%"],
|
38 |
+
["期望会心", f"{round(detail['expected_critical_strike'] * 100, 2)}%"],
|
39 |
]
|
40 |
gradient_content = [
|
41 |
[ATTR_TYPE_TRANSLATE[k], f"{round(v / detail['expected_damage'] * 100, 2)}%"]
|
|
|
110 |
detail_widget.skill_combo.set_items(skill_choices, default_index=default_index)
|
111 |
|
112 |
def set_status(_):
|
113 |
+
set_detail(None)
|
114 |
detail_widget = dashboard_widget.detail_widget
|
115 |
skill = detail_widget.skill_combo.combo_box.currentText()
|
116 |
status = detail_widget.status_combo.combo_box.currentText()
|
qt/scripts/top.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
from PySide6.QtWidgets import QFileDialog, QWidget
|
2 |
|
3 |
from general.consumables import FOODS, POTIONS, WEAPON_ENCHANTS, SNACKS, WINES, SPREADS
|
|
|
4 |
from qt.components.consumables import ConsumablesWidget
|
5 |
from qt.components.dashboard import DashboardWidget
|
6 |
from qt.components.equipments import EquipmentsWidget
|
@@ -11,19 +12,22 @@ from qt.components.top import TopWidget
|
|
11 |
# from general.consumables import FOODS, POTIONS, WEAPON_ENCHANTS, SPREADS, SNACKS, WINES
|
12 |
# from general.gains.formation import FORMATIONS
|
13 |
from qt.constant import MAX_RECIPES, MAX_STONE_LEVEL
|
|
|
14 |
from utils.parser import Parser
|
15 |
|
16 |
|
17 |
-
def top_script(top_widget: TopWidget, config_widget:
|
18 |
-
talents_widget: TalentsWidget, recipes_widget: RecipesWidget,
|
19 |
-
equipments_widget: EquipmentsWidget, consumables_widget: ConsumablesWidget
|
20 |
-
):
|
21 |
parser = Parser()
|
22 |
|
23 |
def upload_logs():
|
24 |
file_name = QFileDialog(top_widget, "Choose File").getOpenFileName()
|
25 |
parser(file_name[0])
|
26 |
school = parser.school
|
|
|
|
|
|
|
27 |
""" Update dashboard """
|
28 |
record_index = list(parser.record_index)
|
29 |
dashboard_widget.fight_select.set_items(record_index)
|
@@ -78,6 +82,7 @@ def top_script(top_widget: TopWidget, config_widget: QWidget, dashboard_widget:
|
|
78 |
consumables_widget.spread.set_items([""] + SPREADS[school.major] + SPREADS[school.kind])
|
79 |
|
80 |
config_widget.show()
|
|
|
81 |
|
82 |
top_widget.upload_button.clicked.connect(upload_logs)
|
83 |
|
|
|
1 |
from PySide6.QtWidgets import QFileDialog, QWidget
|
2 |
|
3 |
from general.consumables import FOODS, POTIONS, WEAPON_ENCHANTS, SNACKS, WINES, SPREADS
|
4 |
+
from qt.components.config import ConfigWidget
|
5 |
from qt.components.consumables import ConsumablesWidget
|
6 |
from qt.components.dashboard import DashboardWidget
|
7 |
from qt.components.equipments import EquipmentsWidget
|
|
|
12 |
# from general.consumables import FOODS, POTIONS, WEAPON_ENCHANTS, SPREADS, SNACKS, WINES
|
13 |
# from general.gains.formation import FORMATIONS
|
14 |
from qt.constant import MAX_RECIPES, MAX_STONE_LEVEL
|
15 |
+
from qt.scripts.config import CONFIG
|
16 |
from utils.parser import Parser
|
17 |
|
18 |
|
19 |
+
def top_script(top_widget: TopWidget, config_widget: ConfigWidget, bottom_widget: QWidget,
|
20 |
+
dashboard_widget: DashboardWidget, talents_widget: TalentsWidget, recipes_widget: RecipesWidget,
|
21 |
+
equipments_widget: EquipmentsWidget, consumables_widget: ConsumablesWidget):
|
|
|
22 |
parser = Parser()
|
23 |
|
24 |
def upload_logs():
|
25 |
file_name = QFileDialog(top_widget, "Choose File").getOpenFileName()
|
26 |
parser(file_name[0])
|
27 |
school = parser.school
|
28 |
+
""" Update config """
|
29 |
+
config_choices = list(CONFIG.get(school.school, {}))
|
30 |
+
config_widget.config_select.set_items(config_choices, default_index=-1)
|
31 |
""" Update dashboard """
|
32 |
record_index = list(parser.record_index)
|
33 |
dashboard_widget.fight_select.set_items(record_index)
|
|
|
82 |
consumables_widget.spread.set_items([""] + SPREADS[school.major] + SPREADS[school.kind])
|
83 |
|
84 |
config_widget.show()
|
85 |
+
bottom_widget.show()
|
86 |
|
87 |
top_widget.upload_button.clicked.connect(upload_logs)
|
88 |
|
schools/bei_ao_jue/skills.py
CHANGED
@@ -59,6 +59,13 @@ SKILLS: Dict[int, Skill | dict] = {
|
|
59 |
[280],
|
60 |
"interval": 48
|
61 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
16933: {
|
63 |
"skill_class": PhysicalDamage,
|
64 |
"skill_name": "惊燕式",
|
@@ -299,6 +306,12 @@ SKILLS: Dict[int, Skill | dict] = {
|
|
299 |
"attack_power_cof": 380,
|
300 |
"interval": 48
|
301 |
|
|
|
|
|
|
|
|
|
|
|
|
|
302 |
}
|
303 |
}
|
304 |
|
|
|
59 |
[280],
|
60 |
"interval": 48
|
61 |
},
|
62 |
+
17060: {
|
63 |
+
"skill_class": Skill,
|
64 |
+
"skill_name": "闹须弥",
|
65 |
+
"bind_skill": 11447,
|
66 |
+
"tick": 8
|
67 |
+
|
68 |
+
},
|
69 |
16933: {
|
70 |
"skill_class": PhysicalDamage,
|
71 |
"skill_name": "惊燕式",
|
|
|
306 |
"attack_power_cof": 380,
|
307 |
"interval": 48
|
308 |
|
309 |
+
},
|
310 |
+
26934: {
|
311 |
+
"skill_class": Skill,
|
312 |
+
"skill_name": "背水沉舟",
|
313 |
+
"bind_skill": 19555,
|
314 |
+
"tick": 8
|
315 |
}
|
316 |
}
|
317 |
|
utils/analyzer.py
CHANGED
@@ -11,7 +11,7 @@ def filter_status(status, school: School, skill_id):
|
|
11 |
if buff.gain_attributes or skill_id in buff.gain_skills:
|
12 |
buffs.append(buff)
|
13 |
|
14 |
-
return buffs
|
15 |
|
16 |
|
17 |
def add_buffs(current_buffs, snapshot_buffs, attribute: Attribute, skill: Skill):
|
@@ -42,7 +42,6 @@ def analyze_details(record, duration: int, attribute: Attribute, school: School)
|
|
42 |
total_gradients = {attr: 0. for attr in attribute.grad_attrs}
|
43 |
duration *= 1000
|
44 |
|
45 |
-
existed_buffs = []
|
46 |
for skill, status in record.items():
|
47 |
skill_id, skill_level, skill_stack = skill
|
48 |
skill: Skill = school.skills[skill_id]
|
@@ -51,7 +50,13 @@ def analyze_details(record, duration: int, attribute: Attribute, school: School)
|
|
51 |
skill_detail = {}
|
52 |
details[skill.display_name] = skill_detail
|
53 |
for (current_status, snapshot_status), timeline in status.items():
|
54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
if not timeline:
|
56 |
continue
|
57 |
|
@@ -59,7 +64,7 @@ def analyze_details(record, duration: int, attribute: Attribute, school: School)
|
|
59 |
snapshot_buffs = filter_status(snapshot_status, school, skill_id)
|
60 |
add_buffs(current_buffs, snapshot_buffs, attribute, skill)
|
61 |
|
62 |
-
damage,
|
63 |
gradients = analyze_gradients(skill, attribute)
|
64 |
|
65 |
sub_buffs(current_buffs, snapshot_buffs, attribute, skill)
|
@@ -68,16 +73,18 @@ def analyze_details(record, duration: int, attribute: Attribute, school: School)
|
|
68 |
for attr, residual_damage in gradients.items():
|
69 |
total_gradients[attr] += residual_damage * len(timeline)
|
70 |
|
71 |
-
buffs =
|
72 |
-
|
73 |
-
|
|
|
74 |
if not buffs:
|
75 |
-
buffs = "
|
76 |
skill_detail[buffs] = dict(
|
77 |
damage=damage,
|
78 |
-
critical_strike=critical_strike,
|
79 |
critical_damage=critical_damage,
|
80 |
expected_damage=expected_damage,
|
|
|
|
|
81 |
# "timeline": [round(t / 1000, 3) for t in timeline],
|
82 |
count=len(timeline),
|
83 |
gradients=gradients
|
@@ -95,10 +102,10 @@ def analyze_summary(details):
|
|
95 |
for skill, skill_detail in details.items():
|
96 |
skill = skill.split("/")[0]
|
97 |
if skill not in summary:
|
98 |
-
summary[skill] = {"count": 0, "
|
99 |
for buff, detail in skill_detail.items():
|
100 |
summary[skill]["count"] += detail['count']
|
101 |
-
summary[skill]["critical"] += detail['count'] * detail['
|
102 |
summary[skill]["damage"] += detail['count'] * detail['expected_damage']
|
103 |
|
104 |
return summary
|
|
|
11 |
if buff.gain_attributes or skill_id in buff.gain_skills:
|
12 |
buffs.append(buff)
|
13 |
|
14 |
+
return tuple(buffs)
|
15 |
|
16 |
|
17 |
def add_buffs(current_buffs, snapshot_buffs, attribute: Attribute, skill: Skill):
|
|
|
42 |
total_gradients = {attr: 0. for attr in attribute.grad_attrs}
|
43 |
duration *= 1000
|
44 |
|
|
|
45 |
for skill, status in record.items():
|
46 |
skill_id, skill_level, skill_stack = skill
|
47 |
skill: Skill = school.skills[skill_id]
|
|
|
50 |
skill_detail = {}
|
51 |
details[skill.display_name] = skill_detail
|
52 |
for (current_status, snapshot_status), timeline in status.items():
|
53 |
+
hit_timeline, critical_timeline = [], []
|
54 |
+
for timestamp, critical in timeline:
|
55 |
+
if critical:
|
56 |
+
critical_timeline.append(timestamp)
|
57 |
+
else:
|
58 |
+
hit_timeline.append(timestamp)
|
59 |
+
timeline = [t for t in timeline if t[0] < duration]
|
60 |
if not timeline:
|
61 |
continue
|
62 |
|
|
|
64 |
snapshot_buffs = filter_status(snapshot_status, school, skill_id)
|
65 |
add_buffs(current_buffs, snapshot_buffs, attribute, skill)
|
66 |
|
67 |
+
damage, expected_critical_strike, critical_damage, expected_damage = skill(attribute)
|
68 |
gradients = analyze_gradients(skill, attribute)
|
69 |
|
70 |
sub_buffs(current_buffs, snapshot_buffs, attribute, skill)
|
|
|
73 |
for attr, residual_damage in gradients.items():
|
74 |
total_gradients[attr] += residual_damage * len(timeline)
|
75 |
|
76 |
+
buffs = ",".join(buff.display_name for buff in current_buffs)
|
77 |
+
if snapshot_buffs and current_buffs != snapshot_buffs:
|
78 |
+
buffs += f"({','.join(buff.display_name for buff in snapshot_buffs)})"
|
79 |
+
|
80 |
if not buffs:
|
81 |
+
buffs = "~"
|
82 |
skill_detail[buffs] = dict(
|
83 |
damage=damage,
|
|
|
84 |
critical_damage=critical_damage,
|
85 |
expected_damage=expected_damage,
|
86 |
+
critical_strike=len(critical_timeline) / (len(critical_timeline) + len(hit_timeline)),
|
87 |
+
expected_critical_strike=expected_critical_strike,
|
88 |
# "timeline": [round(t / 1000, 3) for t in timeline],
|
89 |
count=len(timeline),
|
90 |
gradients=gradients
|
|
|
102 |
for skill, skill_detail in details.items():
|
103 |
skill = skill.split("/")[0]
|
104 |
if skill not in summary:
|
105 |
+
summary[skill] = {"count": 0, "critical": 0, "damage": 0}
|
106 |
for buff, detail in skill_detail.items():
|
107 |
summary[skill]["count"] += detail['count']
|
108 |
+
summary[skill]["critical"] += detail['count'] * detail['expected_critical_strike']
|
109 |
summary[skill]["damage"] += detail['count'] * detail['expected_damage']
|
110 |
|
111 |
return summary
|
utils/parser.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
from dataclasses import dataclass
|
2 |
from typing import Dict, List, Type, Union, Tuple
|
|
|
3 |
|
4 |
from base.attribute import Attribute
|
5 |
from base.buff import Buff
|
@@ -124,8 +125,8 @@ class Parser:
|
|
124 |
self.records = []
|
125 |
self.status = {}
|
126 |
self.snapshot = {}
|
127 |
-
self.stacks =
|
128 |
-
self.ticks =
|
129 |
|
130 |
self.start_time = []
|
131 |
self.end_time = []
|
@@ -161,16 +162,16 @@ class Parser:
|
|
161 |
self.status[(buff_id, buff_level)] = buff_stack
|
162 |
|
163 |
def parse_skill(self, detail, timestamp):
|
164 |
-
skill_id, skill_level = detail[4], detail[5]
|
165 |
if skill_id not in self.school.skills:
|
166 |
return
|
167 |
|
168 |
-
|
169 |
-
|
170 |
-
if self.ticks
|
171 |
self.ticks[skill_id] -= 1
|
172 |
-
|
173 |
-
|
174 |
|
175 |
skill_tuple = (skill_id, skill_level, skill_stack)
|
176 |
skill = self.school.skills[skill_id]
|
@@ -181,10 +182,10 @@ class Parser:
|
|
181 |
else:
|
182 |
if skill_tuple not in self.current_record:
|
183 |
self.current_record[skill_tuple] = {}
|
184 |
-
|
185 |
-
if
|
186 |
-
self.current_record[skill_tuple][
|
187 |
-
self.current_record[skill_tuple][
|
188 |
|
189 |
def __call__(self, file_name):
|
190 |
self.reset()
|
|
|
1 |
from dataclasses import dataclass
|
2 |
from typing import Dict, List, Type, Union, Tuple
|
3 |
+
from collections import defaultdict
|
4 |
|
5 |
from base.attribute import Attribute
|
6 |
from base.buff import Buff
|
|
|
125 |
self.records = []
|
126 |
self.status = {}
|
127 |
self.snapshot = {}
|
128 |
+
self.stacks = defaultdict(int)
|
129 |
+
self.ticks = defaultdict(int)
|
130 |
|
131 |
self.start_time = []
|
132 |
self.end_time = []
|
|
|
162 |
self.status[(buff_id, buff_level)] = buff_stack
|
163 |
|
164 |
def parse_skill(self, detail, timestamp):
|
165 |
+
skill_id, skill_level, critical = detail[4], detail[5], detail[6]
|
166 |
if skill_id not in self.school.skills:
|
167 |
return
|
168 |
|
169 |
+
timestamp = int(timestamp) - self.start_time[-1]
|
170 |
+
skill_stack = max(1, self.stacks[skill_id])
|
171 |
+
if self.ticks[skill_id]:
|
172 |
self.ticks[skill_id] -= 1
|
173 |
+
if not self.ticks[skill_id]:
|
174 |
+
self.stacks[skill_id] = 0
|
175 |
|
176 |
skill_tuple = (skill_id, skill_level, skill_stack)
|
177 |
skill = self.school.skills[skill_id]
|
|
|
182 |
else:
|
183 |
if skill_tuple not in self.current_record:
|
184 |
self.current_record[skill_tuple] = {}
|
185 |
+
status_tuple = self.available_status(skill_id)
|
186 |
+
if status_tuple not in self.current_record[skill_tuple]:
|
187 |
+
self.current_record[skill_tuple][status_tuple] = []
|
188 |
+
self.current_record[skill_tuple][status_tuple].append((timestamp, critical))
|
189 |
|
190 |
def __call__(self, file_name):
|
191 |
self.reset()
|